diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 83496f0412..e1be422508 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -111,7 +111,6 @@ import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWra import java.io.InterruptedIOException; import java.net.SocketException; -import java.net.SocketTimeoutException; import java.security.Security; import java.util.HashMap; import java.util.Map; @@ -167,7 +166,7 @@ public class ApplicationContext extends Application implements AppForegroundObse .addBlocking("crash-handling", this::initializeCrashHandling) .addBlocking("rx-init", this::initializeRx) .addBlocking("event-bus", () -> EventBus.builder().logNoSubscriberMessages(false).installDefaultEventBus()) - .addBlocking("scrubber", () -> Scrubber.setIdentifierHmacKeyProvider(() -> SignalStore.svr().getOrCreateMasterKey().deriveLoggingKey())) + .addBlocking("scrubber", () -> Scrubber.setIdentifierHmacKeyProvider(() -> SignalStore.svr().getMasterKey().deriveLoggingKey())) .addBlocking("first-launch", this::initializeFirstEverAppLaunch) .addBlocking("app-migrations", this::initializeApplicationMigrations) .addBlocking("lifecycle-observer", () -> AppForegroundObserver.addListener(this)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt index a20376033e..8fd61e16d3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt @@ -717,7 +717,7 @@ object BackupRepository { } fun validate(length: Long, inputStreamFactory: () -> InputStream, selfData: SelfData): ValidationResult { - val masterKey = SignalStore.svr.getOrCreateMasterKey() + val masterKey = SignalStore.svr.masterKey val key = LibSignalMessageBackupKey(masterKey.serialize(), Aci.parseFromBinary(selfData.aci.toByteArray())) return MessageBackup.validate(key, MessageBackup.Purpose.REMOTE_BACKUP, inputStreamFactory, length) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt index a2d093d1db..985c4c5af3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt @@ -507,7 +507,7 @@ class ChangeNumberViewModel : ViewModel() { val currentState = store.value val code = currentState.enteredCode ?: throw IllegalStateException("Can't construct registration data without entered code!") val e164: String = number.e164Number ?: throw IllegalStateException("Can't construct registration data without E164!") - val recoveryPassword = if (currentState.sessionId == null) SignalStore.svr.getRecoveryPassword() else null + val recoveryPassword = if (currentState.sessionId == null) SignalStore.svr.recoveryPassword else null val fcmToken = RegistrationRepository.getFcmToken(context) return RegistrationData(code, e164, password, RegistrationRepository.getRegistrationId(), RegistrationRepository.getProfileKey(e164), fcmToken, RegistrationRepository.getPniRegistrationId(), recoveryPassword) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/svr/InternalSvrPlaygroundViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/svr/InternalSvrPlaygroundViewModel.kt index 72fed8590b..3c33fc94ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/svr/InternalSvrPlaygroundViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/svr/InternalSvrPlaygroundViewModel.kt @@ -52,7 +52,7 @@ class InternalSvrPlaygroundViewModel : ViewModel() { disposables += Single .fromCallable { _state.value.selected.toImplementation() - .setPin(_state.value.userPin, SignalStore.svr.getOrCreateMasterKey()) + .setPin(_state.value.userPin, SignalStore.svr.masterKey) .execute() } .subscribeOn(Schedulers.io()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceKeysUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceKeysUpdateJob.java index 0b468ef422..ffc49e2719 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceKeysUpdateJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceKeysUpdateJob.java @@ -11,7 +11,6 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.net.NotPushRegisteredException; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.multidevice.KeysMessage; @@ -72,7 +71,7 @@ public class MultiDeviceKeysUpdateJob extends BaseJob { SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender(); StorageKey storageServiceKey = SignalStore.storageService().getOrCreateStorageKey(); - messageSender.sendSyncMessage(SignalServiceSyncMessage.forKeys(new KeysMessage(Optional.ofNullable(storageServiceKey), Optional.of(SignalStore.svr().getOrCreateMasterKey()))) + messageSender.sendSyncMessage(SignalServiceSyncMessage.forKeys(new KeysMessage(Optional.ofNullable(storageServiceKey), Optional.of(SignalStore.svr().getMasterKey()))) ); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ResetSvrGuessCountJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/ResetSvrGuessCountJob.kt index ebd3379c4a..08cc83e6b2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ResetSvrGuessCountJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ResetSvrGuessCountJob.kt @@ -83,7 +83,7 @@ class ResetSvrGuessCountJob private constructor( return Result.success() } - val masterKey: MasterKey = SignalStore.svr.getOrCreateMasterKey() + val masterKey: MasterKey = SignalStore.svr.masterKey val svr3Result = if (svr3Complete) { Log.d(TAG, "Already reset guess count on SVR3. Skipping.") @@ -138,7 +138,7 @@ class ResetSvrGuessCountJob private constructor( authTokenSaver: (AuthCredentials) -> Unit ): Result { val session: PinChangeSession = if (serializedChangeSession != null) { - svr.resumePinChangeSession(pin, SignalStore.svr.getOrCreateMasterKey(), serializedChangeSession) + svr.resumePinChangeSession(pin, SignalStore.svr.masterKey, serializedChangeSession) } else { svr.setPin(pin, masterKey) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr2MirrorJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr2MirrorJob.kt index 7f00abf526..4c281ea0fd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr2MirrorJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr2MirrorJob.kt @@ -75,8 +75,8 @@ class Svr2MirrorJob private constructor(parameters: Parameters, private var seri val svr2: SecureValueRecoveryV2 = AppDependencies.signalServiceAccountManager.getSecureValueRecoveryV2(BuildConfig.SVR2_MRENCLAVE) val session: PinChangeSession = serializedChangeSession?.let { session -> - svr2.resumePinChangeSession(pin, SignalStore.svr.getOrCreateMasterKey(), session) - } ?: svr2.setPin(pin, SignalStore.svr.getOrCreateMasterKey()) + svr2.resumePinChangeSession(pin, SignalStore.svr.masterKey, session) + } ?: svr2.setPin(pin, SignalStore.svr.masterKey) serializedChangeSession = session.serialize() diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr3MirrorJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr3MirrorJob.kt index 3f11c63350..a2d5b90cf6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr3MirrorJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr3MirrorJob.kt @@ -72,8 +72,8 @@ class Svr3MirrorJob private constructor(parameters: Parameters, private var seri val svr3: SecureValueRecoveryV3 = AppDependencies.signalServiceAccountManager.getSecureValueRecoveryV3(AppDependencies.libsignalNetwork) val session: PinChangeSession = serializedChangeSession?.let { session -> - svr3.resumePinChangeSession(pin, SignalStore.svr.getOrCreateMasterKey(), session) - } ?: svr3.setPin(pin, SignalStore.svr.getOrCreateMasterKey()) + svr3.resumePinChangeSession(pin, SignalStore.svr.masterKey, session) + } ?: svr3.setPin(pin, SignalStore.svr.masterKey) serializedChangeSession = session.serialize() diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/BackupValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/BackupValues.kt index 9320355b1c..b21ccfd312 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/BackupValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/BackupValues.kt @@ -93,7 +93,7 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) { * Key used to backup messages. */ val messageBackupKey: MessageBackupKey - get() = SignalStore.svr.getOrCreateMasterKey().derivateMessageBackupKey() + get() = SignalStore.svr.masterKey.derivateMessageBackupKey() /** * Key used to backup media. Purely random and separate from the message backup key. @@ -108,14 +108,14 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) { Log.i(TAG, "Generating MediaRootBackupKey...", Throwable()) val bytes = Util.getSecretBytes(32) - putBlob(KEY_MEDIA_ROOT_BACKUP_KEY, bytes) + store.beginWrite().putBlob(KEY_MEDIA_ROOT_BACKUP_KEY, bytes).commit() return MediaRootBackupKey(bytes) } } set(value) { lock.withLock { Log.i(TAG, "Setting MediaRootBackupKey", Throwable()) - putBlob(KEY_MEDIA_ROOT_BACKUP_KEY, value.value) + store.beginWrite().putBlob(KEY_MEDIA_ROOT_BACKUP_KEY, value.value).commit() mediaCredentials.clearAll() cachedMediaCdnPath = null } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/StorageServiceValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/StorageServiceValues.java index ff54c8779d..e38f4e33ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/StorageServiceValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/StorageServiceValues.java @@ -33,7 +33,7 @@ public class StorageServiceValues extends SignalStoreValues { if (getStore().containsKey(SYNC_STORAGE_KEY)) { return new StorageKey(getBlob(SYNC_STORAGE_KEY, null)); } - return SignalStore.svr().getOrCreateMasterKey().deriveStorageServiceKey(); + return SignalStore.svr().getMasterKey().deriveStorageServiceKey(); } public long getLastSyncTime() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SvrValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SvrValues.java deleted file mode 100644 index 8f40ede5b5..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SvrValues.java +++ /dev/null @@ -1,278 +0,0 @@ -package org.thoughtcrime.securesms.keyvalue; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.signal.core.util.StringStringSerializer; -import org.thoughtcrime.securesms.util.JsonUtils; -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; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public final class SvrValues extends SignalStoreValues { - - public static final String REGISTRATION_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 PIN = "kbs.pin"; - 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"; - public static final String OPTED_OUT = "kbs.opted_out"; - private static final String PIN_FORGOTTEN_OR_SKIPPED = "kbs.pin.forgotten.or.skipped"; - private static final String SVR2_AUTH_TOKENS = "kbs.kbs_auth_tokens"; - private static final String SVR_LAST_AUTH_REFRESH_TIMESTAMP = "kbs.kbs_auth_tokens.last_refresh_timestamp"; - private static final String SVR3_AUTH_TOKENS = "kbs.svr3_auth_tokens"; - - SvrValues(KeyValueStore store) { - super(store); - } - - @Override - void onFirstEverAppLaunch() { - } - - @Override - @NonNull - List getKeysToIncludeInBackup() { - return List.of( - SVR2_AUTH_TOKENS, - SVR3_AUTH_TOKENS - ); - } - - /** - * Deliberately does not clear the {@link #MASTER_KEY}. - */ - public void clearRegistrationLockAndPin() { - getStore().beginWrite() - .remove(REGISTRATION_LOCK_ENABLED) - .remove(TOKEN_RESPONSE) - .remove(LOCK_LOCAL_PIN_HASH) - .remove(PIN) - .remove(LAST_CREATE_FAILED_TIMESTAMP) - .remove(OPTED_OUT) - .remove(SVR2_AUTH_TOKENS) - .remove(SVR_LAST_AUTH_REFRESH_TIMESTAMP) - .commit(); - } - - public synchronized void setMasterKey(@NonNull MasterKey masterKey, @NonNull String pin) { - getStore().beginWrite() - .putBlob(MASTER_KEY, masterKey.serialize()) - .putString(LOCK_LOCAL_PIN_HASH, PinHashUtil.localPinHash(pin)) - .putString(PIN, pin) - .putLong(LAST_CREATE_FAILED_TIMESTAMP, -1) - .putBoolean(OPTED_OUT, false) - .commit(); - } - - synchronized void setPinIfNotPresent(@NonNull String pin) { - if (getStore().getString(PIN, null) == null) { - getStore().beginWrite().putString(PIN, pin).commit(); - } - } - - public synchronized void setRegistrationLockEnabled(boolean enabled) { - putBoolean(REGISTRATION_LOCK_ENABLED, enabled); - } - - /** - * Whether or not registration lock V2 is enabled. - */ - public synchronized boolean isRegistrationLockEnabled() { - return getBoolean(REGISTRATION_LOCK_ENABLED, false); - } - - public synchronized void onPinCreateFailure() { - putLong(LAST_CREATE_FAILED_TIMESTAMP, System.currentTimeMillis()); - } - - /** - * Whether or not the last time the user attempted to create a PIN, it failed. - */ - public synchronized boolean lastPinCreateFailed() { - return getLong(LAST_CREATE_FAILED_TIMESTAMP, -1) > 0; - } - - /** - * Finds or creates the master key. Therefore this will always return a master key whether backed - * up or not. - *

- * If you only want a key when it's backed up, use {@link #getPinBackedMasterKey()}. - */ - public synchronized @NonNull MasterKey getOrCreateMasterKey() { - byte[] blob = getStore().getBlob(MASTER_KEY, null); - - if (blob == null) { - getStore().beginWrite() - .putBlob(MASTER_KEY, MasterKey.createNew(new SecureRandom()).serialize()) - .commit(); - blob = 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 (!isRegistrationLockEnabled()) return null; - return getMasterKey(); - } - - private synchronized @Nullable MasterKey getMasterKey() { - byte[] blob = getBlob(MASTER_KEY, null); - return blob != null ? new MasterKey(blob) : null; - } - - public @Nullable String getRegistrationLockToken() { - MasterKey masterKey = getPinBackedMasterKey(); - if (masterKey == null) { - return null; - } else { - return masterKey.deriveRegistrationLock(); - } - } - - public synchronized @Nullable String getRecoveryPassword() { - MasterKey masterKey = getMasterKey(); - if (masterKey != null && hasPin()) { - return masterKey.deriveRegistrationRecoveryPassword(); - } else { - return null; - } - } - - public synchronized @Nullable String getPin() { - return getString(PIN, null); - } - - public synchronized @Nullable String getLocalPinHash() { - return getString(LOCK_LOCAL_PIN_HASH, null); - } - - public synchronized boolean hasPin() { - return getLocalPinHash() != null; - } - - public synchronized boolean isPinForgottenOrSkipped() { - return getBoolean(PIN_FORGOTTEN_OR_SKIPPED, false); - } - - public synchronized void setPinForgottenOrSkipped(boolean value) { - putBoolean(PIN_FORGOTTEN_OR_SKIPPED, value); - } - - public synchronized void putSvr2AuthTokens(List tokens) { - putList(SVR2_AUTH_TOKENS, tokens, StringStringSerializer.INSTANCE); - setLastRefreshAuthTimestamp(System.currentTimeMillis()); - } - - public synchronized void putSvr3AuthTokens(List tokens) { - putList(SVR3_AUTH_TOKENS, tokens, StringStringSerializer.INSTANCE); - setLastRefreshAuthTimestamp(System.currentTimeMillis()); - } - - public synchronized List getSvr2AuthTokens() { - return getList(SVR2_AUTH_TOKENS, StringStringSerializer.INSTANCE); - } - - public synchronized List getSvr3AuthTokens() { - return getList(SVR3_AUTH_TOKENS, StringStringSerializer.INSTANCE); - } - - /** - * Keeps the 10 most recent KBS auth tokens. - * @param token - * @return whether the token was added (new) or ignored (already existed) - */ - public synchronized boolean appendSvr2AuthTokenToList(String token) { - List tokens = getSvr2AuthTokens(); - if (tokens.contains(token)) { - return false; - } else { - final List result = Stream.concat(Stream.of(token), tokens.stream()).limit(10).collect(Collectors.toList()); - putSvr2AuthTokens(result); - return true; - } - } - - /** - * Keeps the 10 most recent SVR3 auth tokens. - * @param token - * @return whether the token was added (new) or ignored (already existed) - */ - public synchronized boolean appendSvr3AuthTokenToList(String token) { - List tokens = getSvr3AuthTokens(); - if (tokens.contains(token)) { - return false; - } else { - final List result = Stream.concat(Stream.of(token), tokens.stream()).limit(10).collect(Collectors.toList()); - putSvr3AuthTokens(result); - return true; - } - } - - public boolean removeSvr2AuthTokens(@NonNull List invalid) { - List tokens = new ArrayList<>(getSvr2AuthTokens()); - if (tokens.removeAll(invalid)) { - putSvr2AuthTokens(tokens); - return true; - } - - return false; - } - - public boolean removeSvr3AuthTokens(@NonNull List invalid) { - List tokens = new ArrayList<>(getSvr3AuthTokens()); - if (tokens.removeAll(invalid)) { - putSvr3AuthTokens(tokens); - return true; - } - - return false; - } - - public synchronized void optOut() { - getStore().beginWrite() - .putBoolean(OPTED_OUT, true) - .remove(TOKEN_RESPONSE) - .putBlob(MASTER_KEY, MasterKey.createNew(new SecureRandom()).serialize()) - .remove(LOCK_LOCAL_PIN_HASH) - .remove(PIN) - .putLong(LAST_CREATE_FAILED_TIMESTAMP, -1) - .commit(); - } - - public synchronized boolean hasOptedOut() { - return getBoolean(OPTED_OUT, false); - } - - public synchronized @Nullable TokenResponse getRegistrationLockTokenResponse() { - String token = getStore().getString(TOKEN_RESPONSE, null); - - if (token == null) return null; - - try { - return JsonUtils.fromJson(token, TokenResponse.class); - } catch (IOException e) { - throw new AssertionError(e); - } - } - - private void setLastRefreshAuthTimestamp(long timestamp) { - putLong(SVR_LAST_AUTH_REFRESH_TIMESTAMP, timestamp); - } - - public long getLastRefreshAuthTimestamp() { - return getLong(SVR_LAST_AUTH_REFRESH_TIMESTAMP, 0L); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SvrValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SvrValues.kt new file mode 100644 index 0000000000..53dd94191b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SvrValues.kt @@ -0,0 +1,254 @@ +package org.thoughtcrime.securesms.keyvalue + +import org.signal.core.util.StringStringSerializer +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.util.JsonUtils +import org.whispersystems.signalservice.api.kbs.MasterKey +import org.whispersystems.signalservice.api.kbs.PinHashUtil.localPinHash +import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse +import java.io.IOException +import java.security.SecureRandom + +class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(store) { + companion object { + private val TAG = Log.tag(SvrValues::class) + + const val REGISTRATION_LOCK_ENABLED: String = "kbs.v2_lock_enabled" + const val OPTED_OUT: String = "kbs.opted_out" + + private const val MASTER_KEY = "kbs.registration_lock_master_key" + private const val TOKEN_RESPONSE = "kbs.token_response" + private const val PIN = "kbs.pin" + private const val LOCK_LOCAL_PIN_HASH = "kbs.registration_lock_local_pin_hash" + private const val LAST_CREATE_FAILED_TIMESTAMP = "kbs.last_create_failed_timestamp" + private const val PIN_FORGOTTEN_OR_SKIPPED = "kbs.pin.forgotten.or.skipped" + private const val SVR2_AUTH_TOKENS = "kbs.kbs_auth_tokens" + private const val SVR_LAST_AUTH_REFRESH_TIMESTAMP = "kbs.kbs_auth_tokens.last_refresh_timestamp" + private const val SVR3_AUTH_TOKENS = "kbs.svr3_auth_tokens" + } + + public override fun onFirstEverAppLaunch() = Unit + + public override fun getKeysToIncludeInBackup(): List { + return listOf( + SVR2_AUTH_TOKENS, + SVR3_AUTH_TOKENS + ) + } + + /** Deliberately does not clear the [MASTER_KEY]. */ + @Synchronized + fun clearRegistrationLockAndPin() { + store.beginWrite() + .remove(REGISTRATION_LOCK_ENABLED) + .remove(TOKEN_RESPONSE) + .remove(LOCK_LOCAL_PIN_HASH) + .remove(PIN) + .remove(LAST_CREATE_FAILED_TIMESTAMP) + .remove(OPTED_OUT) + .remove(SVR2_AUTH_TOKENS) + .remove(SVR_LAST_AUTH_REFRESH_TIMESTAMP) + .commit() + } + + @Synchronized + fun setMasterKey(masterKey: MasterKey, pin: String) { + store.beginWrite() + .putBlob(MASTER_KEY, masterKey.serialize()) + .putString(LOCK_LOCAL_PIN_HASH, localPinHash(pin)) + .putString(PIN, pin) + .putLong(LAST_CREATE_FAILED_TIMESTAMP, -1) + .putBoolean(OPTED_OUT, false) + .commit() + } + + @Synchronized + fun setPinIfNotPresent(pin: String) { + if (store.getString(PIN, null) == null) { + store.beginWrite().putString(PIN, pin).commit() + } + } + + /** Whether or not registration lock V2 is enabled. */ + @get:Synchronized + @set:Synchronized + var isRegistrationLockEnabled: Boolean by booleanValue(REGISTRATION_LOCK_ENABLED, false) + + @Synchronized + fun onPinCreateFailure() { + putLong(LAST_CREATE_FAILED_TIMESTAMP, System.currentTimeMillis()) + } + + /** Whether or not the last time the user attempted to create a PIN, it failed. */ + @Synchronized + fun lastPinCreateFailed(): Boolean { + return getLong(LAST_CREATE_FAILED_TIMESTAMP, -1) > 0 + } + + @get:Synchronized + val masterKey: MasterKey + /** Returns the Master Key, lazily creating one if needed. */ + get() { + val blob = store.getBlob(MASTER_KEY, null) + if (blob != null) { + return MasterKey(blob) + } + + Log.i(TAG, "Generating Master Key...", Throwable()) + val masterKey = MasterKey.createNew(SecureRandom()) + store.beginWrite().putBlob(MASTER_KEY, masterKey.serialize()).commit() + return masterKey + } + + @get:Synchronized + val pinBackedMasterKey: MasterKey? + /** Returns null if master key is not backed up by a pin. */ + get() { + if (!isRegistrationLockEnabled) return null + return rawMasterKey + } + + @get:Synchronized + private val rawMasterKey: MasterKey? + get() = getBlob(MASTER_KEY, null)?.let { MasterKey(it) } + + @get:Synchronized + val registrationLockToken: String? + get() { + val masterKey = pinBackedMasterKey + return masterKey?.deriveRegistrationLock() + } + + @get:Synchronized + val recoveryPassword: String? + get() { + val masterKey = rawMasterKey + return if (masterKey != null && hasPin()) { + masterKey.deriveRegistrationRecoveryPassword() + } else { + null + } + } + + @get:Synchronized + val pin: String? by stringValue(PIN, null) + + @get:Synchronized + val localPinHash: String? by stringValue(LOCK_LOCAL_PIN_HASH, null) + + @Synchronized + fun hasPin(): Boolean { + return localPinHash != null + } + + @get:Synchronized + @set:Synchronized + var isPinForgottenOrSkipped: Boolean by booleanValue(PIN_FORGOTTEN_OR_SKIPPED, false) + + @Synchronized + fun putSvr2AuthTokens(tokens: List) { + putList(SVR2_AUTH_TOKENS, tokens, StringStringSerializer) + lastRefreshAuthTimestamp = System.currentTimeMillis() + } + + @Synchronized + fun putSvr3AuthTokens(tokens: List) { + putList(SVR3_AUTH_TOKENS, tokens, StringStringSerializer) + lastRefreshAuthTimestamp = System.currentTimeMillis() + } + + @get:Synchronized + val svr2AuthTokens: List + get() = getList(SVR2_AUTH_TOKENS, StringStringSerializer).requireNoNulls() + + @get:Synchronized + val svr3AuthTokens: List + get() = getList(SVR3_AUTH_TOKENS, StringStringSerializer).requireNoNulls() + + /** + * Keeps the 10 most recent KBS auth tokens. + * @param token + * @return whether the token was added (new) or ignored (already existed) + */ + @Synchronized + fun appendSvr2AuthTokenToList(token: String): Boolean { + val tokens = svr2AuthTokens + if (tokens.contains(token)) { + return false + } else { + val result = (listOf(token) + tokens).take(10) + putSvr2AuthTokens(result) + return true + } + } + + /** + * Keeps the 10 most recent SVR3 auth tokens. + * @param token + * @return whether the token was added (new) or ignored (already existed) + */ + @Synchronized + fun appendSvr3AuthTokenToList(token: String): Boolean { + val tokens = svr3AuthTokens + if (tokens.contains(token)) { + return false + } else { + val result = (listOf(token) + tokens).take(10) + putSvr3AuthTokens(result) + return true + } + } + + @Synchronized + fun removeSvr2AuthTokens(invalid: List): Boolean { + val tokens: MutableList = ArrayList(svr2AuthTokens) + if (tokens.removeAll(invalid)) { + putSvr2AuthTokens(tokens) + return true + } + + return false + } + + @Synchronized + fun removeSvr3AuthTokens(invalid: List): Boolean { + val tokens: MutableList = ArrayList(svr3AuthTokens) + if (tokens.removeAll(invalid)) { + putSvr3AuthTokens(tokens) + return true + } + + return false + } + + @Synchronized + fun optOut() { + store.beginWrite() + .putBoolean(OPTED_OUT, true) + .remove(TOKEN_RESPONSE) + .putBlob(MASTER_KEY, MasterKey.createNew(SecureRandom()).serialize()) + .remove(LOCK_LOCAL_PIN_HASH) + .remove(PIN) + .putLong(LAST_CREATE_FAILED_TIMESTAMP, -1) + .commit() + } + + @Synchronized + fun hasOptedOut(): Boolean { + return getBoolean(OPTED_OUT, false) + } + + @get:Synchronized + val registrationLockTokenResponse: TokenResponse? + get() { + val token = store.getString(TOKEN_RESPONSE, null) ?: return null + + try { + return JsonUtils.fromJson(token, TokenResponse::class.java) + } catch (e: IOException) { + throw AssertionError(e) + } + } + + var lastRefreshAuthTimestamp: Long by longValue(SVR_LAST_AUTH_REFRESH_TIMESTAMP, 0L) +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceRepository.kt index a833d1d4d4..0bee66e448 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceRepository.kt @@ -154,7 +154,7 @@ object LinkDeviceRepository { aciIdentityKeyPair = SignalStore.account.aciIdentityKey, pniIdentityKeyPair = SignalStore.account.pniIdentityKey, profileKey = ProfileKeyUtil.getSelfProfileKey(), - masterKey = SignalStore.svr.getOrCreateMasterKey(), + masterKey = SignalStore.svr.masterKey, code = verificationCodeResult.verificationCode, ephemeralMessageBackupKey = ephemeralMessageBackupKey ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/SvrRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/pin/SvrRepository.kt index 51f72e76d5..3739f66323 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/SvrRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/SvrRepository.kt @@ -235,7 +235,7 @@ object SvrRepository { @JvmStatic fun setPin(userPin: String, keyboardType: PinKeyboardType): BackupResponse { return operationLock.withLock { - val masterKey: MasterKey = SignalStore.svr.getOrCreateMasterKey() + val masterKey: MasterKey = SignalStore.svr.masterKey val writeTargets = writeImplementations @@ -367,7 +367,7 @@ object SvrRepository { check(SignalStore.svr.hasPin() && !SignalStore.svr.hasOptedOut()) { "Must have a PIN to set a registration lock!" } Log.i(TAG, "[enableRegistrationLockForUserWithPin] Enabling registration lock.", true) - AppDependencies.signalServiceAccountManager.enableRegistrationLock(SignalStore.svr.getOrCreateMasterKey()) + AppDependencies.signalServiceAccountManager.enableRegistrationLock(SignalStore.svr.masterKey) SignalStore.svr.isRegistrationLockEnabled = true Log.i(TAG, "[enableRegistrationLockForUserWithPin] Registration lock successfully enabled.", true) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationState.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationState.kt index 40ec6df291..9dfd1a7fac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationState.kt @@ -26,7 +26,7 @@ data class RegistrationState( val phoneNumber: Phonenumber.PhoneNumber? = fetchExistingE164FromValues(), val inProgress: Boolean = false, val isReRegister: Boolean = false, - val recoveryPassword: String? = SignalStore.svr.getRecoveryPassword(), + val recoveryPassword: String? = SignalStore.svr.recoveryPassword, val canSkipSms: Boolean = false, val svr2AuthCredentials: AuthCredentials? = null, val svr3AuthCredentials: Svr3Credentials? = null, diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt index 9044b81322..7ef39dcc65 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt @@ -619,7 +619,7 @@ class RegistrationViewModel : ViewModel() { if (RegistrationRepository.doesPinMatchLocalHash(pin)) { Log.d(TAG, "Found recovery password, attempting to re-register.") viewModelScope.launch(context = coroutineExceptionHandler) { - verifyReRegisterInternal(context, pin, SignalStore.svr.getOrCreateMasterKey()) + verifyReRegisterInternal(context, pin, SignalStore.svr.masterKey) setInProgress(false) } } else { @@ -789,7 +789,7 @@ class RegistrationViewModel : ViewModel() { reglock = true if (pin == null && SignalStore.svr.registrationLockToken != null) { Log.d(TAG, "Retrying registration with stored credentials.") - result = RegistrationRepository.registerAccount(context, sessionId, registrationData, SignalStore.svr.pin) { SignalStore.svr.getOrCreateMasterKey() } + result = RegistrationRepository.registerAccount(context, sessionId, registrationData, SignalStore.svr.pin) { SignalStore.svr.masterKey } } else if (result.svr2Credentials != null || result.svr3Credentials != null) { Log.d(TAG, "Retrying registration with received credentials (svr2: ${result.svr2Credentials != null}, svr3: ${result.svr3Credentials != null}).") val svr2Credentials = result.svr2Credentials @@ -894,7 +894,7 @@ class RegistrationViewModel : ViewModel() { val currentState = store.value val code = currentState.enteredCode val e164: String = currentState.phoneNumber?.toE164() ?: throw IllegalStateException("Can't construct registration data without E164!") - val recoveryPassword = if (currentState.sessionId == null) SignalStore.svr.getRecoveryPassword() else null + val recoveryPassword = if (currentState.sessionId == null) SignalStore.svr.recoveryPassword else null return RegistrationData(code, e164, password, RegistrationRepository.getRegistrationId(), RegistrationRepository.getProfileKey(e164), currentState.fcmToken, RegistrationRepository.getPniRegistrationId(), recoveryPassword) }