Improve local encrypted PIN storage.

This commit is contained in:
Greyson Parrelli 2020-05-29 18:16:38 -04:00
parent 61fe6cc961
commit 79dbf85c1e
8 changed files with 99 additions and 22 deletions

View File

@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.migrations.DatabaseMigrationJob;
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob; import org.thoughtcrime.securesms.migrations.LegacyMigrationJob;
import org.thoughtcrime.securesms.migrations.MigrationCompleteJob; import org.thoughtcrime.securesms.migrations.MigrationCompleteJob;
import org.thoughtcrime.securesms.migrations.PassingMigrationJob; import org.thoughtcrime.securesms.migrations.PassingMigrationJob;
import org.thoughtcrime.securesms.migrations.PinReminderMigrationJob;
import org.thoughtcrime.securesms.migrations.RecipientSearchMigrationJob; import org.thoughtcrime.securesms.migrations.RecipientSearchMigrationJob;
import org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob; import org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob;
import org.thoughtcrime.securesms.migrations.StickerAdditionMigrationJob; import org.thoughtcrime.securesms.migrations.StickerAdditionMigrationJob;
@ -124,6 +125,7 @@ public final class JobManagerFactories {
put(DatabaseMigrationJob.KEY, new DatabaseMigrationJob.Factory()); put(DatabaseMigrationJob.KEY, new DatabaseMigrationJob.Factory());
put(LegacyMigrationJob.KEY, new LegacyMigrationJob.Factory()); put(LegacyMigrationJob.KEY, new LegacyMigrationJob.Factory());
put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory()); put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory());
put(PinReminderMigrationJob.KEY, new PinReminderMigrationJob.Factory());
put(RecipientSearchMigrationJob.KEY, new RecipientSearchMigrationJob.Factory()); put(RecipientSearchMigrationJob.KEY, new RecipientSearchMigrationJob.Factory());
put(RegistrationPinV2MigrationJob.KEY, new RegistrationPinV2MigrationJob.Factory()); put(RegistrationPinV2MigrationJob.KEY, new RegistrationPinV2MigrationJob.Factory());
put(StickerLaunchMigrationJob.KEY, new StickerLaunchMigrationJob.Factory()); put(StickerLaunchMigrationJob.KEY, new StickerLaunchMigrationJob.Factory());

View File

@ -3,8 +3,7 @@ package org.thoughtcrime.securesms.keyvalue;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.lock.PinHashing;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.JsonUtils;
import org.whispersystems.signalservice.api.KbsPinData; import org.whispersystems.signalservice.api.KbsPinData;
import org.whispersystems.signalservice.api.kbs.MasterKey; import org.whispersystems.signalservice.api.kbs.MasterKey;
@ -18,6 +17,7 @@ public final class KbsValues {
public static final String V2_LOCK_ENABLED = "kbs.v2_lock_enabled"; public 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 MASTER_KEY = "kbs.registration_lock_master_key";
private static final String TOKEN_RESPONSE = "kbs.token_response"; private static final String TOKEN_RESPONSE = "kbs.token_response";
private static final String PIN = "kbs.pin";
private static final String LOCK_LOCAL_PIN_HASH = "kbs.registration_lock_local_pin_hash"; private static final String LOCK_LOCAL_PIN_HASH = "kbs.registration_lock_local_pin_hash";
private static final String LAST_CREATE_FAILED_TIMESTAMP = "kbs.last_create_failed_timestamp"; private static final String LAST_CREATE_FAILED_TIMESTAMP = "kbs.last_create_failed_timestamp";
@ -37,12 +37,13 @@ public final class KbsValues {
.remove(V2_LOCK_ENABLED) .remove(V2_LOCK_ENABLED)
.remove(TOKEN_RESPONSE) .remove(TOKEN_RESPONSE)
.remove(LOCK_LOCAL_PIN_HASH) .remove(LOCK_LOCAL_PIN_HASH)
.remove(PIN)
.remove(LAST_CREATE_FAILED_TIMESTAMP) .remove(LAST_CREATE_FAILED_TIMESTAMP)
.commit(); .commit();
} }
/** Should only be set by {@link org.thoughtcrime.securesms.pin.PinState}. */ /** Should only be set by {@link org.thoughtcrime.securesms.pin.PinState}. */
public synchronized void setKbsMasterKey(@NonNull KbsPinData pinData, @NonNull String localPinHash) { public synchronized void setKbsMasterKey(@NonNull KbsPinData pinData, @NonNull String pin) {
MasterKey masterKey = pinData.getMasterKey(); MasterKey masterKey = pinData.getMasterKey();
String tokenResponse; String tokenResponse;
try { try {
@ -54,11 +55,18 @@ public final class KbsValues {
store.beginWrite() store.beginWrite()
.putString(TOKEN_RESPONSE, tokenResponse) .putString(TOKEN_RESPONSE, tokenResponse)
.putBlob(MASTER_KEY, masterKey.serialize()) .putBlob(MASTER_KEY, masterKey.serialize())
.putString(LOCK_LOCAL_PIN_HASH, localPinHash) .putString(LOCK_LOCAL_PIN_HASH, PinHashing.localPinHash(pin))
.putString(PIN, pin)
.putLong(LAST_CREATE_FAILED_TIMESTAMP, -1) .putLong(LAST_CREATE_FAILED_TIMESTAMP, -1)
.commit(); .commit();
} }
synchronized void setPinIfNotPresent(@NonNull String pin) {
if (store.getString(PIN, null) == null) {
store.beginWrite().putString(PIN, pin).commit();
}
}
/** Should only be set by {@link org.thoughtcrime.securesms.pin.PinState}. */ /** Should only be set by {@link org.thoughtcrime.securesms.pin.PinState}. */
public synchronized void setV2RegistrationLockEnabled(boolean enabled) { public synchronized void setV2RegistrationLockEnabled(boolean enabled) {
store.beginWrite().putBoolean(V2_LOCK_ENABLED, enabled).apply(); store.beginWrite().putBoolean(V2_LOCK_ENABLED, enabled).apply();

View File

@ -28,7 +28,7 @@ public final class PinValues {
this.store = store; this.store = store;
} }
public void onEntrySuccess() { public void onEntrySuccess(@NonNull String pin) {
long nextInterval = SignalPinReminders.getNextInterval(getCurrentInterval()); long nextInterval = SignalPinReminders.getNextInterval(getCurrentInterval());
Log.i(TAG, "onEntrySuccess() nextInterval: " + nextInterval); Log.i(TAG, "onEntrySuccess() nextInterval: " + nextInterval);
@ -36,9 +36,11 @@ public final class PinValues {
.putLong(LAST_SUCCESSFUL_ENTRY, System.currentTimeMillis()) .putLong(LAST_SUCCESSFUL_ENTRY, System.currentTimeMillis())
.putLong(NEXT_INTERVAL, nextInterval) .putLong(NEXT_INTERVAL, nextInterval)
.apply(); .apply();
SignalStore.kbsValues().setPinIfNotPresent(pin);
} }
public void onEntrySuccessWithWrongGuess() { public void onEntrySuccessWithWrongGuess(@NonNull String pin) {
long nextInterval = SignalPinReminders.getPreviousInterval(getCurrentInterval()); long nextInterval = SignalPinReminders.getPreviousInterval(getCurrentInterval());
Log.i(TAG, "onEntrySuccessWithWrongGuess() nextInterval: " + nextInterval); Log.i(TAG, "onEntrySuccessWithWrongGuess() nextInterval: " + nextInterval);
@ -46,6 +48,8 @@ public final class PinValues {
.putLong(LAST_SUCCESSFUL_ENTRY, System.currentTimeMillis()) .putLong(LAST_SUCCESSFUL_ENTRY, System.currentTimeMillis())
.putLong(NEXT_INTERVAL, nextInterval) .putLong(NEXT_INTERVAL, nextInterval)
.apply(); .apply();
SignalStore.kbsValues().setPinIfNotPresent(pin);
} }
public void onEntrySkipWithWrongGuess() { public void onEntrySkipWithWrongGuess() {
@ -93,6 +97,14 @@ public final class PinValues {
return PinKeyboardType.fromCode(store.getString(KEYBOARD_TYPE, null)); return PinKeyboardType.fromCode(store.getString(KEYBOARD_TYPE, null));
} }
public void setNextReminderIntervalToAtMost(long maxInterval) {
if (store.getLong(NEXT_INTERVAL, 0) > maxInterval) {
store.beginWrite()
.putLong(NEXT_INTERVAL, maxInterval)
.apply();
}
}
/** Should only be set by {@link org.thoughtcrime.securesms.pin.PinState} */ /** Should only be set by {@link org.thoughtcrime.securesms.pin.PinState} */
public void setPinState(@NonNull String pinState) { public void setPinState(@NonNull String pinState) {
store.beginWrite().putString(PIN_STATE, pinState).commit(); store.beginWrite().putString(PIN_STATE, pinState).commit();

View File

@ -130,7 +130,7 @@ public final class SignalPinReminderDialog {
if (PinHashing.verifyLocalPinHash(localHash, text)) { if (PinHashing.verifyLocalPinHash(localHash, text)) {
dialog.dismiss(); dialog.dismiss();
mainCallback.onReminderCompleted(callback.hadWrongGuess()); mainCallback.onReminderCompleted(text, callback.hadWrongGuess());
} }
} else { } else {
submit.setEnabled(false); submit.setEnabled(false);
@ -149,10 +149,10 @@ public final class SignalPinReminderDialog {
boolean hadWrongGuess = false; boolean hadWrongGuess = false;
@Override @Override
public void onPinCorrect() { public void onPinCorrect(@NonNull String pin) {
Log.i(TAG, "Correct PIN entry."); Log.i(TAG, "Correct PIN entry.");
dialog.dismiss(); dialog.dismiss();
mainCallback.onReminderCompleted(hadWrongGuess); mainCallback.onReminderCompleted(pin, hadWrongGuess);
} }
@Override @Override
@ -188,7 +188,7 @@ public final class SignalPinReminderDialog {
if (pin.length() < KbsConstants.MINIMUM_PIN_LENGTH) return; if (pin.length() < KbsConstants.MINIMUM_PIN_LENGTH) return;
if (PinHashing.verifyLocalPinHash(localPinHash, pin)) { if (PinHashing.verifyLocalPinHash(localPinHash, pin)) {
callback.onPinCorrect(); callback.onPinCorrect(pin);
} else { } else {
callback.onPinWrong(); callback.onPinWrong();
} }
@ -200,7 +200,7 @@ public final class SignalPinReminderDialog {
void verifyPin(@Nullable String pin, @NonNull PinVerifier.Callback callback); void verifyPin(@Nullable String pin, @NonNull PinVerifier.Callback callback);
interface Callback { interface Callback {
void onPinCorrect(); void onPinCorrect(@NonNull String pin);
void onPinWrong(); void onPinWrong();
boolean hadWrongGuess(); boolean hadWrongGuess();
} }
@ -212,6 +212,6 @@ public final class SignalPinReminderDialog {
public interface Callback { public interface Callback {
void onReminderDismissed(boolean includedFailure); void onReminderDismissed(boolean includedFailure);
void onReminderCompleted(boolean includedFailure); void onReminderCompleted(@NonNull String pin, boolean includedFailure);
} }
} }

View File

@ -164,12 +164,12 @@ public final class Megaphones {
} }
@Override @Override
public void onReminderCompleted(boolean includedFailure) { public void onReminderCompleted(@NonNull String pin, boolean includedFailure) {
Log.i(TAG, "[PinReminder] onReminderCompleted(" + includedFailure + ")"); Log.i(TAG, "[PinReminder] onReminderCompleted(" + includedFailure + ")");
if (includedFailure) { if (includedFailure) {
SignalStore.pinValues().onEntrySuccessWithWrongGuess(); SignalStore.pinValues().onEntrySuccessWithWrongGuess(pin);
} else { } else {
SignalStore.pinValues().onEntrySuccess(); SignalStore.pinValues().onEntrySuccess(pin);
} }
controller.onMegaphoneSnooze(Event.PIN_REMINDER); controller.onMegaphoneSnooze(Event.PIN_REMINDER);

View File

@ -39,7 +39,7 @@ public class ApplicationMigrations {
private static final int LEGACY_CANONICAL_VERSION = 455; private static final int LEGACY_CANONICAL_VERSION = 455;
public static final int CURRENT_VERSION = 14; public static final int CURRENT_VERSION = 15;
private static final class Version { private static final class Version {
static final int LEGACY = 1; static final int LEGACY = 1;
@ -56,6 +56,7 @@ public class ApplicationMigrations {
static final int STORAGE_KEY_ROTATE = 12; static final int STORAGE_KEY_ROTATE = 12;
static final int REMOVE_AVATAR_ID = 13; static final int REMOVE_AVATAR_ID = 13;
static final int STORAGE_CAPABILITY = 14; static final int STORAGE_CAPABILITY = 14;
static final int PIN_REMINDER = 15;
} }
/** /**
@ -226,6 +227,10 @@ public class ApplicationMigrations {
jobs.put(Version.STORAGE_CAPABILITY, new StorageCapabilityMigrationJob()); jobs.put(Version.STORAGE_CAPABILITY, new StorageCapabilityMigrationJob());
} }
if (lastSeenVersion < Version.PIN_REMINDER) {
jobs.put(Version.PIN_REMINDER, new PinReminderMigrationJob());
}
return jobs; return jobs;
} }

View File

@ -0,0 +1,50 @@
package org.thoughtcrime.securesms.migrations;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import java.util.concurrent.TimeUnit;
public class PinReminderMigrationJob extends MigrationJob {
public static final String KEY = "PinReminderMigrationJob";
PinReminderMigrationJob() {
this(new Job.Parameters.Builder().build());
}
private PinReminderMigrationJob(@NonNull Parameters parameters) {
super(parameters);
}
@Override
boolean isUiBlocking() {
return false;
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
void performMigration() {
SignalStore.pinValues().setNextReminderIntervalToAtMost(TimeUnit.DAYS.toMillis(3));
}
@Override
boolean shouldRetry(@NonNull Exception e) {
return false;
}
public static class Factory implements Job.Factory<PinReminderMigrationJob> {
@Override
public @NonNull PinReminderMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new PinReminderMigrationJob(parameters);
}
}
}

View File

@ -103,7 +103,7 @@ public final class PinState {
Log.i(TAG, "Registration Lock V2"); Log.i(TAG, "Registration Lock V2");
TextSecurePreferences.setV1RegistrationLockEnabled(context, false); TextSecurePreferences.setV1RegistrationLockEnabled(context, false);
SignalStore.kbsValues().setV2RegistrationLockEnabled(true); SignalStore.kbsValues().setV2RegistrationLockEnabled(true);
SignalStore.kbsValues().setKbsMasterKey(kbsData, PinHashing.localPinHash(pin)); SignalStore.kbsValues().setKbsMasterKey(kbsData, pin);
SignalStore.pinValues().resetPinReminders(); SignalStore.pinValues().resetPinReminders();
resetPinRetryCount(context, pin, kbsData); resetPinRetryCount(context, pin, kbsData);
} else if (hasPinToRestore) { } else if (hasPinToRestore) {
@ -124,7 +124,7 @@ public final class PinState {
* Invoked when the user is going through the PIN restoration flow (which is separate from reglock). * Invoked when the user is going through the PIN restoration flow (which is separate from reglock).
*/ */
public static synchronized void onSignalPinRestore(@NonNull Context context, @NonNull KbsPinData kbsData, @NonNull String pin) { public static synchronized void onSignalPinRestore(@NonNull Context context, @NonNull KbsPinData kbsData, @NonNull String pin) {
SignalStore.kbsValues().setKbsMasterKey(kbsData, PinHashing.localPinHash(pin)); SignalStore.kbsValues().setKbsMasterKey(kbsData, pin);
SignalStore.kbsValues().setV2RegistrationLockEnabled(false); SignalStore.kbsValues().setV2RegistrationLockEnabled(false);
SignalStore.pinValues().resetPinReminders(); SignalStore.pinValues().resetPinReminders();
SignalStore.storageServiceValues().setNeedsAccountRestore(false); SignalStore.storageServiceValues().setNeedsAccountRestore(false);
@ -160,7 +160,7 @@ public final class PinState {
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession); HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey); KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
kbsValues.setKbsMasterKey(kbsData, PinHashing.localPinHash(pin)); kbsValues.setKbsMasterKey(kbsData, pin);
TextSecurePreferences.clearRegistrationLockV1(context); TextSecurePreferences.clearRegistrationLockV1(context);
SignalStore.pinValues().setKeyboardType(keyboard); SignalStore.pinValues().setKeyboardType(keyboard);
SignalStore.pinValues().resetPinReminders(); SignalStore.pinValues().resetPinReminders();
@ -269,7 +269,7 @@ public final class PinState {
pinChangeSession.enableRegistrationLock(masterKey); pinChangeSession.enableRegistrationLock(masterKey);
kbsValues.setKbsMasterKey(kbsData, PinHashing.localPinHash(pin)); kbsValues.setKbsMasterKey(kbsData, pin);
kbsValues.setV2RegistrationLockEnabled(true); kbsValues.setV2RegistrationLockEnabled(true);
TextSecurePreferences.clearRegistrationLockV1(context); TextSecurePreferences.clearRegistrationLockV1(context);
TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis()); TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis());
@ -294,7 +294,7 @@ public final class PinState {
pinChangeSession.enableRegistrationLock(masterKey); pinChangeSession.enableRegistrationLock(masterKey);
kbsValues.setKbsMasterKey(kbsData, PinHashing.localPinHash(pin)); kbsValues.setKbsMasterKey(kbsData, pin);
TextSecurePreferences.clearRegistrationLockV1(context); TextSecurePreferences.clearRegistrationLockV1(context);
updateState(buildInferredStateFromOtherFields()); updateState(buildInferredStateFromOtherFields());
@ -333,7 +333,7 @@ public final class PinState {
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession); HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
KbsPinData newData = pinChangeSession.setPin(hashedPin, masterKey); KbsPinData newData = pinChangeSession.setPin(hashedPin, masterKey);
kbsValues.setKbsMasterKey(newData, PinHashing.localPinHash(pin)); kbsValues.setKbsMasterKey(newData, pin);
TextSecurePreferences.clearRegistrationLockV1(context); TextSecurePreferences.clearRegistrationLockV1(context);
Log.i(TAG, "Pin set/attempts reset on KBS"); Log.i(TAG, "Pin set/attempts reset on KBS");