Fixes for restoring a backup after completing registration.

This commit is contained in:
Nicholas Tinsley 2024-07-30 19:54:48 +02:00 committed by mtang-signal
parent 57adab858c
commit 6424c6bc99
12 changed files with 180 additions and 21 deletions

View file

@ -187,7 +187,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
}
private boolean userCanTransferOrRestore() {
return !SignalStore.registration().isRegistrationComplete() && RemoteConfig.restoreAfterRegistration() && !SignalStore.registration().hasSkippedTransferOrRestore();
return !SignalStore.registration().isRegistrationComplete() && RemoteConfig.restoreAfterRegistration() && !SignalStore.registration().hasSkippedTransferOrRestore() && !SignalStore.registration().hasCompletedRestore();
}
private boolean userMustCreateSignalPin() {
@ -241,6 +241,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
private Intent getTransferOrRestoreIntent() {
Intent intent = RestoreActivity.getIntentForTransferOrRestore(this);
intent.putExtra(NEXT_INTENT_EXTRA, MainActivity.clearTop(this));
return getRoutedIntent(intent, getIntent());
}

View file

@ -13,6 +13,7 @@ import org.greenrobot.eventbus.ThreadMode;
import org.signal.devicetransfer.DeviceToDeviceTransferService;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.devicetransfer.DeviceTransferFragment;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.util.navigation.SafeNavigation;
/**
@ -61,6 +62,7 @@ public final class NewDeviceTransferFragment extends DeviceTransferFragment {
case SUCCESS:
transferFinished = true;
DeviceToDeviceTransferService.stop(requireContext());
SignalStore.registration().markRestoreCompleted();
navigateToTransferComplete();
break;
case FAILURE_VERSION_DOWNGRADE:

View file

@ -0,0 +1,17 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.keyvalue
import org.signal.core.util.ByteSerializer
import org.thoughtcrime.securesms.database.model.databaseprotos.LocalRegistrationMetadata
/**
* Serialize [LocalRegistrationMetadata]
*/
object LocalRegistrationMetadataSerializer : ByteSerializer<LocalRegistrationMetadata> {
override fun serialize(data: LocalRegistrationMetadata): ByteArray = data.encode()
override fun deserialize(data: ByteArray): LocalRegistrationMetadata = LocalRegistrationMetadata.ADAPTER.decode(data)
}

View file

@ -4,6 +4,8 @@ import androidx.annotation.CheckResult;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.database.model.databaseprotos.LocalRegistrationMetadata;
import java.util.Collections;
import java.util.List;
@ -15,6 +17,8 @@ public final class RegistrationValues extends SignalStoreValues {
private static final String SESSION_E164 = "registration.session_e164";
private static final String SESSION_ID = "registration.session_id";
private static final String SKIPPED_TRANSFER_OR_RESTORE = "registration.has_skipped_transfer_or_restore";
private static final String LOCAL_REGISTRATION_DATA = "registration.local_registration_data";
private static final String RESTORE_COMPLETED = "registration.backup_restore_completed";
RegistrationValues(@NonNull KeyValueStore store) {
super(store);
@ -54,6 +58,20 @@ public final class RegistrationValues extends SignalStoreValues {
return getStore().getBoolean(REGISTRATION_COMPLETE, true);
}
public void setLocalRegistrationMetadata(LocalRegistrationMetadata data) {
putObject(LOCAL_REGISTRATION_DATA, data, LocalRegistrationMetadataSerializer.INSTANCE);
}
@Nullable
public LocalRegistrationMetadata getLocalRegistrationMetadata() {
return getObject(LOCAL_REGISTRATION_DATA, null, LocalRegistrationMetadataSerializer.INSTANCE);
}
public void clearLocalRegistrationMetadata() {
remove(LOCAL_REGISTRATION_DATA);
}
public boolean hasUploadedProfile() {
return getBoolean(HAS_UPLOADED_PROFILE, true);
}
@ -95,4 +113,12 @@ public final class RegistrationValues extends SignalStoreValues {
public String getSessionE164() {
return getString(SESSION_E164, null);
}
public boolean hasCompletedRestore() {
return getBoolean(RESTORE_COMPLETED, false);
}
public void markRestoreCompleted() {
putBoolean(RESTORE_COMPLETED, true);
}
}

View file

@ -28,6 +28,7 @@ public final class RegistrationUtil {
{
Log.i(TAG, "Marking registration completed.", new Throwable());
SignalStore.registration().setRegistrationComplete();
SignalStore.registration().clearLocalRegistrationMetadata();
if (SignalStore.phoneNumberPrivacy().getPhoneNumberDiscoverabilityMode() == PhoneNumberDiscoverabilityMode.UNDECIDED) {
Log.w(TAG, "Phone number discoverability mode is still UNDECIDED. Setting to DISCOVERABLE.");

View file

@ -0,0 +1,61 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.registration.data
import okio.ByteString.Companion.toByteString
import org.signal.libsignal.protocol.IdentityKey
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.LocalRegistrationMetadata
import org.thoughtcrime.securesms.registration.RegistrationData
import org.whispersystems.signalservice.api.account.PreKeyCollection
/**
* Takes the two sources of registration data ([RegistrationData], [RegistrationRepository.AccountRegistrationResult])
* and combines them into a proto-backed class [LocalRegistrationMetadata] so they can be serialized & stored.
*/
object LocalRegistrationMetadataUtil {
fun createLocalRegistrationMetadata(registrationData: RegistrationData, remoteResult: RegistrationRepository.AccountRegistrationResult, reglockEnabled: Boolean): LocalRegistrationMetadata {
return LocalRegistrationMetadata.Builder().apply {
aciIdentityKey = remoteResult.aciPreKeyCollection.identityKey.serialize().toByteString()
aciSignedPreKey = remoteResult.aciPreKeyCollection.signedPreKey.serialize().toByteString()
aciLastRestoreKyberPreKey = remoteResult.aciPreKeyCollection.signedPreKey.serialize().toByteString()
pniIdentityKey = remoteResult.pniPreKeyCollection.identityKey.serialize().toByteString()
pniSignedPreKey = remoteResult.pniPreKeyCollection.signedPreKey.serialize().toByteString()
pniLastRestoreKyberPreKey = remoteResult.pniPreKeyCollection.signedPreKey.serialize().toByteString()
aci = remoteResult.uuid
pni = remoteResult.pni
hasPin = remoteResult.storageCapable
remoteResult.pin?.let {
pin = it
}
remoteResult.masterKey?.serialize()?.toByteString()?.let {
masterKey = it
}
e164 = registrationData.e164
fcmEnabled = registrationData.isFcm
profileKey = registrationData.profileKey.serialize().toByteString()
servicePassword = registrationData.password
this.reglockEnabled = reglockEnabled
}.build()
}
fun LocalRegistrationMetadata.getAciPreKeyCollection(): PreKeyCollection {
return PreKeyCollection(
IdentityKey(aciIdentityKey.toByteArray()),
SignedPreKeyRecord(aciSignedPreKey.toByteArray()),
KyberPreKeyRecord(aciLastRestoreKyberPreKey.toByteArray())
)
}
fun LocalRegistrationMetadata.getPniPreKeyCollection(): PreKeyCollection {
return PreKeyCollection(
IdentityKey(pniIdentityKey.toByteArray()),
SignedPreKeyRecord(pniSignedPreKey.toByteArray()),
KyberPreKeyRecord(pniLastRestoreKyberPreKey.toByteArray())
)
}
}

View file

@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore
import org.thoughtcrime.securesms.crypto.storage.SignalServiceAccountDataStoreImpl
import org.thoughtcrime.securesms.database.IdentityTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.databaseprotos.LocalRegistrationMetadata
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.gcm.FcmUtil
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob
@ -45,6 +46,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.registration.PushChallengeRequest
import org.thoughtcrime.securesms.registration.RegistrationData
import org.thoughtcrime.securesms.registration.VerifyAccountRepository
import org.thoughtcrime.securesms.registration.data.LocalRegistrationMetadataUtil.getAciPreKeyCollection
import org.thoughtcrime.securesms.registration.data.LocalRegistrationMetadataUtil.getPniPreKeyCollection
import org.thoughtcrime.securesms.registration.data.network.BackupAuthCheckResult
import org.thoughtcrime.securesms.registration.data.network.RegisterAccountResult
import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionCheckResult
@ -159,14 +162,14 @@ object RegistrationRepository {
* Takes a server response from a successful registration and persists the relevant data.
*/
@JvmStatic
suspend fun registerAccountLocally(context: Context, registrationData: RegistrationData, response: AccountRegistrationResult, reglockEnabled: Boolean) =
suspend fun registerAccountLocally(context: Context, data: LocalRegistrationMetadata) =
withContext(Dispatchers.IO) {
Log.v(TAG, "registerAccountLocally()")
val aciPreKeyCollection: PreKeyCollection = response.aciPreKeyCollection
val pniPreKeyCollection: PreKeyCollection = response.pniPreKeyCollection
val aci: ACI = ACI.parseOrThrow(response.uuid)
val pni: PNI = PNI.parseOrThrow(response.pni)
val hasPin: Boolean = response.storageCapable
val aciPreKeyCollection = data.getAciPreKeyCollection()
val pniPreKeyCollection = data.getPniPreKeyCollection()
val aci: ACI = ACI.parseOrThrow(data.aci)
val pni: PNI = PNI.parseOrThrow(data.pni)
val hasPin: Boolean = data.hasPin
SignalStore.account.setAci(aci)
SignalStore.account.setPni(pni)
@ -187,30 +190,31 @@ object RegistrationRepository {
storeSignedAndLastResortPreKeys(pniProtocolStore, pniMetadataStore, pniPreKeyCollection)
val recipientTable = SignalDatabase.recipients
val selfId = Recipient.trustedPush(aci, pni, registrationData.e164).id
val selfId = Recipient.trustedPush(aci, pni, data.e164).id
recipientTable.setProfileSharing(selfId, true)
recipientTable.markRegisteredOrThrow(selfId, aci)
recipientTable.linkIdsForSelf(aci, pni, registrationData.e164)
recipientTable.setProfileKey(selfId, registrationData.profileKey)
recipientTable.linkIdsForSelf(aci, pni, data.e164)
recipientTable.setProfileKey(selfId, ProfileKey(data.profileKey.toByteArray()))
AppDependencies.recipientCache.clearSelf()
SignalStore.account.setE164(registrationData.e164)
SignalStore.account.fcmToken = registrationData.fcmToken
SignalStore.account.fcmEnabled = registrationData.isFcm
SignalStore.account.setE164(data.e164)
SignalStore.account.fcmToken = data.fcmToken
SignalStore.account.fcmEnabled = data.fcmEnabled
val now = System.currentTimeMillis()
saveOwnIdentityKey(selfId, aci, aciProtocolStore, now)
saveOwnIdentityKey(selfId, pni, pniProtocolStore, now)
SignalStore.account.setServicePassword(registrationData.password)
SignalStore.account.setServicePassword(data.servicePassword)
SignalStore.account.setRegistered(true)
TextSecurePreferences.setPromptedPushRegistration(context, true)
TextSecurePreferences.setUnauthorizedReceived(context, false)
NotificationManagerCompat.from(context).cancel(NotificationIds.UNREGISTERED_NOTIFICATION_ID)
SvrRepository.onRegistrationComplete(response.masterKey, response.pin, hasPin, reglockEnabled)
val masterKey = if (data.masterKey != null) MasterKey(data.masterKey.toByteArray()) else null
SvrRepository.onRegistrationComplete(masterKey, data.pin, hasPin, data.reglockEnabled)
AppDependencies.resetNetwork()
AppDependencies.incomingMessageObserver

View file

@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.pin.SvrRepository
import org.thoughtcrime.securesms.pin.SvrWrongPinException
import org.thoughtcrime.securesms.registration.RegistrationData
import org.thoughtcrime.securesms.registration.RegistrationUtil
import org.thoughtcrime.securesms.registration.data.LocalRegistrationMetadataUtil
import org.thoughtcrime.securesms.registration.data.RegistrationRepository
import org.thoughtcrime.securesms.registration.data.network.BackupAuthCheckResult
import org.thoughtcrime.securesms.registration.data.network.Challenge
@ -815,7 +816,11 @@ class RegistrationViewModel : ViewModel() {
private suspend fun onSuccessfulRegistration(context: Context, registrationData: RegistrationData, remoteResult: RegistrationRepository.AccountRegistrationResult, reglockEnabled: Boolean) {
Log.v(TAG, "onSuccessfulRegistration()")
RegistrationRepository.registerAccountLocally(context, registrationData, remoteResult, reglockEnabled)
val metadata = LocalRegistrationMetadataUtil.createLocalRegistrationMetadata(registrationData, remoteResult, reglockEnabled)
if (RemoteConfig.restoreAfterRegistration) {
SignalStore.registration.localRegistrationMetadata = metadata
}
RegistrationRepository.registerAccountLocally(context, metadata)
if (reglockEnabled) {
SignalStore.onboarding.clearAll()

View file

@ -59,6 +59,7 @@ import org.thoughtcrime.securesms.backup.v2.ui.subscription.RemoteRestoreViewMod
import org.thoughtcrime.securesms.conversation.v2.registerForLifecycle
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobs.ProfileUploadJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.profiles.AvatarHelper
import org.thoughtcrime.securesms.profiles.edit.CreateProfileActivity
import org.thoughtcrime.securesms.recipients.Recipient
@ -101,6 +102,7 @@ class RemoteRestoreActivity : BaseActivity() {
)
if (state.importState == RemoteRestoreViewModel.ImportState.RESTORED) {
SideEffect {
SignalStore.registration.markRestoreCompleted()
RegistrationUtil.maybeMarkRegistrationComplete()
AppDependencies.jobManager.add(ProfileUploadJob())
startActivity(MainActivity.clearTop(this))

View file

@ -99,6 +99,7 @@ class RestoreLocalBackupFragment : LoggingFragment(R.layout.fragment_restore_loc
onBackupCompletedSuccessfully()
} else {
handleBackupImportError(importResult)
restoreLocalBackupViewModel.backupImportErrorShown()
}
}
}

View file

@ -16,6 +16,9 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.backup.BackupEvent
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.registration.RegistrationUtil
import org.thoughtcrime.securesms.registration.data.RegistrationRepository
import org.thoughtcrime.securesms.restore.RestoreRepository
/**
@ -82,13 +85,23 @@ class RestoreLocalBackupViewModel(fileBackupUri: Uri) : ViewModel() {
}
viewModelScope.launch {
val importResult = RestoreRepository.restoreBackupAsynchronously(context, backupFileUri, backupPassphrase)
val importResult: RestoreRepository.BackupImportResult = RestoreRepository.restoreBackupAsynchronously(context, backupFileUri, backupPassphrase)
if (importResult == RestoreRepository.BackupImportResult.SUCCESS) {
SignalStore.registration.localRegistrationMetadata?.let {
RegistrationRepository.registerAccountLocally(context, it)
SignalStore.registration.clearLocalRegistrationMetadata()
RegistrationUtil.maybeMarkRegistrationComplete()
}
SignalStore.registration.markRestoreCompleted()
}
store.update {
it.copy(
backupImportResult = if (importResult == RestoreRepository.BackupImportResult.SUCCESS) null else importResult,
restoreInProgress = false,
backupRestoreComplete = true,
backupRestoreComplete = importResult == RestoreRepository.BackupImportResult.SUCCESS,
backupEstimatedTotalCount = -1L,
backupProgressCount = -1L,
backupVerifyingInProgress = false
@ -102,9 +115,7 @@ class RestoreLocalBackupViewModel(fileBackupUri: Uri) : ViewModel() {
it.copy(
backupProgressCount = event.count,
backupEstimatedTotalCount = event.estimatedTotalCount,
backupVerifyingInProgress = event.type == BackupEvent.Type.PROGRESS_VERIFYING,
backupRestoreComplete = event.type == BackupEvent.Type.FINISHED,
restoreInProgress = event.type != BackupEvent.Type.FINISHED
backupVerifyingInProgress = event.type == BackupEvent.Type.PROGRESS_VERIFYING
)
}
}
@ -113,6 +124,14 @@ class RestoreLocalBackupViewModel(fileBackupUri: Uri) : ViewModel() {
store.update { it.copy(backupFileStateError = null) }
}
fun backupImportErrorShown() {
store.update {
it.copy(
backupImportResult = null
)
}
}
companion object {
private val TAG = Log.tag(RestoreLocalBackupViewModel::class.java)
}

View file

@ -514,3 +514,23 @@ message PaymentTombstone {
CryptoValue amount = 2;
CryptoValue fee = 3;
}
message LocalRegistrationMetadata {
bytes aciIdentityKey = 1;
bytes aciSignedPreKey = 2;
bytes aciLastRestoreKyberPreKey = 3;
bytes pniIdentityKey = 4;
bytes pniSignedPreKey = 5;
bytes pniLastRestoreKyberPreKey = 6;
string aci = 7;
string pni = 8;
bool hasPin = 9;
optional string pin = 10;
optional bytes masterKey = 11;
string e164 = 12;
bool fcmEnabled = 13;
string fcmToken = 14;
bytes profileKey = 15;
string servicePassword = 16;
bool reglockEnabled = 17;
}