mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-30 23:36:32 +00:00
Replace pinstretcher with Argon2 and new PIN encryption.
This commit is contained in:
committed by
Greyson Parrelli
parent
f7a3bb2ae8
commit
e37c4b1f87
@@ -60,7 +60,7 @@ public class MultiDeviceKeysUpdateJob extends BaseJob {
|
||||
|
||||
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
|
||||
|
||||
MasterKey masterKey = SignalStore.kbsValues().getMasterKey();
|
||||
MasterKey masterKey = SignalStore.kbsValues().getPinBackedMasterKey();
|
||||
byte[] storageServiceKey = masterKey != null ? masterKey.deriveStorageServiceKey()
|
||||
: null;
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ public class StorageForcePushJob extends BaseJob {
|
||||
protected void onRun() throws IOException, RetryLaterException {
|
||||
if (!FeatureFlags.STORAGE_SERVICE) throw new AssertionError();
|
||||
|
||||
MasterKey kbsMasterKey = SignalStore.kbsValues().getMasterKey();
|
||||
MasterKey kbsMasterKey = SignalStore.kbsValues().getPinBackedMasterKey();
|
||||
|
||||
if (kbsMasterKey == null) {
|
||||
Log.w(TAG, "No KBS master key is set! Must abort.");
|
||||
|
||||
@@ -110,7 +110,7 @@ public class StorageSyncJob extends BaseJob {
|
||||
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
|
||||
StorageKeyDatabase storageKeyDatabase = DatabaseFactory.getStorageKeyDatabase(context);
|
||||
MasterKey kbsMasterKey = SignalStore.kbsValues().getMasterKey();
|
||||
MasterKey kbsMasterKey = SignalStore.kbsValues().getPinBackedMasterKey();
|
||||
|
||||
if (kbsMasterKey == null) {
|
||||
Log.w(TAG, "No KBS master key is set! Must abort.");
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
package org.thoughtcrime.securesms.keyvalue;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.whispersystems.signalservice.api.RegistrationLockData;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
import org.whispersystems.signalservice.internal.registrationpin.PinStretcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public final class KbsValues {
|
||||
|
||||
private static final String REGISTRATION_LOCK_PREF_V2 = "kbs.registration_lock_v2";
|
||||
private static final String REGISTRATION_LOCK_TOKEN_PREF = "kbs.registration_lock_token";
|
||||
private static final String REGISTRATION_LOCK_PIN_KEY_2_PREF = "kbs.registration_lock_pin_key_2";
|
||||
private static final String REGISTRATION_LOCK_MASTER_KEY = "kbs.registration_lock_master_key";
|
||||
private static final String REGISTRATION_LOCK_TOKEN_RESPONSE = "kbs.registration_lock_token_response";
|
||||
private static final String V2_LOCK_ENABLED = "kbs.v2_lock_enabled";
|
||||
private static final String MASTER_KEY = "kbs.registration_lock_master_key";
|
||||
private static final String TOKEN_RESPONSE = "kbs.token_response";
|
||||
private static final String LOCK_LOCAL_PIN_HASH = "kbs.registration_lock_local_pin_hash";
|
||||
|
||||
private final KeyValueStore store;
|
||||
|
||||
@@ -24,58 +24,85 @@ public final class KbsValues {
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
public void setRegistrationLockMasterKey(@Nullable RegistrationLockData registrationLockData) {
|
||||
KeyValueStore.Writer editor = store.beginWrite();
|
||||
|
||||
if (registrationLockData == null) {
|
||||
editor.remove(REGISTRATION_LOCK_PREF_V2)
|
||||
.remove(REGISTRATION_LOCK_TOKEN_RESPONSE)
|
||||
.remove(REGISTRATION_LOCK_MASTER_KEY)
|
||||
.remove(REGISTRATION_LOCK_TOKEN_PREF)
|
||||
.remove(REGISTRATION_LOCK_PIN_KEY_2_PREF);
|
||||
} else {
|
||||
PinStretcher.MasterKey masterKey = registrationLockData.getMasterKey();
|
||||
String tokenResponse;
|
||||
try {
|
||||
tokenResponse = JsonUtils.toJson(registrationLockData.getTokenResponse());
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
editor.putBoolean(REGISTRATION_LOCK_PREF_V2, true)
|
||||
.putString(REGISTRATION_LOCK_TOKEN_RESPONSE, tokenResponse)
|
||||
.putBlob(REGISTRATION_LOCK_MASTER_KEY, masterKey.getMasterKey())
|
||||
.putString(REGISTRATION_LOCK_TOKEN_PREF, masterKey.getRegistrationLock())
|
||||
.putBlob(REGISTRATION_LOCK_PIN_KEY_2_PREF, masterKey.getPinKey2());
|
||||
}
|
||||
|
||||
editor.commit();
|
||||
/**
|
||||
* Deliberately does not clear the {@link #MASTER_KEY}.
|
||||
*/
|
||||
public void clearRegistrationLock() {
|
||||
store.beginWrite()
|
||||
.remove(V2_LOCK_ENABLED)
|
||||
.remove(TOKEN_RESPONSE)
|
||||
.remove(LOCK_LOCAL_PIN_HASH)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public @Nullable MasterKey getMasterKey() {
|
||||
byte[] blob = store.getBlob(REGISTRATION_LOCK_MASTER_KEY, null);
|
||||
if (blob != null) {
|
||||
return new MasterKey(blob);
|
||||
} else {
|
||||
return null;
|
||||
public synchronized void setRegistrationLockMasterKey(@NonNull RegistrationLockData registrationLockData, @NonNull String localPinHash) {
|
||||
MasterKey masterKey = registrationLockData.getMasterKey();
|
||||
String tokenResponse;
|
||||
try {
|
||||
tokenResponse = JsonUtils.toJson(registrationLockData.getTokenResponse());
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
store.beginWrite()
|
||||
.putBoolean(V2_LOCK_ENABLED, true)
|
||||
.putString(TOKEN_RESPONSE, tokenResponse)
|
||||
.putBlob(MASTER_KEY, masterKey.serialize())
|
||||
.putString(LOCK_LOCAL_PIN_HASH, localPinHash)
|
||||
.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds or creates the master key. Therefore this will always return a master key whether backed
|
||||
* up or not.
|
||||
* <p>
|
||||
* If you only want a key when it's backed up, use {@link #getPinBackedMasterKey()}.
|
||||
*/
|
||||
public synchronized @NonNull MasterKey getOrCreateMasterKey() {
|
||||
byte[] blob = store.getBlob(MASTER_KEY, null);
|
||||
|
||||
if (blob == null) {
|
||||
store.beginWrite()
|
||||
.putBlob(MASTER_KEY, MasterKey.createNew(new SecureRandom()).serialize())
|
||||
.commit();
|
||||
blob = store.getBlob(MASTER_KEY, null);
|
||||
}
|
||||
|
||||
return new MasterKey(blob);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null if master key is not backed up by a pin.
|
||||
*/
|
||||
public synchronized @Nullable MasterKey getPinBackedMasterKey() {
|
||||
if (!isV2RegistrationLockEnabled()) return null;
|
||||
return getMasterKey();
|
||||
}
|
||||
|
||||
private synchronized @Nullable MasterKey getMasterKey() {
|
||||
byte[] blob = store.getBlob(MASTER_KEY, null);
|
||||
return blob != null ? new MasterKey(blob) : null;
|
||||
}
|
||||
|
||||
public @Nullable String getRegistrationLockToken() {
|
||||
return store.getString(REGISTRATION_LOCK_TOKEN_PREF, null);
|
||||
MasterKey masterKey = getPinBackedMasterKey();
|
||||
if (masterKey == null) {
|
||||
return null;
|
||||
} else {
|
||||
return masterKey.deriveRegistrationLock();
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable byte[] getRegistrationLockPinKey2() {
|
||||
return store.getBlob(REGISTRATION_LOCK_PIN_KEY_2_PREF, null);
|
||||
public @Nullable String getLocalPinHash() {
|
||||
return store.getString(LOCK_LOCAL_PIN_HASH, null);
|
||||
}
|
||||
|
||||
public boolean isV2RegistrationLockEnabled() {
|
||||
return store.getBoolean(REGISTRATION_LOCK_PREF_V2, false);
|
||||
return store.getBoolean(V2_LOCK_ENABLED, false);
|
||||
}
|
||||
|
||||
public @Nullable
|
||||
TokenResponse getRegistrationLockTokenResponse() {
|
||||
String token = store.getString(REGISTRATION_LOCK_TOKEN_RESPONSE, null);
|
||||
public @Nullable TokenResponse getRegistrationLockTokenResponse() {
|
||||
String token = store.getString(TOKEN_RESPONSE, null);
|
||||
|
||||
if (token == null) return null;
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.thoughtcrime.securesms.lock;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.argon2.Argon2;
|
||||
import org.signal.argon2.Argon2Exception;
|
||||
import org.signal.argon2.MemoryCost;
|
||||
import org.signal.argon2.Type;
|
||||
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 static final Type KBS_PIN_ARGON_TYPE = Type.Argon2id;
|
||||
private static final Type LOCAL_PIN_ARGON_TYPE = Type.Argon2i;
|
||||
|
||||
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(KBS_PIN_ARGON_TYPE)
|
||||
.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(LOCAL_PIN_ARGON_TYPE)
|
||||
.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);
|
||||
return Argon2.verify(localPinHash, normalized, LOCAL_PIN_ARGON_TYPE);
|
||||
}
|
||||
}
|
||||
@@ -39,14 +39,15 @@ import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
|
||||
import org.whispersystems.signalservice.api.KeyBackupService;
|
||||
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||
import org.whispersystems.signalservice.api.RegistrationLockData;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
import org.whispersystems.signalservice.internal.registrationpin.InvalidPinException;
|
||||
import org.whispersystems.signalservice.internal.registrationpin.PinStretcher;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -54,12 +55,15 @@ public final class RegistrationLockDialog {
|
||||
|
||||
private static final String TAG = Log.tag(RegistrationLockDialog.class);
|
||||
|
||||
private static final int MIN_V2_NUMERIC_PIN_LENGTH_ENTRY = 4;
|
||||
private static final int MIN_V2_NUMERIC_PIN_LENGTH_SETTING = 4;
|
||||
|
||||
public static void showReminderIfNecessary(@NonNull Context context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
|
||||
if (!RegistrationLockReminders.needsReminder(context)) return;
|
||||
|
||||
if (!TextSecurePreferences.isV1RegistrationLockEnabled(context) &&
|
||||
TextUtils.isEmpty(SignalStore.kbsValues().getRegistrationLockToken())) {
|
||||
!SignalStore.kbsValues().isV2RegistrationLockEnabled()) {
|
||||
// Neither v1 or v2 to check against
|
||||
Log.w(TAG, "Reg lock enabled, but no pin stored to verify against");
|
||||
return;
|
||||
@@ -117,62 +121,38 @@ public final class RegistrationLockDialog {
|
||||
//noinspection deprecation Acceptable to check the old pin in a reminder on a non-migrated system.
|
||||
String pin = TextSecurePreferences.getDeprecatedV1RegistrationLockPin(context);
|
||||
|
||||
return new TextWatcher() {
|
||||
return new AfterTextChanged((Editable s) -> {
|
||||
if (s != null && s.toString().replace(" ", "").equals(pin)) {
|
||||
dialog.dismiss();
|
||||
RegistrationLockReminders.scheduleReminder(context, true);
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (s != null && s.toString().replace(" ", "").equals(pin)) {
|
||||
dialog.dismiss();
|
||||
RegistrationLockReminders.scheduleReminder(context, true);
|
||||
|
||||
if (FeatureFlags.KBS) {
|
||||
Log.i(TAG, "Pin V1 successfully remembered, scheduling a migration to V2");
|
||||
ApplicationDependencies.getJobManager().add(new RegistrationPinV2MigrationJob());
|
||||
}
|
||||
if (FeatureFlags.KBS) {
|
||||
Log.i(TAG, "Pin V1 successfully remembered, scheduling a migration to V2");
|
||||
ApplicationDependencies.getJobManager().add(new RegistrationPinV2MigrationJob());
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private static TextWatcher getV2PinWatcher(@NonNull Context context, AlertDialog dialog) {
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
String registrationLockToken = kbsValues.getRegistrationLockToken();
|
||||
byte[] pinKey2 = kbsValues.getRegistrationLockPinKey2();
|
||||
TokenResponse registrationLockTokenResponse = kbsValues.getRegistrationLockTokenResponse();
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
MasterKey masterKey = kbsValues.getPinBackedMasterKey();
|
||||
String localPinHash = kbsValues.getLocalPinHash();
|
||||
|
||||
if (registrationLockToken == null) throw new AssertionError("No V2 reg lock token set at time of reminder");
|
||||
if (pinKey2 == null) throw new AssertionError("No pin key2 set at time of reminder");
|
||||
if (registrationLockTokenResponse == null) throw new AssertionError("No registrationLockTokenResponse set at time of reminder");
|
||||
if (masterKey == null) throw new AssertionError("No masterKey set at time of reminder");
|
||||
if (localPinHash == null) throw new AssertionError("No local pin hash set at time of reminder");
|
||||
|
||||
return new TextWatcher() {
|
||||
return new AfterTextChanged((Editable s) -> {
|
||||
if (s == null) return;
|
||||
String pin = s.toString();
|
||||
if (TextUtils.isEmpty(pin)) return;
|
||||
if (pin.length() < MIN_V2_NUMERIC_PIN_LENGTH_ENTRY) return;
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (s == null) return;
|
||||
String pin = s.toString();
|
||||
if (TextUtils.isEmpty(pin)) return;
|
||||
if (pin.length() < 4) return;
|
||||
|
||||
try {
|
||||
if (registrationLockToken.equals(PinStretcher.stretchPin(pin).withPinKey2(pinKey2).getRegistrationLock())) {
|
||||
dialog.dismiss();
|
||||
RegistrationLockReminders.scheduleReminder(context, true);
|
||||
}
|
||||
} catch (InvalidPinException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
if (PinHashing.verifyLocalPinHash(localPinHash, pin)) {
|
||||
dialog.dismiss();
|
||||
RegistrationLockReminders.scheduleReminder(context, true);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@@ -198,8 +178,10 @@ public final class RegistrationLockDialog {
|
||||
String pinValue = pin.getText().toString().replace(" ", "");
|
||||
String repeatValue = repeat.getText().toString().replace(" ", "");
|
||||
|
||||
if (pinValue.length() < 4) {
|
||||
Toast.makeText(context, R.string.RegistrationLockDialog_the_registration_lock_pin_must_be_at_least_four_digits, Toast.LENGTH_LONG).show();
|
||||
if (pinValue.length() < MIN_V2_NUMERIC_PIN_LENGTH_SETTING) {
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.RegistrationLockDialog_the_registration_lock_pin_must_be_at_least_d_digits, MIN_V2_NUMERIC_PIN_LENGTH_SETTING),
|
||||
Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -226,27 +208,29 @@ public final class RegistrationLockDialog {
|
||||
TextSecurePreferences.setDeprecatedRegistrationLockPin(context, pinValue);
|
||||
} else {
|
||||
Log.i(TAG, "Setting pin on KBS");
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
RegistrationLockData kbsData = keyBackupService.newPinChangeSession()
|
||||
.setPin(pinValue);
|
||||
RegistrationLockData restoredData = keyBackupService.newRestoreSession(kbsData.getTokenResponse())
|
||||
.restorePin(pinValue);
|
||||
String restoredLock = restoredData.getMasterKey()
|
||||
.getRegistrationLock();
|
||||
|
||||
if (!restoredLock.equals(kbsData.getMasterKey().getRegistrationLock())) {
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
||||
HashedPin hashedPin = PinHashing.hashPin(pinValue, pinChangeSession);
|
||||
RegistrationLockData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||
RegistrationLockData restoredData = keyBackupService.newRestoreSession(kbsData.getTokenResponse())
|
||||
.restorePin(hashedPin);
|
||||
|
||||
if (!restoredData.getMasterKey().equals(masterKey)) {
|
||||
throw new AssertionError("Failed to set the pin correctly");
|
||||
} else {
|
||||
Log.i(TAG, "Set and retrieved pin on KBS successfully");
|
||||
}
|
||||
|
||||
SignalStore.kbsValues().setRegistrationLockMasterKey(restoredData);
|
||||
kbsValues.setRegistrationLockMasterKey(restoredData, PinHashing.localPinHash(pinValue));
|
||||
TextSecurePreferences.clearOldRegistrationLockPin(context);
|
||||
TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis());
|
||||
TextSecurePreferences.setRegistrationLockNextReminderInterval(context, RegistrationLockReminders.INITIAL_INTERVAL);
|
||||
}
|
||||
return true;
|
||||
} catch (IOException | UnauthenticatedResponseException | KeyBackupServicePinException | InvalidPinException e) {
|
||||
} catch (IOException | UnauthenticatedResponseException | KeyBackupServicePinException e) {
|
||||
Log.w(TAG, e);
|
||||
return false;
|
||||
}
|
||||
@@ -308,7 +292,7 @@ public final class RegistrationLockDialog {
|
||||
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
keyBackupService.newPinChangeSession(currentToken).removePin();
|
||||
kbsValues.setRegistrationLockMasterKey(null);
|
||||
kbsValues.clearRegistrationLock();
|
||||
|
||||
// It is possible a migration has not occurred, in this case, we need to remove the old V1 Pin
|
||||
if (TextSecurePreferences.isV1RegistrationLockEnabled(context)) {
|
||||
|
||||
@@ -9,13 +9,18 @@ import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.jobs.BaseJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.KbsValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.PinHashing;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.KeyBackupService;
|
||||
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||
import org.whispersystems.signalservice.api.RegistrationLockData;
|
||||
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
import org.whispersystems.signalservice.internal.registrationpin.InvalidPinException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -49,7 +54,7 @@ public final class RegistrationPinV2MigrationJob extends BaseJob {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRun() throws IOException, UnauthenticatedResponseException {
|
||||
protected void onRun() throws IOException, UnauthenticatedResponseException, KeyBackupServicePinException {
|
||||
if (!FeatureFlags.KBS) {
|
||||
Log.i(TAG, "Not migrating pin to KBS");
|
||||
return;
|
||||
@@ -61,27 +66,33 @@ public final class RegistrationPinV2MigrationJob extends BaseJob {
|
||||
}
|
||||
|
||||
//noinspection deprecation Only acceptable place to read the old pin.
|
||||
String registrationLockPin = TextSecurePreferences.getDeprecatedV1RegistrationLockPin(context);
|
||||
String pinValue = TextSecurePreferences.getDeprecatedV1RegistrationLockPin(context);
|
||||
|
||||
if (registrationLockPin == null | TextUtils.isEmpty(registrationLockPin)) {
|
||||
if (pinValue == null | TextUtils.isEmpty(pinValue)) {
|
||||
Log.i(TAG, "No old pin to migrate");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Migrating pin to Key Backup Service");
|
||||
|
||||
try {
|
||||
RegistrationLockData registrationPinV2Key = ApplicationDependencies.getKeyBackupService()
|
||||
.newPinChangeSession()
|
||||
.setPin(registrationLockPin);
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
||||
HashedPin hashedPin = PinHashing.hashPin(pinValue, pinChangeSession);
|
||||
RegistrationLockData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||
RegistrationLockData restoredData = keyBackupService.newRestoreSession(kbsData.getTokenResponse())
|
||||
.restorePin(hashedPin);
|
||||
|
||||
SignalStore.kbsValues().setRegistrationLockMasterKey(registrationPinV2Key);
|
||||
TextSecurePreferences.clearOldRegistrationLockPin(context);
|
||||
} catch (InvalidPinException e) {
|
||||
Log.w(TAG, "The V1 pin cannot be migrated.", e);
|
||||
return;
|
||||
if (!restoredData.getMasterKey().equals(masterKey)) {
|
||||
throw new RuntimeException("Failed to migrate the pin correctly");
|
||||
} else {
|
||||
Log.i(TAG, "Set and retrieved pin on KBS successfully");
|
||||
}
|
||||
|
||||
kbsValues.setRegistrationLockMasterKey(restoredData, PinHashing.localPinHash(pinValue));
|
||||
TextSecurePreferences.clearOldRegistrationLockPin(context);
|
||||
|
||||
Log.i(TAG, "Pin migrated to Key Backup Service");
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,12 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
|
||||
disablePassphrase = (CheckBoxPreference) this.findPreference("pref_enable_passphrase_temporary");
|
||||
|
||||
this.findPreference(TextSecurePreferences.REGISTRATION_LOCK_PREF_V1).setOnPreferenceClickListener(new AccountLockClickListener());
|
||||
SwitchPreferenceCompat regLock = (SwitchPreferenceCompat) this.findPreference(TextSecurePreferences.REGISTRATION_LOCK_PREF_V1);
|
||||
regLock.setChecked(
|
||||
TextSecurePreferences.isV1RegistrationLockEnabled(requireContext()) || SignalStore.kbsValues().isV2RegistrationLockEnabled()
|
||||
);
|
||||
regLock.setOnPreferenceClickListener(new AccountLockClickListener());
|
||||
|
||||
this.findPreference(TextSecurePreferences.SCREEN_LOCK).setOnPreferenceChangeListener(new ScreenLockListener());
|
||||
this.findPreference(TextSecurePreferences.SCREEN_LOCK_TIMEOUT).setOnPreferenceClickListener(new ScreenLockTimeoutListener());
|
||||
|
||||
@@ -149,10 +154,12 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
private class AccountLockClickListener implements Preference.OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
if (((SwitchPreferenceCompat)preference).isChecked()) {
|
||||
RegistrationLockDialog.showRegistrationUnlockPrompt(requireContext(), (SwitchPreferenceCompat)preference);
|
||||
Context context = requireContext();
|
||||
|
||||
if (TextSecurePreferences.isV1RegistrationLockEnabled(context) || SignalStore.kbsValues().isV2RegistrationLockEnabled()) {
|
||||
RegistrationLockDialog.showRegistrationUnlockPrompt(context, (SwitchPreferenceCompat)preference);
|
||||
} else {
|
||||
RegistrationLockDialog.showRegistrationLockPrompt(requireContext(), (SwitchPreferenceCompat)preference);
|
||||
RegistrationLockDialog.showRegistrationLockPrompt(context, (SwitchPreferenceCompat)preference);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -17,7 +17,9 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
|
||||
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.logging.Log;
|
||||
import org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob;
|
||||
@@ -36,11 +38,12 @@ import org.whispersystems.signalservice.api.KeyBackupService;
|
||||
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||
import org.whispersystems.signalservice.api.RegistrationLockData;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
import org.whispersystems.signalservice.internal.push.LockedException;
|
||||
import org.whispersystems.signalservice.internal.registrationpin.InvalidPinException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -160,11 +163,12 @@ public final class CodeVerificationRequest {
|
||||
|
||||
SignalServiceAccountManager accountManager = AccountManagerFactory.createUnauthenticated(context, credentials.getE164number(), credentials.getPassword());
|
||||
RegistrationLockData kbsData = restoreMasterKey(pin, basicStorageCredentials, kbsTokenResponse);
|
||||
String registrationLock = kbsData != null ? kbsData.getMasterKey().getRegistrationLock() : null;
|
||||
String registrationLock = kbsData != null ? kbsData.getMasterKey().deriveRegistrationLock() : null;
|
||||
boolean present = fcmToken != null;
|
||||
String pinForServer = basicStorageCredentials == null ? pin : null;
|
||||
|
||||
UUID uuid = accountManager.verifyAccountWithCode(code, null, registrationId, !present,
|
||||
pin, registrationLock,
|
||||
pinForServer, registrationLock,
|
||||
unidentifiedAccessKey, universalUnidentifiedAccess);
|
||||
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context);
|
||||
@@ -203,8 +207,8 @@ public final class CodeVerificationRequest {
|
||||
TextSecurePreferences.setSignedPreKeyRegistered(context, true);
|
||||
TextSecurePreferences.setPromptedPushRegistration(context, true);
|
||||
TextSecurePreferences.setUnauthorizedReceived(context, false);
|
||||
SignalStore.kbsValues().setRegistrationLockMasterKey(kbsData);
|
||||
if (kbsData == null) {
|
||||
SignalStore.kbsValues().clearRegistrationLock();
|
||||
//noinspection deprecation Only acceptable place to write the old pin.
|
||||
TextSecurePreferences.setDeprecatedRegistrationLockPin(context, pin);
|
||||
//noinspection deprecation Only acceptable place to write the old pin enabled state.
|
||||
@@ -216,6 +220,7 @@ public final class CodeVerificationRequest {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
SignalStore.kbsValues().setRegistrationLockMasterKey(kbsData, PinHashing.localPinHash(pin));
|
||||
repostPinToResetTries(context, pin, kbsData);
|
||||
}
|
||||
if (pin != null) {
|
||||
@@ -227,19 +232,23 @@ public final class CodeVerificationRequest {
|
||||
private static void repostPinToResetTries(@NonNull Context context, @Nullable String pin, @NonNull RegistrationLockData kbsData) {
|
||||
if (!FeatureFlags.KBS) return;
|
||||
|
||||
if (pin == null) return;
|
||||
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
|
||||
try {
|
||||
RegistrationLockData newData = keyBackupService.newPinChangeSession(kbsData.getTokenResponse())
|
||||
.setPin(pin);
|
||||
SignalStore.kbsValues().setRegistrationLockMasterKey(newData);
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession(kbsData.getTokenResponse());
|
||||
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
|
||||
RegistrationLockData newData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||
|
||||
kbsValues.setRegistrationLockMasterKey(newData, PinHashing.localPinHash(pin));
|
||||
TextSecurePreferences.clearOldRegistrationLockPin(context);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "May have failed to reset pin attempts!", e);
|
||||
} catch (UnauthenticatedResponseException e) {
|
||||
Log.w(TAG, "Failed to reset pin attempts", e);
|
||||
} catch (InvalidPinException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +276,8 @@ public final class CodeVerificationRequest {
|
||||
|
||||
try {
|
||||
Log.i(TAG, "Restoring pin from KBS");
|
||||
RegistrationLockData kbsData = session.restorePin(pin);
|
||||
HashedPin hashedPin = PinHashing.hashPin(pin, session);
|
||||
RegistrationLockData kbsData = session.restorePin(hashedPin);
|
||||
if (kbsData != null) {
|
||||
Log.i(TAG, "Found registration lock token on KBS.");
|
||||
} else {
|
||||
@@ -280,9 +290,6 @@ public final class CodeVerificationRequest {
|
||||
} catch (KeyBackupServicePinException e) {
|
||||
Log.w(TAG, "Incorrect pin", e);
|
||||
throw new KeyBackupSystemWrongPinException(e.getToken());
|
||||
} catch (InvalidPinException e) {
|
||||
Log.w(TAG, "Invalid pin", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ public class TextSecurePreferences {
|
||||
@Deprecated
|
||||
private static final String REGISTRATION_LOCK_PIN_PREF_V1 = "pref_registration_lock_pin";
|
||||
|
||||
private static final String REGISTRATION_LOCK_LAST_REMINDER_TIME = "pref_registration_lock_last_reminder_time";
|
||||
private static final String REGISTRATION_LOCK_LAST_REMINDER_TIME = "pref_registration_lock_last_reminder_time_2";
|
||||
private static final String REGISTRATION_LOCK_NEXT_REMINDER_INTERVAL = "pref_registration_lock_next_reminder_interval";
|
||||
|
||||
private static final String SERVICE_OUTAGE = "pref_service_outage";
|
||||
|
||||
Reference in New Issue
Block a user