Start mirroring to SVR2.

This commit is contained in:
Greyson Parrelli
2023-07-05 19:05:30 -04:00
committed by Clark Chen
parent dfb7304626
commit e1570e9512
111 changed files with 1828 additions and 2299 deletions

View File

@@ -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()))

View File

@@ -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() {

View File

@@ -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<AndroidBackupItem> = listOf(
KbsAuthTokens
SvrAuthTokens
)
override fun onBackup(oldState: ParcelFileDescriptor?, data: BackupDataOutput, newState: ParcelFileDescriptor) {

View File

@@ -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.")
}

View File

@@ -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()

View File

@@ -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
)

View File

@@ -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<View>(R.id.change_number_pin_differs_update_pin).setOnClickListener {
changePin.launch(CreateKbsPinActivity.getIntentForPinChangeFromSettings(requireContext()))
changePin.launch(CreateSvrPinActivity.getIntentForPinChangeFromSettings(requireContext()))
}
requireActivity().onBackPressedDispatcher.addCallback(

View File

@@ -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()

View File

@@ -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<ServiceResponse<VerifyResponse>> {
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

View File

@@ -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<ServiceResponse<VerifyResponse>> {
override fun verifyAccountWithRegistrationLock(pin: String, svrAuthCredentials: SvrAuthCredentialSet): Single<ServiceResponse<VerifyResponse>> {
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<Boolean> {
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))

View File

@@ -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 ->

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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<ClearFallbackKbsEnclaveJob> {
@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);
}
}
}

View File

@@ -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());
}};
}

View File

@@ -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<KbsEnclaveMigrationWorkerJob> {
@Override
public @NonNull KbsEnclaveMigrationWorkerJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) {
return new KbsEnclaveMigrationWorkerJob(parameters);
}
}
}

View File

@@ -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

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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<RefreshKbsCredentialsJob> {
override fun create(parameters: Parameters, serializedData: ByteArray?): RefreshKbsCredentialsJob {
return RefreshKbsCredentialsJob(parameters)
class Factory : Job.Factory<RefreshSvrCredentialsJob> {
override fun create(parameters: Parameters, serializedData: ByteArray?): RefreshSvrCredentialsJob {
return RefreshSvrCredentialsJob(parameters)
}
}
}

View File

@@ -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<ResetSvrGuessCountJob> {
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)
)
}
}
}

View File

@@ -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;
}

View File

@@ -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<Svr2MirrorJob> {
override fun create(parameters: Parameters, serializedData: ByteArray?): Svr2MirrorJob {
return Svr2MirrorJob(parameters, JsonJobData.deserialize(serializedData).getString(KEY_CHANGE_SESSION))
}
}
}

View File

@@ -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 {

View File

@@ -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() {

View File

@@ -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<String> getKeysToIncludeInBackup() {
List<String> 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() {

View File

@@ -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() {

View File

@@ -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<String> 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<String> tokens) {
putList(KBS_AUTH_TOKENS, tokens, StringStringSerializer.INSTANCE);
putList(SVR_AUTH_TOKENS, tokens, StringStringSerializer.INSTANCE);
setLastRefreshAuthTimestamp(System.currentTimeMillis());
}
public synchronized List<String> getKbsAuthTokenList() {
return getList(KBS_AUTH_TOKENS, StringStringSerializer.INSTANCE);
public synchronized List<String> 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<String> tokens = getKbsAuthTokenList();
List<String> tokens = getAuthTokenList();
if (tokens.contains(token)) {
return false;
} else {
@@ -209,7 +193,7 @@ public final class KbsValues extends SignalStoreValues {
}
public boolean removeAuthTokens(@NonNull List<String> invalid) {
List<String> tokens = new ArrayList<>(getKbsAuthTokenList());
List<String> 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);
}
}

View File

@@ -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);

View File

@@ -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<ViewModel extends BaseKbsPinViewModel> extends LoggingFragment {
public abstract class BaseSvrPinFragment<ViewModel extends BaseSvrPinViewModel> extends LoggingFragment {
private TextView title;
private LearnMoreTextView description;
@@ -62,8 +61,8 @@ public abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel>
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<ViewModel extends BaseKbsPinViewModel>
@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();
}

View File

@@ -3,8 +3,8 @@ package org.thoughtcrime.securesms.lock.v2;
import androidx.annotation.MainThread;
import androidx.lifecycle.LiveData;
interface BaseKbsPinViewModel {
LiveData<KbsPin> getUserEntry();
interface BaseSvrPinViewModel {
LiveData<SvrPin> getUserEntry();
LiveData<PinKeyboardType> getKeyboard();

View File

@@ -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<PinSetResult> 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
}
}

View File

@@ -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<KbsPin> userEntry = new DefaultValueLiveData<>(KbsPin.EMPTY);
private final DefaultValueLiveData<PinKeyboardType> keyboard = new DefaultValueLiveData<>(PinKeyboardType.NUMERIC);
private final DefaultValueLiveData<SaveAnimation> saveAnimation = new DefaultValueLiveData<>(SaveAnimation.NONE);
private final DefaultValueLiveData<LabelState> 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<SaveAnimation> getSaveAnimation() {
return Transformations.distinctUntilChanged(saveAnimation);
}
LiveData<LabelState> 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<KbsPin> getUserEntry() {
return userEntry;
}
@Override
public LiveData<PinKeyboardType> 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 extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection unchecked
return (T) new ConfirmKbsPinViewModel(pinToConfirm, keyboard, repository);
}
}
}

View File

@@ -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<ConfirmKbsPinViewModel>() {
internal class ConfirmSvrPinFragment : BaseSvrPinFragment<ConfirmSvrPinViewModel>() {
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<ConfirmKbsPinViewModel
ViewCompat.setAutofillHints(input, HintConstants.AUTOFILL_HINT_NEW_PASSWORD)
}
override fun initializeViewModel(): ConfirmKbsPinViewModel {
val args = ConfirmKbsPinFragmentArgs.fromBundle(requireArguments())
override fun initializeViewModel(): ConfirmSvrPinViewModel {
val args = ConfirmSvrPinFragmentArgs.fromBundle(requireArguments())
val userEntry = args.userEntry!!
val keyboard = args.keyboard
val repository = ConfirmKbsPinRepository()
val factory = ConfirmKbsPinViewModel.Factory(userEntry, keyboard, repository)
val viewModel = ViewModelProvider(this, factory)[ConfirmKbsPinViewModel::class.java]
viewModel.label.observe(viewLifecycleOwner) { label: ConfirmKbsPinViewModel.LabelState -> 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<ConfirmKbsPinViewModel
confirm.isEnabled = true
}
private fun updateLabel(labelState: ConfirmKbsPinViewModel.LabelState) {
private fun updateLabel(labelState: ConfirmSvrPinViewModel.LabelState) {
when (labelState) {
ConfirmKbsPinViewModel.LabelState.EMPTY -> 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)

View File

@@ -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<SvrPin> userEntry = new DefaultValueLiveData<>(SvrPin.EMPTY);
private final DefaultValueLiveData<PinKeyboardType> keyboard = new DefaultValueLiveData<>(PinKeyboardType.NUMERIC);
private final DefaultValueLiveData<SaveAnimation> saveAnimation = new DefaultValueLiveData<>(SaveAnimation.NONE);
private final DefaultValueLiveData<LabelState> 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<SaveAnimation> getSaveAnimation() {
return Transformations.distinctUntilChanged(saveAnimation);
}
LiveData<LabelState> 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<SvrPin> getUserEntry() {
return userEntry;
}
@Override
public LiveData<PinKeyboardType> 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 extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection unchecked
return (T) new ConfirmSvrPinViewModel(pinToConfirm, keyboard);
}
}
}

View File

@@ -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());

View File

@@ -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<CreateKbsPinViewModel?>() {
class CreateSvrPinFragment : BaseSvrPinFragment<CreateSvrPinViewModel?>() {
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<CreateKbsPinViewModel?>() {
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<CreateKbsPinViewModel?>() {
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<CreateKbsPinViewModel?>() {
}
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 {

View File

@@ -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<KbsPin> userEntry = new MutableLiveData<>(KbsPin.EMPTY);
private final MutableLiveData<SvrPin> userEntry = new MutableLiveData<>(SvrPin.EMPTY);
private final MutableLiveData<PinKeyboardType> keyboard = new MutableLiveData<>(PinKeyboardType.NUMERIC);
private final SingleLiveEvent<NavigationEvent> events = new SingleLiveEvent<>();
private final SingleLiveEvent<PinErrorEvent> errors = new SingleLiveEvent<>();
@Override
public LiveData<KbsPin> getUserEntry() {
public LiveData<SvrPin> 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;
}

View File

@@ -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();
}
}

View File

@@ -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() { }
}

View File

@@ -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

View File

@@ -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<KbsPin> CREATOR = new Creator<KbsPin>() {
public static final Creator<SvrPin> CREATOR = new Creator<SvrPin>() {
@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];
}
};
}

View File

@@ -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);
}

View File

@@ -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());

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<KbsEnclaveMigrationJob> {
@Override
public @NonNull KbsEnclaveMigrationJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) {
return new KbsEnclaveMigrationJob(parameters);
}
}
}

View File

@@ -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.");

View File

@@ -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<RegistrationPinV2MigrationJob> {
@Override
public @NonNull RegistrationPinV2MigrationJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) {
return new RegistrationPinV2MigrationJob(parameters);
}
}
}

View File

@@ -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<Svr2MirrorMigrationJob> {
override fun create(parameters: Parameters, serializedData: ByteArray?): Svr2MirrorMigrationJob {
return Svr2MirrorMigrationJob(parameters)
}
}
}

View File

@@ -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

View File

@@ -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<Optional<TokenData>> 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<ServiceResponse<TokenData>> getToken(@Nullable String authorization) {
return Single.<ServiceResponse<TokenData>>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());
}
}
}

View File

@@ -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;
}
}

View File

@@ -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.");

View File

@@ -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);

View File

@@ -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());
}

View File

@@ -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();
});

View File

@@ -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<PinResultData> 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<T> {
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
}
}

View File

@@ -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> triesRemaining;
private final SingleLiveEvent<Event> 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<TriesRemaining> getTriesRemaining() {
return triesRemaining;
}
@NonNull LiveData<Event> 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;
}
}
}

View File

@@ -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<TriesRemaining> = DefaultValueLiveData(TriesRemaining(10, false))
private val event: SingleLiveEvent<Event> = 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<Event> {
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
}
}
}

View File

@@ -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<JobTracker.JobState> 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<JobTracker.JobState> 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 {}
}

View File

@@ -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) {

View File

@@ -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<SecureValueRecovery> = 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<Pair<SecureValueRecovery, () -> 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<BackupResponse> = 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()
}
}

View File

@@ -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;
}
}

View File

@@ -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<TokenData> CREATOR = new Creator<TokenData>() {
@Override
public TokenData createFromParcel(Parcel in) {
return new TokenData(in);
}
@Override
public TokenData[] newArray(int size) {
return new TokenData[size];
}
};
}

View File

@@ -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);
}
}
}

View File

@@ -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.<ServiceResponse<VerifyResponse>>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<BackupAuthCheckProcessor> getKbsAuthCredential(@NonNull RegistrationData registrationData, List<String> usernamePasswords) {
public Single<BackupAuthCheckProcessor> getSvrAuthCredential(@NonNull RegistrationData registrationData, List<String> 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();
}
});

View File

@@ -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();

View File

@@ -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<ServiceResponse<VerifyResponse>> {
fun registerAccount(sessionId: String?, registrationData: RegistrationData, pin: String? = null, masterKeyProducer: MasterKeyProducer? = null): Single<ServiceResponse<VerifyResponse>> {
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) {

View File

@@ -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<VerifyAccountResponse>,
kbsData: KbsPinData?,
masterKey: MasterKey?,
pin: String?,
aciPreKeyCollection: PreKeyCollection?,
pniPreKeyCollection: PreKeyCollection?
): ServiceResponse<VerifyResponse> {
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)
}

View File

@@ -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<VerifyResponse>) : ServiceResponseProcessor<VerifyResponse>(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<VerifyResponse>)
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<VerifyResponse>)
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<VerifyResponse>) : 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<VerifyResponse>, 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<TokenData>) : VerifyResponseProcessor(ServiceResponse.coerceError(response)) {
override fun isKbsLocked(): Boolean {
class VerifyResponseHitRegistrationLock(response: ServiceResponse<VerifyResponse>) : VerifyResponseProcessor(response) {
override fun isRegistrationLockPresentAndSvrExhausted(): Boolean {
return false
}
}
@@ -86,18 +82,14 @@ class VerifyResponseWithFailedKbs(response: ServiceResponse<TokenData>) : Verify
* Process responses from attempting to verify an account with registration lock for use in
* account registration.
*/
class VerifyResponseWithRegistrationLockProcessor(response: ServiceResponse<VerifyResponse>, override val tokenData: TokenData?) : VerifyResponseProcessor(response) {
class VerifyResponseWithRegistrationLockProcessor(response: ServiceResponse<VerifyResponse>, 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<VerifyResponse>): VerifyResponseWithRegistrationLockProcessor {
@@ -105,12 +97,12 @@ class VerifyResponseWithRegistrationLockProcessor(response: ServiceResponse<Veri
return this
}
return VerifyResponseWithRegistrationLockProcessor(ServiceResponse.coerceError(response), tokenData)
return VerifyResponseWithRegistrationLockProcessor(ServiceResponse.coerceError(response), svrAuthCredentials)
}
override fun isServerSentError(): Boolean {
return super.isServerSentError() ||
error is KeyBackupSystemWrongPinException ||
error is KeyBackupSystemNoDataException
error is SvrWrongPinException ||
error is SvrNoDataException
}
}

View File

@@ -172,11 +172,9 @@ public abstract class BaseEnterSmsCodeFragment<ViewModel extends BaseRegistratio
handleSuccessfulVerify();
} else if (processor.rateLimit()) {
handleRateLimited();
} else if (processor.registrationLock() && !processor.isKbsLocked()) {
} else if (processor.registrationLock() && !processor.isRegistrationLockPresentAndSvrExhausted()) {
LockedException lockedException = processor.getLockedException();
handleRegistrationLock(lockedException.getTimeRemaining());
} else if (processor.isKbsLocked()) {
handleKbsAccountLocked();
} else if (processor.authorizationFailed()) {
handleIncorrectCodeError();
} else {
@@ -227,7 +225,7 @@ public abstract class BaseEnterSmsCodeFragment<ViewModel extends BaseRegistratio
});
}
protected void handleKbsAccountLocked() {
protected void handleSvrAccountLocked() {
navigateToKbsAccountLocked();
}

View File

@@ -21,12 +21,12 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType;
import org.thoughtcrime.securesms.pin.TokenData;
import org.thoughtcrime.securesms.registration.viewmodel.BaseRegistrationViewModel;
import org.signal.core.util.concurrent.LifecycleDisposable;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
@@ -114,10 +114,9 @@ public abstract class BaseRegistrationLockFragment extends LoggingFragment {
viewModel.getLockedTimeRemaining()
.observe(getViewLifecycleOwner(), t -> 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);
}
}

View File

@@ -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();
},

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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()
}
}

View File

@@ -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<VerifyResponseWithRegistrationLockProcessor> 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<ServiceResponse<VerifyResponse>> verifyAccountWithoutRegistrationLock();
protected abstract Single<ServiceResponse<VerifyResponse>> verifyAccountWithRegistrationLock(@NonNull String pin, @NonNull TokenData kbsTokenData);
protected abstract Single<ServiceResponse<VerifyResponse>> verifyAccountWithRegistrationLock(@NonNull String pin, @NonNull SvrAuthCredentialSet svrAuthCredentials);
protected abstract Single<VerifyResponseProcessor> onVerifySuccess(@NonNull VerifyResponseProcessor processor);

View File

@@ -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<Int> = BehaviorSubject.createDefault(10)
val triesRemaining: Observable<Int> = _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)
}
}
}

View File

@@ -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<ServiceResponse<VerifyResponse>> verifyAccountWithRegistrationLock(@NonNull String pin, @NonNull TokenData kbsTokenData) {
protected Single<ServiceResponse<VerifyResponse>> 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 {
})
.<ServiceResponse<VerifyResponse>>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<VerifyResponseProcessor> verifyReRegisterWithRecoveryPassword(@NonNull String pin, @NonNull KbsPinData pinData) {
private Single<VerifyResponseProcessor> 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<Boolean> checkForValidKbsAuthCredentials() {
final List<String> kbsAuthTokenList = SignalStore.kbsValues().getKbsAuthTokenList();
List<String> usernamePasswords = kbsAuthTokenList
final List<String> svrAuthTokenList = SignalStore.svr().getAuthTokenList();
List<String> 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())));
}
}

View File

@@ -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)
}
}
}

View File

@@ -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<String> 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,

View File

@@ -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());
}
}

View File

@@ -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.
*/