mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-03-03 15:58:40 +00:00
Switch to libsignal for PIN hashing.
This commit is contained in:
@@ -520,12 +520,6 @@ dependencies {
|
||||
exclude group: 'com.google.protobuf'
|
||||
}
|
||||
|
||||
implementation(libs.signal.argon2) {
|
||||
artifact {
|
||||
type = "aar"
|
||||
}
|
||||
}
|
||||
|
||||
implementation libs.signal.ringrtc
|
||||
|
||||
implementation libs.leolin.shortcutbadger
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.lock.PinHashing
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity
|
||||
import org.thoughtcrime.securesms.lock.v2.KbsConstants
|
||||
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType
|
||||
@@ -39,6 +38,7 @@ import org.thoughtcrime.securesms.util.ServiceUtil
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
import org.whispersystems.signalservice.api.kbs.PinHashUtil
|
||||
|
||||
class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFragment__account) {
|
||||
|
||||
@@ -247,7 +247,7 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag
|
||||
pinEditText.typeface = Typeface.DEFAULT
|
||||
turnOffButton.setOnClickListener {
|
||||
val pin = pinEditText.text.toString()
|
||||
val correct = PinHashing.verifyLocalPinHash(SignalStore.kbsValues().localPinHash!!, pin)
|
||||
val correct = PinHashUtil.verifyLocalPinHash(SignalStore.kbsValues().localPinHash!!, pin)
|
||||
if (correct) {
|
||||
SignalStore.pinValues().setPinRemindersEnabled(false)
|
||||
viewModel.refreshState()
|
||||
|
||||
@@ -8,12 +8,12 @@ import androidx.navigation.fragment.findNavController
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberUtil.changeNumberSuccess
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.lock.PinHashing
|
||||
import org.thoughtcrime.securesms.registration.fragments.BaseRegistrationLockFragment
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.BaseRegistrationViewModel
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.SupportEmailUtil
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
import org.whispersystems.signalservice.api.kbs.PinHashUtil
|
||||
|
||||
class ChangeNumberRegistrationLockFragment : BaseRegistrationLockFragment(R.layout.fragment_change_number_registration_lock) {
|
||||
|
||||
@@ -42,7 +42,7 @@ class ChangeNumberRegistrationLockFragment : BaseRegistrationLockFragment(R.layo
|
||||
}
|
||||
|
||||
override fun handleSuccessfulPinEntry(pin: String) {
|
||||
val pinsDiffer: Boolean = SignalStore.kbsValues().localPinHash?.let { !PinHashing.verifyLocalPinHash(it, pin) } ?: false
|
||||
val pinsDiffer: Boolean = SignalStore.kbsValues().localPinHash?.let { !PinHashUtil.verifyLocalPinHash(it, pin) } ?: false
|
||||
|
||||
pinButton.cancelSpinning()
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.StringStringSerializer;
|
||||
import org.thoughtcrime.securesms.lock.PinHashing;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.api.kbs.PinHashUtil;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -75,7 +75,7 @@ public final class KbsValues extends SignalStoreValues {
|
||||
getStore().beginWrite()
|
||||
.putString(TOKEN_RESPONSE, tokenResponse)
|
||||
.putBlob(MASTER_KEY, masterKey.serialize())
|
||||
.putString(LOCK_LOCAL_PIN_HASH, PinHashing.localPinHash(pin))
|
||||
.putString(LOCK_LOCAL_PIN_HASH, PinHashUtil.localPinHash(pin))
|
||||
.putString(PIN, pin)
|
||||
.putLong(LAST_CREATE_FAILED_TIMESTAMP, -1)
|
||||
.putBoolean(OPTED_OUT, false)
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
package org.thoughtcrime.securesms.lock;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.argon2.Argon2;
|
||||
import org.signal.argon2.Argon2Exception;
|
||||
import org.signal.argon2.MemoryCost;
|
||||
import org.signal.argon2.Type;
|
||||
import org.signal.argon2.UnknownTypeException;
|
||||
import org.signal.argon2.Version;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.api.KeyBackupService;
|
||||
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||
import org.whispersystems.signalservice.internal.registrationpin.PinHasher;
|
||||
|
||||
public final class PinHashing {
|
||||
|
||||
private PinHashing() {
|
||||
}
|
||||
|
||||
public static HashedPin hashPin(@NonNull String pin, @NonNull KeyBackupService.HashSession hashSession) {
|
||||
return PinHasher.hashPin(PinHasher.normalize(pin), password -> {
|
||||
try {
|
||||
return new Argon2.Builder(Version.V13)
|
||||
.type(Type.Argon2id)
|
||||
.memoryCost(MemoryCost.MiB(16))
|
||||
.parallelism(1)
|
||||
.iterations(32)
|
||||
.hashLength(64)
|
||||
.build()
|
||||
.hash(password, hashSession.hashSalt())
|
||||
.getHash();
|
||||
} catch (Argon2Exception e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static String localPinHash(@NonNull String pin) {
|
||||
byte[] normalized = PinHasher.normalize(pin);
|
||||
try {
|
||||
return new Argon2.Builder(Version.V13)
|
||||
.type(Type.Argon2i)
|
||||
.memoryCost(MemoryCost.KiB(256))
|
||||
.parallelism(1)
|
||||
.iterations(50)
|
||||
.hashLength(32)
|
||||
.build()
|
||||
.hash(normalized, Util.getSecretBytes(16))
|
||||
.getEncoded();
|
||||
} catch (Argon2Exception e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean verifyLocalPinHash(@NonNull String localPinHash, @NonNull String pin) {
|
||||
byte[] normalized = PinHasher.normalize(pin);
|
||||
try {
|
||||
return Argon2.verify(localPinHash, normalized);
|
||||
} catch (UnknownTypeException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.lock.v2.KbsConstants;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.whispersystems.signalservice.api.kbs.PinHashUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -121,7 +122,7 @@ public final class SignalPinReminderDialog {
|
||||
if (text.length() >= KbsConstants.MINIMUM_PIN_LENGTH) {
|
||||
submit.setEnabled(true);
|
||||
|
||||
if (PinHashing.verifyLocalPinHash(localHash, text)) {
|
||||
if (PinHashUtil.verifyLocalPinHash(localHash, text)) {
|
||||
dialog.dismiss();
|
||||
mainCallback.onReminderCompleted(text, callback.hadWrongGuess());
|
||||
}
|
||||
@@ -180,7 +181,7 @@ public final class SignalPinReminderDialog {
|
||||
|
||||
if (pin.length() < KbsConstants.MINIMUM_PIN_LENGTH) return;
|
||||
|
||||
if (PinHashing.verifyLocalPinHash(localPinHash, pin)) {
|
||||
if (PinHashUtil.verifyLocalPinHash(localPinHash, pin)) {
|
||||
callback.onPinCorrect(pin);
|
||||
} else {
|
||||
callback.onPinWrong();
|
||||
|
||||
@@ -8,7 +8,7 @@ import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||
import org.whispersystems.signalservice.internal.registrationpin.PinValidityChecker;
|
||||
import org.whispersystems.signalservice.api.kbs.PinValidityChecker;
|
||||
|
||||
public final class CreateKbsPinViewModel extends ViewModel implements BaseKbsPinViewModel {
|
||||
|
||||
|
||||
@@ -8,15 +8,15 @@ import androidx.annotation.Nullable;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.svr2.PinHash;
|
||||
import org.thoughtcrime.securesms.KbsEnclave;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.PinHashing;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
import org.whispersystems.signalservice.api.KeyBackupService;
|
||||
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||
import org.whispersystems.signalservice.api.kbs.PinHashUtil;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
@@ -162,7 +162,7 @@ public class KbsRepository {
|
||||
try {
|
||||
Log.i(TAG, "Restoring pin from KBS");
|
||||
|
||||
HashedPin hashedPin = PinHashing.hashPin(pin, session);
|
||||
PinHash hashedPin = PinHashUtil.hashPin(pin, session.hashSalt());
|
||||
KbsPinData kbsData = session.restorePin(hashedPin);
|
||||
|
||||
if (kbsData != null) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.svr2.PinHash;
|
||||
import org.thoughtcrime.securesms.KbsEnclave;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobTracker;
|
||||
@@ -16,15 +17,14 @@ import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
|
||||
import org.thoughtcrime.securesms.jobs.StorageForcePushJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.KbsValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.PinHashing;
|
||||
import org.thoughtcrime.securesms.lock.RegistrationLockReminders;
|
||||
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType;
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphones;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
import org.whispersystems.signalservice.api.KeyBackupService;
|
||||
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.api.kbs.PinHashUtil;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -119,8 +119,8 @@ public final class PinState {
|
||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(kbsEnclave);
|
||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
||||
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
|
||||
KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||
PinHash pinHash = PinHashUtil.hashPin(pin, pinChangeSession.hashSalt());
|
||||
KbsPinData kbsData = pinChangeSession.setPin(pinHash, masterKey);
|
||||
|
||||
kbsValues.setKbsMasterKey(kbsData, pin);
|
||||
kbsValues.setPinForgottenOrSkipped(false);
|
||||
@@ -221,8 +221,8 @@ public final class PinState {
|
||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(kbsEnclave);
|
||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
||||
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
|
||||
KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||
PinHash pinHash = PinHashUtil.hashPin(pin, pinChangeSession.hashSalt());
|
||||
KbsPinData kbsData = pinChangeSession.setPin(pinHash, masterKey);
|
||||
|
||||
pinChangeSession.enableRegistrationLock(masterKey);
|
||||
|
||||
@@ -299,8 +299,8 @@ public final class PinState {
|
||||
|
||||
KeyBackupService kbs = ApplicationDependencies.getKeyBackupService(enclave);
|
||||
KeyBackupService.PinChangeSession pinChangeSession = kbs.newPinChangeSession();
|
||||
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
|
||||
KbsPinData newData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||
PinHash pinHash = PinHashUtil.hashPin(pin, pinChangeSession.hashSalt());
|
||||
KbsPinData newData = pinChangeSession.setPin(pinHash, masterKey);
|
||||
|
||||
SignalStore.kbsValues().setKbsMasterKey(newData, pin);
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ import org.thoughtcrime.securesms.jobs.NewRegistrationUsernameSyncJob;
|
||||
import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob;
|
||||
import org.thoughtcrime.securesms.jobs.StorageSyncJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.PinHashing;
|
||||
import org.thoughtcrime.securesms.pin.KbsRepository;
|
||||
import org.thoughtcrime.securesms.pin.KeyBackupSystemWrongPinException;
|
||||
import org.thoughtcrime.securesms.pin.TokenData;
|
||||
@@ -32,6 +31,7 @@ import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||
import org.whispersystems.signalservice.api.kbs.PinHashUtil;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.IncorrectCodeException;
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
@@ -281,7 +281,7 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
|
||||
String localPinHash = SignalStore.kbsValues().getLocalPinHash();
|
||||
|
||||
if (hasRecoveryPassword() && localPinHash != null) {
|
||||
if (PinHashing.verifyLocalPinHash(localPinHash, pin)) {
|
||||
if (PinHashUtil.verifyLocalPinHash(localPinHash, pin)) {
|
||||
Log.i(TAG, "Local pin matches input, attempting registration");
|
||||
return ReRegistrationData.canProceed(new KbsPinData(SignalStore.kbsValues().getOrCreateMasterKey(), SignalStore.kbsValues().getRegistrationLockTokenResponse()));
|
||||
} else {
|
||||
|
||||
@@ -2,29 +2,33 @@ package org.thoughtcrime.securesms.registration.v2;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.signal.libsignal.svr2.PinHash;
|
||||
import org.thoughtcrime.securesms.registration.v2.testdata.KbsTestVector;
|
||||
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.api.kbs.PinHashUtil;
|
||||
import org.whispersystems.signalservice.internal.util.JsonUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.thoughtcrime.securesms.testutil.SecureRandomTestUtil.mockRandom;
|
||||
|
||||
public final class HashedPinKbsDataTest {
|
||||
public final class PinHashKbsDataTest {
|
||||
|
||||
@Test
|
||||
public void vectors_createNewKbsData() throws IOException {
|
||||
for (KbsTestVector vector : getKbsTestVectorList()) {
|
||||
HashedPin hashedPin = HashedPin.fromArgon2Hash(vector.getArgon2Hash());
|
||||
PinHash pinHash = fromArgon2Hash(vector.getArgon2Hash());
|
||||
|
||||
KbsData kbsData = hashedPin.createNewKbsData(MasterKey.createNew(mockRandom(vector.getMasterKey())));
|
||||
KbsData kbsData = PinHashUtil.createNewKbsData(pinHash, MasterKey.createNew(mockRandom(vector.getMasterKey())));
|
||||
|
||||
assertArrayEquals(vector.getMasterKey(), kbsData.getMasterKey().serialize());
|
||||
assertArrayEquals(vector.getIvAndCipher(), kbsData.getCipherText());
|
||||
@@ -36,9 +40,9 @@ public final class HashedPinKbsDataTest {
|
||||
@Test
|
||||
public void vectors_decryptKbsDataIVCipherText() throws IOException, InvalidCiphertextException {
|
||||
for (KbsTestVector vector : getKbsTestVectorList()) {
|
||||
HashedPin hashedPin = HashedPin.fromArgon2Hash(vector.getArgon2Hash());
|
||||
PinHash hashedPin = fromArgon2Hash(vector.getArgon2Hash());
|
||||
|
||||
KbsData kbsData = hashedPin.decryptKbsDataIVCipherText(vector.getIvAndCipher());
|
||||
KbsData kbsData = PinHashUtil.decryptKbsDataIVCipherText(hashedPin, vector.getIvAndCipher());
|
||||
|
||||
assertArrayEquals(vector.getMasterKey(), kbsData.getMasterKey().serialize());
|
||||
assertArrayEquals(vector.getIvAndCipher(), kbsData.getCipherText());
|
||||
@@ -57,4 +61,17 @@ public final class HashedPinKbsDataTest {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public static PinHash fromArgon2Hash(byte[] argon2Hash64) {
|
||||
if (argon2Hash64.length != 64) throw new AssertionError();
|
||||
|
||||
byte[] K = Arrays.copyOfRange(argon2Hash64, 0, 32);
|
||||
byte[] kbsAccessKey = Arrays.copyOfRange(argon2Hash64, 32, 64);
|
||||
|
||||
PinHash mocked = mock(PinHash.class);
|
||||
when(mocked.encryptionKey()).thenReturn(K);
|
||||
when(mocked.accessKey()).thenReturn(kbsAccessKey);
|
||||
|
||||
return mocked;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.registration.v2;
|
||||
import org.junit.Test;
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.thoughtcrime.securesms.registration.v2.testdata.PinSanitationVector;
|
||||
import org.whispersystems.signalservice.internal.registrationpin.PinHasher;
|
||||
import org.whispersystems.signalservice.api.kbs.PinHashUtil;
|
||||
import org.whispersystems.signalservice.internal.util.Hex;
|
||||
import org.whispersystems.signalservice.internal.util.JsonUtil;
|
||||
|
||||
@@ -14,12 +14,12 @@ import java.util.Arrays;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public final class PinHasher_normalize_Test {
|
||||
public final class PinHashUtil_normalize_Test {
|
||||
|
||||
@Test
|
||||
public void vectors_normalize() throws IOException {
|
||||
for (PinSanitationVector vector : getKbsPinSanitationTestVectorList()) {
|
||||
byte[] normalized = PinHasher.normalize(vector.getPin());
|
||||
byte[] normalized = PinHashUtil.normalize(vector.getPin());
|
||||
|
||||
if (!Arrays.equals(vector.getBytes(), normalized)) {
|
||||
assertEquals(String.format("%s [%s]", vector.getName(), vector.getPin()),
|
||||
@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.registration.v2;
|
||||
import org.junit.Test;
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.thoughtcrime.securesms.registration.v2.testdata.PinValidityVector;
|
||||
import org.whispersystems.signalservice.internal.registrationpin.PinValidityChecker;
|
||||
import org.whispersystems.signalservice.api.kbs.PinValidityChecker;
|
||||
import org.whispersystems.signalservice.internal.util.JsonUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -106,7 +106,6 @@ dependencyResolutionManagement {
|
||||
alias('libsignal-client').to('org.signal', 'libsignal-client').versionRef('libsignal-client')
|
||||
alias('libsignal-android').to('org.signal', 'libsignal-android').versionRef('libsignal-client')
|
||||
alias('signal-aesgcmprovider').to('org.signal:aesgcmprovider:0.0.3')
|
||||
alias('signal-argon2').to('org.signal:argon2:13.1')
|
||||
alias('signal-ringrtc').to('org.signal:ringrtc-android:2.26.3')
|
||||
alias('signal-android-database-sqlcipher').to('org.signal:sqlcipher-android:4.5.4-S1')
|
||||
|
||||
|
||||
@@ -2,10 +2,11 @@ package org.whispersystems.signalservice.api;
|
||||
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.logging.Log;
|
||||
import org.signal.libsignal.svr2.PinHash;
|
||||
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.api.kbs.PinHashUtil;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.KeyBackupCipher;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.Quote;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.RemoteAttestation;
|
||||
@@ -122,7 +123,7 @@ public class KeyBackupService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public KbsPinData restorePin(HashedPin hashedPin)
|
||||
public KbsPinData restorePin(PinHash hashedPin)
|
||||
throws UnauthenticatedResponseException, IOException, KeyBackupServicePinException, KeyBackupSystemNoDataException, InvalidKeyException
|
||||
{
|
||||
int attempt = 0;
|
||||
@@ -155,13 +156,13 @@ public class KeyBackupService {
|
||||
}
|
||||
}
|
||||
|
||||
private KbsPinData restorePin(HashedPin hashedPin, TokenResponse token)
|
||||
private KbsPinData restorePin(PinHash hashedPin, TokenResponse token)
|
||||
throws UnauthenticatedResponseException, IOException, TokenException, KeyBackupSystemNoDataException, InvalidKeyException
|
||||
{
|
||||
try {
|
||||
final int remainingTries = token.getTries();
|
||||
final RemoteAttestation remoteAttestation = getAndVerifyRemoteAttestation();
|
||||
final KeyBackupRequest request = KeyBackupCipher.createKeyRestoreRequest(hashedPin.getKbsAccessKey(), token, remoteAttestation, serviceId);
|
||||
final KeyBackupRequest request = KeyBackupCipher.createKeyRestoreRequest(hashedPin.accessKey(), token, remoteAttestation, serviceId);
|
||||
final KeyBackupResponse response = pushServiceSocket.putKbsData(authorization, request, remoteAttestation.getCookies(), enclaveName);
|
||||
final RestoreResponse status = KeyBackupCipher.getKeyRestoreResponse(response, remoteAttestation);
|
||||
|
||||
@@ -172,7 +173,7 @@ public class KeyBackupService {
|
||||
Log.i(TAG, "Restore " + status.getStatus());
|
||||
switch (status.getStatus()) {
|
||||
case OK:
|
||||
KbsData kbsData = hashedPin.decryptKbsDataIVCipherText(status.getData().toByteArray());
|
||||
KbsData kbsData = PinHashUtil.decryptKbsDataIVCipherText(hashedPin, status.getData().toByteArray());
|
||||
MasterKey masterKey = kbsData.getMasterKey();
|
||||
return new KbsPinData(masterKey, nextToken);
|
||||
case PIN_MISMATCH:
|
||||
@@ -206,8 +207,8 @@ public class KeyBackupService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public KbsPinData setPin(HashedPin hashedPin, MasterKey masterKey) throws IOException, UnauthenticatedResponseException {
|
||||
KbsData newKbsData = hashedPin.createNewKbsData(masterKey);
|
||||
public KbsPinData setPin(PinHash pinHash, MasterKey masterKey) throws IOException, UnauthenticatedResponseException {
|
||||
KbsData newKbsData = PinHashUtil.createNewKbsData(pinHash, masterKey);
|
||||
TokenResponse tokenResponse = putKbsData(newKbsData.getKbsAccessKey(),
|
||||
newKbsData.getCipherText(),
|
||||
enclaveName,
|
||||
@@ -274,13 +275,13 @@ public class KeyBackupService {
|
||||
|
||||
public interface RestoreSession extends HashSession {
|
||||
|
||||
KbsPinData restorePin(HashedPin hashedPin)
|
||||
KbsPinData restorePin(PinHash hashedPin)
|
||||
throws UnauthenticatedResponseException, IOException, KeyBackupServicePinException, KeyBackupSystemNoDataException, InvalidKeyException;
|
||||
}
|
||||
|
||||
public interface PinChangeSession extends HashSession {
|
||||
/** Creates a PIN. Does nothing to registration lock. */
|
||||
KbsPinData setPin(HashedPin hashedPin, MasterKey masterKey) throws IOException, UnauthenticatedResponseException;
|
||||
KbsPinData setPin(PinHash hashedPin, MasterKey masterKey) throws IOException, UnauthenticatedResponseException;
|
||||
|
||||
/** Removes the PIN data from KBS. */
|
||||
void removePin() throws IOException, UnauthenticatedResponseException;
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package org.whispersystems.signalservice.api.kbs;
|
||||
|
||||
import org.whispersystems.signalservice.api.crypto.HmacSIV;
|
||||
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
||||
|
||||
import static java.util.Arrays.copyOfRange;
|
||||
|
||||
/**
|
||||
* Represents a hashed pin, from which you can create or decrypt KBS data.
|
||||
* <p>
|
||||
* Normal Java casing has been ignored to match original specifications.
|
||||
*/
|
||||
public final class HashedPin {
|
||||
|
||||
private final byte[] K;
|
||||
private final byte[] kbsAccessKey;
|
||||
|
||||
private HashedPin(byte[] K, byte[] kbsAccessKey) {
|
||||
this.K = K;
|
||||
this.kbsAccessKey = kbsAccessKey;
|
||||
}
|
||||
|
||||
public static HashedPin fromArgon2Hash(byte[] argon2Hash64) {
|
||||
if (argon2Hash64.length != 64) throw new AssertionError();
|
||||
|
||||
byte[] K = copyOfRange(argon2Hash64, 0, 32);
|
||||
byte[] kbsAccessKey = copyOfRange(argon2Hash64, 32, 64);
|
||||
return new HashedPin(K, kbsAccessKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link KbsData} to store on KBS.
|
||||
*/
|
||||
public KbsData createNewKbsData(MasterKey masterKey) {
|
||||
byte[] M = masterKey.serialize();
|
||||
byte[] IVC = HmacSIV.encrypt(K, M);
|
||||
return new KbsData(masterKey, kbsAccessKey, IVC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes 48 byte IVC from KBS and returns full {@link KbsData}.
|
||||
*/
|
||||
public KbsData decryptKbsDataIVCipherText(byte[] IVC) throws InvalidCiphertextException {
|
||||
byte[] masterKey = HmacSIV.decrypt(K, IVC);
|
||||
return new KbsData(new MasterKey(masterKey), kbsAccessKey, IVC);
|
||||
}
|
||||
|
||||
public byte[] getKbsAccessKey() {
|
||||
return kbsAccessKey;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.whispersystems.signalservice.api.kbs;
|
||||
|
||||
/**
|
||||
* Construct from a {@link HashedPin}.
|
||||
* Construct from a {@link org.signal.libsignal.svr2.PinHash}.
|
||||
*/
|
||||
public final class KbsData {
|
||||
private final MasterKey masterKey;
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.signalservice.api.kbs
|
||||
|
||||
import org.signal.libsignal.svr2.Pin
|
||||
import org.signal.libsignal.svr2.PinHash
|
||||
import org.whispersystems.signalservice.api.crypto.HmacSIV
|
||||
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.text.Normalizer
|
||||
|
||||
object PinHashUtil {
|
||||
|
||||
/**
|
||||
* Takes a user-provided (i.e. non-normalized) PIN, normalizes it, and generates a [PinHash].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun hashPin(pin: String, salt: ByteArray): PinHash {
|
||||
return Pin.hash(normalize(pin), salt)
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a user-provided (i.e. non-normalized) PIN, normalizes it, and generates a hash that is suitable for storing on-device
|
||||
* for the purpose of checking PIN reminder correctness. Use in combination with [verifyLocalPinHash].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun localPinHash(pin: String): String {
|
||||
return Pin.localHash(normalize(pin))
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a user-provided (i.e. non-normalized) PIN, normalizes it, checks to see if it matches a hash that was generated with [localPinHash].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun verifyLocalPinHash(localPinHash: String, pin: String): Boolean {
|
||||
return Pin.verifyLocalHash(localPinHash, normalize(pin))
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new [KbsData] to store on KBS.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createNewKbsData(pinHash: PinHash, masterKey: MasterKey): KbsData {
|
||||
val ivc = HmacSIV.encrypt(pinHash.encryptionKey(), masterKey.serialize())
|
||||
return KbsData(masterKey, pinHash.accessKey(), ivc)
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes 48 byte IVC from KBS and returns full [KbsData].
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(InvalidCiphertextException::class)
|
||||
fun decryptKbsDataIVCipherText(pinHash: PinHash, ivc: ByteArray?): KbsData {
|
||||
val masterKey = HmacSIV.decrypt(pinHash.encryptionKey(), ivc)
|
||||
return KbsData(MasterKey(masterKey), pinHash.accessKey(), ivc)
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a user-input PIN string and normalizes it to a standard character set.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun normalize(pin: String): ByteArray {
|
||||
var normalizedPin = pin.trim()
|
||||
|
||||
if (PinString.allNumeric(normalizedPin)) {
|
||||
normalizedPin = PinString.toArabic(normalizedPin)
|
||||
}
|
||||
|
||||
normalizedPin = Normalizer.normalize(normalizedPin, Normalizer.Form.NFKD)
|
||||
|
||||
return normalizedPin.toByteArray(StandardCharsets.UTF_8)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
package org.whispersystems.signalservice.internal.registrationpin;
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.kbs;
|
||||
|
||||
final class PinString {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
package org.whispersystems.signalservice.internal.registrationpin;
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.kbs;
|
||||
|
||||
public final class PinValidityChecker {
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
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 (PinString.allNumeric(pin)) {
|
||||
pin = PinString.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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user