mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-20 16:49:40 +01:00
Prevent SVRB falling out of sync after re-registrations.
This commit is contained in:
committed by
Michelle Tang
parent
10d6e5293b
commit
d6156ab3f2
@@ -164,6 +164,7 @@ import org.whispersystems.signalservice.internal.crypto.PaddingInputStream
|
||||
import org.whispersystems.signalservice.internal.push.AttachmentUploadForm
|
||||
import org.whispersystems.signalservice.internal.push.AuthCredentials
|
||||
import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
@@ -2394,6 +2395,26 @@ object BackupRepository {
|
||||
).encodeByteString()
|
||||
}
|
||||
|
||||
fun getRemoteBackupForwardSecrecyMetadata(): NetworkResult<ByteArray?> {
|
||||
return initBackupAndFetchAuth()
|
||||
.then { credential -> SignalNetwork.archive.getBackupInfo(SignalStore.account.requireAci(), credential.messageBackupAccess) }
|
||||
.then { info -> getCdnReadCredentials(CredentialType.MESSAGE, info.cdn ?: Cdn.CDN_3.cdnNumber).map { it.headers to info } }
|
||||
.then { pair ->
|
||||
val (cdnCredentials, info) = pair
|
||||
val headers = cdnCredentials.toMutableMap().apply {
|
||||
this["range"] = "bytes=0-${EncryptedBackupReader.BACKUP_SECRET_METADATA_UPPERBOUND - 1}"
|
||||
}
|
||||
|
||||
AppDependencies.signalServiceMessageReceiver.retrieveBackupForwardSecretMetadataBytes(
|
||||
info.cdn!!,
|
||||
headers,
|
||||
"backups/${info.backupDir}/${info.backupName}",
|
||||
EncryptedBackupReader.BACKUP_SECRET_METADATA_UPPERBOUND
|
||||
)
|
||||
}
|
||||
.map { bytes -> EncryptedBackupReader.readForwardSecrecyMetadata(ByteArrayInputStream(bytes)) }
|
||||
}
|
||||
|
||||
interface ExportProgressListener {
|
||||
fun onAccount()
|
||||
fun onRecipient()
|
||||
|
||||
@@ -49,6 +49,13 @@ class EncryptedBackupReader private constructor(
|
||||
companion object {
|
||||
const val MAC_SIZE = 32
|
||||
|
||||
/**
|
||||
* Estimated upperbound need to read backup secrecy metadata from the start of a file.
|
||||
*
|
||||
* Magic Number size + ~varint size (5) + forward secrecy metadata size estimate (200)
|
||||
*/
|
||||
val BACKUP_SECRET_METADATA_UPPERBOUND = EncryptedBackupWriter.MAGIC_NUMBER.size + 5 + 200
|
||||
|
||||
/**
|
||||
* Create a reader for a backup from the archive CDN.
|
||||
* The key difference is that we require forward secrecy data.
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.logging.logW
|
||||
import org.signal.libsignal.messagebackup.BackupForwardSecrecyToken
|
||||
import org.signal.libsignal.net.SvrBStoreResponse
|
||||
import org.signal.libsignal.zkgroup.VerificationFailedException
|
||||
import org.signal.protos.resumableuploads.ResumableUpload
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.backup.ArchiveUploadProgress
|
||||
@@ -165,6 +166,62 @@ class BackupMessagesJob private constructor(
|
||||
is NetworkResult.ApplicationError -> throw result.throwable
|
||||
}
|
||||
|
||||
if (SignalStore.backup.backupSecretRestoreRequired) {
|
||||
Log.i(TAG, "[svrb-restore] First backup of re-registered account without remote restore, read remote data if available to re-init")
|
||||
|
||||
val forwardSecrecyMetadata: ByteArray? = when (val result = BackupRepository.getRemoteBackupForwardSecrecyMetadata()) {
|
||||
is NetworkResult.Success -> result.result
|
||||
is NetworkResult.NetworkError -> return Result.retry(defaultBackoff()).logW(TAG, "[svrb-restore] Network error getting remote forward secrecy metadata.", result.getCause(), true)
|
||||
is NetworkResult.StatusCodeError -> {
|
||||
if (result.code == 401 || result.code == 403 || result.code == 404) {
|
||||
Log.i(TAG, "[svrb-restore] No backup data found, continuing.", true)
|
||||
null
|
||||
} else {
|
||||
return Result.retry(defaultBackoff()).logW(TAG, "[svrb-restore] Status code error when getting remote forward secrecy metadata.", result.getCause(), true)
|
||||
}
|
||||
}
|
||||
is NetworkResult.ApplicationError -> {
|
||||
if (result.getCause() is VerificationFailedException) {
|
||||
Log.w(TAG, "[svrb-restore] zkverification failed getting backup info, continuing.", true)
|
||||
null
|
||||
} else {
|
||||
throw result.throwable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (forwardSecrecyMetadata != null) {
|
||||
when (val result = SignalNetwork.svrB.restore(auth, SignalStore.backup.messageBackupKey, forwardSecrecyMetadata)) {
|
||||
is SvrBApi.RestoreResult.Success -> {
|
||||
Log.i(TAG, "[svrb-restore] Remote secrecy data restored successfully.")
|
||||
SignalStore.backup.nextBackupSecretData = result.data.nextBackupSecretData
|
||||
}
|
||||
|
||||
is SvrBApi.RestoreResult.NetworkError -> {
|
||||
Log.w(TAG, "[svrb-restore] Network error during SVRB.", result.exception)
|
||||
return Result.retry(defaultBackoff())
|
||||
}
|
||||
|
||||
is SvrBApi.RestoreResult.RestoreFailedError,
|
||||
SvrBApi.RestoreResult.InvalidDataError -> {
|
||||
Log.i(TAG, "[svrb-restore] Permanent SVRB error! Continuing $result")
|
||||
}
|
||||
|
||||
SvrBApi.RestoreResult.DataMissingError,
|
||||
is SvrBApi.RestoreResult.SvrError -> {
|
||||
Log.i(TAG, "[svrb-restore] Failed to fetch SVRB data, continuing: $result")
|
||||
}
|
||||
|
||||
is SvrBApi.RestoreResult.UnknownError -> {
|
||||
Log.e(TAG, "[svrb-restore] Unknown SVRB result! Crashing.", result.throwable)
|
||||
return Result.fatalFailure(RuntimeException(result.throwable))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SignalStore.backup.backupSecretRestoreRequired = false
|
||||
}
|
||||
|
||||
val backupSecretData = SignalStore.backup.nextBackupSecretData ?: run {
|
||||
Log.i(TAG, "First SVRB backup! Creating new backup chain.", true)
|
||||
val secretData = SignalNetwork.svrB.createNewBackupChain(auth, SignalStore.backup.messageBackupKey)
|
||||
|
||||
@@ -92,6 +92,7 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
private const val KEY_HAS_VERIFIED_BEFORE = "backup.has_verified_before"
|
||||
|
||||
private const val KEY_NEXT_BACKUP_SECRET_DATA = "backup.next_backup_secret_data"
|
||||
private const val KEY_BACKUP_SECRET_RESTORE_REQUIRED = "backup.backup_secret_restore_required"
|
||||
|
||||
private const val KEY_RESTORING_VIA_QR = "backup.restore_via_qr"
|
||||
|
||||
@@ -393,6 +394,9 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
/** The value from the last successful SVRB operation that must be passed to the next SVRB operation. */
|
||||
var nextBackupSecretData by nullableBlobValue(KEY_NEXT_BACKUP_SECRET_DATA, null)
|
||||
|
||||
/** True if user re-registered but did not restore SVRB secrets during registration, and should on backup. */
|
||||
var backupSecretRestoreRequired by booleanValue(KEY_BACKUP_SECRET_RESTORE_REQUIRED, false)
|
||||
|
||||
/** True if attempting to restore backup from quick restore/QR code */
|
||||
var restoringViaQr by booleanValue(KEY_RESTORING_VIA_QR, false)
|
||||
|
||||
|
||||
@@ -914,6 +914,10 @@ class RegistrationViewModel : ViewModel() {
|
||||
SignalStore.registration.restoreDecisionState = RestoreDecisionState.NewAccount
|
||||
}
|
||||
|
||||
if (remoteResult.reRegistration) {
|
||||
SignalStore.backup.backupSecretRestoreRequired = true
|
||||
}
|
||||
|
||||
if (reglockEnabled || SignalStore.account.restoredAccountEntropyPool) {
|
||||
SignalStore.onboarding.clearAll()
|
||||
|
||||
|
||||
@@ -122,6 +122,7 @@ class RemoteRestoreViewModel(isOnlyRestoreOption: Boolean) : ViewModel() {
|
||||
Log.i(TAG, "Restore successful", true)
|
||||
SignalStore.registration.restoreDecisionState = RestoreDecisionState.Completed
|
||||
|
||||
SignalStore.backup.backupSecretRestoreRequired = false
|
||||
StorageServiceRestore.restore()
|
||||
|
||||
store.update { it.copy(importState = ImportState.Restored) }
|
||||
|
||||
Reference in New Issue
Block a user