From e1570e9512c363f031288d8b7fb1641bcefbf590 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 5 Jul 2023 19:05:30 -0400 Subject: [PATCH] Start mirroring to SVR2. --- app/src/main/AndroidManifest.xml | 4 +- .../securesms/ApplicationContext.java | 4 +- .../securesms/PassphraseRequiredActivity.java | 8 +- .../securesms/absbackup/SignalBackupAgent.kt | 4 +- .../{KbsAuthTokens.kt => SvrAuthTokens.kt} | 12 +- .../app/account/AccountSettingsFragment.kt | 14 +- .../app/account/AccountSettingsViewModel.kt | 4 +- .../ChangeNumberPinDiffersFragment.kt | 6 +- .../ChangeNumberRegistrationLockFragment.kt | 2 +- .../changenumber/ChangeNumberRepository.kt | 26 +- .../app/changenumber/ChangeNumberViewModel.kt | 19 +- .../svr/InternalSvrPlaygroundViewModel.kt | 16 +- .../ConversationListFragment.java | 4 +- .../securesms/jobmanager/Job.java | 9 +- .../jobs/ClearFallbackKbsEnclaveJob.java | 123 ----- .../securesms/jobs/JobManagerFactories.java | 18 +- .../jobs/KbsEnclaveMigrationWorkerJob.java | 100 ---- .../securesms/jobs/PnpInitializeDevicesJob.kt | 2 +- .../securesms/jobs/RefreshAttributesJob.java | 19 +- .../securesms/jobs/RefreshOwnProfileJob.java | 3 +- ...ialsJob.kt => RefreshSvrCredentialsJob.kt} | 20 +- .../securesms/jobs/ResetSvrGuessCountJob.kt | 146 ++++++ .../securesms/jobs/StorageSyncJob.java | 3 +- .../securesms/jobs/Svr2MirrorJob.kt | 119 +++++ .../securesms/keyvalue/PaymentsValues.kt | 4 +- .../securesms/keyvalue/PinValues.java | 11 +- .../securesms/keyvalue/SignalStore.java | 12 +- .../keyvalue/StorageServiceValues.java | 2 +- .../{KbsValues.java => SvrValues.java} | 61 +-- .../lock/SignalPinReminderDialog.java | 16 +- ...nFragment.java => BaseSvrPinFragment.java} | 13 +- ...iewModel.java => BaseSvrPinViewModel.java} | 4 +- .../lock/v2/ConfirmKbsPinRepository.java | 45 -- .../lock/v2/ConfirmKbsPinViewModel.java | 125 ----- ...inFragment.kt => ConfirmSvrPinFragment.kt} | 27 +- .../lock/v2/ConfirmSvrPinViewModel.java | 125 +++++ ...ctivity.java => CreateSvrPinActivity.java} | 14 +- ...PinFragment.kt => CreateSvrPinFragment.kt} | 20 +- ...wModel.java => CreateSvrPinViewModel.java} | 18 +- .../lock/v2/RegistrationLockUtil.java | 17 - .../{KbsConstants.java => SvrConstants.java} | 4 +- ...ctivity.java => SvrMigrationActivity.java} | 6 +- .../lock/v2/{KbsPin.java => SvrPin.java} | 22 +- ...shFragment.java => SvrSplashFragment.java} | 10 +- .../securesms/logsubmit/LogSectionPin.java | 14 +- .../securesms/megaphone/Megaphones.java | 10 +- .../megaphone/PinsForAllSchedule.java | 15 +- .../megaphone/SignalPinReminderSchedule.java | 4 +- .../migrations/ApplicationMigrations.java | 23 +- .../migrations/KbsEnclaveMigrationJob.java | 53 --- .../migrations/PinOptOutMigration.java | 6 +- .../RegistrationPinV2MigrationJob.java | 92 ---- .../migrations/Svr2MirrorMigrationJob.kt | 35 ++ .../preferences/PaymentsHomeFragment.java | 5 +- .../securesms/pin/KbsRepository.java | 183 -------- .../pin/KeyBackupSystemWrongPinException.java | 18 - .../securesms/pin/PinOptOutDialog.java | 2 +- .../securesms/pin/PinRestoreActivity.java | 4 +- .../pin/PinRestoreEntryFragment.java | 12 +- .../pin/PinRestoreLockedFragment.java | 2 +- .../securesms/pin/PinRestoreRepository.java | 83 ---- .../securesms/pin/PinRestoreViewModel.java | 117 ----- .../securesms/pin/PinRestoreViewModel.kt | 81 ++++ .../thoughtcrime/securesms/pin/PinState.java | 435 ------------------ .../pin/RegistrationLockV2Dialog.java | 4 +- .../securesms/pin/SvrRepository.kt | 411 +++++++++++++++++ .../securesms/pin/SvrWrongPinException.java | 14 + .../thoughtcrime/securesms/pin/TokenData.java | 82 ---- .../AdvancedPinPreferenceFragment.java | 13 +- .../registration/RegistrationRepository.java | 13 +- .../registration/RegistrationUtil.java | 2 +- .../registration/VerifyAccountRepository.kt | 23 +- .../securesms/registration/VerifyResponse.kt | 8 +- .../registration/VerifyResponseProcessor.kt | 68 ++- .../fragments/BaseEnterSmsCodeFragment.java | 6 +- .../BaseRegistrationLockFragment.java | 27 +- .../fragments/EnterPhoneNumberFragment.java | 1 - .../fragments/ReRegisterWithPinFragment.kt | 16 +- .../fragments/RegistrationCompleteFragment.kt | 6 +- .../fragments/RegistrationViewDelegate.kt | 2 +- .../viewmodel/BaseRegistrationViewModel.java | 57 +-- .../viewmodel/ReRegisterWithPinViewModel.kt | 7 +- .../viewmodel/RegistrationViewModel.java | 114 ++--- .../viewmodel/SvrAuthCredentialSet.kt | 42 ++ .../securesms/util/FeatureFlags.java | 19 +- .../securesms/util/SupportEmailUtil.java | 6 +- .../securesms/util/TextSecurePreferences.java | 8 - app/src/main/protowire/ExternalBackups.proto | 2 +- .../res/layout/create_kbs_pin_activity.xml | 2 +- .../res/layout/kbs_migration_activity.xml | 2 +- .../layout/pin_restore_locked_fragment.xml | 2 +- .../main/res/navigation/create_kbs_pin.xml | 6 +- app/src/main/res/navigation/kbs_migration.xml | 2 +- .../registration/v2/PinHashKbsDataTest.java | 2 +- .../signalservice/api/KeyBackupService.java | 39 +- .../api/KeyBackupSystemNoDataException.java | 7 - .../api/SignalServiceAccountManager.java | 15 + .../signalservice/api/SvrNoDataException.java | 7 + .../api/{KbsPinData.java => SvrPinData.java} | 4 +- .../api/account/AccountAttributes.kt | 3 - .../signalservice/api/kbs/MasterKey.java | 5 +- .../signalservice/api/kbs/PinHashUtil.kt | 2 +- .../api/svr/SecureValueRecovery.kt | 28 +- .../api/svr/SecureValueRecoveryV1.kt | 130 +++--- .../api/svr/SecureValueRecoveryV2.kt | 312 +++++++------ .../signalservice/api/svr/Svr2Socket.kt | 101 ++-- .../internal/push/AuthCredentials.java | 5 + .../internal/push/LockedException.java | 24 +- .../internal/push/PushServiceSocket.java | 20 +- .../signalservice/internal/util/JsonUtil.java | 17 + .../websocket/DefaultErrorMapper.java | 6 +- 111 files changed, 1828 insertions(+), 2299 deletions(-) rename app/src/main/java/org/thoughtcrime/securesms/absbackup/backupables/{KbsAuthTokens.kt => SvrAuthTokens.kt} (69%) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/ClearFallbackKbsEnclaveJob.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/KbsEnclaveMigrationWorkerJob.java rename app/src/main/java/org/thoughtcrime/securesms/jobs/{RefreshKbsCredentialsJob.kt => RefreshSvrCredentialsJob.kt} (75%) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/ResetSvrGuessCountJob.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/Svr2MirrorJob.kt rename app/src/main/java/org/thoughtcrime/securesms/keyvalue/{KbsValues.java => SvrValues.java} (76%) rename app/src/main/java/org/thoughtcrime/securesms/lock/v2/{BaseKbsPinFragment.java => BaseSvrPinFragment.java} (93%) rename app/src/main/java/org/thoughtcrime/securesms/lock/v2/{BaseKbsPinViewModel.java => BaseSvrPinViewModel.java} (81%) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmKbsPinRepository.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmKbsPinViewModel.java rename app/src/main/java/org/thoughtcrime/securesms/lock/v2/{ConfirmKbsPinFragment.kt => ConfirmSvrPinFragment.kt} (81%) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmSvrPinViewModel.java rename app/src/main/java/org/thoughtcrime/securesms/lock/v2/{CreateKbsPinActivity.java => CreateSvrPinActivity.java} (87%) rename app/src/main/java/org/thoughtcrime/securesms/lock/v2/{CreateKbsPinFragment.kt => CreateSvrPinFragment.kt} (83%) rename app/src/main/java/org/thoughtcrime/securesms/lock/v2/{CreateKbsPinViewModel.java => CreateSvrPinViewModel.java} (76%) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/lock/v2/RegistrationLockUtil.java rename app/src/main/java/org/thoughtcrime/securesms/lock/v2/{KbsConstants.java => SvrConstants.java} (61%) rename app/src/main/java/org/thoughtcrime/securesms/lock/v2/{KbsMigrationActivity.java => SvrMigrationActivity.java} (90%) rename app/src/main/java/org/thoughtcrime/securesms/lock/v2/{KbsPin.java => SvrPin.java} (61%) rename app/src/main/java/org/thoughtcrime/securesms/lock/v2/{KbsSplashFragment.java => SvrSplashFragment.java} (92%) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/migrations/KbsEnclaveMigrationJob.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/migrations/RegistrationPinV2MigrationJob.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/migrations/Svr2MirrorMigrationJob.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/pin/KbsRepository.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/pin/KeyBackupSystemWrongPinException.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreRepository.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreViewModel.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreViewModel.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/pin/SvrRepository.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/pin/SvrWrongPinException.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/pin/TokenData.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/SvrAuthCredentialSet.kt delete mode 100644 libsignal/service/src/main/java/org/whispersystems/signalservice/api/KeyBackupSystemNoDataException.java create mode 100644 libsignal/service/src/main/java/org/whispersystems/signalservice/api/SvrNoDataException.java rename libsignal/service/src/main/java/org/whispersystems/signalservice/api/{KbsPinData.java => SvrPinData.java} (86%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c15ddeef2c..e498ac9142 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -933,12 +933,12 @@ android:theme="@style/TextSecure.LightRegistrationTheme" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> - - diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index b7058ec5f8..0add0be6f1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -60,7 +60,7 @@ import org.thoughtcrime.securesms.jobs.PnpInitializeDevicesJob; import org.thoughtcrime.securesms.jobs.PreKeysSyncJob; import org.thoughtcrime.securesms.jobs.ProfileUploadJob; import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob; -import org.thoughtcrime.securesms.jobs.RefreshKbsCredentialsJob; +import org.thoughtcrime.securesms.jobs.RefreshSvrCredentialsJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.jobs.RetrieveRemoteAnnouncementsJob; import org.thoughtcrime.securesms.jobs.StoryOnboardingDownloadJob; @@ -198,7 +198,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr .addPostRender(this::initializeExpiringMessageManager) .addPostRender(() -> SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this))) .addPostRender(this::initializeTrimThreadsByDateManager) - .addPostRender(RefreshKbsCredentialsJob::enqueueIfNecessary) + .addPostRender(RefreshSvrCredentialsJob::enqueueIfNecessary) .addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this)) .addPostRender(EmojiSearchIndexDownloadJob::scheduleIfNecessary) .addPostRender(() -> SignalDatabase.messageLog().trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge())) diff --git a/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java b/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java index 8850a198a6..f8a19bf9cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java @@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferActivity; import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity; +import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity; import org.thoughtcrime.securesms.migrations.ApplicationMigrationActivity; import org.thoughtcrime.securesms.migrations.ApplicationMigrations; import org.thoughtcrime.securesms.pin.PinRestoreActivity; @@ -189,11 +189,11 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements } private boolean userMustCreateSignalPin() { - return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().lastPinCreateFailed() && !SignalStore.kbsValues().hasOptedOut(); + return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.svr().hasPin() && !SignalStore.svr().lastPinCreateFailed() && !SignalStore.svr().hasOptedOut(); } private boolean userHasSkippedOrForgottenPin() { - return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut() && SignalStore.kbsValues().isPinForgottenOrSkipped(); + return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.svr().hasPin() && !SignalStore.svr().hasOptedOut() && SignalStore.svr().isPinForgottenOrSkipped(); } private boolean userMustSetProfileName() { @@ -234,7 +234,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements intent = getIntent(); } - return getRoutedIntent(CreateKbsPinActivity.class, intent); + return getRoutedIntent(CreateSvrPinActivity.class, intent); } private Intent getCreateProfileNameIntent() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/absbackup/SignalBackupAgent.kt b/app/src/main/java/org/thoughtcrime/securesms/absbackup/SignalBackupAgent.kt index ef1a076688..2b4692b95f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/absbackup/SignalBackupAgent.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/absbackup/SignalBackupAgent.kt @@ -5,7 +5,7 @@ import android.app.backup.BackupDataInput import android.app.backup.BackupDataOutput import android.os.ParcelFileDescriptor import org.signal.core.util.logging.Log -import org.thoughtcrime.securesms.absbackup.backupables.KbsAuthTokens +import org.thoughtcrime.securesms.absbackup.backupables.SvrAuthTokens import java.io.DataInputStream import java.io.DataOutputStream import java.io.FileInputStream @@ -17,7 +17,7 @@ import java.io.IOException */ class SignalBackupAgent : BackupAgent() { private val items: List = listOf( - KbsAuthTokens + SvrAuthTokens ) override fun onBackup(oldState: ParcelFileDescriptor?, data: BackupDataOutput, newState: ParcelFileDescriptor) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/absbackup/backupables/KbsAuthTokens.kt b/app/src/main/java/org/thoughtcrime/securesms/absbackup/backupables/SvrAuthTokens.kt similarity index 69% rename from app/src/main/java/org/thoughtcrime/securesms/absbackup/backupables/KbsAuthTokens.kt rename to app/src/main/java/org/thoughtcrime/securesms/absbackup/backupables/SvrAuthTokens.kt index 0d767d3894..7c530c7b2b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/absbackup/backupables/KbsAuthTokens.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/absbackup/backupables/SvrAuthTokens.kt @@ -3,13 +3,13 @@ package org.thoughtcrime.securesms.absbackup.backupables import com.google.protobuf.InvalidProtocolBufferException import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.absbackup.AndroidBackupItem -import org.thoughtcrime.securesms.absbackup.protos.KbsAuthToken +import org.thoughtcrime.securesms.absbackup.protos.SvrAuthToken import org.thoughtcrime.securesms.keyvalue.SignalStore /** * This backs up the not-secret KBS Auth tokens, which can be combined with a PIN to prove ownership of a phone number in order to complete the registration process. */ -object KbsAuthTokens : AndroidBackupItem { +object SvrAuthTokens : AndroidBackupItem { private const val TAG = "KbsAuthTokens" override fun getKey(): String { @@ -17,19 +17,19 @@ object KbsAuthTokens : AndroidBackupItem { } override fun getDataForBackup(): ByteArray { - val proto = KbsAuthToken(tokens = SignalStore.kbsValues().kbsAuthTokenList) + val proto = SvrAuthToken(tokens = SignalStore.svr().authTokenList) return proto.encode() } override fun restoreData(data: ByteArray) { - if (SignalStore.kbsValues().kbsAuthTokenList.isNotEmpty()) { + if (SignalStore.svr().authTokenList.isNotEmpty()) { return } try { - val proto = KbsAuthToken.ADAPTER.decode(data) + val proto = SvrAuthToken.ADAPTER.decode(data) - SignalStore.kbsValues().putAuthTokenList(proto.tokens) + SignalStore.svr().putAuthTokenList(proto.tokens) } catch (e: InvalidProtocolBufferException) { Log.w(TAG, "Cannot restore KbsAuthToken from backup service.") } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt index 8cc273f2b5..93d1c739d5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt @@ -28,9 +28,9 @@ 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.v2.CreateKbsPinActivity -import org.thoughtcrime.securesms.lock.v2.KbsConstants +import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity import org.thoughtcrime.securesms.lock.v2.PinKeyboardType +import org.thoughtcrime.securesms.lock.v2.SvrConstants import org.thoughtcrime.securesms.pin.RegistrationLockV2Dialog import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity import org.thoughtcrime.securesms.util.PlayStoreUtil @@ -45,7 +45,7 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag lateinit var viewModel: AccountSettingsViewModel override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == CreateKbsPinActivity.REQUEST_NEW_PIN && resultCode == CreateKbsPinActivity.RESULT_OK) { + if (requestCode == CreateSvrPinActivity.REQUEST_NEW_PIN && resultCode == CreateSvrPinActivity.RESULT_OK) { Snackbar.make(requireView(), R.string.ConfirmKbsPinFragment__pin_created, Snackbar.LENGTH_LONG).show() } } @@ -73,9 +73,9 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag isEnabled = state.isDeprecatedOrUnregistered(), onClick = { if (state.hasPin) { - startActivityForResult(CreateKbsPinActivity.getIntentForPinChangeFromSettings(requireContext()), CreateKbsPinActivity.REQUEST_NEW_PIN) + startActivityForResult(CreateSvrPinActivity.getIntentForPinChangeFromSettings(requireContext()), CreateSvrPinActivity.REQUEST_NEW_PIN) } else { - startActivityForResult(CreateKbsPinActivity.getIntentForPinCreate(requireContext()), CreateKbsPinActivity.REQUEST_NEW_PIN) + startActivityForResult(CreateSvrPinActivity.getIntentForPinCreate(requireContext()), CreateSvrPinActivity.REQUEST_NEW_PIN) } } ) @@ -240,14 +240,14 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag pinEditText.addTextChangedListener(object : SimpleTextWatcher() { override fun onTextChanged(text: String) { - turnOffButton.isEnabled = text.length >= KbsConstants.MINIMUM_PIN_LENGTH + turnOffButton.isEnabled = text.length >= SvrConstants.MINIMUM_PIN_LENGTH } }) pinEditText.typeface = Typeface.DEFAULT turnOffButton.setOnClickListener { val pin = pinEditText.text.toString() - val correct = PinHashUtil.verifyLocalPinHash(SignalStore.kbsValues().localPinHash!!, pin) + val correct = PinHashUtil.verifyLocalPinHash(SignalStore.svr().localPinHash!!, pin) if (correct) { SignalStore.pinValues().setPinRemindersEnabled(false) viewModel.refreshState() diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt index b4d3975cea..d47daa9f9f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt @@ -18,9 +18,9 @@ class AccountSettingsViewModel : ViewModel() { private fun getCurrentState(): AccountSettingsState { return AccountSettingsState( - hasPin = SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut(), + hasPin = SignalStore.svr().hasPin() && !SignalStore.svr().hasOptedOut(), pinRemindersEnabled = SignalStore.pinValues().arePinRemindersEnabled(), - registrationLockEnabled = SignalStore.kbsValues().isV2RegistrationLockEnabled, + registrationLockEnabled = SignalStore.svr().isRegistrationLockEnabled, userUnregistered = TextSecurePreferences.isUnauthorizedReceived(ApplicationDependencies.getApplication()), clientDeprecated = SignalStore.misc().isClientDeprecated ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberPinDiffersFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberPinDiffersFragment.kt index 53f98ab9c0..b8a0b92ef8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberPinDiffersFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberPinDiffersFragment.kt @@ -8,7 +8,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.thoughtcrime.securesms.LoggingFragment import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberUtil.changeNumberSuccess -import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity +import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity class ChangeNumberPinDiffersFragment : LoggingFragment(R.layout.fragment_change_number_pin_differs) { @@ -18,13 +18,13 @@ class ChangeNumberPinDiffersFragment : LoggingFragment(R.layout.fragment_change_ } val changePin = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == CreateKbsPinActivity.RESULT_OK) { + if (result.resultCode == CreateSvrPinActivity.RESULT_OK) { changeNumberSuccess() } } view.findViewById(R.id.change_number_pin_differs_update_pin).setOnClickListener { - changePin.launch(CreateKbsPinActivity.getIntentForPinChangeFromSettings(requireContext())) + changePin.launch(CreateSvrPinActivity.getIntentForPinChangeFromSettings(requireContext())) } requireActivity().onBackPressedDispatcher.addCallback( diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRegistrationLockFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRegistrationLockFragment.kt index 4e88a425b9..0b2ef0a7c7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRegistrationLockFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRegistrationLockFragment.kt @@ -42,7 +42,7 @@ class ChangeNumberRegistrationLockFragment : BaseRegistrationLockFragment(R.layo } override fun handleSuccessfulPinEntry(pin: String) { - val pinsDiffer: Boolean = SignalStore.kbsValues().localPinHash?.let { !PinHashUtil.verifyLocalPinHash(it, pin) } ?: false + val pinsDiffer: Boolean = SignalStore.svr().localPinHash?.let { !PinHashUtil.verifyLocalPinHash(it, pin) } ?: false pinButton.cancelSpinning() diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt index 8830475108..5ba3ce55a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt @@ -22,18 +22,18 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.jobs.RefreshAttributesJob import org.thoughtcrime.securesms.keyvalue.CertificateType import org.thoughtcrime.securesms.keyvalue.SignalStore -import org.thoughtcrime.securesms.pin.KbsRepository -import org.thoughtcrime.securesms.pin.KeyBackupSystemWrongPinException -import org.thoughtcrime.securesms.pin.TokenData +import org.thoughtcrime.securesms.pin.SvrRepository +import org.thoughtcrime.securesms.pin.SvrWrongPinException import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.registration.VerifyResponse +import org.thoughtcrime.securesms.registration.viewmodel.SvrAuthCredentialSet import org.thoughtcrime.securesms.storage.StorageSyncHelper -import org.whispersystems.signalservice.api.KbsPinData -import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException import org.whispersystems.signalservice.api.SignalServiceAccountManager import org.whispersystems.signalservice.api.SignalServiceMessageSender +import org.whispersystems.signalservice.api.SvrNoDataException import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest import org.whispersystems.signalservice.api.account.PreKeyUpload +import org.whispersystems.signalservice.api.kbs.MasterKey import org.whispersystems.signalservice.api.push.PNI import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.push.ServiceIdType @@ -143,7 +143,7 @@ class ChangeNumberRepository( VerifyResponse.from( response = changeNumberResponse, - kbsData = null, + masterKey = null, pin = null, aciPreKeyCollection = null, pniPreKeyCollection = null @@ -156,18 +156,18 @@ class ChangeNumberRepository( sessionId: String, newE164: String, pin: String, - tokenData: TokenData + svrAuthCredentials: SvrAuthCredentialSet ): Single> { return Single.fromCallable { - val kbsData: KbsPinData + val masterKey: MasterKey val registrationLock: String try { - kbsData = KbsRepository.restoreMasterKey(pin, tokenData.enclave, tokenData.basicAuth, tokenData.tokenResponse)!! - registrationLock = kbsData.masterKey.deriveRegistrationLock() - } catch (e: KeyBackupSystemWrongPinException) { + masterKey = SvrRepository.restoreMasterKeyPreRegistration(svrAuthCredentials, pin) + registrationLock = masterKey.deriveRegistrationLock() + } catch (e: SvrWrongPinException) { return@fromCallable ServiceResponse.forExecutionError(e) - } catch (e: KeyBackupSystemNoDataException) { + } catch (e: SvrNoDataException) { return@fromCallable ServiceResponse.forExecutionError(e) } catch (e: IOException) { return@fromCallable ServiceResponse.forExecutionError(e) @@ -199,7 +199,7 @@ class ChangeNumberRepository( VerifyResponse.from( response = changeNumberResponse, - kbsData = kbsData, + masterKey = masterKey, pin = pin, aciPreKeyCollection = null, pniPreKeyCollection = null 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 ef9e406144..cc717b4452 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 @@ -16,8 +16,6 @@ import io.reactivex.rxjava3.schedulers.Schedulers import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore -import org.thoughtcrime.securesms.pin.KbsRepository -import org.thoughtcrime.securesms.pin.TokenData import org.thoughtcrime.securesms.registration.RegistrationSessionProcessor import org.thoughtcrime.securesms.registration.SmsRetrieverReceiver import org.thoughtcrime.securesms.registration.VerifyAccountRepository @@ -27,6 +25,7 @@ import org.thoughtcrime.securesms.registration.VerifyResponseWithRegistrationLoc import org.thoughtcrime.securesms.registration.VerifyResponseWithoutKbs import org.thoughtcrime.securesms.registration.viewmodel.BaseRegistrationViewModel import org.thoughtcrime.securesms.registration.viewmodel.NumberViewState +import org.thoughtcrime.securesms.registration.viewmodel.SvrAuthCredentialSet import org.thoughtcrime.securesms.util.DefaultValueLiveData import org.whispersystems.signalservice.api.push.PNI import org.whispersystems.signalservice.api.push.exceptions.IncorrectCodeException @@ -41,9 +40,8 @@ class ChangeNumberViewModel( savedState: SavedStateHandle, password: String, verifyAccountRepository: VerifyAccountRepository, - kbsRepository: KbsRepository, private val smsRetrieverReceiver: SmsRetrieverReceiver = SmsRetrieverReceiver(ApplicationDependencies.getApplication()) -) : BaseRegistrationViewModel(savedState, verifyAccountRepository, kbsRepository, password) { +) : BaseRegistrationViewModel(savedState, verifyAccountRepository, password) { var oldNumberState: NumberViewState = NumberViewState.Builder().build() private set @@ -179,9 +177,9 @@ class ChangeNumberViewModel( } } - override fun verifyAccountWithRegistrationLock(pin: String, kbsTokenData: TokenData): Single> { + override fun verifyAccountWithRegistrationLock(pin: String, svrAuthCredentials: SvrAuthCredentialSet): Single> { val sessionId = sessionId ?: throw IllegalStateException("No valid registration session") - return changeNumberRepository.changeNumber(sessionId, number.e164Number, pin, kbsTokenData) + return changeNumberRepository.changeNumber(sessionId, number.e164Number, pin, svrAuthCredentials) } @WorkerThread @@ -199,14 +197,14 @@ class ChangeNumberViewModel( .map { processor } .onErrorReturn { t -> Log.w(TAG, "Error attempting to change local number", t) - VerifyResponseWithRegistrationLockProcessor(ServiceResponse.forUnknownError(t), processor.tokenData) + VerifyResponseWithRegistrationLockProcessor(ServiceResponse.forUnknownError(t), processor.svrAuthCredentials) } } fun changeNumberWithRecoveryPassword(): Single { - val recoveryPassword = SignalStore.kbsValues().recoveryPassword + val recoveryPassword = SignalStore.svr().recoveryPassword - return if (SignalStore.kbsValues().hasPin() && recoveryPassword != null) { + return if (SignalStore.svr().hasPin() && recoveryPassword != null) { changeNumberRepository.changeNumber(recoveryPassword = recoveryPassword, newE164 = number.e164Number) .map { r -> VerifyResponseWithoutKbs(r) } .flatMap { p -> @@ -233,8 +231,7 @@ class ChangeNumberViewModel( changeNumberRepository = ChangeNumberRepository(), savedState = handle, password = password, - verifyAccountRepository = VerifyAccountRepository(context), - kbsRepository = KbsRepository() + verifyAccountRepository = VerifyAccountRepository(context) ) return requireNotNull(modelClass.cast(viewModel)) 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 2e22685047..095a24e6ee 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 @@ -10,6 +10,7 @@ import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.schedulers.Schedulers @@ -49,9 +50,12 @@ class InternalSvrPlaygroundViewModel : ViewModel() { loading = true ) - disposables += _state.value.selected.toImplementation() - .setPin(_state.value.userPin, SignalStore.kbsValues().getOrCreateMasterKey()) - .execute() + disposables += Single + .fromCallable { + _state.value.selected.toImplementation() + .setPin(_state.value.userPin, SignalStore.svr().getOrCreateMasterKey()) + .execute() + } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { response -> @@ -67,8 +71,7 @@ class InternalSvrPlaygroundViewModel : ViewModel() { loading = true ) - disposables += _state.value.selected.toImplementation() - .restoreDataPostRegistration(_state.value.userPin) + disposables += Single.fromCallable { _state.value.selected.toImplementation().restoreDataPostRegistration(_state.value.userPin) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { response -> @@ -84,8 +87,7 @@ class InternalSvrPlaygroundViewModel : ViewModel() { loading = true ) - disposables += _state.value.selected.toImplementation() - .deleteData() + disposables += Single.fromCallable { _state.value.selected.toImplementation().deleteData() } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { response -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index 2970c28a53..520af3ea0e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -141,7 +141,7 @@ import org.thoughtcrime.securesms.groups.SelectionLimits; import org.thoughtcrime.securesms.insights.InsightsLauncher; import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity; +import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity; import org.thoughtcrime.securesms.main.Material3OnScrollHelperBinder; import org.thoughtcrime.securesms.main.SearchBinder; import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity; @@ -708,7 +708,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode } } - if (resultCode == RESULT_OK && requestCode == CreateKbsPinActivity.REQUEST_NEW_PIN) { + if (resultCode == RESULT_OK && requestCode == CreateSvrPinActivity.REQUEST_NEW_PIN) { Snackbar.make(fab, R.string.ConfirmKbsPinFragment__pin_created, Snackbar.LENGTH_LONG).show(); viewModel.onMegaphoneCompleted(Megaphones.Event.PINS_FOR_ALL); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/Job.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/Job.java index 15e5f89fa0..cdf38aebed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/Job.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/Job.java @@ -8,6 +8,8 @@ import androidx.annotation.VisibleForTesting; import androidx.annotation.WorkerThread; import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.jobmanager.impl.BackoffUtil; +import org.thoughtcrime.securesms.util.FeatureFlags; import java.util.LinkedList; import java.util.List; @@ -93,6 +95,12 @@ public abstract class Job { this.canceled = true; } + /** Provides a default exponential backoff given the current run attempt. */ + protected final long defaultBackoff() { + return BackoffUtil.exponentialBackoff(runAttempt + 1, FeatureFlags.getDefaultMaxBackoff()); + } + + @WorkerThread final void onSubmit() { Log.i(TAG, JobLogger.format(this, "onSubmit()")); @@ -196,7 +204,6 @@ public abstract class Job { return new Result(ResultType.FAILURE, runtimeException, null, INVALID_BACKOFF); } - @VisibleForTesting(otherwise = PACKAGE_PRIVATE) public boolean isSuccess() { return resultType == ResultType.SUCCESS; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ClearFallbackKbsEnclaveJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ClearFallbackKbsEnclaveJob.java deleted file mode 100644 index f1ce609baa..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ClearFallbackKbsEnclaveJob.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.KbsEnclave; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.jobmanager.JsonJobData; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.jobmanager.JobManager; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; -import org.thoughtcrime.securesms.pin.KbsEnclaves; -import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; -import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -/** - * Clears data from an old KBS enclave. - */ -public class ClearFallbackKbsEnclaveJob extends BaseJob { - - public static final String KEY = "ClearFallbackKbsEnclaveJob"; - - private static final String TAG = Log.tag(ClearFallbackKbsEnclaveJob.class); - - private static final String KEY_ENCLAVE_NAME = "enclaveName"; - private static final String KEY_SERVICE_ID = "serviceId"; - private static final String KEY_MR_ENCLAVE = "mrEnclave"; - - private final KbsEnclave enclave; - - ClearFallbackKbsEnclaveJob(@NonNull KbsEnclave enclave) { - this(new Parameters.Builder() - .addConstraint(NetworkConstraint.KEY) - .setLifespan(TimeUnit.DAYS.toMillis(90)) - .setMaxAttempts(Parameters.UNLIMITED) - .setQueue("ClearFallbackKbsEnclaveJob") - .build(), - enclave); - } - - public static void clearAll() { - if (KbsEnclaves.fallbacks().isEmpty()) { - Log.i(TAG, "No fallbacks!"); - return; - } - - JobManager jobManager = ApplicationDependencies.getJobManager(); - - for (KbsEnclave enclave : KbsEnclaves.fallbacks()) { - jobManager.add(new ClearFallbackKbsEnclaveJob(enclave)); - } - } - - private ClearFallbackKbsEnclaveJob(@NonNull Parameters parameters, @NonNull KbsEnclave enclave) { - super(parameters); - this.enclave = enclave; - } - - @Override - public @NonNull String getFactoryKey() { - return KEY; - } - - @Override - public @Nullable byte[] serialize() { - return new JsonJobData.Builder().putString(KEY_ENCLAVE_NAME, enclave.getEnclaveName()) - .putString(KEY_SERVICE_ID, enclave.getServiceId()) - .putString(KEY_MR_ENCLAVE, enclave.getMrEnclave()) - .serialize(); - } - - @Override - public void onRun() throws IOException, UnauthenticatedResponseException { - Log.i(TAG, "Preparing to delete data from " + enclave.getEnclaveName()); - ApplicationDependencies.getKeyBackupService(enclave).newPinChangeSession().removePin(); - Log.i(TAG, "Successfully deleted the data from " + enclave.getEnclaveName()); - } - - @Override - public boolean onShouldRetry(@NonNull Exception e) { - if (e instanceof NonSuccessfulResponseCodeException) { - switch (((NonSuccessfulResponseCodeException) e).getCode()) { - case 404: - return getRunAttempt() < 3; - case 508: - return false; - } - } - - return true; - } - - @Override - public long getNextRunAttemptBackoff(int pastAttemptCount, @NonNull Exception e) { - if (e instanceof NonSuccessfulResponseCodeException && ((NonSuccessfulResponseCodeException) e).getCode() == 404) { - return TimeUnit.DAYS.toMillis(1); - } else { - return super.getNextRunAttemptBackoff(pastAttemptCount, e); - } - } - - @Override - public void onFailure() { - Log.w(TAG, "Job failed! It is likely that the old enclave is offline."); - } - - public static class Factory implements Job.Factory { - @Override - public @NonNull ClearFallbackKbsEnclaveJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) { - JsonJobData data = JsonJobData.deserialize(serializedData); - - KbsEnclave enclave = new KbsEnclave(data.getString(KEY_ENCLAVE_NAME), - data.getString(KEY_SERVICE_ID), - data.getString(KEY_MR_ENCLAVE)); - - return new ClearFallbackKbsEnclaveJob(parameters, enclave); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index 09fb4f7b41..9e7dc6149c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -51,7 +51,6 @@ import org.thoughtcrime.securesms.migrations.DecryptionsDrainedMigrationJob; import org.thoughtcrime.securesms.migrations.DeleteDeprecatedLogsMigrationJob; import org.thoughtcrime.securesms.migrations.DirectoryRefreshMigrationJob; import org.thoughtcrime.securesms.migrations.EmojiDownloadMigrationJob; -import org.thoughtcrime.securesms.migrations.KbsEnclaveMigrationJob; import org.thoughtcrime.securesms.migrations.LegacyMigrationJob; import org.thoughtcrime.securesms.migrations.MigrationCompleteJob; import org.thoughtcrime.securesms.migrations.OptimizeMessageSearchIndexMigrationJob; @@ -65,7 +64,6 @@ import org.thoughtcrime.securesms.migrations.ProfileMigrationJob; import org.thoughtcrime.securesms.migrations.ProfileSharingUpdateMigrationJob; import org.thoughtcrime.securesms.migrations.RebuildMessageSearchIndexMigrationJob; import org.thoughtcrime.securesms.migrations.RecipientSearchMigrationJob; -import org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob; import org.thoughtcrime.securesms.migrations.StickerAdditionMigrationJob; import org.thoughtcrime.securesms.migrations.StickerDayByDayMigrationJob; import org.thoughtcrime.securesms.migrations.StickerLaunchMigrationJob; @@ -75,6 +73,7 @@ import org.thoughtcrime.securesms.migrations.StorageServiceMigrationJob; import org.thoughtcrime.securesms.migrations.StorageServiceSystemNameMigrationJob; import org.thoughtcrime.securesms.migrations.StoryReadStateMigrationJob; import org.thoughtcrime.securesms.migrations.StoryViewedReceiptsStateMigrationJob; +import org.thoughtcrime.securesms.migrations.Svr2MirrorMigrationJob; import org.thoughtcrime.securesms.migrations.SyncDistributionListsMigrationJob; import org.thoughtcrime.securesms.migrations.TrimByLengthSettingsMigrationJob; import org.thoughtcrime.securesms.migrations.UpdateSmsJobsMigrationJob; @@ -101,11 +100,9 @@ public final class JobManagerFactories { put(AvatarGroupsV2DownloadJob.KEY, new AvatarGroupsV2DownloadJob.Factory()); put(BoostReceiptRequestResponseJob.KEY, new BoostReceiptRequestResponseJob.Factory()); put(CallLinkPeekJob.KEY, new CallLinkPeekJob.Factory()); - put("CallSyncEventJob", new FailingJob.Factory()); put(CallSyncEventJob.KEY, new CallSyncEventJob.Factory()); put(CheckServiceReachabilityJob.KEY, new CheckServiceReachabilityJob.Factory()); put(CleanPreKeysJob.KEY, new CleanPreKeysJob.Factory()); - put(ClearFallbackKbsEnclaveJob.KEY, new ClearFallbackKbsEnclaveJob.Factory()); put(ConversationShortcutRankingUpdateJob.KEY, new ConversationShortcutRankingUpdateJob.Factory()); put(ConversationShortcutUpdateJob.KEY, new ConversationShortcutUpdateJob.Factory()); put(CreateReleaseChannelJob.KEY, new CreateReleaseChannelJob.Factory()); @@ -126,7 +123,6 @@ public final class JobManagerFactories { put(GroupCallPeekWorkerJob.KEY, new GroupCallPeekWorkerJob.Factory()); put(GroupV2UpdateSelfProfileKeyJob.KEY, new GroupV2UpdateSelfProfileKeyJob.Factory()); put(IndividualSendJob.KEY, new IndividualSendJob.Factory()); - put(KbsEnclaveMigrationWorkerJob.KEY, new KbsEnclaveMigrationWorkerJob.Factory()); put(LeaveGroupV2Job.KEY, new LeaveGroupV2Job.Factory()); put(LeaveGroupV2WorkerJob.KEY, new LeaveGroupV2WorkerJob.Factory()); put(LocalBackupJob.KEY, new LocalBackupJob.Factory()); @@ -178,7 +174,7 @@ public final class JobManagerFactories { put(RebuildMessageSearchIndexJob.KEY, new RebuildMessageSearchIndexJob.Factory()); put(RefreshAttributesJob.KEY, new RefreshAttributesJob.Factory()); put(RefreshCallLinkDetailsJob.KEY, new RefreshCallLinkDetailsJob.Factory()); - put(RefreshKbsCredentialsJob.KEY, new RefreshKbsCredentialsJob.Factory()); + put(RefreshSvrCredentialsJob.KEY, new RefreshSvrCredentialsJob.Factory()); put(RefreshOwnProfileJob.KEY, new RefreshOwnProfileJob.Factory()); put(RemoteConfigRefreshJob.KEY, new RemoteConfigRefreshJob.Factory()); put(RemoteDeleteSendJob.KEY, new RemoteDeleteSendJob.Factory()); @@ -200,6 +196,7 @@ public final class JobManagerFactories { put(SendViewedReceiptJob.KEY, new SendViewedReceiptJob.Factory(application)); put(SyncSystemContactLinksJob.KEY, new SyncSystemContactLinksJob.Factory()); put(MultiDeviceStorySendSyncJob.KEY, new MultiDeviceStorySendSyncJob.Factory()); + put(ResetSvrGuessCountJob.KEY, new ResetSvrGuessCountJob.Factory()); put(ServiceOutageDetectionJob.KEY, new ServiceOutageDetectionJob.Factory()); put(SmsReceiveJob.KEY, new SmsReceiveJob.Factory()); put(SmsSendJob.KEY, new SmsSendJob.Factory()); @@ -213,6 +210,7 @@ public final class JobManagerFactories { put(SubscriptionReceiptRequestResponseJob.KEY, new SubscriptionReceiptRequestResponseJob.Factory()); put(StoryOnboardingDownloadJob.KEY, new StoryOnboardingDownloadJob.Factory()); put(SubmitRateLimitPushChallengeJob.KEY, new SubmitRateLimitPushChallengeJob.Factory()); + put(Svr2MirrorJob.KEY, new Svr2MirrorJob.Factory()); put(ThreadUpdateJob.KEY, new ThreadUpdateJob.Factory()); put(TrimThreadJob.KEY, new TrimThreadJob.Factory()); put(TypingSendJob.KEY, new TypingSendJob.Factory()); @@ -236,7 +234,6 @@ public final class JobManagerFactories { put(DeleteDeprecatedLogsMigrationJob.KEY, new DeleteDeprecatedLogsMigrationJob.Factory()); put(DirectoryRefreshMigrationJob.KEY, new DirectoryRefreshMigrationJob.Factory()); put(EmojiDownloadMigrationJob.KEY, new EmojiDownloadMigrationJob.Factory()); - put(KbsEnclaveMigrationJob.KEY, new KbsEnclaveMigrationJob.Factory()); put(LegacyMigrationJob.KEY, new LegacyMigrationJob.Factory()); put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory()); put(OptimizeMessageSearchIndexMigrationJob.KEY,new OptimizeMessageSearchIndexMigrationJob.Factory()); @@ -249,7 +246,6 @@ public final class JobManagerFactories { put(ProfileSharingUpdateMigrationJob.KEY, new ProfileSharingUpdateMigrationJob.Factory()); put(RebuildMessageSearchIndexMigrationJob.KEY, new RebuildMessageSearchIndexMigrationJob.Factory()); put(RecipientSearchMigrationJob.KEY, new RecipientSearchMigrationJob.Factory()); - put(RegistrationPinV2MigrationJob.KEY, new RegistrationPinV2MigrationJob.Factory()); put(StickerLaunchMigrationJob.KEY, new StickerLaunchMigrationJob.Factory()); put(StickerAdditionMigrationJob.KEY, new StickerAdditionMigrationJob.Factory()); put(StickerDayByDayMigrationJob.KEY, new StickerDayByDayMigrationJob.Factory()); @@ -259,6 +255,7 @@ public final class JobManagerFactories { put(StorageServiceSystemNameMigrationJob.KEY, new StorageServiceSystemNameMigrationJob.Factory()); put(StoryReadStateMigrationJob.KEY, new StoryReadStateMigrationJob.Factory()); put(StoryViewedReceiptsStateMigrationJob.KEY, new StoryViewedReceiptsStateMigrationJob.Factory()); + put(Svr2MirrorMigrationJob.KEY, new Svr2MirrorMigrationJob.Factory()); put(SyncDistributionListsMigrationJob.KEY, new SyncDistributionListsMigrationJob.Factory()); put(TrimByLengthSettingsMigrationJob.KEY, new TrimByLengthSettingsMigrationJob.Factory()); put(UpdateSmsJobsMigrationJob.KEY, new UpdateSmsJobsMigrationJob.Factory()); @@ -287,6 +284,11 @@ public final class JobManagerFactories { put("PushTextSendJob", new IndividualSendJob.Factory()); put("MultiDevicePniIdentityUpdateJob", new FailingJob.Factory()); put("MultiDeviceGroupUpdateJob", new FailingJob.Factory()); + put("CallSyncEventJob", new FailingJob.Factory()); + put("RegistrationPinV2MigrationJob", new FailingJob.Factory()); + put("KbsEnclaveMigrationWorkerJob", new FailingJob.Factory()); + put("KbsEnclaveMigrationJob", new PassingMigrationJob.Factory()); + put("ClearFallbackKbsEnclaveJob", new FailingJob.Factory()); }}; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/KbsEnclaveMigrationWorkerJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/KbsEnclaveMigrationWorkerJob.java deleted file mode 100644 index 21bef909e2..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/KbsEnclaveMigrationWorkerJob.java +++ /dev/null @@ -1,100 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.jobmanager.JsonJobData; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.jobmanager.impl.BackoffUtil; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.migrations.KbsEnclaveMigrationJob; -import org.thoughtcrime.securesms.pin.PinState; -import org.thoughtcrime.securesms.util.FeatureFlags; -import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; -import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; - -import java.io.IOException; - -/** - * Should only be enqueued by {@link KbsEnclaveMigrationJob}. Does the actual work of migrating KBS - * data to the new enclave and deleting it from the old enclave(s). - */ -public class KbsEnclaveMigrationWorkerJob extends BaseJob { - - public static final String KEY = "KbsEnclaveMigrationWorkerJob"; - - private static final String TAG = Log.tag(KbsEnclaveMigrationWorkerJob.class); - - public KbsEnclaveMigrationWorkerJob() { - this(new Parameters.Builder() - .addConstraint(NetworkConstraint.KEY) - .setLifespan(Parameters.IMMORTAL) - .setMaxAttempts(Parameters.UNLIMITED) - .setQueue("KbsEnclaveMigrationWorkerJob") - .setMaxInstancesForFactory(1) - .build()); - } - - private KbsEnclaveMigrationWorkerJob(@NonNull Parameters parameters) { - super(parameters); - } - - @Override - public @NonNull String getFactoryKey() { - return KEY; - } - - @Override - public @Nullable byte[] serialize() { - return null; - } - - @Override - public void onRun() throws IOException, UnauthenticatedResponseException { - String pin = SignalStore.kbsValues().getPin(); - - if (SignalStore.kbsValues().hasOptedOut()) { - Log.w(TAG, "Opted out of KBS! Nothing to migrate."); - return; - } - - if (pin == null) { - Log.w(TAG, "No PIN available! Can't migrate!"); - return; - } - - PinState.onMigrateToNewEnclave(pin); - Log.i(TAG, "Migration successful!"); - } - - @Override - public boolean onShouldRetry(@NonNull Exception e) { - return e instanceof IOException || - e instanceof UnauthenticatedResponseException; - } - - @Override - public long getNextRunAttemptBackoff(int pastAttemptCount, @NonNull Exception exception) { - if (exception instanceof NonSuccessfulResponseCodeException) { - if (((NonSuccessfulResponseCodeException) exception).is5xx()) { - return BackoffUtil.exponentialBackoff(pastAttemptCount, FeatureFlags.getServerErrorMaxBackoff()); - } - } - - return super.getNextRunAttemptBackoff(pastAttemptCount, exception); - } - - @Override - public void onFailure() { - throw new AssertionError("This job should never fail. " + getClass().getSimpleName()); - } - - public static class Factory implements Job.Factory { - @Override - public @NonNull KbsEnclaveMigrationWorkerJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) { - return new KbsEnclaveMigrationWorkerJob(parameters); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt index 9eeb39351e..6665d50e9a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt @@ -152,7 +152,7 @@ class PnpInitializeDevicesJob private constructor(parameters: Parameters) : Base VerifyResponse.from( response = distributionResponse, - kbsData = null, + masterKey = null, pin = null, aciPreKeyCollection = null, pniPreKeyCollection = null diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java index 12e69c60fe..c224b7fd19 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java @@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.JsonJobData; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; -import org.thoughtcrime.securesms.keyvalue.KbsValues; +import org.thoughtcrime.securesms.keyvalue.SvrValues; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.registration.RegistrationRepository; import org.thoughtcrime.securesms.registration.secondary.DeviceNameCipher; @@ -88,17 +88,13 @@ public class RefreshAttributesJob extends BaseJob { boolean fetchesMessages = !SignalStore.account().isFcmEnabled() || SignalStore.internalValues().isWebsocketModeForced(); byte[] unidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey()); boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context); - String registrationLockV1 = null; String registrationLockV2 = null; - KbsValues kbsValues = SignalStore.kbsValues(); + SvrValues svrValues = SignalStore.svr(); int pniRegistrationId = new RegistrationRepository(ApplicationDependencies.getApplication()).getPniRegistrationId(); - String recoveryPassword = kbsValues.getRecoveryPassword(); + String recoveryPassword = svrValues.getRecoveryPassword(); - if (kbsValues.isV2RegistrationLockEnabled()) { - registrationLockV2 = kbsValues.getRegistrationLockToken(); - } else if (TextSecurePreferences.isV1RegistrationLockEnabled(context)) { - //noinspection deprecation Ok to read here as they have not migrated - registrationLockV1 = TextSecurePreferences.getDeprecatedV1RegistrationLockPin(context); + if (svrValues.isRegistrationLockEnabled()) { + registrationLockV2 = svrValues.getRegistrationLockToken(); } boolean phoneNumberDiscoverable = SignalStore.phoneNumberPrivacy().getPhoneNumberListingMode().isDiscoverable(); @@ -106,8 +102,8 @@ public class RefreshAttributesJob extends BaseJob { String deviceName = SignalStore.account().getDeviceName(); byte[] encryptedDeviceName = (deviceName == null) ? null : DeviceNameCipher.encryptDeviceName(deviceName.getBytes(StandardCharsets.UTF_8), SignalStore.account().getAciIdentityKey()); - AccountAttributes.Capabilities capabilities = AppCapabilities.getCapabilities(kbsValues.hasPin() && !kbsValues.hasOptedOut()); - Log.i(TAG, "Calling setAccountAttributes() reglockV1? " + !TextUtils.isEmpty(registrationLockV1) + ", reglockV2? " + !TextUtils.isEmpty(registrationLockV2) + ", pin? " + kbsValues.hasPin() + + AccountAttributes.Capabilities capabilities = AppCapabilities.getCapabilities(svrValues.hasPin() && !svrValues.hasOptedOut()); + Log.i(TAG, "Calling setAccountAttributes() reglockV2? " + !TextUtils.isEmpty(registrationLockV2) + ", pin? " + svrValues.hasPin() + "\n Recovery password? " + !TextUtils.isEmpty(recoveryPassword) + "\n Phone number discoverable : " + phoneNumberDiscoverable + "\n Device Name : " + (encryptedDeviceName != null) + @@ -117,7 +113,6 @@ public class RefreshAttributesJob extends BaseJob { null, registrationId, fetchesMessages, - registrationLockV1, registrationLockV2, unidentifiedAccessKey, universalUnidentifiedAccess, diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java index 3fdb5f78ab..9f8823c2eb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java @@ -17,7 +17,6 @@ import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.database.RecipientTable; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.jobmanager.JsonJobData; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.keyvalue.SignalStore; @@ -107,7 +106,7 @@ public class RefreshOwnProfileJob extends BaseJob { return; } - if (SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut() && SignalStore.storageService().getLastSyncTime() == 0) { + if (SignalStore.svr().hasPin() && !SignalStore.svr().hasOptedOut() && SignalStore.storageService().getLastSyncTime() == 0) { Log.i(TAG, "Registered with PIN but haven't completed storage sync yet."); return; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshKbsCredentialsJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshSvrCredentialsJob.kt similarity index 75% rename from app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshKbsCredentialsJob.kt rename to app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshSvrCredentialsJob.kt index 224fd8f465..af8b89562e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshKbsCredentialsJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshSvrCredentialsJob.kt @@ -5,7 +5,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.jobmanager.Job import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint import org.thoughtcrime.securesms.keyvalue.SignalStore -import org.thoughtcrime.securesms.pin.KbsRepository +import org.thoughtcrime.securesms.pin.SvrRepository import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException import java.io.IOException import kotlin.time.Duration @@ -14,20 +14,20 @@ import kotlin.time.Duration.Companion.days /** * Refresh KBS authentication credentials for talking to KBS during re-registration. */ -class RefreshKbsCredentialsJob private constructor(parameters: Parameters) : BaseJob(parameters) { +class RefreshSvrCredentialsJob private constructor(parameters: Parameters) : BaseJob(parameters) { companion object { const val KEY = "RefreshKbsCredentialsJob" - private val TAG = Log.tag(RefreshKbsCredentialsJob::class.java) + private val TAG = Log.tag(RefreshSvrCredentialsJob::class.java) private val FREQUENCY: Duration = 15.days @JvmStatic fun enqueueIfNecessary() { - if (SignalStore.kbsValues().hasPin()) { - val lastTimestamp = SignalStore.kbsValues().lastRefreshAuthTimestamp + if (SignalStore.svr().hasPin()) { + val lastTimestamp = SignalStore.svr().lastRefreshAuthTimestamp if (lastTimestamp + FREQUENCY.inWholeMilliseconds < System.currentTimeMillis() || lastTimestamp > System.currentTimeMillis()) { - ApplicationDependencies.getJobManager().add(RefreshKbsCredentialsJob()) + ApplicationDependencies.getJobManager().add(RefreshSvrCredentialsJob()) } else { Log.d(TAG, "Do not need to refresh credentials. Last refresh: $lastTimestamp") } @@ -49,7 +49,7 @@ class RefreshKbsCredentialsJob private constructor(parameters: Parameters) : Bas override fun getFactoryKey(): String = KEY override fun onRun() { - KbsRepository().refreshAuthorization() + SvrRepository.refreshAndStoreAuthorization() } override fun onShouldRetry(e: Exception): Boolean { @@ -58,9 +58,9 @@ class RefreshKbsCredentialsJob private constructor(parameters: Parameters) : Bas override fun onFailure() = Unit - class Factory : Job.Factory { - override fun create(parameters: Parameters, serializedData: ByteArray?): RefreshKbsCredentialsJob { - return RefreshKbsCredentialsJob(parameters) + class Factory : Job.Factory { + override fun create(parameters: Parameters, serializedData: ByteArray?): RefreshSvrCredentialsJob { + return RefreshSvrCredentialsJob(parameters) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ResetSvrGuessCountJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/ResetSvrGuessCountJob.kt new file mode 100644 index 0000000000..bb542091fc --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ResetSvrGuessCountJob.kt @@ -0,0 +1,146 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ +package org.thoughtcrime.securesms.jobs + +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.BuildConfig +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.jobmanager.JsonJobData +import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.pin.SvrRepository +import org.whispersystems.signalservice.api.kbs.MasterKey +import org.whispersystems.signalservice.api.svr.SecureValueRecovery +import org.whispersystems.signalservice.api.svr.SecureValueRecovery.BackupResponse +import org.whispersystems.signalservice.api.svr.SecureValueRecovery.PinChangeSession +import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV1 +import kotlin.concurrent.withLock +import kotlin.time.Duration.Companion.days + +/** + * Attempts to reset the guess on the SVR PIN. Intended to be enqueued after a successful restore. + */ +class ResetSvrGuessCountJob private constructor( + parameters: Parameters, + private val serializedChangeSession: String?, + private var svr2Complete: Boolean +) : Job(parameters) { + + companion object { + const val KEY = "ResetSvrGuessCountJob" + + private val TAG = Log.tag(ResetSvrGuessCountJob::class.java) + + private const val KEY_CHANGE_SESSION = "change_session" + private const val KEY_SVR2_COMPLETE = "svr2_complete" + } + + constructor() : this( + Parameters.Builder() + .addConstraint(NetworkConstraint.KEY) + .setLifespan(1.days.inWholeMilliseconds) + .setMaxAttempts(Parameters.UNLIMITED) + .setQueue("ResetSvrGuessCountJob") + .setMaxInstancesForFactory(1) + .build(), + null, + false + ) + + override fun serialize(): ByteArray? { + return JsonJobData.Builder() + .putString(KEY_CHANGE_SESSION, serializedChangeSession) + .putBoolean(KEY_SVR2_COMPLETE, svr2Complete) + .build() + .serialize() + } + + override fun getFactoryKey(): String = KEY + + override fun run(): Result { + SvrRepository.operationLock.withLock { + val pin = SignalStore.svr().pin + + if (SignalStore.svr().hasOptedOut()) { + Log.w(TAG, "Opted out of SVR! Nothing to migrate.") + return Result.success() + } + + if (pin == null) { + Log.w(TAG, "No PIN available! Can't migrate!") + return Result.success() + } + + val masterKey: MasterKey = SignalStore.svr().getOrCreateMasterKey() + + val svr2Result = if (!svr2Complete) { + resetGuessCount(ApplicationDependencies.getSignalServiceAccountManager().getSecureValueRecoveryV2(BuildConfig.SVR2_MRENCLAVE), pin, masterKey) + } else { + Log.d(TAG, "Already reset guess count on SVR2. Skipping.") + Result.success() + } + + if (!svr2Result.isSuccess) { + return svr2Result + } else { + Log.d(TAG, "SVR2 reset complete. Marking as such so we do not retry it if SVR1 fails.") + svr2Complete = true + } + + return resetGuessCount(SecureValueRecoveryV1(ApplicationDependencies.getKeyBackupService(BuildConfig.KBS_ENCLAVE)), pin, masterKey) + } + } + + override fun onFailure() = Unit + + private fun resetGuessCount(svr: SecureValueRecovery, pin: String, masterKey: MasterKey): Result { + val session: PinChangeSession = if (serializedChangeSession != null) { + svr.resumePinChangeSession(pin, SignalStore.svr().getOrCreateMasterKey(), serializedChangeSession) + } else { + svr.setPin(pin, masterKey) + } + + return when (val response: BackupResponse = session.execute()) { + is BackupResponse.Success -> { + Log.i(TAG, "Successfully reset guess count. $svr") + SignalStore.svr().appendAuthTokenToList(response.authorization.asBasic()) + Result.success() + } + is BackupResponse.ApplicationError -> { + Log.w(TAG, "Hit an application error. Retrying. $svr", response.exception) + Result.retry(defaultBackoff()) + } + BackupResponse.EnclaveNotFound -> { + Log.w(TAG, "Could not find the enclave. Giving up. $svr") + Result.success() + } + BackupResponse.ExposeFailure -> { + Log.w(TAG, "Failed to expose the backup. Giving up. $svr") + Result.success() + } + is BackupResponse.NetworkError -> { + Log.w(TAG, "Hit a network error. Retrying. $svr", response.exception) + Result.retry(defaultBackoff()) + } + BackupResponse.ServerRejected -> { + Log.w(TAG, "Server told us to stop trying. Giving up. $svr") + Result.success() + } + } + } + + class Factory : Job.Factory { + override fun create(parameters: Parameters, serializedData: ByteArray?): ResetSvrGuessCountJob { + val data = JsonJobData.deserialize(serializedData) + + return ResetSvrGuessCountJob( + parameters, + data.getString(KEY_CHANGE_SESSION), + data.getBoolean(KEY_SVR2_COMPLETE) + ) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java index ec586e6aa8..955ee335d6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java @@ -17,7 +17,6 @@ import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.UnknownStorageIdTable; import org.thoughtcrime.securesms.database.model.RecipientRecord; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.jobmanager.JsonJobData; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.keyvalue.SignalStore; @@ -171,7 +170,7 @@ public class StorageSyncJob extends BaseJob { @Override protected void onRun() throws IOException, RetryLaterException, UntrustedIdentityException { - if (!SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut()) { + if (!SignalStore.svr().hasPin() && !SignalStore.svr().hasOptedOut()) { Log.i(TAG, "Doesn't have a PIN. Skipping."); return; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr2MirrorJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr2MirrorJob.kt new file mode 100644 index 0000000000..bac8c3bc77 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr2MirrorJob.kt @@ -0,0 +1,119 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ +package org.thoughtcrime.securesms.jobs + +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.BuildConfig +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.jobmanager.JsonJobData +import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.pin.SvrRepository +import org.thoughtcrime.securesms.util.FeatureFlags +import org.whispersystems.signalservice.api.svr.SecureValueRecovery.BackupResponse +import org.whispersystems.signalservice.api.svr.SecureValueRecovery.PinChangeSession +import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV2 +import kotlin.concurrent.withLock + +/** + * Ensures a user's SVR data is written to SVR2. + */ +class Svr2MirrorJob private constructor(parameters: Parameters, private var serializedChangeSession: String?) : Job(parameters) { + + companion object { + const val KEY = "Svr2MirrorJob" + + private val TAG = Log.tag(Svr2MirrorJob::class.java) + + private const val KEY_CHANGE_SESSION = "change_session" + } + + constructor() : this( + Parameters.Builder() + .addConstraint(NetworkConstraint.KEY) + .setLifespan(Parameters.IMMORTAL) + .setMaxAttempts(Parameters.UNLIMITED) + .setQueue("Svr2MirrorJob") + .setMaxInstancesForFactory(1) + .build(), + null + ) + + override fun serialize(): ByteArray? { + return JsonJobData.Builder() + .putString(KEY_CHANGE_SESSION, serializedChangeSession) + .build() + .serialize() + } + + override fun getFactoryKey(): String = KEY + + override fun run(): Result { + SvrRepository.operationLock.withLock { + val pin = SignalStore.svr().pin + + if (SignalStore.svr().hasOptedOut()) { + Log.w(TAG, "Opted out of SVR! Nothing to migrate.") + return Result.success() + } + + if (pin == null) { + Log.w(TAG, "No PIN available! Can't migrate!") + return Result.success() + } + + if (!FeatureFlags.svr2()) { + Log.w(TAG, "SVR2 was disabled! SKipping.") + return Result.success() + } + + val svr2: SecureValueRecoveryV2 = ApplicationDependencies.getSignalServiceAccountManager().getSecureValueRecoveryV2(BuildConfig.SVR2_MRENCLAVE) + + val session: PinChangeSession = serializedChangeSession?.let { session -> + svr2.resumePinChangeSession(pin, SignalStore.svr().getOrCreateMasterKey(), session) + } ?: svr2.setPin(pin, SignalStore.svr().getOrCreateMasterKey()) + + serializedChangeSession = session.serialize() + + return when (val response: BackupResponse = session.execute()) { + is BackupResponse.Success -> { + Log.i(TAG, "Successfully migrated to SVR2!") + SignalStore.svr().appendAuthTokenToList(response.authorization.asBasic()) + ApplicationDependencies.getJobManager().add(RefreshAttributesJob()) + Result.success() + } + is BackupResponse.ApplicationError -> { + Log.w(TAG, "Hit an application error. Retrying.", response.exception) + Result.retry(defaultBackoff()) + } + BackupResponse.EnclaveNotFound -> { + Log.w(TAG, "Could not find the enclave. Giving up.") + Result.success() + } + BackupResponse.ExposeFailure -> { + Log.w(TAG, "Failed to expose the backup. Giving up.") + Result.success() + } + is BackupResponse.NetworkError -> { + Log.w(TAG, "Hit a network error. Retrying.", response.exception) + Result.retry(defaultBackoff()) + } + BackupResponse.ServerRejected -> { + Log.w(TAG, "Server told us to stop trying. Giving up.") + Result.success() + } + } + } + } + + override fun onFailure() = Unit + + class Factory : Job.Factory { + override fun create(parameters: Parameters, serializedData: ByteArray?): Svr2MirrorJob { + return Svr2MirrorJob(parameters, JsonJobData.deserialize(serializedData).getString(KEY_CHANGE_SESSION)) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PaymentsValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PaymentsValues.kt index 3d0e121182..7476fb02e5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PaymentsValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PaymentsValues.kt @@ -245,8 +245,8 @@ internal class PaymentsValues internal constructor(store: KeyValueStore) : Signa fun showUpdatePinInfoCard(): Boolean { return if (userHasLargeBalance() && - SignalStore.kbsValues().hasPin() && - !SignalStore.kbsValues().hasOptedOut() && SignalStore.pinValues().keyboardType == PinKeyboardType.NUMERIC + SignalStore.svr().hasPin() && + !SignalStore.svr().hasOptedOut() && SignalStore.pinValues().keyboardType == PinKeyboardType.NUMERIC ) { store.getBoolean(SHOW_CASHING_OUT_INFO_CARD, true) } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PinValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PinValues.java index 549ca82064..080fa1cdee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PinValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PinValues.java @@ -1,9 +1,6 @@ package org.thoughtcrime.securesms.keyvalue; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.common.collect.Lists; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; @@ -11,13 +8,11 @@ import org.thoughtcrime.securesms.lock.SignalPinReminders; import org.thoughtcrime.securesms.lock.v2.PinKeyboardType; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; /** - * Specifically handles just the UI/UX state around PINs. For actual keys, see {@link KbsValues}. + * Specifically handles just the UI/UX state around PINs. For actual keys, see {@link SvrValues}. */ public final class PinValues extends SignalStoreValues { @@ -50,7 +45,7 @@ public final class PinValues extends SignalStoreValues { .putLong(NEXT_INTERVAL, nextInterval) .apply(); - SignalStore.kbsValues().setPinIfNotPresent(pin); + SignalStore.svr().setPinIfNotPresent(pin); } public void onEntrySuccessWithWrongGuess(@NonNull String pin) { @@ -62,7 +57,7 @@ public final class PinValues extends SignalStoreValues { .putLong(NEXT_INTERVAL, nextInterval) .apply(); - SignalStore.kbsValues().setPinIfNotPresent(pin); + SignalStore.svr().setPinIfNotPresent(pin); } public void onEntrySkipWithWrongGuess() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SignalStore.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SignalStore.java index 9e1e700b76..537ad26128 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SignalStore.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SignalStore.java @@ -19,7 +19,7 @@ public final class SignalStore { private KeyValueStore store; private final AccountValues accountValues; - private final KbsValues kbsValues; + private final SvrValues svrValues; private final RegistrationValues registrationValues; private final PinValues pinValues; private final RemoteConfigValues remoteConfigValues; @@ -63,7 +63,7 @@ public final class SignalStore { private SignalStore(@NonNull KeyValueStore store) { this.store = store; this.accountValues = new AccountValues(store); - this.kbsValues = new KbsValues(store); + this.svrValues = new SvrValues(store); this.registrationValues = new RegistrationValues(store); this.pinValues = new PinValues(store); this.remoteConfigValues = new RemoteConfigValues(store); @@ -92,7 +92,7 @@ public final class SignalStore { public static void onFirstEverAppLaunch() { account().onFirstEverAppLaunch(); - kbsValues().onFirstEverAppLaunch(); + svr().onFirstEverAppLaunch(); registrationValues().onFirstEverAppLaunch(); pinValues().onFirstEverAppLaunch(); remoteConfigValues().onFirstEverAppLaunch(); @@ -121,7 +121,7 @@ public final class SignalStore { public static List getKeysToIncludeInBackup() { List keys = new ArrayList<>(); keys.addAll(account().getKeysToIncludeInBackup()); - keys.addAll(kbsValues().getKeysToIncludeInBackup()); + keys.addAll(svr().getKeysToIncludeInBackup()); keys.addAll(registrationValues().getKeysToIncludeInBackup()); keys.addAll(pinValues().getKeysToIncludeInBackup()); keys.addAll(remoteConfigValues().getKeysToIncludeInBackup()); @@ -168,8 +168,8 @@ public final class SignalStore { return getInstance().accountValues; } - public static @NonNull KbsValues kbsValues() { - return getInstance().kbsValues; + public static @NonNull SvrValues svr() { + return getInstance().svrValues; } public static @NonNull RegistrationValues registrationValues() { 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 3945483b70..ff54c8779d 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.kbsValues().getOrCreateMasterKey().deriveStorageServiceKey(); + return SignalStore.svr().getOrCreateMasterKey().deriveStorageServiceKey(); } public long getLastSyncTime() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/KbsValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SvrValues.java similarity index 76% rename from app/src/main/java/org/thoughtcrime/securesms/keyvalue/KbsValues.java rename to app/src/main/java/org/thoughtcrime/securesms/keyvalue/SvrValues.java index 3eadf5a765..16ec15fc68 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/KbsValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SvrValues.java @@ -5,7 +5,6 @@ import androidx.annotation.Nullable; import org.signal.core.util.StringStringSerializer; 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; @@ -13,14 +12,13 @@ import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse import java.io.IOException; import java.security.SecureRandom; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; -public final class KbsValues extends SignalStoreValues { +public final class SvrValues extends SignalStoreValues { - public static final String V2_LOCK_ENABLED = "kbs.v2_lock_enabled"; + 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"; @@ -28,10 +26,10 @@ public final class KbsValues extends SignalStoreValues { 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 KBS_AUTH_TOKENS = "kbs.kbs_auth_tokens"; - private static final String KBS_LAST_AUTH_REFRESH_TIMESTAMP = "kbs.kbs_auth_tokens.last_refresh_timestamp"; + private static final String SVR_AUTH_TOKENS = "kbs.kbs_auth_tokens"; + private static final String SVR_LAST_AUTH_REFRESH_TIMESTAMP = "kbs.kbs_auth_tokens.last_refresh_timestamp"; - KbsValues(KeyValueStore store) { + SvrValues(KeyValueStore store) { super(store); } @@ -42,39 +40,27 @@ public final class KbsValues extends SignalStoreValues { @Override @NonNull List getKeysToIncludeInBackup() { - return List.of(KBS_AUTH_TOKENS); + return List.of(SVR_AUTH_TOKENS); } /** * Deliberately does not clear the {@link #MASTER_KEY}. - * - * Should only be called by {@link org.thoughtcrime.securesms.pin.PinState} */ public void clearRegistrationLockAndPin() { getStore().beginWrite() - .remove(V2_LOCK_ENABLED) + .remove(REGISTRATION_LOCK_ENABLED) .remove(TOKEN_RESPONSE) .remove(LOCK_LOCAL_PIN_HASH) .remove(PIN) .remove(LAST_CREATE_FAILED_TIMESTAMP) .remove(OPTED_OUT) - .remove(KBS_AUTH_TOKENS) - .remove(KBS_LAST_AUTH_REFRESH_TIMESTAMP) + .remove(SVR_AUTH_TOKENS) + .remove(SVR_LAST_AUTH_REFRESH_TIMESTAMP) .commit(); } - /** Should only be set by {@link org.thoughtcrime.securesms.pin.PinState}. */ - public synchronized void setKbsMasterKey(@NonNull KbsPinData pinData, @NonNull String pin) { - MasterKey masterKey = pinData.getMasterKey(); - String tokenResponse; - try { - tokenResponse = JsonUtils.toJson(pinData.getTokenResponse()); - } catch (IOException e) { - throw new AssertionError(e); - } - + public synchronized void setMasterKey(@NonNull MasterKey masterKey, @NonNull String pin) { getStore().beginWrite() - .putString(TOKEN_RESPONSE, tokenResponse) .putBlob(MASTER_KEY, masterKey.serialize()) .putString(LOCK_LOCAL_PIN_HASH, PinHashUtil.localPinHash(pin)) .putString(PIN, pin) @@ -89,19 +75,17 @@ public final class KbsValues extends SignalStoreValues { } } - /** Should only be set by {@link org.thoughtcrime.securesms.pin.PinState}. */ - public synchronized void setV2RegistrationLockEnabled(boolean enabled) { - putBoolean(V2_LOCK_ENABLED, enabled); + public synchronized void setRegistrationLockEnabled(boolean enabled) { + putBoolean(REGISTRATION_LOCK_ENABLED, enabled); } /** * Whether or not registration lock V2 is enabled. */ - public synchronized boolean isV2RegistrationLockEnabled() { - return getBoolean(V2_LOCK_ENABLED, false); + public synchronized boolean isRegistrationLockEnabled() { + return getBoolean(REGISTRATION_LOCK_ENABLED, false); } - /** Should only be set by {@link org.thoughtcrime.securesms.pin.PinState}. */ public synchronized void onPinCreateFailure() { putLong(LAST_CREATE_FAILED_TIMESTAMP, System.currentTimeMillis()); } @@ -136,7 +120,7 @@ public final class KbsValues extends SignalStoreValues { * Returns null if master key is not backed up by a pin. */ public synchronized @Nullable MasterKey getPinBackedMasterKey() { - if (!isV2RegistrationLockEnabled()) return null; + if (!isRegistrationLockEnabled()) return null; return getMasterKey(); } @@ -184,12 +168,12 @@ public final class KbsValues extends SignalStoreValues { } public synchronized void putAuthTokenList(List tokens) { - putList(KBS_AUTH_TOKENS, tokens, StringStringSerializer.INSTANCE); + putList(SVR_AUTH_TOKENS, tokens, StringStringSerializer.INSTANCE); setLastRefreshAuthTimestamp(System.currentTimeMillis()); } - public synchronized List getKbsAuthTokenList() { - return getList(KBS_AUTH_TOKENS, StringStringSerializer.INSTANCE); + public synchronized List getAuthTokenList() { + return getList(SVR_AUTH_TOKENS, StringStringSerializer.INSTANCE); } /** @@ -198,7 +182,7 @@ public final class KbsValues extends SignalStoreValues { * @return whether the token was added (new) or ignored (already existed) */ public synchronized boolean appendAuthTokenToList(String token) { - List tokens = getKbsAuthTokenList(); + List tokens = getAuthTokenList(); if (tokens.contains(token)) { return false; } else { @@ -209,7 +193,7 @@ public final class KbsValues extends SignalStoreValues { } public boolean removeAuthTokens(@NonNull List invalid) { - List tokens = new ArrayList<>(getKbsAuthTokenList()); + List tokens = new ArrayList<>(getAuthTokenList()); if (tokens.removeAll(invalid)) { putAuthTokenList(tokens); return true; @@ -218,7 +202,6 @@ public final class KbsValues extends SignalStoreValues { return false; } - /** Should only be called by {@link org.thoughtcrime.securesms.pin.PinState}. */ public synchronized void optOut() { getStore().beginWrite() .putBoolean(OPTED_OUT, true) @@ -247,10 +230,10 @@ public final class KbsValues extends SignalStoreValues { } private void setLastRefreshAuthTimestamp(long timestamp) { - putLong(KBS_LAST_AUTH_REFRESH_TIMESTAMP, timestamp); + putLong(SVR_LAST_AUTH_REFRESH_TIMESTAMP, timestamp); } public long getLastRefreshAuthTimestamp() { - return getLong(KBS_LAST_AUTH_REFRESH_TIMESTAMP, 0L); + return getLong(SVR_LAST_AUTH_REFRESH_TIMESTAMP, 0L); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/SignalPinReminderDialog.java b/app/src/main/java/org/thoughtcrime/securesms/lock/SignalPinReminderDialog.java index f43ffaf970..43025e384d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/lock/SignalPinReminderDialog.java +++ b/app/src/main/java/org/thoughtcrime/securesms/lock/SignalPinReminderDialog.java @@ -31,8 +31,8 @@ import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher; import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity; -import org.thoughtcrime.securesms.lock.v2.KbsConstants; +import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity; +import org.thoughtcrime.securesms.lock.v2.SvrConstants; import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.ViewUtil; import org.whispersystems.signalservice.api.kbs.PinHashUtil; @@ -44,7 +44,7 @@ public final class SignalPinReminderDialog { private static final String TAG = Log.tag(SignalPinReminderDialog.class); public static void show(@NonNull Context context, @NonNull Launcher launcher, @NonNull Callback mainCallback) { - if (!SignalStore.kbsValues().hasPin()) { + if (!SignalStore.svr().hasPin()) { throw new AssertionError("Must have a PIN!"); } @@ -89,7 +89,7 @@ public final class SignalPinReminderDialog { @Override public void onClick(@NonNull View widget) { dialog.dismiss(); - launcher.launch(CreateKbsPinActivity.getIntentForPinChangeFromForgotPin(context), CreateKbsPinActivity.REQUEST_NEW_PIN); + launcher.launch(CreateSvrPinActivity.getIntentForPinChangeFromForgotPin(context), CreateSvrPinActivity.REQUEST_NEW_PIN); } }; @@ -115,11 +115,11 @@ public final class SignalPinReminderDialog { pinEditText.addTextChangedListener(new SimpleTextWatcher() { - private final String localHash = Objects.requireNonNull(SignalStore.kbsValues().getLocalPinHash()); + private final String localHash = Objects.requireNonNull(SignalStore.svr().getLocalPinHash()); @Override public void onTextChanged(String text) { - if (text.length() >= KbsConstants.MINIMUM_PIN_LENGTH) { + if (text.length() >= SvrConstants.MINIMUM_PIN_LENGTH) { submit.setEnabled(true); if (PinHashUtil.verifyLocalPinHash(localHash, text)) { @@ -169,7 +169,7 @@ public final class SignalPinReminderDialog { private final String localPinHash; V2PinVerifier() { - localPinHash = SignalStore.kbsValues().getLocalPinHash(); + localPinHash = SignalStore.svr().getLocalPinHash(); if (localPinHash == null) throw new AssertionError("No local pin hash set at time of reminder"); } @@ -179,7 +179,7 @@ public final class SignalPinReminderDialog { if (pin == null) return; if (TextUtils.isEmpty(pin)) return; - if (pin.length() < KbsConstants.MINIMUM_PIN_LENGTH) return; + if (pin.length() < SvrConstants.MINIMUM_PIN_LENGTH) return; if (PinHashUtil.verifyLocalPinHash(localPinHash, pin)) { callback.onPinCorrect(pin); diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseKbsPinFragment.java b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseSvrPinFragment.java similarity index 93% rename from app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseKbsPinFragment.java rename to app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseSvrPinFragment.java index 0867b5668c..8069eee080 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseKbsPinFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseSvrPinFragment.java @@ -14,7 +14,6 @@ import android.view.inputmethod.EditorInfo; import android.widget.EditText; import android.widget.TextView; -import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; @@ -33,7 +32,7 @@ import org.thoughtcrime.securesms.util.text.AfterTextChanged; import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton; import org.thoughtcrime.securesms.util.views.LearnMoreTextView; -public abstract class BaseKbsPinFragment extends LoggingFragment { +public abstract class BaseSvrPinFragment extends LoggingFragment { private TextView title; private LearnMoreTextView description; @@ -62,8 +61,8 @@ public abstract class BaseKbsPinFragment initializeViews(view); viewModel = initializeViewModel(); - viewModel.getUserEntry().observe(getViewLifecycleOwner(), kbsPin -> { - boolean isEntryValid = kbsPin.length() >= KbsConstants.MINIMUM_PIN_LENGTH; + viewModel.getUserEntry().observe(getViewLifecycleOwner(), svrPin -> { + boolean isEntryValid = svrPin.length() >= SvrConstants.MINIMUM_PIN_LENGTH; confirm.setEnabled(isEntryValid); confirm.setAlpha(isEntryValid ? 1f : 0.5f); @@ -100,9 +99,9 @@ public abstract class BaseKbsPinFragment @Override public void onPrepareOptionsMenu(@NonNull Menu menu) { - if (RegistrationLockUtil.userHasRegistrationLock(requireContext()) || - SignalStore.kbsValues().hasPin() || - SignalStore.kbsValues().hasOptedOut()) + if (SignalStore.svr().isRegistrationLockEnabled() || + SignalStore.svr().hasPin() || + SignalStore.svr().hasOptedOut()) { menu.clear(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseKbsPinViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseSvrPinViewModel.java similarity index 81% rename from app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseKbsPinViewModel.java rename to app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseSvrPinViewModel.java index 4a234792ef..248e5a55c7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseKbsPinViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseSvrPinViewModel.java @@ -3,8 +3,8 @@ package org.thoughtcrime.securesms.lock.v2; import androidx.annotation.MainThread; import androidx.lifecycle.LiveData; -interface BaseKbsPinViewModel { - LiveData getUserEntry(); +interface BaseSvrPinViewModel { + LiveData getUserEntry(); LiveData getKeyboard(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmKbsPinRepository.java b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmKbsPinRepository.java deleted file mode 100644 index b3d590cd95..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmKbsPinRepository.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.thoughtcrime.securesms.lock.v2; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.core.util.Consumer; - -import org.signal.core.util.logging.Log; -import org.signal.libsignal.protocol.InvalidKeyException; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.pin.PinState; -import org.signal.core.util.concurrent.SimpleTask; -import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; - -import java.io.IOException; - -final class ConfirmKbsPinRepository { - - private static final String TAG = Log.tag(ConfirmKbsPinRepository.class); - - void setPin(@NonNull KbsPin kbsPin, @NonNull PinKeyboardType keyboard, @NonNull Consumer resultConsumer) { - - Context context = ApplicationDependencies.getApplication(); - String pinValue = kbsPin.toString(); - - SimpleTask.run(() -> { - try { - Log.i(TAG, "Setting pin on KBS"); - PinState.onPinChangedOrCreated(context, pinValue, keyboard); - Log.i(TAG, "Pin set on KBS"); - - return PinSetResult.SUCCESS; - } catch (IOException | UnauthenticatedResponseException | InvalidKeyException e) { - Log.w(TAG, e); - PinState.onPinCreateFailure(); - return PinSetResult.FAILURE; - } - }, resultConsumer::accept); - } - - enum PinSetResult { - SUCCESS, - FAILURE - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmKbsPinViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmKbsPinViewModel.java deleted file mode 100644 index 0c852ce913..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmKbsPinViewModel.java +++ /dev/null @@ -1,125 +0,0 @@ -package org.thoughtcrime.securesms.lock.v2; - -import androidx.annotation.MainThread; -import androidx.annotation.NonNull; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.Transformations; -import androidx.lifecycle.ViewModel; -import androidx.lifecycle.ViewModelProvider; - -import org.thoughtcrime.securesms.lock.v2.ConfirmKbsPinRepository.PinSetResult; -import org.thoughtcrime.securesms.util.DefaultValueLiveData; - -final class ConfirmKbsPinViewModel extends ViewModel implements BaseKbsPinViewModel { - - private final ConfirmKbsPinRepository repository; - - private final DefaultValueLiveData userEntry = new DefaultValueLiveData<>(KbsPin.EMPTY); - private final DefaultValueLiveData keyboard = new DefaultValueLiveData<>(PinKeyboardType.NUMERIC); - private final DefaultValueLiveData saveAnimation = new DefaultValueLiveData<>(SaveAnimation.NONE); - private final DefaultValueLiveData label = new DefaultValueLiveData<>(LabelState.EMPTY); - - private final KbsPin pinToConfirm; - - private ConfirmKbsPinViewModel(@NonNull KbsPin pinToConfirm, - @NonNull PinKeyboardType keyboard, - @NonNull ConfirmKbsPinRepository repository) - { - this.keyboard.setValue(keyboard); - - this.pinToConfirm = pinToConfirm; - this.repository = repository; - } - - LiveData getSaveAnimation() { - return Transformations.distinctUntilChanged(saveAnimation); - } - - LiveData getLabel() { - return Transformations.distinctUntilChanged(label); - } - - @Override - public void confirm() { - KbsPin userEntry = this.userEntry.getValue(); - - if (pinToConfirm.toString().equals(userEntry.toString())) { - this.label.setValue(LabelState.CREATING_PIN); - this.saveAnimation.setValue(SaveAnimation.LOADING); - - repository.setPin(pinToConfirm, this.keyboard.getValue(), this::handleResult); - } else { - this.userEntry.setValue(KbsPin.EMPTY); - this.label.setValue(LabelState.PIN_DOES_NOT_MATCH); - } - } - - @Override - public LiveData getUserEntry() { - return userEntry; - } - - @Override - public LiveData getKeyboard() { - return keyboard; - } - - @MainThread - public void setUserEntry(String userEntry) { - this.userEntry.setValue(KbsPin.from(userEntry)); - } - - @MainThread - public void toggleAlphaNumeric() { - this.keyboard.setValue(this.keyboard.getValue().getOther()); - } - - private void handleResult(PinSetResult result) { - switch (result) { - case SUCCESS: - this.saveAnimation.setValue(SaveAnimation.SUCCESS); - break; - case FAILURE: - this.saveAnimation.setValue(SaveAnimation.FAILURE); - break; - default: - throw new IllegalStateException("Unknown state: " + result.name()); - } - } - - enum LabelState { - RE_ENTER_PIN, - PIN_DOES_NOT_MATCH, - CREATING_PIN, - EMPTY - } - - enum SaveAnimation { - NONE, - LOADING, - SUCCESS, - FAILURE - } - - static final class Factory implements ViewModelProvider.Factory { - - private final KbsPin pinToConfirm; - private final PinKeyboardType keyboard; - private final ConfirmKbsPinRepository repository; - - Factory(@NonNull KbsPin pinToConfirm, - @NonNull PinKeyboardType keyboard, - @NonNull ConfirmKbsPinRepository repository) - { - this.pinToConfirm = pinToConfirm; - this.keyboard = keyboard; - this.repository = repository; - } - - @Override - public @NonNull T create(@NonNull Class modelClass) { - //noinspection unchecked - return (T) new ConfirmKbsPinViewModel(pinToConfirm, keyboard, repository); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmKbsPinFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmSvrPinFragment.kt similarity index 81% rename from app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmKbsPinFragment.kt rename to app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmSvrPinFragment.kt index 81626a66f4..fc5d924be0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmKbsPinFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmSvrPinFragment.kt @@ -10,16 +10,16 @@ import androidx.lifecycle.ViewModelProvider import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.dependencies.ApplicationDependencies -import org.thoughtcrime.securesms.lock.v2.ConfirmKbsPinViewModel.SaveAnimation +import org.thoughtcrime.securesms.lock.v2.ConfirmSvrPinViewModel.SaveAnimation import org.thoughtcrime.securesms.megaphone.Megaphones import org.thoughtcrime.securesms.registration.RegistrationUtil import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.thoughtcrime.securesms.util.SpanUtil -internal class ConfirmKbsPinFragment : BaseKbsPinFragment() { +internal class ConfirmSvrPinFragment : BaseSvrPinFragment() { override fun initializeViewStates() { - val args = ConfirmKbsPinFragmentArgs.fromBundle(requireArguments()) + val args = ConfirmSvrPinFragmentArgs.fromBundle(requireArguments()) if (args.isPinChange) { initializeViewStatesForPinChange() } else { @@ -28,14 +28,13 @@ internal class ConfirmKbsPinFragment : BaseKbsPinFragment updateLabel(label) } + val factory = ConfirmSvrPinViewModel.Factory(userEntry, keyboard) + val viewModel = ViewModelProvider(this, factory)[ConfirmSvrPinViewModel::class.java] + viewModel.label.observe(viewLifecycleOwner) { label: ConfirmSvrPinViewModel.LabelState -> updateLabel(label) } viewModel.saveAnimation.observe(viewLifecycleOwner) { animation: SaveAnimation -> updateSaveAnimation(animation) } return viewModel } @@ -58,15 +57,15 @@ internal class ConfirmKbsPinFragment : BaseKbsPinFragment label.text = "" - ConfirmKbsPinViewModel.LabelState.CREATING_PIN -> { + ConfirmSvrPinViewModel.LabelState.EMPTY -> label.text = "" + ConfirmSvrPinViewModel.LabelState.CREATING_PIN -> { label.setText(R.string.ConfirmKbsPinFragment__creating_pin) input.isEnabled = false } - ConfirmKbsPinViewModel.LabelState.RE_ENTER_PIN -> label.setText(R.string.ConfirmKbsPinFragment__re_enter_your_pin) - ConfirmKbsPinViewModel.LabelState.PIN_DOES_NOT_MATCH -> { + ConfirmSvrPinViewModel.LabelState.RE_ENTER_PIN -> label.setText(R.string.ConfirmKbsPinFragment__re_enter_your_pin) + ConfirmSvrPinViewModel.LabelState.PIN_DOES_NOT_MATCH -> { label.text = SpanUtil.color( ContextCompat.getColor(requireContext(), R.color.red_500), getString(R.string.ConfirmKbsPinFragment__pins_dont_match) diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmSvrPinViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmSvrPinViewModel.java new file mode 100644 index 0000000000..dcab3d423d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmSvrPinViewModel.java @@ -0,0 +1,125 @@ +package org.thoughtcrime.securesms.lock.v2; + +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.Transformations; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; + +import org.thoughtcrime.securesms.pin.SvrRepository; +import org.thoughtcrime.securesms.util.DefaultValueLiveData; +import org.whispersystems.signalservice.api.svr.SecureValueRecovery; + +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.core.Scheduler; +import io.reactivex.rxjava3.core.Single; +import io.reactivex.rxjava3.disposables.CompositeDisposable; +import io.reactivex.rxjava3.schedulers.Schedulers; + +final class ConfirmSvrPinViewModel extends ViewModel implements BaseSvrPinViewModel { + + private final DefaultValueLiveData userEntry = new DefaultValueLiveData<>(SvrPin.EMPTY); + private final DefaultValueLiveData keyboard = new DefaultValueLiveData<>(PinKeyboardType.NUMERIC); + private final DefaultValueLiveData saveAnimation = new DefaultValueLiveData<>(SaveAnimation.NONE); + private final DefaultValueLiveData label = new DefaultValueLiveData<>(LabelState.EMPTY); + + private final SvrPin pinToConfirm; + + private final CompositeDisposable disposables = new CompositeDisposable(); + + private ConfirmSvrPinViewModel(@NonNull SvrPin pinToConfirm, @NonNull PinKeyboardType keyboard) { + this.keyboard.setValue(keyboard); + this.pinToConfirm = pinToConfirm; + } + + LiveData getSaveAnimation() { + return Transformations.distinctUntilChanged(saveAnimation); + } + + LiveData getLabel() { + return Transformations.distinctUntilChanged(label); + } + + @Override + public void confirm() { + SvrPin userEntry = this.userEntry.getValue(); + + if (pinToConfirm.toString().equals(userEntry.toString())) { + this.label.setValue(LabelState.CREATING_PIN); + this.saveAnimation.setValue(SaveAnimation.LOADING); + + disposables.add( + Single.fromCallable(() -> SvrRepository.setPin(pinToConfirm.toString(), this.keyboard.getValue())) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + if (result instanceof SecureValueRecovery.BackupResponse.Success) { + this.saveAnimation.setValue(SaveAnimation.SUCCESS); + } else { + this.saveAnimation.setValue(SaveAnimation.FAILURE); + } + }) + ); + } else { + this.userEntry.setValue(SvrPin.EMPTY); + this.label.setValue(LabelState.PIN_DOES_NOT_MATCH); + } + } + + @Override + public LiveData getUserEntry() { + return userEntry; + } + + @Override + public LiveData getKeyboard() { + return keyboard; + } + + @MainThread + public void setUserEntry(String userEntry) { + this.userEntry.setValue(SvrPin.from(userEntry)); + } + + @MainThread + public void toggleAlphaNumeric() { + this.keyboard.setValue(this.keyboard.getValue().getOther()); + } + + @Override + protected void onCleared() { + disposables.clear(); + } + + enum LabelState { + RE_ENTER_PIN, + PIN_DOES_NOT_MATCH, + CREATING_PIN, + EMPTY + } + + enum SaveAnimation { + NONE, + LOADING, + SUCCESS, + FAILURE + } + + static final class Factory implements ViewModelProvider.Factory { + + private final SvrPin pinToConfirm; + private final PinKeyboardType keyboard; + + Factory(@NonNull SvrPin pinToConfirm, @NonNull PinKeyboardType keyboard) { + this.pinToConfirm = pinToConfirm; + this.keyboard = keyboard; + } + + @Override + public @NonNull T create(@NonNull Class modelClass) { + //noinspection unchecked + return (T) new ConfirmSvrPinViewModel(pinToConfirm, keyboard); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/CreateKbsPinActivity.java b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/CreateSvrPinActivity.java similarity index 87% rename from app/src/main/java/org/thoughtcrime/securesms/lock/v2/CreateKbsPinActivity.java rename to app/src/main/java/org/thoughtcrime/securesms/lock/v2/CreateSvrPinActivity.java index d2895fec08..12667ea6db 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/CreateKbsPinActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/CreateSvrPinActivity.java @@ -17,14 +17,14 @@ import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.DynamicRegistrationTheme; import org.thoughtcrime.securesms.util.DynamicTheme; -public class CreateKbsPinActivity extends BaseActivity { +public class CreateSvrPinActivity extends BaseActivity { public static final int REQUEST_NEW_PIN = 27698; private final DynamicTheme dynamicTheme = new DynamicRegistrationTheme(); public static @NonNull Intent getIntentForPinCreate(@NonNull Context context) { - CreateKbsPinFragmentArgs args = new CreateKbsPinFragmentArgs.Builder() + CreateSvrPinFragmentArgs args = new CreateSvrPinFragmentArgs.Builder() .setIsForgotPin(false) .setIsPinChange(false) .build(); @@ -33,7 +33,7 @@ public class CreateKbsPinActivity extends BaseActivity { } public static @NonNull Intent getIntentForPinChangeFromForgotPin(@NonNull Context context) { - CreateKbsPinFragmentArgs args = new CreateKbsPinFragmentArgs.Builder() + CreateSvrPinFragmentArgs args = new CreateSvrPinFragmentArgs.Builder() .setIsForgotPin(true) .setIsPinChange(true) .build(); @@ -42,7 +42,7 @@ public class CreateKbsPinActivity extends BaseActivity { } public static @NonNull Intent getIntentForPinChangeFromSettings(@NonNull Context context) { - CreateKbsPinFragmentArgs args = new CreateKbsPinFragmentArgs.Builder() + CreateSvrPinFragmentArgs args = new CreateSvrPinFragmentArgs.Builder() .setIsForgotPin(false) .setIsPinChange(true) .build(); @@ -50,8 +50,8 @@ public class CreateKbsPinActivity extends BaseActivity { return getIntentWithArgs(context, args); } - private static @NonNull Intent getIntentWithArgs(@NonNull Context context, @NonNull CreateKbsPinFragmentArgs args) { - return new Intent(context, CreateKbsPinActivity.class).putExtras(args.toBundle()); + private static @NonNull Intent getIntentWithArgs(@NonNull Context context, @NonNull CreateSvrPinFragmentArgs args) { + return new Intent(context, CreateSvrPinActivity.class).putExtras(args.toBundle()); } @Override @@ -68,7 +68,7 @@ public class CreateKbsPinActivity extends BaseActivity { setContentView(R.layout.create_kbs_pin_activity); - CreateKbsPinFragmentArgs arguments = CreateKbsPinFragmentArgs.fromBundle(getIntent().getExtras()); + CreateSvrPinFragmentArgs arguments = CreateSvrPinFragmentArgs.fromBundle(getIntent().getExtras()); NavGraph graph = Navigation.findNavController(this, R.id.nav_host_fragment).getGraph(); Navigation.findNavController(this, R.id.nav_host_fragment).setGraph(graph, arguments.toBundle()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/CreateKbsPinFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/CreateSvrPinFragment.kt similarity index 83% rename from app/src/main/java/org/thoughtcrime/securesms/lock/v2/CreateKbsPinFragment.kt rename to app/src/main/java/org/thoughtcrime/securesms/lock/v2/CreateSvrPinFragment.kt index 2331fb2f8c..93014ab865 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/CreateKbsPinFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/CreateSvrPinFragment.kt @@ -10,14 +10,14 @@ import androidx.core.view.ViewCompat import androidx.lifecycle.ViewModelProvider import androidx.navigation.Navigation.findNavController import org.thoughtcrime.securesms.R -import org.thoughtcrime.securesms.lock.v2.CreateKbsPinViewModel.NavigationEvent -import org.thoughtcrime.securesms.lock.v2.CreateKbsPinViewModel.PinErrorEvent +import org.thoughtcrime.securesms.lock.v2.CreateSvrPinViewModel.NavigationEvent +import org.thoughtcrime.securesms.lock.v2.CreateSvrPinViewModel.PinErrorEvent import org.thoughtcrime.securesms.util.SpanUtil import org.thoughtcrime.securesms.util.navigation.safeNavigate -class CreateKbsPinFragment : BaseKbsPinFragment() { +class CreateSvrPinFragment : BaseSvrPinFragment() { override fun initializeViewStates() { - val args = CreateKbsPinFragmentArgs.fromBundle(requireArguments()) + val args = CreateSvrPinFragmentArgs.fromBundle(requireArguments()) if (args.isPinChange) { initializeViewStatesForPinChange(args.isForgotPin) } else { @@ -40,9 +40,9 @@ class CreateKbsPinFragment : BaseKbsPinFragment() { description.setLearnMoreVisible(true) } - override fun initializeViewModel(): CreateKbsPinViewModel { - val viewModel = ViewModelProvider(this)[CreateKbsPinViewModel::class.java] - val args = CreateKbsPinFragmentArgs.fromBundle(requireArguments()) + override fun initializeViewModel(): CreateSvrPinViewModel { + val viewModel = ViewModelProvider(this)[CreateSvrPinViewModel::class.java] + val args = CreateSvrPinFragmentArgs.fromBundle(requireArguments()) viewModel.navigationEvents.observe(viewLifecycleOwner) { e: NavigationEvent -> onConfirmPin(e.userEntry, e.keyboard, args.isPinChange) } viewModel.errorEvents.observe(viewLifecycleOwner) { e: PinErrorEvent -> if (e == PinErrorEvent.WEAK_PIN) { @@ -62,8 +62,8 @@ class CreateKbsPinFragment : BaseKbsPinFragment() { return viewModel } - private fun onConfirmPin(userEntry: KbsPin, keyboard: PinKeyboardType, isPinChange: Boolean) { - val action = CreateKbsPinFragmentDirections.actionConfirmPin() + private fun onConfirmPin(userEntry: SvrPin, keyboard: PinKeyboardType, isPinChange: Boolean) { + val action = CreateSvrPinFragmentDirections.actionConfirmPin() action.userEntry = userEntry action.keyboard = keyboard action.isPinChange = isPinChange @@ -79,7 +79,7 @@ class CreateKbsPinFragment : BaseKbsPinFragment() { } private fun getPinLengthRestrictionText(@PluralsRes plurals: Int): String { - return resources.getQuantityString(plurals, KbsConstants.MINIMUM_PIN_LENGTH, KbsConstants.MINIMUM_PIN_LENGTH) + return resources.getQuantityString(plurals, SvrConstants.MINIMUM_PIN_LENGTH, SvrConstants.MINIMUM_PIN_LENGTH) } companion object { diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/CreateKbsPinViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/CreateSvrPinViewModel.java similarity index 76% rename from app/src/main/java/org/thoughtcrime/securesms/lock/v2/CreateKbsPinViewModel.java rename to app/src/main/java/org/thoughtcrime/securesms/lock/v2/CreateSvrPinViewModel.java index 7bd27f2741..59654663a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/CreateKbsPinViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/CreateSvrPinViewModel.java @@ -2,23 +2,23 @@ package org.thoughtcrime.securesms.lock.v2; import androidx.annotation.MainThread; import androidx.annotation.NonNull; -import androidx.core.util.Preconditions; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; import org.thoughtcrime.securesms.util.SingleLiveEvent; import org.whispersystems.signalservice.api.kbs.PinValidityChecker; +import org.whispersystems.signalservice.api.util.Preconditions; -public final class CreateKbsPinViewModel extends ViewModel implements BaseKbsPinViewModel { +public final class CreateSvrPinViewModel extends ViewModel implements BaseSvrPinViewModel { - private final MutableLiveData userEntry = new MutableLiveData<>(KbsPin.EMPTY); + private final MutableLiveData userEntry = new MutableLiveData<>(SvrPin.EMPTY); private final MutableLiveData keyboard = new MutableLiveData<>(PinKeyboardType.NUMERIC); private final SingleLiveEvent events = new SingleLiveEvent<>(); private final SingleLiveEvent errors = new SingleLiveEvent<>(); @Override - public LiveData getUserEntry() { + public LiveData getUserEntry() { return userEntry; } @@ -34,7 +34,7 @@ public final class CreateKbsPinViewModel extends ViewModel implements BaseKbsPin @Override @MainThread public void setUserEntry(String userEntry) { - this.userEntry.setValue(KbsPin.from(userEntry)); + this.userEntry.setValue(SvrPin.from(userEntry)); } @Override @@ -46,7 +46,7 @@ public final class CreateKbsPinViewModel extends ViewModel implements BaseKbsPin @Override @MainThread public void confirm() { - KbsPin pin = Preconditions.checkNotNull(this.getUserEntry().getValue()); + SvrPin pin = Preconditions.checkNotNull(this.getUserEntry().getValue()); PinKeyboardType keyboard = Preconditions.checkNotNull(this.getKeyboard().getValue()); if (PinValidityChecker.valid(pin.toString())) { @@ -57,15 +57,15 @@ public final class CreateKbsPinViewModel extends ViewModel implements BaseKbsPin } static final class NavigationEvent { - private final KbsPin userEntry; + private final SvrPin userEntry; private final PinKeyboardType keyboard; - NavigationEvent(@NonNull KbsPin userEntry, @NonNull PinKeyboardType keyboard) { + NavigationEvent(@NonNull SvrPin userEntry, @NonNull PinKeyboardType keyboard) { this.userEntry = userEntry; this.keyboard = keyboard; } - KbsPin getUserEntry() { + SvrPin getUserEntry() { return userEntry; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/RegistrationLockUtil.java b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/RegistrationLockUtil.java deleted file mode 100644 index f099f4bfa7..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/RegistrationLockUtil.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.thoughtcrime.securesms.lock.v2; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.util.TextSecurePreferences; - -public final class RegistrationLockUtil { - - private RegistrationLockUtil() {} - - public static boolean userHasRegistrationLock(@NonNull Context context) { - return TextSecurePreferences.isV1RegistrationLockEnabled(context) || SignalStore.kbsValues().isV2RegistrationLockEnabled(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/KbsConstants.java b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/SvrConstants.java similarity index 61% rename from app/src/main/java/org/thoughtcrime/securesms/lock/v2/KbsConstants.java rename to app/src/main/java/org/thoughtcrime/securesms/lock/v2/SvrConstants.java index 2876700dbf..71eaec45d2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/KbsConstants.java +++ b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/SvrConstants.java @@ -1,8 +1,8 @@ package org.thoughtcrime.securesms.lock.v2; -public final class KbsConstants { +public final class SvrConstants { public static final int MINIMUM_PIN_LENGTH = 4; - private KbsConstants() { } + private SvrConstants() { } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/KbsMigrationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/SvrMigrationActivity.java similarity index 90% rename from app/src/main/java/org/thoughtcrime/securesms/lock/v2/KbsMigrationActivity.java rename to app/src/main/java/org/thoughtcrime/securesms/lock/v2/SvrMigrationActivity.java index 07ed8b7ca3..d76c9f748b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/KbsMigrationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/SvrMigrationActivity.java @@ -13,14 +13,14 @@ import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.DynamicRegistrationTheme; import org.thoughtcrime.securesms.util.DynamicTheme; -public class KbsMigrationActivity extends BaseActivity { +public class SvrMigrationActivity extends BaseActivity { - public static final int REQUEST_NEW_PIN = CreateKbsPinActivity.REQUEST_NEW_PIN; + public static final int REQUEST_NEW_PIN = CreateSvrPinActivity.REQUEST_NEW_PIN; private final DynamicTheme dynamicTheme = new DynamicRegistrationTheme(); public static Intent createIntent() { - return new Intent(ApplicationDependencies.getApplication(), KbsMigrationActivity.class); + return new Intent(ApplicationDependencies.getApplication(), SvrMigrationActivity.class); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/KbsPin.java b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/SvrPin.java similarity index 61% rename from app/src/main/java/org/thoughtcrime/securesms/lock/v2/KbsPin.java rename to app/src/main/java/org/thoughtcrime/securesms/lock/v2/SvrPin.java index 1caa83ad5f..58ee66c45c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/KbsPin.java +++ b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/SvrPin.java @@ -6,17 +6,17 @@ import android.os.Parcelable; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -public final class KbsPin implements Parcelable { +public final class SvrPin implements Parcelable { - public static KbsPin EMPTY = new KbsPin(""); + public static SvrPin EMPTY = new SvrPin(""); private final String pin; - private KbsPin(String pin) { + private SvrPin(String pin) { this.pin = pin; } - private KbsPin(Parcel in) { + private SvrPin(Parcel in) { pin = in.readString(); } @@ -25,14 +25,14 @@ public final class KbsPin implements Parcelable { return pin; } - public static KbsPin from(@Nullable String pin) { + public static SvrPin from(@Nullable String pin) { if (pin == null) return EMPTY; pin = pin.trim(); if (pin.length() == 0) return EMPTY; - return new KbsPin(pin); + return new SvrPin(pin); } public int length() { @@ -49,15 +49,15 @@ public final class KbsPin implements Parcelable { dest.writeString(pin); } - public static final Creator CREATOR = new Creator() { + public static final Creator CREATOR = new Creator() { @Override - public KbsPin createFromParcel(Parcel in) { - return new KbsPin(in); + public SvrPin createFromParcel(Parcel in) { + return new SvrPin(in); } @Override - public KbsPin[] newArray(int size) { - return new KbsPin[size]; + public SvrPin[] newArray(int size) { + return new SvrPin[size]; } }; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/KbsSplashFragment.java b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/SvrSplashFragment.java similarity index 92% rename from app/src/main/java/org/thoughtcrime/securesms/lock/v2/KbsSplashFragment.java rename to app/src/main/java/org/thoughtcrime/securesms/lock/v2/SvrSplashFragment.java index 464ed30fac..87a3038518 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/KbsSplashFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/SvrSplashFragment.java @@ -24,7 +24,7 @@ import org.thoughtcrime.securesms.pin.PinOptOutDialog; import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.navigation.SafeNavigation; -public final class KbsSplashFragment extends Fragment { +public final class SvrSplashFragment extends Fragment { private TextView title; private TextView description; @@ -55,7 +55,7 @@ public final class KbsSplashFragment extends Fragment { primaryAction.setOnClickListener(v -> onCreatePin()); secondaryAction.setOnClickListener(v -> onLearnMore()); - if (RegistrationLockUtil.userHasRegistrationLock(requireContext())) { + if (SignalStore.svr().isRegistrationLockEnabled()) { setUpRegLockEnabled(); } else { setUpRegLockDisabled(); @@ -80,7 +80,7 @@ public final class KbsSplashFragment extends Fragment { @Override public void onPrepareOptionsMenu(@NonNull Menu menu) { - if (RegistrationLockUtil.userHasRegistrationLock(requireContext())) { + if (SignalStore.svr().isRegistrationLockEnabled()) { menu.clear(); } } @@ -113,9 +113,9 @@ public final class KbsSplashFragment extends Fragment { } private void onCreatePin() { - KbsSplashFragmentDirections.ActionCreateKbsPin action = KbsSplashFragmentDirections.actionCreateKbsPin(); + SvrSplashFragmentDirections.ActionCreateKbsPin action = SvrSplashFragmentDirections.actionCreateKbsPin(); - action.setIsPinChange(SignalStore.kbsValues().hasPin()); + action.setIsPinChange(SignalStore.svr().hasPin()); SafeNavigation.safeNavigate(Navigation.findNavController(requireView()), action); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionPin.java b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionPin.java index e648a592a7..a45da288b5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionPin.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionPin.java @@ -5,8 +5,6 @@ import android.content.Context; import androidx.annotation.NonNull; import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.pin.PinState; -import org.thoughtcrime.securesms.util.TextSecurePreferences; public class LogSectionPin implements LogSection { @@ -17,14 +15,12 @@ public class LogSectionPin implements LogSection { @Override public @NonNull CharSequence getContent(@NonNull Context context) { - return new StringBuilder().append("State: ").append(PinState.getState()).append("\n") - .append("Last Successful Reminder Entry: ").append(SignalStore.pinValues().getLastSuccessfulEntryTime()).append("\n") + return new StringBuilder().append("Last Successful Reminder Entry: ").append(SignalStore.pinValues().getLastSuccessfulEntryTime()).append("\n") .append("Next Reminder Interval: ").append(SignalStore.pinValues().getCurrentInterval()).append("\n") - .append("ReglockV1: ").append(TextSecurePreferences.isV1RegistrationLockEnabled(context)).append("\n") - .append("ReglockV2: ").append(SignalStore.kbsValues().isV2RegistrationLockEnabled()).append("\n") - .append("Signal PIN: ").append(SignalStore.kbsValues().hasPin()).append("\n") - .append("Opted Out: ").append(SignalStore.kbsValues().hasOptedOut()).append("\n") - .append("Last Creation Failed: ").append(SignalStore.kbsValues().lastPinCreateFailed()).append("\n") + .append("Reglock: ").append(SignalStore.svr().isRegistrationLockEnabled()).append("\n") + .append("Signal PIN: ").append(SignalStore.svr().hasPin()).append("\n") + .append("Opted Out: ").append(SignalStore.svr().hasOptedOut()).append("\n") + .append("Last Creation Failed: ").append(SignalStore.svr().lastPinCreateFailed()).append("\n") .append("Needs Account Restore: ").append(SignalStore.storageService().needsAccountRestore()).append("\n") .append("PIN Required at Registration: ").append(SignalStore.registrationValues().pinWasRequiredAtRegistration()).append("\n") .append("Registration Complete: ").append(SignalStore.registrationValues().isRegistrationComplete()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/megaphone/Megaphones.java b/app/src/main/java/org/thoughtcrime/securesms/megaphone/Megaphones.java index f141d59392..b2ade31a82 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/megaphone/Megaphones.java +++ b/app/src/main/java/org/thoughtcrime/securesms/megaphone/Megaphones.java @@ -30,8 +30,8 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SmsExportPhase; import org.thoughtcrime.securesms.lock.SignalPinReminderDialog; import org.thoughtcrime.securesms.lock.SignalPinReminders; -import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity; -import org.thoughtcrime.securesms.lock.v2.KbsMigrationActivity; +import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity; +import org.thoughtcrime.securesms.lock.v2.SvrMigrationActivity; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.profiles.AvatarHelper; @@ -164,7 +164,7 @@ public final class Megaphones { .enableSnooze(null) .setOnVisibleListener((megaphone, listener) -> { if (new NetworkConstraint.Factory(ApplicationDependencies.getApplication()).create().isMet()) { - listener.onMegaphoneNavigationRequested(KbsMigrationActivity.createIntent(), KbsMigrationActivity.REQUEST_NEW_PIN); + listener.onMegaphoneNavigationRequested(SvrMigrationActivity.createIntent(), SvrMigrationActivity.REQUEST_NEW_PIN); } }) .build(); @@ -174,9 +174,9 @@ public final class Megaphones { .setTitle(R.string.KbsMegaphone__create_a_pin) .setBody(R.string.KbsMegaphone__pins_keep_information_thats_stored_with_signal_encrytped) .setActionButton(R.string.KbsMegaphone__create_pin, (megaphone, listener) -> { - Intent intent = CreateKbsPinActivity.getIntentForPinCreate(ApplicationDependencies.getApplication()); + Intent intent = CreateSvrPinActivity.getIntentForPinCreate(ApplicationDependencies.getApplication()); - listener.onMegaphoneNavigationRequested(intent, CreateKbsPinActivity.REQUEST_NEW_PIN); + listener.onMegaphoneNavigationRequested(intent, CreateSvrPinActivity.REQUEST_NEW_PIN); }) .build(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/megaphone/PinsForAllSchedule.java b/app/src/main/java/org/thoughtcrime/securesms/megaphone/PinsForAllSchedule.java index beecae8bbe..b8b75e480a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/megaphone/PinsForAllSchedule.java +++ b/app/src/main/java/org/thoughtcrime/securesms/megaphone/PinsForAllSchedule.java @@ -43,11 +43,11 @@ class PinsForAllSchedule implements MegaphoneSchedule { } private static boolean isEnabled() { - if (SignalStore.kbsValues().hasOptedOut()) { + if (SignalStore.svr().hasOptedOut()) { return false; } - if (SignalStore.kbsValues().hasPin()) { + if (SignalStore.svr().hasPin()) { return false; } @@ -55,10 +55,6 @@ class PinsForAllSchedule implements MegaphoneSchedule { return true; } - if (newlyRegisteredRegistrationLockV1User()) { - return true; - } - if (SignalStore.registrationValues().pinWasRequiredAtRegistration()) { return false; } @@ -68,11 +64,6 @@ class PinsForAllSchedule implements MegaphoneSchedule { private static boolean pinCreationFailedDuringRegistration() { return SignalStore.registrationValues().pinWasRequiredAtRegistration() && - !SignalStore.kbsValues().hasPin() && - !TextSecurePreferences.isV1RegistrationLockEnabled(ApplicationDependencies.getApplication()); - } - - private static boolean newlyRegisteredRegistrationLockV1User() { - return SignalStore.registrationValues().pinWasRequiredAtRegistration() && TextSecurePreferences.isV1RegistrationLockEnabled(ApplicationDependencies.getApplication()); + !SignalStore.svr().hasPin(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/megaphone/SignalPinReminderSchedule.java b/app/src/main/java/org/thoughtcrime/securesms/megaphone/SignalPinReminderSchedule.java index 2b603fd41a..000ee18e8b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/megaphone/SignalPinReminderSchedule.java +++ b/app/src/main/java/org/thoughtcrime/securesms/megaphone/SignalPinReminderSchedule.java @@ -6,11 +6,11 @@ final class SignalPinReminderSchedule implements MegaphoneSchedule { @Override public boolean shouldDisplay(int seenCount, long lastSeen, long firstVisible, long currentTime) { - if (SignalStore.kbsValues().hasOptedOut()) { + if (SignalStore.svr().hasOptedOut()) { return false; } - if (!SignalStore.kbsValues().hasPin()) { + if (!SignalStore.svr().hasPin()) { return false; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java index 096f8c40be..49ac9d6d41 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java @@ -96,7 +96,7 @@ public class ApplicationMigrations { static final int JUMBOMOJI_DOWNLOAD = 52; static final int FIX_EMOJI_QUALITY = 53; static final int CHANGE_NUMBER_CAPABILITY_4 = 54; - static final int KBS_MIGRATION = 55; + //static final int KBS_MIGRATION = 55; static final int PNI_IDENTITY = 56; static final int PNI_IDENTITY_2 = 57; static final int PNI_IDENTITY_3 = 58; @@ -106,7 +106,7 @@ public class ApplicationMigrations { static final int REFRESH_EXPIRING_CREDENTIAL = 62; static final int EMOJI_SEARCH_INDEX_10 = 63; static final int REFRESH_PNI_REGISTRATION_ID = 64; - static final int KBS_MIGRATION_2 = 65; + //static final int KBS_MIGRATION_2 = 65; static final int PNI_2 = 66; static final int SYSTEM_NAME_SYNC = 67; static final int STORY_VIEWED_STATE = 68; @@ -132,9 +132,10 @@ public class ApplicationMigrations { static final int DEDUPE_DB_MIGRATION = 88; static final int DEDUPE_DB_MIGRATION_2 = 89; static final int EMOJI_VERSION_8 = 90; + static final int SVR2_MIRROR = 91; } - public static final int CURRENT_VERSION = 90; + public static final int CURRENT_VERSION = 91; /** * This *must* be called after the {@link JobManager} has been instantiated, but *before* the call @@ -452,9 +453,9 @@ public class ApplicationMigrations { jobs.put(Version.CHANGE_NUMBER_CAPABILITY_4,new AttributesMigrationJob()); } - if (lastSeenVersion < Version.KBS_MIGRATION) { - jobs.put(Version.KBS_MIGRATION, new KbsEnclaveMigrationJob()); - } + // if (lastSeenVersion < Version.KBS_MIGRATION) { + // jobs.put(Version.KBS_MIGRATION, new KbsEnclaveMigrationJob()); + // } if (lastSeenVersion < Version.PNI_IDENTITY) { jobs.put(Version.PNI_IDENTITY, new PniAccountInitializationMigrationJob()); @@ -492,9 +493,9 @@ public class ApplicationMigrations { jobs.put(Version.REFRESH_PNI_REGISTRATION_ID, new AttributesMigrationJob()); } - if (lastSeenVersion < Version.KBS_MIGRATION_2) { - jobs.put(Version.KBS_MIGRATION_2, new KbsEnclaveMigrationJob()); - } + // if (lastSeenVersion < Version.KBS_MIGRATION_2) { + // jobs.put(Version.KBS_MIGRATION_2, new KbsEnclaveMigrationJob()); + // } if (lastSeenVersion < Version.PNI_2) { jobs.put(Version.PNI_2, new PniMigrationJob()); @@ -596,6 +597,10 @@ public class ApplicationMigrations { jobs.put(Version.EMOJI_VERSION_8, new EmojiDownloadMigrationJob()); } + if (lastSeenVersion < Version.SVR2_MIRROR) { + jobs.put(Version.SVR2_MIRROR, new Svr2MirrorMigrationJob()); + } + return jobs; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/KbsEnclaveMigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/KbsEnclaveMigrationJob.java deleted file mode 100644 index 39999c7256..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/KbsEnclaveMigrationJob.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.thoughtcrime.securesms.migrations; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.jobs.KbsEnclaveMigrationWorkerJob; - -/** - * A job to be run whenever we add a new KBS enclave. In order to prevent this moderately-expensive - * task from blocking the network for too long, this task simply enqueues another non-migration job, - * {@link KbsEnclaveMigrationWorkerJob}, to do the heavy lifting. - */ -public class KbsEnclaveMigrationJob extends MigrationJob { - - public static final String KEY = "KbsEnclaveMigrationJob"; - - KbsEnclaveMigrationJob() { - this(new Parameters.Builder().build()); - } - - private KbsEnclaveMigrationJob(@NonNull Parameters parameters) { - super(parameters); - } - - @Override - public boolean isUiBlocking() { - return false; - } - - @Override - public @NonNull String getFactoryKey() { - return KEY; - } - - @Override - public void performMigration() { - ApplicationDependencies.getJobManager().add(new KbsEnclaveMigrationWorkerJob()); - } - - @Override - boolean shouldRetry(@NonNull Exception e) { - return false; - } - - public static class Factory implements Job.Factory { - @Override - public @NonNull KbsEnclaveMigrationJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) { - return new KbsEnclaveMigrationJob(parameters); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/PinOptOutMigration.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/PinOptOutMigration.java index 90702fcfc0..c4d0273ae9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/PinOptOutMigration.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/PinOptOutMigration.java @@ -37,15 +37,15 @@ public final class PinOptOutMigration extends MigrationJob { @Override void performMigration() { - if (SignalStore.kbsValues().hasOptedOut() && SignalStore.kbsValues().hasPin()) { + if (SignalStore.svr().hasOptedOut() && SignalStore.svr().hasPin()) { Log.w(TAG, "Discovered a legacy opt-out user! Resetting the state."); - SignalStore.kbsValues().optOut(); + SignalStore.svr().optOut(); ApplicationDependencies.getJobManager().startChain(new RefreshAttributesJob()) .then(new RefreshOwnProfileJob()) .then(new StorageForcePushJob()) .enqueue(); - } else if (SignalStore.kbsValues().hasOptedOut()) { + } else if (SignalStore.svr().hasOptedOut()) { Log.i(TAG, "Discovered an opt-out user, but they're already in a good state. No action required."); } else { Log.i(TAG, "Discovered a normal PIN user. No action required."); diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/RegistrationPinV2MigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/RegistrationPinV2MigrationJob.java deleted file mode 100644 index 6c248a7ba3..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/RegistrationPinV2MigrationJob.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.thoughtcrime.securesms.migrations; - -import android.text.TextUtils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.signal.core.util.logging.Log; -import org.signal.libsignal.protocol.InvalidKeyException; -import org.thoughtcrime.securesms.jobmanager.JsonJobData; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; -import org.thoughtcrime.securesms.jobs.BaseJob; -import org.thoughtcrime.securesms.pin.PinState; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; - -import java.io.IOException; - -/** - * Migrates an existing V1 registration lock user to a V2 registration lock that is backed by a - * Signal PIN. - * - * Deliberately not a {@link MigrationJob} because it is not something that needs to run at app start. - * This migration can run at anytime. - */ -public final class RegistrationPinV2MigrationJob extends BaseJob { - - private static final String TAG = Log.tag(RegistrationPinV2MigrationJob.class); - - public static final String KEY = "RegistrationPinV2MigrationJob"; - - public RegistrationPinV2MigrationJob() { - this(new Parameters.Builder() - .setQueue(KEY) - .setMaxInstancesForFactory(1) - .addConstraint(NetworkConstraint.KEY) - .setLifespan(Job.Parameters.IMMORTAL) - .setMaxAttempts(Job.Parameters.UNLIMITED) - .build()); - } - - private RegistrationPinV2MigrationJob(@NonNull Parameters parameters) { - super(parameters); - } - - @Override - public @Nullable byte[] serialize() { - return null; - } - - @Override - protected void onRun() throws IOException, UnauthenticatedResponseException, InvalidKeyException { - if (!TextSecurePreferences.isV1RegistrationLockEnabled(context)) { - Log.i(TAG, "Registration lock disabled"); - return; - } - - //noinspection deprecation Only acceptable place to read the old pin. - String pinValue = TextSecurePreferences.getDeprecatedV1RegistrationLockPin(context); - - if (pinValue == null | TextUtils.isEmpty(pinValue)) { - Log.i(TAG, "No old pin to migrate"); - return; - } - - Log.i(TAG, "Migrating pin to Key Backup Service"); - PinState.onMigrateToRegistrationLockV2(context, pinValue); - Log.i(TAG, "Pin migrated to Key Backup Service"); - } - - @Override - protected boolean onShouldRetry(@NonNull Exception e) { - return e instanceof IOException; - } - - @Override - public @NonNull String getFactoryKey() { - return KEY; - } - - @Override - public void onFailure() { - } - - public static class Factory implements Job.Factory { - @Override - public @NonNull RegistrationPinV2MigrationJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) { - return new RegistrationPinV2MigrationJob(parameters); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/Svr2MirrorMigrationJob.kt b/app/src/main/java/org/thoughtcrime/securesms/migrations/Svr2MirrorMigrationJob.kt new file mode 100644 index 0000000000..37d5a807ab --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/Svr2MirrorMigrationJob.kt @@ -0,0 +1,35 @@ +package org.thoughtcrime.securesms.migrations + +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.jobs.Svr2MirrorJob + +/** + * Mirrors the user's SVR1 data to SVR2. + */ +internal class Svr2MirrorMigrationJob( + parameters: Parameters = Parameters.Builder().build() +) : MigrationJob(parameters) { + + companion object { + val TAG = Log.tag(Svr2MirrorMigrationJob::class.java) + const val KEY = "Svr2MirrorMigrationJob" + } + + override fun getFactoryKey(): String = KEY + + override fun isUiBlocking(): Boolean = false + + override fun performMigration() { + ApplicationDependencies.getJobManager().add(Svr2MirrorJob()) + } + + override fun shouldRetry(e: Exception): Boolean = false + + class Factory : Job.Factory { + override fun create(parameters: Parameters, serializedData: ByteArray?): Svr2MirrorMigrationJob { + return Svr2MirrorMigrationJob(parameters) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentsHomeFragment.java b/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentsHomeFragment.java index a6059de917..ce8d55966d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentsHomeFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentsHomeFragment.java @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.payments.preferences; -import android.app.AlertDialog; import android.os.Bundle; import android.view.MenuItem; import android.view.View; @@ -31,7 +30,7 @@ import org.thoughtcrime.securesms.components.reminder.ReminderView; import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity; import org.thoughtcrime.securesms.help.HelpFragment; import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity; +import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity; import org.thoughtcrime.securesms.payments.FiatMoneyUtil; import org.thoughtcrime.securesms.payments.MoneyView; import org.thoughtcrime.securesms.payments.backup.RecoveryPhraseStates; @@ -369,7 +368,7 @@ public class PaymentsHomeFragment extends LoggingFragment { @Override public void onUpdatePin() { - startActivityForResult(CreateKbsPinActivity.getIntentForPinChangeFromSettings(requireContext()), CreateKbsPinActivity.REQUEST_NEW_PIN); + startActivityForResult(CreateSvrPinActivity.getIntentForPinChangeFromSettings(requireContext()), CreateSvrPinActivity.REQUEST_NEW_PIN); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/KbsRepository.java b/app/src/main/java/org/thoughtcrime/securesms/pin/KbsRepository.java deleted file mode 100644 index df12cdf404..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/KbsRepository.java +++ /dev/null @@ -1,183 +0,0 @@ -package org.thoughtcrime.securesms.pin; - -import android.app.backup.BackupManager; - -import androidx.annotation.NonNull; -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.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.PinHashUtil; -import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; -import org.whispersystems.signalservice.internal.ServiceResponse; -import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; -import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; - -import java.io.IOException; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Consumer; - -import io.reactivex.rxjava3.core.Single; -import io.reactivex.rxjava3.schedulers.Schedulers; - -/** - * Using provided or already stored authorization, provides various get token data from KBS - * and generate {@link KbsPinData}. - */ -public class KbsRepository { - - private static final String TAG = Log.tag(KbsRepository.class); - - public void getToken(@NonNull Consumer> callback) { - SignalExecutors.UNBOUNDED.execute(() -> { - try { - callback.accept(Optional.ofNullable(getTokenSync(null))); - } catch (IOException e) { - callback.accept(Optional.empty()); - } - }); - } - - /** - * @param authorization If this is being called before the user is registered (i.e. as part of - * reglock), you must pass in an authorization token that can be used to - * retrieve a backup. Otherwise, pass in null and we'll fetch one. - */ - public Single> getToken(@Nullable String authorization) { - return Single.>fromCallable(() -> { - try { - return ServiceResponse.forResult(getTokenSync(authorization), 200, null); - } catch (IOException e) { - return ServiceResponse.forUnknownError(e); - } - }).subscribeOn(Schedulers.io()); - } - - /** - * Fetch and store a new KBS authorization. - */ - public void refreshAuthorization() throws IOException { - for (KbsEnclave enclave : KbsEnclaves.all()) { - KeyBackupService kbs = ApplicationDependencies.getKeyBackupService(enclave); - - try { - String authorization = kbs.getAuthorization(); - backupAuthToken(authorization); - } catch (NonSuccessfulResponseCodeException e) { - if (e.getCode() == 404) { - Log.i(TAG, "Enclave decommissioned, skipping", e); - } else { - throw e; - } - } - } - } - - private @NonNull TokenData getTokenSync(@Nullable String authorization) throws IOException { - TokenData firstKnownTokenData = null; - - for (KbsEnclave enclave : KbsEnclaves.all()) { - KeyBackupService kbs = ApplicationDependencies.getKeyBackupService(enclave); - TokenResponse token; - - try { - authorization = authorization == null ? kbs.getAuthorization() : authorization; - backupAuthToken(authorization); - token = kbs.getToken(authorization); - } catch (NonSuccessfulResponseCodeException e) { - if (e.getCode() == 404) { - Log.i(TAG, "Enclave decommissioned, skipping", e); - continue; - } else { - throw e; - } - } - - TokenData tokenData = new TokenData(enclave, authorization, token); - - if (tokenData.getTriesRemaining() > 0) { - Log.i(TAG, "Found data! " + enclave.getEnclaveName()); - return tokenData; - } else if (firstKnownTokenData == null) { - Log.i(TAG, "No data, but storing as the first response. " + enclave.getEnclaveName()); - firstKnownTokenData = tokenData; - } else { - Log.i(TAG, "No data, and we already have a 'first response'. " + enclave.getEnclaveName()); - } - } - - return Objects.requireNonNull(firstKnownTokenData); - } - - private static void backupAuthToken(String token) { - final boolean tokenIsNew = SignalStore.kbsValues().appendAuthTokenToList(token); - if (tokenIsNew && SignalStore.kbsValues().hasPin()) { - new BackupManager(ApplicationDependencies.getApplication()).dataChanged(); - } - } - - /** - * Invoked during registration to restore the master key based on the server response during - * verification. - * - * Does not affect {@link PinState}. - */ - public static synchronized @Nullable KbsPinData restoreMasterKey(@Nullable String pin, - @NonNull KbsEnclave enclave, - @Nullable String basicStorageCredentials, - @NonNull TokenResponse tokenResponse) - throws IOException, KeyBackupSystemWrongPinException, KeyBackupSystemNoDataException - { - Log.i(TAG, "restoreMasterKey()"); - - if (pin == null) return null; - - if (basicStorageCredentials == null) { - throw new AssertionError("Cannot restore KBS key, no storage credentials supplied. Enclave: " + enclave.getEnclaveName()); - } - - Log.i(TAG, "Preparing to restore from " + enclave.getEnclaveName()); - return restoreMasterKeyFromEnclave(enclave, pin, basicStorageCredentials, tokenResponse); - } - - private static @NonNull KbsPinData restoreMasterKeyFromEnclave(@NonNull KbsEnclave enclave, - @NonNull String pin, - @NonNull String basicStorageCredentials, - @NonNull TokenResponse tokenResponse) - throws IOException, KeyBackupSystemWrongPinException, KeyBackupSystemNoDataException - { - KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(enclave); - KeyBackupService.RestoreSession session = keyBackupService.newRegistrationSession(basicStorageCredentials, tokenResponse); - - try { - Log.i(TAG, "Restoring pin from KBS"); - - PinHash hashedPin = PinHashUtil.hashPin(pin, session.hashSalt()); - KbsPinData kbsData = session.restorePin(hashedPin); - - if (kbsData != null) { - Log.i(TAG, "Found registration lock token on KBS."); - } else { - throw new AssertionError("Null not expected"); - } - - return kbsData; - } catch (UnauthenticatedResponseException | InvalidKeyException e) { - Log.w(TAG, "Failed to restore key", e); - throw new IOException(e); - } catch (KeyBackupServicePinException e) { - Log.w(TAG, "Incorrect pin", e); - throw new KeyBackupSystemWrongPinException(e.getToken()); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/KeyBackupSystemWrongPinException.java b/app/src/main/java/org/thoughtcrime/securesms/pin/KeyBackupSystemWrongPinException.java deleted file mode 100644 index ac8365be08..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/KeyBackupSystemWrongPinException.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.thoughtcrime.securesms.pin; - -import androidx.annotation.NonNull; - -import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; - -public final class KeyBackupSystemWrongPinException extends Exception { - - private final TokenResponse tokenResponse; - - public KeyBackupSystemWrongPinException(@NonNull TokenResponse tokenResponse){ - this.tokenResponse = tokenResponse; - } - - public @NonNull TokenResponse getTokenResponse() { - return tokenResponse; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/PinOptOutDialog.java b/app/src/main/java/org/thoughtcrime/securesms/pin/PinOptOutDialog.java index 94c7398b98..1f91e41380 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/PinOptOutDialog.java +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/PinOptOutDialog.java @@ -29,7 +29,7 @@ public final class PinOptOutDialog { AlertDialog progress = SimpleProgressDialog.show(context); SimpleTask.run(() -> { - PinState.onPinOptOut(); + SvrRepository.optOutOfPin(); return null; }, success -> { Log.i(TAG, "Disable operation finished."); diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreActivity.java b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreActivity.java index 15e0122d60..a4967e831c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreActivity.java @@ -9,7 +9,7 @@ import androidx.appcompat.app.AppCompatActivity; import org.thoughtcrime.securesms.MainActivity; import org.thoughtcrime.securesms.PassphraseRequiredActivity; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity; +import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; @@ -32,7 +32,7 @@ public final class PinRestoreActivity extends AppCompatActivity { void navigateToPinCreation() { final Intent main = MainActivity.clearTop(this); - final Intent createPin = CreateKbsPinActivity.getIntentForPinCreate(this); + final Intent createPin = CreateSvrPinActivity.getIntentForPinCreate(this); final Intent chained = PassphraseRequiredActivity.chainIntent(createPin, main); startActivity(chained); diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreEntryFragment.java b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreEntryFragment.java index d07db50608..42723f9cea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreEntryFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreEntryFragment.java @@ -29,7 +29,7 @@ import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobs.ProfileUploadJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.lock.v2.KbsConstants; +import org.thoughtcrime.securesms.lock.v2.SvrConstants; import org.thoughtcrime.securesms.lock.v2.PinKeyboardType; import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity; @@ -113,7 +113,7 @@ public class PinRestoreEntryFragment extends LoggingFragment { private void initViewModel() { viewModel = new ViewModelProvider(this).get(PinRestoreViewModel.class); - viewModel.getTriesRemaining().observe(getViewLifecycleOwner(), this::presentTriesRemaining); + viewModel.triesRemaining.observe(getViewLifecycleOwner(), this::presentTriesRemaining); viewModel.getEvent().observe(getViewLifecycleOwner(), this::presentEvent); } @@ -194,9 +194,9 @@ public class PinRestoreEntryFragment extends LoggingFragment { private void onNeedHelpClicked() { new MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.PinRestoreEntryFragment_need_help) - .setMessage(getString(R.string.PinRestoreEntryFragment_your_pin_is_a_d_digit_code, KbsConstants.MINIMUM_PIN_LENGTH)) + .setMessage(getString(R.string.PinRestoreEntryFragment_your_pin_is_a_d_digit_code, SvrConstants.MINIMUM_PIN_LENGTH)) .setPositiveButton(R.string.PinRestoreEntryFragment_create_new_pin, ((dialog, which) -> { - PinState.onPinRestoreForgottenOrSkipped(); + SvrRepository.onPinRestoreForgottenOrSkipped(); ((PinRestoreActivity) requireActivity()).navigateToPinCreation(); })) .setNeutralButton(R.string.PinRestoreEntryFragment_contact_support, (dialog, which) -> { @@ -218,7 +218,7 @@ public class PinRestoreEntryFragment extends LoggingFragment { .setTitle(R.string.PinRestoreEntryFragment_skip_pin_entry) .setMessage(R.string.PinRestoreEntryFragment_if_you_cant_remember_your_pin) .setPositiveButton(R.string.PinRestoreEntryFragment_create_new_pin, (dialog, which) -> { - PinState.onPinRestoreForgottenOrSkipped(); + SvrRepository.onPinRestoreForgottenOrSkipped(); ((PinRestoreActivity) requireActivity()).navigateToPinCreation(); }) .setNegativeButton(R.string.PinRestoreEntryFragment_cancel, null) @@ -226,7 +226,7 @@ public class PinRestoreEntryFragment extends LoggingFragment { } private void onAccountLocked() { - PinState.onPinRestoreForgottenOrSkipped(); + SvrRepository.onPinRestoreForgottenOrSkipped(); SafeNavigation.safeNavigate(Navigation.findNavController(requireView()), PinRestoreEntryFragmentDirections.actionAccountLocked()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreLockedFragment.java b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreLockedFragment.java index 5effccaa61..fa5ba51431 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreLockedFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreLockedFragment.java @@ -25,7 +25,7 @@ public class PinRestoreLockedFragment extends LoggingFragment { View learnMoreButton = view.findViewById(R.id.pin_locked_learn_more); createPinButton.setOnClickListener(v -> { - PinState.onPinRestoreForgottenOrSkipped(); + SvrRepository.onPinRestoreForgottenOrSkipped(); ((PinRestoreActivity) requireActivity()).navigateToPinCreation(); }); diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreRepository.java b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreRepository.java deleted file mode 100644 index 82a13c9f73..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreRepository.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.thoughtcrime.securesms.pin; - -import androidx.annotation.NonNull; - -import org.signal.core.util.concurrent.SignalExecutors; -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.jobs.NewRegistrationUsernameSyncJob; -import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob; -import org.thoughtcrime.securesms.jobs.StorageSyncJob; -import org.signal.core.util.Stopwatch; -import org.whispersystems.signalservice.api.KbsPinData; -import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException; - -import java.io.IOException; -import java.util.Objects; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; - -public class PinRestoreRepository { - - private static final String TAG = Log.tag(PinRestoreRepository.class); - - private final Executor executor = SignalExecutors.UNBOUNDED; - - void submitPin(@NonNull String pin, @NonNull TokenData tokenData, @NonNull Callback callback) { - executor.execute(() -> { - try { - Stopwatch stopwatch = new Stopwatch("PinSubmission"); - - KbsPinData kbsData = KbsRepository.restoreMasterKey(pin, tokenData.getEnclave(), tokenData.getBasicAuth(), tokenData.getTokenResponse()); - PinState.onSignalPinRestore(ApplicationDependencies.getApplication(), Objects.requireNonNull(kbsData), pin); - stopwatch.split("MasterKey"); - - ApplicationDependencies.getJobManager().runSynchronously(new StorageAccountRestoreJob(), StorageAccountRestoreJob.LIFESPAN); - stopwatch.split("AccountRestore"); - - ApplicationDependencies - .getJobManager() - .startChain(new StorageSyncJob()) - .then(new NewRegistrationUsernameSyncJob()) - .enqueueAndBlockUntilCompletion(TimeUnit.SECONDS.toMillis(10)); - stopwatch.split("ContactRestore"); - - stopwatch.stop(TAG); - - callback.onComplete(new PinResultData(PinResult.SUCCESS, tokenData)); - } catch (IOException e) { - callback.onComplete(new PinResultData(PinResult.NETWORK_ERROR, tokenData)); - } catch (KeyBackupSystemNoDataException e) { - callback.onComplete(new PinResultData(PinResult.LOCKED, tokenData)); - } catch (KeyBackupSystemWrongPinException e) { - callback.onComplete(new PinResultData(PinResult.INCORRECT, TokenData.withResponse(tokenData, e.getTokenResponse()))); - } - }); - } - - interface Callback { - void onComplete(@NonNull T value); - } - - static class PinResultData { - private final PinResult result; - private final TokenData tokenData; - - PinResultData(@NonNull PinResult result, @NonNull TokenData tokenData) { - this.result = result; - this.tokenData = tokenData; - } - - public @NonNull PinResult getResult() { - return result; - } - - public @NonNull TokenData getTokenData() { - return tokenData; - } - } - - enum PinResult { - SUCCESS, INCORRECT, LOCKED, NETWORK_ERROR - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreViewModel.java deleted file mode 100644 index 7b8f6c4598..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreViewModel.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.thoughtcrime.securesms.pin; - -import androidx.annotation.NonNull; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.ViewModel; - -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.lock.v2.KbsConstants; -import org.thoughtcrime.securesms.lock.v2.PinKeyboardType; -import org.thoughtcrime.securesms.util.DefaultValueLiveData; -import org.thoughtcrime.securesms.util.SingleLiveEvent; - -public class PinRestoreViewModel extends ViewModel { - - private final PinRestoreRepository repo; - private final DefaultValueLiveData triesRemaining; - private final SingleLiveEvent event; - private final KbsRepository kbsRepository; - - private volatile TokenData tokenData; - - public PinRestoreViewModel() { - this.repo = new PinRestoreRepository(); - this.kbsRepository = new KbsRepository(); - this.triesRemaining = new DefaultValueLiveData<>(new TriesRemaining(10, false)); - this.event = new SingleLiveEvent<>(); - - kbsRepository.getToken(token -> { - if (token.isPresent()) { - updateTokenData(token.get(), false); - } else { - event.postValue(Event.NETWORK_ERROR); - } - }); - } - - void onPinSubmitted(@NonNull String pin, @NonNull PinKeyboardType pinKeyboardType) { - int trimmedLength = pin.replace(" ", "").length(); - - if (trimmedLength == 0) { - event.postValue(Event.EMPTY_PIN); - return; - } - - if (trimmedLength < KbsConstants.MINIMUM_PIN_LENGTH) { - event.postValue(Event.PIN_TOO_SHORT); - return; - } - - if (tokenData != null) { - repo.submitPin(pin, tokenData, result -> { - - switch (result.getResult()) { - case SUCCESS: - SignalStore.pinValues().setKeyboardType(pinKeyboardType); - SignalStore.storageService().setNeedsAccountRestore(false); - event.postValue(Event.SUCCESS); - break; - case LOCKED: - event.postValue(Event.PIN_LOCKED); - break; - case INCORRECT: - event.postValue(Event.PIN_INCORRECT); - updateTokenData(result.getTokenData(), true); - break; - case NETWORK_ERROR: - event.postValue(Event.NETWORK_ERROR); - break; - } - }); - } else { - kbsRepository.getToken(token -> { - if (token.isPresent()) { - updateTokenData(token.get(), false); - onPinSubmitted(pin, pinKeyboardType); - } else { - event.postValue(Event.NETWORK_ERROR); - } - }); - } - } - - @NonNull DefaultValueLiveData getTriesRemaining() { - return triesRemaining; - } - - @NonNull LiveData getEvent() { - return event; - } - - private void updateTokenData(@NonNull TokenData tokenData, boolean incorrectGuess) { - this.tokenData = tokenData; - triesRemaining.postValue(new TriesRemaining(tokenData.getTriesRemaining(), incorrectGuess)); - } - - enum Event { - SUCCESS, EMPTY_PIN, PIN_TOO_SHORT, PIN_INCORRECT, PIN_LOCKED, NETWORK_ERROR - } - - static class TriesRemaining { - private final int triesRemaining; - private final boolean hasIncorrectGuess; - - TriesRemaining(int triesRemaining, boolean hasIncorrectGuess) { - this.triesRemaining = triesRemaining; - this.hasIncorrectGuess = hasIncorrectGuess; - } - - public int getCount() { - return triesRemaining; - } - - public boolean hasIncorrectGuess() { - return hasIncorrectGuess; - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreViewModel.kt new file mode 100644 index 0000000000..08d7556bec --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreViewModel.kt @@ -0,0 +1,81 @@ +package org.thoughtcrime.securesms.pin + +import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.disposables.CompositeDisposable +import io.reactivex.rxjava3.kotlin.plusAssign +import io.reactivex.rxjava3.schedulers.Schedulers +import org.thoughtcrime.securesms.lock.v2.PinKeyboardType +import org.thoughtcrime.securesms.lock.v2.SvrConstants +import org.thoughtcrime.securesms.util.DefaultValueLiveData +import org.thoughtcrime.securesms.util.SingleLiveEvent +import org.whispersystems.signalservice.api.svr.SecureValueRecovery + +class PinRestoreViewModel : ViewModel() { + private val repo: SvrRepository = SvrRepository + + @JvmField + val triesRemaining: DefaultValueLiveData = DefaultValueLiveData(TriesRemaining(10, false)) + + private val event: SingleLiveEvent = SingleLiveEvent() + + private val disposables = CompositeDisposable() + + fun onPinSubmitted(pin: String, pinKeyboardType: PinKeyboardType) { + val trimmedLength = pin.trim().length + if (trimmedLength == 0) { + event.postValue(Event.EMPTY_PIN) + return + } + if (trimmedLength < SvrConstants.MINIMUM_PIN_LENGTH) { + event.postValue(Event.PIN_TOO_SHORT) + return + } + + disposables += Single + .fromCallable { repo.restoreMasterKeyPostRegistration(pin, pinKeyboardType) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { result -> + when (result) { + is SecureValueRecovery.RestoreResponse.Success -> { + event.postValue(Event.SUCCESS) + } + is SecureValueRecovery.RestoreResponse.PinMismatch -> { + event.postValue(Event.PIN_INCORRECT) + triesRemaining.postValue(TriesRemaining(result.triesRemaining, true)) + } + SecureValueRecovery.RestoreResponse.Missing -> { + event.postValue(Event.PIN_LOCKED) + } + is SecureValueRecovery.RestoreResponse.NetworkError -> { + event.postValue(Event.NETWORK_ERROR) + } + is SecureValueRecovery.RestoreResponse.ApplicationError -> { + event.postValue(Event.NETWORK_ERROR) + } + } + } + } + + fun getEvent(): LiveData { + return event + } + + enum class Event { + SUCCESS, + EMPTY_PIN, + PIN_TOO_SHORT, + PIN_INCORRECT, + PIN_LOCKED, + NETWORK_ERROR + } + + class TriesRemaining(val count: Int, private val hasIncorrectGuess: Boolean) { + fun hasIncorrectGuess(): Boolean { + return hasIncorrectGuess + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java b/app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java deleted file mode 100644 index 3458d4d704..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java +++ /dev/null @@ -1,435 +0,0 @@ -package org.thoughtcrime.securesms.pin; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -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; -import org.thoughtcrime.securesms.jobs.ClearFallbackKbsEnclaveJob; -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.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.MasterKey; -import org.whispersystems.signalservice.api.kbs.PinHashUtil; -import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Locale; -import java.util.Optional; -import java.util.concurrent.TimeUnit; - -public final class PinState { - - private static final String TAG = Log.tag(PinState.class); - - /** - * Invoked after a user has successfully registered. Ensures all the necessary state is updated. - */ - public static synchronized void onRegistration(@NonNull Context context, - @Nullable KbsPinData kbsData, - @Nullable String pin, - boolean hasPinToRestore, - boolean setRegistrationLockEnabled) - { - Log.i(TAG, "onRegistration()"); - - TextSecurePreferences.setV1RegistrationLockPin(context, pin); - - if (kbsData == null && pin != null) { - Log.i(TAG, "Registration Lock V1"); - SignalStore.kbsValues().clearRegistrationLockAndPin(); - TextSecurePreferences.setV1RegistrationLockEnabled(context, true); - TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis()); - TextSecurePreferences.setRegistrationLockNextReminderInterval(context, RegistrationLockReminders.INITIAL_INTERVAL); - } else if (kbsData != null && pin != null) { - if (setRegistrationLockEnabled) { - Log.i(TAG, "Registration Lock V2"); - TextSecurePreferences.setV1RegistrationLockEnabled(context, false); - SignalStore.kbsValues().setV2RegistrationLockEnabled(true); - } else { - Log.i(TAG, "ReRegistration Skip SMS"); - } - SignalStore.kbsValues().setKbsMasterKey(kbsData, pin); - SignalStore.pinValues().resetPinReminders(); - resetPinRetryCount(context, pin); - ClearFallbackKbsEnclaveJob.clearAll(); - } else if (hasPinToRestore) { - Log.i(TAG, "Has a PIN to restore."); - SignalStore.kbsValues().clearRegistrationLockAndPin(); - TextSecurePreferences.setV1RegistrationLockEnabled(context, false); - SignalStore.storageService().setNeedsAccountRestore(true); - } else { - Log.i(TAG, "No registration lock or PIN at all."); - SignalStore.kbsValues().clearRegistrationLockAndPin(); - TextSecurePreferences.setV1RegistrationLockEnabled(context, false); - } - } - - /** - * 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) { - Log.i(TAG, "onSignalPinRestore()"); - - SignalStore.kbsValues().setKbsMasterKey(kbsData, pin); - SignalStore.kbsValues().setV2RegistrationLockEnabled(false); - SignalStore.pinValues().resetPinReminders(); - SignalStore.kbsValues().setPinForgottenOrSkipped(false); - SignalStore.storageService().setNeedsAccountRestore(false); - resetPinRetryCount(context, pin); - ClearFallbackKbsEnclaveJob.clearAll(); - } - - /** - * Invoked when the user skips out on PIN restoration or otherwise fails to remember their PIN. - */ - public static synchronized void onPinRestoreForgottenOrSkipped() { - SignalStore.kbsValues().clearRegistrationLockAndPin(); - SignalStore.storageService().setNeedsAccountRestore(false); - SignalStore.kbsValues().setPinForgottenOrSkipped(true); - } - - /** - * Invoked whenever the Signal PIN is changed or created. - */ - @WorkerThread - public static synchronized void onPinChangedOrCreated(@NonNull Context context, @NonNull String pin, @NonNull PinKeyboardType keyboard) - throws IOException, UnauthenticatedResponseException, InvalidKeyException - { - Log.i(TAG, "onPinChangedOrCreated()"); - - KbsEnclave kbsEnclave = KbsEnclaves.current(); - KbsValues kbsValues = SignalStore.kbsValues(); - boolean isFirstPin = !kbsValues.hasPin() || kbsValues.hasOptedOut(); - MasterKey masterKey = kbsValues.getOrCreateMasterKey(); - KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(kbsEnclave); - KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession(); - PinHash pinHash = PinHashUtil.hashPin(pin, pinChangeSession.hashSalt()); - KbsPinData kbsData = pinChangeSession.setPin(pinHash, masterKey); - - kbsValues.setKbsMasterKey(kbsData, pin); - kbsValues.setPinForgottenOrSkipped(false); - TextSecurePreferences.clearRegistrationLockV1(context); - SignalStore.pinValues().setKeyboardType(keyboard); - SignalStore.pinValues().resetPinReminders(); - ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.PINS_FOR_ALL); - - if (isFirstPin) { - Log.i(TAG, "First time setting a PIN. Refreshing attributes to set the 'storage' capability. Enclave: " + kbsEnclave.getEnclaveName()); - bestEffortRefreshAttributes(); - } else { - Log.i(TAG, "Not the first time setting a PIN. Enclave: " + kbsEnclave.getEnclaveName()); - ApplicationDependencies.getJobManager().add(new RefreshAttributesJob()); - } - } - - /** - * Invoked when PIN creation fails. - */ - public static synchronized void onPinCreateFailure() { - Log.i(TAG, "onPinCreateFailure()"); - if (getState() == State.NO_REGISTRATION_LOCK) { - SignalStore.kbsValues().onPinCreateFailure(); - } - } - - /** - * Invoked when the user has enabled the "PIN opt out" setting. - */ - @WorkerThread - public static synchronized void onPinOptOut() { - Log.i(TAG, "onPinOptOutEnabled()"); - assertState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED, State.NO_REGISTRATION_LOCK); - - optOutOfPin(); - } - - /** - * Invoked whenever a Signal PIN user enables registration lock. - */ - @WorkerThread - public static synchronized void onEnableRegistrationLockForUserWithPin() throws IOException { - Log.i(TAG, "onEnableRegistrationLockForUserWithPin()"); - - if (getState() == State.PIN_WITH_REGISTRATION_LOCK_ENABLED) { - Log.i(TAG, "Registration lock already enabled. Skipping."); - return; - } - - assertState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED); - - - KbsEnclave kbsEnclave = KbsEnclaves.current(); - Log.i(TAG, "Enclave: " + kbsEnclave.getEnclaveName()); - - SignalStore.kbsValues().setV2RegistrationLockEnabled(false); - ApplicationDependencies.getKeyBackupService(kbsEnclave) - .newPinChangeSession(SignalStore.kbsValues().getRegistrationLockTokenResponse()) - .enableRegistrationLock(SignalStore.kbsValues().getOrCreateMasterKey()); - SignalStore.kbsValues().setV2RegistrationLockEnabled(true); - } - - /** - * Invoked whenever a Signal PIN user disables registration lock. - */ - @WorkerThread - public static synchronized void onDisableRegistrationLockForUserWithPin() throws IOException { - Log.i(TAG, "onDisableRegistrationLockForUserWithPin()"); - - if (getState() == State.PIN_WITH_REGISTRATION_LOCK_DISABLED) { - Log.i(TAG, "Registration lock already disabled. Skipping."); - return; - } - - assertState(State.PIN_WITH_REGISTRATION_LOCK_ENABLED); - - SignalStore.kbsValues().setV2RegistrationLockEnabled(true); - ApplicationDependencies.getKeyBackupService(KbsEnclaves.current()) - .newPinChangeSession(SignalStore.kbsValues().getRegistrationLockTokenResponse()) - .disableRegistrationLock(); - SignalStore.kbsValues().setV2RegistrationLockEnabled(false); - } - - /** - * Should only be called by {@link org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob}. - */ - @WorkerThread - public static synchronized void onMigrateToRegistrationLockV2(@NonNull Context context, @NonNull String pin) - throws IOException, UnauthenticatedResponseException, InvalidKeyException - { - Log.i(TAG, "onMigrateToRegistrationLockV2()"); - - KbsEnclave kbsEnclave = KbsEnclaves.current(); - Log.i(TAG, "Enclave: " + kbsEnclave.getEnclaveName()); - - KbsValues kbsValues = SignalStore.kbsValues(); - MasterKey masterKey = kbsValues.getOrCreateMasterKey(); - KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(kbsEnclave); - KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession(); - PinHash pinHash = PinHashUtil.hashPin(pin, pinChangeSession.hashSalt()); - KbsPinData kbsData = pinChangeSession.setPin(pinHash, masterKey); - - pinChangeSession.enableRegistrationLock(masterKey); - - kbsValues.setKbsMasterKey(kbsData, pin); - TextSecurePreferences.clearRegistrationLockV1(context); - } - - /** - * Should only be called by {@link org.thoughtcrime.securesms.jobs.KbsEnclaveMigrationWorkerJob}. - */ - @WorkerThread - public static synchronized void onMigrateToNewEnclave(@NonNull String pin) - throws IOException, UnauthenticatedResponseException - { - Log.i(TAG, "onMigrateToNewEnclave()"); - assertState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED, State.PIN_WITH_REGISTRATION_LOCK_ENABLED); - - Log.i(TAG, "Migrating to enclave " + KbsEnclaves.current().getEnclaveName()); - setPinOnEnclave(KbsEnclaves.current(), pin, SignalStore.kbsValues().getOrCreateMasterKey()); - - ClearFallbackKbsEnclaveJob.clearAll(); - } - - @WorkerThread - private static void bestEffortRefreshAttributes() { - Optional result = ApplicationDependencies.getJobManager().runSynchronously(new RefreshAttributesJob(), TimeUnit.SECONDS.toMillis(10)); - - if (result.isPresent() && result.get() == JobTracker.JobState.SUCCESS) { - Log.i(TAG, "Attributes were refreshed successfully."); - } else if (result.isPresent()) { - Log.w(TAG, "Attribute refresh finished, but was not successful. Enqueuing one for later. (Result: " + result.get() + ")"); - ApplicationDependencies.getJobManager().add(new RefreshAttributesJob()); - } else { - Log.w(TAG, "Job did not finish in the allotted time. It'll finish later."); - } - } - - @WorkerThread - private static void bestEffortForcePushStorage() { - Optional result = ApplicationDependencies.getJobManager().runSynchronously(new StorageForcePushJob(), TimeUnit.SECONDS.toMillis(10)); - - if (result.isPresent() && result.get() == JobTracker.JobState.SUCCESS) { - Log.i(TAG, "Storage was force-pushed successfully."); - } else if (result.isPresent()) { - Log.w(TAG, "Storage force-pushed finished, but was not successful. Enqueuing one for later. (Result: " + result.get() + ")"); - ApplicationDependencies.getJobManager().add(new RefreshAttributesJob()); - } else { - Log.w(TAG, "Storage fore push did not finish in the allotted time. It'll finish later."); - } - } - - @WorkerThread - private static void resetPinRetryCount(@NonNull Context context, @Nullable String pin) { - if (pin == null) { - return; - } - - try { - setPinOnEnclave(KbsEnclaves.current(), pin, SignalStore.kbsValues().getOrCreateMasterKey()); - TextSecurePreferences.clearRegistrationLockV1(context); - Log.i(TAG, "Pin set/attempts reset on KBS"); - } 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); - } - } - - @WorkerThread - private static @NonNull KbsPinData setPinOnEnclave(@NonNull KbsEnclave enclave, @NonNull String pin, @NonNull MasterKey masterKey) - throws IOException, UnauthenticatedResponseException - { - Log.i(TAG, "Setting PIN on enclave: " + enclave.getEnclaveName()); - - KeyBackupService kbs = ApplicationDependencies.getKeyBackupService(enclave); - KeyBackupService.PinChangeSession pinChangeSession = kbs.newPinChangeSession(); - PinHash pinHash = PinHashUtil.hashPin(pin, pinChangeSession.hashSalt()); - KbsPinData newData = pinChangeSession.setPin(pinHash, masterKey); - - SignalStore.kbsValues().setKbsMasterKey(newData, pin); - - return newData; - } - - @WorkerThread - private static void optOutOfPin() { - SignalStore.kbsValues().optOut(); - - ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.PINS_FOR_ALL); - - bestEffortRefreshAttributes(); - bestEffortForcePushStorage(); - } - - private static @NonNull State assertState(State... allowed) { - State currentState = getState(); - - for (State state : allowed) { - if (currentState == state) { - return currentState; - } - } - - switch (currentState) { - case NO_REGISTRATION_LOCK: throw new InvalidState_NoRegistrationLock(); - case REGISTRATION_LOCK_V1: throw new InvalidState_RegistrationLockV1(); - case PIN_WITH_REGISTRATION_LOCK_ENABLED: throw new InvalidState_PinWithRegistrationLockEnabled(); - case PIN_WITH_REGISTRATION_LOCK_DISABLED: throw new InvalidState_PinWithRegistrationLockDisabled(); - case PIN_OPT_OUT: throw new InvalidState_PinOptOut(); - default: throw new IllegalStateException("Expected: " + Arrays.toString(allowed) + ", Actual: " + currentState); - } - } - - public static @NonNull State getState() { - Context context = ApplicationDependencies.getApplication(); - KbsValues kbsValues = SignalStore.kbsValues(); - - boolean v1Enabled = TextSecurePreferences.isV1RegistrationLockEnabled(context); - boolean v2Enabled = kbsValues.isV2RegistrationLockEnabled(); - boolean hasPin = kbsValues.hasPin(); - boolean optedOut = kbsValues.hasOptedOut(); - - if (optedOut && !v2Enabled && !v1Enabled) { - return State.PIN_OPT_OUT; - } - - if (!v1Enabled && !v2Enabled && !hasPin) { - return State.NO_REGISTRATION_LOCK; - } - - if (v1Enabled && !v2Enabled && !hasPin) { - return State.REGISTRATION_LOCK_V1; - } - - if (v2Enabled && hasPin) { - TextSecurePreferences.setV1RegistrationLockEnabled(context, false); - return State.PIN_WITH_REGISTRATION_LOCK_ENABLED; - } - - if (!v2Enabled && hasPin) { - TextSecurePreferences.setV1RegistrationLockEnabled(context, false); - return State.PIN_WITH_REGISTRATION_LOCK_DISABLED; - } - - throw new InvalidInferredStateError(String.format(Locale.ENGLISH, "Invalid state! v1: %b, v2: %b, pin: %b", v1Enabled, v2Enabled, hasPin)); - } - - private enum State { - /** - * User has nothing -- either in the process of registration, or pre-PIN-migration - */ - NO_REGISTRATION_LOCK("no_registration_lock"), - - /** - * User has a V1 registration lock set - */ - REGISTRATION_LOCK_V1("registration_lock_v1"), - - /** - * User has a PIN, and registration lock is enabled. - */ - PIN_WITH_REGISTRATION_LOCK_ENABLED("pin_with_registration_lock_enabled"), - - /** - * User has a PIN, but registration lock is disabled. - */ - PIN_WITH_REGISTRATION_LOCK_DISABLED("pin_with_registration_lock_disabled"), - - /** - * The user has opted out of creating a PIN. In this case, we will generate a high-entropy PIN - * on their behalf. - */ - PIN_OPT_OUT("pin_opt_out"); - - /** - * Using a string key so that people can rename/reorder values in the future without breaking - * serialization. - */ - private final String key; - - State(String key) { - this.key = key; - } - - public @NonNull String serialize() { - return key; - } - - public static @NonNull State deserialize(@NonNull String serialized) { - for (State state : values()) { - if (state.key.equals(serialized)) { - return state; - } - } - throw new IllegalArgumentException("No state for value: " + serialized); - } - } - - private static class InvalidInferredStateError extends Error { - InvalidInferredStateError(String message) { - super(message); - } - } - - private static class InvalidState_NoRegistrationLock extends IllegalStateException {} - private static class InvalidState_RegistrationLockV1 extends IllegalStateException {} - private static class InvalidState_PinWithRegistrationLockEnabled extends IllegalStateException {} - private static class InvalidState_PinWithRegistrationLockDisabled extends IllegalStateException {} - private static class InvalidState_PinOptOut extends IllegalStateException {} -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/RegistrationLockV2Dialog.java b/app/src/main/java/org/thoughtcrime/securesms/pin/RegistrationLockV2Dialog.java index 343f40a763..5853bdca85 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/RegistrationLockV2Dialog.java +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/RegistrationLockV2Dialog.java @@ -45,7 +45,7 @@ public final class RegistrationLockV2Dialog { SimpleTask.run(SignalExecutors.UNBOUNDED, () -> { try { - PinState.onEnableRegistrationLockForUserWithPin(); + SvrRepository.enableRegistrationLockForUserWithPin(); Log.i(TAG, "Successfully enabled registration lock."); return true; } catch (IOException e) { @@ -87,7 +87,7 @@ public final class RegistrationLockV2Dialog { SimpleTask.run(SignalExecutors.UNBOUNDED, () -> { try { - PinState.onDisableRegistrationLockForUserWithPin(); + SvrRepository.disableRegistrationLockForUserWithPin(); Log.i(TAG, "Successfully disabled registration lock."); return true; } catch (IOException e) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/SvrRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/pin/SvrRepository.kt new file mode 100644 index 0000000000..0a5ca545ea --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/SvrRepository.kt @@ -0,0 +1,411 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.pin + +import android.app.backup.BackupManager +import androidx.annotation.WorkerThread +import org.signal.core.util.Stopwatch +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.BuildConfig +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobmanager.JobTracker +import org.thoughtcrime.securesms.jobs.NewRegistrationUsernameSyncJob +import org.thoughtcrime.securesms.jobs.RefreshAttributesJob +import org.thoughtcrime.securesms.jobs.ResetSvrGuessCountJob +import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob +import org.thoughtcrime.securesms.jobs.StorageForcePushJob +import org.thoughtcrime.securesms.jobs.StorageSyncJob +import org.thoughtcrime.securesms.jobs.Svr2MirrorJob +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.lock.v2.PinKeyboardType +import org.thoughtcrime.securesms.megaphone.Megaphones +import org.thoughtcrime.securesms.registration.viewmodel.SvrAuthCredentialSet +import org.thoughtcrime.securesms.util.FeatureFlags +import org.whispersystems.signalservice.api.SvrNoDataException +import org.whispersystems.signalservice.api.kbs.MasterKey +import org.whispersystems.signalservice.api.svr.SecureValueRecovery +import org.whispersystems.signalservice.api.svr.SecureValueRecovery.BackupResponse +import org.whispersystems.signalservice.api.svr.SecureValueRecovery.RestoreResponse +import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV1 +import org.whispersystems.signalservice.internal.push.AuthCredentials +import java.io.IOException +import java.util.concurrent.TimeUnit +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +object SvrRepository { + + val TAG = Log.tag(SvrRepository::class.java) + + private val svr2: SecureValueRecovery = ApplicationDependencies.getSignalServiceAccountManager().getSecureValueRecoveryV2(BuildConfig.SVR2_MRENCLAVE) + private val svr1: SecureValueRecovery = SecureValueRecoveryV1(ApplicationDependencies.getKeyBackupService(BuildConfig.KBS_ENCLAVE)) + + /** An ordered list of SVR implementations. They should be in priority order, with the most important one listed first. */ + private val implementations: List = listOf(svr2, svr1) + + /** + * A lock that ensures that only one thread at a time is altering the various pieces of SVR state. + * + * External usage of this should be limited to one-time migrations. Any routine operation that needs the lock should go in + * this repository instead. + */ + val operationLock = ReentrantLock() + + /** + * Restores the master key from the first available SVR implementation available. + * + * This is intended to be called before registration has been completed, requiring + * that you pass in the credentials provided during registration to access SVR. + * + * You could be hitting this because the user has reglock (and therefore need to + * restore the master key before you can register), or you may be doing the + * sms-skip flow. + */ + @JvmStatic + @WorkerThread + @Throws(IOException::class, SvrWrongPinException::class, SvrNoDataException::class) + fun restoreMasterKeyPreRegistration(credentials: SvrAuthCredentialSet, userPin: String): MasterKey { + operationLock.withLock { + Log.i(TAG, "restoreMasterKeyPreRegistration()", true) + + val operations: List RestoreResponse>> = listOf( + svr2 to { restoreMasterKeyPreRegistration(svr2, credentials.svr2, userPin) }, + svr1 to { restoreMasterKeyPreRegistration(svr1, credentials.svr1, userPin) } + ) + + for ((implementation, operation) in operations) { + when (val response: RestoreResponse = operation()) { + is RestoreResponse.Success -> { + Log.i(TAG, "[restoreMasterKeyPreRegistration] Successfully restored master key. $implementation", true) + if (implementation == svr2) { + SignalStore.svr().appendAuthTokenToList(response.authorization.asBasic()) + } + + return response.masterKey + } + + is RestoreResponse.PinMismatch -> { + Log.i(TAG, "[restoreMasterKeyPreRegistration] Incorrect PIN. $implementation", true) + throw SvrWrongPinException(response.triesRemaining) + } + + is RestoreResponse.NetworkError -> { + Log.i(TAG, "[restoreMasterKeyPreRegistration] Network error. $implementation", response.exception, true) + throw response.exception + } + + is RestoreResponse.ApplicationError -> { + Log.i(TAG, "[restoreMasterKeyPreRegistration] Application error. $implementation", response.exception, true) + throw IOException(response.exception) + } + + RestoreResponse.Missing -> { + Log.w(TAG, "[restoreMasterKeyPreRegistration] No data found for $implementation | Continuing to next implementation.", true) + } + } + } + + Log.w(TAG, "[restoreMasterKeyPreRegistration] No data found for any implementation!", true) + + throw SvrNoDataException() + } + } + + /** + * Restores the master key from the first available SVR implementation available. + * + * This is intended to be called after the user has registered, allowing the function + * to fetch credentials on its own. + */ + @WorkerThread + fun restoreMasterKeyPostRegistration(userPin: String, pinKeyboardType: PinKeyboardType): RestoreResponse { + val stopwatch = Stopwatch("pin-submission") + + operationLock.withLock { + for (implementation in implementations) { + when (val response: RestoreResponse = implementation.restoreDataPostRegistration(userPin)) { + is RestoreResponse.Success -> { + Log.i(TAG, "[restoreMasterKeyPostRegistration] Successfully restored master key. $implementation", true) + stopwatch.split("restore") + + SignalStore.svr().setMasterKey(response.masterKey, userPin) + SignalStore.svr().isRegistrationLockEnabled = false + SignalStore.pinValues().resetPinReminders() + SignalStore.svr().isPinForgottenOrSkipped = false + SignalStore.storageService().setNeedsAccountRestore(false) + SignalStore.pinValues().keyboardType = pinKeyboardType + SignalStore.storageService().setNeedsAccountRestore(false) + if (implementation == svr2) { + SignalStore.svr().appendAuthTokenToList(response.authorization.asBasic()) + } + ApplicationDependencies.getJobManager().add(ResetSvrGuessCountJob()) + stopwatch.split("metadata") + + ApplicationDependencies.getJobManager().runSynchronously(StorageAccountRestoreJob(), StorageAccountRestoreJob.LIFESPAN) + stopwatch.split("account-restore") + + ApplicationDependencies + .getJobManager() + .startChain(StorageSyncJob()) + .then(NewRegistrationUsernameSyncJob()) + .enqueueAndBlockUntilCompletion(TimeUnit.SECONDS.toMillis(10)) + stopwatch.split("contact-restore") + + if (implementation != svr2) { + ApplicationDependencies.getJobManager().add(Svr2MirrorJob()) + } + + stopwatch.stop(TAG) + + return response + } + + is RestoreResponse.PinMismatch -> { + Log.i(TAG, "[restoreMasterKeyPostRegistration] Incorrect PIN. $implementation", true) + return response + } + + is RestoreResponse.NetworkError -> { + Log.i(TAG, "[restoreMasterKeyPostRegistration] Network error. $implementation", response.exception, true) + return response + } + + is RestoreResponse.ApplicationError -> { + Log.i(TAG, "[restoreMasterKeyPostRegistration] Application error. $implementation", response.exception, true) + return response + } + + RestoreResponse.Missing -> { + Log.w(TAG, "[restoreMasterKeyPostRegistration] No data found for: $implementation | Continuing to next implementation.", true) + } + } + } + + Log.w(TAG, "[restoreMasterKeyPostRegistration] No data found for any implementation!", true) + return RestoreResponse.Missing + } + } + + /** + * Sets the user's PIN the one specified, updating local stores as necessary. + * The resulting Single will not throw an error in any expected case, only if there's a runtime exception. + */ + @WorkerThread + @JvmStatic + fun setPin(userPin: String, keyboardType: PinKeyboardType): BackupResponse { + return operationLock.withLock { + val masterKey: MasterKey = SignalStore.svr().getOrCreateMasterKey() + + val responses: List = implementations + .filter { it != svr2 || FeatureFlags.svr2() } + .map { it.setPin(userPin, masterKey) } + .map { it.execute() } + + Log.i(TAG, "[setPin] Responses: $responses", true) + + val error: BackupResponse? = responses.map { + when (it) { + is BackupResponse.ApplicationError -> it + BackupResponse.ExposeFailure -> it + is BackupResponse.NetworkError -> it + BackupResponse.ServerRejected -> it + BackupResponse.EnclaveNotFound -> null + is BackupResponse.Success -> null + } + }.firstOrNull() + + val overallResponse = error + ?: responses.firstOrNull { it is BackupResponse.Success } + ?: responses[0] + + if (overallResponse is BackupResponse.Success) { + Log.i(TAG, "[setPin] Success!", true) + + SignalStore.svr().setMasterKey(masterKey, userPin) + SignalStore.svr().isPinForgottenOrSkipped = false + SignalStore.svr().appendAuthTokenToList(overallResponse.authorization.asBasic()) + + SignalStore.pinValues().keyboardType = keyboardType + SignalStore.pinValues().resetPinReminders() + + ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.PINS_FOR_ALL) + + ApplicationDependencies.getJobManager().add(RefreshAttributesJob()) + } else { + Log.w(TAG, "[setPin] Failed to set PIN! $overallResponse", true) + + if (hasNoRegistrationLock) { + SignalStore.svr().onPinCreateFailure() + } + } + + overallResponse + } + } + + /** + * Invoked after a user has successfully registered. Ensures all the necessary state is updated. + */ + @WorkerThread + @JvmStatic + fun onRegistrationComplete( + masterKey: MasterKey?, + userPin: String?, + hasPinToRestore: Boolean, + setRegistrationLockEnabled: Boolean + ) { + Log.i(TAG, "[onRegistrationComplete] Starting", true) + operationLock.withLock { + if (masterKey == null && userPin != null) { + error("If masterKey is present, pin must also be present!") + } + + if (masterKey != null && userPin != null) { + if (setRegistrationLockEnabled) { + Log.i(TAG, "[onRegistrationComplete] Registration Lock", true) + SignalStore.svr().isRegistrationLockEnabled = true + } else { + Log.i(TAG, "[onRegistrationComplete] ReRegistration Skip SMS", true) + } + + SignalStore.svr().setMasterKey(masterKey, userPin) + SignalStore.pinValues().resetPinReminders() + + ApplicationDependencies.getJobManager().add(ResetSvrGuessCountJob()) + } else if (hasPinToRestore) { + Log.i(TAG, "[onRegistrationComplete] Has a PIN to restore.", true) + SignalStore.svr().clearRegistrationLockAndPin() + SignalStore.storageService().setNeedsAccountRestore(true) + } else { + Log.i(TAG, "[onRegistrationComplete] No registration lock or PIN at all.", true) + SignalStore.svr().clearRegistrationLockAndPin() + } + } + + ApplicationDependencies.getJobManager().add(RefreshAttributesJob()) + } + + /** + * Invoked when the user skips out on PIN restoration or otherwise fails to remember their PIN. + */ + @JvmStatic + fun onPinRestoreForgottenOrSkipped() { + operationLock.withLock { + SignalStore.svr().clearRegistrationLockAndPin() + SignalStore.storageService().setNeedsAccountRestore(false) + SignalStore.svr().isPinForgottenOrSkipped = true + } + } + + @JvmStatic + @WorkerThread + fun optOutOfPin() { + operationLock.withLock { + SignalStore.svr().optOut() + + ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.PINS_FOR_ALL) + + bestEffortRefreshAttributes() + bestEffortForcePushStorage() + } + } + + @JvmStatic + @WorkerThread + @Throws(IOException::class) + fun enableRegistrationLockForUserWithPin() { + operationLock.withLock { + check(SignalStore.svr().hasPin() && !SignalStore.svr().hasOptedOut()) { "Must have a PIN to set a registration lock!" } + + Log.i(TAG, "[enableRegistrationLockForUserWithPin] Enabling registration lock.", true) + ApplicationDependencies.getSignalServiceAccountManager().enableRegistrationLock(SignalStore.svr().getOrCreateMasterKey()) + SignalStore.svr().isRegistrationLockEnabled = true + Log.i(TAG, "[enableRegistrationLockForUserWithPin] Registration lock successfully enabled.", true) + } + } + + @JvmStatic + @WorkerThread + @Throws(IOException::class) + fun disableRegistrationLockForUserWithPin() { + operationLock.withLock { + check(SignalStore.svr().hasPin() && !SignalStore.svr().hasOptedOut()) { "Must have a PIN to disable registration lock!" } + + Log.i(TAG, "[disableRegistrationLockForUserWithPin] Disabling registration lock.", true) + ApplicationDependencies.getSignalServiceAccountManager().disableRegistrationLock() + SignalStore.svr().isRegistrationLockEnabled = false + Log.i(TAG, "[disableRegistrationLockForUserWithPin] Registration lock successfully disabled.", true) + } + } + + /** + * Fetches new SVR credentials and persists them in the backup store to be used during re-registration. + */ + @WorkerThread + @Throws(IOException::class) + fun refreshAndStoreAuthorization() { + try { + val credentials: AuthCredentials = svr2.authorization() + backupSvrCredentials(credentials) + } catch (e: Throwable) { + if (e is IOException) { + throw e + } else { + throw IOException(e) + } + } + } + + @WorkerThread + private fun restoreMasterKeyPreRegistration(svr: SecureValueRecovery, credentials: AuthCredentials?, userPin: String): RestoreResponse { + return if (credentials == null) { + RestoreResponse.Missing + } else { + svr.restoreDataPreRegistration(credentials, userPin) + } + } + + @WorkerThread + private fun bestEffortRefreshAttributes() { + val result = ApplicationDependencies.getJobManager().runSynchronously(RefreshAttributesJob(), TimeUnit.SECONDS.toMillis(10)) + if (result.isPresent && result.get() == JobTracker.JobState.SUCCESS) { + Log.i(TAG, "Attributes were refreshed successfully.", true) + } else if (result.isPresent) { + Log.w(TAG, "Attribute refresh finished, but was not successful. Enqueuing one for later. (Result: " + result.get() + ")", true) + ApplicationDependencies.getJobManager().add(RefreshAttributesJob()) + } else { + Log.w(TAG, "Job did not finish in the allotted time. It'll finish later.", true) + } + } + + @WorkerThread + private fun bestEffortForcePushStorage() { + val result = ApplicationDependencies.getJobManager().runSynchronously(StorageForcePushJob(), TimeUnit.SECONDS.toMillis(10)) + if (result.isPresent && result.get() == JobTracker.JobState.SUCCESS) { + Log.i(TAG, "Storage was force-pushed successfully.", true) + } else if (result.isPresent) { + Log.w(TAG, "Storage force-pushed finished, but was not successful. Enqueuing one for later. (Result: " + result.get() + ")", true) + ApplicationDependencies.getJobManager().add(RefreshAttributesJob()) + } else { + Log.w(TAG, "Storage fore push did not finish in the allotted time. It'll finish later.", true) + } + } + + private fun backupSvrCredentials(credentials: AuthCredentials) { + val tokenIsNew = SignalStore.svr().appendAuthTokenToList(credentials.asBasic()) + + if (tokenIsNew && SignalStore.svr().hasPin()) { + BackupManager(ApplicationDependencies.getApplication()).dataChanged() + } + } + + private val hasNoRegistrationLock: Boolean + get() { + return !SignalStore.svr().isRegistrationLockEnabled && + !SignalStore.svr().hasPin() && + !SignalStore.svr().hasOptedOut() + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/SvrWrongPinException.java b/app/src/main/java/org/thoughtcrime/securesms/pin/SvrWrongPinException.java new file mode 100644 index 0000000000..5f68aa0d1c --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/SvrWrongPinException.java @@ -0,0 +1,14 @@ +package org.thoughtcrime.securesms.pin; + +public final class SvrWrongPinException extends Exception { + + private final int triesRemaining; + + public SvrWrongPinException(int triesRemaining){ + this.triesRemaining = triesRemaining; + } + + public int getTriesRemaining() { + return triesRemaining; + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/TokenData.java b/app/src/main/java/org/thoughtcrime/securesms/pin/TokenData.java deleted file mode 100644 index 6abb3e9b6c..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/TokenData.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.thoughtcrime.securesms.pin; - -import android.os.Parcel; -import android.os.Parcelable; - -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.KbsEnclave; -import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; - -public class TokenData implements Parcelable { - private final KbsEnclave enclave; - private final String basicAuth; - private final TokenResponse tokenResponse; - - TokenData(@NonNull KbsEnclave enclave, @NonNull String basicAuth, @NonNull TokenResponse tokenResponse) { - this.enclave = enclave; - this.basicAuth = basicAuth; - this.tokenResponse = tokenResponse; - } - - private TokenData(Parcel in) { - this.enclave = new KbsEnclave(in.readString(), in.readString(), in.readString()); - this.basicAuth = in.readString(); - - byte[] backupId = in.createByteArray(); - byte[] token = in.createByteArray(); - - this.tokenResponse = new TokenResponse(backupId, token, in.readInt()); - } - - public static @NonNull TokenData withResponse(@NonNull TokenData data, @NonNull TokenResponse response) { - return new TokenData(data.getEnclave(), data.getBasicAuth(), response); - } - - public int getTriesRemaining() { - return tokenResponse.getTries(); - } - - public @NonNull String getBasicAuth() { - return basicAuth; - } - - public @NonNull TokenResponse getTokenResponse() { - return tokenResponse; - } - - public @NonNull KbsEnclave getEnclave() { - return enclave; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(enclave.getEnclaveName()); - dest.writeString(enclave.getServiceId()); - dest.writeString(enclave.getMrEnclave()); - - dest.writeString(basicAuth); - - dest.writeByteArray(tokenResponse.getBackupId()); - dest.writeByteArray(tokenResponse.getToken()); - dest.writeInt(tokenResponse.getTries()); - } - - public static final Creator CREATOR = new Creator() { - @Override - public TokenData createFromParcel(Parcel in) { - return new TokenData(in); - } - - @Override - public TokenData[] newArray(int size) { - return new TokenData[size]; - } - }; - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPinPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPinPreferenceFragment.java index 719416bf0f..7c41e4a688 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPinPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPinPreferenceFragment.java @@ -4,7 +4,6 @@ import android.content.Intent; import android.os.Bundle; import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; import com.google.android.material.dialog.MaterialAlertDialogBuilder; @@ -12,11 +11,10 @@ import com.google.android.material.snackbar.Snackbar; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity; +import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity; import org.thoughtcrime.securesms.payments.backup.PaymentsRecoveryStartFragmentArgs; import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity; import org.thoughtcrime.securesms.pin.PinOptOutDialog; -import org.thoughtcrime.securesms.util.TextSecurePreferences; public class AdvancedPinPreferenceFragment extends ListSummaryPreferenceFragment { @@ -41,7 +39,7 @@ public class AdvancedPinPreferenceFragment extends ListSummaryPreferenceFragment @Override public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { - if (requestCode == CreateKbsPinActivity.REQUEST_NEW_PIN && resultCode == CreateKbsPinActivity.RESULT_OK) { + if (requestCode == CreateSvrPinActivity.REQUEST_NEW_PIN && resultCode == CreateSvrPinActivity.RESULT_OK) { Snackbar.make(requireView(), R.string.ApplicationPreferencesActivity_pin_created, Snackbar.LENGTH_LONG).show(); } } @@ -50,7 +48,7 @@ public class AdvancedPinPreferenceFragment extends ListSummaryPreferenceFragment Preference enable = this.findPreference(PREF_ENABLE); Preference disable = this.findPreference(PREF_DISABLE); - if (SignalStore.kbsValues().hasOptedOut()) { + if (SignalStore.svr().hasOptedOut()) { enable.setVisible(true); disable.setVisible(false); @@ -70,8 +68,7 @@ public class AdvancedPinPreferenceFragment extends ListSummaryPreferenceFragment } private void onPreferenceChanged(boolean enabled) { - boolean hasRegistrationLock = TextSecurePreferences.isV1RegistrationLockEnabled(requireContext()) || - SignalStore.kbsValues().isV2RegistrationLockEnabled(); + boolean hasRegistrationLock = SignalStore.svr().isRegistrationLockEnabled(); if (!enabled && hasRegistrationLock) { new MaterialAlertDialogBuilder(requireContext()) @@ -102,7 +99,7 @@ public class AdvancedPinPreferenceFragment extends ListSummaryPreferenceFragment Snackbar.make(requireView(), R.string.ApplicationPreferencesActivity_pin_disabled, Snackbar.LENGTH_SHORT).show(); }); } else { - startActivityForResult(CreateKbsPinActivity.getIntentForPinCreate(requireContext()), CreateKbsPinActivity.REQUEST_NEW_PIN); + startActivityForResult(CreateSvrPinActivity.getIntentForPinCreate(requireContext()), CreateSvrPinActivity.REQUEST_NEW_PIN); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java index 023f398726..94c23a63e5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java @@ -29,7 +29,7 @@ import org.thoughtcrime.securesms.jobs.PreKeysSyncJob; import org.thoughtcrime.securesms.jobs.RotateCertificateJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.notifications.NotificationIds; -import org.thoughtcrime.securesms.pin.PinState; +import org.thoughtcrime.securesms.pin.SvrRepository; import org.thoughtcrime.securesms.push.AccountManagerFactory; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; @@ -102,13 +102,8 @@ public final class RegistrationRepository { { return Single.>fromCallable(() -> { try { - String pin = response.getPin(); registerAccountInternal(registrationData, response, setRegistrationLockEnabled); - if (pin != null && !pin.isEmpty()) { - PinState.onPinChangedOrCreated(context, pin, SignalStore.pinValues().getKeyboardType()); - } - JobManager jobManager = ApplicationDependencies.getJobManager(); jobManager.add(new DirectoryRefreshJob(false)); jobManager.add(new RotateCertificateJob()); @@ -176,7 +171,7 @@ public final class RegistrationRepository { TextSecurePreferences.setUnauthorizedReceived(context, false); NotificationManagerCompat.from(context).cancel(NotificationIds.UNREGISTERED_NOTIFICATION_ID); - PinState.onRegistration(context, response.getKbsData(), response.getPin(), hasPin, setRegistrationLockEnabled); + SvrRepository.onRegistrationComplete(response.getMasterKey(), response.getPin(), hasPin, setRegistrationLockEnabled); ApplicationDependencies.closeConnections(); ApplicationDependencies.getIncomingMessageObserver(); @@ -226,13 +221,13 @@ public final class RegistrationRepository { return null; } - public Single getKbsAuthCredential(@NonNull RegistrationData registrationData, List usernamePasswords) { + public Single getSvrAuthCredential(@NonNull RegistrationData registrationData, List usernamePasswords) { SignalServiceAccountManager accountManager = AccountManagerFactory.getInstance().createUnauthenticated(context, registrationData.getE164(), SignalServiceAddress.DEFAULT_DEVICE_ID, registrationData.getPassword()); return accountManager.checkBackupAuthCredentials(registrationData.getE164(), usernamePasswords) .map(BackupAuthCheckProcessor::new) .doOnSuccess(processor -> { - if (SignalStore.kbsValues().removeAuthTokens(processor.getInvalid())) { + if (SignalStore.svr().removeAuthTokens(processor.getInvalid())) { new BackupManager(context).dataChanged(); } }); diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationUtil.java b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationUtil.java index f905aafa8e..49295ec93e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationUtil.java @@ -22,7 +22,7 @@ public final class RegistrationUtil { if (!SignalStore.registrationValues().isRegistrationComplete() && SignalStore.account().isRegistered() && !Recipient.self().getProfileName().isEmpty() && - (SignalStore.kbsValues().hasPin() || SignalStore.kbsValues().hasOptedOut())) + (SignalStore.svr().hasPin() || SignalStore.svr().hasOptedOut())) { Log.i(TAG, "Marking registration completed.", new Throwable()); SignalStore.registrationValues().setRegistrationComplete(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountRepository.kt index 92b1b7e638..67eb6ce75b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountRepository.kt @@ -10,15 +10,15 @@ import org.signal.libsignal.protocol.IdentityKeyPair import org.thoughtcrime.securesms.AppCapabilities import org.thoughtcrime.securesms.gcm.FcmUtil import org.thoughtcrime.securesms.keyvalue.SignalStore -import org.thoughtcrime.securesms.pin.KeyBackupSystemWrongPinException +import org.thoughtcrime.securesms.pin.SvrWrongPinException import org.thoughtcrime.securesms.push.AccountManagerFactory import org.thoughtcrime.securesms.registration.PushChallengeRequest.PushChallengeEvent import org.thoughtcrime.securesms.util.TextSecurePreferences -import org.whispersystems.signalservice.api.KbsPinData -import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException import org.whispersystems.signalservice.api.SignalServiceAccountManager +import org.whispersystems.signalservice.api.SvrNoDataException import org.whispersystems.signalservice.api.account.AccountAttributes import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess +import org.whispersystems.signalservice.api.kbs.MasterKey import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException import org.whispersystems.signalservice.internal.ServiceResponse @@ -157,7 +157,7 @@ class VerifyAccountRepository(private val context: Application) { }.subscribeOn(Schedulers.io()) } - fun registerAccount(sessionId: String?, registrationData: RegistrationData, pin: String? = null, kbsPinDataProducer: KbsPinDataProducer? = null): Single> { + fun registerAccount(sessionId: String?, registrationData: RegistrationData, pin: String? = null, masterKeyProducer: MasterKeyProducer? = null): Single> { val universalUnidentifiedAccess: Boolean = TextSecurePreferences.isUniversalUnidentifiedAccess(context) val unidentifiedAccessKey: ByteArray = UnidentifiedAccess.deriveAccessKeyFrom(registrationData.profileKey) @@ -168,15 +168,14 @@ class VerifyAccountRepository(private val context: Application) { registrationData.password ) - val kbsData = kbsPinDataProducer?.produceKbsPinData() - val registrationLockV2: String? = kbsData?.masterKey?.deriveRegistrationLock() + val masterKey: MasterKey? = masterKeyProducer?.produceMasterKey() + val registrationLock: String? = masterKey?.deriveRegistrationLock() val accountAttributes = AccountAttributes( signalingKey = null, registrationId = registrationData.registrationId, fetchesMessages = registrationData.isNotFcm, - pin = pin, - registrationLock = registrationLockV2, + registrationLock = registrationLock, unidentifiedAccessKey = unidentifiedAccessKey, unrestrictedUnidentifiedAccess = universalUnidentifiedAccess, capabilities = AppCapabilities.getCapabilities(true), @@ -197,7 +196,7 @@ class VerifyAccountRepository(private val context: Application) { return Single.fromCallable { val response = accountManager.registerAccount(sessionId, registrationData.recoveryPassword, accountAttributes, aciPreKeyCollection, pniPreKeyCollection, registrationData.fcmToken, true) - VerifyResponse.from(response, kbsData, pin, aciPreKeyCollection, pniPreKeyCollection) + VerifyResponse.from(response, masterKey, pin, aciPreKeyCollection, pniPreKeyCollection) }.subscribeOn(Schedulers.io()) } @@ -207,9 +206,9 @@ class VerifyAccountRepository(private val context: Application) { }.subscribeOn(Schedulers.io()) } - interface KbsPinDataProducer { - @Throws(IOException::class, KeyBackupSystemWrongPinException::class, KeyBackupSystemNoDataException::class) - fun produceKbsPinData(): KbsPinData + interface MasterKeyProducer { + @Throws(IOException::class, SvrWrongPinException::class, SvrNoDataException::class) + fun produceMasterKey(): MasterKey } enum class Mode(val isSmsRetrieverSupported: Boolean) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponse.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponse.kt index 5692784946..10cf7c9088 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponse.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponse.kt @@ -1,13 +1,13 @@ package org.thoughtcrime.securesms.registration -import org.whispersystems.signalservice.api.KbsPinData import org.whispersystems.signalservice.api.account.PreKeyCollection +import org.whispersystems.signalservice.api.kbs.MasterKey import org.whispersystems.signalservice.internal.ServiceResponse import org.whispersystems.signalservice.internal.push.VerifyAccountResponse data class VerifyResponse( val verifyAccountResponse: VerifyAccountResponse, - val kbsData: KbsPinData?, + val masterKey: MasterKey?, val pin: String?, val aciPreKeyCollection: PreKeyCollection?, val pniPreKeyCollection: PreKeyCollection? @@ -15,13 +15,13 @@ data class VerifyResponse( companion object { fun from( response: ServiceResponse, - kbsData: KbsPinData?, + masterKey: MasterKey?, pin: String?, aciPreKeyCollection: PreKeyCollection?, pniPreKeyCollection: PreKeyCollection? ): ServiceResponse { return if (response.result.isPresent) { - ServiceResponse.forResult(VerifyResponse(response.result.get(), kbsData, pin, aciPreKeyCollection, pniPreKeyCollection), 200, null) + ServiceResponse.forResult(VerifyResponse(response.result.get(), masterKey, pin, aciPreKeyCollection, pniPreKeyCollection), 200, null) } else { ServiceResponse.coerceError(response) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponseProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponseProcessor.kt index e98ced9652..43ffaae3ec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponseProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponseProcessor.kt @@ -1,14 +1,12 @@ package org.thoughtcrime.securesms.registration -import org.thoughtcrime.securesms.pin.KeyBackupSystemWrongPinException -import org.thoughtcrime.securesms.pin.TokenData -import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException +import org.thoughtcrime.securesms.pin.SvrWrongPinException +import org.thoughtcrime.securesms.registration.viewmodel.SvrAuthCredentialSet +import org.whispersystems.signalservice.api.SvrNoDataException import org.whispersystems.signalservice.api.push.exceptions.IncorrectRegistrationRecoveryPasswordException -import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException import org.whispersystems.signalservice.internal.ServiceResponse import org.whispersystems.signalservice.internal.ServiceResponseProcessor -import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse import org.whispersystems.signalservice.internal.push.LockedException /** @@ -16,7 +14,19 @@ import org.whispersystems.signalservice.internal.push.LockedException */ sealed class VerifyResponseProcessor(response: ServiceResponse) : ServiceResponseProcessor(response) { - open val tokenData: TokenData? = null + open val svrTriesRemaining: Int? + get() = (error as? SvrWrongPinException)?.triesRemaining + + open val svrAuthCredentials: SvrAuthCredentialSet? + get() { + return error?.let { + if (it is LockedException) { + SvrAuthCredentialSet(it.svr1Credentials, it.svr2Credentials) + } else { + null + } + } + } public override fun authorizationFailed(): Boolean { return super.authorizationFailed() @@ -34,10 +44,6 @@ sealed class VerifyResponseProcessor(response: ServiceResponse) return super.getError() } - fun invalidSession(): Boolean { - return error is NoSuchSessionException - } - fun getLockedException(): LockedException { return error as LockedException } @@ -50,34 +56,24 @@ sealed class VerifyResponseProcessor(response: ServiceResponse) return error is IncorrectRegistrationRecoveryPasswordException } - abstract fun isKbsLocked(): Boolean + /** True if the account has reglock enabled but all guesses have been exhausted, otherwise false. */ + abstract fun isRegistrationLockPresentAndSvrExhausted(): Boolean } /** * Verify processor specific to verifying without needing to handle registration lock. */ class VerifyResponseWithoutKbs(response: ServiceResponse) : VerifyResponseProcessor(response) { - override fun isKbsLocked(): Boolean { - return registrationLock() && getLockedException().basicStorageCredentials == null + override fun isRegistrationLockPresentAndSvrExhausted(): Boolean { + return registrationLock() && getLockedException().svr1Credentials == null && getLockedException().svr2Credentials == null } } /** - * Verify processor specific to verifying and successfully retrieving KBS information to - * later attempt to verif with registration lock data (pin). + * Verify processor indicating we cannot register until registration lock has been resolved. */ -class VerifyResponseWithSuccessfulKbs(response: ServiceResponse, override val tokenData: TokenData) : VerifyResponseProcessor(response) { - override fun isKbsLocked(): Boolean { - return registrationLock() && tokenData.triesRemaining == 0 - } -} - -/** - * Verify processor specific to verifying and unsuccessfully retrieving KBS information that - * is required for attempting to verify a registration locked account. - */ -class VerifyResponseWithFailedKbs(response: ServiceResponse) : VerifyResponseProcessor(ServiceResponse.coerceError(response)) { - override fun isKbsLocked(): Boolean { +class VerifyResponseHitRegistrationLock(response: ServiceResponse) : VerifyResponseProcessor(response) { + override fun isRegistrationLockPresentAndSvrExhausted(): Boolean { return false } } @@ -86,18 +82,14 @@ class VerifyResponseWithFailedKbs(response: ServiceResponse) : Verify * Process responses from attempting to verify an account with registration lock for use in * account registration. */ -class VerifyResponseWithRegistrationLockProcessor(response: ServiceResponse, override val tokenData: TokenData?) : VerifyResponseProcessor(response) { +class VerifyResponseWithRegistrationLockProcessor(response: ServiceResponse, override val svrAuthCredentials: SvrAuthCredentialSet?) : VerifyResponseProcessor(response) { fun wrongPin(): Boolean { - return error is KeyBackupSystemWrongPinException + return error is SvrWrongPinException } - fun getTokenResponse(): TokenResponse { - return (error as KeyBackupSystemWrongPinException).tokenResponse - } - - override fun isKbsLocked(): Boolean { - return error is KeyBackupSystemNoDataException + override fun isRegistrationLockPresentAndSvrExhausted(): Boolean { + return error is SvrNoDataException } fun updatedIfRegistrationFailed(response: ServiceResponse): VerifyResponseWithRegistrationLockProcessor { @@ -105,12 +97,12 @@ class VerifyResponseWithRegistrationLockProcessor(response: ServiceResponse timeRemaining = t); - TokenData keyBackupCurrentToken = viewModel.getKeyBackupCurrentToken(); + Integer triesRemaining = viewModel.getSvrTriesRemaining(); - if (keyBackupCurrentToken != null) { - int triesRemaining = keyBackupCurrentToken.getTriesRemaining(); + if (triesRemaining != null) { if (triesRemaining <= 3) { int daysRemaining = getLockoutDays(timeRemaining); @@ -177,8 +176,8 @@ public abstract class BaseRegistrationLockFragment extends LoggingFragment { if (processor.hasResult()) { handleSuccessfulPinEntry(pin); } else if (processor.wrongPin()) { - onIncorrectKbsRegistrationLockPin(processor.getTokenData()); - } else if (processor.isKbsLocked() || processor.registrationLock()) { + onIncorrectKbsRegistrationLockPin(Objects.requireNonNull(processor.getSvrTriesRemaining())); + } else if (processor.isRegistrationLockPresentAndSvrExhausted() || processor.registrationLock()) { onKbsAccountLocked(); } else if (processor.rateLimit()) { onRateLimited(); @@ -191,35 +190,33 @@ public abstract class BaseRegistrationLockFragment extends LoggingFragment { disposables.add(verify); } - public void onIncorrectKbsRegistrationLockPin(@NonNull TokenData tokenData) { + public void onIncorrectKbsRegistrationLockPin(int svrTriesRemaining) { pinButton.cancelSpinning(); pinEntry.getText().clear(); enableAndFocusPinEntry(); - viewModel.setKeyBackupTokenData(tokenData); + viewModel.setSvrTriesRemaining(svrTriesRemaining); - int triesRemaining = tokenData.getTriesRemaining(); - - if (triesRemaining == 0) { + if (svrTriesRemaining == 0) { Log.w(TAG, "Account locked. User out of attempts on KBS."); onAccountLocked(); return; } - if (triesRemaining == 3) { + if (svrTriesRemaining == 3) { int daysRemaining = getLockoutDays(timeRemaining); new MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.RegistrationLockFragment__incorrect_pin) - .setMessage(getTriesRemainingDialogMessage(triesRemaining, daysRemaining)) + .setMessage(getTriesRemainingDialogMessage(svrTriesRemaining, daysRemaining)) .setPositiveButton(android.R.string.ok, null) .show(); } - if (triesRemaining > 5) { + if (svrTriesRemaining > 5) { errorLabel.setText(R.string.RegistrationLockFragment__incorrect_pin_try_again); } else { - errorLabel.setText(requireContext().getResources().getQuantityString(R.plurals.RegistrationLockFragment__incorrect_pin_d_attempts_remaining, triesRemaining, triesRemaining)); + errorLabel.setText(requireContext().getResources().getQuantityString(R.plurals.RegistrationLockFragment__incorrect_pin_d_attempts_remaining, svrTriesRemaining, svrTriesRemaining)); forgotPin.setVisibility(View.VISIBLE); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterPhoneNumberFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterPhoneNumberFragment.java index 79a06c234d..3f20da327f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterPhoneNumberFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterPhoneNumberFragment.java @@ -470,7 +470,6 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R : R.string.RegistrationActivity_a_verification_code_will_be_sent_to_this_number, e164number, () -> { - exitInProgressUiState(); ViewUtil.hideKeyboard(context, number.getEditText()); onConfirmed.run(); }, diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/ReRegisterWithPinFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/ReRegisterWithPinFragment.kt index 316fc31c74..94442d2e0c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/ReRegisterWithPinFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/ReRegisterWithPinFragment.kt @@ -14,8 +14,8 @@ import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.LoggingFragment import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.databinding.PinRestoreEntryFragmentBinding -import org.thoughtcrime.securesms.lock.v2.KbsConstants import org.thoughtcrime.securesms.lock.v2.PinKeyboardType +import org.thoughtcrime.securesms.lock.v2.SvrConstants import org.thoughtcrime.securesms.registration.VerifyResponseWithRegistrationLockProcessor import org.thoughtcrime.securesms.registration.viewmodel.ReRegisterWithPinViewModel import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel @@ -80,7 +80,7 @@ class ReRegisterWithPinFragment : LoggingFragment(R.layout.pin_restore_entry_fra binding.pinRestoreKeyboardToggle.setIconResource(getPinEntryKeyboardType().other.iconResource) - reRegisterViewModel.updateTokenData(registrationViewModel.keyBackupCurrentToken) + reRegisterViewModel.updateSvrTriesRemaining(registrationViewModel.svrTriesRemaining) disposables += reRegisterViewModel.triesRemaining.subscribe(this::updateTriesRemaining) } @@ -93,7 +93,7 @@ class ReRegisterWithPinFragment : LoggingFragment(R.layout.pin_restore_entry_fra private fun handlePinEntry() { val pin: String? = binding.pinRestorePinInput.text?.toString() - val trimmedLength = pin?.replace(" ", "")?.length ?: 0 + val trimmedLength = pin?.trim()?.length ?: 0 if (trimmedLength == 0) { Toast.makeText(requireContext(), R.string.RegistrationActivity_you_must_enter_your_registration_lock_PIN, Toast.LENGTH_LONG).show() enableAndFocusPinEntry() @@ -126,12 +126,12 @@ class ReRegisterWithPinFragment : LoggingFragment(R.layout.pin_restore_entry_fra reRegisterViewModel.hasIncorrectGuess = true if (processor is VerifyResponseWithRegistrationLockProcessor && processor.wrongPin()) { - reRegisterViewModel.updateTokenData(processor.tokenData) - if (processor.tokenData != null) { - registrationViewModel.setKeyBackupTokenData(processor.tokenData) + reRegisterViewModel.updateSvrTriesRemaining(processor.svrTriesRemaining) + if (processor.svrTriesRemaining != null) { + registrationViewModel.svrTriesRemaining = processor.svrTriesRemaining } return@subscribe - } else if (processor.isKbsLocked()) { + } else if (processor.isRegistrationLockPresentAndSvrExhausted()) { Log.w(TAG, "Unable to continue skip flow, KBS is locked") onAccountLocked() } else if (processor.isIncorrectRegistrationRecoveryPassword()) { @@ -215,7 +215,7 @@ class ReRegisterWithPinFragment : LoggingFragment(R.layout.pin_restore_entry_fra MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.PinRestoreEntryFragment_need_help) - .setMessage(getString(message, KbsConstants.MINIMUM_PIN_LENGTH)) + .setMessage(getString(message, SvrConstants.MINIMUM_PIN_LENGTH)) .setPositiveButton(R.string.PinRestoreEntryFragment_skip) { _, _ -> onSkipPinEntry() } .setNeutralButton(R.string.PinRestoreEntryFragment_contact_support) { _, _ -> val body = SupportEmailUtil.generateSupportEmailBody(requireContext(), R.string.ReRegisterWithPinFragment_support_email_subject, null, null) diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationCompleteFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationCompleteFragment.kt index 725eaa03a0..3c511d9319 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationCompleteFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationCompleteFragment.kt @@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceProfileContentUpdateJob import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob import org.thoughtcrime.securesms.jobs.ProfileUploadJob import org.thoughtcrime.securesms.keyvalue.SignalStore -import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity +import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity import org.thoughtcrime.securesms.pin.PinRestoreActivity import org.thoughtcrime.securesms.profiles.AvatarHelper import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity @@ -51,7 +51,7 @@ class RegistrationCompleteFragment : LoggingFragment() { val isProfileNameEmpty = Recipient.self().profileName.isEmpty val isAvatarEmpty = !AvatarHelper.hasAvatar(activity, Recipient.self().id) val needsProfile = isProfileNameEmpty || isAvatarEmpty - val needsPin = !SignalStore.kbsValues().hasPin() && !viewModel.isReregister + val needsPin = !SignalStore.svr().hasPin() && !viewModel.isReregister Log.i(TAG, "Pin restore flow not required. Profile name: $isProfileNameEmpty | Profile avatar: $isAvatarEmpty | Needs PIN: $needsPin") @@ -66,7 +66,7 @@ class RegistrationCompleteFragment : LoggingFragment() { var startIntent = MainActivity.clearTop(activity) if (needsPin) { - startIntent = chainIntents(CreateKbsPinActivity.getIntentForPinCreate(activity), startIntent) + startIntent = chainIntents(CreateSvrPinActivity.getIntentForPinCreate(activity), startIntent) } if (needsProfile) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationViewDelegate.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationViewDelegate.kt index a8e6a18bd0..ed4c5c3322 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationViewDelegate.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationViewDelegate.kt @@ -58,7 +58,7 @@ object RegistrationViewDelegate { setMessage(message) setPositiveButton(android.R.string.ok) { _, _ -> onConfirmed.run() } setNegativeButton(R.string.RegistrationActivity_edit_number) { _, _ -> onEditNumber.run() } - setOnDismissListener { onEditNumber.run() } + setOnCancelListener { onEditNumber.run() } }.show() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/BaseRegistrationViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/BaseRegistrationViewModel.java index 102ea87dfc..828d9d7f74 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/BaseRegistrationViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/BaseRegistrationViewModel.java @@ -12,16 +12,13 @@ import com.google.i18n.phonenumbers.Phonenumber; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.pin.KbsRepository; -import org.thoughtcrime.securesms.pin.TokenData; import org.thoughtcrime.securesms.registration.RegistrationSessionProcessor; import org.thoughtcrime.securesms.registration.VerifyAccountRepository; import org.thoughtcrime.securesms.registration.VerifyAccountRepository.Mode; import org.thoughtcrime.securesms.registration.VerifyResponse; import org.thoughtcrime.securesms.registration.VerifyResponseProcessor; -import org.thoughtcrime.securesms.registration.VerifyResponseWithFailedKbs; import org.thoughtcrime.securesms.registration.VerifyResponseWithRegistrationLockProcessor; -import org.thoughtcrime.securesms.registration.VerifyResponseWithSuccessfulKbs; +import org.thoughtcrime.securesms.registration.VerifyResponseHitRegistrationLock; import org.thoughtcrime.securesms.registration.VerifyResponseWithoutKbs; import org.whispersystems.signalservice.internal.ServiceResponse; @@ -48,7 +45,8 @@ public abstract class BaseRegistrationViewModel extends ViewModel { private static final String STATE_PUSH_TIMED_OUT = "PUSH_TIMED_OUT"; private static final String STATE_INCORRECT_CODE_ATTEMPTS = "STATE_INCORRECT_CODE_ATTEMPTS"; private static final String STATE_REQUEST_RATE_LIMITER = "REQUEST_RATE_LIMITER"; - private static final String STATE_KBS_TOKEN = "KBS_TOKEN"; + private static final String STATE_SVR_AUTH = "SVR_AUTH"; + private static final String STATE_SVR_TRIES_REMAINING = "SVR_TRIES_REMAINING"; private static final String STATE_TIME_REMAINING = "TIME_REMAINING"; private static final String STATE_CAN_CALL_AT_TIME = "CAN_CALL_AT_TIME"; private static final String STATE_CAN_SMS_AT_TIME = "CAN_SMS_AT_TIME"; @@ -56,24 +54,21 @@ public abstract class BaseRegistrationViewModel extends ViewModel { protected final SavedStateHandle savedState; protected final VerifyAccountRepository verifyAccountRepository; - protected final KbsRepository kbsRepository; public BaseRegistrationViewModel(@NonNull SavedStateHandle savedStateHandle, @NonNull VerifyAccountRepository verifyAccountRepository, - @NonNull KbsRepository kbsRepository, @NonNull String password) { this.savedState = savedStateHandle; this.verifyAccountRepository = verifyAccountRepository; - this.kbsRepository = kbsRepository; setInitialDefaultValue(STATE_NUMBER, NumberViewState.INITIAL); setInitialDefaultValue(STATE_REGISTRATION_SECRET, password); setInitialDefaultValue(STATE_VERIFICATION_CODE, ""); setInitialDefaultValue(STATE_INCORRECT_CODE_ATTEMPTS, 0); setInitialDefaultValue(STATE_REQUEST_RATE_LIMITER, new LocalCodeRequestRateLimiter(60_000)); - setInitialDefaultValue(STATE_RECOVERY_PASSWORD, SignalStore.kbsValues().getRecoveryPassword()); + setInitialDefaultValue(STATE_RECOVERY_PASSWORD, SignalStore.svr().getRecoveryPassword()); setInitialDefaultValue(STATE_PUSH_TIMED_OUT, false); } @@ -188,12 +183,20 @@ public abstract class BaseRegistrationViewModel extends ViewModel { return challengeKeys; } - public @Nullable TokenData getKeyBackupCurrentToken() { - return savedState.get(STATE_KBS_TOKEN); + protected void setSvrAuthCredentials(SvrAuthCredentialSet credentials) { + savedState.set(STATE_SVR_AUTH, credentials); } - public void setKeyBackupTokenData(@Nullable TokenData tokenData) { - savedState.set(STATE_KBS_TOKEN, tokenData); + protected @Nullable SvrAuthCredentialSet getSvrAuthCredentials() { + return savedState.get(STATE_SVR_AUTH); + } + + public @Nullable Integer getSvrTriesRemaining() { + return savedState.get(STATE_SVR_TRIES_REMAINING); + } + + public void setSvrTriesRemaining(@Nullable Integer triesRemaining) { + savedState.set(STATE_SVR_TRIES_REMAINING, triesRemaining); } public void setRecoveryPassword(@Nullable String recoveryPassword) { @@ -338,56 +341,54 @@ public abstract class BaseRegistrationViewModel extends ViewModel { return verifyAccountWithoutRegistrationLock() .flatMap(response -> { - if (response.getResult().isPresent() && response.getResult().get().getKbsData() != null) { + if (response.getResult().isPresent() && response.getResult().get().getMasterKey() != null) { return onVerifySuccessWithRegistrationLock(new VerifyResponseWithRegistrationLockProcessor(response, null), response.getResult().get().getPin()); } VerifyResponseProcessor processor = new VerifyResponseWithoutKbs(response); if (processor.hasResult()) { return onVerifySuccess(processor); - } else if (processor.registrationLock() && !processor.isKbsLocked()) { - return kbsRepository.getToken(processor.getLockedException().getBasicStorageCredentials()) - .map(r -> r.getResult().isPresent() ? new VerifyResponseWithSuccessfulKbs(processor.getResponse(), r.getResult().get()) - : new VerifyResponseWithFailedKbs(r)); + } else if (processor.registrationLock() && !processor.isRegistrationLockPresentAndSvrExhausted()) { + return Single.just(new VerifyResponseHitRegistrationLock(processor.getResponse())); } return Single.just(processor); }) .observeOn(AndroidSchedulers.mainThread()) .doOnSuccess(processor -> { - if (processor.registrationLock() && !processor.isKbsLocked()) { + if (processor.registrationLock() && !processor.isRegistrationLockPresentAndSvrExhausted()) { setLockedTimeRemaining(processor.getLockedException().getTimeRemaining()); - setKeyBackupTokenData(processor.getTokenData()); - } else if (processor.isKbsLocked()) { + setSvrTriesRemaining(processor.getSvrTriesRemaining()); + setSvrAuthCredentials(processor.getSvrAuthCredentials()); + } else if (processor.isRegistrationLockPresentAndSvrExhausted()) { setLockedTimeRemaining(processor.getLockedException().getTimeRemaining()); } }); } public Single verifyCodeAndRegisterAccountWithRegistrationLock(@NonNull String pin) { - TokenData kbsTokenData = Objects.requireNonNull(getKeyBackupCurrentToken()); + SvrAuthCredentialSet authCredentials = Objects.requireNonNull(getSvrAuthCredentials()); - return verifyAccountWithRegistrationLock(pin, kbsTokenData) - .map(r -> new VerifyResponseWithRegistrationLockProcessor(r, kbsTokenData)) + return verifyAccountWithRegistrationLock(pin, authCredentials) + .map(r -> new VerifyResponseWithRegistrationLockProcessor(r, authCredentials)) .flatMap(processor -> { if (processor.hasResult()) { return onVerifySuccessWithRegistrationLock(processor, pin); } else if (processor.wrongPin()) { - TokenData newToken = TokenData.withResponse(kbsTokenData, processor.getTokenResponse()); - return Single.just(new VerifyResponseWithRegistrationLockProcessor(processor.getResponse(), newToken)); + return Single.just(new VerifyResponseWithRegistrationLockProcessor(processor.getResponse(), authCredentials)); } return Single.just(processor); }) .observeOn(AndroidSchedulers.mainThread()) .doOnSuccess(processor -> { if (processor.wrongPin()) { - setKeyBackupTokenData(processor.getTokenData()); + setSvrTriesRemaining(processor.getSvrTriesRemaining()); } }); } protected abstract Single> verifyAccountWithoutRegistrationLock(); - protected abstract Single> verifyAccountWithRegistrationLock(@NonNull String pin, @NonNull TokenData kbsTokenData); + protected abstract Single> verifyAccountWithRegistrationLock(@NonNull String pin, @NonNull SvrAuthCredentialSet svrAuthCredentials); protected abstract Single onVerifySuccess(@NonNull VerifyResponseProcessor processor); diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/ReRegisterWithPinViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/ReRegisterWithPinViewModel.kt index 4d91f54722..9b10098372 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/ReRegisterWithPinViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/ReRegisterWithPinViewModel.kt @@ -4,7 +4,6 @@ import androidx.lifecycle.ViewModel import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.subjects.BehaviorSubject -import org.thoughtcrime.securesms.pin.TokenData /** * Used during re-registration flow when pin entry is required to skip SMS verification. Mostly tracks @@ -19,14 +18,14 @@ class ReRegisterWithPinViewModel : ViewModel() { private val _triesRemaining: BehaviorSubject = BehaviorSubject.createDefault(10) val triesRemaining: Observable = _triesRemaining.observeOn(AndroidSchedulers.mainThread()) - fun updateTokenData(tokenData: TokenData?) { - if (tokenData == null) { + fun updateSvrTriesRemaining(triesRemaining: Int?) { + if (triesRemaining == null) { isLocalVerification = true if (hasIncorrectGuess) { _triesRemaining.onNext((_triesRemaining.value!! - 1).coerceAtLeast(0)) } } else { - _triesRemaining.onNext(tokenData.triesRemaining) + _triesRemaining.onNext(triesRemaining) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java index f0e63d06de..578713ab24 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java @@ -16,9 +16,8 @@ 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.pin.KbsRepository; -import org.thoughtcrime.securesms.pin.KeyBackupSystemWrongPinException; -import org.thoughtcrime.securesms.pin.TokenData; +import org.thoughtcrime.securesms.pin.SvrWrongPinException; +import org.thoughtcrime.securesms.pin.SvrRepository; import org.thoughtcrime.securesms.registration.RegistrationData; import org.thoughtcrime.securesms.registration.RegistrationRepository; import org.thoughtcrime.securesms.registration.RegistrationSessionProcessor; @@ -29,13 +28,13 @@ import org.thoughtcrime.securesms.registration.VerifyResponseWithRegistrationLoc import org.thoughtcrime.securesms.registration.VerifyResponseWithoutKbs; 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.SvrNoDataException; +import org.whispersystems.signalservice.api.kbs.MasterKey; import org.whispersystems.signalservice.api.kbs.PinHashUtil; import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.exceptions.IncorrectCodeException; +import org.whispersystems.signalservice.api.push.exceptions.IncorrectRegistrationRecoveryPasswordException; import org.whispersystems.signalservice.internal.ServiceResponse; -import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse; import org.whispersystems.util.Base64; @@ -67,10 +66,9 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel { public RegistrationViewModel(@NonNull SavedStateHandle savedStateHandle, boolean isReregister, @NonNull VerifyAccountRepository verifyAccountRepository, - @NonNull KbsRepository kbsRepository, @NonNull RegistrationRepository registrationRepository) { - super(savedStateHandle, verifyAccountRepository, kbsRepository, Util.getSecret(18)); + super(savedStateHandle, verifyAccountRepository, Util.getSecret(18)); this.registrationRepository = registrationRepository; @@ -174,14 +172,12 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel { }) .flatMap(verifyAccountWithoutKbsResponse -> { VerifyResponseProcessor processor = new VerifyResponseWithoutKbs(verifyAccountWithoutKbsResponse); - String pin = SignalStore.kbsValues().getPin(); + String pin = SignalStore.svr().getPin(); - if ((processor.isKbsLocked() || processor.registrationLock()) && SignalStore.kbsValues().getRegistrationLockToken() != null && pin != null) { - KbsPinData pinData = new KbsPinData(SignalStore.kbsValues().getOrCreateMasterKey(), SignalStore.kbsValues().getRegistrationLockTokenResponse()); - - return verifyAccountRepository.registerAccount(sessionId, getRegistrationData(), pin, () -> pinData) + if ((processor.isRegistrationLockPresentAndSvrExhausted() || processor.registrationLock()) && SignalStore.svr().getRegistrationLockToken() != null && pin != null) { + return verifyAccountRepository.registerAccount(sessionId, getRegistrationData(), pin, () -> SignalStore.svr().getOrCreateMasterKey()) .map(verifyAccountWithPinResponse -> { - if (verifyAccountWithPinResponse.getResult().isPresent() && verifyAccountWithPinResponse.getResult().get().getKbsData() != null) { + if (verifyAccountWithPinResponse.getResult().isPresent() && verifyAccountWithPinResponse.getResult().get().getMasterKey() != null) { return verifyAccountWithPinResponse; } else { return verifyAccountWithoutKbsResponse; @@ -195,7 +191,7 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel { } @Override - protected Single> verifyAccountWithRegistrationLock(@NonNull String pin, @NonNull TokenData kbsTokenData) { + protected Single> verifyAccountWithRegistrationLock(@NonNull String pin, @NonNull SvrAuthCredentialSet svrAuthCredentials) { final String sessionId = getSessionId(); if (sessionId == null) { throw new IllegalStateException("No valid registration session"); @@ -210,7 +206,7 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel { }) .>flatMap(processor -> { if (processor.isAlreadyVerified() || (processor.hasResult() && processor.isVerified())) { - return verifyAccountRepository.registerAccount(sessionId, getRegistrationData(), pin, () -> Objects.requireNonNull(KbsRepository.restoreMasterKey(pin, kbsTokenData.getEnclave(), kbsTokenData.getBasicAuth(), kbsTokenData.getTokenResponse()))); + return verifyAccountRepository.registerAccount(sessionId, getRegistrationData(), pin, () -> SvrRepository.restoreMasterKeyPreRegistration(svrAuthCredentials, pin)); } else { return Single.just(ServiceResponse.coerceError(processor.getResponse())); } @@ -250,18 +246,17 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel { return updateFcmTokenValue().subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) .onErrorReturnItem("") - .flatMap(s -> verifyReRegisterWithRecoveryPassword(pin, data.pinData)); + .flatMap(s -> verifyReRegisterWithRecoveryPassword(pin, data.masterKey)); } else { - throw new IllegalStateException("Unable to get token or master key"); + throw new IncorrectRegistrationRecoveryPasswordException(); } }) - .onErrorReturn(t -> new VerifyResponseWithRegistrationLockProcessor(ServiceResponse.forUnknownError(t), getKeyBackupCurrentToken())) + .onErrorReturn(t -> new VerifyResponseWithRegistrationLockProcessor(ServiceResponse.forUnknownError(t), getSvrAuthCredentials())) .map(p -> { if (p instanceof VerifyResponseWithRegistrationLockProcessor) { VerifyResponseWithRegistrationLockProcessor lockProcessor = (VerifyResponseWithRegistrationLockProcessor) p; - if (lockProcessor.wrongPin() && lockProcessor.getTokenData() != null) { - TokenData newToken = TokenData.withResponse(lockProcessor.getTokenData(), lockProcessor.getTokenResponse()); - return new VerifyResponseWithRegistrationLockProcessor(lockProcessor.getResponse(), newToken); + if (lockProcessor.wrongPin() && lockProcessor.getSvrTriesRemaining() != null) { + return new VerifyResponseWithRegistrationLockProcessor(lockProcessor.getResponse(), lockProcessor.getSvrAuthCredentials()); } } @@ -277,37 +272,33 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel { @WorkerThread private @NonNull ReRegistrationData verifyReRegisterWithPinInternal(@NonNull String pin) - throws KeyBackupSystemWrongPinException, IOException, KeyBackupSystemNoDataException + throws SvrWrongPinException, IOException, SvrNoDataException { - String localPinHash = SignalStore.kbsValues().getLocalPinHash(); + String localPinHash = SignalStore.svr().getLocalPinHash(); if (hasRecoveryPassword() && localPinHash != null) { 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())); + return ReRegistrationData.canProceed(SignalStore.svr().getOrCreateMasterKey()); } else { - throw new KeyBackupSystemWrongPinException(new TokenResponse(null, null, 0)); + throw new SvrWrongPinException(0); } } else { - TokenData data = getKeyBackupCurrentToken(); - if (data == null) { - Log.w(TAG, "No token data, abort skip flow"); + SvrAuthCredentialSet authCredentials = getSvrAuthCredentials(); + if (authCredentials == null) { + Log.w(TAG, "No SVR auth credentials, abort skip flow"); return ReRegistrationData.cannotProceed(); } - KbsPinData kbsPinData = KbsRepository.restoreMasterKey(pin, data.getEnclave(), data.getBasicAuth(), data.getTokenResponse()); - if (kbsPinData == null || kbsPinData.getMasterKey() == null) { - Log.w(TAG, "No kbs data, abort skip flow"); - return ReRegistrationData.cannotProceed(); - } + MasterKey masterKey = SvrRepository.restoreMasterKeyPreRegistration(authCredentials, pin); - setRecoveryPassword(kbsPinData.getMasterKey().deriveRegistrationRecoveryPassword()); - setKeyBackupTokenData(data); - return ReRegistrationData.canProceed(kbsPinData); + setRecoveryPassword(masterKey.deriveRegistrationRecoveryPassword()); + setSvrTriesRemaining(10); + return ReRegistrationData.canProceed(masterKey); } } - private Single verifyReRegisterWithRecoveryPassword(@NonNull String pin, @NonNull KbsPinData pinData) { + private Single verifyReRegisterWithRecoveryPassword(@NonNull String pin, @NonNull MasterKey masterKey) { RegistrationData registrationData = getRegistrationData(); if (registrationData.getRecoveryPassword() == null) { throw new IllegalStateException("No valid recovery password"); @@ -319,9 +310,10 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel { .map(VerifyResponseWithoutKbs::new) .flatMap(processor -> { if (processor.registrationLock()) { - return verifyAccountRepository.registerAccount(null, registrationData, pin, () -> pinData) + setSvrAuthCredentials(processor.getSvrAuthCredentials()); + return verifyAccountRepository.registerAccount(null, registrationData, pin, () -> masterKey) .onErrorReturn(ServiceResponse::forUnknownError) - .map(r -> new VerifyResponseWithRegistrationLockProcessor(r, getKeyBackupCurrentToken())); + .map(r -> new VerifyResponseWithRegistrationLockProcessor(r, processor.getSvrAuthCredentials())); } else { return Single.just(processor); } @@ -329,14 +321,14 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel { .flatMap(processor -> { if (processor.hasResult()) { VerifyResponse verifyResponse = processor.getResult(); - boolean setRegistrationLockEnabled = verifyResponse.getKbsData() != null; + boolean setRegistrationLockEnabled = verifyResponse.getMasterKey() != null; if (!setRegistrationLockEnabled) { - verifyResponse = new VerifyResponse(processor.getResult().getVerifyAccountResponse(), pinData, pin, verifyResponse.getAciPreKeyCollection(), verifyResponse.getPniPreKeyCollection()); + verifyResponse = new VerifyResponse(processor.getResult().getVerifyAccountResponse(), masterKey, pin, verifyResponse.getAciPreKeyCollection(), verifyResponse.getPniPreKeyCollection()); } return registrationRepository.registerAccount(registrationData, verifyResponse, setRegistrationLockEnabled) - .map(r -> new VerifyResponseWithRegistrationLockProcessor(r, getKeyBackupCurrentToken())); + .map(r -> new VerifyResponseWithRegistrationLockProcessor(r, getSvrAuthCredentials())); } else { return Single.just(processor); } @@ -360,8 +352,8 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel { } private Single checkForValidKbsAuthCredentials() { - final List kbsAuthTokenList = SignalStore.kbsValues().getKbsAuthTokenList(); - List usernamePasswords = kbsAuthTokenList + final List svrAuthTokenList = SignalStore.svr().getAuthTokenList(); + List usernamePasswords = svrAuthTokenList .stream() .limit(10) .map(t -> { @@ -377,23 +369,8 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel { return Single.just(false); } - return registrationRepository.getKbsAuthCredential(getRegistrationData(), usernamePasswords) - .flatMap(p -> { - if (p.getValid() != null) { - return kbsRepository.getToken(p.getValid()) - .flatMap(r -> { - if (r.getResult().isPresent()) { - TokenData tokenData = r.getResult().get(); - setKeyBackupTokenData(tokenData); - return Single.just(tokenData.getTriesRemaining() > 0); - } else { - return Single.just(false); - } - }); - } else { - return Single.just(false); - } - }) + return registrationRepository.getSvrAuthCredential(getRegistrationData(), usernamePasswords) + .flatMap(p -> Single.just(p.getValid() != null)) .onErrorReturnItem(false) .observeOn(AndroidSchedulers.mainThread()); } @@ -432,20 +409,20 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel { } private static class ReRegistrationData { - public boolean canProceed; - public KbsPinData pinData; + public boolean canProceed; + public MasterKey masterKey; - private ReRegistrationData(boolean canProceed, @Nullable KbsPinData pinData) { + private ReRegistrationData(boolean canProceed, @Nullable MasterKey masterKey) { this.canProceed = canProceed; - this.pinData = pinData; + this.masterKey = masterKey; } public static ReRegistrationData cannotProceed() { return new ReRegistrationData(false, null); } - public static ReRegistrationData canProceed(@NonNull KbsPinData pinData) { - return new ReRegistrationData(true, pinData); + public static ReRegistrationData canProceed(@NonNull MasterKey masterKey) { + return new ReRegistrationData(true, masterKey); } } @@ -463,7 +440,6 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel { return modelClass.cast(new RegistrationViewModel(handle, isReregister, new VerifyAccountRepository(ApplicationDependencies.getApplication()), - new KbsRepository(), new RegistrationRepository(ApplicationDependencies.getApplication()))); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/SvrAuthCredentialSet.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/SvrAuthCredentialSet.kt new file mode 100644 index 0000000000..74af655131 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/SvrAuthCredentialSet.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.registration.viewmodel + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import org.whispersystems.signalservice.internal.push.AuthCredentials + +@Parcelize +data class SvrAuthCredentialSet( + private val svr1Credentials: ParcelableAuthCredentials?, + private val svr2Credentials: ParcelableAuthCredentials? +) : Parcelable { + constructor( + svr1Credentials: AuthCredentials?, + svr2Credentials: AuthCredentials? + ) : this(ParcelableAuthCredentials.createOrNull(svr1Credentials), ParcelableAuthCredentials.createOrNull(svr2Credentials)) + + val svr1: AuthCredentials? = svr1Credentials?.credentials() + val svr2: AuthCredentials? = svr2Credentials?.credentials() + + @Parcelize + data class ParcelableAuthCredentials(private val username: String, private val password: String) : Parcelable { + + companion object { + fun createOrNull(creds: AuthCredentials?): ParcelableAuthCredentials? { + return if (creds != null) { + ParcelableAuthCredentials(creds.username(), creds.password()) + } else { + null + } + } + } + + fun credentials(): AuthCredentials { + return AuthCredentials.create(username, password) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java index a0c051a03c..80a5ca93ef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -18,7 +18,6 @@ import org.thoughtcrime.securesms.groups.SelectionLimits; import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver; -import org.whispersystems.signalservice.internal.crypto.PaddingInputStream; import java.io.IOException; import java.util.HashMap; @@ -109,6 +108,7 @@ public final class FeatureFlags { private static final String MAX_ATTACHMENT_COUNT = "android.attachments.maxCount"; private static final String MAX_ATTACHMENT_RECEIVE_SIZE_BYTES = "global.attachments.maxReceiveBytes"; private static final String MAX_ATTACHMENT_SIZE_BYTES = "global.attachments.maxBytes"; + private static final String SVR2_KILLSWITCH = "android.svr2.killSwitch"; /** * We will only store remote values for flags in this set. If you want a flag to be controllable @@ -169,7 +169,8 @@ public final class FeatureFlags { MAX_ATTACHMENT_COUNT, MAX_ATTACHMENT_RECEIVE_SIZE_BYTES, MAX_ATTACHMENT_SIZE_BYTES, - AD_HOC_CALLING + AD_HOC_CALLING, + SVR2_KILLSWITCH ); @VisibleForTesting @@ -235,7 +236,8 @@ public final class FeatureFlags { EDIT_MESSAGE_SEND, MAX_ATTACHMENT_COUNT, MAX_ATTACHMENT_RECEIVE_SIZE_BYTES, - MAX_ATTACHMENT_SIZE_BYTES + MAX_ATTACHMENT_SIZE_BYTES, + SVR2_KILLSWITCH ); /** @@ -243,7 +245,8 @@ public final class FeatureFlags { */ @VisibleForTesting static final Set STICKY = SetUtil.newHashSet( - VERIFY_V2 + VERIFY_V2, + SVR2_KILLSWITCH ); /** @@ -752,6 +755,14 @@ public final class FeatureFlags { return getInteger(MESSAGE_PROCESSOR_DELAY, 300); } + /** + * Whether or not SVR2 should be used at all. Defaults to true. In practice this is reserved as a killswitch. + */ + public static boolean svr2() { + // Despite us always inverting the value, it's important that this defaults to false so that the STICKY property works as intended + return !getBoolean(SVR2_KILLSWITCH, false); + } + private enum VersionFlag { /** The flag is no set */ OFF, diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SupportEmailUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/SupportEmailUtil.java index 11652ba28a..10fe50d257 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SupportEmailUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SupportEmailUtil.java @@ -65,7 +65,7 @@ public final class SupportEmailUtil { "\n" + context.getString(R.string.SupportEmailUtil_signal_package) + " " + getSignalPackage(context) + "\n" + - context.getString(R.string.SupportEmailUtil_registration_lock) + " " + getRegistrationLockEnabled(context) + + context.getString(R.string.SupportEmailUtil_registration_lock) + " " + getRegistrationLockEnabled() + "\n" + context.getString(R.string.SupportEmailUtil_locale) + " " + Locale.getDefault().toString(); } @@ -86,7 +86,7 @@ public final class SupportEmailUtil { return String.format("%s (%s)", BuildConfig.APPLICATION_ID, AppSignatureUtil.getAppSignature(context)); } - private static CharSequence getRegistrationLockEnabled(@NonNull Context context) { - return String.valueOf(TextSecurePreferences.isV1RegistrationLockEnabled(context) || SignalStore.kbsValues().isV2RegistrationLockEnabled()); + private static CharSequence getRegistrationLockEnabled() { + return String.valueOf(SignalStore.svr().isRegistrationLockEnabled()); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java index a0eb7fe926..ecab8b5e4b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -334,14 +334,6 @@ public class TextSecurePreferences { return getStringPreference(context, REGISTRATION_LOCK_PIN_PREF_V1, null); } - public static void clearRegistrationLockV1(@NonNull Context context) { - //noinspection deprecation - getSharedPreferences(context) - .edit() - .remove(REGISTRATION_LOCK_PIN_PREF_V1) - .apply(); - } - /** * @deprecated Use only for migrations to the Key Backup Store registration pinV2. */ diff --git a/app/src/main/protowire/ExternalBackups.proto b/app/src/main/protowire/ExternalBackups.proto index abf7bbc85d..ce6cbb96b1 100644 --- a/app/src/main/protowire/ExternalBackups.proto +++ b/app/src/main/protowire/ExternalBackups.proto @@ -10,6 +10,6 @@ package signal; option java_package = "org.thoughtcrime.securesms.absbackup.protos"; -message KbsAuthToken { +message SvrAuthToken { repeated string tokens = 1; } \ No newline at end of file diff --git a/app/src/main/res/layout/create_kbs_pin_activity.xml b/app/src/main/res/layout/create_kbs_pin_activity.xml index fe96a194d2..85fc45c26a 100644 --- a/app/src/main/res/layout/create_kbs_pin_activity.xml +++ b/app/src/main/res/layout/create_kbs_pin_activity.xml @@ -5,7 +5,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".lock.v2.CreateKbsPinActivity"> + tools:context=".lock.v2.CreateSvrPinActivity"> + tools:context=".lock.v2.SvrMigrationActivity"> @@ -33,7 +33,7 @@ @@ -45,7 +45,7 @@ diff --git a/app/src/test/java/org/thoughtcrime/securesms/registration/v2/PinHashKbsDataTest.java b/app/src/test/java/org/thoughtcrime/securesms/registration/v2/PinHashKbsDataTest.java index 767183f99d..353c14ec55 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/registration/v2/PinHashKbsDataTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/registration/v2/PinHashKbsDataTest.java @@ -42,7 +42,7 @@ public final class PinHashKbsDataTest { for (KbsTestVector vector : getKbsTestVectorList()) { PinHash hashedPin = fromArgon2Hash(vector.getArgon2Hash()); - KbsData kbsData = PinHashUtil.decryptKbsDataIVCipherText(hashedPin, vector.getIvAndCipher()); + KbsData kbsData = PinHashUtil.decryptSvrDataIVCipherText(hashedPin, vector.getIvAndCipher()); assertArrayEquals(vector.getMasterKey(), kbsData.getMasterKey().serialize()); assertArrayEquals(vector.getIvAndCipher(), kbsData.getCipherText()); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/KeyBackupService.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/KeyBackupService.java index 1cda5ed349..4be5ffc8d0 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/KeyBackupService.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/KeyBackupService.java @@ -17,6 +17,7 @@ import org.whispersystems.signalservice.internal.contacts.entities.KeyBackupResp import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; import org.whispersystems.signalservice.internal.keybackup.protos.BackupResponse; import org.whispersystems.signalservice.internal.keybackup.protos.RestoreResponse; +import org.whispersystems.signalservice.internal.push.AuthCredentials; import org.whispersystems.signalservice.internal.push.PushServiceSocket; import org.whispersystems.signalservice.internal.push.RemoteAttestationUtil; import org.whispersystems.signalservice.internal.util.Util; @@ -59,7 +60,7 @@ public class KeyBackupService { public PinChangeSession newPinChangeSession() throws IOException { - return newSession(pushServiceSocket.getKeyBackupServiceAuthorization(), null); + return newSession(pushServiceSocket.getKeyBackupServiceAuthorization().asBasic(), null); } /** @@ -69,7 +70,7 @@ public class KeyBackupService { public PinChangeSession newPinChangeSession(TokenResponse currentToken) throws IOException { - return newSession(pushServiceSocket.getKeyBackupServiceAuthorization(), currentToken); + return newSession(pushServiceSocket.getKeyBackupServiceAuthorization().asBasic(), currentToken); } /** @@ -84,7 +85,7 @@ public class KeyBackupService { /** * Retrieve the authorization token to be used with other requests. */ - public String getAuthorization() throws IOException { + public AuthCredentials getAuthorization() throws IOException { return pushServiceSocket.getKeyBackupServiceAuthorization(); } @@ -99,6 +100,14 @@ public class KeyBackupService { return newSession(authAuthorization, tokenResponse); } + public String getEnclaveName() { + return enclaveName; + } + + public String getMrenclave() { + return mrenclave; + } + private Session newSession(String authorization, TokenResponse currentToken) throws IOException { @@ -123,8 +132,8 @@ public class KeyBackupService { } @Override - public KbsPinData restorePin(PinHash hashedPin) - throws UnauthenticatedResponseException, IOException, KeyBackupServicePinException, KeyBackupSystemNoDataException, InvalidKeyException + public SvrPinData restorePin(PinHash hashedPin) + throws UnauthenticatedResponseException, IOException, KeyBackupServicePinException, SvrNoDataException, InvalidKeyException { int attempt = 0; SecureRandom random = new SecureRandom(); @@ -156,8 +165,8 @@ public class KeyBackupService { } } - private KbsPinData restorePin(PinHash hashedPin, TokenResponse token) - throws UnauthenticatedResponseException, IOException, TokenException, KeyBackupSystemNoDataException, InvalidKeyException + private SvrPinData restorePin(PinHash hashedPin, TokenResponse token) + throws UnauthenticatedResponseException, IOException, TokenException, SvrNoDataException, InvalidKeyException { try { final int remainingTries = token.getTries(); @@ -173,9 +182,9 @@ public class KeyBackupService { Log.i(TAG, "Restore " + status.getStatus()); switch (status.getStatus()) { case OK: - KbsData kbsData = PinHashUtil.decryptKbsDataIVCipherText(hashedPin, status.getData().toByteArray()); + KbsData kbsData = PinHashUtil.decryptSvrDataIVCipherText(hashedPin, status.getData().toByteArray()); MasterKey masterKey = kbsData.getMasterKey(); - return new KbsPinData(masterKey, nextToken); + return new SvrPinData(masterKey, nextToken); case PIN_MISMATCH: Log.i(TAG, "Restore PIN_MISMATCH"); throw new KeyBackupServicePinException(nextToken); @@ -187,7 +196,7 @@ public class KeyBackupService { throw new TokenException(nextToken, canRetry); case MISSING: Log.i(TAG, "Restore OK! No data though"); - throw new KeyBackupSystemNoDataException(); + throw new SvrNoDataException(); case NOT_YET_VALID: throw new UnauthenticatedResponseException("Key is not valid yet, clock mismatch"); default: @@ -207,14 +216,14 @@ public class KeyBackupService { } @Override - public KbsPinData setPin(PinHash pinHash, MasterKey masterKey) throws IOException, UnauthenticatedResponseException { + public SvrPinData setPin(PinHash pinHash, MasterKey masterKey) throws IOException, UnauthenticatedResponseException { KbsData newKbsData = PinHashUtil.createNewKbsData(pinHash, masterKey); TokenResponse tokenResponse = putKbsData(newKbsData.getKbsAccessKey(), newKbsData.getCipherText(), enclaveName, currentToken); - return new KbsPinData(masterKey, tokenResponse); + return new SvrPinData(masterKey, tokenResponse); } @Override @@ -275,13 +284,13 @@ public class KeyBackupService { public interface RestoreSession extends HashSession { - KbsPinData restorePin(PinHash hashedPin) - throws UnauthenticatedResponseException, IOException, KeyBackupServicePinException, KeyBackupSystemNoDataException, InvalidKeyException; + SvrPinData restorePin(PinHash hashedPin) + throws UnauthenticatedResponseException, IOException, KeyBackupServicePinException, SvrNoDataException, InvalidKeyException; } public interface PinChangeSession extends HashSession { /** Creates a PIN. Does nothing to registration lock. */ - KbsPinData setPin(PinHash hashedPin, MasterKey masterKey) throws IOException, UnauthenticatedResponseException; + SvrPinData setPin(PinHash hashedPin, MasterKey masterKey) throws IOException, UnauthenticatedResponseException; /** Removes the PIN data from KBS. */ void removePin() throws IOException, UnauthenticatedResponseException; diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/KeyBackupSystemNoDataException.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/KeyBackupSystemNoDataException.java deleted file mode 100644 index a49bc73690..0000000000 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/KeyBackupSystemNoDataException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.whispersystems.signalservice.api; - -public final class KeyBackupSystemNoDataException extends Exception { - - public KeyBackupSystemNoDataException() { - } -} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java index 37576b8d9d..ed3a0f9584 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java @@ -549,6 +549,21 @@ public class SignalServiceAccountManager { return writeStorageRecords(storageKey, manifest, inserts, deletes, false); } + + /** + * Enables registration lock for this account. + */ + public void enableRegistrationLock(MasterKey masterKey) throws IOException { + pushServiceSocket.setRegistrationLockV2(masterKey.deriveRegistrationLock()); + } + + /** + * Disables registration lock for this account. + */ + public void disableRegistrationLock() throws IOException { + pushServiceSocket.disableRegistrationLockV2(); + } + /** * @return If there was a conflict, the latest {@link SignalStorageManifest}. Otherwise absent. */ diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SvrNoDataException.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SvrNoDataException.java new file mode 100644 index 0000000000..d855e738ef --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SvrNoDataException.java @@ -0,0 +1,7 @@ +package org.whispersystems.signalservice.api; + +public final class SvrNoDataException extends Exception { + + public SvrNoDataException() { + } +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/KbsPinData.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SvrPinData.java similarity index 86% rename from libsignal/service/src/main/java/org/whispersystems/signalservice/api/KbsPinData.java rename to libsignal/service/src/main/java/org/whispersystems/signalservice/api/SvrPinData.java index 58df4334b9..a3b7e25bef 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/KbsPinData.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SvrPinData.java @@ -3,13 +3,13 @@ package org.whispersystems.signalservice.api; import org.whispersystems.signalservice.api.kbs.MasterKey; import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; -public final class KbsPinData { +public final class SvrPinData { private final MasterKey masterKey; private final TokenResponse tokenResponse; // Visible for testing - public KbsPinData(MasterKey masterKey, TokenResponse tokenResponse) { + public SvrPinData(MasterKey masterKey, TokenResponse tokenResponse) { this.masterKey = masterKey; this.tokenResponse = tokenResponse; } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/AccountAttributes.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/AccountAttributes.kt index 584b1efe41..fba36c3122 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/AccountAttributes.kt +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/AccountAttributes.kt @@ -16,7 +16,6 @@ class AccountAttributes @JsonCreator constructor( @JsonProperty val voice: Boolean, @JsonProperty val video: Boolean, @JsonProperty val fetchesMessages: Boolean, - @JsonProperty val pin: String?, @JsonProperty val registrationLock: String?, @JsonProperty val unidentifiedAccessKey: ByteArray?, @JsonProperty val unrestrictedUnidentifiedAccess: Boolean, @@ -30,7 +29,6 @@ class AccountAttributes @JsonCreator constructor( signalingKey: String?, registrationId: Int, fetchesMessages: Boolean, - pin: String?, registrationLock: String?, unidentifiedAccessKey: ByteArray?, unrestrictedUnidentifiedAccess: Boolean, @@ -45,7 +43,6 @@ class AccountAttributes @JsonCreator constructor( voice = true, video = true, fetchesMessages = fetchesMessages, - pin = pin, registrationLock = registrationLock, unidentifiedAccessKey = unidentifiedAccessKey, unrestrictedUnidentifiedAccess = unrestrictedUnidentifiedAccess, diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/kbs/MasterKey.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/kbs/MasterKey.java index 28d1481964..5e6f43354b 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/kbs/MasterKey.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/kbs/MasterKey.java @@ -2,6 +2,7 @@ package org.whispersystems.signalservice.api.kbs; import org.whispersystems.signalservice.api.storage.StorageKey; import org.whispersystems.signalservice.internal.util.Hex; +import org.whispersystems.util.Base64; import org.whispersystems.util.StringUtil; import java.security.SecureRandom; @@ -32,7 +33,7 @@ public final class MasterKey { } public String deriveRegistrationRecoveryPassword() { - return Hex.toStringCondensed(derive("Registration Recovery")); + return Base64.encodeBytes(derive("Registration Recovery")); } public StorageKey deriveStorageServiceKey() { @@ -61,6 +62,6 @@ public final class MasterKey { @Override public String toString() { - return "MasterKey(HashCode: " + hashCode() + ")"; + return "MasterKey(xxx)"; } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/kbs/PinHashUtil.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/kbs/PinHashUtil.kt index b8713b9e10..3e58ca7314 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/kbs/PinHashUtil.kt +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/kbs/PinHashUtil.kt @@ -52,7 +52,7 @@ object PinHashUtil { */ @JvmStatic @Throws(InvalidCiphertextException::class) - fun decryptKbsDataIVCipherText(pinHash: PinHash, ivc: ByteArray?): KbsData { + fun decryptSvrDataIVCipherText(pinHash: PinHash, ivc: ByteArray?): KbsData { val masterKey = HmacSIV.decrypt(pinHash.encryptionKey(), ivc) return KbsData(MasterKey(masterKey), pinHash.accessKey(), ivc) } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecovery.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecovery.kt index e6089f7fba..4235655cc4 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecovery.kt +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecovery.kt @@ -5,10 +5,10 @@ package org.whispersystems.signalservice.api.svr -import io.reactivex.rxjava3.core.Single import org.whispersystems.signalservice.api.kbs.MasterKey import org.whispersystems.signalservice.internal.push.AuthCredentials import java.io.IOException +import kotlin.jvm.Throws interface SecureValueRecovery { /** @@ -26,6 +26,13 @@ interface SecureValueRecovery { */ fun setPin(userPin: String, masterKey: MasterKey): PinChangeSession + /** + * Resumes a PIN change session that you previously started via [setPin] using serialized data obtained via + * [PinChangeSession.serialize]. The provided [userPin] and [masterKey] will be checked against the + * serialized [PinChangeSession], and if the data no longer matches, a new [PinChangeSession] will be created. + */ + fun resumePinChangeSession(userPin: String, masterKey: MasterKey, serializedChangeSession: String): PinChangeSession + /** * Restores the user's SVR data from the service. Intended to be called in the situation where the user is not yet registered. * Currently, this will only happen during a reglock challenge. When in this state, the user is not registered, and will instead @@ -33,26 +40,33 @@ interface SecureValueRecovery { * * If the user is already registered, use [restoreDataPostRegistration] */ - fun restoreDataPreRegistration(authorization: AuthCredentials, userPin: String): Single + fun restoreDataPreRegistration(authorization: AuthCredentials, userPin: String): RestoreResponse /** * Restores data from SVR. Only intended to be called if the user is already registered. If the user is not yet registered, use [restoreDataPreRegistration] */ - fun restoreDataPostRegistration(userPin: String): Single + fun restoreDataPostRegistration(userPin: String): RestoreResponse /** * Deletes the user's SVR data from the service. */ - fun deleteData(): Single + fun deleteData(): DeleteResponse + + /** + * Retrieves an auth credential that could be used to talk with the service. + */ + @Throws(IOException::class) + fun authorization(): AuthCredentials interface PinChangeSession { - fun execute(): Single + fun execute(): BackupResponse + fun serialize(): String } /** Response for setting a PIN. */ sealed class BackupResponse { /** Operation completed successfully. */ - data class Success(val masterKey: MasterKey) : BackupResponse() + data class Success(val masterKey: MasterKey, val authorization: AuthCredentials) : BackupResponse() /** The operation failed because the server was unable to expose the backup data we created. There is no further action that can be taken besides logging the error and treating it as a success. */ object ExposeFailure : BackupResponse() @@ -73,7 +87,7 @@ interface SecureValueRecovery { /** Response for restoring data with you PIN. */ sealed class RestoreResponse { /** Operation completed successfully. Includes the restored data. */ - data class Success(val masterKey: MasterKey) : RestoreResponse() + data class Success(val masterKey: MasterKey, val authorization: AuthCredentials) : RestoreResponse() /** No data was found for this user. Could mean that none ever existed, or that the service deleted the data after too many incorrect PIN guesses. */ object Missing : RestoreResponse() diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecoveryV1.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecoveryV1.kt index 413b419282..a0caec7852 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecoveryV1.kt +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecoveryV1.kt @@ -5,12 +5,11 @@ package org.whispersystems.signalservice.api.svr -import io.reactivex.rxjava3.core.Single import org.signal.libsignal.svr2.PinHash -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.SvrNoDataException +import org.whispersystems.signalservice.api.SvrPinData import org.whispersystems.signalservice.api.kbs.MasterKey import org.whispersystems.signalservice.api.kbs.PinHashUtil import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException @@ -21,88 +20,105 @@ import org.whispersystems.signalservice.api.svr.SecureValueRecovery.RestoreRespo import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException import org.whispersystems.signalservice.internal.push.AuthCredentials import java.io.IOException +import kotlin.jvm.Throws /** * An implementation of the [SecureValueRecovery] interface backed by the [KeyBackupService]. */ class SecureValueRecoveryV1(private val kbs: KeyBackupService) : SecureValueRecovery { + companion object { + const val TAG = "SVR1" + } + override fun setPin(userPin: String, masterKey: MasterKey): PinChangeSession { return Svr1PinChangeSession(userPin, masterKey) } - override fun restoreDataPreRegistration(authorization: AuthCredentials, userPin: String): Single { - return restoreData(Single.just(authorization.asBasic()), userPin) + override fun resumePinChangeSession(userPin: String, masterKey: MasterKey, serializedChangeSession: String): PinChangeSession { + return setPin(userPin, masterKey) } - override fun restoreDataPostRegistration(userPin: String): Single { - return restoreData(Single.fromCallable { kbs.authorization }, userPin) + override fun restoreDataPreRegistration(authorization: AuthCredentials, userPin: String): RestoreResponse { + return restoreData({ authorization }, userPin) } - override fun deleteData(): Single { - return Single.fromCallable { - try { - kbs.newPinChangeSession().removePin() - DeleteResponse.Success - } catch (e: UnauthenticatedResponseException) { - DeleteResponse.ApplicationError(e) - } catch (e: NonSuccessfulResponseCodeException) { - when (e.code) { - 404 -> DeleteResponse.EnclaveNotFound - 508 -> DeleteResponse.ServerRejected - else -> DeleteResponse.NetworkError(e) - } - } catch (e: IOException) { - DeleteResponse.NetworkError(e) + override fun restoreDataPostRegistration(userPin: String): RestoreResponse { + return restoreData({ kbs.authorization }, userPin) + } + + override fun deleteData(): DeleteResponse { + return try { + kbs.newPinChangeSession().removePin() + DeleteResponse.Success + } catch (e: UnauthenticatedResponseException) { + DeleteResponse.ApplicationError(e) + } catch (e: NonSuccessfulResponseCodeException) { + when (e.code) { + 404 -> DeleteResponse.EnclaveNotFound + 508 -> DeleteResponse.ServerRejected + else -> DeleteResponse.NetworkError(e) } + } catch (e: IOException) { + DeleteResponse.NetworkError(e) } } - private fun restoreData(authorization: Single, userPin: String): Single { - return authorization - .flatMap { auth -> - Single.fromCallable { - try { - val session = kbs.newRegistrationSession(auth, null) - val pinHash: PinHash = PinHashUtil.hashPin(userPin, session.hashSalt()) + @Throws(IOException::class) + override fun authorization(): AuthCredentials { + return kbs.authorization + } - val data: KbsPinData = session.restorePin(pinHash) - RestoreResponse.Success(data.masterKey) - } catch (e: KeyBackupSystemNoDataException) { - RestoreResponse.Missing - } catch (e: KeyBackupServicePinException) { - RestoreResponse.PinMismatch(e.triesRemaining) - } catch (e: IOException) { - RestoreResponse.NetworkError(e) - } - } - } + override fun toString(): String { + return "SVR1::${kbs.enclaveName}::${kbs.mrenclave}" + } + + private fun restoreData(fetchAuthorization: () -> AuthCredentials, userPin: String): RestoreResponse { + return try { + val authorization: AuthCredentials = fetchAuthorization() + val session = kbs.newRegistrationSession(authorization.asBasic(), null) + val pinHash: PinHash = PinHashUtil.hashPin(userPin, session.hashSalt()) + + val data: SvrPinData = session.restorePin(pinHash) + RestoreResponse.Success(data.masterKey, authorization) + } catch (e: SvrNoDataException) { + RestoreResponse.Missing + } catch (e: KeyBackupServicePinException) { + RestoreResponse.PinMismatch(e.triesRemaining) + } catch (e: IOException) { + RestoreResponse.NetworkError(e) + } catch (e: Exception) { + RestoreResponse.ApplicationError(e) + } } inner class Svr1PinChangeSession( private val userPin: String, private val masterKey: MasterKey ) : PinChangeSession { - override fun execute(): Single { - return Single.fromCallable { - try { - val session = kbs.newPinChangeSession() - val pinHash: PinHash = PinHashUtil.hashPin(userPin, session.hashSalt()) + override fun execute(): BackupResponse { + return try { + val session = kbs.newPinChangeSession() + val pinHash: PinHash = PinHashUtil.hashPin(userPin, session.hashSalt()) - val data: KbsPinData = session.setPin(pinHash, masterKey) - BackupResponse.Success(data.masterKey) - } catch (e: UnauthenticatedResponseException) { - BackupResponse.ApplicationError(e) - } catch (e: NonSuccessfulResponseCodeException) { - when (e.code) { - 404 -> BackupResponse.EnclaveNotFound - 508 -> BackupResponse.ServerRejected - else -> BackupResponse.NetworkError(e) - } - } catch (e: IOException) { - BackupResponse.NetworkError(e) + val data: SvrPinData = session.setPin(pinHash, masterKey) + BackupResponse.Success(data.masterKey, kbs.authorization) + } catch (e: UnauthenticatedResponseException) { + BackupResponse.ApplicationError(e) + } catch (e: NonSuccessfulResponseCodeException) { + when (e.code) { + 404 -> BackupResponse.EnclaveNotFound + 508 -> BackupResponse.ServerRejected + else -> BackupResponse.NetworkError(e) } + } catch (e: IOException) { + BackupResponse.NetworkError(e) } } + + /** No real need to serialize */ + override fun serialize(): String { + return "" + } } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecoveryV2.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecoveryV2.kt index b31ca0e1b7..6f34e43cab 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecoveryV2.kt +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecoveryV2.kt @@ -1,8 +1,10 @@ package org.whispersystems.signalservice.api.svr -import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.schedulers.Schedulers +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import com.fasterxml.jackson.databind.annotation.JsonSerialize import okio.ByteString.Companion.toByteString +import org.signal.libsignal.svr2.PinHash import org.signal.svr2.proto.BackupRequest import org.signal.svr2.proto.DeleteRequest import org.signal.svr2.proto.ExposeRequest @@ -20,7 +22,10 @@ import org.whispersystems.signalservice.api.svr.SecureValueRecovery.RestoreRespo import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration import org.whispersystems.signalservice.internal.push.AuthCredentials import org.whispersystems.signalservice.internal.push.PushServiceSocket +import org.whispersystems.signalservice.internal.util.Hex +import org.whispersystems.signalservice.internal.util.JsonUtil import java.io.IOException +import kotlin.jvm.Throws import org.signal.svr2.proto.BackupResponse as ProtoBackupResponse import org.signal.svr2.proto.ExposeResponse as ProtoExposeResponse import org.signal.svr2.proto.RestoreResponse as ProtoRestoreResponse @@ -34,113 +39,106 @@ class SecureValueRecoveryV2( private val pushServiceSocket: PushServiceSocket ) : SecureValueRecovery { - /** - * Begins a PIN change. - * - * Under the hood, setting a PIN is a two-phase process. This is abstracted through the [PinChangeSession]. - * To use it, simply call [PinChangeSession.execute], which will return the result of the operation. - * If the operation is not successful and warrants a retry, it is extremely important to use the same [PinChangeSession]. - * - * Do not have any automated retry system that calls [setPin] unconditionally. Always reuse the same [PinChangeSession] - * for as long as it is still valid (i.e. as long as you're still trying to set the same PIN). - * - * @param pin The user-specified PIN. - * @param masterKey The data to set on SVR. - */ override fun setPin(userPin: String, masterKey: MasterKey): PinChangeSession { return Svr2PinChangeSession(userPin, masterKey) } - /** - * Restores the user's SVR data from the service. Intended to be called in the situation where the user is not yet registered. - * Currently, this will only happen during a reglock challenge. When in this state, the user is not registered, and will instead - * be provided credentials in a service response to give the user an opportunity to restore SVR data and generate the reglock proof. - * - * If the user is already registered, use [restoreDataPostRegistration] - */ - override fun restoreDataPreRegistration(authorization: AuthCredentials, userPin: String): Single { - return restoreData(Single.just(authorization), userPin) + override fun resumePinChangeSession(userPin: String, masterKey: MasterKey, serializedChangeSession: String): PinChangeSession { + val data: Svr2SessionData = JsonUtil.fromJson(serializedChangeSession, Svr2SessionData::class.java) + + if (data.userPin == userPin && data.masterKey == masterKey) { + return Svr2PinChangeSession(data.userPin, data.masterKey, data.setupComplete) + } else { + return setPin(userPin, masterKey) + } } - /** - * Restores data from SVR. Only intended to be called if the user is already registered. If the user is not yet registered, use [restoreDataPreRegistration] - */ - override fun restoreDataPostRegistration(userPin: String): Single { - return restoreData(getAuthorization(), userPin) + override fun restoreDataPreRegistration(authorization: AuthCredentials, userPin: String): RestoreResponse { + return restoreData({ authorization }, userPin) } - /** - * Deletes the user's SVR data from the service. - */ - override fun deleteData(): Single { - val request: (Svr2PinHasher) -> Request = { Request(delete = DeleteRequest()) } - - return getAuthorization() - .flatMap { auth -> Svr2Socket(serviceConfiguration, mrEnclave).makeRequest(auth, request) } - .map { DeleteResponse.Success as DeleteResponse } - .onErrorReturn { throwable -> - when (throwable) { - is NonSuccessfulResponseCodeException -> DeleteResponse.ApplicationError(throwable) - is IOException -> DeleteResponse.NetworkError(throwable) - else -> DeleteResponse.ApplicationError(throwable) - } - } - .subscribeOn(Schedulers.io()) + override fun restoreDataPostRegistration(userPin: String): RestoreResponse { + return restoreData({ authorization() }, userPin) } - private fun restoreData(authorization: Single, userPin: String): Single { + override fun deleteData(): DeleteResponse { + val request = Request(delete = DeleteRequest()) + + return try { + val authorization: AuthCredentials = authorization() + + // noinspection CheckResult The only possible result is a successful one + Svr2Socket(serviceConfiguration, mrEnclave).makeRequest(authorization, request) + + DeleteResponse.Success + } catch (e: NonSuccessfulResponseCodeException) { + DeleteResponse.ApplicationError(e) + } catch (e: IOException) { + DeleteResponse.NetworkError(e) + } catch (e: Exception) { + DeleteResponse.ApplicationError(e) + } + } + + @Throws(IOException::class) + override fun authorization(): AuthCredentials { + return pushServiceSocket.svr2Authorization + } + + override fun toString(): String { + return "SVR2::$mrEnclave" + } + + private fun restoreData(fetchAuth: () -> AuthCredentials, userPin: String): RestoreResponse { val normalizedPin: ByteArray = PinHashUtil.normalize(userPin) - return authorization - .flatMap { auth -> - Svr2Socket(serviceConfiguration, mrEnclave).makeRequest(auth) { pinHasher -> - val pinHash = pinHasher.hash(normalizedPin) + return try { + val authorization: AuthCredentials = fetchAuth() - Request( - restore = RestoreRequest( - pin = pinHash.accessKey().toByteString() - ) + val response = Svr2Socket(serviceConfiguration, mrEnclave).makeRequest( + authorization = fetchAuth(), + clientRequest = Request( + restore = RestoreRequest( + pin = PinHash.svr2(normalizedPin, authorization.username(), Hex.fromStringCondensed(mrEnclave)).accessKey().toByteString() ) - } - } - .map { (response, pinHasher) -> - when (response.restore?.status) { - ProtoRestoreResponse.Status.OK -> { - val ciphertext: ByteArray = response.restore.data_.toByteArray() - try { - val pinHash = pinHasher.hash(normalizedPin) - val masterKey: MasterKey = PinHashUtil.decryptKbsDataIVCipherText(pinHash, ciphertext).masterKey - RestoreResponse.Success(masterKey) - } catch (e: InvalidCiphertextException) { - RestoreResponse.ApplicationError(e) - } - } - ProtoRestoreResponse.Status.MISSING -> { - RestoreResponse.Missing - } - ProtoRestoreResponse.Status.PIN_MISMATCH -> { - RestoreResponse.PinMismatch(response.restore.tries) - } - ProtoRestoreResponse.Status.REQUEST_INVALID -> { - RestoreResponse.ApplicationError(InvalidRequestException("RestoreResponse returned status code for REQUEST_INVALID")) - } - else -> { - RestoreResponse.ApplicationError(IllegalStateException("Unknown status: ${response.backup?.status}")) - } - } - } - .onErrorReturn { throwable -> - when (throwable) { - is NonSuccessfulResponseCodeException -> RestoreResponse.ApplicationError(throwable) - is IOException -> RestoreResponse.NetworkError(throwable) - else -> RestoreResponse.ApplicationError(throwable) - } - } - .subscribeOn(Schedulers.io()) - } + ) + ) - private fun getAuthorization(): Single { - return Single.fromCallable { pushServiceSocket.svr2Authorization } + when (response.restore?.status) { + ProtoRestoreResponse.Status.OK -> { + val ciphertext: ByteArray = response.restore.data_.toByteArray() + try { + val pinHash = PinHash.svr2(normalizedPin, authorization.username(), Hex.fromStringCondensed(mrEnclave)) + val masterKey: MasterKey = PinHashUtil.decryptSvrDataIVCipherText(pinHash, ciphertext).masterKey + RestoreResponse.Success(masterKey, authorization) + } catch (e: InvalidCiphertextException) { + RestoreResponse.ApplicationError(e) + } + } + + ProtoRestoreResponse.Status.MISSING -> { + RestoreResponse.Missing + } + + ProtoRestoreResponse.Status.PIN_MISMATCH -> { + RestoreResponse.PinMismatch(response.restore.tries) + } + + ProtoRestoreResponse.Status.REQUEST_INVALID -> { + RestoreResponse.ApplicationError(InvalidRequestException("RestoreResponse returned status code for REQUEST_INVALID")) + } + + else -> { + RestoreResponse.ApplicationError(IllegalStateException("Unknown status: ${response.backup?.status}")) + } + } + } catch (e: NonSuccessfulResponseCodeException) { + RestoreResponse.ApplicationError(e) + } catch (e: IOException) { + RestoreResponse.NetworkError(e) + } catch (e: Exception) { + RestoreResponse.ApplicationError(e) + } } /** @@ -155,90 +153,89 @@ class SecureValueRecoveryV2( * proper bookkeeping. */ inner class Svr2PinChangeSession( + @JsonProperty("user_pin") val userPin: String, + + @JsonProperty("master_key") + @JsonSerialize(using = JsonUtil.MasterKeySerializer::class) + @JsonDeserialize(using = JsonUtil.MasterKeyDeserializer::class) val masterKey: MasterKey, + + @JsonProperty("setup_complete") private var setupComplete: Boolean = false ) : PinChangeSession { /** * Performs the PIN change operation. This is safe to call repeatedly if you get back a retryable error. */ - override fun execute(): Single { + override fun execute(): BackupResponse { val normalizedPin: ByteArray = PinHashUtil.normalize(userPin) - return getAuthorization() - .flatMap { auth -> - if (setupComplete) { - Single.just(auth to ProtoBackupResponse(status = ProtoBackupResponse.Status.OK)) - } else { - getBackupResponse(auth, normalizedPin).map { auth to it } + return try { + val authorization: AuthCredentials = authorization() + val response: ProtoBackupResponse = if (setupComplete) { + ProtoBackupResponse(status = ProtoBackupResponse.Status.OK) + } else { + getBackupResponse(authorization, normalizedPin) + }.also { + setupComplete = true + } + + when (response.status) { + ProtoBackupResponse.Status.OK -> { + getExposeResponse(authorization, normalizedPin) + } + ProtoBackupResponse.Status.REQUEST_INVALID -> { + BackupResponse.ApplicationError(InvalidRequestException("BackupResponse returned status code for REQUEST_INVALID")) + } + else -> { + BackupResponse.ApplicationError(IllegalStateException("Unknown status: ${response.status}")) } } - .doOnSuccess { (_, response) -> - if (response.status == ProtoBackupResponse.Status.OK) { - setupComplete = true - } - } - .flatMap { (auth, response) -> - when (response.status) { - ProtoBackupResponse.Status.OK -> { - getExposeResponse(auth, normalizedPin) - } - ProtoBackupResponse.Status.REQUEST_INVALID -> { - Single.just(BackupResponse.ApplicationError(InvalidRequestException("BackupResponse returned status code for REQUEST_INVALID"))) - } - else -> { - Single.just(BackupResponse.ApplicationError(IllegalStateException("Unknown status: ${response.status}"))) - } - } - } - .onErrorReturn { throwable -> - when (throwable) { - is NonSuccessfulResponseCodeException -> BackupResponse.ApplicationError(throwable) - is IOException -> BackupResponse.NetworkError(throwable) - else -> BackupResponse.ApplicationError(throwable) - } - } - .subscribeOn(Schedulers.io()) + } catch (e: NonSuccessfulResponseCodeException) { + BackupResponse.ApplicationError(e) + } catch (e: IOException) { + BackupResponse.NetworkError(e) + } catch (e: Exception) { + BackupResponse.ApplicationError(e) + } } - private fun getBackupResponse(authorization: AuthCredentials, normalizedPin: ByteArray): Single { - val request: (Svr2PinHasher) -> Request = { pinHasher -> - val hashedPin = pinHasher.hash(normalizedPin) - val data = PinHashUtil.createNewKbsData(hashedPin, masterKey) + override fun serialize(): String { + return JsonUtil.toJson(Svr2SessionData(userPin, masterKey, setupComplete)) + } - Request( - backup = BackupRequest( - pin = data.kbsAccessKey.toByteString(), - data_ = data.cipherText.toByteString(), - maxTries = 10 - ) + private fun getBackupResponse(authorization: AuthCredentials, normalizedPin: ByteArray): ProtoBackupResponse { + val hashedPin = PinHash.svr2(normalizedPin, authorization.username(), Hex.fromStringCondensed(mrEnclave)) + val data = PinHashUtil.createNewKbsData(hashedPin, masterKey) + val request = Request( + backup = BackupRequest( + pin = data.kbsAccessKey.toByteString(), + data_ = data.cipherText.toByteString(), + maxTries = 10 ) - } + ) return Svr2Socket(serviceConfiguration, mrEnclave) .makeRequest(authorization, request) - .map { (response, _) -> response.backup ?: throw IllegalStateException("Backup response not set!") } + .let { response -> response.backup ?: throw IllegalStateException("Backup response not set!") } } - private fun getExposeResponse(authorization: AuthCredentials, normalizedPin: ByteArray): Single { - val request: (Svr2PinHasher) -> Request = { pinHasher -> - val hashedPin = pinHasher.hash(normalizedPin) - val data = PinHashUtil.createNewKbsData(hashedPin, masterKey) - - Request( - expose = ExposeRequest( - data_ = data.cipherText.toByteString() - ) + private fun getExposeResponse(authorization: AuthCredentials, normalizedPin: ByteArray): BackupResponse { + val hashedPin = PinHash.svr2(normalizedPin, authorization.username(), Hex.fromStringCondensed(mrEnclave)) + val data = PinHashUtil.createNewKbsData(hashedPin, masterKey) + val request = Request( + expose = ExposeRequest( + data_ = data.cipherText.toByteString() ) - } + ) return Svr2Socket(serviceConfiguration, mrEnclave) .makeRequest(authorization, request) - .map { (response, _) -> + .let { response -> when (response.expose?.status) { ProtoExposeResponse.Status.OK -> { - BackupResponse.Success(masterKey) + BackupResponse.Success(masterKey, authorization) } ProtoExposeResponse.Status.ERROR -> { BackupResponse.ExposeFailure @@ -250,4 +247,17 @@ class SecureValueRecoveryV2( } } } + + data class Svr2SessionData( + @JsonProperty("user_pin") + val userPin: String, + + @JsonProperty("master_key") + @JsonSerialize(using = JsonUtil.MasterKeySerializer::class) + @JsonDeserialize(using = JsonUtil.MasterKeyDeserializer::class) + val masterKey: MasterKey, + + @JsonProperty("setup_complete") + val setupComplete: Boolean = false + ) } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/svr/Svr2Socket.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/svr/Svr2Socket.kt index c96e5baaef..3f12c69f78 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/svr/Svr2Socket.kt +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/svr/Svr2Socket.kt @@ -1,7 +1,5 @@ package org.whispersystems.signalservice.api.svr -import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.core.SingleEmitter import okhttp3.ConnectionSpec import okhttp3.OkHttpClient import okhttp3.Request @@ -28,11 +26,13 @@ import java.io.IOException import java.security.KeyManagementException import java.security.NoSuchAlgorithmException import java.time.Instant +import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicReference import javax.net.ssl.SSLContext import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509TrustManager +import kotlin.jvm.Throws import okhttp3.Response as OkHttpResponse import org.signal.svr2.proto.Request as Svr2Request import org.signal.svr2.proto.Response as Svr2Response @@ -47,41 +47,39 @@ internal class Svr2Socket( private val svr2Url: SignalSvr2Url = chooseUrl(configuration.signalSvr2Urls) private val okhttp: OkHttpClient = buildOkHttpClient(configuration, svr2Url) - fun makeRequest(authorization: AuthCredentials, clientRequest: (Svr2PinHasher) -> Svr2Request): Single { - return Single.create { emitter -> - val openRequest: Request.Builder = Request.Builder() - .url("${svr2Url.url}/v1/$mrEnclave") - .addHeader("Authorization", authorization.asBasic()) + @Throws(IOException::class) + fun makeRequest(authorization: AuthCredentials, clientRequest: Svr2Request): Svr2Response { + val openRequest: Request.Builder = Request.Builder() + .url("${svr2Url.url}/v1/$mrEnclave") + .addHeader("Authorization", authorization.asBasic()) - if (svr2Url.hostHeader.isPresent) { - openRequest.addHeader("Host", svr2Url.hostHeader.get()) - Log.w(TAG, "Using alternate host: ${svr2Url.hostHeader.get()}") - } - - val webSocket = okhttp.newWebSocket( - openRequest.build(), - SvrWebSocketListener( - authorization = authorization, - mrEnclave = mrEnclave, - clientRequest = clientRequest, - emitter = emitter - ) - ) - - emitter.setCancellable { webSocket.close(1000, "OK") } + if (svr2Url.hostHeader.isPresent) { + openRequest.addHeader("Host", svr2Url.hostHeader.get()) + Log.w(TAG, "Using alternate host: ${svr2Url.hostHeader.get()}") } + + val listener = SvrWebSocketListener( + mrEnclave = mrEnclave, + clientRequest = clientRequest + ) + + okhttp.newWebSocket(openRequest.build(), listener) + + return listener.blockAndWaitForResult() } private class SvrWebSocketListener( - private val authorization: AuthCredentials, private val mrEnclave: String, - private val clientRequest: (Svr2PinHasher) -> Svr2Request, - private val emitter: SingleEmitter + private val clientRequest: Svr2Request ) : WebSocketListener() { private val stage = AtomicReference(Stage.WAITING_TO_INITIALIZE) private lateinit var client: Svr2Client - private lateinit var pinHasher: Svr2PinHasher + + private val latch: CountDownLatch = CountDownLatch(1) + + private var response: Svr2Response? = null + private var exception: IOException? = null override fun onOpen(webSocket: WebSocket, response: OkHttpResponse) { Log.d(TAG, "[onOpen]") @@ -99,7 +97,6 @@ internal class Svr2Socket( Stage.WAITING_FOR_CONNECTION -> { val mrEnclave: ByteArray = Hex.fromStringCondensed(mrEnclave) client = Svr2Client(mrEnclave, bytes.toByteArray(), Instant.now()) - pinHasher = Svr2PinHasher(authorization, mrEnclave) Log.d(TAG, "[onMessage] Sending initial handshake...") webSocket.send(client.initialRequest().toByteString()) @@ -110,7 +107,7 @@ internal class Svr2Socket( client.completeHandshake(bytes.toByteArray()) Log.d(TAG, "[onMessage] Handshake read success. Sending request...") - val ciphertextBytes = client.establishedSend(clientRequest(pinHasher).encode()) + val ciphertextBytes = client.establishedSend(clientRequest.encode()) webSocket.send(ciphertextBytes.toByteString()) Log.d(TAG, "[onMessage] Request sent.") @@ -119,12 +116,7 @@ internal class Svr2Socket( Stage.WAITING_FOR_RESPONSE -> { Log.d(TAG, "[onMessage] Received response for our request.") - emitter.onSuccess( - Response( - response = Svr2Response.ADAPTER.decode(client.establishedRecv(bytes.toByteArray())), - pinHasher = pinHasher - ) - ) + emitSuccess(Svr2Response.ADAPTER.decode(client.establishedRecv(bytes.toByteArray()))) } Stage.CLOSED -> { @@ -139,15 +131,15 @@ internal class Svr2Socket( } catch (e: IOException) { Log.w(TAG, e) webSocket.close(1000, "OK") - emitter.tryOnError(e) + emitError(e) } catch (e: AttestationDataException) { Log.w(TAG, e) webSocket.close(1000, "OK") - emitter.tryOnError(e) + emitError(IOException(e)) } catch (e: SgxCommunicationFailureException) { Log.w(TAG, e) webSocket.close(1000, "OK") - emitter.tryOnError(e) + emitError(IOException(e)) } } @@ -155,24 +147,51 @@ internal class Svr2Socket( Log.i(TAG, "[onClosing] code: $code, reason: $reason") if (code == 1000) { - emitter.tryOnError(IOException("Websocket was closed with code 1000")) + emitError(IOException("Websocket was closed with code 1000")) stage.set(Stage.CLOSED) } else { Log.w(TAG, "Remote side is closing with non-normal code $code") webSocket.close(1000, "Remote closed with code $code") stage.set(Stage.FAILED) - emitter.tryOnError(NonSuccessfulResponseCodeException(code)) + emitError(NonSuccessfulResponseCodeException(code)) } } override fun onFailure(webSocket: WebSocket, t: Throwable, response: OkHttpResponse?) { - if (emitter.tryOnError(t)) { + if (emitError(IOException(t))) { Log.w(TAG, "[onFailure] response? " + (response != null), t) stage.set(Stage.FAILED) webSocket.close(1000, "OK") } } + + @Throws(IOException::class) + fun blockAndWaitForResult(): Svr2Response { + latch.await() + + exception?.let { throw it } + response?.let { return it } + throw IllegalStateException("Neither the response nor exception were set!") + } + + private fun emitSuccess(result: Svr2Response) { + response = result + latch.countDown() + } + + /** Returns true if this was the first error emitted, otherwise false. */ + private fun emitError(e: IOException): Boolean { + val isFirstError = exception == null + + if (isFirstError) { + exception = e + } + + latch.countDown() + + return isFirstError + } } private enum class Stage { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/AuthCredentials.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/AuthCredentials.java index 883945f1f7..270444269e 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/AuthCredentials.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/AuthCredentials.java @@ -26,4 +26,9 @@ public class AuthCredentials { public String username() { return username; } public String password() { return password; } + + @Override + public String toString() { + return "AuthCredentials(xxx)"; + } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/LockedException.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/LockedException.java index 8ab00aa22a..eb61e64eb9 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/LockedException.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/LockedException.java @@ -5,15 +5,17 @@ import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulRespons public final class LockedException extends NonSuccessfulResponseCodeException { - private final int length; - private final long timeRemaining; - private final String basicStorageCredentials; + private final int length; + private final long timeRemaining; + private final AuthCredentials svr1Credentials; + private final AuthCredentials svr2Credentials; - public LockedException(int length, long timeRemaining, String basicStorageCredentials) { + public LockedException(int length, long timeRemaining, AuthCredentials svr1Credentials, AuthCredentials svr2Credentials) { super(423); - this.length = length; - this.timeRemaining = timeRemaining; - this.basicStorageCredentials = basicStorageCredentials; + this.length = length; + this.timeRemaining = timeRemaining; + this.svr1Credentials = svr1Credentials; + this.svr2Credentials = svr2Credentials; } public int getLength() { @@ -24,7 +26,11 @@ public final class LockedException extends NonSuccessfulResponseCodeException { return timeRemaining; } - public String getBasicStorageCredentials() { - return basicStorageCredentials; + public AuthCredentials getSvr1Credentials() { + return svr1Credentials; + } + + public AuthCredentials getSvr2Credentials() { + return svr2Credentials; } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index 8b219b4583..e1a49aa350 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -488,10 +488,6 @@ public class PushServiceSocket { public void setAccountAttributes(@Nonnull AccountAttributes accountAttributes) throws IOException { - if (accountAttributes.getRegistrationLock() != null && accountAttributes.getPin() != null) { - throw new AssertionError("Pin should be null if registrationLock is set."); - } - makeServiceRequest(SET_ACCOUNT_ATTRIBUTES, "PUT", JsonUtil.toJson(accountAttributes)); } @@ -1236,8 +1232,8 @@ public class PushServiceSocket { return getCredentials(DIRECTORY_AUTH_PATH); } - public String getKeyBackupServiceAuthorization() throws IOException { - return getCredentials(KBS_AUTH_PATH); + public AuthCredentials getKeyBackupServiceAuthorization() throws IOException { + return getAuthCredentials(KBS_AUTH_PATH); } public AuthCredentials getPaymentsAuthorization() throws IOException { @@ -1822,13 +1818,12 @@ public class PushServiceSocket { case 417: throw new ExpectationFailedException(); case 423: - RegistrationLockFailure accountLockFailure = readResponseJson(response, RegistrationLockFailure.class); - AuthCredentials credentials = accountLockFailure.backupCredentials; - String basicStorageCredentials = credentials != null ? credentials.asBasic() : null; + RegistrationLockFailure accountLockFailure = readResponseJson(response, RegistrationLockFailure.class); throw new LockedException(accountLockFailure.length, accountLockFailure.timeRemaining, - basicStorageCredentials); + accountLockFailure.svr1Credentials, + accountLockFailure.svr2Credentials); case 428: ProofRequiredResponse proofRequiredResponse = readResponseJson(response, ProofRequiredResponse.class); String retryAfterRaw = response.header("Retry-After"); @@ -2330,8 +2325,11 @@ public class PushServiceSocket { @JsonProperty public long timeRemaining; + @JsonProperty("backupCredentials") + public AuthCredentials svr1Credentials; + @JsonProperty - public AuthCredentials backupCredentials; + public AuthCredentials svr2Credentials; } private static class ConnectionHolder { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/util/JsonUtil.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/util/JsonUtil.java index ff4b8a6be3..4e809491d6 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/util/JsonUtil.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/util/JsonUtil.java @@ -17,11 +17,13 @@ import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.module.kotlin.KotlinModule; import com.google.protobuf.ByteString; import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.logging.Log; +import org.whispersystems.signalservice.api.kbs.MasterKey; import org.whispersystems.signalservice.api.push.ACI; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.exceptions.MalformedResponseException; @@ -42,6 +44,7 @@ public class JsonUtil { static { objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.registerModule(new KotlinModule()); } public static String toJson(Object object) { @@ -156,4 +159,18 @@ public class JsonUtil { return ServiceId.parseOrNull(p.getValueAsString()); } } + + public static class MasterKeySerializer extends JsonSerializer { + @Override + public void serialize(MasterKey value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeString(Base64.encodeBytes(value.serialize())); + } + } + + public static class MasterKeyDeserializer extends JsonDeserializer { + @Override + public MasterKey deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return new MasterKey(Base64.decode(p.getValueAsString())); + } + } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/DefaultErrorMapper.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/DefaultErrorMapper.java index 0ae413be34..03e1b8457c 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/DefaultErrorMapper.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/DefaultErrorMapper.java @@ -119,12 +119,10 @@ public final class DefaultErrorMapper implements ErrorMapper { return e; } - AuthCredentials credentials = accountLockFailure.backupCredentials; - String basicStorageCredentials = credentials != null ? credentials.asBasic() : null; - return new LockedException(accountLockFailure.length, accountLockFailure.timeRemaining, - basicStorageCredentials); + accountLockFailure.svr1Credentials, + accountLockFailure.svr2Credentials); case 428: ProofRequiredResponse proofRequiredResponse; try {