mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-26 00:17:27 +00:00
Replace pinstretcher with Argon2 and new PIN encryption.
This commit is contained in:

committed by
Greyson Parrelli

parent
f7a3bb2ae8
commit
e37c4b1f87
@@ -2,6 +2,9 @@ package org.whispersystems.signalservice.api;
|
||||
|
||||
import org.whispersystems.libsignal.logging.Log;
|
||||
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
||||
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||
import org.whispersystems.signalservice.api.kbs.KbsData;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.KeyBackupCipher;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.Quote;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.RemoteAttestation;
|
||||
@@ -14,8 +17,7 @@ import org.whispersystems.signalservice.internal.keybackup.protos.BackupResponse
|
||||
import org.whispersystems.signalservice.internal.keybackup.protos.RestoreResponse;
|
||||
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
|
||||
import org.whispersystems.signalservice.internal.push.RemoteAttestationUtil;
|
||||
import org.whispersystems.signalservice.internal.registrationpin.InvalidPinException;
|
||||
import org.whispersystems.signalservice.internal.registrationpin.PinStretcher;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.SignalStorage;
|
||||
import org.whispersystems.signalservice.internal.util.Hex;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
||||
@@ -116,8 +118,13 @@ public final class KeyBackupService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationLockData restorePin(String pin)
|
||||
throws UnauthenticatedResponseException, IOException, KeyBackupServicePinException, InvalidPinException
|
||||
public byte[] hashSalt() {
|
||||
return currentToken.getBackupId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationLockData restorePin(HashedPin hashedPin)
|
||||
throws UnauthenticatedResponseException, IOException, KeyBackupServicePinException
|
||||
{
|
||||
int attempt = 0;
|
||||
SecureRandom random = new SecureRandom();
|
||||
@@ -128,7 +135,7 @@ public final class KeyBackupService {
|
||||
attempt++;
|
||||
|
||||
try {
|
||||
return restorePin(pin, token);
|
||||
return restorePin(hashedPin, token);
|
||||
} catch (TokenException tokenException) {
|
||||
|
||||
token = tokenException.getToken();
|
||||
@@ -149,15 +156,13 @@ public final class KeyBackupService {
|
||||
}
|
||||
}
|
||||
|
||||
private RegistrationLockData restorePin(String pin, TokenResponse token)
|
||||
throws UnauthenticatedResponseException, IOException, TokenException, InvalidPinException
|
||||
private RegistrationLockData restorePin(HashedPin hashedPin, TokenResponse token)
|
||||
throws UnauthenticatedResponseException, IOException, TokenException
|
||||
{
|
||||
PinStretcher.StretchedPin stretchedPin = PinStretcher.stretchPin(pin);
|
||||
|
||||
try {
|
||||
final int remainingTries = token.getTries();
|
||||
final RemoteAttestation remoteAttestation = getAndVerifyRemoteAttestation();
|
||||
final KeyBackupRequest request = KeyBackupCipher.createKeyRestoreRequest(stretchedPin.getKbsAccessKey(), token, remoteAttestation, Hex.fromStringCondensed(enclaveName));
|
||||
final KeyBackupRequest request = KeyBackupCipher.createKeyRestoreRequest(hashedPin.getKbsAccessKey(), token, remoteAttestation, Hex.fromStringCondensed(enclaveName));
|
||||
final KeyBackupResponse response = pushServiceSocket.putKbsData(authorization, request, remoteAttestation.getCookies(), enclaveName);
|
||||
final RestoreResponse status = KeyBackupCipher.getKeyRestoreResponse(response, remoteAttestation);
|
||||
|
||||
@@ -169,7 +174,8 @@ public final class KeyBackupService {
|
||||
switch (status.getStatus()) {
|
||||
case OK:
|
||||
Log.i(TAG, String.format(Locale.US,"Restore OK! data: %s tries: %d", Hex.toStringCondensed(status.getData().toByteArray()), status.getTries()));
|
||||
PinStretcher.MasterKey masterKey = stretchedPin.withPinKey2(status.getData().toByteArray());
|
||||
KbsData kbsData = hashedPin.decryptKbsDataIVCipherText(status.getData().toByteArray());
|
||||
MasterKey masterKey = kbsData.getMasterKey();
|
||||
return new RegistrationLockData(masterKey, nextToken);
|
||||
case PIN_MISMATCH:
|
||||
Log.i(TAG, "Restore PIN_MISMATCH");
|
||||
@@ -203,16 +209,15 @@ public final class KeyBackupService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistrationLockData setPin(String pin) throws IOException, UnauthenticatedResponseException, InvalidPinException {
|
||||
PinStretcher.MasterKey masterKey = PinStretcher.stretchPin(pin)
|
||||
.withNewSecurePinKey2();
|
||||
public RegistrationLockData setPin(HashedPin hashedPin, MasterKey masterKey) throws IOException, UnauthenticatedResponseException {
|
||||
KbsData newKbsData = hashedPin.createNewKbsData(masterKey);
|
||||
|
||||
TokenResponse tokenResponse = putKbsData(masterKey.getKbsAccessKey(),
|
||||
masterKey.getPinKey2(),
|
||||
TokenResponse tokenResponse = putKbsData(newKbsData.getKbsAccessKey(),
|
||||
newKbsData.getCipherText(),
|
||||
enclaveName,
|
||||
currentToken);
|
||||
|
||||
pushServiceSocket.setRegistrationLock(masterKey.getRegistrationLock());
|
||||
pushServiceSocket.setRegistrationLock(masterKey.deriveRegistrationLock());
|
||||
|
||||
return new RegistrationLockData(masterKey, tokenResponse);
|
||||
}
|
||||
@@ -264,16 +269,21 @@ public final class KeyBackupService {
|
||||
}
|
||||
}
|
||||
|
||||
public interface RestoreSession {
|
||||
public interface HashSession {
|
||||
|
||||
RegistrationLockData restorePin(String pin)
|
||||
throws UnauthenticatedResponseException, IOException, KeyBackupServicePinException, InvalidPinException;
|
||||
byte[] hashSalt();
|
||||
}
|
||||
|
||||
public interface PinChangeSession {
|
||||
public interface RestoreSession extends HashSession {
|
||||
|
||||
RegistrationLockData setPin(String pin)
|
||||
throws IOException, UnauthenticatedResponseException, InvalidPinException;
|
||||
RegistrationLockData restorePin(HashedPin hashedPin)
|
||||
throws UnauthenticatedResponseException, IOException, KeyBackupServicePinException;
|
||||
}
|
||||
|
||||
public interface PinChangeSession extends HashSession {
|
||||
|
||||
RegistrationLockData setPin(HashedPin hashedPin, MasterKey masterKey)
|
||||
throws IOException, UnauthenticatedResponseException;
|
||||
|
||||
void removePin()
|
||||
throws IOException, UnauthenticatedResponseException;
|
||||
|
@@ -1,19 +1,19 @@
|
||||
package org.whispersystems.signalservice.api;
|
||||
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
import org.whispersystems.signalservice.internal.registrationpin.PinStretcher;
|
||||
|
||||
public final class RegistrationLockData {
|
||||
|
||||
private final PinStretcher.MasterKey masterKey;
|
||||
private final TokenResponse tokenResponse;
|
||||
private final MasterKey masterKey;
|
||||
private final TokenResponse tokenResponse;
|
||||
|
||||
RegistrationLockData(PinStretcher.MasterKey masterKey, TokenResponse tokenResponse) {
|
||||
RegistrationLockData(MasterKey masterKey, TokenResponse tokenResponse) {
|
||||
this.masterKey = masterKey;
|
||||
this.tokenResponse = tokenResponse;
|
||||
}
|
||||
|
||||
public PinStretcher.MasterKey getMasterKey() {
|
||||
public MasterKey getMasterKey() {
|
||||
return masterKey;
|
||||
}
|
||||
|
||||
|
@@ -3,8 +3,6 @@ package org.whispersystems.signalservice.api.kbs;
|
||||
import org.whispersystems.signalservice.api.crypto.HmacSIV;
|
||||
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import static java.util.Arrays.copyOfRange;
|
||||
|
||||
/**
|
||||
@@ -33,11 +31,10 @@ public final class HashedPin {
|
||||
/**
|
||||
* Creates a new {@link KbsData} to store on KBS.
|
||||
*/
|
||||
public KbsData createNewKbsData(SecureRandom random) {
|
||||
byte[] M = new byte[32];
|
||||
random.nextBytes(M);
|
||||
public KbsData createNewKbsData(MasterKey masterKey) {
|
||||
byte[] M = masterKey.serialize();
|
||||
byte[] IVC = HmacSIV.encrypt(K, M);
|
||||
return new KbsData(M, kbsAccessKey, IVC);
|
||||
return new KbsData(masterKey, kbsAccessKey, IVC);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,6 +42,10 @@ public final class HashedPin {
|
||||
*/
|
||||
public KbsData decryptKbsDataIVCipherText(byte[] IVC) throws InvalidCiphertextException {
|
||||
byte[] masterKey = HmacSIV.decrypt(K, IVC);
|
||||
return new KbsData(masterKey, kbsAccessKey, IVC);
|
||||
return new KbsData(new MasterKey(masterKey), kbsAccessKey, IVC);
|
||||
}
|
||||
|
||||
public byte[] getKbsAccessKey() {
|
||||
return kbsAccessKey;
|
||||
}
|
||||
}
|
||||
|
@@ -8,8 +8,8 @@ public final class KbsData {
|
||||
private final byte[] kbsAccessKey;
|
||||
private final byte[] cipherText;
|
||||
|
||||
KbsData(byte[] masterKey, byte[] kbsAccessKey, byte[] cipherText) {
|
||||
this.masterKey = new MasterKey(masterKey);
|
||||
KbsData(MasterKey masterKey, byte[] kbsAccessKey, byte[] cipherText) {
|
||||
this.masterKey = masterKey;
|
||||
this.kbsAccessKey = kbsAccessKey;
|
||||
this.cipherText = cipherText;
|
||||
}
|
||||
|
@@ -3,18 +3,29 @@ package org.whispersystems.signalservice.api.kbs;
|
||||
import org.whispersystems.signalservice.internal.util.Hex;
|
||||
import org.whispersystems.util.StringUtil;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.whispersystems.signalservice.api.crypto.CryptoUtil.hmacSha256;
|
||||
|
||||
public final class MasterKey {
|
||||
|
||||
private static final int LENGTH = 32;
|
||||
|
||||
private final byte[] masterKey;
|
||||
|
||||
public MasterKey(byte[] masterKey) {
|
||||
if (masterKey.length != 32) throw new AssertionError();
|
||||
if (masterKey.length != LENGTH) throw new AssertionError();
|
||||
|
||||
this.masterKey = masterKey;
|
||||
}
|
||||
|
||||
public static MasterKey createNew(SecureRandom secureRandom) {
|
||||
byte[] key = new byte[LENGTH];
|
||||
secureRandom.nextBytes(key);
|
||||
return new MasterKey(key);
|
||||
}
|
||||
|
||||
public String deriveRegistrationLock() {
|
||||
return Hex.toStringCondensed(derive("Registration Lock"));
|
||||
}
|
||||
@@ -30,4 +41,16 @@ public final class MasterKey {
|
||||
public byte[] serialize() {
|
||||
return masterKey.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || o.getClass() != getClass()) return false;
|
||||
|
||||
return Arrays.equals(((MasterKey) o).masterKey, masterKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(masterKey);
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +0,0 @@
|
||||
package org.whispersystems.signalservice.internal.registrationpin;
|
||||
|
||||
public final class InvalidPinException extends Exception {
|
||||
|
||||
InvalidPinException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
package org.whispersystems.signalservice.internal.registrationpin;
|
||||
|
||||
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.Normalizer;
|
||||
|
||||
public final class PinHasher {
|
||||
|
||||
public static byte[] normalize(String pin) {
|
||||
pin = pin.trim();
|
||||
|
||||
if (allNumeric(pin)) {
|
||||
pin = new String(toArabic(pin));
|
||||
}
|
||||
|
||||
pin = Normalizer.normalize(pin, Normalizer.Form.NFKD);
|
||||
|
||||
return pin.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static HashedPin hashPin(byte[] normalizedPinBytes, Argon2 argon2) {
|
||||
return HashedPin.fromArgon2Hash(argon2.hash(normalizedPinBytes));
|
||||
}
|
||||
|
||||
public interface Argon2 {
|
||||
byte[] hash(byte[] password);
|
||||
}
|
||||
|
||||
private static boolean allNumeric(CharSequence pin) {
|
||||
for (int i = 0; i < pin.length(); i++) {
|
||||
if (!Character.isDigit(pin.charAt(i))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string of not necessarily Arabic numerals to Arabic 0..9 characters.
|
||||
*/
|
||||
private static char[] toArabic(CharSequence numerals) {
|
||||
int length = numerals.length();
|
||||
char[] arabic = new char[length];
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
int digit = Character.digit(numerals.charAt(i), 10);
|
||||
|
||||
arabic[i] = (char) ('0' + digit);
|
||||
}
|
||||
|
||||
return arabic;
|
||||
}
|
||||
}
|
@@ -1,166 +0,0 @@
|
||||
package org.whispersystems.signalservice.internal.registrationpin;
|
||||
|
||||
import org.whispersystems.signalservice.internal.util.Hex;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.ShortBufferException;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public final class PinStretcher {
|
||||
|
||||
private static final String HMAC_SHA256 = "HmacSHA256";
|
||||
private static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
public static StretchedPin stretchPin(CharSequence pin) throws InvalidPinException {
|
||||
return new StretchedPin(pin);
|
||||
}
|
||||
|
||||
public static class StretchedPin {
|
||||
private final byte[] stretchedPin;
|
||||
private final byte[] pinKey1;
|
||||
private final byte[] kbsAccessKey;
|
||||
|
||||
private StretchedPin(byte[] stretchedPin, byte[] pinKey1, byte[] kbsAccessKey) {
|
||||
this.stretchedPin = stretchedPin;
|
||||
this.pinKey1 = pinKey1;
|
||||
this.kbsAccessKey = kbsAccessKey;
|
||||
}
|
||||
|
||||
private StretchedPin(CharSequence pin) throws InvalidPinException {
|
||||
if (pin.length() < 4) throw new InvalidPinException("Pin too short");
|
||||
|
||||
char[] arabicPin = toArabic(pin);
|
||||
|
||||
stretchedPin = pbkdf2HmacSHA256(arabicPin, "nosalt", 20000, 256);
|
||||
|
||||
try {
|
||||
Mac mac = Mac.getInstance(HMAC_SHA256);
|
||||
mac.init(new SecretKeySpec(stretchedPin, HMAC_SHA256));
|
||||
mac.update("Master Key Encryption".getBytes(UTF_8));
|
||||
|
||||
pinKey1 = new byte[32];
|
||||
mac.doFinal(pinKey1, 0);
|
||||
|
||||
mac.init(new SecretKeySpec(stretchedPin, HMAC_SHA256));
|
||||
mac.update("KBS Access Key".getBytes(UTF_8));
|
||||
|
||||
kbsAccessKey = new byte[32];
|
||||
mac.doFinal(kbsAccessKey, 0);
|
||||
} catch (NoSuchAlgorithmException | ShortBufferException | InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public MasterKey withPinKey2(byte[] pinKey2) {
|
||||
return new MasterKey(pinKey1, pinKey2, this);
|
||||
}
|
||||
|
||||
public MasterKey withNewSecurePinKey2() {
|
||||
return withPinKey2(Util.getSecretBytes(32));
|
||||
}
|
||||
|
||||
public byte[] getPinKey1() {
|
||||
return pinKey1;
|
||||
}
|
||||
|
||||
public byte[] getStretchedPin() {
|
||||
return stretchedPin;
|
||||
}
|
||||
|
||||
public byte[] getKbsAccessKey() {
|
||||
return kbsAccessKey;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MasterKey extends StretchedPin {
|
||||
private final byte[] pinKey2;
|
||||
private final byte[] masterKey;
|
||||
private final String registrationLock;
|
||||
|
||||
private MasterKey(byte[] pinKey1, byte[] pinKey2, StretchedPin stretchedPin) {
|
||||
super(stretchedPin.stretchedPin, stretchedPin.pinKey1, stretchedPin.kbsAccessKey);
|
||||
|
||||
if (pinKey2.length != 32) {
|
||||
throw new AssertionError("PinKey2 must be exactly 32 bytes");
|
||||
}
|
||||
|
||||
this.pinKey2 = pinKey2.clone();
|
||||
|
||||
try {
|
||||
Mac mac = Mac.getInstance(HMAC_SHA256);
|
||||
|
||||
mac.init(new SecretKeySpec(pinKey1, HMAC_SHA256));
|
||||
mac.update(pinKey2);
|
||||
|
||||
masterKey = new byte[32];
|
||||
mac.doFinal(masterKey, 0);
|
||||
|
||||
mac.init(new SecretKeySpec(masterKey, HMAC_SHA256));
|
||||
mac.update("Registration Lock".getBytes(UTF_8));
|
||||
|
||||
byte[] registration_lock_token_bytes = new byte[32];
|
||||
mac.doFinal(registration_lock_token_bytes, 0);
|
||||
registrationLock = Hex.toStringCondensed(registration_lock_token_bytes);
|
||||
|
||||
} catch (NoSuchAlgorithmException | ShortBufferException | InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getPinKey2() {
|
||||
return pinKey2;
|
||||
}
|
||||
|
||||
public String getRegistrationLock() {
|
||||
return registrationLock;
|
||||
}
|
||||
|
||||
public byte[] getMasterKey() {
|
||||
return masterKey;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] pbkdf2HmacSHA256(char[] pin, String salt, int iterationCount, int outputSize) {
|
||||
byte[] saltBytes = salt.getBytes(Charset.forName("UTF-8"));
|
||||
|
||||
try {
|
||||
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
PBEKeySpec spec = new PBEKeySpec(pin, saltBytes, iterationCount, outputSize);
|
||||
byte[] encoded = skf.generateSecret(spec).getEncoded();
|
||||
|
||||
spec.clearPassword();
|
||||
|
||||
return encoded;
|
||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||
throw new AssertionError("Could not stretch pin", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string of not necessarily Arabic numerals to Arabic 0..9 characters.
|
||||
*/
|
||||
private static char[] toArabic(CharSequence numerals) throws InvalidPinException {
|
||||
int length = numerals.length();
|
||||
char[] arabic = new char[length];
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
int digit = Character.digit(numerals.charAt(i), 10);
|
||||
|
||||
if (digit < 0) {
|
||||
throw new InvalidPinException("Pin must only consist of decimals");
|
||||
}
|
||||
|
||||
arabic[i] = (char) ('0' + digit);
|
||||
}
|
||||
|
||||
return arabic;
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
package org.whispersystems.signalservice.api.kbs;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
public final class MasterKeyTest {
|
||||
|
||||
@Test(expected = AssertionError.class)
|
||||
public void wrong_length_too_short() {
|
||||
new MasterKey(new byte[31]);
|
||||
}
|
||||
|
||||
@Test(expected = AssertionError.class)
|
||||
public void wrong_length_too_long() {
|
||||
new MasterKey(new byte[33]);
|
||||
}
|
||||
|
||||
@Test(expected = NullPointerException.class)
|
||||
public void invalid_input_null() {
|
||||
//noinspection ConstantConditions
|
||||
new MasterKey(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equality() {
|
||||
byte[] masterKeyBytes1 = new byte[32];
|
||||
byte[] masterKeyBytes2 = new byte[32];
|
||||
MasterKey masterKey1 = new MasterKey(masterKeyBytes1);
|
||||
MasterKey masterKey2 = new MasterKey(masterKeyBytes2);
|
||||
|
||||
assertEquals(masterKey1, masterKey2);
|
||||
assertEquals(masterKey1.hashCode(), masterKey2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void in_equality() {
|
||||
byte[] masterKeyBytes1 = new byte[32];
|
||||
byte[] masterKeyBytes2 = new byte[32];
|
||||
|
||||
masterKeyBytes1[0] = 1;
|
||||
|
||||
MasterKey masterKey1 = new MasterKey(masterKeyBytes1);
|
||||
MasterKey masterKey2 = new MasterKey(masterKeyBytes2);
|
||||
|
||||
assertNotEquals(masterKey1, masterKey2);
|
||||
assertNotEquals(masterKey1.hashCode(), masterKey2.hashCode());
|
||||
}
|
||||
}
|
@@ -1,31 +0,0 @@
|
||||
package org.whispersystems.signalservice.internal.registrationpin;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public final class PinStretchFailureTest {
|
||||
|
||||
@Test(expected = InvalidPinException.class)
|
||||
public void non_numeric_pin() throws InvalidPinException {
|
||||
PinStretcher.stretchPin("A");
|
||||
}
|
||||
|
||||
@Test(expected = InvalidPinException.class)
|
||||
public void empty() throws InvalidPinException {
|
||||
PinStretcher.stretchPin("");
|
||||
}
|
||||
|
||||
@Test(expected = InvalidPinException.class)
|
||||
public void too_few_digits() throws InvalidPinException {
|
||||
PinStretcher.stretchPin("123");
|
||||
}
|
||||
|
||||
@Test(expected = AssertionError.class)
|
||||
public void pin_key_2_too_short() throws InvalidPinException {
|
||||
PinStretcher.stretchPin("0000").withPinKey2(new byte[31]);
|
||||
}
|
||||
|
||||
@Test(expected = AssertionError.class)
|
||||
public void pin_key_2_too_long() throws InvalidPinException {
|
||||
PinStretcher.stretchPin("0000").withPinKey2(new byte[33]);
|
||||
}
|
||||
}
|
@@ -1,127 +0,0 @@
|
||||
package org.whispersystems.signalservice.internal.registrationpin;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.whispersystems.signalservice.internal.util.Hex;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public final class PinStretchTest {
|
||||
|
||||
private final String pin;
|
||||
private final byte[] expectedStretchedPin;
|
||||
private final byte[] expectedKeyPin1;
|
||||
private final byte[] pinKey2;
|
||||
private final byte[] expectedMasterKey;
|
||||
private final String expectedRegistrationLock;
|
||||
private final byte[] expectedKbsAccessKey;
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection<Object[]> data() {
|
||||
return Arrays.asList(new Object[]{
|
||||
"12345",
|
||||
"4e84b9b2567e1999f665a4288fbc98a30fd7c4a6a1b504b07e56d4183107ff1d",
|
||||
"0191747f14295c6c2d42af3ff94d610b7899d5eb6cccd14c71aa314f70aaaf0f",
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
"892f2cab29c09b13718e5f06a3e4aa0dd42cd7e0b20c411668eed10bb06f72b2",
|
||||
"65cdbc33682f3be3c8809f54ed41c8f2f85cfce23b77d2a8b435ccff9681071d",
|
||||
"7a2d4f7974c4c2314bee8e68d62a03fd97af0ef6904ee1b912dcc900c19215ba"
|
||||
},
|
||||
new Object[]{
|
||||
"12345",
|
||||
"4e84b9b2567e1999f665a4288fbc98a30fd7c4a6a1b504b07e56d4183107ff1d",
|
||||
"0191747f14295c6c2d42af3ff94d610b7899d5eb6cccd14c71aa314f70aaaf0f",
|
||||
"abababababababababababababababababababababababababababababababab",
|
||||
"01198dc427cbf9c6b47f344654d75a263e53b992db73be44b201f357d072dc38",
|
||||
"bd1f4e129cc705c26c2fcebd3fbc6e7db60caade89e6c465c68ed60aeedbb0c3",
|
||||
"7a2d4f7974c4c2314bee8e68d62a03fd97af0ef6904ee1b912dcc900c19215ba"
|
||||
},
|
||||
new Object[]{
|
||||
"١٢٣٤٥",
|
||||
"4e84b9b2567e1999f665a4288fbc98a30fd7c4a6a1b504b07e56d4183107ff1d",
|
||||
"0191747f14295c6c2d42af3ff94d610b7899d5eb6cccd14c71aa314f70aaaf0f",
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
"892f2cab29c09b13718e5f06a3e4aa0dd42cd7e0b20c411668eed10bb06f72b2",
|
||||
"65cdbc33682f3be3c8809f54ed41c8f2f85cfce23b77d2a8b435ccff9681071d",
|
||||
"7a2d4f7974c4c2314bee8e68d62a03fd97af0ef6904ee1b912dcc900c19215ba"
|
||||
},
|
||||
new Object[]{
|
||||
"9876543210",
|
||||
"1ec376ca694b5c1fb185be3864343aaa08829833153f3a72813e3e48cb3579b9",
|
||||
"40f35cdc3f3325b037f9fedddd25c68b7ea9c3e50e6a1a81319c43263da7bec3",
|
||||
"abababababababababababababababababababababababababababababababab",
|
||||
"127a435c15be2528f4b735423f8ee558b789e8ea1f6fe64d144d5b21a87c4e06",
|
||||
"348d327acb823b54a988cf6bea647a154e21da25cbb121a115c13b871dccd548",
|
||||
"90aaa3156952db441a8c875e8e4abab3d48965df7f563fbfb39f567d1ec7354e",
|
||||
},
|
||||
new Object[]{
|
||||
"9876543210",
|
||||
"1ec376ca694b5c1fb185be3864343aaa08829833153f3a72813e3e48cb3579b9",
|
||||
"40f35cdc3f3325b037f9fedddd25c68b7ea9c3e50e6a1a81319c43263da7bec3",
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
"128833dbde1af3da703852b6b5a845e226fe9c7e069427b9c1e41279c0cdfb3a",
|
||||
"6be0b17899cfb5c4316b92acc7db3b6a2fa5b9a19ef3e58a1c84a4de49230aa6",
|
||||
"90aaa3156952db441a8c875e8e4abab3d48965df7f563fbfb39f567d1ec7354e",
|
||||
},
|
||||
new Object[]{
|
||||
"0123",
|
||||
"b9bc227d893edc7cade32d16ba210599f9e901c721bcad85ad458ab90432cbe7",
|
||||
"bb8c8fc51b705dcdce43467ad7417fa5f28708941bcc9682fc4123a006701567",
|
||||
"abababababababababababababababababababababababababababababababab",
|
||||
"ca94b0a7b26d44078ccfcb88fd67151d891b3b8eb8c65ab94d536c3cb0e1d7dd",
|
||||
"d182bde40ee91969192d5166fc871cd4bf5e261b090bbc707354bddb29fb8290",
|
||||
"c0ae6e108296e507ee9ebd7fd5d8564b8e644bd53d50a2fc7ab379aea8074a91"
|
||||
},
|
||||
new Object[]{
|
||||
"௦௧௨௩",
|
||||
"b9bc227d893edc7cade32d16ba210599f9e901c721bcad85ad458ab90432cbe7",
|
||||
"bb8c8fc51b705dcdce43467ad7417fa5f28708941bcc9682fc4123a006701567",
|
||||
"abababababababababababababababababababababababababababababababab",
|
||||
"ca94b0a7b26d44078ccfcb88fd67151d891b3b8eb8c65ab94d536c3cb0e1d7dd",
|
||||
"d182bde40ee91969192d5166fc871cd4bf5e261b090bbc707354bddb29fb8290",
|
||||
"c0ae6e108296e507ee9ebd7fd5d8564b8e644bd53d50a2fc7ab379aea8074a91"
|
||||
});
|
||||
}
|
||||
|
||||
public PinStretchTest(String pin,
|
||||
String expectedStretchedPin,
|
||||
String expectedKeyPin1,
|
||||
String pinKey2,
|
||||
String expectedMasterKey,
|
||||
String expectedRegistrationLock,
|
||||
String expectedKbsAccessKey) throws IOException {
|
||||
this.pin = pin;
|
||||
this.expectedStretchedPin = Hex.fromStringCondensed(expectedStretchedPin);
|
||||
this.expectedKeyPin1 = Hex.fromStringCondensed(expectedKeyPin1);
|
||||
this.pinKey2 = Hex.fromStringCondensed(pinKey2);
|
||||
this.expectedMasterKey = Hex.fromStringCondensed(expectedMasterKey);
|
||||
this.expectedRegistrationLock = expectedRegistrationLock;
|
||||
this.expectedKbsAccessKey = Hex.fromStringCondensed(expectedKbsAccessKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stretch_pin() throws InvalidPinException {
|
||||
PinStretcher.StretchedPin stretchedPin = PinStretcher.stretchPin(pin);
|
||||
|
||||
assertArrayEquals(expectedStretchedPin, stretchedPin.getStretchedPin());
|
||||
assertArrayEquals(expectedKeyPin1, stretchedPin.getPinKey1());
|
||||
assertArrayEquals(expectedKbsAccessKey, stretchedPin.getKbsAccessKey());
|
||||
|
||||
PinStretcher.MasterKey masterKey = stretchedPin.withPinKey2(pinKey2);
|
||||
|
||||
assertArrayEquals(pinKey2, masterKey.getPinKey2());
|
||||
assertArrayEquals(expectedMasterKey, masterKey.getMasterKey());
|
||||
assertEquals(expectedRegistrationLock, masterKey.getRegistrationLock());
|
||||
|
||||
assertArrayEquals(expectedStretchedPin, masterKey.getStretchedPin());
|
||||
assertArrayEquals(expectedKeyPin1, masterKey.getPinKey1());
|
||||
assertArrayEquals(expectedKbsAccessKey, masterKey.getKbsAccessKey());
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user