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 {
|
sealed class Step {
|
||||||
data class ComputingKey(val progress: Int, val total: Int) : Step()
|
data class ComputingKey(val progress: Int, val total: Int) : Step()
|
||||||
object DownloadingKey : Step()
|
object DownloadingKey : Step()
|
||||||
|
data class DecryptingKey(val progress: Int, val total: Int) : Step()
|
||||||
data class ImportingKey(val progress: Int, val total: Int) : Step()
|
data class ImportingKey(val progress: Int, val total: Int) : Step()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.crypto.keysbackup
|
package org.matrix.android.sdk.api.session.crypto.keysbackup
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||||
import org.matrix.android.sdk.api.listeners.StepProgressListener
|
import org.matrix.android.sdk.api.listeners.StepProgressListener
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||||
|
@ -31,18 +30,17 @@ interface KeysBackupService {
|
||||||
* Retrieve the current version of the backup from the homeserver
|
* Retrieve the current version of the backup from the homeserver
|
||||||
*
|
*
|
||||||
* It can be different than keysBackupVersion.
|
* It can be different than keysBackupVersion.
|
||||||
* @param callback onSuccess(null) will be called if there is no backup on the server
|
|
||||||
*/
|
*/
|
||||||
fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>)
|
suspend fun getCurrentVersion(): KeysVersionResult?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new keys backup version and enable it, using the information return from [prepareKeysBackupVersion].
|
* Create a new keys backup version and enable it, using the information return from [prepareKeysBackupVersion].
|
||||||
*
|
*
|
||||||
* @param keysBackupCreationInfo the info object from [prepareKeysBackupVersion].
|
* @param keysBackupCreationInfo the info object from [prepareKeysBackupVersion].
|
||||||
* @param callback Asynchronous callback
|
* @return KeysVersion
|
||||||
*/
|
*/
|
||||||
fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo,
|
@Throws
|
||||||
callback: MatrixCallback<KeysVersion>)
|
suspend fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo): KeysVersion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Facility method to get the total number of locally stored keys
|
* Facility method to get the total number of locally stored keys
|
||||||
|
@ -54,23 +52,21 @@ interface KeysBackupService {
|
||||||
*/
|
*/
|
||||||
fun getTotalNumbersOfBackedUpKeys(): Int
|
fun getTotalNumbersOfBackedUpKeys(): Int
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Start to back up keys immediately.
|
// * Start to back up keys immediately.
|
||||||
*
|
// *
|
||||||
* @param progressListener the callback to follow the progress
|
// * @param progressListener the callback to follow the progress
|
||||||
* @param callback the main callback
|
// * @param callback the main callback
|
||||||
*/
|
// */
|
||||||
fun backupAllGroupSessions(progressListener: ProgressListener?,
|
// fun backupAllGroupSessions(progressListener: ProgressListener?,
|
||||||
callback: MatrixCallback<Unit>?)
|
// callback: MatrixCallback<Unit>?)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check trust on a key backup version.
|
* Check trust on a key backup version.
|
||||||
*
|
*
|
||||||
* @param keysBackupVersion the backup version to check.
|
* @param keysBackupVersion the backup version to check.
|
||||||
* @param callback block called when the operations completes.
|
|
||||||
*/
|
*/
|
||||||
fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult,
|
suspend fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult): KeysBackupVersionTrust
|
||||||
callback: MatrixCallback<KeysBackupVersionTrust>)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the current progress of the backup
|
* Return the current progress of the backup
|
||||||
|
@ -82,18 +78,16 @@ interface KeysBackupService {
|
||||||
*
|
*
|
||||||
* It can be different than keysBackupVersion.
|
* It can be different than keysBackupVersion.
|
||||||
* @param version the backup version
|
* @param version the backup version
|
||||||
* @param callback
|
|
||||||
*/
|
*/
|
||||||
fun getVersion(version: String,
|
suspend fun getVersion(version: String): KeysVersionResult?
|
||||||
callback: MatrixCallback<KeysVersionResult?>)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method fetches the last backup version on the server, then compare to the currently backup version use.
|
* This method fetches the last backup version on the server, then compare to the currently backup version use.
|
||||||
* If versions are not the same, the current backup is deleted (on server or locally), then the backup may be started again, using the last version.
|
* If versions are not the same, the current backup is deleted (on server or locally), then the backup may be started again, using the last version.
|
||||||
*
|
*
|
||||||
* @param callback true if backup is already using the last version, and false if it is not the case
|
* @return true if backup is already using the last version, and false if it is not the case
|
||||||
*/
|
*/
|
||||||
fun forceUsingLastVersion(callback: MatrixCallback<Boolean>)
|
suspend fun forceUsingLastVersion(): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the server for an active key backup.
|
* Check the server for an active key backup.
|
||||||
|
@ -101,7 +95,7 @@ interface KeysBackupService {
|
||||||
* If one is present and has a valid signature from one of the user's verified
|
* If one is present and has a valid signature from one of the user's verified
|
||||||
* devices, start backing up to it.
|
* devices, start backing up to it.
|
||||||
*/
|
*/
|
||||||
fun checkAndStartKeysBackup()
|
suspend fun checkAndStartKeysBackup()
|
||||||
|
|
||||||
fun addListener(listener: KeysBackupStateListener)
|
fun addListener(listener: KeysBackupStateListener)
|
||||||
|
|
||||||
|
@ -119,19 +113,16 @@ interface KeysBackupService {
|
||||||
* @param progressListener a progress listener, as generating private key from password may take a while
|
* @param progressListener a progress listener, as generating private key from password may take a while
|
||||||
* @param callback Asynchronous callback
|
* @param callback Asynchronous callback
|
||||||
*/
|
*/
|
||||||
fun prepareKeysBackupVersion(password: String?,
|
suspend fun prepareKeysBackupVersion(password: String?): MegolmBackupCreationInfo
|
||||||
progressListener: ProgressListener?,
|
|
||||||
callback: MatrixCallback<MegolmBackupCreationInfo>)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a keys backup version. It will delete all backed up keys on the server, and the backup itself.
|
* Delete a keys backup version. It will delete all backed up keys on the server, and the backup itself.
|
||||||
* If we are backing up to this version. Backup will be stopped.
|
* If we are backing up to this version. Backup will be stopped.
|
||||||
*
|
*
|
||||||
* @param version the backup version to delete.
|
* @param version the backup version to delete.
|
||||||
* @param callback Asynchronous callback
|
|
||||||
*/
|
*/
|
||||||
fun deleteBackup(version: String,
|
@Throws
|
||||||
callback: MatrixCallback<Unit>?)
|
suspend fun deleteBackup(version: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ask if the backup on the server contains keys that we may do not have locally.
|
* Ask if the backup on the server contains keys that we may do not have locally.
|
||||||
|
@ -145,35 +136,29 @@ interface KeysBackupService {
|
||||||
*
|
*
|
||||||
* @param keysBackupVersion the backup version to check.
|
* @param keysBackupVersion the backup version to check.
|
||||||
* @param trust the trust to set to the keys backup.
|
* @param trust the trust to set to the keys backup.
|
||||||
* @param callback block called when the operations completes.
|
|
||||||
*/
|
*/
|
||||||
fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult,
|
@Throws
|
||||||
trust: Boolean,
|
suspend fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, trust: Boolean)
|
||||||
callback: MatrixCallback<Unit>)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set trust on a keys backup version.
|
* Set trust on a keys backup version.
|
||||||
*
|
*
|
||||||
* @param keysBackupVersion the backup version to check.
|
* @param keysBackupVersion the backup version to check.
|
||||||
* @param recoveryKey the recovery key to challenge with the key backup public key.
|
* @param recoveryKey the recovery key to challenge with the key backup public key.
|
||||||
* @param callback block called when the operations completes.
|
|
||||||
*/
|
*/
|
||||||
fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult,
|
suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult,
|
||||||
recoveryKey: String,
|
recoveryKey: String)
|
||||||
callback: MatrixCallback<Unit>)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set trust on a keys backup version.
|
* Set trust on a keys backup version.
|
||||||
*
|
*
|
||||||
* @param keysBackupVersion the backup version to check.
|
* @param keysBackupVersion the backup version to check.
|
||||||
* @param password the pass phrase to challenge with the keyBackupVersion public key.
|
* @param password the pass phrase to challenge with the keyBackupVersion public key.
|
||||||
* @param callback block called when the operations completes.
|
|
||||||
*/
|
*/
|
||||||
fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult,
|
suspend fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult,
|
||||||
password: String,
|
password: String)
|
||||||
callback: MatrixCallback<Unit>)
|
|
||||||
|
|
||||||
fun onSecretKeyGossip(secret: String)
|
suspend fun onSecretKeyGossip(secret: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore a backup with a recovery key from a given backup version stored on the homeserver.
|
* Restore a backup with a recovery key from a given backup version stored on the homeserver.
|
||||||
|
@ -185,11 +170,10 @@ interface KeysBackupService {
|
||||||
* @param stepProgressListener the step progress listener
|
* @param stepProgressListener the step progress listener
|
||||||
* @param callback Callback. It provides the number of found keys and the number of successfully imported keys.
|
* @param callback Callback. It provides the number of found keys and the number of successfully imported keys.
|
||||||
*/
|
*/
|
||||||
fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
|
suspend fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
|
||||||
recoveryKey: String, roomId: String?,
|
recoveryKey: String, roomId: String?,
|
||||||
sessionId: String?,
|
sessionId: String?,
|
||||||
stepProgressListener: StepProgressListener?,
|
stepProgressListener: StepProgressListener?): ImportRoomKeysResult
|
||||||
callback: MatrixCallback<ImportRoomKeysResult>)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore a backup with a password from a given backup version stored on the homeserver.
|
* Restore a backup with a password from a given backup version stored on the homeserver.
|
||||||
|
@ -201,12 +185,11 @@ interface KeysBackupService {
|
||||||
* @param stepProgressListener the step progress listener
|
* @param stepProgressListener the step progress listener
|
||||||
* @param callback Callback. It provides the number of found keys and the number of successfully imported keys.
|
* @param callback Callback. It provides the number of found keys and the number of successfully imported keys.
|
||||||
*/
|
*/
|
||||||
fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult,
|
suspend fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult,
|
||||||
password: String,
|
password: String,
|
||||||
roomId: String?,
|
roomId: String?,
|
||||||
sessionId: String?,
|
sessionId: String?,
|
||||||
stepProgressListener: StepProgressListener?,
|
stepProgressListener: StepProgressListener?): ImportRoomKeysResult
|
||||||
callback: MatrixCallback<ImportRoomKeysResult>)
|
|
||||||
|
|
||||||
val keysBackupVersion: KeysVersionResult?
|
val keysBackupVersion: KeysVersionResult?
|
||||||
val currentBackupVersion: String?
|
val currentBackupVersion: String?
|
||||||
|
@ -218,5 +201,5 @@ interface KeysBackupService {
|
||||||
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
|
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
|
||||||
fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
|
fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
|
||||||
|
|
||||||
fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback<Boolean>)
|
suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -256,7 +256,12 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
|
override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
|
||||||
return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
|
return if (onlyBackedUp) {
|
||||||
|
keysBackupService.getTotalNumbersOfBackedUpKeys()
|
||||||
|
} else {
|
||||||
|
keysBackupService.getTotalNumbersOfKeys()
|
||||||
|
}
|
||||||
|
// return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -331,8 +336,10 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
|
|
||||||
// We try to enable key backups, if the backup version on the server is trusted,
|
// We try to enable key backups, if the backup version on the server is trusted,
|
||||||
// we're gonna continue backing up.
|
// we're gonna continue backing up.
|
||||||
tryOrNull {
|
cryptoCoroutineScope.launch {
|
||||||
keysBackupService.checkAndStartKeysBackup()
|
tryOrNull {
|
||||||
|
keysBackupService.checkAndStartKeysBackup()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the store
|
// Open the store
|
||||||
|
|
|
@ -70,4 +70,15 @@ data class MegolmSessionData(
|
||||||
*/
|
*/
|
||||||
@Json(name = "forwarding_curve25519_key_chain")
|
@Json(name = "forwarding_curve25519_key_chain")
|
||||||
val forwardingCurve25519KeyChain: List<String>? = null
|
val forwardingCurve25519KeyChain: List<String>? = null
|
||||||
)
|
) {
|
||||||
|
|
||||||
|
fun isValid(): Boolean {
|
||||||
|
return roomId != null &&
|
||||||
|
forwardingCurve25519KeyChain != null &&
|
||||||
|
algorithm != null &&
|
||||||
|
senderKey != null &&
|
||||||
|
senderClaimedKeys != null &&
|
||||||
|
sessionId != null &&
|
||||||
|
sessionKey != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -213,13 +213,14 @@ internal class RequestSender @Inject constructor(
|
||||||
suspend fun getKeyBackupVersion(version: String? = null): KeysVersionResult? {
|
suspend fun getKeyBackupVersion(version: String? = null): KeysVersionResult? {
|
||||||
return try {
|
return try {
|
||||||
if (version != null) {
|
if (version != null) {
|
||||||
getKeysBackupVersionTask.execute(version)
|
getKeysBackupVersionTask.executeRetry(version, 3)
|
||||||
} else {
|
} else {
|
||||||
getKeysBackupLastVersionTask.execute(Unit)
|
getKeysBackupLastVersionTask.executeRetry(Unit, 3)
|
||||||
}
|
}
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
if (failure is Failure.ServerError &&
|
if (failure is Failure.ServerError &&
|
||||||
failure.error.code == MatrixError.M_NOT_FOUND) {
|
failure.error.code == MatrixError.M_NOT_FOUND) {
|
||||||
|
// Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
throw failure
|
throw failure
|
||||||
|
|
|
@ -21,7 +21,6 @@ import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest
|
import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest
|
||||||
import org.matrix.android.sdk.internal.crypto.IncomingSecretShareRequest
|
import org.matrix.android.sdk.internal.crypto.IncomingSecretShareRequest
|
||||||
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
|
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for decrypting data
|
* An interface for decrypting data
|
||||||
|
@ -44,7 +43,7 @@ internal interface IMXDecrypting {
|
||||||
*
|
*
|
||||||
* @param event the key event.
|
* @param event the key event.
|
||||||
*/
|
*/
|
||||||
fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {}
|
fun onRoomKeyEvent(event: Event/*, defaultKeysBackupService: DefaultKeysBackupService*/) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the some messages can be decrypted with a new session
|
* Check if the some messages can be decrypted with a new session
|
||||||
|
|
|
@ -34,7 +34,6 @@ import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevice
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
||||||
import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting
|
import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting
|
||||||
import org.matrix.android.sdk.internal.crypto.algorithms.IMXWithHeldExtension
|
import org.matrix.android.sdk.internal.crypto.algorithms.IMXWithHeldExtension
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent
|
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent
|
||||||
|
@ -232,7 +231,7 @@ internal class MXMegolmDecryption(private val userId: String,
|
||||||
*
|
*
|
||||||
* @param event the key event.
|
* @param event the key event.
|
||||||
*/
|
*/
|
||||||
override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {
|
override fun onRoomKeyEvent(event: Event/*, defaultKeysBackupService: DefaultKeysBackupService*/) {
|
||||||
Timber.tag(loggerTag.value).v("onRoomKeyEvent()")
|
Timber.tag(loggerTag.value).v("onRoomKeyEvent()")
|
||||||
var exportFormat = false
|
var exportFormat = false
|
||||||
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return
|
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return
|
||||||
|
@ -295,7 +294,7 @@ internal class MXMegolmDecryption(private val userId: String,
|
||||||
exportFormat)
|
exportFormat)
|
||||||
|
|
||||||
if (added) {
|
if (added) {
|
||||||
defaultKeysBackupService.maybeBackupKeys()
|
// defaultKeysBackupService.maybeBackupKeys()
|
||||||
|
|
||||||
val content = RoomKeyRequestBody(
|
val content = RoomKeyRequestBody(
|
||||||
algorithm = roomKeyContent.algorithm,
|
algorithm = roomKeyContent.algorithm,
|
||||||
|
|
|
@ -31,7 +31,7 @@ import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevice
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
||||||
import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting
|
import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting
|
||||||
import org.matrix.android.sdk.internal.crypto.algorithms.IMXGroupEncryption
|
import org.matrix.android.sdk.internal.crypto.algorithms.IMXGroupEncryption
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
// import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
||||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
|
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
|
||||||
|
@ -53,7 +53,7 @@ internal class MXMegolmEncryption(
|
||||||
// The id of the room we will be sending to.
|
// The id of the room we will be sending to.
|
||||||
private val roomId: String,
|
private val roomId: String,
|
||||||
private val olmDevice: MXOlmDevice,
|
private val olmDevice: MXOlmDevice,
|
||||||
private val defaultKeysBackupService: DefaultKeysBackupService,
|
// private val defaultKeysBackupService: DefaultKeysBackupService,
|
||||||
private val cryptoStore: IMXCryptoStore,
|
private val cryptoStore: IMXCryptoStore,
|
||||||
private val deviceListManager: DeviceListManager,
|
private val deviceListManager: DeviceListManager,
|
||||||
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
|
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
|
||||||
|
@ -149,7 +149,7 @@ internal class MXMegolmEncryption(
|
||||||
olmDevice.addInboundGroupSession(sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!,
|
olmDevice.addInboundGroupSession(sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!,
|
||||||
emptyList(), keysClaimedMap, false)
|
emptyList(), keysClaimedMap, false)
|
||||||
|
|
||||||
defaultKeysBackupService.maybeBackupKeys()
|
// defaultKeysBackupService.maybeBackupKeys()
|
||||||
|
|
||||||
return MXOutboundSessionInfo(sessionId, SharedWithHelper(roomId, sessionId, cryptoStore))
|
return MXOutboundSessionInfo(sessionId, SharedWithHelper(roomId, sessionId, cryptoStore))
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
||||||
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
|
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
|
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
|
||||||
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
||||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||||
|
@ -32,7 +31,7 @@ import javax.inject.Inject
|
||||||
|
|
||||||
internal class MXMegolmEncryptionFactory @Inject constructor(
|
internal class MXMegolmEncryptionFactory @Inject constructor(
|
||||||
private val olmDevice: MXOlmDevice,
|
private val olmDevice: MXOlmDevice,
|
||||||
private val defaultKeysBackupService: DefaultKeysBackupService,
|
// private val defaultKeysBackupService: DefaultKeysBackupService,
|
||||||
private val cryptoStore: IMXCryptoStore,
|
private val cryptoStore: IMXCryptoStore,
|
||||||
private val deviceListManager: DeviceListManager,
|
private val deviceListManager: DeviceListManager,
|
||||||
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
|
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
|
||||||
|
@ -48,7 +47,7 @@ internal class MXMegolmEncryptionFactory @Inject constructor(
|
||||||
return MXMegolmEncryption(
|
return MXMegolmEncryption(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
olmDevice = olmDevice,
|
olmDevice = olmDevice,
|
||||||
defaultKeysBackupService = defaultKeysBackupService,
|
// defaultKeysBackupService = defaultKeysBackupService,
|
||||||
cryptoStore = cryptoStore,
|
cryptoStore = cryptoStore,
|
||||||
deviceListManager = deviceListManager,
|
deviceListManager = deviceListManager,
|
||||||
ensureOlmSessionsForDevicesAction = ensureOlmSessionsForDevicesAction,
|
ensureOlmSessionsForDevicesAction = ensureOlmSessionsForDevicesAction,
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -17,6 +17,7 @@
|
||||||
package org.matrix.android.sdk.internal.crypto.keysbackup
|
package org.matrix.android.sdk.internal.crypto.keysbackup
|
||||||
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -33,11 +34,13 @@ internal class KeysBackupStateManager(private val uiHandler: Handler) {
|
||||||
field = newState
|
field = newState
|
||||||
|
|
||||||
// Notify listeners about the state change, on the ui thread
|
// Notify listeners about the state change, on the ui thread
|
||||||
uiHandler.post {
|
synchronized(listeners) {
|
||||||
synchronized(listeners) {
|
listeners.forEach {
|
||||||
listeners.forEach {
|
uiHandler.post {
|
||||||
// Use newState because state may have already changed again
|
// Use newState because state may have already changed again
|
||||||
it.onStateChange(newState)
|
tryOrNull {
|
||||||
|
it.onStateChange(newState)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,16 +18,16 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
|
||||||
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.annotation.UiThread
|
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.failure.MatrixError
|
import org.matrix.android.sdk.api.failure.MatrixError
|
||||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||||
|
@ -52,10 +52,8 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBa
|
||||||
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
||||||
import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
|
import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
import org.matrix.android.sdk.internal.extensions.foldToCallback
|
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
|
||||||
import org.matrix.olm.OlmException
|
import org.matrix.olm.OlmException
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uniffi.olm.BackupRecoveryKey
|
import uniffi.olm.BackupRecoveryKey
|
||||||
|
@ -91,7 +89,9 @@ internal class RustKeyBackupService @Inject constructor(
|
||||||
override var keysBackupVersion: KeysVersionResult? = null
|
override var keysBackupVersion: KeysVersionResult? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null
|
// private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null
|
||||||
|
|
||||||
|
private val importScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||||
|
|
||||||
private var keysBackupStateListener: KeysBackupStateListener? = null
|
private var keysBackupStateListener: KeysBackupStateListener? = null
|
||||||
|
|
||||||
|
@ -115,64 +115,57 @@ internal class RustKeyBackupService @Inject constructor(
|
||||||
keysBackupStateManager.removeListener(listener)
|
keysBackupStateManager.removeListener(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun prepareKeysBackupVersion(password: String?,
|
override suspend fun prepareKeysBackupVersion(password: String?): MegolmBackupCreationInfo {
|
||||||
progressListener: ProgressListener?,
|
return withContext(coroutineDispatchers.computation) {
|
||||||
callback: MatrixCallback<MegolmBackupCreationInfo>) {
|
val key = if (password != null) {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
// this might be a bit slow as it's stretching the password
|
||||||
runCatching {
|
BackupRecoveryKey.newFromPassphrase(password)
|
||||||
withContext(coroutineDispatchers.crypto) {
|
} else {
|
||||||
val key = if (password != null) {
|
BackupRecoveryKey()
|
||||||
BackupRecoveryKey.newFromPassphrase(password)
|
}
|
||||||
} else {
|
|
||||||
BackupRecoveryKey()
|
|
||||||
}
|
|
||||||
|
|
||||||
val publicKey = key.megolmV1PublicKey()
|
val publicKey = key.megolmV1PublicKey()
|
||||||
val backupAuthData = SignalableMegolmBackupAuthData(
|
val backupAuthData = SignalableMegolmBackupAuthData(
|
||||||
publicKey = publicKey.publicKey,
|
publicKey = publicKey.publicKey,
|
||||||
privateKeySalt = publicKey.passphraseInfo?.privateKeySalt,
|
privateKeySalt = publicKey.passphraseInfo?.privateKeySalt,
|
||||||
privateKeyIterations = publicKey.passphraseInfo?.privateKeyIterations
|
privateKeyIterations = publicKey.passphraseInfo?.privateKeyIterations
|
||||||
)
|
)
|
||||||
val canonicalJson = JsonCanonicalizer.getCanonicalJson(
|
val canonicalJson = JsonCanonicalizer.getCanonicalJson(
|
||||||
Map::class.java,
|
Map::class.java,
|
||||||
backupAuthData.signalableJSONDictionary()
|
backupAuthData.signalableJSONDictionary()
|
||||||
)
|
)
|
||||||
|
|
||||||
val signedMegolmBackupAuthData = MegolmBackupAuthData(
|
val signedMegolmBackupAuthData = MegolmBackupAuthData(
|
||||||
publicKey = backupAuthData.publicKey,
|
publicKey = backupAuthData.publicKey,
|
||||||
privateKeySalt = backupAuthData.privateKeySalt,
|
privateKeySalt = backupAuthData.privateKeySalt,
|
||||||
privateKeyIterations = backupAuthData.privateKeyIterations,
|
privateKeyIterations = backupAuthData.privateKeyIterations,
|
||||||
signatures = olmMachine.sign(canonicalJson)
|
signatures = olmMachine.sign(canonicalJson)
|
||||||
)
|
)
|
||||||
|
|
||||||
MegolmBackupCreationInfo(
|
MegolmBackupCreationInfo(
|
||||||
algorithm = publicKey.backupAlgorithm,
|
algorithm = publicKey.backupAlgorithm,
|
||||||
authData = signedMegolmBackupAuthData,
|
authData = signedMegolmBackupAuthData,
|
||||||
recoveryKey = key.toBase58()
|
recoveryKey = key.toBase58()
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}.foldToCallback(callback)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo,
|
override suspend fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo): KeysVersion {
|
||||||
callback: MatrixCallback<KeysVersion>) {
|
return withContext(coroutineDispatchers.crypto) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
val createKeysBackupVersionBody = CreateKeysBackupVersionBody(
|
||||||
val createKeysBackupVersionBody = CreateKeysBackupVersionBody(
|
algorithm = keysBackupCreationInfo.algorithm,
|
||||||
algorithm = keysBackupCreationInfo.algorithm,
|
authData = keysBackupCreationInfo.authData.toJsonDict()
|
||||||
authData = keysBackupCreationInfo.authData.toJsonDict()
|
)
|
||||||
)
|
|
||||||
|
|
||||||
keysBackupStateManager.state = KeysBackupState.Enabling
|
keysBackupStateManager.state = KeysBackupState.Enabling
|
||||||
|
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
|
||||||
try {
|
try {
|
||||||
val data = sender.createKeyBackup(createKeysBackupVersionBody)
|
val data = withContext(coroutineDispatchers.io) {
|
||||||
|
sender.createKeyBackup(createKeysBackupVersionBody)
|
||||||
|
}
|
||||||
// Reset backup markers.
|
// Reset backup markers.
|
||||||
// Don't we need to join the task here? Isn't this a race condition?
|
// Don't we need to join the task here? Isn't this a race condition?
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
olmMachine.disableBackup()
|
||||||
olmMachine.disableBackup()
|
|
||||||
}
|
|
||||||
|
|
||||||
val keyBackupVersion = KeysVersionResult(
|
val keyBackupVersion = KeysVersionResult(
|
||||||
algorithm = createKeysBackupVersionBody.algorithm,
|
algorithm = createKeysBackupVersionBody.algorithm,
|
||||||
|
@ -182,13 +175,11 @@ internal class RustKeyBackupService @Inject constructor(
|
||||||
count = 0,
|
count = 0,
|
||||||
hash = ""
|
hash = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
enableKeysBackup(keyBackupVersion)
|
enableKeysBackup(keyBackupVersion)
|
||||||
|
data
|
||||||
callback.onSuccess(data)
|
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
keysBackupStateManager.state = KeysBackupState.Disabled
|
keysBackupStateManager.state = KeysBackupState.Disabled
|
||||||
callback.onFailure(failure)
|
throw failure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,7 +191,7 @@ internal class RustKeyBackupService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetBackupAllGroupSessionsListeners() {
|
private fun resetBackupAllGroupSessionsListeners() {
|
||||||
backupAllGroupSessionsCallback = null
|
// backupAllGroupSessionsCallback = null
|
||||||
|
|
||||||
keysBackupStateListener?.let {
|
keysBackupStateListener?.let {
|
||||||
keysBackupStateManager.removeListener(it)
|
keysBackupStateManager.removeListener(it)
|
||||||
|
@ -219,29 +210,24 @@ internal class RustKeyBackupService @Inject constructor(
|
||||||
olmMachine.disableBackup()
|
olmMachine.disableBackup()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteBackup(version: String, callback: MatrixCallback<Unit>?) {
|
override suspend fun deleteBackup(version: String) {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
withContext(coroutineDispatchers.crypto) {
|
if (keysBackupVersion != null && version == keysBackupVersion?.version) {
|
||||||
if (keysBackupVersion != null && version == keysBackupVersion?.version) {
|
resetKeysBackupData()
|
||||||
resetKeysBackupData()
|
keysBackupVersion = null
|
||||||
keysBackupVersion = null
|
keysBackupStateManager.state = KeysBackupState.Unknown
|
||||||
keysBackupStateManager.state = KeysBackupState.Unknown
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun eventuallyRestartBackup() {
|
try {
|
||||||
// Do not stay in KeysBackupState.Unknown but check what is available on the homeserver
|
sender.deleteKeyBackup(version)
|
||||||
if (state == KeysBackupState.Unknown) {
|
// Do not stay in KeysBackupState.Unknown but check what is available on the homeserver
|
||||||
checkAndStartKeysBackup()
|
if (state == KeysBackupState.Unknown) {
|
||||||
}
|
checkAndStartKeysBackup()
|
||||||
}
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
try {
|
// Do not stay in KeysBackupState.Unknown but check what is available on the homeserver
|
||||||
sender.deleteKeyBackup(version)
|
if (state == KeysBackupState.Unknown) {
|
||||||
eventuallyRestartBackup()
|
checkAndStartKeysBackup()
|
||||||
uiHandler.post { callback?.onSuccess(Unit) }
|
|
||||||
} catch (failure: Throwable) {
|
|
||||||
eventuallyRestartBackup()
|
|
||||||
uiHandler.post { callback?.onFailure(failure) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -264,12 +250,12 @@ internal class RustKeyBackupService @Inject constructor(
|
||||||
return olmMachine.roomKeyCounts().backedUp.toInt()
|
return olmMachine.roomKeyCounts().backedUp.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun backupAllGroupSessions(progressListener: ProgressListener?,
|
// override fun backupAllGroupSessions(progressListener: ProgressListener?,
|
||||||
callback: MatrixCallback<Unit>?) {
|
// callback: MatrixCallback<Unit>?) {
|
||||||
// This is only used in tests? While it's fine have methods that are
|
// // This is only used in tests? While it's fine have methods that are
|
||||||
// only used for tests, this one has a lot of logic that is nowhere else used.
|
// // only used for tests, this one has a lot of logic that is nowhere else used.
|
||||||
TODO()
|
// TODO()
|
||||||
}
|
// }
|
||||||
|
|
||||||
private suspend fun checkBackupTrust(authData: MegolmBackupAuthData?): KeysBackupVersionTrust {
|
private suspend fun checkBackupTrust(authData: MegolmBackupAuthData?): KeysBackupVersionTrust {
|
||||||
return if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isNullOrEmpty()) {
|
return if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isNullOrEmpty()) {
|
||||||
|
@ -280,82 +266,68 @@ internal class RustKeyBackupService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult,
|
override suspend fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult): KeysBackupVersionTrust {
|
||||||
callback: MatrixCallback<KeysBackupVersionTrust>) {
|
|
||||||
val authData = keysBackupVersion.getAuthDataAsMegolmBackupAuthData()
|
val authData = keysBackupVersion.getAuthDataAsMegolmBackupAuthData()
|
||||||
|
return withContext(coroutineDispatchers.crypto) {
|
||||||
cryptoCoroutineScope.launch {
|
checkBackupTrust(authData)
|
||||||
try {
|
|
||||||
callback.onSuccess(checkBackupTrust(authData))
|
|
||||||
} catch (exception: Throwable) {
|
|
||||||
callback.onFailure(exception)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult,
|
override suspend fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, trust: Boolean) {
|
||||||
trust: Boolean,
|
withContext(coroutineDispatchers.crypto) {
|
||||||
callback: MatrixCallback<Unit>) {
|
Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}")
|
||||||
Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}")
|
|
||||||
|
|
||||||
// Get auth data to update it
|
// Get auth data to update it
|
||||||
val authData = getMegolmBackupAuthData(keysBackupVersion)
|
val authData = getMegolmBackupAuthData(keysBackupVersion)
|
||||||
|
|
||||||
if (authData == null) {
|
if (authData == null) {
|
||||||
Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data")
|
Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data")
|
||||||
|
throw IllegalArgumentException("Missing element")
|
||||||
|
} else {
|
||||||
|
// Get current signatures, or create an empty set
|
||||||
|
val userId = olmMachine.userId()
|
||||||
|
val signatures = authData.signatures?.get(userId).orEmpty().toMutableMap()
|
||||||
|
|
||||||
callback.onFailure(IllegalArgumentException("Missing element"))
|
if (trust) {
|
||||||
} else {
|
// Add current device signature
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary())
|
||||||
val body = withContext(coroutineDispatchers.crypto) {
|
val deviceSignature = olmMachine.sign(canonicalJson)
|
||||||
// Get current signatures, or create an empty set
|
|
||||||
val userId = olmMachine.userId()
|
|
||||||
val signatures = authData.signatures?.get(userId).orEmpty().toMutableMap()
|
|
||||||
|
|
||||||
if (trust) {
|
deviceSignature[userId]?.forEach { entry ->
|
||||||
// Add current device signature
|
signatures[entry.key] = entry.value
|
||||||
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary())
|
|
||||||
val deviceSignature = olmMachine.sign(canonicalJson)
|
|
||||||
|
|
||||||
deviceSignature[userId]?.forEach { entry ->
|
|
||||||
signatures[entry.key] = entry.value
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
signatures.remove("ed25519:${olmMachine.deviceId()}")
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
val newAuthData = authData.copy()
|
signatures.remove("ed25519:${olmMachine.deviceId()}")
|
||||||
val newSignatures = newAuthData.signatures.orEmpty().toMutableMap()
|
|
||||||
newSignatures[userId] = signatures
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
UpdateKeysBackupVersionBody(
|
|
||||||
algorithm = keysBackupVersion.algorithm,
|
|
||||||
authData = newAuthData.copy(signatures = newSignatures).toJsonDict(),
|
|
||||||
version = keysBackupVersion.version)
|
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
|
val newAuthData = authData.copy()
|
||||||
|
val newSignatures = newAuthData.signatures.orEmpty().toMutableMap()
|
||||||
|
newSignatures[userId] = signatures
|
||||||
|
|
||||||
|
val body = UpdateKeysBackupVersionBody(
|
||||||
|
algorithm = keysBackupVersion.algorithm,
|
||||||
|
authData = newAuthData.copy(signatures = newSignatures).toJsonDict(),
|
||||||
|
version = keysBackupVersion.version)
|
||||||
|
|
||||||
|
withContext(coroutineDispatchers.io) {
|
||||||
sender.updateBackup(keysBackupVersion, body)
|
sender.updateBackup(keysBackupVersion, body)
|
||||||
|
|
||||||
val newKeysBackupVersion = KeysVersionResult(
|
|
||||||
algorithm = keysBackupVersion.algorithm,
|
|
||||||
authData = body.authData,
|
|
||||||
version = keysBackupVersion.version,
|
|
||||||
hash = keysBackupVersion.hash,
|
|
||||||
count = keysBackupVersion.count
|
|
||||||
)
|
|
||||||
|
|
||||||
checkAndStartWithKeysBackupVersion(newKeysBackupVersion)
|
|
||||||
callback.onSuccess(Unit)
|
|
||||||
} catch (exception: Throwable) {
|
|
||||||
callback.onFailure(exception)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val newKeysBackupVersion = KeysVersionResult(
|
||||||
|
algorithm = keysBackupVersion.algorithm,
|
||||||
|
authData = body.authData,
|
||||||
|
version = keysBackupVersion.version,
|
||||||
|
hash = keysBackupVersion.hash,
|
||||||
|
count = keysBackupVersion.count
|
||||||
|
)
|
||||||
|
|
||||||
|
checkAndStartWithKeysBackupVersion(newKeysBackupVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the recovery key matches to the public key that we downloaded from the server.
|
// Check that the recovery key matches to the public key that we downloaded from the server.
|
||||||
// If they match, we can trust the public key and enable backups since we have the private key.
|
// If they match, we can trust the public key and enable backups since we have the private key.
|
||||||
private fun checkRecoveryKey(recoveryKey: BackupRecoveryKey, keysBackupData: KeysVersionResult) {
|
private fun checkRecoveryKey(recoveryKey: BackupRecoveryKey, keysBackupData: KeysVersionResult) {
|
||||||
val backupKey = recoveryKey.megolmV1PublicKey()
|
val backupKey = recoveryKey.megolmV1PublicKey()
|
||||||
val authData = getMegolmBackupAuthData(keysBackupData)
|
val authData = getMegolmBackupAuthData(keysBackupData)
|
||||||
|
@ -376,60 +348,50 @@ internal class RustKeyBackupService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult,
|
override suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, recoveryKey: String) {
|
||||||
recoveryKey: String,
|
|
||||||
callback: MatrixCallback<Unit>) {
|
|
||||||
Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}")
|
Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}")
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
cryptoCoroutineScope.launch {
|
// This is ~nowhere mentioned, the string here is actually a base58 encoded key.
|
||||||
try {
|
// This not really supported by the spec for the backup key, the 4S key supports
|
||||||
// This is ~nowhere mentioned, the string here is actually a base58 encoded key.
|
// base58 encoding and the same method seems to be used here.
|
||||||
// This not really supported by the spec for the backup key, the 4S key supports
|
val key = BackupRecoveryKey.fromBase58(recoveryKey)
|
||||||
// base58 encoding and the same method seems to be used here.
|
checkRecoveryKey(key, keysBackupVersion)
|
||||||
val key = BackupRecoveryKey.fromBase58(recoveryKey)
|
trustKeysBackupVersion(keysBackupVersion, true)
|
||||||
checkRecoveryKey(key, keysBackupVersion)
|
|
||||||
trustKeysBackupVersion(keysBackupVersion, true, callback)
|
|
||||||
} catch (exception: Throwable) {
|
|
||||||
callback.onFailure(exception)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult,
|
override suspend fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, password: String) {
|
||||||
password: String,
|
withContext(coroutineDispatchers.crypto) {
|
||||||
callback: MatrixCallback<Unit>) {
|
val key = recoveryKeyFromPassword(password, keysBackupVersion)
|
||||||
cryptoCoroutineScope.launch {
|
checkRecoveryKey(key, keysBackupVersion)
|
||||||
try {
|
trustKeysBackupVersion(keysBackupVersion, true)
|
||||||
val key = recoveryKeyFromPassword(password, keysBackupVersion)
|
|
||||||
checkRecoveryKey(key, keysBackupVersion)
|
|
||||||
trustKeysBackupVersion(keysBackupVersion, true, callback)
|
|
||||||
} catch (exception: Throwable) {
|
|
||||||
Timber.w(exception)
|
|
||||||
callback.onFailure(exception)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSecretKeyGossip(secret: String) {
|
override suspend fun onSecretKeyGossip(curveKeyBase64: String) {
|
||||||
Timber.i("## CrossSigning - onSecretKeyGossip")
|
Timber.i("## CrossSigning - onSecretKeyGossip")
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
try {
|
try {
|
||||||
val version = sender.getKeyBackupVersion()
|
val version = sender.getKeyBackupVersion()
|
||||||
|
|
||||||
if (version != null) {
|
if (version != null) {
|
||||||
val key = BackupRecoveryKey.fromBase64(secret)
|
val key = BackupRecoveryKey.fromBase64(curveKeyBase64)
|
||||||
|
if (isValidRecoveryKey(key, version)) {
|
||||||
|
trustKeysBackupVersion(version, true)
|
||||||
|
// we don't want to wait for that
|
||||||
|
importScope.launch {
|
||||||
|
try {
|
||||||
|
val importResult = restoreBackup(version, key, null, null, null)
|
||||||
|
|
||||||
awaitCallback<Unit> {
|
Timber.i("onSecretKeyGossip: Recovered keys ${importResult.successfullyNumberOfImportedKeys} out of ${importResult.totalNumberOfKeys}")
|
||||||
trustKeysBackupVersion(version, true, it)
|
} catch (failure: Throwable) {
|
||||||
}
|
// fail silently..
|
||||||
val importResult = awaitCallback<ImportRoomKeysResult> {
|
Timber.e(failure, "onSecretKeyGossip: Failed to import keys from backup")
|
||||||
cryptoCoroutineScope.launch {
|
}
|
||||||
restoreBackup(version, key, null, null, null)
|
|
||||||
}
|
}
|
||||||
|
// we can save, it's valid
|
||||||
|
saveBackupRecoveryKey(key.toBase64(), version.version)
|
||||||
}
|
}
|
||||||
Timber.i("onSecretKeyGossip: Recovered keys ${importResult.successfullyNumberOfImportedKeys} out of ${importResult.totalNumberOfKeys}")
|
|
||||||
|
|
||||||
saveBackupRecoveryKey(secret, version.version)
|
|
||||||
} else {
|
} else {
|
||||||
Timber.e("onSecretKeyGossip: Failed to import backup recovery key, no backup version was found on the server")
|
Timber.e("onSecretKeyGossip: Failed to import backup recovery key, no backup version was found on the server")
|
||||||
}
|
}
|
||||||
|
@ -511,9 +473,14 @@ internal class RustKeyBackupService @Inject constructor(
|
||||||
Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version")
|
Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version")
|
||||||
throw InvalidParameterException("Invalid recovery key")
|
throw InvalidParameterException("Invalid recovery key")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save for next time and for gossiping
|
||||||
|
saveBackupRecoveryKey(recoveryKey.toBase64(), keysVersionResult.version)
|
||||||
}
|
}
|
||||||
|
|
||||||
stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey)
|
withContext(coroutineDispatchers.main) {
|
||||||
|
stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey)
|
||||||
|
}
|
||||||
|
|
||||||
// Get backed up keys from the homeserver
|
// Get backed up keys from the homeserver
|
||||||
val data = getKeys(sessionId, roomId, keysVersionResult.version)
|
val data = getKeys(sessionId, roomId, keysVersionResult.version)
|
||||||
|
@ -522,13 +489,33 @@ internal class RustKeyBackupService @Inject constructor(
|
||||||
val sessionsData = ArrayList<MegolmSessionData>()
|
val sessionsData = ArrayList<MegolmSessionData>()
|
||||||
// Restore that data
|
// Restore that data
|
||||||
var sessionsFromHsCount = 0
|
var sessionsFromHsCount = 0
|
||||||
|
cryptoCoroutineScope.launch(Dispatchers.Main) {
|
||||||
|
stepProgressListener?.onStepProgress(StepProgressListener.Step.DecryptingKey(0, data.roomIdToRoomKeysBackupData.size))
|
||||||
|
}
|
||||||
|
var progressDecryptIndex = 0
|
||||||
|
|
||||||
|
// TODO this is quite long, could we add some concurrency here?
|
||||||
for ((roomIdLoop, backupData) in data.roomIdToRoomKeysBackupData) {
|
for ((roomIdLoop, backupData) in data.roomIdToRoomKeysBackupData) {
|
||||||
|
val roomIndex = progressDecryptIndex
|
||||||
|
progressDecryptIndex++
|
||||||
|
cryptoCoroutineScope.launch(Dispatchers.Main) {
|
||||||
|
stepProgressListener?.onStepProgress(StepProgressListener.Step.DecryptingKey(roomIndex, data.roomIdToRoomKeysBackupData.size))
|
||||||
|
}
|
||||||
for ((sessionIdLoop, keyBackupData) in backupData.sessionIdToKeyBackupData) {
|
for ((sessionIdLoop, keyBackupData) in backupData.sessionIdToKeyBackupData) {
|
||||||
sessionsFromHsCount++
|
sessionsFromHsCount++
|
||||||
|
|
||||||
val sessionData = decryptKeyBackupData(keyBackupData, sessionIdLoop, roomIdLoop, recoveryKey)
|
val sessionData = decryptKeyBackupData(keyBackupData, sessionIdLoop, roomIdLoop, recoveryKey)
|
||||||
|
|
||||||
sessionData?.let {
|
// rust is not very lax and will throw if field are missing,
|
||||||
|
// add a check
|
||||||
|
// TODO maybe could be done on rust side?
|
||||||
|
sessionData?.takeIf {
|
||||||
|
it.isValid().also {
|
||||||
|
if (!it) {
|
||||||
|
Timber.w("restoreKeysWithRecoveryKey: malformed sessionData $sessionData")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}?.let {
|
||||||
sessionsData.add(it)
|
sessionsData.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -548,7 +535,9 @@ internal class RustKeyBackupService @Inject constructor(
|
||||||
object : ProgressListener {
|
object : ProgressListener {
|
||||||
override fun onProgress(progress: Int, total: Int) {
|
override fun onProgress(progress: Int, total: Int) {
|
||||||
// Note: no need to post to UI thread, importMegolmSessionsData() will do it
|
// Note: no need to post to UI thread, importMegolmSessionsData() will do it
|
||||||
stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total))
|
cryptoCoroutineScope.launch(Dispatchers.Main) {
|
||||||
|
stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -562,132 +551,120 @@ internal class RustKeyBackupService @Inject constructor(
|
||||||
maybeBackupKeys()
|
maybeBackupKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save for next time and for gossiping
|
|
||||||
saveBackupRecoveryKey(recoveryKey.toBase64(), keysVersionResult.version)
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
|
override suspend fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
|
||||||
recoveryKey: String,
|
recoveryKey: String,
|
||||||
roomId: String?,
|
roomId: String?,
|
||||||
sessionId: String?,
|
sessionId: String?,
|
||||||
stepProgressListener: StepProgressListener?,
|
stepProgressListener: StepProgressListener?): ImportRoomKeysResult {
|
||||||
callback: MatrixCallback<ImportRoomKeysResult>) {
|
|
||||||
Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}")
|
Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}")
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
|
||||||
runCatching {
|
val key = BackupRecoveryKey.fromBase58(recoveryKey)
|
||||||
val key = BackupRecoveryKey.fromBase58(recoveryKey)
|
|
||||||
restoreBackup(keysVersionResult, key, roomId, sessionId, stepProgressListener)
|
return restoreBackup(keysVersionResult, key, roomId, sessionId, stepProgressListener)
|
||||||
}.foldToCallback(callback)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult,
|
override suspend fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult,
|
||||||
password: String,
|
password: String,
|
||||||
roomId: String?,
|
roomId: String?,
|
||||||
sessionId: String?,
|
sessionId: String?,
|
||||||
stepProgressListener: StepProgressListener?,
|
stepProgressListener: StepProgressListener?): ImportRoomKeysResult {
|
||||||
callback: MatrixCallback<ImportRoomKeysResult>) {
|
|
||||||
Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}")
|
Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}")
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
val recoveryKey = withContext(coroutineDispatchers.crypto) {
|
||||||
runCatching {
|
recoveryKeyFromPassword(password, keysBackupVersion)
|
||||||
val recoveryKey = withContext(coroutineDispatchers.crypto) {
|
}
|
||||||
recoveryKeyFromPassword(password, keysBackupVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreBackup(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener)
|
return restoreBackup(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener)
|
||||||
}.foldToCallback(callback)
|
}
|
||||||
|
|
||||||
|
override suspend fun getVersion(version: String): KeysVersionResult? {
|
||||||
|
return withContext(coroutineDispatchers.io) {
|
||||||
|
sender.getKeyBackupVersion(version)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getVersion(version: String, callback: MatrixCallback<KeysVersionResult?>) {
|
@Throws
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
override suspend fun getCurrentVersion(): KeysVersionResult? {
|
||||||
runCatching {
|
return withContext(coroutineDispatchers.io) {
|
||||||
sender.getKeyBackupVersion(version)
|
sender.getKeyBackupVersion()
|
||||||
}.foldToCallback(callback)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) {
|
override suspend fun forceUsingLastVersion(): Boolean {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
val response = withContext(coroutineDispatchers.io) {
|
||||||
runCatching {
|
sender.getKeyBackupVersion()
|
||||||
sender.getKeyBackupVersion()
|
|
||||||
}.foldToCallback(callback)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun forceUsingLastVersionHelper(): Boolean {
|
return withContext(coroutineDispatchers.crypto) {
|
||||||
val response = sender.getKeyBackupVersion()
|
val serverBackupVersion = response?.version
|
||||||
val serverBackupVersion = response?.version
|
val localBackupVersion = keysBackupVersion?.version
|
||||||
val localBackupVersion = keysBackupVersion?.version
|
|
||||||
|
|
||||||
Timber.d("BACKUP: $serverBackupVersion")
|
Timber.d("BACKUP: $serverBackupVersion")
|
||||||
|
|
||||||
return if (serverBackupVersion == null) {
|
if (serverBackupVersion == null) {
|
||||||
if (localBackupVersion == null) {
|
if (localBackupVersion == null) {
|
||||||
// No backup on the server, and backup is not active
|
// No backup on the server, and backup is not active
|
||||||
true
|
|
||||||
} else {
|
|
||||||
// No backup on the server, and we are currently backing up, so stop backing up
|
|
||||||
resetKeysBackupData()
|
|
||||||
keysBackupVersion = null
|
|
||||||
keysBackupStateManager.state = KeysBackupState.Disabled
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (localBackupVersion == null) {
|
|
||||||
// Do a check
|
|
||||||
checkAndStartWithKeysBackupVersion(response)
|
|
||||||
// backup on the server, and backup is not active
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
// Backup on the server, and we are currently backing up, compare version
|
|
||||||
if (localBackupVersion == serverBackupVersion) {
|
|
||||||
// We are already using the last version of the backup
|
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
// This will automatically check for the last version then
|
// No backup on the server, and we are currently backing up, so stop backing up
|
||||||
deleteBackup(localBackupVersion, null)
|
resetKeysBackupData()
|
||||||
// We are not using the last version, so delete the current version we are using on the server
|
keysBackupVersion = null
|
||||||
|
keysBackupStateManager.state = KeysBackupState.Disabled
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (localBackupVersion == null) {
|
||||||
|
// Do a check
|
||||||
|
checkAndStartWithKeysBackupVersion(response)
|
||||||
|
// backup on the server, and backup is not active
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
// Backup on the server, and we are currently backing up, compare version
|
||||||
|
if (localBackupVersion == serverBackupVersion) {
|
||||||
|
// We are already using the last version of the backup
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
// This will automatically check for the last version then
|
||||||
|
tryOrNull("Failed to automatically check for the last version") {
|
||||||
|
deleteBackup(localBackupVersion)
|
||||||
|
}
|
||||||
|
// We are not using the last version, so delete the current version we are using on the server
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun forceUsingLastVersion(callback: MatrixCallback<Boolean>) {
|
override suspend fun checkAndStartKeysBackup() {
|
||||||
cryptoCoroutineScope.launch {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
runCatching {
|
if (!isStucked) {
|
||||||
forceUsingLastVersionHelper()
|
// Try to start or restart the backup only if it is in unknown or bad state
|
||||||
}.foldToCallback(callback)
|
Timber.w("checkAndStartKeysBackup: invalid state: $state")
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
|
||||||
|
keysBackupVersion = null
|
||||||
|
keysBackupStateManager.state = KeysBackupState.CheckingBackUpOnHomeserver
|
||||||
|
|
||||||
|
withContext(coroutineDispatchers.io) {
|
||||||
|
try {
|
||||||
|
val data = getCurrentVersion()
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
|
checkAndStartWithKeysBackupVersion(data)
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version")
|
||||||
|
keysBackupStateManager.state = KeysBackupState.Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun checkAndStartKeysBackup() {
|
private suspend fun checkAndStartWithKeysBackupVersion(keyBackupVersion: KeysVersionResult?) {
|
||||||
if (!isStucked) {
|
|
||||||
// Try to start or restart the backup only if it is in unknown or bad state
|
|
||||||
Timber.w("checkAndStartKeysBackup: invalid state: $state")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
keysBackupVersion = null
|
|
||||||
keysBackupStateManager.state = KeysBackupState.CheckingBackUpOnHomeserver
|
|
||||||
|
|
||||||
getCurrentVersion(object : MatrixCallback<KeysVersionResult?> {
|
|
||||||
override fun onSuccess(data: KeysVersionResult?) {
|
|
||||||
checkAndStartWithKeysBackupVersion(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version")
|
|
||||||
keysBackupStateManager.state = KeysBackupState.Unknown
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkAndStartWithKeysBackupVersion(keyBackupVersion: KeysVersionResult?) {
|
|
||||||
Timber.v("checkAndStartWithKeyBackupVersion: ${keyBackupVersion?.version}")
|
Timber.v("checkAndStartWithKeyBackupVersion: ${keyBackupVersion?.version}")
|
||||||
|
|
||||||
keysBackupVersion = keyBackupVersion
|
keysBackupVersion = keyBackupVersion
|
||||||
|
@ -697,37 +674,34 @@ internal class RustKeyBackupService @Inject constructor(
|
||||||
resetKeysBackupData()
|
resetKeysBackupData()
|
||||||
keysBackupStateManager.state = KeysBackupState.Disabled
|
keysBackupStateManager.state = KeysBackupState.Disabled
|
||||||
} else {
|
} else {
|
||||||
getKeysBackupTrust(keyBackupVersion, object : MatrixCallback<KeysBackupVersionTrust> {
|
try {
|
||||||
override fun onSuccess(data: KeysBackupVersionTrust) {
|
val data = getKeysBackupTrust(keyBackupVersion)
|
||||||
val versionInStore = getKeyBackupRecoveryKeyInfo()?.version
|
val versionInStore = getKeyBackupRecoveryKeyInfo()?.version
|
||||||
|
|
||||||
if (data.usable) {
|
if (data.usable) {
|
||||||
Timber.v("checkAndStartWithKeysBackupVersion: Found usable key backup. version: ${keyBackupVersion.version}")
|
Timber.v("checkAndStartWithKeysBackupVersion: Found usable key backup. version: ${keyBackupVersion.version}")
|
||||||
// Check the version we used at the previous app run
|
// Check the version we used at the previous app run
|
||||||
if (versionInStore != null && versionInStore != keyBackupVersion.version) {
|
if (versionInStore != null && versionInStore != keyBackupVersion.version) {
|
||||||
Timber.v(" -> clean the previously used version $versionInStore")
|
Timber.v(" -> clean the previously used version $versionInStore")
|
||||||
resetKeysBackupData()
|
resetKeysBackupData()
|
||||||
}
|
|
||||||
|
|
||||||
Timber.v(" -> enabling key backups")
|
|
||||||
cryptoCoroutineScope.launch {
|
|
||||||
enableKeysBackup(keyBackupVersion)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Timber.v("checkAndStartWithKeysBackupVersion: No usable key backup. version: ${keyBackupVersion.version}")
|
|
||||||
if (versionInStore != null) {
|
|
||||||
Timber.v(" -> disabling key backup")
|
|
||||||
resetKeysBackupData()
|
|
||||||
}
|
|
||||||
|
|
||||||
keysBackupStateManager.state = KeysBackupState.NotTrusted
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
Timber.v(" -> enabling key backups")
|
||||||
// Cannot happen
|
cryptoCoroutineScope.launch {
|
||||||
|
enableKeysBackup(keyBackupVersion)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Timber.v("checkAndStartWithKeysBackupVersion: No usable key backup. version: ${keyBackupVersion.version}")
|
||||||
|
if (versionInStore != null) {
|
||||||
|
Timber.v(" -> disabling key backup")
|
||||||
|
resetKeysBackupData()
|
||||||
|
}
|
||||||
|
|
||||||
|
keysBackupStateManager.state = KeysBackupState.NotTrusted
|
||||||
}
|
}
|
||||||
})
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e(failure, "Failed to checkAndStartWithKeysBackupVersion $keyBackupVersion")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -737,14 +711,17 @@ internal class RustKeyBackupService @Inject constructor(
|
||||||
return authData.publicKey == publicKey
|
return authData.publicKey == publicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback<Boolean>) {
|
override suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean {
|
||||||
val keysBackupVersion = keysBackupVersion ?: return Unit.also { callback.onSuccess(false) }
|
return withContext(coroutineDispatchers.crypto) {
|
||||||
|
val keysBackupVersion = keysBackupVersion ?: return@withContext false
|
||||||
|
|
||||||
try {
|
|
||||||
val key = BackupRecoveryKey.fromBase64(recoveryKey)
|
val key = BackupRecoveryKey.fromBase64(recoveryKey)
|
||||||
callback.onSuccess(isValidRecoveryKey(key, keysBackupVersion))
|
try {
|
||||||
} catch (error: Throwable) {
|
isValidRecoveryKey(key, keysBackupVersion)
|
||||||
callback.onFailure(error)
|
} catch (failure: Throwable) {
|
||||||
|
Timber.i("isValidRecoveryKeyForCurrentVersion: Invalid recovery key")
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -822,31 +799,30 @@ internal class RustKeyBackupService @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* Do a backup if there are new keys, with a delay
|
* Do a backup if there are new keys, with a delay
|
||||||
*/
|
*/
|
||||||
fun maybeBackupKeys() {
|
suspend fun maybeBackupKeys() {
|
||||||
when {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
isStucked -> {
|
when {
|
||||||
// If not already done, or in error case, check for a valid backup version on the homeserver.
|
isStucked -> {
|
||||||
// If there is one, maybeBackupKeys will be called again.
|
// If not already done, or in error case, check for a valid backup version on the homeserver.
|
||||||
checkAndStartKeysBackup()
|
// If there is one, maybeBackupKeys will be called again.
|
||||||
}
|
checkAndStartKeysBackup()
|
||||||
state == KeysBackupState.ReadyToBackUp -> {
|
}
|
||||||
keysBackupStateManager.state = KeysBackupState.WillBackUp
|
state == KeysBackupState.ReadyToBackUp -> {
|
||||||
|
keysBackupStateManager.state = KeysBackupState.WillBackUp
|
||||||
|
|
||||||
// Wait between 0 and 10 seconds, to avoid backup requests from
|
// Wait between 0 and 10 seconds, to avoid backup requests from
|
||||||
// different clients hitting the server all at the same time when a
|
// different clients hitting the server all at the same time when a
|
||||||
// new key is sent
|
// new key is sent
|
||||||
val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS)
|
val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS)
|
||||||
|
|
||||||
cryptoCoroutineScope.launch {
|
importScope.launch {
|
||||||
delay(delayInMs)
|
delay(delayInMs)
|
||||||
// TODO is this correct? we used to call uiHandler.post() instead of this
|
tryOrNull("AUTO backup failed") { backupKeys() }
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
backupKeys()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else -> {
|
||||||
else -> {
|
Timber.v("maybeBackupKeys: Skip it because state: $state")
|
||||||
Timber.v("maybeBackupKeys: Skip it because state: $state")
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -854,84 +830,79 @@ internal class RustKeyBackupService @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* Send a chunk of keys to backup
|
* Send a chunk of keys to backup
|
||||||
*/
|
*/
|
||||||
@UiThread
|
|
||||||
private suspend fun backupKeys(forceRecheck: Boolean = false) {
|
private suspend fun backupKeys(forceRecheck: Boolean = false) {
|
||||||
Timber.v("backupKeys")
|
Timber.v("backupKeys")
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
|
// Sanity check, as this method can be called after a delay, the state may have change during the delay
|
||||||
|
if (!isEnabled || !olmMachine.backupEnabled() || keysBackupVersion == null) {
|
||||||
|
Timber.v("backupKeys: Invalid configuration $isEnabled ${olmMachine.backupEnabled()} $keysBackupVersion")
|
||||||
|
// backupAllGroupSessionsCallback?.onFailure(IllegalStateException("Invalid configuration"))
|
||||||
|
resetBackupAllGroupSessionsListeners()
|
||||||
|
|
||||||
// Sanity check, as this method can be called after a delay, the state may have change during the delay
|
return@withContext
|
||||||
if (!isEnabled || !olmMachine.backupEnabled() || keysBackupVersion == null) {
|
}
|
||||||
Timber.v("backupKeys: Invalid configuration $isEnabled ${olmMachine.backupEnabled()} $keysBackupVersion")
|
|
||||||
backupAllGroupSessionsCallback?.onFailure(IllegalStateException("Invalid configuration"))
|
|
||||||
resetBackupAllGroupSessionsListeners()
|
|
||||||
|
|
||||||
return
|
if (state === KeysBackupState.BackingUp && !forceRecheck) {
|
||||||
}
|
// Do nothing if we are already backing up
|
||||||
|
Timber.v("backupKeys: Invalid state: $state")
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
|
||||||
if (state === KeysBackupState.BackingUp && !forceRecheck) {
|
Timber.d("BACKUP: CREATING REQUEST")
|
||||||
// Do nothing if we are already backing up
|
|
||||||
Timber.v("backupKeys: Invalid state: $state")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Timber.d("BACKUP: CREATING REQUEST")
|
val request = olmMachine.backupRoomKeys()
|
||||||
|
|
||||||
val request = olmMachine.backupRoomKeys()
|
Timber.d("BACKUP: GOT REQUEST $request")
|
||||||
|
|
||||||
Timber.d("BACKUP: GOT REQUEST $request")
|
if (request == null) {
|
||||||
|
// Backup is up to date
|
||||||
|
// Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess()
|
||||||
|
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
|
||||||
|
|
||||||
if (request == null) {
|
// backupAllGroupSessionsCallback?.onSuccess(Unit)
|
||||||
// Backup is up to date
|
resetBackupAllGroupSessionsListeners()
|
||||||
// Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess()
|
} else {
|
||||||
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
|
try {
|
||||||
|
if (request is Request.KeysBackup) {
|
||||||
|
keysBackupStateManager.state = KeysBackupState.BackingUp
|
||||||
|
|
||||||
backupAllGroupSessionsCallback?.onSuccess(Unit)
|
Timber.d("BACKUP SENDING REQUEST")
|
||||||
resetBackupAllGroupSessionsListeners()
|
val response = withContext(coroutineDispatchers.io) { sender.backupRoomKeys(request) }
|
||||||
} else {
|
Timber.d("BACKUP GOT RESPONSE $response")
|
||||||
try {
|
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_BACKUP, response)
|
||||||
if (request is Request.KeysBackup) {
|
Timber.d("BACKUP MARKED REQUEST AS SENT")
|
||||||
keysBackupStateManager.state = KeysBackupState.BackingUp
|
|
||||||
|
|
||||||
Timber.d("BACKUP SENDING REQUEST")
|
|
||||||
val response = sender.backupRoomKeys(request)
|
|
||||||
Timber.d("BACKUP GOT RESPONSE $response")
|
|
||||||
olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_BACKUP, response)
|
|
||||||
Timber.d("BACKUP MARKED REQUEST AS SENT")
|
|
||||||
|
|
||||||
// TODO, again is this correct?
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
backupKeys(true)
|
backupKeys(true)
|
||||||
|
} else {
|
||||||
|
// Can't happen, do we want to panic?
|
||||||
}
|
}
|
||||||
} else {
|
} catch (failure: Throwable) {
|
||||||
// Can't happen, do we want to panic?
|
if (failure is Failure.ServerError) {
|
||||||
}
|
withContext(Dispatchers.Main) {
|
||||||
} catch (failure: Throwable) {
|
Timber.e(failure, "backupKeys: backupKeys failed.")
|
||||||
if (failure is Failure.ServerError) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
Timber.e(failure, "backupKeys: backupKeys failed.")
|
|
||||||
|
|
||||||
when (failure.error.code) {
|
when (failure.error.code) {
|
||||||
MatrixError.M_NOT_FOUND,
|
MatrixError.M_NOT_FOUND,
|
||||||
MatrixError.M_WRONG_ROOM_KEYS_VERSION -> {
|
MatrixError.M_WRONG_ROOM_KEYS_VERSION -> {
|
||||||
// Backup has been deleted on the server, or we are not using
|
// Backup has been deleted on the server, or we are not using
|
||||||
// the last backup version
|
// the last backup version
|
||||||
keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion
|
keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion
|
||||||
backupAllGroupSessionsCallback?.onFailure(failure)
|
// backupAllGroupSessionsCallback?.onFailure(failure)
|
||||||
resetBackupAllGroupSessionsListeners()
|
resetBackupAllGroupSessionsListeners()
|
||||||
resetKeysBackupData()
|
resetKeysBackupData()
|
||||||
keysBackupVersion = null
|
keysBackupVersion = null
|
||||||
|
|
||||||
// Do not stay in KeysBackupState.WrongBackUpVersion but check what
|
// Do not stay in KeysBackupState.WrongBackUpVersion but check what
|
||||||
// is available on the homeserver
|
// is available on the homeserver
|
||||||
checkAndStartKeysBackup()
|
checkAndStartKeysBackup()
|
||||||
|
}
|
||||||
|
else ->
|
||||||
|
// Come back to the ready state so that we will retry on the next received key
|
||||||
|
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
|
||||||
}
|
}
|
||||||
else ->
|
|
||||||
// Come back to the ready state so that we will retry on the next received key
|
|
||||||
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
// backupAllGroupSessionsCallback?.onFailure(failure)
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
backupAllGroupSessionsCallback?.onFailure(failure)
|
|
||||||
resetBackupAllGroupSessionsListeners()
|
resetBackupAllGroupSessionsListeners()
|
||||||
|
|
||||||
Timber.e("backupKeys: backupKeys failed: $failure")
|
Timber.e("backupKeys: backupKeys failed: $failure")
|
||||||
|
|
|
@ -83,11 +83,6 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewModel.keyVersionResult.value == null) {
|
|
||||||
// We need to fetch from API
|
|
||||||
viewModel.getLatestVersion()
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.navigateEvent.observeEvent(this) { uxStateEvent ->
|
viewModel.navigateEvent.observeEvent(this) { uxStateEvent ->
|
||||||
when (uxStateEvent) {
|
when (uxStateEvent) {
|
||||||
KeysBackupRestoreSharedViewModel.NAVIGATE_TO_RECOVER_WITH_KEY -> {
|
KeysBackupRestoreSharedViewModel.NAVIGATE_TO_RECOVER_WITH_KEY -> {
|
||||||
|
|
|
@ -23,9 +23,11 @@ import im.vector.app.R
|
||||||
import im.vector.app.core.platform.WaitingViewData
|
import im.vector.app.core.platform.WaitingViewData
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.core.utils.LiveEvent
|
import im.vector.app.core.utils.LiveEvent
|
||||||
|
import im.vector.app.features.session.coroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.listeners.StepProgressListener
|
import org.matrix.android.sdk.api.listeners.StepProgressListener
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
||||||
|
@ -35,7 +37,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey
|
import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey
|
||||||
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -75,10 +76,15 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
|
||||||
var importRoomKeysFinishWithResult: MutableLiveData<LiveEvent<ImportRoomKeysResult>> = MutableLiveData()
|
var importRoomKeysFinishWithResult: MutableLiveData<LiveEvent<ImportRoomKeysResult>> = MutableLiveData()
|
||||||
|
|
||||||
fun initSession(session: Session) {
|
fun initSession(session: Session) {
|
||||||
this.session = session
|
if (!this::session.isInitialized) {
|
||||||
|
this.session = session
|
||||||
|
viewModelScope.launch {
|
||||||
|
getLatestVersion()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val progressObserver = object : StepProgressListener {
|
private val progressObserver = object : StepProgressListener {
|
||||||
override fun onStepProgress(step: StepProgressListener.Step) {
|
override fun onStepProgress(step: StepProgressListener.Step) {
|
||||||
when (step) {
|
when (step) {
|
||||||
is StepProgressListener.Step.ComputingKey -> {
|
is StepProgressListener.Step.ComputingKey -> {
|
||||||
|
@ -106,62 +112,71 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
|
||||||
step.total))
|
step.total))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is StepProgressListener.Step.DecryptingKey -> {
|
||||||
|
if (step.progress == 0) {
|
||||||
|
loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restoring_waiting_message) +
|
||||||
|
"\n" + stringProvider.getString(R.string.keys_backup_restoring_decrypting_keys_waiting_message),
|
||||||
|
isIndeterminate = true))
|
||||||
|
} else {
|
||||||
|
loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restoring_waiting_message) +
|
||||||
|
"\n" + stringProvider.getString(R.string.keys_backup_restoring_decrypting_keys_waiting_message),
|
||||||
|
step.progress,
|
||||||
|
step.total))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLatestVersion() {
|
private suspend fun getLatestVersion() {
|
||||||
val keysBackup = session.cryptoService().keysBackupService()
|
val keysBackup = session.cryptoService().keysBackupService()
|
||||||
|
|
||||||
loadingEvent.value = WaitingViewData(stringProvider.getString(R.string.keys_backup_restore_is_getting_backup_version))
|
loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restore_is_getting_backup_version)))
|
||||||
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
try {
|
||||||
try {
|
val version = keysBackup.getCurrentVersion()
|
||||||
val version = awaitCallback<KeysVersionResult?> {
|
if (version?.version == null) {
|
||||||
keysBackup.getCurrentVersion(it)
|
loadingEvent.postValue(null)
|
||||||
}
|
_keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, "")))
|
||||||
if (version?.version == null) {
|
return
|
||||||
loadingEvent.postValue(null)
|
}
|
||||||
_keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, "")))
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
keyVersionResult.postValue(version)
|
keyVersionResult.postValue(version)
|
||||||
// Let's check if there is quads
|
// Let's check if there is quads
|
||||||
val isBackupKeyInQuadS = isBackupKeyInQuadS()
|
val isBackupKeyInQuadS = isBackupKeyInQuadS()
|
||||||
|
|
||||||
val savedSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()
|
val savedSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()
|
||||||
if (savedSecret != null && savedSecret.version == version.version) {
|
if (savedSecret != null && savedSecret.version == version.version) {
|
||||||
// key is in memory!
|
// key is in memory!
|
||||||
keySourceModel.postValue(
|
keySourceModel.postValue(
|
||||||
KeySource(isInMemory = true, isInQuadS = true)
|
KeySource(isInMemory = true, isInQuadS = true)
|
||||||
)
|
)
|
||||||
// Go and use it!!
|
// Go and use it!!
|
||||||
try {
|
try {
|
||||||
recoverUsingBackupRecoveryKey(savedSecret.recoveryKey)
|
recoverUsingBackupRecoveryKey(computeRecoveryKey(savedSecret.recoveryKey.fromBase64()), version)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
keySourceModel.postValue(
|
Timber.e(failure, "## recoverUsingBackupRecoveryKey FAILED")
|
||||||
KeySource(isInMemory = false, isInQuadS = true)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (isBackupKeyInQuadS) {
|
|
||||||
// key is in QuadS!
|
|
||||||
keySourceModel.postValue(
|
keySourceModel.postValue(
|
||||||
KeySource(isInMemory = false, isInQuadS = true)
|
KeySource(isInMemory = false, isInQuadS = true)
|
||||||
)
|
)
|
||||||
_navigateEvent.postValue(LiveEvent(NAVIGATE_TO_4S))
|
|
||||||
} else {
|
|
||||||
// we need to restore directly
|
|
||||||
keySourceModel.postValue(
|
|
||||||
KeySource(isInMemory = false, isInQuadS = false)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
} else if (isBackupKeyInQuadS) {
|
||||||
loadingEvent.postValue(null)
|
// key is in QuadS!
|
||||||
} catch (failure: Throwable) {
|
keySourceModel.postValue(
|
||||||
loadingEvent.postValue(null)
|
KeySource(isInMemory = false, isInQuadS = true)
|
||||||
_keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, failure.localizedMessage)))
|
)
|
||||||
|
_navigateEvent.postValue(LiveEvent(NAVIGATE_TO_4S))
|
||||||
|
} else {
|
||||||
|
// we need to restore directly
|
||||||
|
keySourceModel.postValue(
|
||||||
|
KeySource(isInMemory = false, isInQuadS = false)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadingEvent.postValue(null)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
loadingEvent.postValue(null)
|
||||||
|
_keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, failure.localizedMessage)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,11 +191,11 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
loadingEvent.value = WaitingViewData(stringProvider.getString(R.string.keys_backup_restore_is_getting_backup_version))
|
loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restore_is_getting_backup_version)))
|
||||||
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
recoverUsingBackupRecoveryKey(computeRecoveryKey(secret.fromBase64()))
|
recoverUsingBackupRecoveryKey(secret)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_navigateEvent.postValue(
|
_navigateEvent.postValue(
|
||||||
LiveEvent(NAVIGATE_FAILED_TO_LOAD_4S)
|
LiveEvent(NAVIGATE_FAILED_TO_LOAD_4S)
|
||||||
|
@ -202,15 +217,12 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
|
||||||
loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading)))
|
loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading)))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val result = awaitCallback<ImportRoomKeysResult> {
|
val result = keysBackup.restoreKeyBackupWithPassword(keyVersion,
|
||||||
keysBackup.restoreKeyBackupWithPassword(keyVersion,
|
passphrase,
|
||||||
passphrase,
|
null,
|
||||||
null,
|
session.myUserId,
|
||||||
session.myUserId,
|
progressObserver
|
||||||
progressObserver,
|
)
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
loadingEvent.postValue(null)
|
loadingEvent.postValue(null)
|
||||||
didRecoverSucceed(result)
|
didRecoverSucceed(result)
|
||||||
trustOnDecrypt(keysBackup, keyVersion)
|
trustOnDecrypt(keysBackup, keyVersion)
|
||||||
|
@ -220,26 +232,27 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun recoverUsingBackupRecoveryKey(recoveryKey: String) {
|
suspend fun recoverUsingBackupRecoveryKey(recoveryKey: String, keyVersion: KeysVersionResult? = null) {
|
||||||
val keysBackup = session.cryptoService().keysBackupService()
|
val keysBackup = session.cryptoService().keysBackupService()
|
||||||
val keyVersion = keyVersionResult.value ?: return
|
// This is badddddd
|
||||||
|
val version = keyVersion ?: keyVersionResult.value ?: return
|
||||||
|
|
||||||
loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading)))
|
loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading)))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val result = awaitCallback<ImportRoomKeysResult> {
|
val result = keysBackup.restoreKeysWithRecoveryKey(version,
|
||||||
keysBackup.restoreKeysWithRecoveryKey(keyVersion,
|
recoveryKey,
|
||||||
recoveryKey,
|
null,
|
||||||
null,
|
session.myUserId,
|
||||||
session.myUserId,
|
progressObserver
|
||||||
progressObserver,
|
)
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
loadingEvent.postValue(null)
|
loadingEvent.postValue(null)
|
||||||
didRecoverSucceed(result)
|
withContext(Dispatchers.Main) {
|
||||||
trustOnDecrypt(keysBackup, keyVersion)
|
didRecoverSucceed(result)
|
||||||
|
trustOnDecrypt(keysBackup, version)
|
||||||
|
}
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e(failure, "## restoreKeysWithRecoveryKey failure")
|
||||||
loadingEvent.postValue(null)
|
loadingEvent.postValue(null)
|
||||||
throw failure
|
throw failure
|
||||||
}
|
}
|
||||||
|
@ -258,19 +271,19 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun trustOnDecrypt(keysBackup: KeysBackupService, keysVersionResult: KeysVersionResult) {
|
private fun trustOnDecrypt(keysBackup: KeysBackupService, keysVersionResult: KeysVersionResult) {
|
||||||
keysBackup.trustKeysBackupVersion(keysVersionResult, true,
|
// do that on session scope because could happen outside of view model lifecycle
|
||||||
object : MatrixCallback<Unit> {
|
session.coroutineScope.launch {
|
||||||
override fun onSuccess(data: Unit) {
|
tryOrNull("## Failed to trustKeysBackupVersion") {
|
||||||
Timber.v("##### trustKeysBackupVersion onSuccess")
|
keysBackup.trustKeysBackupVersion(keysVersionResult, true)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun moveToRecoverWithKey() {
|
fun moveToRecoverWithKey() {
|
||||||
_navigateEvent.value = LiveEvent(NAVIGATE_TO_RECOVER_WITH_KEY)
|
_navigateEvent.postValue(LiveEvent(NAVIGATE_TO_RECOVER_WITH_KEY))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun didRecoverSucceed(result: ImportRoomKeysResult) {
|
private fun didRecoverSucceed(result: ImportRoomKeysResult) {
|
||||||
importKeyResult = result
|
importKeyResult = result
|
||||||
_navigateEvent.postValue(LiveEvent(NAVIGATE_TO_SUCCESS))
|
_navigateEvent.postValue(LiveEvent(NAVIGATE_TO_SUCCESS))
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,13 +27,11 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.platform.EmptyViewEvents
|
import im.vector.app.core.platform.EmptyViewEvents
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState,
|
class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState,
|
||||||
|
@ -70,7 +68,9 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun init() {
|
private fun init() {
|
||||||
keysBackupService.forceUsingLastVersion(NoOpMatrixCallback())
|
viewModelScope.launch {
|
||||||
|
keysBackupService.forceUsingLastVersion()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getKeysBackupTrust() = withState { state ->
|
private fun getKeysBackupTrust() = withState { state ->
|
||||||
|
@ -86,26 +86,24 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
|
||||||
}
|
}
|
||||||
Timber.d("BACKUP: HEEEEEEE TWO")
|
Timber.d("BACKUP: HEEEEEEE TWO")
|
||||||
|
|
||||||
keysBackupService
|
viewModelScope.launch {
|
||||||
.getKeysBackupTrust(versionResult, object : MatrixCallback<KeysBackupVersionTrust> {
|
try {
|
||||||
override fun onSuccess(data: KeysBackupVersionTrust) {
|
val data = keysBackupService.getKeysBackupTrust(versionResult)
|
||||||
Timber.d("BACKUP: HEEEE suceeeded $data")
|
Timber.d("BACKUP: HEEEE suceeeded $data")
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
keysBackupVersionTrust = Success(data)
|
keysBackupVersionTrust = Success(data)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
} catch (failure: Throwable) {
|
||||||
|
Timber.d("BACKUP: HEEEE FAILED $failure")
|
||||||
override fun onFailure(failure: Throwable) {
|
setState {
|
||||||
Timber.d("BACKUP: HEEEE FAILED $failure")
|
copy(
|
||||||
setState {
|
keysBackupVersionTrust = Fail(failure)
|
||||||
copy(
|
)
|
||||||
keysBackupVersionTrust = Fail(failure)
|
}
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,15 +126,16 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
|
||||||
private fun deleteCurrentBackup() {
|
private fun deleteCurrentBackup() {
|
||||||
val keysBackupService = keysBackupService
|
val keysBackupService = keysBackupService
|
||||||
|
|
||||||
if (keysBackupService.currentBackupVersion != null) {
|
val currentBackupVersion = keysBackupService.currentBackupVersion
|
||||||
|
if (currentBackupVersion != null) {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
deleteBackupRequest = Loading()
|
deleteBackupRequest = Loading()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
viewModelScope.launch {
|
||||||
keysBackupService.deleteBackup(keysBackupService.currentBackupVersion!!, object : MatrixCallback<Unit> {
|
try {
|
||||||
override fun onSuccess(data: Unit) {
|
keysBackupService.deleteBackup(currentBackupVersion)
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
keysBackupVersion = null,
|
keysBackupVersion = null,
|
||||||
|
@ -145,16 +144,14 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
|
||||||
deleteBackupRequest = Uninitialized
|
deleteBackupRequest = Uninitialized
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
} catch (failure: Throwable) {
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
deleteBackupRequest = Fail(failure)
|
deleteBackupRequest = Fail(failure)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,17 +19,16 @@ package im.vector.app.features.crypto.keysbackup.setup
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.nulabinc.zxcvbn.Strength
|
import com.nulabinc.zxcvbn.Strength
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.platform.WaitingViewData
|
import im.vector.app.core.platform.WaitingViewData
|
||||||
import im.vector.app.core.utils.LiveEvent
|
import im.vector.app.core.utils.LiveEvent
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
|
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -89,48 +88,30 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() {
|
||||||
|
|
||||||
recoveryKey.value = null
|
recoveryKey.value = null
|
||||||
prepareRecoverFailError.value = null
|
prepareRecoverFailError.value = null
|
||||||
session.let { mxSession ->
|
val requestedId = currentRequestId.value!!
|
||||||
val requestedId = currentRequestId.value!!
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val data = session.cryptoService().keysBackupService().prepareKeysBackupVersion(withPassphrase)
|
||||||
|
if (requestedId != currentRequestId.value) {
|
||||||
|
// this is an old request, we can't cancel but we can ignore
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
recoveryKey.postValue(data.recoveryKey)
|
||||||
|
megolmBackupCreationInfo = data
|
||||||
|
copyHasBeenMade = false
|
||||||
|
|
||||||
mxSession.cryptoService().keysBackupService().prepareKeysBackupVersion(withPassphrase,
|
val keyBackup = session.cryptoService().keysBackupService()
|
||||||
object : ProgressListener {
|
createKeysBackup(context, keyBackup)
|
||||||
override fun onProgress(progress: Int, total: Int) {
|
} catch (failure: Throwable) {
|
||||||
if (requestedId != currentRequestId.value) {
|
if (requestedId != currentRequestId.value) {
|
||||||
// this is an old request, we can't cancel but we can ignore
|
// this is an old request, we can't cancel but we can ignore
|
||||||
return
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
loadingStatus.value = WaitingViewData(context.getString(R.string.keys_backup_setup_step3_generating_key_status),
|
loadingStatus.postValue(null)
|
||||||
progress,
|
isCreatingBackupVersion.postValue(false)
|
||||||
total)
|
prepareRecoverFailError.postValue(failure)
|
||||||
}
|
}
|
||||||
},
|
|
||||||
object : MatrixCallback<MegolmBackupCreationInfo> {
|
|
||||||
override fun onSuccess(data: MegolmBackupCreationInfo) {
|
|
||||||
if (requestedId != currentRequestId.value) {
|
|
||||||
// this is an old request, we can't cancel but we can ignore
|
|
||||||
return
|
|
||||||
}
|
|
||||||
recoveryKey.value = data.recoveryKey
|
|
||||||
megolmBackupCreationInfo = data
|
|
||||||
copyHasBeenMade = false
|
|
||||||
|
|
||||||
val keyBackup = session.cryptoService().keysBackupService()
|
|
||||||
createKeysBackup(context, keyBackup)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
if (requestedId != currentRequestId.value) {
|
|
||||||
// this is an old request, we can't cancel but we can ignore
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loadingStatus.value = null
|
|
||||||
|
|
||||||
isCreatingBackupVersion.value = false
|
|
||||||
prepareRecoverFailError.value = failure
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,9 +121,11 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopAndKeepAfterDetectingExistingOnServer() {
|
fun stopAndKeepAfterDetectingExistingOnServer() {
|
||||||
loadingStatus.value = null
|
loadingStatus.postValue(null)
|
||||||
navigateEvent.value = LiveEvent(NAVIGATE_FINISH)
|
navigateEvent.postValue(LiveEvent(NAVIGATE_FINISH))
|
||||||
session.cryptoService().keysBackupService().checkAndStartKeysBackup()
|
viewModelScope.launch {
|
||||||
|
session.cryptoService().keysBackupService().checkAndStartKeysBackup()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createKeysBackup(context: Context, keysBackup: KeysBackupService, forceOverride: Boolean = false) {
|
private fun createKeysBackup(context: Context, keysBackup: KeysBackupService, forceOverride: Boolean = false) {
|
||||||
|
@ -150,45 +133,35 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() {
|
||||||
|
|
||||||
creatingBackupError.value = null
|
creatingBackupError.value = null
|
||||||
|
|
||||||
keysBackup.getCurrentVersion(object : MatrixCallback<KeysVersionResult?> {
|
viewModelScope.launch {
|
||||||
override fun onSuccess(data: KeysVersionResult?) {
|
try {
|
||||||
|
val data = keysBackup.getCurrentVersion()
|
||||||
if (data?.version.isNullOrBlank() || forceOverride) {
|
if (data?.version.isNullOrBlank() || forceOverride) {
|
||||||
processOnCreate()
|
processOnCreate(keysBackup)
|
||||||
} else {
|
} else {
|
||||||
loadingStatus.value = null
|
loadingStatus.postValue(null)
|
||||||
// we should prompt
|
// we should prompt
|
||||||
isCreatingBackupVersion.value = false
|
isCreatingBackupVersion.postValue(false)
|
||||||
navigateEvent.value = LiveEvent(NAVIGATE_PROMPT_REPLACE)
|
navigateEvent.postValue(LiveEvent(NAVIGATE_PROMPT_REPLACE))
|
||||||
}
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
suspend fun processOnCreate(keysBackup: KeysBackupService) {
|
||||||
Timber.e(failure, "## createKeyBackupVersion")
|
try {
|
||||||
loadingStatus.value = null
|
loadingStatus.postValue(null)
|
||||||
|
val created = keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!)
|
||||||
|
isCreatingBackupVersion.postValue(false)
|
||||||
|
keysVersion.postValue(created)
|
||||||
|
navigateEvent.value = LiveEvent(NAVIGATE_TO_STEP_3)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e(failure, "## createKeyBackupVersion")
|
||||||
|
loadingStatus.postValue(null)
|
||||||
|
|
||||||
isCreatingBackupVersion.value = false
|
isCreatingBackupVersion.postValue(false)
|
||||||
creatingBackupError.value = failure
|
creatingBackupError.postValue(failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun processOnCreate() {
|
|
||||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!, object : MatrixCallback<KeysVersion> {
|
|
||||||
override fun onSuccess(data: KeysVersion) {
|
|
||||||
loadingStatus.value = null
|
|
||||||
|
|
||||||
isCreatingBackupVersion.value = false
|
|
||||||
keysVersion.value = data
|
|
||||||
navigateEvent.value = LiveEvent(NAVIGATE_TO_STEP_3)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
Timber.e(failure, "## createKeyBackupVersion")
|
|
||||||
loadingStatus.value = null
|
|
||||||
|
|
||||||
isCreatingBackupVersion.value = false
|
|
||||||
creatingBackupError.value = failure
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,9 @@ import im.vector.app.R
|
||||||
import im.vector.app.core.platform.ViewModelTask
|
import im.vector.app.core.platform.ViewModelTask
|
||||||
import im.vector.app.core.platform.WaitingViewData
|
import im.vector.app.core.platform.WaitingViewData
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
import im.vector.app.features.session.coroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
||||||
|
@ -32,7 +34,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.deriveKey
|
import org.matrix.android.sdk.internal.crypto.keysbackup.deriveKey
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey
|
import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
|
import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -87,9 +88,7 @@ class BackupToQuadSMigrationTask @Inject constructor(
|
||||||
reportProgress(params, R.string.bootstrap_progress_compute_curve_key)
|
reportProgress(params, R.string.bootstrap_progress_compute_curve_key)
|
||||||
val recoveryKey = computeRecoveryKey(curveKey)
|
val recoveryKey = computeRecoveryKey(curveKey)
|
||||||
|
|
||||||
val isValid = awaitCallback<Boolean> {
|
val isValid = keysBackupService.isValidRecoveryKeyForCurrentVersion(recoveryKey)
|
||||||
keysBackupService.isValidRecoveryKeyForCurrentVersion(recoveryKey, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isValid) return Result.InvalidRecoverySecret
|
if (!isValid) return Result.InvalidRecoverySecret
|
||||||
|
|
||||||
|
@ -141,14 +140,17 @@ class BackupToQuadSMigrationTask @Inject constructor(
|
||||||
keysBackupService.saveBackupRecoveryKey(recoveryKey, version.version)
|
keysBackupService.saveBackupRecoveryKey(recoveryKey, version.version)
|
||||||
|
|
||||||
// while we are there let's restore, but do not block
|
// while we are there let's restore, but do not block
|
||||||
session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
|
session.coroutineScope.launch {
|
||||||
version,
|
tryOrNull {
|
||||||
recoveryKey,
|
session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
|
||||||
null,
|
version,
|
||||||
null,
|
recoveryKey,
|
||||||
null,
|
null,
|
||||||
NoOpMatrixCallback()
|
null,
|
||||||
)
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Result.Success
|
return Result.Success
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
|
|
|
@ -33,9 +33,6 @@ import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageServi
|
||||||
import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo
|
import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo
|
||||||
import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec
|
import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
|
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
|
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
|
import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -221,9 +218,7 @@ class BootstrapCrossSigningTask @Inject constructor(
|
||||||
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Checking megolm backup")
|
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Checking megolm backup")
|
||||||
|
|
||||||
// First ensure that in sync
|
// First ensure that in sync
|
||||||
var serverVersion = awaitCallback<KeysVersionResult?> {
|
var serverVersion = session.cryptoService().keysBackupService().getCurrentVersion()
|
||||||
session.cryptoService().keysBackupService().getCurrentVersion(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
val knownMegolmSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()
|
val knownMegolmSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()
|
||||||
val isMegolmBackupSecretKnown = knownMegolmSecret != null && knownMegolmSecret.version == serverVersion?.version
|
val isMegolmBackupSecretKnown = knownMegolmSecret != null && knownMegolmSecret.version == serverVersion?.version
|
||||||
|
@ -233,21 +228,14 @@ class BootstrapCrossSigningTask @Inject constructor(
|
||||||
if (shouldCreateKeyBackup) {
|
if (shouldCreateKeyBackup) {
|
||||||
// clear all existing backups
|
// clear all existing backups
|
||||||
while (serverVersion != null) {
|
while (serverVersion != null) {
|
||||||
awaitCallback<Unit> {
|
session.cryptoService().keysBackupService().deleteBackup(serverVersion.version)
|
||||||
session.cryptoService().keysBackupService().deleteBackup(serverVersion!!.version, it)
|
serverVersion = session.cryptoService().keysBackupService().getCurrentVersion()
|
||||||
}
|
|
||||||
serverVersion = awaitCallback {
|
|
||||||
session.cryptoService().keysBackupService().getCurrentVersion(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Create megolm backup")
|
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Create megolm backup")
|
||||||
val creationInfo = awaitCallback<MegolmBackupCreationInfo> {
|
val creationInfo = session.cryptoService().keysBackupService().prepareKeysBackupVersion(null)
|
||||||
session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
|
val version = session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo)
|
||||||
}
|
|
||||||
val version = awaitCallback<KeysVersion> {
|
|
||||||
session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
|
|
||||||
}
|
|
||||||
// Save it for gossiping
|
// Save it for gossiping
|
||||||
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Save megolm backup key for gossiping")
|
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Save megolm backup key for gossiping")
|
||||||
session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
|
session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
|
||||||
|
@ -264,12 +252,10 @@ class BootstrapCrossSigningTask @Inject constructor(
|
||||||
// ensure we store existing backup secret if we have it!
|
// ensure we store existing backup secret if we have it!
|
||||||
if (isMegolmBackupSecretKnown) {
|
if (isMegolmBackupSecretKnown) {
|
||||||
// check it matches
|
// check it matches
|
||||||
val isValid = awaitCallback<Boolean> {
|
val isValid = session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownMegolmSecret!!.recoveryKey)
|
||||||
session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownMegolmSecret!!.recoveryKey, it)
|
|
||||||
}
|
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key valid and known")
|
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key valid and known")
|
||||||
extractCurveKeyFromRecoveryKey(knownMegolmSecret!!.recoveryKey)?.toBase64NoPadding()?.let { secret ->
|
extractCurveKeyFromRecoveryKey(knownMegolmSecret.recoveryKey)?.toBase64NoPadding()?.let { secret ->
|
||||||
ssssService.storeSecret(
|
ssssService.storeSecret(
|
||||||
KEYBACKUP_SECRET_SSSS_NAME,
|
KEYBACKUP_SECRET_SSSS_NAME,
|
||||||
secret,
|
secret,
|
||||||
|
|
|
@ -42,14 +42,13 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||||
import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage
|
import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec
|
import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
|
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
|
import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth
|
import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import kotlin.coroutines.Continuation
|
import kotlin.coroutines.Continuation
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
@ -104,9 +103,8 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
// We need to check if there is an existing backup
|
// We need to check if there is an existing backup
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val version = awaitCallback<KeysVersionResult?> {
|
val version = tryOrNull { session.cryptoService().keysBackupService().getCurrentVersion() }
|
||||||
session.cryptoService().keysBackupService().getCurrentVersion(it)
|
|
||||||
}
|
|
||||||
if (version == null) {
|
if (version == null) {
|
||||||
// we just resume plain bootstrap
|
// we just resume plain bootstrap
|
||||||
doesKeyBackupExist = false
|
doesKeyBackupExist = false
|
||||||
|
@ -115,8 +113,8 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// we need to get existing backup passphrase/key and convert to SSSS
|
// we need to get existing backup passphrase/key and convert to SSSS
|
||||||
val keyVersion = awaitCallback<KeysVersionResult?> {
|
val keyVersion = tryOrNull {
|
||||||
session.cryptoService().keysBackupService().getVersion(version.version, it)
|
session.cryptoService().keysBackupService().getVersion(version.version)
|
||||||
}
|
}
|
||||||
if (keyVersion == null) {
|
if (keyVersion == null) {
|
||||||
// strange case... just finish?
|
// strange case... just finish?
|
||||||
|
|
|
@ -31,8 +31,10 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.extensions.exhaustive
|
import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.features.session.coroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
|
||||||
|
@ -51,10 +53,7 @@ import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
|
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
|
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey
|
import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey
|
||||||
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
data class VerificationBottomSheetViewState(
|
data class VerificationBottomSheetViewState(
|
||||||
|
@ -419,30 +418,27 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tentativeRestoreBackup(res: Map<String, String>?) {
|
private fun tentativeRestoreBackup(res: Map<String, String>?) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
// on session scope because will happen after viewmodel is cleared
|
||||||
|
session.coroutineScope.launch {
|
||||||
try {
|
try {
|
||||||
val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) ?: return@launch Unit.also {
|
val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) ?: return@launch Unit.also {
|
||||||
Timber.v("## Keybackup secret not restored from SSSS")
|
Timber.v("## Keybackup secret not restored from SSSS")
|
||||||
}
|
}
|
||||||
|
|
||||||
val version = awaitCallback<KeysVersionResult?> {
|
val version = tryOrNull { session.cryptoService().keysBackupService().getCurrentVersion() }
|
||||||
session.cryptoService().keysBackupService().getCurrentVersion(it)
|
?: return@launch
|
||||||
} ?: return@launch
|
|
||||||
|
|
||||||
awaitCallback<ImportRoomKeysResult> {
|
// TODO maybe mark as trusted earlier by checking recovery key early, then download?
|
||||||
session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
|
|
||||||
version,
|
|
||||||
computeRecoveryKey(secret.fromBase64()),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
awaitCallback<Unit> {
|
session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
|
||||||
session.cryptoService().keysBackupService().trustKeysBackupVersion(version, true, it)
|
version,
|
||||||
}
|
computeRecoveryKey(secret.fromBase64()),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
|
||||||
|
session.cryptoService().keysBackupService().trustKeysBackupVersion(version, true)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
// Just ignore for now
|
// Just ignore for now
|
||||||
Timber.e(failure, "## Failed to restore backup after SSSS recovery")
|
Timber.e(failure, "## Failed to restore backup after SSSS recovery")
|
||||||
|
|
|
@ -155,7 +155,9 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS
|
||||||
|
|
||||||
fun refreshRemoteStateIfNeeded() {
|
fun refreshRemoteStateIfNeeded() {
|
||||||
if (keysBackupState.value == KeysBackupState.Disabled) {
|
if (keysBackupState.value == KeysBackupState.Disabled) {
|
||||||
session.cryptoService().keysBackupService().checkAndStartKeysBackup()
|
viewModelScope.launch {
|
||||||
|
session.cryptoService().keysBackupService().checkAndStartKeysBackup()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,9 @@ class SignoutCheckViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
init {
|
init {
|
||||||
session.cryptoService().keysBackupService().addListener(this)
|
session.cryptoService().keysBackupService().addListener(this)
|
||||||
session.cryptoService().keysBackupService().checkAndStartKeysBackup()
|
viewModelScope.launch {
|
||||||
|
session.cryptoService().keysBackupService().checkAndStartKeysBackup()
|
||||||
|
}
|
||||||
|
|
||||||
val quad4SIsSetup = session.sharedSecretStorageService.isRecoverySetup()
|
val quad4SIsSetup = session.sharedSecretStorageService.isRecoverySetup()
|
||||||
val allKeysKnown = session.cryptoService().crossSigningService().allPrivateKeysKnown()
|
val allKeysKnown = session.cryptoService().crossSigningService().allPrivateKeysKnown()
|
||||||
|
@ -112,7 +114,9 @@ class SignoutCheckViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
fun refreshRemoteStateIfNeeded() = withState { state ->
|
fun refreshRemoteStateIfNeeded() = withState { state ->
|
||||||
if (state.keysBackupState == KeysBackupState.Disabled) {
|
if (state.keysBackupState == KeysBackupState.Disabled) {
|
||||||
session.cryptoService().keysBackupService().checkAndStartKeysBackup()
|
viewModelScope.launch {
|
||||||
|
session.cryptoService().keysBackupService().checkAndStartKeysBackup()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2041,6 +2041,7 @@
|
||||||
<string name="keys_backup_restoring_computing_key_waiting_message">Computing recovery key…</string>
|
<string name="keys_backup_restoring_computing_key_waiting_message">Computing recovery key…</string>
|
||||||
<string name="keys_backup_restoring_downloading_backup_waiting_message">Downloading keys…</string>
|
<string name="keys_backup_restoring_downloading_backup_waiting_message">Downloading keys…</string>
|
||||||
<string name="keys_backup_restoring_importing_keys_waiting_message">Importing keys…</string>
|
<string name="keys_backup_restoring_importing_keys_waiting_message">Importing keys…</string>
|
||||||
|
<string name="keys_backup_restoring_decrypting_keys_waiting_message">Decrypting keys…</string>
|
||||||
<string name="keys_backup_unlock_button">Unlock History</string>
|
<string name="keys_backup_unlock_button">Unlock History</string>
|
||||||
<string name="keys_backup_recovery_code_empty_error_message">Please enter a recovery key</string>
|
<string name="keys_backup_recovery_code_empty_error_message">Please enter a recovery key</string>
|
||||||
<string name="keys_backup_recovery_code_error_decrypt">Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key.</string>
|
<string name="keys_backup_recovery_code_error_decrypt">Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key.</string>
|
||||||
|
@ -3442,6 +3443,7 @@
|
||||||
<string name="command_description_join_space">Join the Space with the given id</string>
|
<string name="command_description_join_space">Join the Space with the given id</string>
|
||||||
<string name="command_description_leave_room">Leave room with given id (or current room if null)</string>
|
<string name="command_description_leave_room">Leave room with given id (or current room if null)</string>
|
||||||
<string name="command_description_upgrade_room">Upgrades a room to a new version</string>
|
<string name="command_description_upgrade_room">Upgrades a room to a new version</string>
|
||||||
|
<string name="command_description_gen_keys">Gen keys</string>
|
||||||
|
|
||||||
<string name="event_status_a11y_sending">Sending</string>
|
<string name="event_status_a11y_sending">Sending</string>
|
||||||
<string name="event_status_a11y_sent">Sent</string>
|
<string name="event_status_a11y_sent">Sent</string>
|
||||||
|
|
Loading…
Reference in a new issue