Make keybackup service suspend + fixes

This commit is contained in:
Valere 2021-11-25 12:19:31 +01:00
parent f0f64d8380
commit 210e0241d3
23 changed files with 2105 additions and 2163 deletions

View file

@ -24,6 +24,7 @@ interface StepProgressListener {
sealed class Step { sealed class Step {
data class ComputingKey(val progress: Int, val total: Int) : Step() data class ComputingKey(val progress: Int, val total: Int) : Step()
object DownloadingKey : Step() object DownloadingKey : Step()
data class DecryptingKey(val progress: Int, val total: Int) : Step()
data class ImportingKey(val progress: Int, val total: Int) : Step() data class ImportingKey(val progress: Int, val total: Int) : Step()
} }

View file

@ -16,7 +16,6 @@
package org.matrix.android.sdk.api.session.crypto.keysbackup package org.matrix.android.sdk.api.session.crypto.keysbackup
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.listeners.StepProgressListener import org.matrix.android.sdk.api.listeners.StepProgressListener
import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust
@ -31,18 +30,17 @@ interface KeysBackupService {
* Retrieve the current version of the backup from the homeserver * Retrieve the current version of the backup from the homeserver
* *
* It can be different than keysBackupVersion. * It can be different than keysBackupVersion.
* @param callback onSuccess(null) will be called if there is no backup on the server
*/ */
fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) suspend fun getCurrentVersion(): KeysVersionResult?
/** /**
* Create a new keys backup version and enable it, using the information return from [prepareKeysBackupVersion]. * Create a new keys backup version and enable it, using the information return from [prepareKeysBackupVersion].
* *
* @param keysBackupCreationInfo the info object from [prepareKeysBackupVersion]. * @param keysBackupCreationInfo the info object from [prepareKeysBackupVersion].
* @param callback Asynchronous callback * @return KeysVersion
*/ */
fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, @Throws
callback: MatrixCallback<KeysVersion>) suspend fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo): KeysVersion
/** /**
* Facility method to get the total number of locally stored keys * Facility method to get the total number of locally stored keys
@ -54,23 +52,21 @@ interface KeysBackupService {
*/ */
fun getTotalNumbersOfBackedUpKeys(): Int fun getTotalNumbersOfBackedUpKeys(): Int
/** // /**
* Start to back up keys immediately. // * Start to back up keys immediately.
* // *
* @param progressListener the callback to follow the progress // * @param progressListener the callback to follow the progress
* @param callback the main callback // * @param callback the main callback
*/ // */
fun backupAllGroupSessions(progressListener: ProgressListener?, // fun backupAllGroupSessions(progressListener: ProgressListener?,
callback: MatrixCallback<Unit>?) // callback: MatrixCallback<Unit>?)
/** /**
* Check trust on a key backup version. * Check trust on a key backup version.
* *
* @param keysBackupVersion the backup version to check. * @param keysBackupVersion the backup version to check.
* @param callback block called when the operations completes.
*/ */
fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult, suspend fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult): KeysBackupVersionTrust
callback: MatrixCallback<KeysBackupVersionTrust>)
/** /**
* Return the current progress of the backup * Return the current progress of the backup
@ -82,18 +78,16 @@ interface KeysBackupService {
* *
* It can be different than keysBackupVersion. * It can be different than keysBackupVersion.
* @param version the backup version * @param version the backup version
* @param callback
*/ */
fun getVersion(version: String, suspend fun getVersion(version: String): KeysVersionResult?
callback: MatrixCallback<KeysVersionResult?>)
/** /**
* This method fetches the last backup version on the server, then compare to the currently backup version use. * This method fetches the last backup version on the server, then compare to the currently backup version use.
* If versions are not the same, the current backup is deleted (on server or locally), then the backup may be started again, using the last version. * If versions are not the same, the current backup is deleted (on server or locally), then the backup may be started again, using the last version.
* *
* @param callback true if backup is already using the last version, and false if it is not the case * @return true if backup is already using the last version, and false if it is not the case
*/ */
fun forceUsingLastVersion(callback: MatrixCallback<Boolean>) suspend fun forceUsingLastVersion(): Boolean
/** /**
* Check the server for an active key backup. * Check the server for an active key backup.
@ -101,7 +95,7 @@ interface KeysBackupService {
* If one is present and has a valid signature from one of the user's verified * If one is present and has a valid signature from one of the user's verified
* devices, start backing up to it. * devices, start backing up to it.
*/ */
fun checkAndStartKeysBackup() suspend fun checkAndStartKeysBackup()
fun addListener(listener: KeysBackupStateListener) fun addListener(listener: KeysBackupStateListener)
@ -119,19 +113,16 @@ interface KeysBackupService {
* @param progressListener a progress listener, as generating private key from password may take a while * @param progressListener a progress listener, as generating private key from password may take a while
* @param callback Asynchronous callback * @param callback Asynchronous callback
*/ */
fun prepareKeysBackupVersion(password: String?, suspend fun prepareKeysBackupVersion(password: String?): MegolmBackupCreationInfo
progressListener: ProgressListener?,
callback: MatrixCallback<MegolmBackupCreationInfo>)
/** /**
* Delete a keys backup version. It will delete all backed up keys on the server, and the backup itself. * Delete a keys backup version. It will delete all backed up keys on the server, and the backup itself.
* If we are backing up to this version. Backup will be stopped. * If we are backing up to this version. Backup will be stopped.
* *
* @param version the backup version to delete. * @param version the backup version to delete.
* @param callback Asynchronous callback
*/ */
fun deleteBackup(version: String, @Throws
callback: MatrixCallback<Unit>?) suspend fun deleteBackup(version: String)
/** /**
* Ask if the backup on the server contains keys that we may do not have locally. * Ask if the backup on the server contains keys that we may do not have locally.
@ -145,35 +136,29 @@ interface KeysBackupService {
* *
* @param keysBackupVersion the backup version to check. * @param keysBackupVersion the backup version to check.
* @param trust the trust to set to the keys backup. * @param trust the trust to set to the keys backup.
* @param callback block called when the operations completes.
*/ */
fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, @Throws
trust: Boolean, suspend fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, trust: Boolean)
callback: MatrixCallback<Unit>)
/** /**
* Set trust on a keys backup version. * Set trust on a keys backup version.
* *
* @param keysBackupVersion the backup version to check. * @param keysBackupVersion the backup version to check.
* @param recoveryKey the recovery key to challenge with the key backup public key. * @param recoveryKey the recovery key to challenge with the key backup public key.
* @param callback block called when the operations completes.
*/ */
fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult,
recoveryKey: String, recoveryKey: String)
callback: MatrixCallback<Unit>)
/** /**
* Set trust on a keys backup version. * Set trust on a keys backup version.
* *
* @param keysBackupVersion the backup version to check. * @param keysBackupVersion the backup version to check.
* @param password the pass phrase to challenge with the keyBackupVersion public key. * @param password the pass phrase to challenge with the keyBackupVersion public key.
* @param callback block called when the operations completes.
*/ */
fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, suspend fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult,
password: String, password: String)
callback: MatrixCallback<Unit>)
fun onSecretKeyGossip(secret: String) suspend fun onSecretKeyGossip(secret: String)
/** /**
* Restore a backup with a recovery key from a given backup version stored on the homeserver. * Restore a backup with a recovery key from a given backup version stored on the homeserver.
@ -185,11 +170,10 @@ interface KeysBackupService {
* @param stepProgressListener the step progress listener * @param stepProgressListener the step progress listener
* @param callback Callback. It provides the number of found keys and the number of successfully imported keys. * @param callback Callback. It provides the number of found keys and the number of successfully imported keys.
*/ */
fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, suspend fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
recoveryKey: String, roomId: String?, recoveryKey: String, roomId: String?,
sessionId: String?, sessionId: String?,
stepProgressListener: StepProgressListener?, stepProgressListener: StepProgressListener?): ImportRoomKeysResult
callback: MatrixCallback<ImportRoomKeysResult>)
/** /**
* Restore a backup with a password from a given backup version stored on the homeserver. * Restore a backup with a password from a given backup version stored on the homeserver.
@ -201,12 +185,11 @@ interface KeysBackupService {
* @param stepProgressListener the step progress listener * @param stepProgressListener the step progress listener
* @param callback Callback. It provides the number of found keys and the number of successfully imported keys. * @param callback Callback. It provides the number of found keys and the number of successfully imported keys.
*/ */
fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, suspend fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult,
password: String, password: String,
roomId: String?, roomId: String?,
sessionId: String?, sessionId: String?,
stepProgressListener: StepProgressListener?, stepProgressListener: StepProgressListener?): ImportRoomKeysResult
callback: MatrixCallback<ImportRoomKeysResult>)
val keysBackupVersion: KeysVersionResult? val keysBackupVersion: KeysVersionResult?
val currentBackupVersion: String? val currentBackupVersion: String?
@ -218,5 +201,5 @@ interface KeysBackupService {
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback<Boolean>) suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean
} }

View file

@ -256,7 +256,12 @@ internal class DefaultCryptoService @Inject constructor(
} }
override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int { override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
return cryptoStore.inboundGroupSessionsCount(onlyBackedUp) return if (onlyBackedUp) {
keysBackupService.getTotalNumbersOfBackedUpKeys()
} else {
keysBackupService.getTotalNumbersOfKeys()
}
// return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
} }
/** /**
@ -331,9 +336,11 @@ internal class DefaultCryptoService @Inject constructor(
// We try to enable key backups, if the backup version on the server is trusted, // We try to enable key backups, if the backup version on the server is trusted,
// we're gonna continue backing up. // we're gonna continue backing up.
cryptoCoroutineScope.launch {
tryOrNull { tryOrNull {
keysBackupService.checkAndStartKeysBackup() keysBackupService.checkAndStartKeysBackup()
} }
}
// Open the store // Open the store
cryptoStore.open() cryptoStore.open()

View file

@ -70,4 +70,15 @@ data class MegolmSessionData(
*/ */
@Json(name = "forwarding_curve25519_key_chain") @Json(name = "forwarding_curve25519_key_chain")
val forwardingCurve25519KeyChain: List<String>? = null val forwardingCurve25519KeyChain: List<String>? = null
) ) {
fun isValid(): Boolean {
return roomId != null &&
forwardingCurve25519KeyChain != null &&
algorithm != null &&
senderKey != null &&
senderClaimedKeys != null &&
sessionId != null &&
sessionKey != null
}
}

View file

@ -213,13 +213,14 @@ internal class RequestSender @Inject constructor(
suspend fun getKeyBackupVersion(version: String? = null): KeysVersionResult? { suspend fun getKeyBackupVersion(version: String? = null): KeysVersionResult? {
return try { return try {
if (version != null) { if (version != null) {
getKeysBackupVersionTask.execute(version) getKeysBackupVersionTask.executeRetry(version, 3)
} else { } else {
getKeysBackupLastVersionTask.execute(Unit) getKeysBackupLastVersionTask.executeRetry(Unit, 3)
} }
} catch (failure: Throwable) { } catch (failure: Throwable) {
if (failure is Failure.ServerError && if (failure is Failure.ServerError &&
failure.error.code == MatrixError.M_NOT_FOUND) { failure.error.code == MatrixError.M_NOT_FOUND) {
// Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup
null null
} else { } else {
throw failure throw failure

View file

@ -21,7 +21,6 @@ import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest
import org.matrix.android.sdk.internal.crypto.IncomingSecretShareRequest import org.matrix.android.sdk.internal.crypto.IncomingSecretShareRequest
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
/** /**
* An interface for decrypting data * An interface for decrypting data
@ -44,7 +43,7 @@ internal interface IMXDecrypting {
* *
* @param event the key event. * @param event the key event.
*/ */
fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {} fun onRoomKeyEvent(event: Event/*, defaultKeysBackupService: DefaultKeysBackupService*/) {}
/** /**
* Check if the some messages can be decrypted with a new session * Check if the some messages can be decrypted with a new session

View file

@ -34,7 +34,6 @@ import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevice
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting
import org.matrix.android.sdk.internal.crypto.algorithms.IMXWithHeldExtension import org.matrix.android.sdk.internal.crypto.algorithms.IMXWithHeldExtension
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent
@ -232,7 +231,7 @@ internal class MXMegolmDecryption(private val userId: String,
* *
* @param event the key event. * @param event the key event.
*/ */
override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) { override fun onRoomKeyEvent(event: Event/*, defaultKeysBackupService: DefaultKeysBackupService*/) {
Timber.tag(loggerTag.value).v("onRoomKeyEvent()") Timber.tag(loggerTag.value).v("onRoomKeyEvent()")
var exportFormat = false var exportFormat = false
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return
@ -295,7 +294,7 @@ internal class MXMegolmDecryption(private val userId: String,
exportFormat) exportFormat)
if (added) { if (added) {
defaultKeysBackupService.maybeBackupKeys() // defaultKeysBackupService.maybeBackupKeys()
val content = RoomKeyRequestBody( val content = RoomKeyRequestBody(
algorithm = roomKeyContent.algorithm, algorithm = roomKeyContent.algorithm,

View file

@ -31,7 +31,7 @@ import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevice
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting
import org.matrix.android.sdk.internal.crypto.algorithms.IMXGroupEncryption import org.matrix.android.sdk.internal.crypto.algorithms.IMXGroupEncryption
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService // import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
@ -53,7 +53,7 @@ internal class MXMegolmEncryption(
// The id of the room we will be sending to. // The id of the room we will be sending to.
private val roomId: String, private val roomId: String,
private val olmDevice: MXOlmDevice, private val olmDevice: MXOlmDevice,
private val defaultKeysBackupService: DefaultKeysBackupService, // private val defaultKeysBackupService: DefaultKeysBackupService,
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCryptoStore,
private val deviceListManager: DeviceListManager, private val deviceListManager: DeviceListManager,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
@ -149,7 +149,7 @@ internal class MXMegolmEncryption(
olmDevice.addInboundGroupSession(sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!, olmDevice.addInboundGroupSession(sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!,
emptyList(), keysClaimedMap, false) emptyList(), keysClaimedMap, false)
defaultKeysBackupService.maybeBackupKeys() // defaultKeysBackupService.maybeBackupKeys()
return MXOutboundSessionInfo(sessionId, SharedWithHelper(roomId, sessionId, cryptoStore)) return MXOutboundSessionInfo(sessionId, SharedWithHelper(roomId, sessionId, cryptoStore))
} }

View file

@ -22,7 +22,6 @@ import org.matrix.android.sdk.internal.crypto.DeviceListManager
import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.MXOlmDevice
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
@ -32,7 +31,7 @@ import javax.inject.Inject
internal class MXMegolmEncryptionFactory @Inject constructor( internal class MXMegolmEncryptionFactory @Inject constructor(
private val olmDevice: MXOlmDevice, private val olmDevice: MXOlmDevice,
private val defaultKeysBackupService: DefaultKeysBackupService, // private val defaultKeysBackupService: DefaultKeysBackupService,
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCryptoStore,
private val deviceListManager: DeviceListManager, private val deviceListManager: DeviceListManager,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
@ -48,7 +47,7 @@ internal class MXMegolmEncryptionFactory @Inject constructor(
return MXMegolmEncryption( return MXMegolmEncryption(
roomId = roomId, roomId = roomId,
olmDevice = olmDevice, olmDevice = olmDevice,
defaultKeysBackupService = defaultKeysBackupService, // defaultKeysBackupService = defaultKeysBackupService,
cryptoStore = cryptoStore, cryptoStore = cryptoStore,
deviceListManager = deviceListManager, deviceListManager = deviceListManager,
ensureOlmSessionsForDevicesAction = ensureOlmSessionsForDevicesAction, ensureOlmSessionsForDevicesAction = ensureOlmSessionsForDevicesAction,

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.crypto.keysbackup package org.matrix.android.sdk.internal.crypto.keysbackup
import android.os.Handler import android.os.Handler
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
import timber.log.Timber import timber.log.Timber
@ -33,15 +34,17 @@ internal class KeysBackupStateManager(private val uiHandler: Handler) {
field = newState field = newState
// Notify listeners about the state change, on the ui thread // Notify listeners about the state change, on the ui thread
uiHandler.post {
synchronized(listeners) { synchronized(listeners) {
listeners.forEach { listeners.forEach {
uiHandler.post {
// Use newState because state may have already changed again // Use newState because state may have already changed again
tryOrNull {
it.onStateChange(newState) it.onStateChange(newState)
} }
} }
} }
} }
}
val isEnabled: Boolean val isEnabled: Boolean
get() = state == KeysBackupState.ReadyToBackUp || get() = state == KeysBackupState.ReadyToBackUp ||

View file

@ -18,16 +18,16 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import androidx.annotation.UiThread
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.listeners.ProgressListener
@ -52,10 +52,8 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBa
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.extensions.foldToCallback
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.util.JsonCanonicalizer import org.matrix.android.sdk.internal.util.JsonCanonicalizer
import org.matrix.android.sdk.internal.util.awaitCallback
import org.matrix.olm.OlmException import org.matrix.olm.OlmException
import timber.log.Timber import timber.log.Timber
import uniffi.olm.BackupRecoveryKey import uniffi.olm.BackupRecoveryKey
@ -91,7 +89,9 @@ internal class RustKeyBackupService @Inject constructor(
override var keysBackupVersion: KeysVersionResult? = null override var keysBackupVersion: KeysVersionResult? = null
private set private set
private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null // private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null
private val importScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private var keysBackupStateListener: KeysBackupStateListener? = null private var keysBackupStateListener: KeysBackupStateListener? = null
@ -115,13 +115,10 @@ internal class RustKeyBackupService @Inject constructor(
keysBackupStateManager.removeListener(listener) keysBackupStateManager.removeListener(listener)
} }
override fun prepareKeysBackupVersion(password: String?, override suspend fun prepareKeysBackupVersion(password: String?): MegolmBackupCreationInfo {
progressListener: ProgressListener?, return withContext(coroutineDispatchers.computation) {
callback: MatrixCallback<MegolmBackupCreationInfo>) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
runCatching {
withContext(coroutineDispatchers.crypto) {
val key = if (password != null) { val key = if (password != null) {
// this might be a bit slow as it's stretching the password
BackupRecoveryKey.newFromPassphrase(password) BackupRecoveryKey.newFromPassphrase(password)
} else { } else {
BackupRecoveryKey() BackupRecoveryKey()
@ -151,13 +148,10 @@ internal class RustKeyBackupService @Inject constructor(
recoveryKey = key.toBase58() recoveryKey = key.toBase58()
) )
} }
}.foldToCallback(callback)
}
} }
override fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, override suspend fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo): KeysVersion {
callback: MatrixCallback<KeysVersion>) { return withContext(coroutineDispatchers.crypto) {
@Suppress("UNCHECKED_CAST")
val createKeysBackupVersionBody = CreateKeysBackupVersionBody( val createKeysBackupVersionBody = CreateKeysBackupVersionBody(
algorithm = keysBackupCreationInfo.algorithm, algorithm = keysBackupCreationInfo.algorithm,
authData = keysBackupCreationInfo.authData.toJsonDict() authData = keysBackupCreationInfo.authData.toJsonDict()
@ -165,14 +159,13 @@ internal class RustKeyBackupService @Inject constructor(
keysBackupStateManager.state = KeysBackupState.Enabling keysBackupStateManager.state = KeysBackupState.Enabling
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
try { try {
val data = sender.createKeyBackup(createKeysBackupVersionBody) val data = withContext(coroutineDispatchers.io) {
sender.createKeyBackup(createKeysBackupVersionBody)
}
// Reset backup markers. // Reset backup markers.
// Don't we need to join the task here? Isn't this a race condition? // Don't we need to join the task here? Isn't this a race condition?
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
olmMachine.disableBackup() olmMachine.disableBackup()
}
val keyBackupVersion = KeysVersionResult( val keyBackupVersion = KeysVersionResult(
algorithm = createKeysBackupVersionBody.algorithm, algorithm = createKeysBackupVersionBody.algorithm,
@ -182,13 +175,11 @@ internal class RustKeyBackupService @Inject constructor(
count = 0, count = 0,
hash = "" hash = ""
) )
enableKeysBackup(keyBackupVersion) enableKeysBackup(keyBackupVersion)
data
callback.onSuccess(data)
} catch (failure: Throwable) { } catch (failure: Throwable) {
keysBackupStateManager.state = KeysBackupState.Disabled keysBackupStateManager.state = KeysBackupState.Disabled
callback.onFailure(failure) throw failure
} }
} }
} }
@ -200,7 +191,7 @@ internal class RustKeyBackupService @Inject constructor(
} }
private fun resetBackupAllGroupSessionsListeners() { private fun resetBackupAllGroupSessionsListeners() {
backupAllGroupSessionsCallback = null // backupAllGroupSessionsCallback = null
keysBackupStateListener?.let { keysBackupStateListener?.let {
keysBackupStateManager.removeListener(it) keysBackupStateManager.removeListener(it)
@ -219,8 +210,7 @@ internal class RustKeyBackupService @Inject constructor(
olmMachine.disableBackup() olmMachine.disableBackup()
} }
override fun deleteBackup(version: String, callback: MatrixCallback<Unit>?) { override suspend fun deleteBackup(version: String) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
withContext(coroutineDispatchers.crypto) { withContext(coroutineDispatchers.crypto) {
if (keysBackupVersion != null && version == keysBackupVersion?.version) { if (keysBackupVersion != null && version == keysBackupVersion?.version) {
resetKeysBackupData() resetKeysBackupData()
@ -228,20 +218,16 @@ internal class RustKeyBackupService @Inject constructor(
keysBackupStateManager.state = KeysBackupState.Unknown keysBackupStateManager.state = KeysBackupState.Unknown
} }
fun eventuallyRestartBackup() { try {
sender.deleteKeyBackup(version)
// Do not stay in KeysBackupState.Unknown but check what is available on the homeserver // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver
if (state == KeysBackupState.Unknown) { if (state == KeysBackupState.Unknown) {
checkAndStartKeysBackup() checkAndStartKeysBackup()
} }
}
try {
sender.deleteKeyBackup(version)
eventuallyRestartBackup()
uiHandler.post { callback?.onSuccess(Unit) }
} catch (failure: Throwable) { } catch (failure: Throwable) {
eventuallyRestartBackup() // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver
uiHandler.post { callback?.onFailure(failure) } if (state == KeysBackupState.Unknown) {
checkAndStartKeysBackup()
} }
} }
} }
@ -264,12 +250,12 @@ internal class RustKeyBackupService @Inject constructor(
return olmMachine.roomKeyCounts().backedUp.toInt() return olmMachine.roomKeyCounts().backedUp.toInt()
} }
override fun backupAllGroupSessions(progressListener: ProgressListener?, // override fun backupAllGroupSessions(progressListener: ProgressListener?,
callback: MatrixCallback<Unit>?) { // callback: MatrixCallback<Unit>?) {
// This is only used in tests? While it's fine have methods that are // // This is only used in tests? While it's fine have methods that are
// only used for tests, this one has a lot of logic that is nowhere else used. // // only used for tests, this one has a lot of logic that is nowhere else used.
TODO() // TODO()
} // }
private suspend fun checkBackupTrust(authData: MegolmBackupAuthData?): KeysBackupVersionTrust { private suspend fun checkBackupTrust(authData: MegolmBackupAuthData?): KeysBackupVersionTrust {
return if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isNullOrEmpty()) { return if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isNullOrEmpty()) {
@ -280,22 +266,15 @@ internal class RustKeyBackupService @Inject constructor(
} }
} }
override fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult, override suspend fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult): KeysBackupVersionTrust {
callback: MatrixCallback<KeysBackupVersionTrust>) {
val authData = keysBackupVersion.getAuthDataAsMegolmBackupAuthData() val authData = keysBackupVersion.getAuthDataAsMegolmBackupAuthData()
return withContext(coroutineDispatchers.crypto) {
cryptoCoroutineScope.launch { checkBackupTrust(authData)
try {
callback.onSuccess(checkBackupTrust(authData))
} catch (exception: Throwable) {
callback.onFailure(exception)
}
} }
} }
override fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, override suspend fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, trust: Boolean) {
trust: Boolean, withContext(coroutineDispatchers.crypto) {
callback: MatrixCallback<Unit>) {
Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}") Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}")
// Get auth data to update it // Get auth data to update it
@ -303,11 +282,8 @@ internal class RustKeyBackupService @Inject constructor(
if (authData == null) { if (authData == null) {
Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data") Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data")
throw IllegalArgumentException("Missing element")
callback.onFailure(IllegalArgumentException("Missing element"))
} else { } else {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
val body = withContext(coroutineDispatchers.crypto) {
// Get current signatures, or create an empty set // Get current signatures, or create an empty set
val userId = olmMachine.userId() val userId = olmMachine.userId()
val signatures = authData.signatures?.get(userId).orEmpty().toMutableMap() val signatures = authData.signatures?.get(userId).orEmpty().toMutableMap()
@ -328,14 +304,14 @@ internal class RustKeyBackupService @Inject constructor(
val newSignatures = newAuthData.signatures.orEmpty().toMutableMap() val newSignatures = newAuthData.signatures.orEmpty().toMutableMap()
newSignatures[userId] = signatures newSignatures[userId] = signatures
@Suppress("UNCHECKED_CAST") val body = UpdateKeysBackupVersionBody(
UpdateKeysBackupVersionBody(
algorithm = keysBackupVersion.algorithm, algorithm = keysBackupVersion.algorithm,
authData = newAuthData.copy(signatures = newSignatures).toJsonDict(), authData = newAuthData.copy(signatures = newSignatures).toJsonDict(),
version = keysBackupVersion.version) version = keysBackupVersion.version)
}
try { withContext(coroutineDispatchers.io) {
sender.updateBackup(keysBackupVersion, body) sender.updateBackup(keysBackupVersion, body)
}
val newKeysBackupVersion = KeysVersionResult( val newKeysBackupVersion = KeysVersionResult(
algorithm = keysBackupVersion.algorithm, algorithm = keysBackupVersion.algorithm,
@ -346,10 +322,6 @@ internal class RustKeyBackupService @Inject constructor(
) )
checkAndStartWithKeysBackupVersion(newKeysBackupVersion) checkAndStartWithKeysBackupVersion(newKeysBackupVersion)
callback.onSuccess(Unit)
} catch (exception: Throwable) {
callback.onFailure(exception)
}
} }
} }
} }
@ -376,60 +348,50 @@ internal class RustKeyBackupService @Inject constructor(
} }
} }
override fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, override suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, recoveryKey: String) {
recoveryKey: String,
callback: MatrixCallback<Unit>) {
Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}") Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}")
withContext(coroutineDispatchers.crypto) {
cryptoCoroutineScope.launch {
try {
// This is ~nowhere mentioned, the string here is actually a base58 encoded key. // This is ~nowhere mentioned, the string here is actually a base58 encoded key.
// This not really supported by the spec for the backup key, the 4S key supports // This not really supported by the spec for the backup key, the 4S key supports
// base58 encoding and the same method seems to be used here. // base58 encoding and the same method seems to be used here.
val key = BackupRecoveryKey.fromBase58(recoveryKey) val key = BackupRecoveryKey.fromBase58(recoveryKey)
checkRecoveryKey(key, keysBackupVersion) checkRecoveryKey(key, keysBackupVersion)
trustKeysBackupVersion(keysBackupVersion, true, callback) trustKeysBackupVersion(keysBackupVersion, true)
} catch (exception: Throwable) {
callback.onFailure(exception)
}
} }
} }
override fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, override suspend fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, password: String) {
password: String, withContext(coroutineDispatchers.crypto) {
callback: MatrixCallback<Unit>) {
cryptoCoroutineScope.launch {
try {
val key = recoveryKeyFromPassword(password, keysBackupVersion) val key = recoveryKeyFromPassword(password, keysBackupVersion)
checkRecoveryKey(key, keysBackupVersion) checkRecoveryKey(key, keysBackupVersion)
trustKeysBackupVersion(keysBackupVersion, true, callback) trustKeysBackupVersion(keysBackupVersion, true)
} catch (exception: Throwable) {
Timber.w(exception)
callback.onFailure(exception)
}
} }
} }
override fun onSecretKeyGossip(secret: String) { override suspend fun onSecretKeyGossip(curveKeyBase64: String) {
Timber.i("## CrossSigning - onSecretKeyGossip") Timber.i("## CrossSigning - onSecretKeyGossip")
cryptoCoroutineScope.launch(coroutineDispatchers.main) { withContext(coroutineDispatchers.crypto) {
try { try {
val version = sender.getKeyBackupVersion() val version = sender.getKeyBackupVersion()
if (version != null) { if (version != null) {
val key = BackupRecoveryKey.fromBase64(secret) val key = BackupRecoveryKey.fromBase64(curveKeyBase64)
if (isValidRecoveryKey(key, version)) {
trustKeysBackupVersion(version, true)
// we don't want to wait for that
importScope.launch {
try {
val importResult = restoreBackup(version, key, null, null, null)
awaitCallback<Unit> {
trustKeysBackupVersion(version, true, it)
}
val importResult = awaitCallback<ImportRoomKeysResult> {
cryptoCoroutineScope.launch {
restoreBackup(version, key, null, null, null)
}
}
Timber.i("onSecretKeyGossip: Recovered keys ${importResult.successfullyNumberOfImportedKeys} out of ${importResult.totalNumberOfKeys}") Timber.i("onSecretKeyGossip: Recovered keys ${importResult.successfullyNumberOfImportedKeys} out of ${importResult.totalNumberOfKeys}")
} catch (failure: Throwable) {
saveBackupRecoveryKey(secret, version.version) // fail silently..
Timber.e(failure, "onSecretKeyGossip: Failed to import keys from backup")
}
}
// we can save, it's valid
saveBackupRecoveryKey(key.toBase64(), version.version)
}
} else { } else {
Timber.e("onSecretKeyGossip: Failed to import backup recovery key, no backup version was found on the server") Timber.e("onSecretKeyGossip: Failed to import backup recovery key, no backup version was found on the server")
} }
@ -511,9 +473,14 @@ internal class RustKeyBackupService @Inject constructor(
Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version") Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version")
throw InvalidParameterException("Invalid recovery key") throw InvalidParameterException("Invalid recovery key")
} }
// Save for next time and for gossiping
saveBackupRecoveryKey(recoveryKey.toBase64(), keysVersionResult.version)
} }
withContext(coroutineDispatchers.main) {
stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey) stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey)
}
// Get backed up keys from the homeserver // Get backed up keys from the homeserver
val data = getKeys(sessionId, roomId, keysVersionResult.version) val data = getKeys(sessionId, roomId, keysVersionResult.version)
@ -522,13 +489,33 @@ internal class RustKeyBackupService @Inject constructor(
val sessionsData = ArrayList<MegolmSessionData>() val sessionsData = ArrayList<MegolmSessionData>()
// Restore that data // Restore that data
var sessionsFromHsCount = 0 var sessionsFromHsCount = 0
cryptoCoroutineScope.launch(Dispatchers.Main) {
stepProgressListener?.onStepProgress(StepProgressListener.Step.DecryptingKey(0, data.roomIdToRoomKeysBackupData.size))
}
var progressDecryptIndex = 0
// TODO this is quite long, could we add some concurrency here?
for ((roomIdLoop, backupData) in data.roomIdToRoomKeysBackupData) { for ((roomIdLoop, backupData) in data.roomIdToRoomKeysBackupData) {
val roomIndex = progressDecryptIndex
progressDecryptIndex++
cryptoCoroutineScope.launch(Dispatchers.Main) {
stepProgressListener?.onStepProgress(StepProgressListener.Step.DecryptingKey(roomIndex, data.roomIdToRoomKeysBackupData.size))
}
for ((sessionIdLoop, keyBackupData) in backupData.sessionIdToKeyBackupData) { for ((sessionIdLoop, keyBackupData) in backupData.sessionIdToKeyBackupData) {
sessionsFromHsCount++ sessionsFromHsCount++
val sessionData = decryptKeyBackupData(keyBackupData, sessionIdLoop, roomIdLoop, recoveryKey) val sessionData = decryptKeyBackupData(keyBackupData, sessionIdLoop, roomIdLoop, recoveryKey)
sessionData?.let { // rust is not very lax and will throw if field are missing,
// add a check
// TODO maybe could be done on rust side?
sessionData?.takeIf {
it.isValid().also {
if (!it) {
Timber.w("restoreKeysWithRecoveryKey: malformed sessionData $sessionData")
}
}
}?.let {
sessionsData.add(it) sessionsData.add(it)
} }
} }
@ -548,9 +535,11 @@ internal class RustKeyBackupService @Inject constructor(
object : ProgressListener { object : ProgressListener {
override fun onProgress(progress: Int, total: Int) { override fun onProgress(progress: Int, total: Int) {
// Note: no need to post to UI thread, importMegolmSessionsData() will do it // Note: no need to post to UI thread, importMegolmSessionsData() will do it
cryptoCoroutineScope.launch(Dispatchers.Main) {
stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total)) stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total))
} }
} }
}
} else { } else {
null null
} }
@ -562,69 +551,60 @@ internal class RustKeyBackupService @Inject constructor(
maybeBackupKeys() maybeBackupKeys()
} }
// Save for next time and for gossiping
saveBackupRecoveryKey(recoveryKey.toBase64(), keysVersionResult.version)
result result
} }
} }
override fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, override suspend fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
recoveryKey: String, recoveryKey: String,
roomId: String?, roomId: String?,
sessionId: String?, sessionId: String?,
stepProgressListener: StepProgressListener?, stepProgressListener: StepProgressListener?): ImportRoomKeysResult {
callback: MatrixCallback<ImportRoomKeysResult>) {
Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}") Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}")
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
runCatching {
val key = BackupRecoveryKey.fromBase58(recoveryKey) val key = BackupRecoveryKey.fromBase58(recoveryKey)
restoreBackup(keysVersionResult, key, roomId, sessionId, stepProgressListener)
}.foldToCallback(callback) return restoreBackup(keysVersionResult, key, roomId, sessionId, stepProgressListener)
}
} }
override fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, override suspend fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult,
password: String, password: String,
roomId: String?, roomId: String?,
sessionId: String?, sessionId: String?,
stepProgressListener: StepProgressListener?, stepProgressListener: StepProgressListener?): ImportRoomKeysResult {
callback: MatrixCallback<ImportRoomKeysResult>) {
Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}") Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}")
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
runCatching {
val recoveryKey = withContext(coroutineDispatchers.crypto) { val recoveryKey = withContext(coroutineDispatchers.crypto) {
recoveryKeyFromPassword(password, keysBackupVersion) recoveryKeyFromPassword(password, keysBackupVersion)
} }
restoreBackup(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener) return restoreBackup(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener)
}.foldToCallback(callback)
}
} }
override fun getVersion(version: String, callback: MatrixCallback<KeysVersionResult?>) { override suspend fun getVersion(version: String): KeysVersionResult? {
cryptoCoroutineScope.launch(coroutineDispatchers.main) { return withContext(coroutineDispatchers.io) {
runCatching {
sender.getKeyBackupVersion(version) sender.getKeyBackupVersion(version)
}.foldToCallback(callback)
} }
} }
override fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) { @Throws
cryptoCoroutineScope.launch(coroutineDispatchers.main) { override suspend fun getCurrentVersion(): KeysVersionResult? {
runCatching { return withContext(coroutineDispatchers.io) {
sender.getKeyBackupVersion() sender.getKeyBackupVersion()
}.foldToCallback(callback)
} }
} }
private suspend fun forceUsingLastVersionHelper(): Boolean { override suspend fun forceUsingLastVersion(): Boolean {
val response = sender.getKeyBackupVersion() val response = withContext(coroutineDispatchers.io) {
sender.getKeyBackupVersion()
}
return withContext(coroutineDispatchers.crypto) {
val serverBackupVersion = response?.version val serverBackupVersion = response?.version
val localBackupVersion = keysBackupVersion?.version val localBackupVersion = keysBackupVersion?.version
Timber.d("BACKUP: $serverBackupVersion") Timber.d("BACKUP: $serverBackupVersion")
return if (serverBackupVersion == null) { if (serverBackupVersion == null) {
if (localBackupVersion == null) { if (localBackupVersion == null) {
// No backup on the server, and backup is not active // No backup on the server, and backup is not active
true true
@ -648,46 +628,43 @@ internal class RustKeyBackupService @Inject constructor(
true true
} else { } else {
// This will automatically check for the last version then // This will automatically check for the last version then
deleteBackup(localBackupVersion, null) tryOrNull("Failed to automatically check for the last version") {
deleteBackup(localBackupVersion)
}
// We are not using the last version, so delete the current version we are using on the server // We are not using the last version, so delete the current version we are using on the server
false false
} }
} }
} }
} }
override fun forceUsingLastVersion(callback: MatrixCallback<Boolean>) {
cryptoCoroutineScope.launch {
runCatching {
forceUsingLastVersionHelper()
}.foldToCallback(callback)
}
} }
override fun checkAndStartKeysBackup() { override suspend fun checkAndStartKeysBackup() {
withContext(coroutineDispatchers.crypto) {
if (!isStucked) { if (!isStucked) {
// Try to start or restart the backup only if it is in unknown or bad state // Try to start or restart the backup only if it is in unknown or bad state
Timber.w("checkAndStartKeysBackup: invalid state: $state") Timber.w("checkAndStartKeysBackup: invalid state: $state")
return@withContext
return
} }
keysBackupVersion = null keysBackupVersion = null
keysBackupStateManager.state = KeysBackupState.CheckingBackUpOnHomeserver keysBackupStateManager.state = KeysBackupState.CheckingBackUpOnHomeserver
getCurrentVersion(object : MatrixCallback<KeysVersionResult?> { withContext(coroutineDispatchers.io) {
override fun onSuccess(data: KeysVersionResult?) { try {
val data = getCurrentVersion()
withContext(coroutineDispatchers.crypto) {
checkAndStartWithKeysBackupVersion(data) checkAndStartWithKeysBackupVersion(data)
} }
} catch (failure: Throwable) {
override fun onFailure(failure: Throwable) {
Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version") Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version")
keysBackupStateManager.state = KeysBackupState.Unknown keysBackupStateManager.state = KeysBackupState.Unknown
} }
}) }
}
} }
private fun checkAndStartWithKeysBackupVersion(keyBackupVersion: KeysVersionResult?) { private suspend fun checkAndStartWithKeysBackupVersion(keyBackupVersion: KeysVersionResult?) {
Timber.v("checkAndStartWithKeyBackupVersion: ${keyBackupVersion?.version}") Timber.v("checkAndStartWithKeyBackupVersion: ${keyBackupVersion?.version}")
keysBackupVersion = keyBackupVersion keysBackupVersion = keyBackupVersion
@ -697,8 +674,8 @@ internal class RustKeyBackupService @Inject constructor(
resetKeysBackupData() resetKeysBackupData()
keysBackupStateManager.state = KeysBackupState.Disabled keysBackupStateManager.state = KeysBackupState.Disabled
} else { } else {
getKeysBackupTrust(keyBackupVersion, object : MatrixCallback<KeysBackupVersionTrust> { try {
override fun onSuccess(data: KeysBackupVersionTrust) { val data = getKeysBackupTrust(keyBackupVersion)
val versionInStore = getKeyBackupRecoveryKeyInfo()?.version val versionInStore = getKeyBackupRecoveryKeyInfo()?.version
if (data.usable) { if (data.usable) {
@ -722,12 +699,9 @@ internal class RustKeyBackupService @Inject constructor(
keysBackupStateManager.state = KeysBackupState.NotTrusted keysBackupStateManager.state = KeysBackupState.NotTrusted
} }
} catch (failure: Throwable) {
Timber.e(failure, "Failed to checkAndStartWithKeysBackupVersion $keyBackupVersion")
} }
override fun onFailure(failure: Throwable) {
// Cannot happen
}
})
} }
} }
@ -737,14 +711,17 @@ internal class RustKeyBackupService @Inject constructor(
return authData.publicKey == publicKey return authData.publicKey == publicKey
} }
override fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback<Boolean>) { override suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean {
val keysBackupVersion = keysBackupVersion ?: return Unit.also { callback.onSuccess(false) } return withContext(coroutineDispatchers.crypto) {
val keysBackupVersion = keysBackupVersion ?: return@withContext false
try {
val key = BackupRecoveryKey.fromBase64(recoveryKey) val key = BackupRecoveryKey.fromBase64(recoveryKey)
callback.onSuccess(isValidRecoveryKey(key, keysBackupVersion)) try {
} catch (error: Throwable) { isValidRecoveryKey(key, keysBackupVersion)
callback.onFailure(error) } catch (failure: Throwable) {
Timber.i("isValidRecoveryKeyForCurrentVersion: Invalid recovery key")
false
}
} }
} }
@ -822,7 +799,8 @@ internal class RustKeyBackupService @Inject constructor(
/** /**
* Do a backup if there are new keys, with a delay * Do a backup if there are new keys, with a delay
*/ */
fun maybeBackupKeys() { suspend fun maybeBackupKeys() {
withContext(coroutineDispatchers.crypto) {
when { when {
isStucked -> { isStucked -> {
// If not already done, or in error case, check for a valid backup version on the homeserver. // If not already done, or in error case, check for a valid backup version on the homeserver.
@ -837,12 +815,9 @@ internal class RustKeyBackupService @Inject constructor(
// new key is sent // new key is sent
val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS) val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS)
cryptoCoroutineScope.launch { importScope.launch {
delay(delayInMs) delay(delayInMs)
// TODO is this correct? we used to call uiHandler.post() instead of this tryOrNull("AUTO backup failed") { backupKeys() }
withContext(Dispatchers.Main) {
backupKeys()
}
} }
} }
else -> { else -> {
@ -850,27 +825,27 @@ internal class RustKeyBackupService @Inject constructor(
} }
} }
} }
}
/** /**
* Send a chunk of keys to backup * Send a chunk of keys to backup
*/ */
@UiThread
private suspend fun backupKeys(forceRecheck: Boolean = false) { private suspend fun backupKeys(forceRecheck: Boolean = false) {
Timber.v("backupKeys") Timber.v("backupKeys")
withContext(coroutineDispatchers.crypto) {
// Sanity check, as this method can be called after a delay, the state may have change during the delay // Sanity check, as this method can be called after a delay, the state may have change during the delay
if (!isEnabled || !olmMachine.backupEnabled() || keysBackupVersion == null) { if (!isEnabled || !olmMachine.backupEnabled() || keysBackupVersion == null) {
Timber.v("backupKeys: Invalid configuration $isEnabled ${olmMachine.backupEnabled()} $keysBackupVersion") Timber.v("backupKeys: Invalid configuration $isEnabled ${olmMachine.backupEnabled()} $keysBackupVersion")
backupAllGroupSessionsCallback?.onFailure(IllegalStateException("Invalid configuration")) // backupAllGroupSessionsCallback?.onFailure(IllegalStateException("Invalid configuration"))
resetBackupAllGroupSessionsListeners() resetBackupAllGroupSessionsListeners()
return return@withContext
} }
if (state === KeysBackupState.BackingUp && !forceRecheck) { if (state === KeysBackupState.BackingUp && !forceRecheck) {
// Do nothing if we are already backing up // Do nothing if we are already backing up
Timber.v("backupKeys: Invalid state: $state") Timber.v("backupKeys: Invalid state: $state")
return return@withContext
} }
Timber.d("BACKUP: CREATING REQUEST") Timber.d("BACKUP: CREATING REQUEST")
@ -884,7 +859,7 @@ internal class RustKeyBackupService @Inject constructor(
// Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess() // Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess()
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
backupAllGroupSessionsCallback?.onSuccess(Unit) // backupAllGroupSessionsCallback?.onSuccess(Unit)
resetBackupAllGroupSessionsListeners() resetBackupAllGroupSessionsListeners()
} else { } else {
try { try {
@ -892,15 +867,12 @@ internal class RustKeyBackupService @Inject constructor(
keysBackupStateManager.state = KeysBackupState.BackingUp keysBackupStateManager.state = KeysBackupState.BackingUp
Timber.d("BACKUP SENDING REQUEST") Timber.d("BACKUP SENDING REQUEST")
val response = sender.backupRoomKeys(request) val response = withContext(coroutineDispatchers.io) { sender.backupRoomKeys(request) }
Timber.d("BACKUP GOT RESPONSE $response") Timber.d("BACKUP GOT RESPONSE $response")
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_BACKUP, response) olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_BACKUP, response)
Timber.d("BACKUP MARKED REQUEST AS SENT") Timber.d("BACKUP MARKED REQUEST AS SENT")
// TODO, again is this correct?
withContext(Dispatchers.Main) {
backupKeys(true) backupKeys(true)
}
} else { } else {
// Can't happen, do we want to panic? // Can't happen, do we want to panic?
} }
@ -915,7 +887,7 @@ internal class RustKeyBackupService @Inject constructor(
// Backup has been deleted on the server, or we are not using // Backup has been deleted on the server, or we are not using
// the last backup version // the last backup version
keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion
backupAllGroupSessionsCallback?.onFailure(failure) // backupAllGroupSessionsCallback?.onFailure(failure)
resetBackupAllGroupSessionsListeners() resetBackupAllGroupSessionsListeners()
resetKeysBackupData() resetKeysBackupData()
keysBackupVersion = null keysBackupVersion = null
@ -930,8 +902,7 @@ internal class RustKeyBackupService @Inject constructor(
} }
} }
} else { } else {
withContext(Dispatchers.Main) { // backupAllGroupSessionsCallback?.onFailure(failure)
backupAllGroupSessionsCallback?.onFailure(failure)
resetBackupAllGroupSessionsListeners() resetBackupAllGroupSessionsListeners()
Timber.e("backupKeys: backupKeys failed: $failure") Timber.e("backupKeys: backupKeys failed: $failure")

View file

@ -83,11 +83,6 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {
.show() .show()
} }
if (viewModel.keyVersionResult.value == null) {
// We need to fetch from API
viewModel.getLatestVersion()
}
viewModel.navigateEvent.observeEvent(this) { uxStateEvent -> viewModel.navigateEvent.observeEvent(this) { uxStateEvent ->
when (uxStateEvent) { when (uxStateEvent) {
KeysBackupRestoreSharedViewModel.NAVIGATE_TO_RECOVER_WITH_KEY -> { KeysBackupRestoreSharedViewModel.NAVIGATE_TO_RECOVER_WITH_KEY -> {

View file

@ -23,9 +23,11 @@ import im.vector.app.R
import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.platform.WaitingViewData
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.LiveEvent import im.vector.app.core.utils.LiveEvent
import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.listeners.StepProgressListener import org.matrix.android.sdk.api.listeners.StepProgressListener
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
@ -35,7 +37,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -75,10 +76,15 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
var importRoomKeysFinishWithResult: MutableLiveData<LiveEvent<ImportRoomKeysResult>> = MutableLiveData() var importRoomKeysFinishWithResult: MutableLiveData<LiveEvent<ImportRoomKeysResult>> = MutableLiveData()
fun initSession(session: Session) { fun initSession(session: Session) {
if (!this::session.isInitialized) {
this.session = session this.session = session
viewModelScope.launch {
getLatestVersion()
}
}
} }
val progressObserver = object : StepProgressListener { private val progressObserver = object : StepProgressListener {
override fun onStepProgress(step: StepProgressListener.Step) { override fun onStepProgress(step: StepProgressListener.Step) {
when (step) { when (step) {
is StepProgressListener.Step.ComputingKey -> { is StepProgressListener.Step.ComputingKey -> {
@ -106,24 +112,33 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
step.total)) step.total))
} }
} }
is StepProgressListener.Step.DecryptingKey -> {
if (step.progress == 0) {
loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restoring_waiting_message) +
"\n" + stringProvider.getString(R.string.keys_backup_restoring_decrypting_keys_waiting_message),
isIndeterminate = true))
} else {
loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restoring_waiting_message) +
"\n" + stringProvider.getString(R.string.keys_backup_restoring_decrypting_keys_waiting_message),
step.progress,
step.total))
}
}
} }
} }
} }
fun getLatestVersion() { private suspend fun getLatestVersion() {
val keysBackup = session.cryptoService().keysBackupService() val keysBackup = session.cryptoService().keysBackupService()
loadingEvent.value = WaitingViewData(stringProvider.getString(R.string.keys_backup_restore_is_getting_backup_version)) loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restore_is_getting_backup_version)))
viewModelScope.launch(Dispatchers.IO) {
try { try {
val version = awaitCallback<KeysVersionResult?> { val version = keysBackup.getCurrentVersion()
keysBackup.getCurrentVersion(it)
}
if (version?.version == null) { if (version?.version == null) {
loadingEvent.postValue(null) loadingEvent.postValue(null)
_keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, ""))) _keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, "")))
return@launch return
} }
keyVersionResult.postValue(version) keyVersionResult.postValue(version)
@ -138,8 +153,9 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
) )
// Go and use it!! // Go and use it!!
try { try {
recoverUsingBackupRecoveryKey(savedSecret.recoveryKey) recoverUsingBackupRecoveryKey(computeRecoveryKey(savedSecret.recoveryKey.fromBase64()), version)
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.e(failure, "## recoverUsingBackupRecoveryKey FAILED")
keySourceModel.postValue( keySourceModel.postValue(
KeySource(isInMemory = false, isInQuadS = true) KeySource(isInMemory = false, isInQuadS = true)
) )
@ -163,7 +179,6 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
_keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, failure.localizedMessage))) _keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, failure.localizedMessage)))
} }
} }
}
fun handleGotSecretFromSSSS(cipherData: String, alias: String) { fun handleGotSecretFromSSSS(cipherData: String, alias: String) {
try { try {
@ -176,11 +191,11 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
) )
return return
} }
loadingEvent.value = WaitingViewData(stringProvider.getString(R.string.keys_backup_restore_is_getting_backup_version)) loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restore_is_getting_backup_version)))
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
recoverUsingBackupRecoveryKey(computeRecoveryKey(secret.fromBase64())) recoverUsingBackupRecoveryKey(secret)
} catch (failure: Throwable) { } catch (failure: Throwable) {
_navigateEvent.postValue( _navigateEvent.postValue(
LiveEvent(NAVIGATE_FAILED_TO_LOAD_4S) LiveEvent(NAVIGATE_FAILED_TO_LOAD_4S)
@ -202,15 +217,12 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading))) loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading)))
try { try {
val result = awaitCallback<ImportRoomKeysResult> { val result = keysBackup.restoreKeyBackupWithPassword(keyVersion,
keysBackup.restoreKeyBackupWithPassword(keyVersion,
passphrase, passphrase,
null, null,
session.myUserId, session.myUserId,
progressObserver, progressObserver
it
) )
}
loadingEvent.postValue(null) loadingEvent.postValue(null)
didRecoverSucceed(result) didRecoverSucceed(result)
trustOnDecrypt(keysBackup, keyVersion) trustOnDecrypt(keysBackup, keyVersion)
@ -220,26 +232,27 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
} }
} }
suspend fun recoverUsingBackupRecoveryKey(recoveryKey: String) { suspend fun recoverUsingBackupRecoveryKey(recoveryKey: String, keyVersion: KeysVersionResult? = null) {
val keysBackup = session.cryptoService().keysBackupService() val keysBackup = session.cryptoService().keysBackupService()
val keyVersion = keyVersionResult.value ?: return // This is badddddd
val version = keyVersion ?: keyVersionResult.value ?: return
loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading))) loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading)))
try { try {
val result = awaitCallback<ImportRoomKeysResult> { val result = keysBackup.restoreKeysWithRecoveryKey(version,
keysBackup.restoreKeysWithRecoveryKey(keyVersion,
recoveryKey, recoveryKey,
null, null,
session.myUserId, session.myUserId,
progressObserver, progressObserver
it
) )
}
loadingEvent.postValue(null) loadingEvent.postValue(null)
withContext(Dispatchers.Main) {
didRecoverSucceed(result) didRecoverSucceed(result)
trustOnDecrypt(keysBackup, keyVersion) trustOnDecrypt(keysBackup, version)
}
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.e(failure, "## restoreKeysWithRecoveryKey failure")
loadingEvent.postValue(null) loadingEvent.postValue(null)
throw failure throw failure
} }
@ -258,19 +271,19 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
} }
private fun trustOnDecrypt(keysBackup: KeysBackupService, keysVersionResult: KeysVersionResult) { private fun trustOnDecrypt(keysBackup: KeysBackupService, keysVersionResult: KeysVersionResult) {
keysBackup.trustKeysBackupVersion(keysVersionResult, true, // do that on session scope because could happen outside of view model lifecycle
object : MatrixCallback<Unit> { session.coroutineScope.launch {
override fun onSuccess(data: Unit) { tryOrNull("## Failed to trustKeysBackupVersion") {
Timber.v("##### trustKeysBackupVersion onSuccess") keysBackup.trustKeysBackupVersion(keysVersionResult, true)
}
} }
})
} }
fun moveToRecoverWithKey() { fun moveToRecoverWithKey() {
_navigateEvent.value = LiveEvent(NAVIGATE_TO_RECOVER_WITH_KEY) _navigateEvent.postValue(LiveEvent(NAVIGATE_TO_RECOVER_WITH_KEY))
} }
fun didRecoverSucceed(result: ImportRoomKeysResult) { private fun didRecoverSucceed(result: ImportRoomKeysResult) {
importKeyResult = result importKeyResult = result
_navigateEvent.postValue(LiveEvent(NAVIGATE_TO_SUCCESS)) _navigateEvent.postValue(LiveEvent(NAVIGATE_TO_SUCCESS))
} }

View file

@ -27,13 +27,11 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import org.matrix.android.sdk.api.MatrixCallback import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.NoOpMatrixCallback
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust
import timber.log.Timber import timber.log.Timber
class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState, class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState,
@ -70,7 +68,9 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
} }
private fun init() { private fun init() {
keysBackupService.forceUsingLastVersion(NoOpMatrixCallback()) viewModelScope.launch {
keysBackupService.forceUsingLastVersion()
}
} }
private fun getKeysBackupTrust() = withState { state -> private fun getKeysBackupTrust() = withState { state ->
@ -86,18 +86,16 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
} }
Timber.d("BACKUP: HEEEEEEE TWO") Timber.d("BACKUP: HEEEEEEE TWO")
keysBackupService viewModelScope.launch {
.getKeysBackupTrust(versionResult, object : MatrixCallback<KeysBackupVersionTrust> { try {
override fun onSuccess(data: KeysBackupVersionTrust) { val data = keysBackupService.getKeysBackupTrust(versionResult)
Timber.d("BACKUP: HEEEE suceeeded $data") Timber.d("BACKUP: HEEEE suceeeded $data")
setState { setState {
copy( copy(
keysBackupVersionTrust = Success(data) keysBackupVersionTrust = Success(data)
) )
} }
} } catch (failure: Throwable) {
override fun onFailure(failure: Throwable) {
Timber.d("BACKUP: HEEEE FAILED $failure") Timber.d("BACKUP: HEEEE FAILED $failure")
setState { setState {
copy( copy(
@ -105,7 +103,7 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
) )
} }
} }
}) }
} }
} }
@ -128,15 +126,16 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
private fun deleteCurrentBackup() { private fun deleteCurrentBackup() {
val keysBackupService = keysBackupService val keysBackupService = keysBackupService
if (keysBackupService.currentBackupVersion != null) { val currentBackupVersion = keysBackupService.currentBackupVersion
if (currentBackupVersion != null) {
setState { setState {
copy( copy(
deleteBackupRequest = Loading() deleteBackupRequest = Loading()
) )
} }
viewModelScope.launch {
keysBackupService.deleteBackup(keysBackupService.currentBackupVersion!!, object : MatrixCallback<Unit> { try {
override fun onSuccess(data: Unit) { keysBackupService.deleteBackup(currentBackupVersion)
setState { setState {
copy( copy(
keysBackupVersion = null, keysBackupVersion = null,
@ -145,16 +144,14 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
deleteBackupRequest = Uninitialized deleteBackupRequest = Uninitialized
) )
} }
} } catch (failure: Throwable) {
override fun onFailure(failure: Throwable) {
setState { setState {
copy( copy(
deleteBackupRequest = Fail(failure) deleteBackupRequest = Fail(failure)
) )
} }
} }
}) }
} }
} }

View file

@ -19,17 +19,16 @@ package im.vector.app.features.crypto.keysbackup.setup
import android.content.Context import android.content.Context
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.nulabinc.zxcvbn.Strength import com.nulabinc.zxcvbn.Strength
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.platform.WaitingViewData
import im.vector.app.core.utils.LiveEvent import im.vector.app.core.utils.LiveEvent
import org.matrix.android.sdk.api.MatrixCallback import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -89,48 +88,30 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() {
recoveryKey.value = null recoveryKey.value = null
prepareRecoverFailError.value = null prepareRecoverFailError.value = null
session.let { mxSession ->
val requestedId = currentRequestId.value!! val requestedId = currentRequestId.value!!
viewModelScope.launch {
mxSession.cryptoService().keysBackupService().prepareKeysBackupVersion(withPassphrase, try {
object : ProgressListener { val data = session.cryptoService().keysBackupService().prepareKeysBackupVersion(withPassphrase)
override fun onProgress(progress: Int, total: Int) {
if (requestedId != currentRequestId.value) { if (requestedId != currentRequestId.value) {
// this is an old request, we can't cancel but we can ignore // this is an old request, we can't cancel but we can ignore
return return@launch
} }
recoveryKey.postValue(data.recoveryKey)
loadingStatus.value = WaitingViewData(context.getString(R.string.keys_backup_setup_step3_generating_key_status),
progress,
total)
}
},
object : MatrixCallback<MegolmBackupCreationInfo> {
override fun onSuccess(data: MegolmBackupCreationInfo) {
if (requestedId != currentRequestId.value) {
// this is an old request, we can't cancel but we can ignore
return
}
recoveryKey.value = data.recoveryKey
megolmBackupCreationInfo = data megolmBackupCreationInfo = data
copyHasBeenMade = false copyHasBeenMade = false
val keyBackup = session.cryptoService().keysBackupService() val keyBackup = session.cryptoService().keysBackupService()
createKeysBackup(context, keyBackup) createKeysBackup(context, keyBackup)
} } catch (failure: Throwable) {
override fun onFailure(failure: Throwable) {
if (requestedId != currentRequestId.value) { if (requestedId != currentRequestId.value) {
// this is an old request, we can't cancel but we can ignore // this is an old request, we can't cancel but we can ignore
return return@launch
} }
loadingStatus.value = null loadingStatus.postValue(null)
isCreatingBackupVersion.postValue(false)
isCreatingBackupVersion.value = false prepareRecoverFailError.postValue(failure)
prepareRecoverFailError.value = failure
} }
})
} }
} }
@ -140,55 +121,47 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() {
} }
fun stopAndKeepAfterDetectingExistingOnServer() { fun stopAndKeepAfterDetectingExistingOnServer() {
loadingStatus.value = null loadingStatus.postValue(null)
navigateEvent.value = LiveEvent(NAVIGATE_FINISH) navigateEvent.postValue(LiveEvent(NAVIGATE_FINISH))
viewModelScope.launch {
session.cryptoService().keysBackupService().checkAndStartKeysBackup() session.cryptoService().keysBackupService().checkAndStartKeysBackup()
} }
}
private fun createKeysBackup(context: Context, keysBackup: KeysBackupService, forceOverride: Boolean = false) { private fun createKeysBackup(context: Context, keysBackup: KeysBackupService, forceOverride: Boolean = false) {
loadingStatus.value = WaitingViewData(context.getString(R.string.keys_backup_setup_creating_backup), isIndeterminate = true) loadingStatus.value = WaitingViewData(context.getString(R.string.keys_backup_setup_creating_backup), isIndeterminate = true)
creatingBackupError.value = null creatingBackupError.value = null
keysBackup.getCurrentVersion(object : MatrixCallback<KeysVersionResult?> { viewModelScope.launch {
override fun onSuccess(data: KeysVersionResult?) { try {
val data = keysBackup.getCurrentVersion()
if (data?.version.isNullOrBlank() || forceOverride) { if (data?.version.isNullOrBlank() || forceOverride) {
processOnCreate() processOnCreate(keysBackup)
} else { } else {
loadingStatus.value = null loadingStatus.postValue(null)
// we should prompt // we should prompt
isCreatingBackupVersion.value = false isCreatingBackupVersion.postValue(false)
navigateEvent.value = LiveEvent(NAVIGATE_PROMPT_REPLACE) navigateEvent.postValue(LiveEvent(NAVIGATE_PROMPT_REPLACE))
}
} catch (failure: Throwable) {
}
} }
} }
override fun onFailure(failure: Throwable) { suspend fun processOnCreate(keysBackup: KeysBackupService) {
Timber.e(failure, "## createKeyBackupVersion") try {
loadingStatus.value = null loadingStatus.postValue(null)
val created = keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!)
isCreatingBackupVersion.value = false isCreatingBackupVersion.postValue(false)
creatingBackupError.value = failure keysVersion.postValue(created)
}
fun processOnCreate() {
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!, object : MatrixCallback<KeysVersion> {
override fun onSuccess(data: KeysVersion) {
loadingStatus.value = null
isCreatingBackupVersion.value = false
keysVersion.value = data
navigateEvent.value = LiveEvent(NAVIGATE_TO_STEP_3) navigateEvent.value = LiveEvent(NAVIGATE_TO_STEP_3)
} } catch (failure: Throwable) {
override fun onFailure(failure: Throwable) {
Timber.e(failure, "## createKeyBackupVersion") Timber.e(failure, "## createKeyBackupVersion")
loadingStatus.value = null loadingStatus.postValue(null)
isCreatingBackupVersion.value = false isCreatingBackupVersion.postValue(false)
creatingBackupError.value = failure creatingBackupError.postValue(failure)
} }
})
}
})
} }
} }

View file

@ -20,7 +20,9 @@ import im.vector.app.R
import im.vector.app.core.platform.ViewModelTask import im.vector.app.core.platform.ViewModelTask
import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.platform.WaitingViewData
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import org.matrix.android.sdk.api.NoOpMatrixCallback import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
@ -32,7 +34,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
import org.matrix.android.sdk.internal.crypto.keysbackup.deriveKey import org.matrix.android.sdk.internal.crypto.keysbackup.deriveKey
import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey
import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber import timber.log.Timber
import java.util.UUID import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
@ -87,9 +88,7 @@ class BackupToQuadSMigrationTask @Inject constructor(
reportProgress(params, R.string.bootstrap_progress_compute_curve_key) reportProgress(params, R.string.bootstrap_progress_compute_curve_key)
val recoveryKey = computeRecoveryKey(curveKey) val recoveryKey = computeRecoveryKey(curveKey)
val isValid = awaitCallback<Boolean> { val isValid = keysBackupService.isValidRecoveryKeyForCurrentVersion(recoveryKey)
keysBackupService.isValidRecoveryKeyForCurrentVersion(recoveryKey, it)
}
if (!isValid) return Result.InvalidRecoverySecret if (!isValid) return Result.InvalidRecoverySecret
@ -141,14 +140,17 @@ class BackupToQuadSMigrationTask @Inject constructor(
keysBackupService.saveBackupRecoveryKey(recoveryKey, version.version) keysBackupService.saveBackupRecoveryKey(recoveryKey, version.version)
// while we are there let's restore, but do not block // while we are there let's restore, but do not block
session.coroutineScope.launch {
tryOrNull {
session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
version, version,
recoveryKey, recoveryKey,
null, null,
null, null,
null, null
NoOpMatrixCallback()
) )
}
}
return Result.Success return Result.Success
} catch (failure: Throwable) { } catch (failure: Throwable) {

View file

@ -33,9 +33,6 @@ import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageServi
import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo
import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber import timber.log.Timber
@ -221,9 +218,7 @@ class BootstrapCrossSigningTask @Inject constructor(
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Checking megolm backup") Timber.d("## BootstrapCrossSigningTask: Creating 4S - Checking megolm backup")
// First ensure that in sync // First ensure that in sync
var serverVersion = awaitCallback<KeysVersionResult?> { var serverVersion = session.cryptoService().keysBackupService().getCurrentVersion()
session.cryptoService().keysBackupService().getCurrentVersion(it)
}
val knownMegolmSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() val knownMegolmSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()
val isMegolmBackupSecretKnown = knownMegolmSecret != null && knownMegolmSecret.version == serverVersion?.version val isMegolmBackupSecretKnown = knownMegolmSecret != null && knownMegolmSecret.version == serverVersion?.version
@ -233,21 +228,14 @@ class BootstrapCrossSigningTask @Inject constructor(
if (shouldCreateKeyBackup) { if (shouldCreateKeyBackup) {
// clear all existing backups // clear all existing backups
while (serverVersion != null) { while (serverVersion != null) {
awaitCallback<Unit> { session.cryptoService().keysBackupService().deleteBackup(serverVersion.version)
session.cryptoService().keysBackupService().deleteBackup(serverVersion!!.version, it) serverVersion = session.cryptoService().keysBackupService().getCurrentVersion()
}
serverVersion = awaitCallback {
session.cryptoService().keysBackupService().getCurrentVersion(it)
}
} }
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Create megolm backup") Timber.d("## BootstrapCrossSigningTask: Creating 4S - Create megolm backup")
val creationInfo = awaitCallback<MegolmBackupCreationInfo> { val creationInfo = session.cryptoService().keysBackupService().prepareKeysBackupVersion(null)
session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it) val version = session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo)
}
val version = awaitCallback<KeysVersion> {
session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
}
// Save it for gossiping // Save it for gossiping
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Save megolm backup key for gossiping") Timber.d("## BootstrapCrossSigningTask: Creating 4S - Save megolm backup key for gossiping")
session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version) session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
@ -264,12 +252,10 @@ class BootstrapCrossSigningTask @Inject constructor(
// ensure we store existing backup secret if we have it! // ensure we store existing backup secret if we have it!
if (isMegolmBackupSecretKnown) { if (isMegolmBackupSecretKnown) {
// check it matches // check it matches
val isValid = awaitCallback<Boolean> { val isValid = session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownMegolmSecret!!.recoveryKey)
session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownMegolmSecret!!.recoveryKey, it)
}
if (isValid) { if (isValid) {
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key valid and known") Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key valid and known")
extractCurveKeyFromRecoveryKey(knownMegolmSecret!!.recoveryKey)?.toBase64NoPadding()?.let { secret -> extractCurveKeyFromRecoveryKey(knownMegolmSecret.recoveryKey)?.toBase64NoPadding()?.let { secret ->
ssssService.storeSecret( ssssService.storeSecret(
KEYBACKUP_SECRET_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME,
secret, secret,

View file

@ -42,14 +42,13 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth
import org.matrix.android.sdk.internal.util.awaitCallback
import java.io.OutputStream import java.io.OutputStream
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
@ -104,9 +103,8 @@ class BootstrapSharedViewModel @AssistedInject constructor(
// We need to check if there is an existing backup // We need to check if there is an existing backup
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
val version = awaitCallback<KeysVersionResult?> { val version = tryOrNull { session.cryptoService().keysBackupService().getCurrentVersion() }
session.cryptoService().keysBackupService().getCurrentVersion(it)
}
if (version == null) { if (version == null) {
// we just resume plain bootstrap // we just resume plain bootstrap
doesKeyBackupExist = false doesKeyBackupExist = false
@ -115,8 +113,8 @@ class BootstrapSharedViewModel @AssistedInject constructor(
} }
} else { } else {
// we need to get existing backup passphrase/key and convert to SSSS // we need to get existing backup passphrase/key and convert to SSSS
val keyVersion = awaitCallback<KeysVersionResult?> { val keyVersion = tryOrNull {
session.cryptoService().keysBackupService().getVersion(version.version, it) session.cryptoService().keysBackupService().getVersion(version.version)
} }
if (keyVersion == null) { if (keyVersion == null) {
// strange case... just finish? // strange case... just finish?

View file

@ -31,8 +31,10 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
@ -51,10 +53,7 @@ import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber import timber.log.Timber
data class VerificationBottomSheetViewState( data class VerificationBottomSheetViewState(
@ -419,30 +418,27 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
} }
private fun tentativeRestoreBackup(res: Map<String, String>?) { private fun tentativeRestoreBackup(res: Map<String, String>?) {
viewModelScope.launch(Dispatchers.IO) { // on session scope because will happen after viewmodel is cleared
session.coroutineScope.launch {
try { try {
val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) ?: return@launch Unit.also { val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) ?: return@launch Unit.also {
Timber.v("## Keybackup secret not restored from SSSS") Timber.v("## Keybackup secret not restored from SSSS")
} }
val version = awaitCallback<KeysVersionResult?> { val version = tryOrNull { session.cryptoService().keysBackupService().getCurrentVersion() }
session.cryptoService().keysBackupService().getCurrentVersion(it) ?: return@launch
} ?: return@launch
// TODO maybe mark as trusted earlier by checking recovery key early, then download?
awaitCallback<ImportRoomKeysResult> {
session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
version, version,
computeRecoveryKey(secret.fromBase64()), computeRecoveryKey(secret.fromBase64()),
null, null,
null, null,
null, null
it
) )
}
awaitCallback<Unit> { session.cryptoService().keysBackupService().trustKeysBackupVersion(version, true)
session.cryptoService().keysBackupService().trustKeysBackupVersion(version, true, it)
}
} catch (failure: Throwable) { } catch (failure: Throwable) {
// Just ignore for now // Just ignore for now
Timber.e(failure, "## Failed to restore backup after SSSS recovery") Timber.e(failure, "## Failed to restore backup after SSSS recovery")

View file

@ -155,9 +155,11 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS
fun refreshRemoteStateIfNeeded() { fun refreshRemoteStateIfNeeded() {
if (keysBackupState.value == KeysBackupState.Disabled) { if (keysBackupState.value == KeysBackupState.Disabled) {
viewModelScope.launch {
session.cryptoService().keysBackupService().checkAndStartKeysBackup() session.cryptoService().keysBackupService().checkAndStartKeysBackup()
} }
} }
}
override fun handle(action: EmptyAction) {} override fun handle(action: EmptyAction) {}
} }

View file

@ -73,7 +73,9 @@ class SignoutCheckViewModel @AssistedInject constructor(
init { init {
session.cryptoService().keysBackupService().addListener(this) session.cryptoService().keysBackupService().addListener(this)
viewModelScope.launch {
session.cryptoService().keysBackupService().checkAndStartKeysBackup() session.cryptoService().keysBackupService().checkAndStartKeysBackup()
}
val quad4SIsSetup = session.sharedSecretStorageService.isRecoverySetup() val quad4SIsSetup = session.sharedSecretStorageService.isRecoverySetup()
val allKeysKnown = session.cryptoService().crossSigningService().allPrivateKeysKnown() val allKeysKnown = session.cryptoService().crossSigningService().allPrivateKeysKnown()
@ -112,9 +114,11 @@ class SignoutCheckViewModel @AssistedInject constructor(
fun refreshRemoteStateIfNeeded() = withState { state -> fun refreshRemoteStateIfNeeded() = withState { state ->
if (state.keysBackupState == KeysBackupState.Disabled) { if (state.keysBackupState == KeysBackupState.Disabled) {
viewModelScope.launch {
session.cryptoService().keysBackupService().checkAndStartKeysBackup() session.cryptoService().keysBackupService().checkAndStartKeysBackup()
} }
} }
}
override fun handle(action: Actions) { override fun handle(action: Actions) {
when (action) { when (action) {

View file

@ -2041,6 +2041,7 @@
<string name="keys_backup_restoring_computing_key_waiting_message">Computing recovery key…</string> <string name="keys_backup_restoring_computing_key_waiting_message">Computing recovery key…</string>
<string name="keys_backup_restoring_downloading_backup_waiting_message">Downloading keys…</string> <string name="keys_backup_restoring_downloading_backup_waiting_message">Downloading keys…</string>
<string name="keys_backup_restoring_importing_keys_waiting_message">Importing keys…</string> <string name="keys_backup_restoring_importing_keys_waiting_message">Importing keys…</string>
<string name="keys_backup_restoring_decrypting_keys_waiting_message">Decrypting keys…</string>
<string name="keys_backup_unlock_button">Unlock History</string> <string name="keys_backup_unlock_button">Unlock History</string>
<string name="keys_backup_recovery_code_empty_error_message">Please enter a recovery key</string> <string name="keys_backup_recovery_code_empty_error_message">Please enter a recovery key</string>
<string name="keys_backup_recovery_code_error_decrypt">Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key.</string> <string name="keys_backup_recovery_code_error_decrypt">Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key.</string>
@ -3442,6 +3443,7 @@
<string name="command_description_join_space">Join the Space with the given id</string> <string name="command_description_join_space">Join the Space with the given id</string>
<string name="command_description_leave_room">Leave room with given id (or current room if null)</string> <string name="command_description_leave_room">Leave room with given id (or current room if null)</string>
<string name="command_description_upgrade_room">Upgrades a room to a new version</string> <string name="command_description_upgrade_room">Upgrades a room to a new version</string>
<string name="command_description_gen_keys">Gen keys</string>
<string name="event_status_a11y_sending">Sending</string> <string name="event_status_a11y_sending">Sending</string>
<string name="event_status_a11y_sent">Sent</string> <string name="event_status_a11y_sent">Sent</string>