mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-12-28 03:48:37 +03:00
Make keybackup service suspend + fixes
This commit is contained in:
parent
f0f64d8380
commit
210e0241d3
23 changed files with 2105 additions and 2163 deletions
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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<KeysVersionResult?>)
|
||||
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<KeysVersion>)
|
||||
@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<Unit>?)
|
||||
// /**
|
||||
// * 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<Unit>?)
|
||||
|
||||
/**
|
||||
* 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<KeysBackupVersionTrust>)
|
||||
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<KeysVersionResult?>)
|
||||
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<Boolean>)
|
||||
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<MegolmBackupCreationInfo>)
|
||||
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<Unit>?)
|
||||
@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<Unit>)
|
||||
@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<Unit>)
|
||||
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<Unit>)
|
||||
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<ImportRoomKeysResult>)
|
||||
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<ImportRoomKeysResult>)
|
||||
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<Boolean>)
|
||||
suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -70,4 +70,15 @@ data class MegolmSessionData(
|
|||
*/
|
||||
@Json(name = "forwarding_curve25519_key_chain")
|
||||
val forwardingCurve25519KeyChain: List<String>? = null
|
||||
)
|
||||
) {
|
||||
|
||||
fun isValid(): Boolean {
|
||||
return roomId != null &&
|
||||
forwardingCurve25519KeyChain != null &&
|
||||
algorithm != null &&
|
||||
senderKey != null &&
|
||||
senderClaimedKeys != null &&
|
||||
sessionId != null &&
|
||||
sessionKey != null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<RoomKeyContent>() ?: 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,
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Unit>? = null
|
||||
// private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = 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<MegolmBackupCreationInfo>) {
|
||||
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<KeysVersion>) {
|
||||
@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<Unit>?) {
|
||||
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<Unit>?) {
|
||||
// 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<Unit>?) {
|
||||
// // 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<KeysBackupVersionTrust>) {
|
||||
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<Unit>) {
|
||||
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<Unit>) {
|
||||
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<Unit>) {
|
||||
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<Unit> {
|
||||
trustKeysBackupVersion(version, true, it)
|
||||
}
|
||||
val importResult = awaitCallback<ImportRoomKeysResult> {
|
||||
cryptoCoroutineScope.launch {
|
||||
restoreBackup(version, key, null, null, null)
|
||||
Timber.i("onSecretKeyGossip: Recovered keys ${importResult.successfullyNumberOfImportedKeys} out of ${importResult.totalNumberOfKeys}")
|
||||
} 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<MegolmSessionData>()
|
||||
// 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<ImportRoomKeysResult>) {
|
||||
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<ImportRoomKeysResult>) {
|
||||
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<KeysVersionResult?>) {
|
||||
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<KeysVersionResult?>) {
|
||||
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<Boolean>) {
|
||||
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<KeysVersionResult?> {
|
||||
override fun onSuccess(data: KeysVersionResult?) {
|
||||
checkAndStartWithKeysBackupVersion(data)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version")
|
||||
keysBackupStateManager.state = KeysBackupState.Unknown
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun checkAndStartWithKeysBackupVersion(keyBackupVersion: KeysVersionResult?) {
|
||||
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<KeysBackupVersionTrust> {
|
||||
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<Boolean>) {
|
||||
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")
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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<LiveEvent<ImportRoomKeysResult>> = 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<KeysVersionResult?> {
|
||||
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<ImportRoomKeysResult> {
|
||||
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<ImportRoomKeysResult> {
|
||||
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<Unit> {
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -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<KeysBackupVersionTrust> {
|
||||
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<Unit> {
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<MegolmBackupCreationInfo> {
|
||||
override fun onSuccess(data: MegolmBackupCreationInfo) {
|
||||
if (requestedId != currentRequestId.value) {
|
||||
// this is an old request, we can't cancel but we can ignore
|
||||
return
|
||||
}
|
||||
recoveryKey.value = data.recoveryKey
|
||||
megolmBackupCreationInfo = data
|
||||
copyHasBeenMade = false
|
||||
|
||||
val keyBackup = session.cryptoService().keysBackupService()
|
||||
createKeysBackup(context, keyBackup)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
if (requestedId != currentRequestId.value) {
|
||||
// this is an old request, we can't cancel but we can ignore
|
||||
return
|
||||
}
|
||||
|
||||
loadingStatus.value = null
|
||||
|
||||
isCreatingBackupVersion.value = false
|
||||
prepareRecoverFailError.value = failure
|
||||
}
|
||||
})
|
||||
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<KeysVersionResult?> {
|
||||
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<KeysVersion> {
|
||||
override fun onSuccess(data: KeysVersion) {
|
||||
loadingStatus.value = null
|
||||
|
||||
isCreatingBackupVersion.value = false
|
||||
keysVersion.value = data
|
||||
navigateEvent.value = LiveEvent(NAVIGATE_TO_STEP_3)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.e(failure, "## createKeyBackupVersion")
|
||||
loadingStatus.value = null
|
||||
|
||||
isCreatingBackupVersion.value = false
|
||||
creatingBackupError.value = failure
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
isCreatingBackupVersion.postValue(false)
|
||||
creatingBackupError.postValue(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Boolean> {
|
||||
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) {
|
||||
|
|
|
@ -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<KeysVersionResult?> {
|
||||
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<Unit> {
|
||||
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<MegolmBackupCreationInfo> {
|
||||
session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
|
||||
}
|
||||
val version = awaitCallback<KeysVersion> {
|
||||
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<Boolean> {
|
||||
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,
|
||||
|
|
|
@ -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<KeysVersionResult?> {
|
||||
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<KeysVersionResult?> {
|
||||
session.cryptoService().keysBackupService().getVersion(version.version, it)
|
||||
val keyVersion = tryOrNull {
|
||||
session.cryptoService().keysBackupService().getVersion(version.version)
|
||||
}
|
||||
if (keyVersion == null) {
|
||||
// strange case... just finish?
|
||||
|
|
|
@ -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<String, String>?) {
|
||||
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<KeysVersionResult?> {
|
||||
session.cryptoService().keysBackupService().getCurrentVersion(it)
|
||||
} ?: return@launch
|
||||
val version = tryOrNull { session.cryptoService().keysBackupService().getCurrentVersion() }
|
||||
?: return@launch
|
||||
|
||||
awaitCallback<ImportRoomKeysResult> {
|
||||
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<Unit> {
|
||||
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")
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2041,6 +2041,7 @@
|
|||
<string name="keys_backup_restoring_computing_key_waiting_message">Computing recovery key…</string>
|
||||
<string name="keys_backup_restoring_downloading_backup_waiting_message">Downloading keys…</string>
|
||||
<string name="keys_backup_restoring_importing_keys_waiting_message">Importing keys…</string>
|
||||
<string name="keys_backup_restoring_decrypting_keys_waiting_message">Decrypting keys…</string>
|
||||
<string name="keys_backup_unlock_button">Unlock History</string>
|
||||
<string name="keys_backup_recovery_code_empty_error_message">Please enter a recovery key</string>
|
||||
<string name="keys_backup_recovery_code_error_decrypt">Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key.</string>
|
||||
|
@ -3442,6 +3443,7 @@
|
|||
<string name="command_description_join_space">Join the Space with the given id</string>
|
||||
<string name="command_description_leave_room">Leave room with given id (or current room if null)</string>
|
||||
<string name="command_description_upgrade_room">Upgrades a room to a new version</string>
|
||||
<string name="command_description_gen_keys">Gen keys</string>
|
||||
|
||||
<string name="event_status_a11y_sending">Sending</string>
|
||||
<string name="event_status_a11y_sent">Sent</string>
|
||||
|
|
Loading…
Reference in a new issue