diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/listeners/StepProgressListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/listeners/StepProgressListener.kt index 0fabfed2ff..423885299f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/listeners/StepProgressListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/listeners/StepProgressListener.kt @@ -24,6 +24,7 @@ interface StepProgressListener { sealed class Step { data class ComputingKey(val progress: Int, val total: Int) : Step() object DownloadingKey : Step() + data class DecryptingKey(val progress: Int, val total: Int) : Step() data class ImportingKey(val progress: Int, val total: Int) : Step() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt index 4464427b90..6bb4dbc620 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt @@ -16,7 +16,6 @@ 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.StepProgressListener 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 * * 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) + suspend fun getCurrentVersion(): KeysVersionResult? /** * Create a new keys backup version and enable it, using the information return from [prepareKeysBackupVersion]. * * @param keysBackupCreationInfo the info object from [prepareKeysBackupVersion]. - * @param callback Asynchronous callback + * @return KeysVersion */ - fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, - callback: MatrixCallback) + @Throws + suspend fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo): KeysVersion /** * Facility method to get the total number of locally stored keys @@ -54,23 +52,21 @@ interface KeysBackupService { */ fun getTotalNumbersOfBackedUpKeys(): Int - /** - * Start to back up keys immediately. - * - * @param progressListener the callback to follow the progress - * @param callback the main callback - */ - fun backupAllGroupSessions(progressListener: ProgressListener?, - callback: MatrixCallback?) +// /** +// * Start to back up keys immediately. +// * +// * @param progressListener the callback to follow the progress +// * @param callback the main callback +// */ +// fun backupAllGroupSessions(progressListener: ProgressListener?, +// callback: MatrixCallback?) /** * Check trust on a key backup version. * * @param keysBackupVersion the backup version to check. - * @param callback block called when the operations completes. */ - fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult, - callback: MatrixCallback) + suspend fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult): KeysBackupVersionTrust /** * Return the current progress of the backup @@ -82,18 +78,16 @@ interface KeysBackupService { * * It can be different than keysBackupVersion. * @param version the backup version - * @param callback */ - fun getVersion(version: String, - callback: MatrixCallback) + suspend fun getVersion(version: String): KeysVersionResult? /** * 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. * - * @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) + suspend fun forceUsingLastVersion(): Boolean /** * 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 * devices, start backing up to it. */ - fun checkAndStartKeysBackup() + suspend fun checkAndStartKeysBackup() 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 callback Asynchronous callback */ - fun prepareKeysBackupVersion(password: String?, - progressListener: ProgressListener?, - callback: MatrixCallback) + suspend fun prepareKeysBackupVersion(password: String?): MegolmBackupCreationInfo /** * 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. * * @param version the backup version to delete. - * @param callback Asynchronous callback */ - fun deleteBackup(version: String, - callback: MatrixCallback?) + @Throws + suspend fun deleteBackup(version: String) /** * 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 trust the trust to set to the keys backup. - * @param callback block called when the operations completes. */ - fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, - trust: Boolean, - callback: MatrixCallback) + @Throws + suspend fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, trust: Boolean) /** * Set trust on a keys backup version. * * @param keysBackupVersion the backup version to check. * @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, - recoveryKey: String, - callback: MatrixCallback) + suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, + recoveryKey: String) /** * Set trust on a keys backup version. * * @param keysBackupVersion the backup version to check. * @param password the pass phrase to challenge with the keyBackupVersion public key. - * @param callback block called when the operations completes. */ - fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, - password: String, - callback: MatrixCallback) + suspend fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, + password: String) - 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. @@ -185,11 +170,10 @@ interface KeysBackupService { * @param stepProgressListener the step progress listener * @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?, sessionId: String?, - stepProgressListener: StepProgressListener?, - callback: MatrixCallback) + stepProgressListener: StepProgressListener?): ImportRoomKeysResult /** * 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 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, roomId: String?, sessionId: String?, - stepProgressListener: StepProgressListener?, - callback: MatrixCallback) + stepProgressListener: StepProgressListener?): ImportRoomKeysResult val keysBackupVersion: KeysVersionResult? val currentBackupVersion: String? @@ -218,5 +201,5 @@ interface KeysBackupService { fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? - fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback) + suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 3c24df1e0f..1a006c51c5 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -256,7 +256,12 @@ internal class DefaultCryptoService @Inject constructor( } 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're gonna continue backing up. - tryOrNull { - keysBackupService.checkAndStartKeysBackup() + cryptoCoroutineScope.launch { + tryOrNull { + keysBackupService.checkAndStartKeysBackup() + } } // Open the store diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt index caff2d76f1..d3100ede7d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt @@ -70,4 +70,15 @@ data class MegolmSessionData( */ @Json(name = "forwarding_curve25519_key_chain") val forwardingCurve25519KeyChain: List? = null -) +) { + + fun isValid(): Boolean { + return roomId != null && + forwardingCurve25519KeyChain != null && + algorithm != null && + senderKey != null && + senderClaimedKeys != null && + sessionId != null && + sessionKey != null + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RequestSender.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RequestSender.kt index 6ce0fea71e..45644d951f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RequestSender.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RequestSender.kt @@ -213,13 +213,14 @@ internal class RequestSender @Inject constructor( suspend fun getKeyBackupVersion(version: String? = null): KeysVersionResult? { return try { if (version != null) { - getKeysBackupVersionTask.execute(version) + getKeysBackupVersionTask.executeRetry(version, 3) } else { - getKeysBackupLastVersionTask.execute(Unit) + getKeysBackupLastVersionTask.executeRetry(Unit, 3) } } catch (failure: Throwable) { if (failure is Failure.ServerError && failure.error.code == MatrixError.M_NOT_FOUND) { + // Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup null } else { throw failure diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt index b9cfd942ce..54ccbf016f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt @@ -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.IncomingSecretShareRequest import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult -import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService /** * An interface for decrypting data @@ -44,7 +43,7 @@ internal interface IMXDecrypting { * * @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 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index 8bbc71543c..ea239dad53 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -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.algorithms.IMXDecrypting 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.event.EncryptedEventContent 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. */ - override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) { + override fun onRoomKeyEvent(event: Event/*, defaultKeysBackupService: DefaultKeysBackupService*/) { Timber.tag(loggerTag.value).v("onRoomKeyEvent()") var exportFormat = false val roomKeyContent = event.getClearContent().toModel() ?: return @@ -295,7 +294,7 @@ internal class MXMegolmDecryption(private val userId: String, exportFormat) if (added) { - defaultKeysBackupService.maybeBackupKeys() + // defaultKeysBackupService.maybeBackupKeys() val content = RoomKeyRequestBody( algorithm = roomKeyContent.algorithm, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index 44b59b208a..bc479f0537 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -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.algorithms.IMXEncrypting 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.MXUsersDevicesMap 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. private val roomId: String, private val olmDevice: MXOlmDevice, - private val defaultKeysBackupService: DefaultKeysBackupService, +// private val defaultKeysBackupService: DefaultKeysBackupService, private val cryptoStore: IMXCryptoStore, private val deviceListManager: DeviceListManager, private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, @@ -149,7 +149,7 @@ internal class MXMegolmEncryption( olmDevice.addInboundGroupSession(sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!, emptyList(), keysClaimedMap, false) - defaultKeysBackupService.maybeBackupKeys() +// defaultKeysBackupService.maybeBackupKeys() return MXOutboundSessionInfo(sessionId, SharedWithHelper(roomId, sessionId, cryptoStore)) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt index 136fdc05f5..3797f3a588 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt @@ -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.actions.EnsureOlmSessionsForDevicesAction 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.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask @@ -32,7 +31,7 @@ import javax.inject.Inject internal class MXMegolmEncryptionFactory @Inject constructor( private val olmDevice: MXOlmDevice, - private val defaultKeysBackupService: DefaultKeysBackupService, +// private val defaultKeysBackupService: DefaultKeysBackupService, private val cryptoStore: IMXCryptoStore, private val deviceListManager: DeviceListManager, private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, @@ -48,7 +47,7 @@ internal class MXMegolmEncryptionFactory @Inject constructor( return MXMegolmEncryption( roomId = roomId, olmDevice = olmDevice, - defaultKeysBackupService = defaultKeysBackupService, +// defaultKeysBackupService = defaultKeysBackupService, cryptoStore = cryptoStore, deviceListManager = deviceListManager, ensureOlmSessionsForDevicesAction = ensureOlmSessionsForDevicesAction, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt index b0c7c8b610..c70a01f54e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -1,1438 +1,1438 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.keysbackup - -import android.os.Handler -import android.os.Looper -import androidx.annotation.UiThread -import androidx.annotation.VisibleForTesting -import androidx.annotation.WorkerThread -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -import org.matrix.android.sdk.api.auth.data.Credentials -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixError -import org.matrix.android.sdk.api.listeners.ProgressListener -import org.matrix.android.sdk.api.listeners.StepProgressListener -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.KeysBackupStateListener -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP -import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.MegolmSessionData -import org.matrix.android.sdk.internal.crypto.ObjectSigner -import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter -import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 -import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust -import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrustSignature -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo -import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmBackupAuthData -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeyBackupData -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData -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.model.rest.RoomKeysBackupData -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteBackupTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteRoomSessionDataTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteRoomSessionsDataTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteSessionsDataTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupLastVersionTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionDataTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionsDataTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetSessionsDataTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionDataTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask -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.model.ImportRoomKeysResult -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo -import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity -import org.matrix.android.sdk.internal.di.MoshiProvider -import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.extensions.foldToCallback -import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.task.Task -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.TaskThread -import org.matrix.android.sdk.internal.task.configureWith -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.OlmPkDecryption -import org.matrix.olm.OlmPkEncryption -import org.matrix.olm.OlmPkMessage -import timber.log.Timber -import java.security.InvalidParameterException -import javax.inject.Inject -import kotlin.random.Random - -/** - * A DefaultKeysBackupService class instance manage incremental backup of e2e keys (megolm keys) - * to the user's homeserver. - */ -@SessionScope -@Deprecated("use rust") -internal class DefaultKeysBackupService @Inject constructor( - @UserId private val userId: String, - private val credentials: Credentials, - private val cryptoStore: IMXCryptoStore, - private val olmDevice: MXOlmDevice, - private val objectSigner: ObjectSigner, - // Actions - private val megolmSessionDataImporter: MegolmSessionDataImporter, - // Tasks - private val createKeysBackupVersionTask: CreateKeysBackupVersionTask, - private val deleteBackupTask: DeleteBackupTask, - private val deleteRoomSessionDataTask: DeleteRoomSessionDataTask, - private val deleteRoomSessionsDataTask: DeleteRoomSessionsDataTask, - private val deleteSessionDataTask: DeleteSessionsDataTask, - private val getKeysBackupLastVersionTask: GetKeysBackupLastVersionTask, - private val getKeysBackupVersionTask: GetKeysBackupVersionTask, - private val getRoomSessionDataTask: GetRoomSessionDataTask, - private val getRoomSessionsDataTask: GetRoomSessionsDataTask, - private val getSessionsDataTask: GetSessionsDataTask, - private val storeRoomSessionDataTask: StoreRoomSessionDataTask, - private val storeSessionsDataTask: StoreRoomSessionsDataTask, - private val storeSessionDataTask: StoreSessionsDataTask, - private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask, - // Task executor - private val taskExecutor: TaskExecutor, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val cryptoCoroutineScope: CoroutineScope -) : KeysBackupService { - - private val uiHandler = Handler(Looper.getMainLooper()) - - private val keysBackupStateManager = KeysBackupStateManager(uiHandler) - - // The backup version - override var keysBackupVersion: KeysVersionResult? = null - private set - - // The backup key being used. - private var backupOlmPkEncryption: OlmPkEncryption? = null - - private var backupAllGroupSessionsCallback: MatrixCallback? = null - - private var keysBackupStateListener: KeysBackupStateListener? = null - - override val isEnabled: Boolean - get() = keysBackupStateManager.isEnabled - - override val isStucked: Boolean - get() = keysBackupStateManager.isStucked - - override val state: KeysBackupState - get() = keysBackupStateManager.state - - override val currentBackupVersion: String? - get() = keysBackupVersion?.version - - override fun addListener(listener: KeysBackupStateListener) { - keysBackupStateManager.addListener(listener) - } - - override fun removeListener(listener: KeysBackupStateListener) { - keysBackupStateManager.removeListener(listener) - } - - override fun prepareKeysBackupVersion(password: String?, - progressListener: ProgressListener?, - callback: MatrixCallback) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - withContext(coroutineDispatchers.crypto) { - val olmPkDecryption = OlmPkDecryption() - val signalableMegolmBackupAuthData = if (password != null) { - // Generate a private key from the password - val backgroundProgressListener = if (progressListener == null) { - null - } else { - object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - uiHandler.post { - try { - progressListener.onProgress(progress, total) - } catch (e: Exception) { - Timber.e(e, "prepareKeysBackupVersion: onProgress failure") - } - } - } - } - } - - val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener) - SignalableMegolmBackupAuthData( - publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey), - privateKeySalt = generatePrivateKeyResult.salt, - privateKeyIterations = generatePrivateKeyResult.iterations - ) - } else { - val publicKey = olmPkDecryption.generateKey() - - SignalableMegolmBackupAuthData( - publicKey = publicKey - ) - } - - val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableMegolmBackupAuthData.signalableJSONDictionary()) - - val signedMegolmBackupAuthData = MegolmBackupAuthData( - publicKey = signalableMegolmBackupAuthData.publicKey, - privateKeySalt = signalableMegolmBackupAuthData.privateKeySalt, - privateKeyIterations = signalableMegolmBackupAuthData.privateKeyIterations, - signatures = objectSigner.signObject(canonicalJson) - ) - - MegolmBackupCreationInfo( - algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, - authData = signedMegolmBackupAuthData, - recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey()) - ) - } - }.foldToCallback(callback) - } - } - - override fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, - callback: MatrixCallback) { - @Suppress("UNCHECKED_CAST") - val createKeysBackupVersionBody = CreateKeysBackupVersionBody( - algorithm = keysBackupCreationInfo.algorithm, - authData = keysBackupCreationInfo.authData.toJsonDict() - ) - - keysBackupStateManager.state = KeysBackupState.Enabling - - createKeysBackupVersionTask - .configureWith(createKeysBackupVersionBody) { - this.callback = object : MatrixCallback { - override fun onSuccess(data: KeysVersion) { - // Reset backup markers. - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - // move tx out of UI thread - cryptoStore.resetBackupMarkers() - } - - val keyBackupVersion = KeysVersionResult( - algorithm = createKeysBackupVersionBody.algorithm, - authData = createKeysBackupVersionBody.authData, - version = data.version, - // We can consider that the server does not have keys yet - count = 0, - hash = "" - ) - - enableKeysBackup(keyBackupVersion) - - callback.onSuccess(data) - } - - override fun onFailure(failure: Throwable) { - keysBackupStateManager.state = KeysBackupState.Disabled - callback.onFailure(failure) - } - } - } - .executeBy(taskExecutor) - } - - override fun deleteBackup(version: String, callback: MatrixCallback?) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - withContext(coroutineDispatchers.crypto) { - // If we're currently backing up to this backup... stop. - // (We start using it automatically in createKeysBackupVersion so this is symmetrical). - if (keysBackupVersion != null && version == keysBackupVersion?.version) { - resetKeysBackupData() - keysBackupVersion = null - keysBackupStateManager.state = KeysBackupState.Unknown - } - - deleteBackupTask - .configureWith(DeleteBackupTask.Params(version)) { - this.callback = object : MatrixCallback { - private fun eventuallyRestartBackup() { - // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver - if (state == KeysBackupState.Unknown) { - checkAndStartKeysBackup() - } - } - - override fun onSuccess(data: Unit) { - eventuallyRestartBackup() - - uiHandler.post { callback?.onSuccess(Unit) } - } - - override fun onFailure(failure: Throwable) { - eventuallyRestartBackup() - - uiHandler.post { callback?.onFailure(failure) } - } - } - } - .executeBy(taskExecutor) - } - } - } - - override fun canRestoreKeys(): Boolean { - // Server contains more keys than locally - val totalNumberOfKeysLocally = getTotalNumbersOfKeys() - - val keysBackupData = cryptoStore.getKeysBackupData() - - val totalNumberOfKeysServer = keysBackupData?.backupLastServerNumberOfKeys ?: -1 - // Not used for the moment - // val hashServer = keysBackupData?.backupLastServerHash - - return when { - totalNumberOfKeysLocally < totalNumberOfKeysServer -> { - // Server contains more keys than this device - true - } - totalNumberOfKeysLocally == totalNumberOfKeysServer -> { - // Same number, compare hash? - // TODO We have not found any algorithm to determine if a restore is recommended here. Return false for the moment - false - } - else -> false - } - } - - override fun getTotalNumbersOfKeys(): Int { - return cryptoStore.inboundGroupSessionsCount(false) - } - - override fun getTotalNumbersOfBackedUpKeys(): Int { - return cryptoStore.inboundGroupSessionsCount(true) - } - - override fun backupAllGroupSessions(progressListener: ProgressListener?, - callback: MatrixCallback?) { - // Get a status right now - getBackupProgress(object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - // Reset previous listeners if any - resetBackupAllGroupSessionsListeners() - Timber.v("backupAllGroupSessions: backupProgress: $progress/$total") - try { - progressListener?.onProgress(progress, total) - } catch (e: Exception) { - Timber.e(e, "backupAllGroupSessions: onProgress failure") - } - - if (progress == total) { - Timber.v("backupAllGroupSessions: complete") - callback?.onSuccess(Unit) - return - } - - backupAllGroupSessionsCallback = callback - - // Listen to `state` change to determine when to call onBackupProgress and onComplete - keysBackupStateListener = object : KeysBackupStateListener { - override fun onStateChange(newState: KeysBackupState) { - getBackupProgress(object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - try { - progressListener?.onProgress(progress, total) - } catch (e: Exception) { - Timber.e(e, "backupAllGroupSessions: onProgress failure 2") - } - - // If backup is finished, notify the main listener - if (state === KeysBackupState.ReadyToBackUp) { - backupAllGroupSessionsCallback?.onSuccess(Unit) - resetBackupAllGroupSessionsListeners() - } - } - }) - } - }.also { keysBackupStateManager.addListener(it) } - - backupKeys() - } - }) - } - - override fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult, - callback: MatrixCallback) { - // TODO Validate with François that this is correct - object : Task { - override suspend fun execute(params: KeysVersionResult): KeysBackupVersionTrust { - return getKeysBackupTrustBg(params) - } - } - .configureWith(keysBackupVersion) { - this.callback = callback - this.executionThread = TaskThread.COMPUTATION - } - .executeBy(taskExecutor) - } - - /** - * Check trust on a key backup version. - * This has to be called on background thread. - * - * @param keysBackupVersion the backup version to check. - * @return a KeysBackupVersionTrust object - */ - @WorkerThread - private fun getKeysBackupTrustBg(keysBackupVersion: KeysVersionResult): KeysBackupVersionTrust { - val keysBackupVersionTrust = KeysBackupVersionTrust() - val authData = keysBackupVersion.getAuthDataAsMegolmBackupAuthData() - - if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isNullOrEmpty()) { - Timber.v("getKeysBackupTrust: Key backup is absent or missing required data") - return keysBackupVersionTrust - } - - val mySigs = authData.signatures[userId] - if (mySigs.isNullOrEmpty()) { - Timber.v("getKeysBackupTrust: Ignoring key backup because it lacks any signatures from this user") - return keysBackupVersionTrust - } - - for ((keyId, mySignature) in mySigs) { - // XXX: is this how we're supposed to get the device id? - var deviceId: String? = null - val components = keyId.split(":") - if (components.size == 2) { - deviceId = components[1] - } - - if (deviceId != null) { - val device = cryptoStore.getUserDevice(userId, deviceId) - var isSignatureValid = false - - if (device == null) { - Timber.v("getKeysBackupTrust: Signature from unknown device $deviceId") - } else { - val fingerprint = device.fingerprint() - if (fingerprint != null) { - try { - olmDevice.verifySignature(fingerprint, authData.signalableJSONDictionary(), mySignature) - isSignatureValid = true - } catch (e: OlmException) { - Timber.w(e, "getKeysBackupTrust: Bad signature from device ${device.deviceId}") - } - } - - if (isSignatureValid && device.isVerified) { - keysBackupVersionTrust.usable = true - } - } - - val signature = KeysBackupVersionTrustSignature() - signature.device = device - signature.valid = isSignatureValid - signature.deviceId = deviceId - keysBackupVersionTrust.signatures.add(signature) - } - } - - return keysBackupVersionTrust - } - - override fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, - trust: Boolean, - callback: MatrixCallback) { - Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}") - - // Get auth data to update it - val authData = getMegolmBackupAuthData(keysBackupVersion) - - if (authData == null) { - Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data") - - callback.onFailure(IllegalArgumentException("Missing element")) - } else { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) { - // Get current signatures, or create an empty set - val myUserSignatures = authData.signatures?.get(userId).orEmpty().toMutableMap() - - if (trust) { - // Add current device signature - val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary()) - - val deviceSignatures = objectSigner.signObject(canonicalJson) - - deviceSignatures[userId]?.forEach { entry -> - myUserSignatures[entry.key] = entry.value - } - } else { - // Remove current device signature - myUserSignatures.remove("ed25519:${credentials.deviceId}") - } - - // Create an updated version of KeysVersionResult - val newMegolmBackupAuthData = authData.copy() - - val newSignatures = newMegolmBackupAuthData.signatures.orEmpty().toMutableMap() - newSignatures[userId] = myUserSignatures - - val newMegolmBackupAuthDataWithNewSignature = newMegolmBackupAuthData.copy( - signatures = newSignatures - ) - - @Suppress("UNCHECKED_CAST") - UpdateKeysBackupVersionBody( - algorithm = keysBackupVersion.algorithm, - authData = newMegolmBackupAuthDataWithNewSignature.toJsonDict(), - version = keysBackupVersion.version) - } - - // And send it to the homeserver - updateKeysBackupVersionTask - .configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version, updateKeysBackupVersionBody)) { - this.callback = object : MatrixCallback { - override fun onSuccess(data: Unit) { - // Relaunch the state machine on this updated backup version - val newKeysBackupVersion = KeysVersionResult( - algorithm = keysBackupVersion.algorithm, - authData = updateKeysBackupVersionBody.authData, - version = keysBackupVersion.version, - hash = keysBackupVersion.hash, - count = keysBackupVersion.count - ) - - checkAndStartWithKeysBackupVersion(newKeysBackupVersion) - - callback.onSuccess(data) - } - - override fun onFailure(failure: Throwable) { - callback.onFailure(failure) - } - } - } - .executeBy(taskExecutor) - } - } - } - - override fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, - recoveryKey: String, - callback: MatrixCallback) { - Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}") - - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - val isValid = withContext(coroutineDispatchers.crypto) { - isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion) - } - - if (!isValid) { - Timber.w("trustKeyBackupVersionWithRecoveryKey: Invalid recovery key.") - - callback.onFailure(IllegalArgumentException("Invalid recovery key or password")) - } else { - trustKeysBackupVersion(keysBackupVersion, true, callback) - } - } - } - - override fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, - password: String, - callback: MatrixCallback) { - Timber.v("trustKeysBackupVersionWithPassphrase: version ${keysBackupVersion.version}") - - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - val recoveryKey = withContext(coroutineDispatchers.crypto) { - recoveryKeyFromPassword(password, keysBackupVersion, null) - } - - if (recoveryKey == null) { - Timber.w("trustKeysBackupVersionWithPassphrase: Key backup is missing required data") - - callback.onFailure(IllegalArgumentException("Missing element")) - } else { - // Check trust using the recovery key - trustKeysBackupVersionWithRecoveryKey(keysBackupVersion, recoveryKey, callback) - } - } - } - - override fun onSecretKeyGossip(secret: String) { - Timber.i("## CrossSigning - onSecretKeyGossip") - - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - try { - val keysBackupVersion = getKeysBackupLastVersionTask.execute(Unit) - val recoveryKey = computeRecoveryKey(secret.fromBase64()) - if (isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)) { - awaitCallback { - trustKeysBackupVersion(keysBackupVersion, true, it) - } - val importResult = awaitCallback { - restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, null, null, null, it) - } - withContext(coroutineDispatchers.crypto) { - cryptoStore.saveBackupRecoveryKey(recoveryKey, keysBackupVersion.version) - } - Timber.i("onSecretKeyGossip: Recovered keys ${importResult.successfullyNumberOfImportedKeys} out of ${importResult.totalNumberOfKeys}") - } else { - Timber.e("onSecretKeyGossip: Recovery key is not valid ${keysBackupVersion.version}") - } - } catch (failure: Throwable) { - Timber.e("onSecretKeyGossip: failed to trust key backup version ${keysBackupVersion?.version}") - } - } - } - - /** - * Get public key from a Recovery key - * - * @param recoveryKey the recovery key - * @return the corresponding public key, from Olm - */ - @WorkerThread - private fun pkPublicKeyFromRecoveryKey(recoveryKey: String): String? { - // Extract the primary key - val privateKey = extractCurveKeyFromRecoveryKey(recoveryKey) - - if (privateKey == null) { - Timber.w("pkPublicKeyFromRecoveryKey: private key is null") - - return null - } - - // Built the PK decryption with it - val pkPublicKey: String - - try { - val decryption = OlmPkDecryption() - pkPublicKey = decryption.setPrivateKey(privateKey) - } catch (e: OlmException) { - return null - } - - return pkPublicKey - } - - private fun resetBackupAllGroupSessionsListeners() { - backupAllGroupSessionsCallback = null - - keysBackupStateListener?.let { - keysBackupStateManager.removeListener(it) - } - - keysBackupStateListener = null - } - - override fun getBackupProgress(progressListener: ProgressListener) { - val backedUpKeys = cryptoStore.inboundGroupSessionsCount(true) - val total = cryptoStore.inboundGroupSessionsCount(false) - - progressListener.onProgress(backedUpKeys, total) - } - - override fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, - recoveryKey: String, - roomId: String?, - sessionId: String?, - stepProgressListener: StepProgressListener?, - callback: MatrixCallback) { - Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}") - - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - val decryption = withContext(coroutineDispatchers.crypto) { - // Check if the recovery is valid before going any further - if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysVersionResult)) { - Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version") - throw InvalidParameterException("Invalid recovery key") - } - - // Get a PK decryption instance - pkDecryptionFromRecoveryKey(recoveryKey) - } - if (decryption == null) { - // This should not happen anymore - Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key. Error") - throw InvalidParameterException("Invalid recovery key") - } - - stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey) - - // Get backed up keys from the homeserver - val data = getKeys(sessionId, roomId, keysVersionResult.version) - - withContext(coroutineDispatchers.computation) { - val sessionsData = ArrayList() - // Restore that data - var sessionsFromHsCount = 0 - for ((roomIdLoop, backupData) in data.roomIdToRoomKeysBackupData) { - for ((sessionIdLoop, keyBackupData) in backupData.sessionIdToKeyBackupData) { - sessionsFromHsCount++ - - val sessionData = decryptKeyBackupData(keyBackupData, sessionIdLoop, roomIdLoop, decryption) - - sessionData?.let { - sessionsData.add(it) - } - } - } - Timber.v("restoreKeysWithRecoveryKey: Decrypted ${sessionsData.size} keys out" + - " of $sessionsFromHsCount from the backup store on the homeserver") - - // Do not trigger a backup for them if they come from the backup version we are using - val backUp = keysVersionResult.version != keysBackupVersion?.version - if (backUp) { - Timber.v("restoreKeysWithRecoveryKey: Those keys will be backed up" + - " to backup version: ${keysBackupVersion?.version}") - } - - // Import them into the crypto store - val progressListener = if (stepProgressListener != null) { - object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - // Note: no need to post to UI thread, importMegolmSessionsData() will do it - stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total)) - } - } - } else { - null - } - - val result = megolmSessionDataImporter.handle(sessionsData, !backUp, progressListener) - - // Do not back up the key if it comes from a backup recovery - if (backUp) { - maybeBackupKeys() - } - // Save for next time and for gossiping - saveBackupRecoveryKey(recoveryKey, keysVersionResult.version) - result - } - }.foldToCallback(callback) - } - } - - override fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, - password: String, - roomId: String?, - sessionId: String?, - stepProgressListener: StepProgressListener?, - callback: MatrixCallback) { - Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}") - - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - val progressListener = if (stepProgressListener != null) { - object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - uiHandler.post { - stepProgressListener.onStepProgress(StepProgressListener.Step.ComputingKey(progress, total)) - } - } - } - } else { - null - } - - val recoveryKey = withContext(coroutineDispatchers.crypto) { - recoveryKeyFromPassword(password, keysBackupVersion, progressListener) - } - if (recoveryKey == null) { - Timber.v("backupKeys: Invalid configuration") - throw IllegalStateException("Invalid configuration") - } else { - awaitCallback { - restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener, it) - } - } - }.foldToCallback(callback) - } - } - - /** - * Same method as [RoomKeysRestClient.getRoomKey] except that it accepts nullable - * parameters and always returns a KeysBackupData object through the Callback - */ - private suspend fun getKeys(sessionId: String?, - roomId: String?, - version: String): KeysBackupData { - return if (roomId != null && sessionId != null) { - // Get key for the room and for the session - val data = getRoomSessionDataTask.execute(GetRoomSessionDataTask.Params(roomId, sessionId, version)) - // Convert to KeysBackupData - KeysBackupData(mutableMapOf( - roomId to RoomKeysBackupData(mutableMapOf( - sessionId to data - )) - )) - } else if (roomId != null) { - // Get all keys for the room - val data = getRoomSessionsDataTask.execute(GetRoomSessionsDataTask.Params(roomId, version)) - // Convert to KeysBackupData - KeysBackupData(mutableMapOf(roomId to data)) - } else { - // Get all keys - getSessionsDataTask.execute(GetSessionsDataTask.Params(version)) - } - } - - @VisibleForTesting - @WorkerThread - fun pkDecryptionFromRecoveryKey(recoveryKey: String): OlmPkDecryption? { - // Extract the primary key - val privateKey = extractCurveKeyFromRecoveryKey(recoveryKey) - - // Built the PK decryption with it - var decryption: OlmPkDecryption? = null - if (privateKey != null) { - try { - decryption = OlmPkDecryption() - decryption.setPrivateKey(privateKey) - } catch (e: OlmException) { - Timber.e(e, "OlmException") - } - } - - return decryption - } - - /** - * Do a backup if there are new keys, with a delay - */ - fun maybeBackupKeys() { - when { - isStucked -> { - // If not already done, or in error case, check for a valid backup version on the homeserver. - // If there is one, maybeBackupKeys will be called again. - checkAndStartKeysBackup() - } - state == KeysBackupState.ReadyToBackUp -> { - keysBackupStateManager.state = KeysBackupState.WillBackUp - - // Wait between 0 and 10 seconds, to avoid backup requests from - // different clients hitting the server all at the same time when a - // new key is sent - val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS) - - cryptoCoroutineScope.launch { - delay(delayInMs) - uiHandler.post { backupKeys() } - } - } - else -> { - Timber.v("maybeBackupKeys: Skip it because state: $state") - } - } - } - - override fun getVersion(version: String, - callback: MatrixCallback) { - getKeysBackupVersionTask - .configureWith(version) { - this.callback = object : MatrixCallback { - override fun onSuccess(data: KeysVersionResult) { - callback.onSuccess(data) - } - - override fun onFailure(failure: Throwable) { - if (failure is Failure.ServerError && - failure.error.code == MatrixError.M_NOT_FOUND) { - // Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup - callback.onSuccess(null) - } else { - // Transmit the error - callback.onFailure(failure) - } - } - } - } - .executeBy(taskExecutor) - } - - override fun getCurrentVersion(callback: MatrixCallback) { - getKeysBackupLastVersionTask - .configureWith { - this.callback = object : MatrixCallback { - override fun onSuccess(data: KeysVersionResult) { - callback.onSuccess(data) - } - - override fun onFailure(failure: Throwable) { - if (failure is Failure.ServerError && - failure.error.code == MatrixError.M_NOT_FOUND) { - // Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup - callback.onSuccess(null) - } else { - // Transmit the error - callback.onFailure(failure) - } - } - } - } - .executeBy(taskExecutor) - } - - override fun forceUsingLastVersion(callback: MatrixCallback) { - getCurrentVersion(object : MatrixCallback { - override fun onSuccess(data: KeysVersionResult?) { - val localBackupVersion = keysBackupVersion?.version - val serverBackupVersion = data?.version - - if (serverBackupVersion == null) { - if (localBackupVersion == null) { - // No backup on the server, and backup is not active - callback.onSuccess(true) - } else { - // No backup on the server, and we are currently backing up, so stop backing up - callback.onSuccess(false) - resetKeysBackupData() - keysBackupVersion = null - keysBackupStateManager.state = KeysBackupState.Disabled - } - } else { - if (localBackupVersion == null) { - // backup on the server, and backup is not active - callback.onSuccess(false) - // Do a check - checkAndStartWithKeysBackupVersion(data) - } 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 - callback.onSuccess(true) - } else { - // We are not using the last version, so delete the current version we are using on the server - callback.onSuccess(false) - - // This will automatically check for the last version then - deleteBackup(localBackupVersion, null) - } - } - } - } - - override fun onFailure(failure: Throwable) { - callback.onFailure(failure) - } - }) - } - - override fun checkAndStartKeysBackup() { - 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 { - 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}") - - keysBackupVersion = keyBackupVersion - - if (keyBackupVersion == null) { - Timber.v("checkAndStartWithKeysBackupVersion: Found no key backup version on the homeserver") - resetKeysBackupData() - keysBackupStateManager.state = KeysBackupState.Disabled - } else { - getKeysBackupTrust(keyBackupVersion, object : MatrixCallback { - override fun onSuccess(data: KeysBackupVersionTrust) { - val versionInStore = cryptoStore.getKeyBackupVersion() - - if (data.usable) { - Timber.v("checkAndStartWithKeysBackupVersion: Found usable key backup. version: ${keyBackupVersion.version}") - // Check the version we used at the previous app run - if (versionInStore != null && versionInStore != keyBackupVersion.version) { - Timber.v(" -> clean the previously used version $versionInStore") - resetKeysBackupData() - } - - Timber.v(" -> enabling key backups") - 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) { - // Cannot happen - } - }) - } - } - -/* ========================================================================================== - * Private - * ========================================================================================== */ - - /** - * Extract MegolmBackupAuthData data from a backup version. - * - * @param keysBackupData the key backup data - * - * @return the authentication if found and valid, null in other case - */ - private fun getMegolmBackupAuthData(keysBackupData: KeysVersionResult): MegolmBackupAuthData? { - return keysBackupData - .takeIf { it.version.isNotEmpty() && it.algorithm == MXCRYPTO_ALGORITHM_MEGOLM_BACKUP } - ?.getAuthDataAsMegolmBackupAuthData() - ?.takeIf { it.publicKey.isNotEmpty() } - } - - /** - * Compute the recovery key from a password and key backup version. - * - * @param password the password. - * @param keysBackupData the backup and its auth data. - * - * @return the recovery key if successful, null in other cases - */ - @WorkerThread - private fun recoveryKeyFromPassword(password: String, keysBackupData: KeysVersionResult, progressListener: ProgressListener?): String? { - val authData = getMegolmBackupAuthData(keysBackupData) - - if (authData == null) { - Timber.w("recoveryKeyFromPassword: invalid parameter") - return null - } - - if (authData.privateKeySalt.isNullOrBlank() || - authData.privateKeyIterations == null) { - Timber.w("recoveryKeyFromPassword: Salt and/or iterations not found in key backup auth data") - - return null - } - - // Extract the recovery key from the passphrase - val data = retrievePrivateKeyWithPassword(password, authData.privateKeySalt, authData.privateKeyIterations, progressListener) - - return computeRecoveryKey(data) - } - - /** - * Check if a recovery key matches key backup authentication data. - * - * @param recoveryKey the recovery key to challenge. - * @param keysBackupData the backup and its auth data. - * - * @return true if successful. - */ - @WorkerThread - private fun isValidRecoveryKeyForKeysBackupVersion(recoveryKey: String, keysBackupData: KeysVersionResult): Boolean { - // Build PK decryption instance with the recovery key - val publicKey = pkPublicKeyFromRecoveryKey(recoveryKey) - - if (publicKey == null) { - Timber.w("isValidRecoveryKeyForKeysBackupVersion: public key is null") - - return false - } - - val authData = getMegolmBackupAuthData(keysBackupData) - - if (authData == null) { - Timber.w("isValidRecoveryKeyForKeysBackupVersion: Key backup is missing required data") - - return false - } - - // Compare both - if (publicKey != authData.publicKey) { - Timber.w("isValidRecoveryKeyForKeysBackupVersion: Public keys mismatch") - - return false - } - - // Public keys match! - return true - } - - override fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback) { - val safeKeysBackupVersion = keysBackupVersion ?: return Unit.also { callback.onSuccess(false) } - - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - isValidRecoveryKeyForKeysBackupVersion(recoveryKey, safeKeysBackupVersion).let { - callback.onSuccess(it) - } - } - } - - /** - * Enable backing up of keys. - * This method will update the state and will start sending keys in nominal case - * - * @param keysVersionResult backup information object as returned by [getCurrentVersion]. - */ - private fun enableKeysBackup(keysVersionResult: KeysVersionResult) { - val retrievedMegolmBackupAuthData = keysVersionResult.getAuthDataAsMegolmBackupAuthData() - - if (retrievedMegolmBackupAuthData != null) { - keysBackupVersion = keysVersionResult - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - cryptoStore.setKeyBackupVersion(keysVersionResult.version) - } - - onServerDataRetrieved(keysVersionResult.count, keysVersionResult.hash) - - try { - backupOlmPkEncryption = OlmPkEncryption().apply { - setRecipientKey(retrievedMegolmBackupAuthData.publicKey) - } - } catch (e: OlmException) { - Timber.e(e, "OlmException") - keysBackupStateManager.state = KeysBackupState.Disabled - return - } - - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp - - maybeBackupKeys() - } else { - Timber.e("Invalid authentication data") - keysBackupStateManager.state = KeysBackupState.Disabled - } - } - - /** - * Update the DB with data fetch from the server - */ - private fun onServerDataRetrieved(count: Int?, etag: String?) { - cryptoStore.setKeysBackupData(KeysBackupDataEntity() - .apply { - backupLastServerNumberOfKeys = count - backupLastServerHash = etag - } - ) - } - - /** - * Reset all local key backup data. - * - * Note: This method does not update the state - */ - private fun resetKeysBackupData() { - resetBackupAllGroupSessionsListeners() - - cryptoStore.setKeyBackupVersion(null) - cryptoStore.setKeysBackupData(null) - backupOlmPkEncryption?.releaseEncryption() - backupOlmPkEncryption = null - - // Reset backup markers - cryptoStore.resetBackupMarkers() - } - - /** - * Send a chunk of keys to backup - */ - @UiThread - private fun backupKeys() { - Timber.v("backupKeys") - - // Sanity check, as this method can be called after a delay, the state may have change during the delay - if (!isEnabled || backupOlmPkEncryption == null || keysBackupVersion == null) { - Timber.v("backupKeys: Invalid configuration") - backupAllGroupSessionsCallback?.onFailure(IllegalStateException("Invalid configuration")) - resetBackupAllGroupSessionsListeners() - - return - } - - if (state === KeysBackupState.BackingUp) { - // Do nothing if we are already backing up - Timber.v("backupKeys: Invalid state: $state") - return - } - - // Get a chunk of keys to backup - val olmInboundGroupSessionWrappers = cryptoStore.inboundGroupSessionsToBackup(KEY_BACKUP_SEND_KEYS_MAX_COUNT) - - Timber.v("backupKeys: 1 - ${olmInboundGroupSessionWrappers.size} sessions to back up") - - if (olmInboundGroupSessionWrappers.isEmpty()) { - // Backup is up to date - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp - - backupAllGroupSessionsCallback?.onSuccess(Unit) - resetBackupAllGroupSessionsListeners() - return - } - - keysBackupStateManager.state = KeysBackupState.BackingUp - - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - withContext(coroutineDispatchers.crypto) { - Timber.v("backupKeys: 2 - Encrypting keys") - - // Gather data to send to the homeserver - // roomId -> sessionId -> MXKeyBackupData - val keysBackupData = KeysBackupData() - - olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper -> - val roomId = olmInboundGroupSessionWrapper.roomId ?: return@forEach - val olmInboundGroupSession = olmInboundGroupSessionWrapper.olmInboundGroupSession ?: return@forEach - - try { - encryptGroupSession(olmInboundGroupSessionWrapper) - ?.let { - keysBackupData.roomIdToRoomKeysBackupData - .getOrPut(roomId) { RoomKeysBackupData() } - .sessionIdToKeyBackupData[olmInboundGroupSession.sessionIdentifier()] = it - } - } catch (e: OlmException) { - Timber.e(e, "OlmException") - } - } - - Timber.v("backupKeys: 4 - Sending request") - - // Make the request - val version = keysBackupVersion?.version ?: return@withContext - - storeSessionDataTask - .configureWith(StoreSessionsDataTask.Params(version, keysBackupData)) { - this.callback = object : MatrixCallback { - override fun onSuccess(data: BackupKeysResult) { - uiHandler.post { - Timber.v("backupKeys: 5a - Request complete") - - // Mark keys as backed up - cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers) - - if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) { - Timber.v("backupKeys: All keys have been backed up") - onServerDataRetrieved(data.count, data.hash) - - // Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess() - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp - } else { - Timber.v("backupKeys: Continue to back up keys") - keysBackupStateManager.state = KeysBackupState.WillBackUp - - backupKeys() - } - } - } - - override fun onFailure(failure: Throwable) { - if (failure is Failure.ServerError) { - uiHandler.post { - Timber.e(failure, "backupKeys: backupKeys failed.") - - when (failure.error.code) { - MatrixError.M_NOT_FOUND, - MatrixError.M_WRONG_ROOM_KEYS_VERSION -> { - // Backup has been deleted on the server, or we are not using the last backup version - keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion - backupAllGroupSessionsCallback?.onFailure(failure) - resetBackupAllGroupSessionsListeners() - resetKeysBackupData() - keysBackupVersion = null - - // Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver - checkAndStartKeysBackup() - } - else -> - // Come back to the ready state so that we will retry on the next received key - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp - } - } - } else { - uiHandler.post { - backupAllGroupSessionsCallback?.onFailure(failure) - resetBackupAllGroupSessionsListeners() - - Timber.e("backupKeys: backupKeys failed.") - - // Retry a bit later - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp - maybeBackupKeys() - } - } - } - } - } - .executeBy(taskExecutor) - } - } - } - - @VisibleForTesting - @WorkerThread - fun encryptGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2): KeyBackupData? { - // Gather information for each key - val device = olmInboundGroupSessionWrapper.senderKey?.let { cryptoStore.deviceWithIdentityKey(it) } - - // Build the m.megolm_backup.v1.curve25519-aes-sha2 data as defined at - // https://github.com/uhoreg/matrix-doc/blob/e2e_backup/proposals/1219-storing-megolm-keys-serverside.md#mmegolm_backupv1curve25519-aes-sha2-key-format - val sessionData = olmInboundGroupSessionWrapper.exportKeys() ?: return null - val sessionBackupData = mapOf( - "algorithm" to sessionData.algorithm, - "sender_key" to sessionData.senderKey, - "sender_claimed_keys" to sessionData.senderClaimedKeys, - "forwarding_curve25519_key_chain" to (sessionData.forwardingCurve25519KeyChain.orEmpty()), - "session_key" to sessionData.sessionKey) - - val json = MoshiProvider.providesMoshi() - .adapter(Map::class.java) - .toJson(sessionBackupData) - - val encryptedSessionBackupData = try { - backupOlmPkEncryption?.encrypt(json) - } catch (e: OlmException) { - Timber.e(e, "OlmException") - null - } - ?: return null - - // Build backup data for that key - return KeyBackupData( - firstMessageIndex = try { - olmInboundGroupSessionWrapper.olmInboundGroupSession?.firstKnownIndex ?: 0 - } catch (e: OlmException) { - Timber.e(e, "OlmException") - 0L - }, - forwardedCount = olmInboundGroupSessionWrapper.forwardingCurve25519KeyChain.orEmpty().size, - isVerified = device?.isVerified == true, - - sessionData = mapOf( - "ciphertext" to encryptedSessionBackupData.mCipherText, - "mac" to encryptedSessionBackupData.mMac, - "ephemeral" to encryptedSessionBackupData.mEphemeralKey) - ) - } - - @VisibleForTesting - @WorkerThread - fun decryptKeyBackupData(keyBackupData: KeyBackupData, sessionId: String, roomId: String, decryption: OlmPkDecryption): MegolmSessionData? { - var sessionBackupData: MegolmSessionData? = null - - val jsonObject = keyBackupData.sessionData - - val ciphertext = jsonObject["ciphertext"]?.toString() - val mac = jsonObject["mac"]?.toString() - val ephemeralKey = jsonObject["ephemeral"]?.toString() - - if (ciphertext != null && mac != null && ephemeralKey != null) { - val encrypted = OlmPkMessage() - encrypted.mCipherText = ciphertext - encrypted.mMac = mac - encrypted.mEphemeralKey = ephemeralKey - - try { - val decrypted = decryption.decrypt(encrypted) - - val moshi = MoshiProvider.providesMoshi() - val adapter = moshi.adapter(MegolmSessionData::class.java) - - sessionBackupData = adapter.fromJson(decrypted) - } catch (e: OlmException) { - Timber.e(e, "OlmException") - } - - if (sessionBackupData != null) { - sessionBackupData = sessionBackupData.copy( - sessionId = sessionId, - roomId = roomId - ) - } - } - - return sessionBackupData - } - - /* ========================================================================================== - * For test only - * ========================================================================================== */ - - // Direct access for test only - @VisibleForTesting - val store - get() = cryptoStore - - @VisibleForTesting - fun createFakeKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, - callback: MatrixCallback) { - @Suppress("UNCHECKED_CAST") - val createKeysBackupVersionBody = CreateKeysBackupVersionBody( - algorithm = keysBackupCreationInfo.algorithm, - authData = keysBackupCreationInfo.authData.toJsonDict() - ) - - createKeysBackupVersionTask - .configureWith(createKeysBackupVersionBody) { - this.callback = callback - } - .executeBy(taskExecutor) - } - - override fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? { - return cryptoStore.getKeyBackupRecoveryKeyInfo() - } - - override fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) { - cryptoStore.saveBackupRecoveryKey(recoveryKey, version) - } - - companion object { - // Maximum delay in ms in {@link maybeBackupKeys} - private const val KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS = 10_000L - - // Maximum number of keys to send at a time to the homeserver. - private const val KEY_BACKUP_SEND_KEYS_MAX_COUNT = 100 - } - -/* ========================================================================================== - * DEBUG INFO - * ========================================================================================== */ - - override fun toString() = "KeysBackup for $userId" -} +///* +// * Copyright 2020 The Matrix.org Foundation C.I.C. +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// +//package org.matrix.android.sdk.internal.crypto.keysbackup +// +//import android.os.Handler +//import android.os.Looper +//import androidx.annotation.UiThread +//import androidx.annotation.VisibleForTesting +//import androidx.annotation.WorkerThread +//import kotlinx.coroutines.CoroutineScope +//import kotlinx.coroutines.delay +//import kotlinx.coroutines.launch +//import kotlinx.coroutines.withContext +//import org.matrix.android.sdk.api.MatrixCallback +//import org.matrix.android.sdk.api.MatrixCoroutineDispatchers +//import org.matrix.android.sdk.api.auth.data.Credentials +//import org.matrix.android.sdk.api.failure.Failure +//import org.matrix.android.sdk.api.failure.MatrixError +//import org.matrix.android.sdk.api.listeners.ProgressListener +//import org.matrix.android.sdk.api.listeners.StepProgressListener +//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.KeysBackupStateListener +//import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP +//import org.matrix.android.sdk.internal.crypto.MXOlmDevice +//import org.matrix.android.sdk.internal.crypto.MegolmSessionData +//import org.matrix.android.sdk.internal.crypto.ObjectSigner +//import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter +//import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 +//import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust +//import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrustSignature +//import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData +//import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo +//import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmBackupAuthData +//import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult +//import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody +//import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeyBackupData +//import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData +//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.model.rest.RoomKeysBackupData +//import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody +//import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask +//import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteBackupTask +//import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteRoomSessionDataTask +//import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteRoomSessionsDataTask +//import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteSessionsDataTask +//import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupLastVersionTask +//import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask +//import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionDataTask +//import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionsDataTask +//import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetSessionsDataTask +//import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionDataTask +//import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask +//import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask +//import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask +//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.model.ImportRoomKeysResult +//import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 +//import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +//import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo +//import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity +//import org.matrix.android.sdk.internal.di.MoshiProvider +//import org.matrix.android.sdk.internal.di.UserId +//import org.matrix.android.sdk.internal.extensions.foldToCallback +//import org.matrix.android.sdk.internal.session.SessionScope +//import org.matrix.android.sdk.internal.task.Task +//import org.matrix.android.sdk.internal.task.TaskExecutor +//import org.matrix.android.sdk.internal.task.TaskThread +//import org.matrix.android.sdk.internal.task.configureWith +//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.OlmPkDecryption +//import org.matrix.olm.OlmPkEncryption +//import org.matrix.olm.OlmPkMessage +//import timber.log.Timber +//import java.security.InvalidParameterException +//import javax.inject.Inject +//import kotlin.random.Random +// +///** +// * A DefaultKeysBackupService class instance manage incremental backup of e2e keys (megolm keys) +// * to the user's homeserver. +// */ +//@SessionScope +//@Deprecated("use rust") +//internal class DefaultKeysBackupService @Inject constructor( +// @UserId private val userId: String, +// private val credentials: Credentials, +// private val cryptoStore: IMXCryptoStore, +// private val olmDevice: MXOlmDevice, +// private val objectSigner: ObjectSigner, +// // Actions +// private val megolmSessionDataImporter: MegolmSessionDataImporter, +// // Tasks +// private val createKeysBackupVersionTask: CreateKeysBackupVersionTask, +// private val deleteBackupTask: DeleteBackupTask, +// private val deleteRoomSessionDataTask: DeleteRoomSessionDataTask, +// private val deleteRoomSessionsDataTask: DeleteRoomSessionsDataTask, +// private val deleteSessionDataTask: DeleteSessionsDataTask, +// private val getKeysBackupLastVersionTask: GetKeysBackupLastVersionTask, +// private val getKeysBackupVersionTask: GetKeysBackupVersionTask, +// private val getRoomSessionDataTask: GetRoomSessionDataTask, +// private val getRoomSessionsDataTask: GetRoomSessionsDataTask, +// private val getSessionsDataTask: GetSessionsDataTask, +// private val storeRoomSessionDataTask: StoreRoomSessionDataTask, +// private val storeSessionsDataTask: StoreRoomSessionsDataTask, +// private val storeSessionDataTask: StoreSessionsDataTask, +// private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask, +// // Task executor +// private val taskExecutor: TaskExecutor, +// private val coroutineDispatchers: MatrixCoroutineDispatchers, +// private val cryptoCoroutineScope: CoroutineScope +//) : KeysBackupService { +// +// private val uiHandler = Handler(Looper.getMainLooper()) +// +// private val keysBackupStateManager = KeysBackupStateManager(uiHandler) +// +// // The backup version +// override var keysBackupVersion: KeysVersionResult? = null +// private set +// +// // The backup key being used. +// private var backupOlmPkEncryption: OlmPkEncryption? = null +// +// private var backupAllGroupSessionsCallback: MatrixCallback? = null +// +// private var keysBackupStateListener: KeysBackupStateListener? = null +// +// override val isEnabled: Boolean +// get() = keysBackupStateManager.isEnabled +// +// override val isStucked: Boolean +// get() = keysBackupStateManager.isStucked +// +// override val state: KeysBackupState +// get() = keysBackupStateManager.state +// +// override val currentBackupVersion: String? +// get() = keysBackupVersion?.version +// +// override fun addListener(listener: KeysBackupStateListener) { +// keysBackupStateManager.addListener(listener) +// } +// +// override fun removeListener(listener: KeysBackupStateListener) { +// keysBackupStateManager.removeListener(listener) +// } +// +// override fun prepareKeysBackupVersion(password: String?, +// progressListener: ProgressListener?, +// callback: MatrixCallback) { +// cryptoCoroutineScope.launch(coroutineDispatchers.main) { +// runCatching { +// withContext(coroutineDispatchers.crypto) { +// val olmPkDecryption = OlmPkDecryption() +// val signalableMegolmBackupAuthData = if (password != null) { +// // Generate a private key from the password +// val backgroundProgressListener = if (progressListener == null) { +// null +// } else { +// object : ProgressListener { +// override fun onProgress(progress: Int, total: Int) { +// uiHandler.post { +// try { +// progressListener.onProgress(progress, total) +// } catch (e: Exception) { +// Timber.e(e, "prepareKeysBackupVersion: onProgress failure") +// } +// } +// } +// } +// } +// +// val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener) +// SignalableMegolmBackupAuthData( +// publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey), +// privateKeySalt = generatePrivateKeyResult.salt, +// privateKeyIterations = generatePrivateKeyResult.iterations +// ) +// } else { +// val publicKey = olmPkDecryption.generateKey() +// +// SignalableMegolmBackupAuthData( +// publicKey = publicKey +// ) +// } +// +// val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableMegolmBackupAuthData.signalableJSONDictionary()) +// +// val signedMegolmBackupAuthData = MegolmBackupAuthData( +// publicKey = signalableMegolmBackupAuthData.publicKey, +// privateKeySalt = signalableMegolmBackupAuthData.privateKeySalt, +// privateKeyIterations = signalableMegolmBackupAuthData.privateKeyIterations, +// signatures = objectSigner.signObject(canonicalJson) +// ) +// +// MegolmBackupCreationInfo( +// algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, +// authData = signedMegolmBackupAuthData, +// recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey()) +// ) +// } +// }.foldToCallback(callback) +// } +// } +// +// override fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, +// callback: MatrixCallback) { +// @Suppress("UNCHECKED_CAST") +// val createKeysBackupVersionBody = CreateKeysBackupVersionBody( +// algorithm = keysBackupCreationInfo.algorithm, +// authData = keysBackupCreationInfo.authData.toJsonDict() +// ) +// +// keysBackupStateManager.state = KeysBackupState.Enabling +// +// createKeysBackupVersionTask +// .configureWith(createKeysBackupVersionBody) { +// this.callback = object : MatrixCallback { +// override fun onSuccess(data: KeysVersion) { +// // Reset backup markers. +// cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { +// // move tx out of UI thread +// cryptoStore.resetBackupMarkers() +// } +// +// val keyBackupVersion = KeysVersionResult( +// algorithm = createKeysBackupVersionBody.algorithm, +// authData = createKeysBackupVersionBody.authData, +// version = data.version, +// // We can consider that the server does not have keys yet +// count = 0, +// hash = "" +// ) +// +// enableKeysBackup(keyBackupVersion) +// +// callback.onSuccess(data) +// } +// +// override fun onFailure(failure: Throwable) { +// keysBackupStateManager.state = KeysBackupState.Disabled +// callback.onFailure(failure) +// } +// } +// } +// .executeBy(taskExecutor) +// } +// +// override fun deleteBackup(version: String, callback: MatrixCallback?) { +// cryptoCoroutineScope.launch(coroutineDispatchers.main) { +// withContext(coroutineDispatchers.crypto) { +// // If we're currently backing up to this backup... stop. +// // (We start using it automatically in createKeysBackupVersion so this is symmetrical). +// if (keysBackupVersion != null && version == keysBackupVersion?.version) { +// resetKeysBackupData() +// keysBackupVersion = null +// keysBackupStateManager.state = KeysBackupState.Unknown +// } +// +// deleteBackupTask +// .configureWith(DeleteBackupTask.Params(version)) { +// this.callback = object : MatrixCallback { +// private fun eventuallyRestartBackup() { +// // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver +// if (state == KeysBackupState.Unknown) { +// checkAndStartKeysBackup() +// } +// } +// +// override fun onSuccess(data: Unit) { +// eventuallyRestartBackup() +// +// uiHandler.post { callback?.onSuccess(Unit) } +// } +// +// override fun onFailure(failure: Throwable) { +// eventuallyRestartBackup() +// +// uiHandler.post { callback?.onFailure(failure) } +// } +// } +// } +// .executeBy(taskExecutor) +// } +// } +// } +// +// override fun canRestoreKeys(): Boolean { +// // Server contains more keys than locally +// val totalNumberOfKeysLocally = getTotalNumbersOfKeys() +// +// val keysBackupData = cryptoStore.getKeysBackupData() +// +// val totalNumberOfKeysServer = keysBackupData?.backupLastServerNumberOfKeys ?: -1 +// // Not used for the moment +// // val hashServer = keysBackupData?.backupLastServerHash +// +// return when { +// totalNumberOfKeysLocally < totalNumberOfKeysServer -> { +// // Server contains more keys than this device +// true +// } +// totalNumberOfKeysLocally == totalNumberOfKeysServer -> { +// // Same number, compare hash? +// // TODO We have not found any algorithm to determine if a restore is recommended here. Return false for the moment +// false +// } +// else -> false +// } +// } +// +// override fun getTotalNumbersOfKeys(): Int { +// return cryptoStore.inboundGroupSessionsCount(false) +// } +// +// override fun getTotalNumbersOfBackedUpKeys(): Int { +// return cryptoStore.inboundGroupSessionsCount(true) +// } +// +// override fun backupAllGroupSessions(progressListener: ProgressListener?, +// callback: MatrixCallback?) { +// // Get a status right now +// getBackupProgress(object : ProgressListener { +// override fun onProgress(progress: Int, total: Int) { +// // Reset previous listeners if any +// resetBackupAllGroupSessionsListeners() +// Timber.v("backupAllGroupSessions: backupProgress: $progress/$total") +// try { +// progressListener?.onProgress(progress, total) +// } catch (e: Exception) { +// Timber.e(e, "backupAllGroupSessions: onProgress failure") +// } +// +// if (progress == total) { +// Timber.v("backupAllGroupSessions: complete") +// callback?.onSuccess(Unit) +// return +// } +// +// backupAllGroupSessionsCallback = callback +// +// // Listen to `state` change to determine when to call onBackupProgress and onComplete +// keysBackupStateListener = object : KeysBackupStateListener { +// override fun onStateChange(newState: KeysBackupState) { +// getBackupProgress(object : ProgressListener { +// override fun onProgress(progress: Int, total: Int) { +// try { +// progressListener?.onProgress(progress, total) +// } catch (e: Exception) { +// Timber.e(e, "backupAllGroupSessions: onProgress failure 2") +// } +// +// // If backup is finished, notify the main listener +// if (state === KeysBackupState.ReadyToBackUp) { +// backupAllGroupSessionsCallback?.onSuccess(Unit) +// resetBackupAllGroupSessionsListeners() +// } +// } +// }) +// } +// }.also { keysBackupStateManager.addListener(it) } +// +// backupKeys() +// } +// }) +// } +// +// override fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult, +// callback: MatrixCallback) { +// // TODO Validate with François that this is correct +// object : Task { +// override suspend fun execute(params: KeysVersionResult): KeysBackupVersionTrust { +// return getKeysBackupTrustBg(params) +// } +// } +// .configureWith(keysBackupVersion) { +// this.callback = callback +// this.executionThread = TaskThread.COMPUTATION +// } +// .executeBy(taskExecutor) +// } +// +// /** +// * Check trust on a key backup version. +// * This has to be called on background thread. +// * +// * @param keysBackupVersion the backup version to check. +// * @return a KeysBackupVersionTrust object +// */ +// @WorkerThread +// private fun getKeysBackupTrustBg(keysBackupVersion: KeysVersionResult): KeysBackupVersionTrust { +// val keysBackupVersionTrust = KeysBackupVersionTrust() +// val authData = keysBackupVersion.getAuthDataAsMegolmBackupAuthData() +// +// if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isNullOrEmpty()) { +// Timber.v("getKeysBackupTrust: Key backup is absent or missing required data") +// return keysBackupVersionTrust +// } +// +// val mySigs = authData.signatures[userId] +// if (mySigs.isNullOrEmpty()) { +// Timber.v("getKeysBackupTrust: Ignoring key backup because it lacks any signatures from this user") +// return keysBackupVersionTrust +// } +// +// for ((keyId, mySignature) in mySigs) { +// // XXX: is this how we're supposed to get the device id? +// var deviceId: String? = null +// val components = keyId.split(":") +// if (components.size == 2) { +// deviceId = components[1] +// } +// +// if (deviceId != null) { +// val device = cryptoStore.getUserDevice(userId, deviceId) +// var isSignatureValid = false +// +// if (device == null) { +// Timber.v("getKeysBackupTrust: Signature from unknown device $deviceId") +// } else { +// val fingerprint = device.fingerprint() +// if (fingerprint != null) { +// try { +// olmDevice.verifySignature(fingerprint, authData.signalableJSONDictionary(), mySignature) +// isSignatureValid = true +// } catch (e: OlmException) { +// Timber.w(e, "getKeysBackupTrust: Bad signature from device ${device.deviceId}") +// } +// } +// +// if (isSignatureValid && device.isVerified) { +// keysBackupVersionTrust.usable = true +// } +// } +// +// val signature = KeysBackupVersionTrustSignature() +// signature.device = device +// signature.valid = isSignatureValid +// signature.deviceId = deviceId +// keysBackupVersionTrust.signatures.add(signature) +// } +// } +// +// return keysBackupVersionTrust +// } +// +// override fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, +// trust: Boolean, +// callback: MatrixCallback) { +// Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}") +// +// // Get auth data to update it +// val authData = getMegolmBackupAuthData(keysBackupVersion) +// +// if (authData == null) { +// Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data") +// +// callback.onFailure(IllegalArgumentException("Missing element")) +// } else { +// cryptoCoroutineScope.launch(coroutineDispatchers.main) { +// val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) { +// // Get current signatures, or create an empty set +// val myUserSignatures = authData.signatures?.get(userId).orEmpty().toMutableMap() +// +// if (trust) { +// // Add current device signature +// val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary()) +// +// val deviceSignatures = objectSigner.signObject(canonicalJson) +// +// deviceSignatures[userId]?.forEach { entry -> +// myUserSignatures[entry.key] = entry.value +// } +// } else { +// // Remove current device signature +// myUserSignatures.remove("ed25519:${credentials.deviceId}") +// } +// +// // Create an updated version of KeysVersionResult +// val newMegolmBackupAuthData = authData.copy() +// +// val newSignatures = newMegolmBackupAuthData.signatures.orEmpty().toMutableMap() +// newSignatures[userId] = myUserSignatures +// +// val newMegolmBackupAuthDataWithNewSignature = newMegolmBackupAuthData.copy( +// signatures = newSignatures +// ) +// +// @Suppress("UNCHECKED_CAST") +// UpdateKeysBackupVersionBody( +// algorithm = keysBackupVersion.algorithm, +// authData = newMegolmBackupAuthDataWithNewSignature.toJsonDict(), +// version = keysBackupVersion.version) +// } +// +// // And send it to the homeserver +// updateKeysBackupVersionTask +// .configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version, updateKeysBackupVersionBody)) { +// this.callback = object : MatrixCallback { +// override fun onSuccess(data: Unit) { +// // Relaunch the state machine on this updated backup version +// val newKeysBackupVersion = KeysVersionResult( +// algorithm = keysBackupVersion.algorithm, +// authData = updateKeysBackupVersionBody.authData, +// version = keysBackupVersion.version, +// hash = keysBackupVersion.hash, +// count = keysBackupVersion.count +// ) +// +// checkAndStartWithKeysBackupVersion(newKeysBackupVersion) +// +// callback.onSuccess(data) +// } +// +// override fun onFailure(failure: Throwable) { +// callback.onFailure(failure) +// } +// } +// } +// .executeBy(taskExecutor) +// } +// } +// } +// +// override fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, +// recoveryKey: String, +// callback: MatrixCallback) { +// Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}") +// +// cryptoCoroutineScope.launch(coroutineDispatchers.main) { +// val isValid = withContext(coroutineDispatchers.crypto) { +// isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion) +// } +// +// if (!isValid) { +// Timber.w("trustKeyBackupVersionWithRecoveryKey: Invalid recovery key.") +// +// callback.onFailure(IllegalArgumentException("Invalid recovery key or password")) +// } else { +// trustKeysBackupVersion(keysBackupVersion, true, callback) +// } +// } +// } +// +// override fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, +// password: String, +// callback: MatrixCallback) { +// Timber.v("trustKeysBackupVersionWithPassphrase: version ${keysBackupVersion.version}") +// +// cryptoCoroutineScope.launch(coroutineDispatchers.main) { +// val recoveryKey = withContext(coroutineDispatchers.crypto) { +// recoveryKeyFromPassword(password, keysBackupVersion, null) +// } +// +// if (recoveryKey == null) { +// Timber.w("trustKeysBackupVersionWithPassphrase: Key backup is missing required data") +// +// callback.onFailure(IllegalArgumentException("Missing element")) +// } else { +// // Check trust using the recovery key +// trustKeysBackupVersionWithRecoveryKey(keysBackupVersion, recoveryKey, callback) +// } +// } +// } +// +// override fun onSecretKeyGossip(secret: String) { +// Timber.i("## CrossSigning - onSecretKeyGossip") +// +// cryptoCoroutineScope.launch(coroutineDispatchers.main) { +// try { +// val keysBackupVersion = getKeysBackupLastVersionTask.execute(Unit) +// val recoveryKey = computeRecoveryKey(secret.fromBase64()) +// if (isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)) { +// awaitCallback { +// trustKeysBackupVersion(keysBackupVersion, true, it) +// } +// val importResult = awaitCallback { +// restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, null, null, null, it) +// } +// withContext(coroutineDispatchers.crypto) { +// cryptoStore.saveBackupRecoveryKey(recoveryKey, keysBackupVersion.version) +// } +// Timber.i("onSecretKeyGossip: Recovered keys ${importResult.successfullyNumberOfImportedKeys} out of ${importResult.totalNumberOfKeys}") +// } else { +// Timber.e("onSecretKeyGossip: Recovery key is not valid ${keysBackupVersion.version}") +// } +// } catch (failure: Throwable) { +// Timber.e("onSecretKeyGossip: failed to trust key backup version ${keysBackupVersion?.version}") +// } +// } +// } +// +// /** +// * Get public key from a Recovery key +// * +// * @param recoveryKey the recovery key +// * @return the corresponding public key, from Olm +// */ +// @WorkerThread +// private fun pkPublicKeyFromRecoveryKey(recoveryKey: String): String? { +// // Extract the primary key +// val privateKey = extractCurveKeyFromRecoveryKey(recoveryKey) +// +// if (privateKey == null) { +// Timber.w("pkPublicKeyFromRecoveryKey: private key is null") +// +// return null +// } +// +// // Built the PK decryption with it +// val pkPublicKey: String +// +// try { +// val decryption = OlmPkDecryption() +// pkPublicKey = decryption.setPrivateKey(privateKey) +// } catch (e: OlmException) { +// return null +// } +// +// return pkPublicKey +// } +// +// private fun resetBackupAllGroupSessionsListeners() { +// backupAllGroupSessionsCallback = null +// +// keysBackupStateListener?.let { +// keysBackupStateManager.removeListener(it) +// } +// +// keysBackupStateListener = null +// } +// +// override fun getBackupProgress(progressListener: ProgressListener) { +// val backedUpKeys = cryptoStore.inboundGroupSessionsCount(true) +// val total = cryptoStore.inboundGroupSessionsCount(false) +// +// progressListener.onProgress(backedUpKeys, total) +// } +// +// override fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, +// recoveryKey: String, +// roomId: String?, +// sessionId: String?, +// stepProgressListener: StepProgressListener?, +// callback: MatrixCallback) { +// Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}") +// +// cryptoCoroutineScope.launch(coroutineDispatchers.main) { +// runCatching { +// val decryption = withContext(coroutineDispatchers.crypto) { +// // Check if the recovery is valid before going any further +// if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysVersionResult)) { +// Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version") +// throw InvalidParameterException("Invalid recovery key") +// } +// +// // Get a PK decryption instance +// pkDecryptionFromRecoveryKey(recoveryKey) +// } +// if (decryption == null) { +// // This should not happen anymore +// Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key. Error") +// throw InvalidParameterException("Invalid recovery key") +// } +// +// stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey) +// +// // Get backed up keys from the homeserver +// val data = getKeys(sessionId, roomId, keysVersionResult.version) +// +// withContext(coroutineDispatchers.computation) { +// val sessionsData = ArrayList() +// // Restore that data +// var sessionsFromHsCount = 0 +// for ((roomIdLoop, backupData) in data.roomIdToRoomKeysBackupData) { +// for ((sessionIdLoop, keyBackupData) in backupData.sessionIdToKeyBackupData) { +// sessionsFromHsCount++ +// +// val sessionData = decryptKeyBackupData(keyBackupData, sessionIdLoop, roomIdLoop, decryption) +// +// sessionData?.let { +// sessionsData.add(it) +// } +// } +// } +// Timber.v("restoreKeysWithRecoveryKey: Decrypted ${sessionsData.size} keys out" + +// " of $sessionsFromHsCount from the backup store on the homeserver") +// +// // Do not trigger a backup for them if they come from the backup version we are using +// val backUp = keysVersionResult.version != keysBackupVersion?.version +// if (backUp) { +// Timber.v("restoreKeysWithRecoveryKey: Those keys will be backed up" + +// " to backup version: ${keysBackupVersion?.version}") +// } +// +// // Import them into the crypto store +// val progressListener = if (stepProgressListener != null) { +// object : ProgressListener { +// override fun onProgress(progress: Int, total: Int) { +// // Note: no need to post to UI thread, importMegolmSessionsData() will do it +// stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total)) +// } +// } +// } else { +// null +// } +// +// val result = megolmSessionDataImporter.handle(sessionsData, !backUp, progressListener) +// +// // Do not back up the key if it comes from a backup recovery +// if (backUp) { +// maybeBackupKeys() +// } +// // Save for next time and for gossiping +// saveBackupRecoveryKey(recoveryKey, keysVersionResult.version) +// result +// } +// }.foldToCallback(callback) +// } +// } +// +// override fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, +// password: String, +// roomId: String?, +// sessionId: String?, +// stepProgressListener: StepProgressListener?, +// callback: MatrixCallback) { +// Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}") +// +// cryptoCoroutineScope.launch(coroutineDispatchers.main) { +// runCatching { +// val progressListener = if (stepProgressListener != null) { +// object : ProgressListener { +// override fun onProgress(progress: Int, total: Int) { +// uiHandler.post { +// stepProgressListener.onStepProgress(StepProgressListener.Step.ComputingKey(progress, total)) +// } +// } +// } +// } else { +// null +// } +// +// val recoveryKey = withContext(coroutineDispatchers.crypto) { +// recoveryKeyFromPassword(password, keysBackupVersion, progressListener) +// } +// if (recoveryKey == null) { +// Timber.v("backupKeys: Invalid configuration") +// throw IllegalStateException("Invalid configuration") +// } else { +// awaitCallback { +// restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener, it) +// } +// } +// }.foldToCallback(callback) +// } +// } +// +// /** +// * Same method as [RoomKeysRestClient.getRoomKey] except that it accepts nullable +// * parameters and always returns a KeysBackupData object through the Callback +// */ +// private suspend fun getKeys(sessionId: String?, +// roomId: String?, +// version: String): KeysBackupData { +// return if (roomId != null && sessionId != null) { +// // Get key for the room and for the session +// val data = getRoomSessionDataTask.execute(GetRoomSessionDataTask.Params(roomId, sessionId, version)) +// // Convert to KeysBackupData +// KeysBackupData(mutableMapOf( +// roomId to RoomKeysBackupData(mutableMapOf( +// sessionId to data +// )) +// )) +// } else if (roomId != null) { +// // Get all keys for the room +// val data = getRoomSessionsDataTask.execute(GetRoomSessionsDataTask.Params(roomId, version)) +// // Convert to KeysBackupData +// KeysBackupData(mutableMapOf(roomId to data)) +// } else { +// // Get all keys +// getSessionsDataTask.execute(GetSessionsDataTask.Params(version)) +// } +// } +// +// @VisibleForTesting +// @WorkerThread +// fun pkDecryptionFromRecoveryKey(recoveryKey: String): OlmPkDecryption? { +// // Extract the primary key +// val privateKey = extractCurveKeyFromRecoveryKey(recoveryKey) +// +// // Built the PK decryption with it +// var decryption: OlmPkDecryption? = null +// if (privateKey != null) { +// try { +// decryption = OlmPkDecryption() +// decryption.setPrivateKey(privateKey) +// } catch (e: OlmException) { +// Timber.e(e, "OlmException") +// } +// } +// +// return decryption +// } +// +// /** +// * Do a backup if there are new keys, with a delay +// */ +// fun maybeBackupKeys() { +// when { +// isStucked -> { +// // If not already done, or in error case, check for a valid backup version on the homeserver. +// // If there is one, maybeBackupKeys will be called again. +// checkAndStartKeysBackup() +// } +// state == KeysBackupState.ReadyToBackUp -> { +// keysBackupStateManager.state = KeysBackupState.WillBackUp +// +// // Wait between 0 and 10 seconds, to avoid backup requests from +// // different clients hitting the server all at the same time when a +// // new key is sent +// val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS) +// +// cryptoCoroutineScope.launch { +// delay(delayInMs) +// uiHandler.post { backupKeys() } +// } +// } +// else -> { +// Timber.v("maybeBackupKeys: Skip it because state: $state") +// } +// } +// } +// +// override fun getVersion(version: String, +// callback: MatrixCallback) { +// getKeysBackupVersionTask +// .configureWith(version) { +// this.callback = object : MatrixCallback { +// override fun onSuccess(data: KeysVersionResult) { +// callback.onSuccess(data) +// } +// +// override fun onFailure(failure: Throwable) { +// if (failure is Failure.ServerError && +// failure.error.code == MatrixError.M_NOT_FOUND) { +// // Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup +// callback.onSuccess(null) +// } else { +// // Transmit the error +// callback.onFailure(failure) +// } +// } +// } +// } +// .executeBy(taskExecutor) +// } +// +// override fun getCurrentVersion(callback: MatrixCallback) { +// getKeysBackupLastVersionTask +// .configureWith { +// this.callback = object : MatrixCallback { +// override fun onSuccess(data: KeysVersionResult) { +// callback.onSuccess(data) +// } +// +// override fun onFailure(failure: Throwable) { +// if (failure is Failure.ServerError && +// failure.error.code == MatrixError.M_NOT_FOUND) { +// // Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup +// callback.onSuccess(null) +// } else { +// // Transmit the error +// callback.onFailure(failure) +// } +// } +// } +// } +// .executeBy(taskExecutor) +// } +// +// override fun forceUsingLastVersion(callback: MatrixCallback) { +// getCurrentVersion(object : MatrixCallback { +// override fun onSuccess(data: KeysVersionResult?) { +// val localBackupVersion = keysBackupVersion?.version +// val serverBackupVersion = data?.version +// +// if (serverBackupVersion == null) { +// if (localBackupVersion == null) { +// // No backup on the server, and backup is not active +// callback.onSuccess(true) +// } else { +// // No backup on the server, and we are currently backing up, so stop backing up +// callback.onSuccess(false) +// resetKeysBackupData() +// keysBackupVersion = null +// keysBackupStateManager.state = KeysBackupState.Disabled +// } +// } else { +// if (localBackupVersion == null) { +// // backup on the server, and backup is not active +// callback.onSuccess(false) +// // Do a check +// checkAndStartWithKeysBackupVersion(data) +// } 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 +// callback.onSuccess(true) +// } else { +// // We are not using the last version, so delete the current version we are using on the server +// callback.onSuccess(false) +// +// // This will automatically check for the last version then +// deleteBackup(localBackupVersion, null) +// } +// } +// } +// } +// +// override fun onFailure(failure: Throwable) { +// callback.onFailure(failure) +// } +// }) +// } +// +// override fun checkAndStartKeysBackup() { +// 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 { +// 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}") +// +// keysBackupVersion = keyBackupVersion +// +// if (keyBackupVersion == null) { +// Timber.v("checkAndStartWithKeysBackupVersion: Found no key backup version on the homeserver") +// resetKeysBackupData() +// keysBackupStateManager.state = KeysBackupState.Disabled +// } else { +// getKeysBackupTrust(keyBackupVersion, object : MatrixCallback { +// override fun onSuccess(data: KeysBackupVersionTrust) { +// val versionInStore = cryptoStore.getKeyBackupVersion() +// +// if (data.usable) { +// Timber.v("checkAndStartWithKeysBackupVersion: Found usable key backup. version: ${keyBackupVersion.version}") +// // Check the version we used at the previous app run +// if (versionInStore != null && versionInStore != keyBackupVersion.version) { +// Timber.v(" -> clean the previously used version $versionInStore") +// resetKeysBackupData() +// } +// +// Timber.v(" -> enabling key backups") +// 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) { +// // Cannot happen +// } +// }) +// } +// } +// +///* ========================================================================================== +// * Private +// * ========================================================================================== */ +// +// /** +// * Extract MegolmBackupAuthData data from a backup version. +// * +// * @param keysBackupData the key backup data +// * +// * @return the authentication if found and valid, null in other case +// */ +// private fun getMegolmBackupAuthData(keysBackupData: KeysVersionResult): MegolmBackupAuthData? { +// return keysBackupData +// .takeIf { it.version.isNotEmpty() && it.algorithm == MXCRYPTO_ALGORITHM_MEGOLM_BACKUP } +// ?.getAuthDataAsMegolmBackupAuthData() +// ?.takeIf { it.publicKey.isNotEmpty() } +// } +// +// /** +// * Compute the recovery key from a password and key backup version. +// * +// * @param password the password. +// * @param keysBackupData the backup and its auth data. +// * +// * @return the recovery key if successful, null in other cases +// */ +// @WorkerThread +// private fun recoveryKeyFromPassword(password: String, keysBackupData: KeysVersionResult, progressListener: ProgressListener?): String? { +// val authData = getMegolmBackupAuthData(keysBackupData) +// +// if (authData == null) { +// Timber.w("recoveryKeyFromPassword: invalid parameter") +// return null +// } +// +// if (authData.privateKeySalt.isNullOrBlank() || +// authData.privateKeyIterations == null) { +// Timber.w("recoveryKeyFromPassword: Salt and/or iterations not found in key backup auth data") +// +// return null +// } +// +// // Extract the recovery key from the passphrase +// val data = retrievePrivateKeyWithPassword(password, authData.privateKeySalt, authData.privateKeyIterations, progressListener) +// +// return computeRecoveryKey(data) +// } +// +// /** +// * Check if a recovery key matches key backup authentication data. +// * +// * @param recoveryKey the recovery key to challenge. +// * @param keysBackupData the backup and its auth data. +// * +// * @return true if successful. +// */ +// @WorkerThread +// private fun isValidRecoveryKeyForKeysBackupVersion(recoveryKey: String, keysBackupData: KeysVersionResult): Boolean { +// // Build PK decryption instance with the recovery key +// val publicKey = pkPublicKeyFromRecoveryKey(recoveryKey) +// +// if (publicKey == null) { +// Timber.w("isValidRecoveryKeyForKeysBackupVersion: public key is null") +// +// return false +// } +// +// val authData = getMegolmBackupAuthData(keysBackupData) +// +// if (authData == null) { +// Timber.w("isValidRecoveryKeyForKeysBackupVersion: Key backup is missing required data") +// +// return false +// } +// +// // Compare both +// if (publicKey != authData.publicKey) { +// Timber.w("isValidRecoveryKeyForKeysBackupVersion: Public keys mismatch") +// +// return false +// } +// +// // Public keys match! +// return true +// } +// +// override fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback) { +// val safeKeysBackupVersion = keysBackupVersion ?: return Unit.also { callback.onSuccess(false) } +// +// cryptoCoroutineScope.launch(coroutineDispatchers.main) { +// isValidRecoveryKeyForKeysBackupVersion(recoveryKey, safeKeysBackupVersion).let { +// callback.onSuccess(it) +// } +// } +// } +// +// /** +// * Enable backing up of keys. +// * This method will update the state and will start sending keys in nominal case +// * +// * @param keysVersionResult backup information object as returned by [getCurrentVersion]. +// */ +// private fun enableKeysBackup(keysVersionResult: KeysVersionResult) { +// val retrievedMegolmBackupAuthData = keysVersionResult.getAuthDataAsMegolmBackupAuthData() +// +// if (retrievedMegolmBackupAuthData != null) { +// keysBackupVersion = keysVersionResult +// cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { +// cryptoStore.setKeyBackupVersion(keysVersionResult.version) +// } +// +// onServerDataRetrieved(keysVersionResult.count, keysVersionResult.hash) +// +// try { +// backupOlmPkEncryption = OlmPkEncryption().apply { +// setRecipientKey(retrievedMegolmBackupAuthData.publicKey) +// } +// } catch (e: OlmException) { +// Timber.e(e, "OlmException") +// keysBackupStateManager.state = KeysBackupState.Disabled +// return +// } +// +// keysBackupStateManager.state = KeysBackupState.ReadyToBackUp +// +// maybeBackupKeys() +// } else { +// Timber.e("Invalid authentication data") +// keysBackupStateManager.state = KeysBackupState.Disabled +// } +// } +// +// /** +// * Update the DB with data fetch from the server +// */ +// private fun onServerDataRetrieved(count: Int?, etag: String?) { +// cryptoStore.setKeysBackupData(KeysBackupDataEntity() +// .apply { +// backupLastServerNumberOfKeys = count +// backupLastServerHash = etag +// } +// ) +// } +// +// /** +// * Reset all local key backup data. +// * +// * Note: This method does not update the state +// */ +// private fun resetKeysBackupData() { +// resetBackupAllGroupSessionsListeners() +// +// cryptoStore.setKeyBackupVersion(null) +// cryptoStore.setKeysBackupData(null) +// backupOlmPkEncryption?.releaseEncryption() +// backupOlmPkEncryption = null +// +// // Reset backup markers +// cryptoStore.resetBackupMarkers() +// } +// +// /** +// * Send a chunk of keys to backup +// */ +// @UiThread +// private fun backupKeys() { +// Timber.v("backupKeys") +// +// // Sanity check, as this method can be called after a delay, the state may have change during the delay +// if (!isEnabled || backupOlmPkEncryption == null || keysBackupVersion == null) { +// Timber.v("backupKeys: Invalid configuration") +// backupAllGroupSessionsCallback?.onFailure(IllegalStateException("Invalid configuration")) +// resetBackupAllGroupSessionsListeners() +// +// return +// } +// +// if (state === KeysBackupState.BackingUp) { +// // Do nothing if we are already backing up +// Timber.v("backupKeys: Invalid state: $state") +// return +// } +// +// // Get a chunk of keys to backup +// val olmInboundGroupSessionWrappers = cryptoStore.inboundGroupSessionsToBackup(KEY_BACKUP_SEND_KEYS_MAX_COUNT) +// +// Timber.v("backupKeys: 1 - ${olmInboundGroupSessionWrappers.size} sessions to back up") +// +// if (olmInboundGroupSessionWrappers.isEmpty()) { +// // Backup is up to date +// keysBackupStateManager.state = KeysBackupState.ReadyToBackUp +// +// backupAllGroupSessionsCallback?.onSuccess(Unit) +// resetBackupAllGroupSessionsListeners() +// return +// } +// +// keysBackupStateManager.state = KeysBackupState.BackingUp +// +// cryptoCoroutineScope.launch(coroutineDispatchers.main) { +// withContext(coroutineDispatchers.crypto) { +// Timber.v("backupKeys: 2 - Encrypting keys") +// +// // Gather data to send to the homeserver +// // roomId -> sessionId -> MXKeyBackupData +// val keysBackupData = KeysBackupData() +// +// olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper -> +// val roomId = olmInboundGroupSessionWrapper.roomId ?: return@forEach +// val olmInboundGroupSession = olmInboundGroupSessionWrapper.olmInboundGroupSession ?: return@forEach +// +// try { +// encryptGroupSession(olmInboundGroupSessionWrapper) +// ?.let { +// keysBackupData.roomIdToRoomKeysBackupData +// .getOrPut(roomId) { RoomKeysBackupData() } +// .sessionIdToKeyBackupData[olmInboundGroupSession.sessionIdentifier()] = it +// } +// } catch (e: OlmException) { +// Timber.e(e, "OlmException") +// } +// } +// +// Timber.v("backupKeys: 4 - Sending request") +// +// // Make the request +// val version = keysBackupVersion?.version ?: return@withContext +// +// storeSessionDataTask +// .configureWith(StoreSessionsDataTask.Params(version, keysBackupData)) { +// this.callback = object : MatrixCallback { +// override fun onSuccess(data: BackupKeysResult) { +// uiHandler.post { +// Timber.v("backupKeys: 5a - Request complete") +// +// // Mark keys as backed up +// cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers) +// +// if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) { +// Timber.v("backupKeys: All keys have been backed up") +// onServerDataRetrieved(data.count, data.hash) +// +// // Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess() +// keysBackupStateManager.state = KeysBackupState.ReadyToBackUp +// } else { +// Timber.v("backupKeys: Continue to back up keys") +// keysBackupStateManager.state = KeysBackupState.WillBackUp +// +// backupKeys() +// } +// } +// } +// +// override fun onFailure(failure: Throwable) { +// if (failure is Failure.ServerError) { +// uiHandler.post { +// Timber.e(failure, "backupKeys: backupKeys failed.") +// +// when (failure.error.code) { +// MatrixError.M_NOT_FOUND, +// MatrixError.M_WRONG_ROOM_KEYS_VERSION -> { +// // Backup has been deleted on the server, or we are not using the last backup version +// keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion +// backupAllGroupSessionsCallback?.onFailure(failure) +// resetBackupAllGroupSessionsListeners() +// resetKeysBackupData() +// keysBackupVersion = null +// +// // Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver +// checkAndStartKeysBackup() +// } +// else -> +// // Come back to the ready state so that we will retry on the next received key +// keysBackupStateManager.state = KeysBackupState.ReadyToBackUp +// } +// } +// } else { +// uiHandler.post { +// backupAllGroupSessionsCallback?.onFailure(failure) +// resetBackupAllGroupSessionsListeners() +// +// Timber.e("backupKeys: backupKeys failed.") +// +// // Retry a bit later +// keysBackupStateManager.state = KeysBackupState.ReadyToBackUp +// maybeBackupKeys() +// } +// } +// } +// } +// } +// .executeBy(taskExecutor) +// } +// } +// } +// +// @VisibleForTesting +// @WorkerThread +// fun encryptGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2): KeyBackupData? { +// // Gather information for each key +// val device = olmInboundGroupSessionWrapper.senderKey?.let { cryptoStore.deviceWithIdentityKey(it) } +// +// // Build the m.megolm_backup.v1.curve25519-aes-sha2 data as defined at +// // https://github.com/uhoreg/matrix-doc/blob/e2e_backup/proposals/1219-storing-megolm-keys-serverside.md#mmegolm_backupv1curve25519-aes-sha2-key-format +// val sessionData = olmInboundGroupSessionWrapper.exportKeys() ?: return null +// val sessionBackupData = mapOf( +// "algorithm" to sessionData.algorithm, +// "sender_key" to sessionData.senderKey, +// "sender_claimed_keys" to sessionData.senderClaimedKeys, +// "forwarding_curve25519_key_chain" to (sessionData.forwardingCurve25519KeyChain.orEmpty()), +// "session_key" to sessionData.sessionKey) +// +// val json = MoshiProvider.providesMoshi() +// .adapter(Map::class.java) +// .toJson(sessionBackupData) +// +// val encryptedSessionBackupData = try { +// backupOlmPkEncryption?.encrypt(json) +// } catch (e: OlmException) { +// Timber.e(e, "OlmException") +// null +// } +// ?: return null +// +// // Build backup data for that key +// return KeyBackupData( +// firstMessageIndex = try { +// olmInboundGroupSessionWrapper.olmInboundGroupSession?.firstKnownIndex ?: 0 +// } catch (e: OlmException) { +// Timber.e(e, "OlmException") +// 0L +// }, +// forwardedCount = olmInboundGroupSessionWrapper.forwardingCurve25519KeyChain.orEmpty().size, +// isVerified = device?.isVerified == true, +// +// sessionData = mapOf( +// "ciphertext" to encryptedSessionBackupData.mCipherText, +// "mac" to encryptedSessionBackupData.mMac, +// "ephemeral" to encryptedSessionBackupData.mEphemeralKey) +// ) +// } +// +// @VisibleForTesting +// @WorkerThread +// fun decryptKeyBackupData(keyBackupData: KeyBackupData, sessionId: String, roomId: String, decryption: OlmPkDecryption): MegolmSessionData? { +// var sessionBackupData: MegolmSessionData? = null +// +// val jsonObject = keyBackupData.sessionData +// +// val ciphertext = jsonObject["ciphertext"]?.toString() +// val mac = jsonObject["mac"]?.toString() +// val ephemeralKey = jsonObject["ephemeral"]?.toString() +// +// if (ciphertext != null && mac != null && ephemeralKey != null) { +// val encrypted = OlmPkMessage() +// encrypted.mCipherText = ciphertext +// encrypted.mMac = mac +// encrypted.mEphemeralKey = ephemeralKey +// +// try { +// val decrypted = decryption.decrypt(encrypted) +// +// val moshi = MoshiProvider.providesMoshi() +// val adapter = moshi.adapter(MegolmSessionData::class.java) +// +// sessionBackupData = adapter.fromJson(decrypted) +// } catch (e: OlmException) { +// Timber.e(e, "OlmException") +// } +// +// if (sessionBackupData != null) { +// sessionBackupData = sessionBackupData.copy( +// sessionId = sessionId, +// roomId = roomId +// ) +// } +// } +// +// return sessionBackupData +// } +// +// /* ========================================================================================== +// * For test only +// * ========================================================================================== */ +// +// // Direct access for test only +// @VisibleForTesting +// val store +// get() = cryptoStore +// +// @VisibleForTesting +// fun createFakeKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, +// callback: MatrixCallback) { +// @Suppress("UNCHECKED_CAST") +// val createKeysBackupVersionBody = CreateKeysBackupVersionBody( +// algorithm = keysBackupCreationInfo.algorithm, +// authData = keysBackupCreationInfo.authData.toJsonDict() +// ) +// +// createKeysBackupVersionTask +// .configureWith(createKeysBackupVersionBody) { +// this.callback = callback +// } +// .executeBy(taskExecutor) +// } +// +// override fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? { +// return cryptoStore.getKeyBackupRecoveryKeyInfo() +// } +// +// override fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) { +// cryptoStore.saveBackupRecoveryKey(recoveryKey, version) +// } +// +// companion object { +// // Maximum delay in ms in {@link maybeBackupKeys} +// private const val KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS = 10_000L +// +// // Maximum number of keys to send at a time to the homeserver. +// private const val KEY_BACKUP_SEND_KEYS_MAX_COUNT = 100 +// } +// +///* ========================================================================================== +// * DEBUG INFO +// * ========================================================================================== */ +// +// override fun toString() = "KeysBackup for $userId" +//} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupStateManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupStateManager.kt index 78ef958bbf..1dbccf15bd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupStateManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupStateManager.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.keysbackup 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.KeysBackupStateListener import timber.log.Timber @@ -33,11 +34,13 @@ internal class KeysBackupStateManager(private val uiHandler: Handler) { field = newState // Notify listeners about the state change, on the ui thread - uiHandler.post { - synchronized(listeners) { - listeners.forEach { + synchronized(listeners) { + listeners.forEach { + uiHandler.post { // Use newState because state may have already changed again - it.onStateChange(newState) + tryOrNull { + it.onStateChange(newState) + } } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt index 4134c0e8aa..40d27c926d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt @@ -18,16 +18,16 @@ package org.matrix.android.sdk.internal.crypto.keysbackup import android.os.Handler import android.os.Looper -import androidx.annotation.UiThread import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import org.matrix.android.sdk.api.MatrixCallback 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.MatrixError 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.store.SavedKeyBackupKeyInfo 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.util.JsonCanonicalizer -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.olm.OlmException import timber.log.Timber import uniffi.olm.BackupRecoveryKey @@ -91,7 +89,9 @@ internal class RustKeyBackupService @Inject constructor( override var keysBackupVersion: KeysVersionResult? = null private set - private var backupAllGroupSessionsCallback: MatrixCallback? = null +// private var backupAllGroupSessionsCallback: MatrixCallback? = null + + private val importScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) private var keysBackupStateListener: KeysBackupStateListener? = null @@ -115,64 +115,57 @@ internal class RustKeyBackupService @Inject constructor( keysBackupStateManager.removeListener(listener) } - override fun prepareKeysBackupVersion(password: String?, - progressListener: ProgressListener?, - callback: MatrixCallback) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - withContext(coroutineDispatchers.crypto) { - val key = if (password != null) { - BackupRecoveryKey.newFromPassphrase(password) - } else { - BackupRecoveryKey() - } + override suspend fun prepareKeysBackupVersion(password: String?): MegolmBackupCreationInfo { + return withContext(coroutineDispatchers.computation) { + val key = if (password != null) { + // this might be a bit slow as it's stretching the password + BackupRecoveryKey.newFromPassphrase(password) + } else { + BackupRecoveryKey() + } - val publicKey = key.megolmV1PublicKey() - val backupAuthData = SignalableMegolmBackupAuthData( - publicKey = publicKey.publicKey, - privateKeySalt = publicKey.passphraseInfo?.privateKeySalt, - privateKeyIterations = publicKey.passphraseInfo?.privateKeyIterations - ) - val canonicalJson = JsonCanonicalizer.getCanonicalJson( - Map::class.java, - backupAuthData.signalableJSONDictionary() - ) + val publicKey = key.megolmV1PublicKey() + val backupAuthData = SignalableMegolmBackupAuthData( + publicKey = publicKey.publicKey, + privateKeySalt = publicKey.passphraseInfo?.privateKeySalt, + privateKeyIterations = publicKey.passphraseInfo?.privateKeyIterations + ) + val canonicalJson = JsonCanonicalizer.getCanonicalJson( + Map::class.java, + backupAuthData.signalableJSONDictionary() + ) - val signedMegolmBackupAuthData = MegolmBackupAuthData( - publicKey = backupAuthData.publicKey, - privateKeySalt = backupAuthData.privateKeySalt, - privateKeyIterations = backupAuthData.privateKeyIterations, - signatures = olmMachine.sign(canonicalJson) - ) + val signedMegolmBackupAuthData = MegolmBackupAuthData( + publicKey = backupAuthData.publicKey, + privateKeySalt = backupAuthData.privateKeySalt, + privateKeyIterations = backupAuthData.privateKeyIterations, + signatures = olmMachine.sign(canonicalJson) + ) - MegolmBackupCreationInfo( - algorithm = publicKey.backupAlgorithm, - authData = signedMegolmBackupAuthData, - recoveryKey = key.toBase58() - ) - } - }.foldToCallback(callback) + MegolmBackupCreationInfo( + algorithm = publicKey.backupAlgorithm, + authData = signedMegolmBackupAuthData, + recoveryKey = key.toBase58() + ) } } - override fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, - callback: MatrixCallback) { - @Suppress("UNCHECKED_CAST") - val createKeysBackupVersionBody = CreateKeysBackupVersionBody( - algorithm = keysBackupCreationInfo.algorithm, - authData = keysBackupCreationInfo.authData.toJsonDict() - ) + override suspend fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo): KeysVersion { + return withContext(coroutineDispatchers.crypto) { + val createKeysBackupVersionBody = CreateKeysBackupVersionBody( + algorithm = keysBackupCreationInfo.algorithm, + authData = keysBackupCreationInfo.authData.toJsonDict() + ) - keysBackupStateManager.state = KeysBackupState.Enabling + keysBackupStateManager.state = KeysBackupState.Enabling - cryptoCoroutineScope.launch(coroutineDispatchers.main) { try { - val data = sender.createKeyBackup(createKeysBackupVersionBody) + val data = withContext(coroutineDispatchers.io) { + sender.createKeyBackup(createKeysBackupVersionBody) + } // Reset backup markers. // 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( algorithm = createKeysBackupVersionBody.algorithm, @@ -182,13 +175,11 @@ internal class RustKeyBackupService @Inject constructor( count = 0, hash = "" ) - enableKeysBackup(keyBackupVersion) - - callback.onSuccess(data) + data } catch (failure: Throwable) { keysBackupStateManager.state = KeysBackupState.Disabled - callback.onFailure(failure) + throw failure } } } @@ -200,7 +191,7 @@ internal class RustKeyBackupService @Inject constructor( } private fun resetBackupAllGroupSessionsListeners() { - backupAllGroupSessionsCallback = null +// backupAllGroupSessionsCallback = null keysBackupStateListener?.let { keysBackupStateManager.removeListener(it) @@ -219,29 +210,24 @@ internal class RustKeyBackupService @Inject constructor( olmMachine.disableBackup() } - override fun deleteBackup(version: String, callback: MatrixCallback?) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - withContext(coroutineDispatchers.crypto) { - if (keysBackupVersion != null && version == keysBackupVersion?.version) { - resetKeysBackupData() - keysBackupVersion = null - keysBackupStateManager.state = KeysBackupState.Unknown - } + override suspend fun deleteBackup(version: String) { + withContext(coroutineDispatchers.crypto) { + if (keysBackupVersion != null && version == keysBackupVersion?.version) { + resetKeysBackupData() + keysBackupVersion = null + keysBackupStateManager.state = KeysBackupState.Unknown + } - fun eventuallyRestartBackup() { - // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver - if (state == KeysBackupState.Unknown) { - checkAndStartKeysBackup() - } + try { + sender.deleteKeyBackup(version) + // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver + if (state == KeysBackupState.Unknown) { + checkAndStartKeysBackup() } - - try { - sender.deleteKeyBackup(version) - eventuallyRestartBackup() - uiHandler.post { callback?.onSuccess(Unit) } - } catch (failure: Throwable) { - eventuallyRestartBackup() - uiHandler.post { callback?.onFailure(failure) } + } catch (failure: Throwable) { + // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver + if (state == KeysBackupState.Unknown) { + checkAndStartKeysBackup() } } } @@ -264,12 +250,12 @@ internal class RustKeyBackupService @Inject constructor( return olmMachine.roomKeyCounts().backedUp.toInt() } - override fun backupAllGroupSessions(progressListener: ProgressListener?, - callback: MatrixCallback?) { - // 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. - TODO() - } +// override fun backupAllGroupSessions(progressListener: ProgressListener?, +// callback: MatrixCallback?) { +// // 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. +// TODO() +// } private suspend fun checkBackupTrust(authData: MegolmBackupAuthData?): KeysBackupVersionTrust { return if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isNullOrEmpty()) { @@ -280,82 +266,68 @@ internal class RustKeyBackupService @Inject constructor( } } - override fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult, - callback: MatrixCallback) { + override suspend fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult): KeysBackupVersionTrust { val authData = keysBackupVersion.getAuthDataAsMegolmBackupAuthData() - - cryptoCoroutineScope.launch { - try { - callback.onSuccess(checkBackupTrust(authData)) - } catch (exception: Throwable) { - callback.onFailure(exception) - } + return withContext(coroutineDispatchers.crypto) { + checkBackupTrust(authData) } } - override fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, - trust: Boolean, - callback: MatrixCallback) { - Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}") + override suspend fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, trust: Boolean) { + withContext(coroutineDispatchers.crypto) { + Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}") - // Get auth data to update it - val authData = getMegolmBackupAuthData(keysBackupVersion) + // Get auth data to update it + val authData = getMegolmBackupAuthData(keysBackupVersion) - if (authData == null) { - Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data") + if (authData == null) { + 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")) - } else { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - val body = withContext(coroutineDispatchers.crypto) { - // Get current signatures, or create an empty set - val userId = olmMachine.userId() - val signatures = authData.signatures?.get(userId).orEmpty().toMutableMap() + if (trust) { + // Add current device signature + val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary()) + val deviceSignature = olmMachine.sign(canonicalJson) - if (trust) { - // Add current device signature - 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()}") + deviceSignature[userId]?.forEach { entry -> + signatures[entry.key] = entry.value } - - val newAuthData = authData.copy() - 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) + } else { + signatures.remove("ed25519:${olmMachine.deviceId()}") } - 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) - - 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. - // 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) { val backupKey = recoveryKey.megolmV1PublicKey() val authData = getMegolmBackupAuthData(keysBackupData) @@ -376,60 +348,50 @@ internal class RustKeyBackupService @Inject constructor( } } - override fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, - recoveryKey: String, - callback: MatrixCallback) { + override suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, recoveryKey: String) { Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}") - - cryptoCoroutineScope.launch { - try { - // This is ~nowhere mentioned, the string here is actually a base58 encoded key. - // This not really supported by the spec for the backup key, the 4S key supports - // base58 encoding and the same method seems to be used here. - val key = BackupRecoveryKey.fromBase58(recoveryKey) - checkRecoveryKey(key, keysBackupVersion) - trustKeysBackupVersion(keysBackupVersion, true, callback) - } catch (exception: Throwable) { - callback.onFailure(exception) - } + withContext(coroutineDispatchers.crypto) { + // This is ~nowhere mentioned, the string here is actually a base58 encoded key. + // This not really supported by the spec for the backup key, the 4S key supports + // base58 encoding and the same method seems to be used here. + val key = BackupRecoveryKey.fromBase58(recoveryKey) + checkRecoveryKey(key, keysBackupVersion) + trustKeysBackupVersion(keysBackupVersion, true) } } - override fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, - password: String, - callback: MatrixCallback) { - cryptoCoroutineScope.launch { - try { - val key = recoveryKeyFromPassword(password, keysBackupVersion) - checkRecoveryKey(key, keysBackupVersion) - trustKeysBackupVersion(keysBackupVersion, true, callback) - } catch (exception: Throwable) { - Timber.w(exception) - callback.onFailure(exception) - } + override suspend fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, password: String) { + withContext(coroutineDispatchers.crypto) { + val key = recoveryKeyFromPassword(password, keysBackupVersion) + checkRecoveryKey(key, keysBackupVersion) + trustKeysBackupVersion(keysBackupVersion, true) } } - override fun onSecretKeyGossip(secret: String) { + override suspend fun onSecretKeyGossip(curveKeyBase64: String) { Timber.i("## CrossSigning - onSecretKeyGossip") - cryptoCoroutineScope.launch(coroutineDispatchers.main) { + withContext(coroutineDispatchers.crypto) { try { val version = sender.getKeyBackupVersion() 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 { - trustKeysBackupVersion(version, true, it) - } - val importResult = awaitCallback { - cryptoCoroutineScope.launch { - restoreBackup(version, key, null, null, null) + Timber.i("onSecretKeyGossip: Recovered keys ${importResult.successfullyNumberOfImportedKeys} out of ${importResult.totalNumberOfKeys}") + } catch (failure: Throwable) { + // fail silently.. + Timber.e(failure, "onSecretKeyGossip: Failed to import keys from backup") + } } + // 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 { 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") 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 val data = getKeys(sessionId, roomId, keysVersionResult.version) @@ -522,13 +489,33 @@ internal class RustKeyBackupService @Inject constructor( val sessionsData = ArrayList() // Restore that data 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) { + val roomIndex = progressDecryptIndex + progressDecryptIndex++ + cryptoCoroutineScope.launch(Dispatchers.Main) { + stepProgressListener?.onStepProgress(StepProgressListener.Step.DecryptingKey(roomIndex, data.roomIdToRoomKeysBackupData.size)) + } for ((sessionIdLoop, keyBackupData) in backupData.sessionIdToKeyBackupData) { sessionsFromHsCount++ 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) } } @@ -548,7 +535,9 @@ internal class RustKeyBackupService @Inject constructor( object : ProgressListener { override fun onProgress(progress: Int, total: Int) { // 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 { @@ -562,132 +551,120 @@ internal class RustKeyBackupService @Inject constructor( maybeBackupKeys() } - // Save for next time and for gossiping - saveBackupRecoveryKey(recoveryKey.toBase64(), keysVersionResult.version) result } } - override fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, - recoveryKey: String, - roomId: String?, - sessionId: String?, - stepProgressListener: StepProgressListener?, - callback: MatrixCallback) { + override suspend fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, + recoveryKey: String, + roomId: String?, + sessionId: String?, + stepProgressListener: StepProgressListener?): ImportRoomKeysResult { Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}") - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - val key = BackupRecoveryKey.fromBase58(recoveryKey) - restoreBackup(keysVersionResult, key, roomId, sessionId, stepProgressListener) - }.foldToCallback(callback) - } + + val key = BackupRecoveryKey.fromBase58(recoveryKey) + + return restoreBackup(keysVersionResult, key, roomId, sessionId, stepProgressListener) } - override fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, - password: String, - roomId: String?, - sessionId: String?, - stepProgressListener: StepProgressListener?, - callback: MatrixCallback) { + override suspend fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, + password: String, + roomId: String?, + sessionId: String?, + stepProgressListener: StepProgressListener?): ImportRoomKeysResult { Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}") - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - val recoveryKey = withContext(coroutineDispatchers.crypto) { - recoveryKeyFromPassword(password, keysBackupVersion) - } + val recoveryKey = withContext(coroutineDispatchers.crypto) { + recoveryKeyFromPassword(password, keysBackupVersion) + } - restoreBackup(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener) - }.foldToCallback(callback) + return restoreBackup(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener) + } + + override suspend fun getVersion(version: String): KeysVersionResult? { + return withContext(coroutineDispatchers.io) { + sender.getKeyBackupVersion(version) } } - override fun getVersion(version: String, callback: MatrixCallback) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - sender.getKeyBackupVersion(version) - }.foldToCallback(callback) + @Throws + override suspend fun getCurrentVersion(): KeysVersionResult? { + return withContext(coroutineDispatchers.io) { + sender.getKeyBackupVersion() } } - override fun getCurrentVersion(callback: MatrixCallback) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - sender.getKeyBackupVersion() - }.foldToCallback(callback) + override suspend fun forceUsingLastVersion(): Boolean { + val response = withContext(coroutineDispatchers.io) { + sender.getKeyBackupVersion() } - } - private suspend fun forceUsingLastVersionHelper(): Boolean { - val response = sender.getKeyBackupVersion() - val serverBackupVersion = response?.version - val localBackupVersion = keysBackupVersion?.version + return withContext(coroutineDispatchers.crypto) { + val serverBackupVersion = response?.version + val localBackupVersion = keysBackupVersion?.version - Timber.d("BACKUP: $serverBackupVersion") + Timber.d("BACKUP: $serverBackupVersion") - return if (serverBackupVersion == null) { - if (localBackupVersion == null) { - // 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 + if (serverBackupVersion == null) { + if (localBackupVersion == null) { + // No backup on the server, and backup is not active true } else { - // This will automatically check for the last version then - deleteBackup(localBackupVersion, null) - // We are not using the last version, so delete the current version we are using on the server + // 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 + } 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) { - cryptoCoroutineScope.launch { - runCatching { - forceUsingLastVersionHelper() - }.foldToCallback(callback) + override suspend fun checkAndStartKeysBackup() { + withContext(coroutineDispatchers.crypto) { + 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@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() { - 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 { - 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?) { + private suspend fun checkAndStartWithKeysBackupVersion(keyBackupVersion: KeysVersionResult?) { Timber.v("checkAndStartWithKeyBackupVersion: ${keyBackupVersion?.version}") keysBackupVersion = keyBackupVersion @@ -697,37 +674,34 @@ internal class RustKeyBackupService @Inject constructor( resetKeysBackupData() keysBackupStateManager.state = KeysBackupState.Disabled } else { - getKeysBackupTrust(keyBackupVersion, object : MatrixCallback { - override fun onSuccess(data: KeysBackupVersionTrust) { - val versionInStore = getKeyBackupRecoveryKeyInfo()?.version + try { + val data = getKeysBackupTrust(keyBackupVersion) + val versionInStore = getKeyBackupRecoveryKeyInfo()?.version - if (data.usable) { - Timber.v("checkAndStartWithKeysBackupVersion: Found usable key backup. version: ${keyBackupVersion.version}") - // Check the version we used at the previous app run - if (versionInStore != null && versionInStore != keyBackupVersion.version) { - Timber.v(" -> clean the previously used version $versionInStore") - 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 + if (data.usable) { + Timber.v("checkAndStartWithKeysBackupVersion: Found usable key backup. version: ${keyBackupVersion.version}") + // Check the version we used at the previous app run + if (versionInStore != null && versionInStore != keyBackupVersion.version) { + Timber.v(" -> clean the previously used version $versionInStore") + resetKeysBackupData() } - } - override fun onFailure(failure: Throwable) { - // Cannot happen + 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 } - }) + } catch (failure: Throwable) { + Timber.e(failure, "Failed to checkAndStartWithKeysBackupVersion $keyBackupVersion") + } } } @@ -737,14 +711,17 @@ internal class RustKeyBackupService @Inject constructor( return authData.publicKey == publicKey } - override fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback) { - val keysBackupVersion = keysBackupVersion ?: return Unit.also { callback.onSuccess(false) } + override suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean { + return withContext(coroutineDispatchers.crypto) { + val keysBackupVersion = keysBackupVersion ?: return@withContext false - try { val key = BackupRecoveryKey.fromBase64(recoveryKey) - callback.onSuccess(isValidRecoveryKey(key, keysBackupVersion)) - } catch (error: Throwable) { - callback.onFailure(error) + try { + isValidRecoveryKey(key, keysBackupVersion) + } 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 */ - fun maybeBackupKeys() { - when { - isStucked -> { - // If not already done, or in error case, check for a valid backup version on the homeserver. - // If there is one, maybeBackupKeys will be called again. - checkAndStartKeysBackup() - } - state == KeysBackupState.ReadyToBackUp -> { - keysBackupStateManager.state = KeysBackupState.WillBackUp + suspend fun maybeBackupKeys() { + withContext(coroutineDispatchers.crypto) { + when { + isStucked -> { + // If not already done, or in error case, check for a valid backup version on the homeserver. + // If there is one, maybeBackupKeys will be called again. + checkAndStartKeysBackup() + } + state == KeysBackupState.ReadyToBackUp -> { + keysBackupStateManager.state = KeysBackupState.WillBackUp - // Wait between 0 and 10 seconds, to avoid backup requests from - // different clients hitting the server all at the same time when a - // new key is sent - val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS) + // Wait between 0 and 10 seconds, to avoid backup requests from + // different clients hitting the server all at the same time when a + // new key is sent + val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS) - cryptoCoroutineScope.launch { - delay(delayInMs) - // TODO is this correct? we used to call uiHandler.post() instead of this - withContext(Dispatchers.Main) { - backupKeys() + importScope.launch { + delay(delayInMs) + tryOrNull("AUTO backup failed") { backupKeys() } } } - } - else -> { - Timber.v("maybeBackupKeys: Skip it because state: $state") + else -> { + Timber.v("maybeBackupKeys: Skip it because state: $state") + } } } } @@ -854,84 +830,79 @@ internal class RustKeyBackupService @Inject constructor( /** * Send a chunk of keys to backup */ - @UiThread private suspend fun backupKeys(forceRecheck: Boolean = false) { 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 - if (!isEnabled || !olmMachine.backupEnabled() || keysBackupVersion == null) { - Timber.v("backupKeys: Invalid configuration $isEnabled ${olmMachine.backupEnabled()} $keysBackupVersion") - backupAllGroupSessionsCallback?.onFailure(IllegalStateException("Invalid configuration")) - resetBackupAllGroupSessionsListeners() + return@withContext + } - 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) { - // Do nothing if we are already backing up - Timber.v("backupKeys: Invalid state: $state") - return - } + Timber.d("BACKUP: CREATING REQUEST") - 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) { - // Backup is up to date - // Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess() - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp +// backupAllGroupSessionsCallback?.onSuccess(Unit) + resetBackupAllGroupSessionsListeners() + } else { + try { + if (request is Request.KeysBackup) { + keysBackupStateManager.state = KeysBackupState.BackingUp - backupAllGroupSessionsCallback?.onSuccess(Unit) - resetBackupAllGroupSessionsListeners() - } else { - try { - if (request is Request.KeysBackup) { - keysBackupStateManager.state = KeysBackupState.BackingUp + Timber.d("BACKUP SENDING REQUEST") + val response = withContext(coroutineDispatchers.io) { sender.backupRoomKeys(request) } + Timber.d("BACKUP GOT RESPONSE $response") + olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_BACKUP, response) + Timber.d("BACKUP MARKED REQUEST AS SENT") - 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) + } else { + // Can't happen, do we want to panic? } - } else { - // Can't happen, do we want to panic? - } - } catch (failure: Throwable) { - if (failure is Failure.ServerError) { - withContext(Dispatchers.Main) { - Timber.e(failure, "backupKeys: backupKeys failed.") + } catch (failure: Throwable) { + if (failure is Failure.ServerError) { + withContext(Dispatchers.Main) { + Timber.e(failure, "backupKeys: backupKeys failed.") - when (failure.error.code) { - MatrixError.M_NOT_FOUND, - MatrixError.M_WRONG_ROOM_KEYS_VERSION -> { - // Backup has been deleted on the server, or we are not using - // the last backup version - keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion - backupAllGroupSessionsCallback?.onFailure(failure) - resetBackupAllGroupSessionsListeners() - resetKeysBackupData() - keysBackupVersion = null + when (failure.error.code) { + MatrixError.M_NOT_FOUND, + MatrixError.M_WRONG_ROOM_KEYS_VERSION -> { + // Backup has been deleted on the server, or we are not using + // the last backup version + keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion +// backupAllGroupSessionsCallback?.onFailure(failure) + resetBackupAllGroupSessionsListeners() + resetKeysBackupData() + keysBackupVersion = null - // Do not stay in KeysBackupState.WrongBackUpVersion but check what - // is available on the homeserver - checkAndStartKeysBackup() + // Do not stay in KeysBackupState.WrongBackUpVersion but check what + // is available on the homeserver + 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 { - withContext(Dispatchers.Main) { - backupAllGroupSessionsCallback?.onFailure(failure) + } else { +// backupAllGroupSessionsCallback?.onFailure(failure) resetBackupAllGroupSessionsListeners() Timber.e("backupKeys: backupKeys failed: $failure") diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt index bdae975846..0f532e4fea 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt @@ -83,11 +83,6 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() { .show() } - if (viewModel.keyVersionResult.value == null) { - // We need to fetch from API - viewModel.getLatestVersion() - } - viewModel.navigateEvent.observeEvent(this) { uxStateEvent -> when (uxStateEvent) { KeysBackupRestoreSharedViewModel.NAVIGATE_TO_RECOVER_WITH_KEY -> { diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt index 8362a3566e..079828dc52 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt @@ -23,9 +23,11 @@ import im.vector.app.R import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.LiveEvent +import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.Dispatchers 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.session.Session 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.util.computeRecoveryKey import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult -import org.matrix.android.sdk.internal.util.awaitCallback import timber.log.Timber import javax.inject.Inject @@ -75,10 +76,15 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( var importRoomKeysFinishWithResult: MutableLiveData> = MutableLiveData() 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) { when (step) { is StepProgressListener.Step.ComputingKey -> { @@ -106,62 +112,71 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( 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() - 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 { - val version = awaitCallback { - keysBackup.getCurrentVersion(it) - } - if (version?.version == null) { - loadingEvent.postValue(null) - _keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, ""))) - return@launch - } + try { + val version = keysBackup.getCurrentVersion() + if (version?.version == null) { + loadingEvent.postValue(null) + _keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, ""))) + return + } - keyVersionResult.postValue(version) - // Let's check if there is quads - val isBackupKeyInQuadS = isBackupKeyInQuadS() + keyVersionResult.postValue(version) + // Let's check if there is quads + val isBackupKeyInQuadS = isBackupKeyInQuadS() - val savedSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() - if (savedSecret != null && savedSecret.version == version.version) { - // key is in memory! - keySourceModel.postValue( - KeySource(isInMemory = true, isInQuadS = true) - ) - // Go and use it!! - try { - recoverUsingBackupRecoveryKey(savedSecret.recoveryKey) - } catch (failure: Throwable) { - keySourceModel.postValue( - KeySource(isInMemory = false, isInQuadS = true) - ) - } - } else if (isBackupKeyInQuadS) { - // key is in QuadS! + val savedSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() + if (savedSecret != null && savedSecret.version == version.version) { + // key is in memory! + keySourceModel.postValue( + KeySource(isInMemory = true, isInQuadS = true) + ) + // Go and use it!! + try { + recoverUsingBackupRecoveryKey(computeRecoveryKey(savedSecret.recoveryKey.fromBase64()), version) + } catch (failure: Throwable) { + Timber.e(failure, "## recoverUsingBackupRecoveryKey FAILED") keySourceModel.postValue( KeySource(isInMemory = false, isInQuadS = true) ) - _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))) + } else if (isBackupKeyInQuadS) { + // key is in QuadS! + keySourceModel.postValue( + KeySource(isInMemory = false, isInQuadS = true) + ) + _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 } - 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 { - recoverUsingBackupRecoveryKey(computeRecoveryKey(secret.fromBase64())) + recoverUsingBackupRecoveryKey(secret) } catch (failure: Throwable) { _navigateEvent.postValue( LiveEvent(NAVIGATE_FAILED_TO_LOAD_4S) @@ -202,15 +217,12 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading))) try { - val result = awaitCallback { - keysBackup.restoreKeyBackupWithPassword(keyVersion, - passphrase, - null, - session.myUserId, - progressObserver, - it - ) - } + val result = keysBackup.restoreKeyBackupWithPassword(keyVersion, + passphrase, + null, + session.myUserId, + progressObserver + ) loadingEvent.postValue(null) didRecoverSucceed(result) 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 keyVersion = keyVersionResult.value ?: return + // This is badddddd + val version = keyVersion ?: keyVersionResult.value ?: return loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading))) try { - val result = awaitCallback { - keysBackup.restoreKeysWithRecoveryKey(keyVersion, - recoveryKey, - null, - session.myUserId, - progressObserver, - it - ) - } + val result = keysBackup.restoreKeysWithRecoveryKey(version, + recoveryKey, + null, + session.myUserId, + progressObserver + ) loadingEvent.postValue(null) - didRecoverSucceed(result) - trustOnDecrypt(keysBackup, keyVersion) + withContext(Dispatchers.Main) { + didRecoverSucceed(result) + trustOnDecrypt(keysBackup, version) + } } catch (failure: Throwable) { + Timber.e(failure, "## restoreKeysWithRecoveryKey failure") loadingEvent.postValue(null) throw failure } @@ -258,19 +271,19 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( } private fun trustOnDecrypt(keysBackup: KeysBackupService, keysVersionResult: KeysVersionResult) { - keysBackup.trustKeysBackupVersion(keysVersionResult, true, - object : MatrixCallback { - override fun onSuccess(data: Unit) { - Timber.v("##### trustKeysBackupVersion onSuccess") - } - }) + // do that on session scope because could happen outside of view model lifecycle + session.coroutineScope.launch { + tryOrNull("## Failed to trustKeysBackupVersion") { + keysBackup.trustKeysBackupVersion(keysVersionResult, true) + } + } } 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 _navigateEvent.postValue(LiveEvent(NAVIGATE_TO_SUCCESS)) } diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt index 9a5c62f2c8..51213fcd38 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt @@ -27,13 +27,11 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.NoOpMatrixCallback +import kotlinx.coroutines.launch 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.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener -import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust import timber.log.Timber class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState, @@ -70,7 +68,9 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS } private fun init() { - keysBackupService.forceUsingLastVersion(NoOpMatrixCallback()) + viewModelScope.launch { + keysBackupService.forceUsingLastVersion() + } } private fun getKeysBackupTrust() = withState { state -> @@ -86,26 +86,24 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS } Timber.d("BACKUP: HEEEEEEE TWO") - keysBackupService - .getKeysBackupTrust(versionResult, object : MatrixCallback { - override fun onSuccess(data: KeysBackupVersionTrust) { - Timber.d("BACKUP: HEEEE suceeeded $data") - setState { - copy( - keysBackupVersionTrust = Success(data) - ) - } - } - - override fun onFailure(failure: Throwable) { - Timber.d("BACKUP: HEEEE FAILED $failure") - setState { - copy( - keysBackupVersionTrust = Fail(failure) - ) - } - } - }) + viewModelScope.launch { + try { + val data = keysBackupService.getKeysBackupTrust(versionResult) + Timber.d("BACKUP: HEEEE suceeeded $data") + setState { + copy( + keysBackupVersionTrust = Success(data) + ) + } + } catch (failure: Throwable) { + Timber.d("BACKUP: HEEEE FAILED $failure") + setState { + copy( + keysBackupVersionTrust = Fail(failure) + ) + } + } + } } } @@ -128,15 +126,16 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS private fun deleteCurrentBackup() { val keysBackupService = keysBackupService - if (keysBackupService.currentBackupVersion != null) { + val currentBackupVersion = keysBackupService.currentBackupVersion + if (currentBackupVersion != null) { setState { copy( deleteBackupRequest = Loading() ) } - - keysBackupService.deleteBackup(keysBackupService.currentBackupVersion!!, object : MatrixCallback { - override fun onSuccess(data: Unit) { + viewModelScope.launch { + try { + keysBackupService.deleteBackup(currentBackupVersion) setState { copy( keysBackupVersion = null, @@ -145,16 +144,14 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS deleteBackupRequest = Uninitialized ) } - } - - override fun onFailure(failure: Throwable) { + } catch (failure: Throwable) { setState { copy( deleteBackupRequest = Fail(failure) ) } } - }) + } } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt index 1141886689..bc3479860a 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt @@ -19,17 +19,16 @@ package im.vector.app.features.crypto.keysbackup.setup import android.content.Context import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.nulabinc.zxcvbn.Strength import im.vector.app.R import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.utils.LiveEvent -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.listeners.ProgressListener +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session 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.rest.KeysVersion -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult import timber.log.Timber import javax.inject.Inject @@ -89,48 +88,30 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() { recoveryKey.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, - object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - if (requestedId != currentRequestId.value) { - // this is an old request, we can't cancel but we can ignore - return - } + val keyBackup = session.cryptoService().keysBackupService() + createKeysBackup(context, keyBackup) + } catch (failure: Throwable) { + if (requestedId != currentRequestId.value) { + // this is an old request, we can't cancel but we can ignore + return@launch + } - loadingStatus.value = WaitingViewData(context.getString(R.string.keys_backup_setup_step3_generating_key_status), - progress, - total) - } - }, - object : MatrixCallback { - 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 - } - }) + loadingStatus.postValue(null) + isCreatingBackupVersion.postValue(false) + prepareRecoverFailError.postValue(failure) + } } } @@ -140,9 +121,11 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() { } fun stopAndKeepAfterDetectingExistingOnServer() { - loadingStatus.value = null - navigateEvent.value = LiveEvent(NAVIGATE_FINISH) - session.cryptoService().keysBackupService().checkAndStartKeysBackup() + loadingStatus.postValue(null) + navigateEvent.postValue(LiveEvent(NAVIGATE_FINISH)) + viewModelScope.launch { + session.cryptoService().keysBackupService().checkAndStartKeysBackup() + } } private fun createKeysBackup(context: Context, keysBackup: KeysBackupService, forceOverride: Boolean = false) { @@ -150,45 +133,35 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() { creatingBackupError.value = null - keysBackup.getCurrentVersion(object : MatrixCallback { - override fun onSuccess(data: KeysVersionResult?) { + viewModelScope.launch { + try { + val data = keysBackup.getCurrentVersion() if (data?.version.isNullOrBlank() || forceOverride) { - processOnCreate() + processOnCreate(keysBackup) } else { - loadingStatus.value = null + loadingStatus.postValue(null) // we should prompt - isCreatingBackupVersion.value = false - navigateEvent.value = LiveEvent(NAVIGATE_PROMPT_REPLACE) + isCreatingBackupVersion.postValue(false) + navigateEvent.postValue(LiveEvent(NAVIGATE_PROMPT_REPLACE)) } + } catch (failure: Throwable) { } + } + } - override fun onFailure(failure: Throwable) { - Timber.e(failure, "## createKeyBackupVersion") - loadingStatus.value = null + suspend fun processOnCreate(keysBackup: KeysBackupService) { + try { + 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 - creatingBackupError.value = failure - } - - fun processOnCreate() { - keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!, object : MatrixCallback { - 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 - } - }) - } - }) + isCreatingBackupVersion.postValue(false) + creatingBackupError.postValue(failure) + } } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt index 74bab9b0b6..a962dbcf6c 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt @@ -20,7 +20,9 @@ import im.vector.app.R import im.vector.app.core.platform.ViewModelTask import im.vector.app.core.platform.WaitingViewData 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.session.Session 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.util.computeRecoveryKey import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey -import org.matrix.android.sdk.internal.util.awaitCallback import timber.log.Timber import java.util.UUID import javax.inject.Inject @@ -87,9 +88,7 @@ class BackupToQuadSMigrationTask @Inject constructor( reportProgress(params, R.string.bootstrap_progress_compute_curve_key) val recoveryKey = computeRecoveryKey(curveKey) - val isValid = awaitCallback { - keysBackupService.isValidRecoveryKeyForCurrentVersion(recoveryKey, it) - } + val isValid = keysBackupService.isValidRecoveryKeyForCurrentVersion(recoveryKey) if (!isValid) return Result.InvalidRecoverySecret @@ -141,14 +140,17 @@ class BackupToQuadSMigrationTask @Inject constructor( keysBackupService.saveBackupRecoveryKey(recoveryKey, version.version) // while we are there let's restore, but do not block - session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( - version, - recoveryKey, - null, - null, - null, - NoOpMatrixCallback() - ) + session.coroutineScope.launch { + tryOrNull { + session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( + version, + recoveryKey, + null, + null, + null + ) + } + } return Result.Success } catch (failure: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt index cc863346aa..de627e1f78 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt @@ -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.SsssKeySpec 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.util.awaitCallback import timber.log.Timber @@ -221,9 +218,7 @@ class BootstrapCrossSigningTask @Inject constructor( Timber.d("## BootstrapCrossSigningTask: Creating 4S - Checking megolm backup") // First ensure that in sync - var serverVersion = awaitCallback { - session.cryptoService().keysBackupService().getCurrentVersion(it) - } + var serverVersion = session.cryptoService().keysBackupService().getCurrentVersion() val knownMegolmSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() val isMegolmBackupSecretKnown = knownMegolmSecret != null && knownMegolmSecret.version == serverVersion?.version @@ -233,21 +228,14 @@ class BootstrapCrossSigningTask @Inject constructor( if (shouldCreateKeyBackup) { // clear all existing backups while (serverVersion != null) { - awaitCallback { - session.cryptoService().keysBackupService().deleteBackup(serverVersion!!.version, it) - } - serverVersion = awaitCallback { - session.cryptoService().keysBackupService().getCurrentVersion(it) - } + session.cryptoService().keysBackupService().deleteBackup(serverVersion.version) + serverVersion = session.cryptoService().keysBackupService().getCurrentVersion() } Timber.d("## BootstrapCrossSigningTask: Creating 4S - Create megolm backup") - val creationInfo = awaitCallback { - session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it) - } - val version = awaitCallback { - session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it) - } + val creationInfo = session.cryptoService().keysBackupService().prepareKeysBackupVersion(null) + val version = session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo) + // Save it for gossiping Timber.d("## BootstrapCrossSigningTask: Creating 4S - Save megolm backup key for gossiping") 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! if (isMegolmBackupSecretKnown) { // check it matches - val isValid = awaitCallback { - session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownMegolmSecret!!.recoveryKey, it) - } + val isValid = session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownMegolmSecret!!.recoveryKey) if (isValid) { Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key valid and known") - extractCurveKeyFromRecoveryKey(knownMegolmSecret!!.recoveryKey)?.toBase64NoPadding()?.let { secret -> + extractCurveKeyFromRecoveryKey(knownMegolmSecret.recoveryKey)?.toBase64NoPadding()?.let { secret -> ssssService.storeSecret( KEYBACKUP_SECRET_SSSS_NAME, secret, diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt index f75ab634b8..1e7eab64e7 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt @@ -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.registration.RegistrationFlowResponse 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.session.Session 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.keysbackup.model.rest.KeysVersionResult 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.util.awaitCallback import java.io.OutputStream import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -104,9 +103,8 @@ class BootstrapSharedViewModel @AssistedInject constructor( // We need to check if there is an existing backup viewModelScope.launch(Dispatchers.IO) { - val version = awaitCallback { - session.cryptoService().keysBackupService().getCurrentVersion(it) - } + val version = tryOrNull { session.cryptoService().keysBackupService().getCurrentVersion() } + if (version == null) { // we just resume plain bootstrap doesKeyBackupExist = false @@ -115,8 +113,8 @@ class BootstrapSharedViewModel @AssistedInject constructor( } } else { // we need to get existing backup passphrase/key and convert to SSSS - val keyVersion = awaitCallback { - session.cryptoService().keysBackupService().getVersion(version.version, it) + val keyVersion = tryOrNull { + session.cryptoService().keysBackupService().getVersion(version.version) } if (keyVersion == null) { // strange case... just finish? diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt index ed29f5b1d0..ee3bbe8f31 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -31,8 +31,10 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.Dispatchers 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.crypto.crosssigning.KEYBACKUP_SECRET_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.internal.crypto.crosssigning.fromBase64 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.model.ImportRoomKeysResult -import org.matrix.android.sdk.internal.util.awaitCallback import timber.log.Timber data class VerificationBottomSheetViewState( @@ -419,30 +418,27 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( } private fun tentativeRestoreBackup(res: Map?) { - viewModelScope.launch(Dispatchers.IO) { + // on session scope because will happen after viewmodel is cleared + session.coroutineScope.launch { try { val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) ?: return@launch Unit.also { Timber.v("## Keybackup secret not restored from SSSS") } - val version = awaitCallback { - session.cryptoService().keysBackupService().getCurrentVersion(it) - } ?: return@launch + val version = tryOrNull { session.cryptoService().keysBackupService().getCurrentVersion() } + ?: return@launch - awaitCallback { - session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( - version, - computeRecoveryKey(secret.fromBase64()), - null, - null, - null, - it - ) - } + // TODO maybe mark as trusted earlier by checking recovery key early, then download? - awaitCallback { - session.cryptoService().keysBackupService().trustKeysBackupVersion(version, true, it) - } + session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( + version, + computeRecoveryKey(secret.fromBase64()), + null, + null, + null + ) + + session.cryptoService().keysBackupService().trustKeysBackupVersion(version, true) } catch (failure: Throwable) { // Just ignore for now Timber.e(failure, "## Failed to restore backup after SSSS recovery") diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt index 8fb5b27376..ded42e23e0 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt @@ -155,7 +155,9 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS fun refreshRemoteStateIfNeeded() { if (keysBackupState.value == KeysBackupState.Disabled) { - session.cryptoService().keysBackupService().checkAndStartKeysBackup() + viewModelScope.launch { + session.cryptoService().keysBackupService().checkAndStartKeysBackup() + } } } diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt index 4daaef6fe1..0fbbb3812e 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt @@ -73,7 +73,9 @@ class SignoutCheckViewModel @AssistedInject constructor( init { session.cryptoService().keysBackupService().addListener(this) - session.cryptoService().keysBackupService().checkAndStartKeysBackup() + viewModelScope.launch { + session.cryptoService().keysBackupService().checkAndStartKeysBackup() + } val quad4SIsSetup = session.sharedSecretStorageService.isRecoverySetup() val allKeysKnown = session.cryptoService().crossSigningService().allPrivateKeysKnown() @@ -112,7 +114,9 @@ class SignoutCheckViewModel @AssistedInject constructor( fun refreshRemoteStateIfNeeded() = withState { state -> if (state.keysBackupState == KeysBackupState.Disabled) { - session.cryptoService().keysBackupService().checkAndStartKeysBackup() + viewModelScope.launch { + session.cryptoService().keysBackupService().checkAndStartKeysBackup() + } } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index dec9e6e8db..6bec52f1b1 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2041,6 +2041,7 @@ Computing recovery key… Downloading keys… Importing keys… + Decrypting keys… Unlock History Please enter a recovery key Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key. @@ -3442,6 +3443,7 @@ Join the Space with the given id Leave room with given id (or current room if null) Upgrades a room to a new version + Gen keys Sending Sent