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,8 +336,10 @@ 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.
tryOrNull { cryptoCoroutineScope.launch {
keysBackupService.checkAndStartKeysBackup() tryOrNull {
keysBackupService.checkAndStartKeysBackup()
}
} }
// Open the store // Open the store

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,11 +34,13 @@ 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
it.onStateChange(newState) tryOrNull {
it.onStateChange(newState)
}
} }
} }
} }

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,64 +115,57 @@ 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>) { val key = if (password != null) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) { // this might be a bit slow as it's stretching the password
runCatching { BackupRecoveryKey.newFromPassphrase(password)
withContext(coroutineDispatchers.crypto) { } else {
val key = if (password != null) { BackupRecoveryKey()
BackupRecoveryKey.newFromPassphrase(password) }
} else {
BackupRecoveryKey()
}
val publicKey = key.megolmV1PublicKey() val publicKey = key.megolmV1PublicKey()
val backupAuthData = SignalableMegolmBackupAuthData( val backupAuthData = SignalableMegolmBackupAuthData(
publicKey = publicKey.publicKey, publicKey = publicKey.publicKey,
privateKeySalt = publicKey.passphraseInfo?.privateKeySalt, privateKeySalt = publicKey.passphraseInfo?.privateKeySalt,
privateKeyIterations = publicKey.passphraseInfo?.privateKeyIterations privateKeyIterations = publicKey.passphraseInfo?.privateKeyIterations
) )
val canonicalJson = JsonCanonicalizer.getCanonicalJson( val canonicalJson = JsonCanonicalizer.getCanonicalJson(
Map::class.java, Map::class.java,
backupAuthData.signalableJSONDictionary() backupAuthData.signalableJSONDictionary()
) )
val signedMegolmBackupAuthData = MegolmBackupAuthData( val signedMegolmBackupAuthData = MegolmBackupAuthData(
publicKey = backupAuthData.publicKey, publicKey = backupAuthData.publicKey,
privateKeySalt = backupAuthData.privateKeySalt, privateKeySalt = backupAuthData.privateKeySalt,
privateKeyIterations = backupAuthData.privateKeyIterations, privateKeyIterations = backupAuthData.privateKeyIterations,
signatures = olmMachine.sign(canonicalJson) signatures = olmMachine.sign(canonicalJson)
) )
MegolmBackupCreationInfo( MegolmBackupCreationInfo(
algorithm = publicKey.backupAlgorithm, algorithm = publicKey.backupAlgorithm,
authData = signedMegolmBackupAuthData, authData = signedMegolmBackupAuthData,
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() )
)
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,29 +210,24 @@ 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() keysBackupVersion = null
keysBackupVersion = null keysBackupStateManager.state = KeysBackupState.Unknown
keysBackupStateManager.state = KeysBackupState.Unknown }
}
fun eventuallyRestartBackup() { try {
// Do not stay in KeysBackupState.Unknown but check what is available on the homeserver sender.deleteKeyBackup(version)
if (state == KeysBackupState.Unknown) { // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver
checkAndStartKeysBackup() if (state == KeysBackupState.Unknown) {
} checkAndStartKeysBackup()
} }
} catch (failure: Throwable) {
try { // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver
sender.deleteKeyBackup(version) if (state == KeysBackupState.Unknown) {
eventuallyRestartBackup() checkAndStartKeysBackup()
uiHandler.post { callback?.onSuccess(Unit) }
} catch (failure: Throwable) {
eventuallyRestartBackup()
uiHandler.post { callback?.onFailure(failure) }
} }
} }
} }
@ -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,82 +266,68 @@ 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
val authData = getMegolmBackupAuthData(keysBackupVersion) val authData = getMegolmBackupAuthData(keysBackupVersion)
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")
} else {
// Get current signatures, or create an empty set
val userId = olmMachine.userId()
val signatures = authData.signatures?.get(userId).orEmpty().toMutableMap()
callback.onFailure(IllegalArgumentException("Missing element")) if (trust) {
} else { // Add current device signature
cryptoCoroutineScope.launch(coroutineDispatchers.main) { val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary())
val body = withContext(coroutineDispatchers.crypto) { val deviceSignature = olmMachine.sign(canonicalJson)
// Get current signatures, or create an empty set
val userId = olmMachine.userId()
val signatures = authData.signatures?.get(userId).orEmpty().toMutableMap()
if (trust) { deviceSignature[userId]?.forEach { entry ->
// Add current device signature signatures[entry.key] = entry.value
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary())
val deviceSignature = olmMachine.sign(canonicalJson)
deviceSignature[userId]?.forEach { entry ->
signatures[entry.key] = entry.value
}
} else {
signatures.remove("ed25519:${olmMachine.deviceId()}")
} }
} else {
val newAuthData = authData.copy() signatures.remove("ed25519:${olmMachine.deviceId()}")
val newSignatures = newAuthData.signatures.orEmpty().toMutableMap()
newSignatures[userId] = signatures
@Suppress("UNCHECKED_CAST")
UpdateKeysBackupVersionBody(
algorithm = keysBackupVersion.algorithm,
authData = newAuthData.copy(signatures = newSignatures).toJsonDict(),
version = keysBackupVersion.version)
} }
try {
val newAuthData = authData.copy()
val newSignatures = newAuthData.signatures.orEmpty().toMutableMap()
newSignatures[userId] = signatures
val body = UpdateKeysBackupVersionBody(
algorithm = keysBackupVersion.algorithm,
authData = newAuthData.copy(signatures = newSignatures).toJsonDict(),
version = keysBackupVersion.version)
withContext(coroutineDispatchers.io) {
sender.updateBackup(keysBackupVersion, body) sender.updateBackup(keysBackupVersion, body)
val newKeysBackupVersion = KeysVersionResult(
algorithm = keysBackupVersion.algorithm,
authData = body.authData,
version = keysBackupVersion.version,
hash = keysBackupVersion.hash,
count = keysBackupVersion.count
)
checkAndStartWithKeysBackupVersion(newKeysBackupVersion)
callback.onSuccess(Unit)
} catch (exception: Throwable) {
callback.onFailure(exception)
} }
val newKeysBackupVersion = KeysVersionResult(
algorithm = keysBackupVersion.algorithm,
authData = body.authData,
version = keysBackupVersion.version,
hash = keysBackupVersion.hash,
count = keysBackupVersion.count
)
checkAndStartWithKeysBackupVersion(newKeysBackupVersion)
} }
} }
} }
// Check that the recovery key matches to the public key that we downloaded from the server. // Check that the recovery key matches to the public key that we downloaded from the server.
// If they match, we can trust the public key and enable backups since we have the private key. // If they match, we can trust the public key and enable backups since we have the private key.
private fun checkRecoveryKey(recoveryKey: BackupRecoveryKey, keysBackupData: KeysVersionResult) { private fun checkRecoveryKey(recoveryKey: BackupRecoveryKey, keysBackupData: KeysVersionResult) {
val backupKey = recoveryKey.megolmV1PublicKey() val backupKey = recoveryKey.megolmV1PublicKey()
val authData = getMegolmBackupAuthData(keysBackupData) val authData = getMegolmBackupAuthData(keysBackupData)
@ -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 { // This is ~nowhere mentioned, the string here is actually a base58 encoded key.
try { // This not really supported by the spec for the backup key, the 4S key supports
// This is ~nowhere mentioned, the string here is actually a base58 encoded key. // base58 encoding and the same method seems to be used here.
// This not really supported by the spec for the backup key, the 4S key supports val key = BackupRecoveryKey.fromBase58(recoveryKey)
// base58 encoding and the same method seems to be used here. checkRecoveryKey(key, keysBackupVersion)
val key = BackupRecoveryKey.fromBase58(recoveryKey) trustKeysBackupVersion(keysBackupVersion, true)
checkRecoveryKey(key, keysBackupVersion)
trustKeysBackupVersion(keysBackupVersion, true, callback)
} 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>) { val key = recoveryKeyFromPassword(password, keysBackupVersion)
cryptoCoroutineScope.launch { checkRecoveryKey(key, keysBackupVersion)
try { trustKeysBackupVersion(keysBackupVersion, true)
val key = recoveryKeyFromPassword(password, keysBackupVersion)
checkRecoveryKey(key, keysBackupVersion)
trustKeysBackupVersion(keysBackupVersion, true, callback)
} 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> { Timber.i("onSecretKeyGossip: Recovered keys ${importResult.successfullyNumberOfImportedKeys} out of ${importResult.totalNumberOfKeys}")
trustKeysBackupVersion(version, true, it) } catch (failure: Throwable) {
} // fail silently..
val importResult = awaitCallback<ImportRoomKeysResult> { Timber.e(failure, "onSecretKeyGossip: Failed to import keys from backup")
cryptoCoroutineScope.launch { }
restoreBackup(version, key, null, null, null)
} }
// we can save, it's valid
saveBackupRecoveryKey(key.toBase64(), version.version)
} }
Timber.i("onSecretKeyGossip: Recovered keys ${importResult.successfullyNumberOfImportedKeys} out of ${importResult.totalNumberOfKeys}")
saveBackupRecoveryKey(secret, 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)
} }
stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey) withContext(coroutineDispatchers.main) {
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,7 +535,9 @@ 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
stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total)) cryptoCoroutineScope.launch(Dispatchers.Main) {
stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total))
}
} }
} }
} else { } else {
@ -562,132 +551,120 @@ 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) return restoreBackup(keysVersionResult, key, roomId, sessionId, stepProgressListener)
}.foldToCallback(callback)
}
} }
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) { val recoveryKey = withContext(coroutineDispatchers.crypto) {
runCatching { recoveryKeyFromPassword(password, keysBackupVersion)
val recoveryKey = withContext(coroutineDispatchers.crypto) { }
recoveryKeyFromPassword(password, keysBackupVersion)
}
restoreBackup(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener) return restoreBackup(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener)
}.foldToCallback(callback) }
override suspend fun getVersion(version: String): KeysVersionResult? {
return withContext(coroutineDispatchers.io) {
sender.getKeyBackupVersion(version)
} }
} }
override fun getVersion(version: String, callback: MatrixCallback<KeysVersionResult?>) { @Throws
cryptoCoroutineScope.launch(coroutineDispatchers.main) { override suspend fun getCurrentVersion(): KeysVersionResult? {
runCatching { return withContext(coroutineDispatchers.io) {
sender.getKeyBackupVersion(version) sender.getKeyBackupVersion()
}.foldToCallback(callback)
} }
} }
override fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) { override suspend fun forceUsingLastVersion(): Boolean {
cryptoCoroutineScope.launch(coroutineDispatchers.main) { val response = withContext(coroutineDispatchers.io) {
runCatching { sender.getKeyBackupVersion()
sender.getKeyBackupVersion()
}.foldToCallback(callback)
} }
}
private suspend fun forceUsingLastVersionHelper(): Boolean { return withContext(coroutineDispatchers.crypto) {
val response = sender.getKeyBackupVersion() 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
} else {
// No backup on the server, and we are currently backing up, so stop backing up
resetKeysBackupData()
keysBackupVersion = null
keysBackupStateManager.state = KeysBackupState.Disabled
false
}
} else {
if (localBackupVersion == null) {
// Do a check
checkAndStartWithKeysBackupVersion(response)
// backup on the server, and backup is not active
false
} else {
// Backup on the server, and we are currently backing up, compare version
if (localBackupVersion == serverBackupVersion) {
// We are already using the last version of the backup
true true
} else { } else {
// This will automatically check for the last version then // No backup on the server, and we are currently backing up, so stop backing up
deleteBackup(localBackupVersion, null) resetKeysBackupData()
// We are not using the last version, so delete the current version we are using on the server keysBackupVersion = null
keysBackupStateManager.state = KeysBackupState.Disabled
false false
} }
} else {
if (localBackupVersion == null) {
// Do a check
checkAndStartWithKeysBackupVersion(response)
// backup on the server, and backup is not active
false
} else {
// Backup on the server, and we are currently backing up, compare version
if (localBackupVersion == serverBackupVersion) {
// We are already using the last version of the backup
true
} else {
// This will automatically check for the last version then
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
false
}
}
} }
} }
} }
override fun forceUsingLastVersion(callback: MatrixCallback<Boolean>) { override suspend fun checkAndStartKeysBackup() {
cryptoCoroutineScope.launch { withContext(coroutineDispatchers.crypto) {
runCatching { if (!isStucked) {
forceUsingLastVersionHelper() // Try to start or restart the backup only if it is in unknown or bad state
}.foldToCallback(callback) Timber.w("checkAndStartKeysBackup: invalid state: $state")
return@withContext
}
keysBackupVersion = null
keysBackupStateManager.state = KeysBackupState.CheckingBackUpOnHomeserver
withContext(coroutineDispatchers.io) {
try {
val data = getCurrentVersion()
withContext(coroutineDispatchers.crypto) {
checkAndStartWithKeysBackupVersion(data)
}
} catch (failure: Throwable) {
Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version")
keysBackupStateManager.state = KeysBackupState.Unknown
}
}
} }
} }
override fun checkAndStartKeysBackup() { private suspend fun checkAndStartWithKeysBackupVersion(keyBackupVersion: KeysVersionResult?) {
if (!isStucked) {
// Try to start or restart the backup only if it is in unknown or bad state
Timber.w("checkAndStartKeysBackup: invalid state: $state")
return
}
keysBackupVersion = null
keysBackupStateManager.state = KeysBackupState.CheckingBackUpOnHomeserver
getCurrentVersion(object : MatrixCallback<KeysVersionResult?> {
override fun onSuccess(data: KeysVersionResult?) {
checkAndStartWithKeysBackupVersion(data)
}
override fun onFailure(failure: Throwable) {
Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version")
keysBackupStateManager.state = KeysBackupState.Unknown
}
})
}
private fun checkAndStartWithKeysBackupVersion(keyBackupVersion: KeysVersionResult?) {
Timber.v("checkAndStartWithKeyBackupVersion: ${keyBackupVersion?.version}") Timber.v("checkAndStartWithKeyBackupVersion: ${keyBackupVersion?.version}")
keysBackupVersion = keyBackupVersion keysBackupVersion = keyBackupVersion
@ -697,37 +674,34 @@ 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) {
Timber.v("checkAndStartWithKeysBackupVersion: Found usable key backup. version: ${keyBackupVersion.version}") Timber.v("checkAndStartWithKeysBackupVersion: Found usable key backup. version: ${keyBackupVersion.version}")
// Check the version we used at the previous app run // Check the version we used at the previous app run
if (versionInStore != null && versionInStore != keyBackupVersion.version) { if (versionInStore != null && versionInStore != keyBackupVersion.version) {
Timber.v(" -> clean the previously used version $versionInStore") Timber.v(" -> clean the previously used version $versionInStore")
resetKeysBackupData() resetKeysBackupData()
}
Timber.v(" -> enabling key backups")
cryptoCoroutineScope.launch {
enableKeysBackup(keyBackupVersion)
}
} else {
Timber.v("checkAndStartWithKeysBackupVersion: No usable key backup. version: ${keyBackupVersion.version}")
if (versionInStore != null) {
Timber.v(" -> disabling key backup")
resetKeysBackupData()
}
keysBackupStateManager.state = KeysBackupState.NotTrusted
} }
}
override fun onFailure(failure: Throwable) { Timber.v(" -> enabling key backups")
// Cannot happen cryptoCoroutineScope.launch {
enableKeysBackup(keyBackupVersion)
}
} else {
Timber.v("checkAndStartWithKeysBackupVersion: No usable key backup. version: ${keyBackupVersion.version}")
if (versionInStore != null) {
Timber.v(" -> disabling key backup")
resetKeysBackupData()
}
keysBackupStateManager.state = KeysBackupState.NotTrusted
} }
}) } catch (failure: Throwable) {
Timber.e(failure, "Failed to checkAndStartWithKeysBackupVersion $keyBackupVersion")
}
} }
} }
@ -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,31 +799,30 @@ 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() {
when { withContext(coroutineDispatchers.crypto) {
isStucked -> { when {
// If not already done, or in error case, check for a valid backup version on the homeserver. isStucked -> {
// If there is one, maybeBackupKeys will be called again. // If not already done, or in error case, check for a valid backup version on the homeserver.
checkAndStartKeysBackup() // If there is one, maybeBackupKeys will be called again.
} checkAndStartKeysBackup()
state == KeysBackupState.ReadyToBackUp -> { }
keysBackupStateManager.state = KeysBackupState.WillBackUp state == KeysBackupState.ReadyToBackUp -> {
keysBackupStateManager.state = KeysBackupState.WillBackUp
// Wait between 0 and 10 seconds, to avoid backup requests from // Wait between 0 and 10 seconds, to avoid backup requests from
// different clients hitting the server all at the same time when a // different clients hitting the server all at the same time when a
// 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 -> { Timber.v("maybeBackupKeys: Skip it because state: $state")
Timber.v("maybeBackupKeys: Skip it because state: $state") }
} }
} }
} }
@ -854,84 +830,79 @@ 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
if (!isEnabled || !olmMachine.backupEnabled() || keysBackupVersion == null) {
Timber.v("backupKeys: Invalid configuration $isEnabled ${olmMachine.backupEnabled()} $keysBackupVersion")
// backupAllGroupSessionsCallback?.onFailure(IllegalStateException("Invalid configuration"))
resetBackupAllGroupSessionsListeners()
// Sanity check, as this method can be called after a delay, the state may have change during the delay return@withContext
if (!isEnabled || !olmMachine.backupEnabled() || keysBackupVersion == null) { }
Timber.v("backupKeys: Invalid configuration $isEnabled ${olmMachine.backupEnabled()} $keysBackupVersion")
backupAllGroupSessionsCallback?.onFailure(IllegalStateException("Invalid configuration"))
resetBackupAllGroupSessionsListeners()
return if (state === KeysBackupState.BackingUp && !forceRecheck) {
} // Do nothing if we are already backing up
Timber.v("backupKeys: Invalid state: $state")
return@withContext
}
if (state === KeysBackupState.BackingUp && !forceRecheck) { Timber.d("BACKUP: CREATING REQUEST")
// Do nothing if we are already backing up
Timber.v("backupKeys: Invalid state: $state")
return
}
Timber.d("BACKUP: CREATING REQUEST") val request = olmMachine.backupRoomKeys()
val request = olmMachine.backupRoomKeys() Timber.d("BACKUP: GOT REQUEST $request")
Timber.d("BACKUP: GOT REQUEST $request") if (request == null) {
// Backup is up to date
// Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess()
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
if (request == null) { // backupAllGroupSessionsCallback?.onSuccess(Unit)
// Backup is up to date resetBackupAllGroupSessionsListeners()
// Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess() } else {
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp try {
if (request is Request.KeysBackup) {
keysBackupStateManager.state = KeysBackupState.BackingUp
backupAllGroupSessionsCallback?.onSuccess(Unit) Timber.d("BACKUP SENDING REQUEST")
resetBackupAllGroupSessionsListeners() val response = withContext(coroutineDispatchers.io) { sender.backupRoomKeys(request) }
} else { Timber.d("BACKUP GOT RESPONSE $response")
try { olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_BACKUP, response)
if (request is Request.KeysBackup) { Timber.d("BACKUP MARKED REQUEST AS SENT")
keysBackupStateManager.state = KeysBackupState.BackingUp
Timber.d("BACKUP SENDING REQUEST")
val response = sender.backupRoomKeys(request)
Timber.d("BACKUP GOT RESPONSE $response")
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_BACKUP, response)
Timber.d("BACKUP MARKED REQUEST AS SENT")
// TODO, again is this correct?
withContext(Dispatchers.Main) {
backupKeys(true) backupKeys(true)
} else {
// Can't happen, do we want to panic?
} }
} else { } catch (failure: Throwable) {
// Can't happen, do we want to panic? if (failure is Failure.ServerError) {
} withContext(Dispatchers.Main) {
} catch (failure: Throwable) { Timber.e(failure, "backupKeys: backupKeys failed.")
if (failure is Failure.ServerError) {
withContext(Dispatchers.Main) {
Timber.e(failure, "backupKeys: backupKeys failed.")
when (failure.error.code) { when (failure.error.code) {
MatrixError.M_NOT_FOUND, MatrixError.M_NOT_FOUND,
MatrixError.M_WRONG_ROOM_KEYS_VERSION -> { MatrixError.M_WRONG_ROOM_KEYS_VERSION -> {
// 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
// Do not stay in KeysBackupState.WrongBackUpVersion but check what // Do not stay in KeysBackupState.WrongBackUpVersion but check what
// is available on the homeserver // is available on the homeserver
checkAndStartKeysBackup() checkAndStartKeysBackup()
}
else ->
// Come back to the ready state so that we will retry on the next received key
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
} }
else ->
// Come back to the ready state so that we will retry on the next received key
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
} }
} } else {
} else { // backupAllGroupSessionsCallback?.onFailure(failure)
withContext(Dispatchers.Main) {
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) {
this.session = session if (!this::session.isInitialized) {
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,62 +112,71 @@ 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 = keysBackup.getCurrentVersion()
val version = awaitCallback<KeysVersionResult?> { if (version?.version == null) {
keysBackup.getCurrentVersion(it) loadingEvent.postValue(null)
} _keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, "")))
if (version?.version == null) { return
loadingEvent.postValue(null) }
_keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, "")))
return@launch
}
keyVersionResult.postValue(version) keyVersionResult.postValue(version)
// Let's check if there is quads // Let's check if there is quads
val isBackupKeyInQuadS = isBackupKeyInQuadS() val isBackupKeyInQuadS = isBackupKeyInQuadS()
val savedSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() val savedSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()
if (savedSecret != null && savedSecret.version == version.version) { if (savedSecret != null && savedSecret.version == version.version) {
// key is in memory! // key is in memory!
keySourceModel.postValue( keySourceModel.postValue(
KeySource(isInMemory = true, isInQuadS = true) KeySource(isInMemory = true, isInQuadS = true)
) )
// 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) {
keySourceModel.postValue( Timber.e(failure, "## recoverUsingBackupRecoveryKey FAILED")
KeySource(isInMemory = false, isInQuadS = true)
)
}
} else if (isBackupKeyInQuadS) {
// key is in QuadS!
keySourceModel.postValue( keySourceModel.postValue(
KeySource(isInMemory = false, isInQuadS = true) KeySource(isInMemory = false, isInQuadS = true)
) )
_navigateEvent.postValue(LiveEvent(NAVIGATE_TO_4S))
} else {
// we need to restore directly
keySourceModel.postValue(
KeySource(isInMemory = false, isInQuadS = false)
)
} }
} else if (isBackupKeyInQuadS) {
loadingEvent.postValue(null) // key is in QuadS!
} catch (failure: Throwable) { keySourceModel.postValue(
loadingEvent.postValue(null) KeySource(isInMemory = false, isInQuadS = true)
_keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, failure.localizedMessage))) )
_navigateEvent.postValue(LiveEvent(NAVIGATE_TO_4S))
} else {
// we need to restore directly
keySourceModel.postValue(
KeySource(isInMemory = false, isInQuadS = false)
)
} }
loadingEvent.postValue(null)
} catch (failure: Throwable) {
loadingEvent.postValue(null)
_keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, failure.localizedMessage)))
} }
} }
@ -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)
didRecoverSucceed(result) withContext(Dispatchers.Main) {
trustOnDecrypt(keysBackup, keyVersion) didRecoverSucceed(result)
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,26 +86,24 @@ 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) {
Timber.d("BACKUP: HEEEE FAILED $failure")
override fun onFailure(failure: Throwable) { setState {
Timber.d("BACKUP: HEEEE FAILED $failure") copy(
setState { keysBackupVersionTrust = Fail(failure)
copy( )
keysBackupVersionTrust = Fail(failure) }
) }
} }
}
})
} }
} }
@ -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 {
try {
val data = session.cryptoService().keysBackupService().prepareKeysBackupVersion(withPassphrase)
if (requestedId != currentRequestId.value) {
// this is an old request, we can't cancel but we can ignore
return@launch
}
recoveryKey.postValue(data.recoveryKey)
megolmBackupCreationInfo = data
copyHasBeenMade = false
mxSession.cryptoService().keysBackupService().prepareKeysBackupVersion(withPassphrase, val keyBackup = session.cryptoService().keysBackupService()
object : ProgressListener { createKeysBackup(context, keyBackup)
override fun onProgress(progress: Int, total: Int) { } catch (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 = WaitingViewData(context.getString(R.string.keys_backup_setup_step3_generating_key_status), loadingStatus.postValue(null)
progress, isCreatingBackupVersion.postValue(false)
total) prepareRecoverFailError.postValue(failure)
} }
},
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
copyHasBeenMade = false
val keyBackup = session.cryptoService().keysBackupService()
createKeysBackup(context, keyBackup)
}
override fun onFailure(failure: Throwable) {
if (requestedId != currentRequestId.value) {
// this is an old request, we can't cancel but we can ignore
return
}
loadingStatus.value = null
isCreatingBackupVersion.value = false
prepareRecoverFailError.value = failure
}
})
} }
} }
@ -140,9 +121,11 @@ 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))
session.cryptoService().keysBackupService().checkAndStartKeysBackup() viewModelScope.launch {
session.cryptoService().keysBackupService().checkAndStartKeysBackup()
}
} }
private fun createKeysBackup(context: Context, keysBackup: KeysBackupService, forceOverride: Boolean = false) { private fun createKeysBackup(context: Context, keysBackup: KeysBackupService, forceOverride: Boolean = false) {
@ -150,45 +133,35 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() {
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.postValue(false)
keysVersion.postValue(created)
navigateEvent.value = LiveEvent(NAVIGATE_TO_STEP_3)
} catch (failure: Throwable) {
Timber.e(failure, "## createKeyBackupVersion")
loadingStatus.postValue(null)
isCreatingBackupVersion.value = false isCreatingBackupVersion.postValue(false)
creatingBackupError.value = failure creatingBackupError.postValue(failure)
} }
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)
}
override fun onFailure(failure: Throwable) {
Timber.e(failure, "## createKeyBackupVersion")
loadingStatus.value = null
isCreatingBackupVersion.value = false
creatingBackupError.value = 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.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( session.coroutineScope.launch {
version, tryOrNull {
recoveryKey, session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
null, version,
null, recoveryKey,
null, null,
NoOpMatrixCallback() null,
) null
)
}
}
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
awaitCallback<ImportRoomKeysResult> { // TODO maybe mark as trusted earlier by checking recovery key early, then download?
session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
version,
computeRecoveryKey(secret.fromBase64()),
null,
null,
null,
it
)
}
awaitCallback<Unit> { session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
session.cryptoService().keysBackupService().trustKeysBackupVersion(version, true, it) version,
} computeRecoveryKey(secret.fromBase64()),
null,
null,
null
)
session.cryptoService().keysBackupService().trustKeysBackupVersion(version, true)
} 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,7 +155,9 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS
fun refreshRemoteStateIfNeeded() { fun refreshRemoteStateIfNeeded() {
if (keysBackupState.value == KeysBackupState.Disabled) { if (keysBackupState.value == KeysBackupState.Disabled) {
session.cryptoService().keysBackupService().checkAndStartKeysBackup() viewModelScope.launch {
session.cryptoService().keysBackupService().checkAndStartKeysBackup()
}
} }
} }

View file

@ -73,7 +73,9 @@ class SignoutCheckViewModel @AssistedInject constructor(
init { init {
session.cryptoService().keysBackupService().addListener(this) session.cryptoService().keysBackupService().addListener(this)
session.cryptoService().keysBackupService().checkAndStartKeysBackup() viewModelScope.launch {
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,7 +114,9 @@ class SignoutCheckViewModel @AssistedInject constructor(
fun refreshRemoteStateIfNeeded() = withState { state -> fun refreshRemoteStateIfNeeded() = withState { state ->
if (state.keysBackupState == KeysBackupState.Disabled) { if (state.keysBackupState == KeysBackupState.Disabled) {
session.cryptoService().keysBackupService().checkAndStartKeysBackup() viewModelScope.launch {
session.cryptoService().keysBackupService().checkAndStartKeysBackup()
}
} }
} }

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>