1
0
mirror of https://github.com/vector-im/riotX-android synced 2025-10-05 15:52:47 +02:00

Ensure that database access are done using coroutineDispatchers.crypto

This commit is contained in:
Benoit Marty
2023-06-14 15:49:27 +02:00
parent 0b09a51bb2
commit 53469eab2b

View File

@@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
import android.os.Handler
import android.os.Looper
import androidx.annotation.UiThread
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import kotlinx.coroutines.CoroutineScope
@@ -238,45 +237,49 @@ internal class DefaultKeysBackupService @Inject constructor(
keysBackupCreationInfo: MegolmBackupCreationInfo,
callback: MatrixCallback<KeysVersion>
) {
@Suppress("UNCHECKED_CAST")
val createKeysBackupVersionBody = CreateKeysBackupVersionBody(
algorithm = keysBackupCreationInfo.algorithm,
authData = keysBackupCreationInfo.authData.toJsonDict()
)
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
@Suppress("UNCHECKED_CAST")
val createKeysBackupVersionBody = CreateKeysBackupVersionBody(
algorithm = keysBackupCreationInfo.algorithm,
authData = keysBackupCreationInfo.authData.toJsonDict()
)
keysBackupStateManager.state = KeysBackupState.Enabling
keysBackupStateManager.state = KeysBackupState.Enabling
createKeysBackupVersionTask
.configureWith(createKeysBackupVersionBody) {
this.callback = object : MatrixCallback<KeysVersion> {
override fun onSuccess(data: KeysVersion) {
// Reset backup markers.
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
// move tx out of UI thread
createKeysBackupVersionTask
.configureWith(createKeysBackupVersionBody) {
this.callbackThread = TaskThread.CRYPTO
this.callback = object : MatrixCallback<KeysVersion> {
override fun onSuccess(data: KeysVersion) {
// Reset backup markers.
cryptoStore.resetBackupMarkers()
val keyBackupVersion = KeysVersionResult(
algorithm = createKeysBackupVersionBody.algorithm,
authData = createKeysBackupVersionBody.authData,
version = data.version,
// We can consider that the server does not have keys yet
count = 0,
hash = ""
)
enableKeysBackup(keyBackupVersion)
uiHandler.post {
callback.onSuccess(data)
}
}
val keyBackupVersion = KeysVersionResult(
algorithm = createKeysBackupVersionBody.algorithm,
authData = createKeysBackupVersionBody.authData,
version = data.version,
// We can consider that the server does not have keys yet
count = 0,
hash = ""
)
enableKeysBackup(keyBackupVersion)
callback.onSuccess(data)
}
override fun onFailure(failure: Throwable) {
keysBackupStateManager.state = KeysBackupState.Disabled
callback.onFailure(failure)
override fun onFailure(failure: Throwable) {
keysBackupStateManager.state = KeysBackupState.Disabled
uiHandler.post {
callback.onFailure(failure)
}
}
}
}
}
.executeBy(taskExecutor)
.executeBy(taskExecutor)
}
}
override fun deleteBackup(version: String, callback: MatrixCallback<Unit>?) {
@@ -291,6 +294,7 @@ internal class DefaultKeysBackupService @Inject constructor(
deleteBackupTask
.configureWith(DeleteBackupTask.Params(version)) {
this.callbackThread = TaskThread.CRYPTO
this.callback = object : MatrixCallback<Unit> {
private fun eventuallyRestartBackup() {
// Do not stay in KeysBackupState.Unknown but check what is available on the homeserver
@@ -518,17 +522,16 @@ internal class DefaultKeysBackupService @Inject constructor(
callback: MatrixCallback<Unit>
) {
Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}")
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
// Get auth data to update it
val authData = getMegolmBackupAuthData(keysBackupVersion)
// Get auth data to update it
val authData = getMegolmBackupAuthData(keysBackupVersion)
if (authData == null) {
Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data")
uiHandler.post {
callback.onFailure(IllegalArgumentException("Missing element"))
}
} else {
cryptoCoroutineScope.launch(coroutineDispatchers.io) {
if (authData == null) {
Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data")
uiHandler.post {
callback.onFailure(IllegalArgumentException("Missing element"))
}
} else {
val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) {
// Get current signatures, or create an empty set
val myUserSignatures = authData.signatures?.get(userId).orEmpty().toMutableMap()
@@ -568,6 +571,7 @@ internal class DefaultKeysBackupService @Inject constructor(
// And send it to the homeserver
updateKeysBackupVersionTask
.configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version, updateKeysBackupVersionBody)) {
this.callbackThread = TaskThread.CRYPTO
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
// Relaunch the state machine on this updated backup version
@@ -604,7 +608,6 @@ internal class DefaultKeysBackupService @Inject constructor(
callback: MatrixCallback<Unit>
) {
Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}")
cryptoCoroutineScope.launch(coroutineDispatchers.io) {
val isValid = isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)
@@ -988,37 +991,39 @@ internal class DefaultKeysBackupService @Inject constructor(
override fun forceUsingLastVersion(callback: MatrixCallback<Boolean>) {
getCurrentVersion(object : MatrixCallback<KeysBackupLastVersionResult> {
override fun onSuccess(data: KeysBackupLastVersionResult) {
val localBackupVersion = keysBackupVersion?.version
when (data) {
KeysBackupLastVersionResult.NoKeysBackup -> {
if (localBackupVersion == null) {
// No backup on the server, and backup is not active
callback.onSuccess(true)
} else {
// No backup on the server, and we are currently backing up, so stop backing up
callback.onSuccess(false)
resetKeysBackupData()
keysBackupVersion = null
keysBackupStateManager.state = KeysBackupState.Disabled
}
}
is KeysBackupLastVersionResult.KeysBackup -> {
if (localBackupVersion == null) {
// backup on the server, and backup is not active
callback.onSuccess(false)
// Do a check
checkAndStartWithKeysBackupVersion(data.keysVersionResult)
} else {
// Backup on the server, and we are currently backing up, compare version
if (localBackupVersion == data.keysVersionResult.version) {
// We are already using the last version of the backup
callback.onSuccess(true)
cryptoCoroutineScope.launch(coroutineDispatchers.io) {
val localBackupVersion = keysBackupVersion?.version
when (data) {
KeysBackupLastVersionResult.NoKeysBackup -> {
if (localBackupVersion == null) {
// No backup on the server, and backup is not active
uiHandler.post { callback.onSuccess(true) }
} else {
// We are not using the last version, so delete the current version we are using on the server
callback.onSuccess(false)
// No backup on the server, and we are currently backing up, so stop backing up
uiHandler.post { callback.onSuccess(false) }
resetKeysBackupData()
keysBackupVersion = null
keysBackupStateManager.state = KeysBackupState.Disabled
}
}
is KeysBackupLastVersionResult.KeysBackup -> {
if (localBackupVersion == null) {
// backup on the server, and backup is not active
uiHandler.post { callback.onSuccess(false) }
// Do a check
checkAndStartWithKeysBackupVersion(data.keysVersionResult)
} else {
// Backup on the server, and we are currently backing up, compare version
if (localBackupVersion == data.keysVersionResult.version) {
// We are already using the last version of the backup
uiHandler.post { callback.onSuccess(true) }
} else {
// We are not using the last version, so delete the current version we are using on the server
uiHandler.post { callback.onSuccess(false) }
// This will automatically check for the last version then
deleteBackup(localBackupVersion, null)
// This will automatically check for the last version then
deleteBackup(localBackupVersion, null)
}
}
}
}
@@ -1057,48 +1062,52 @@ internal class DefaultKeysBackupService @Inject constructor(
private fun checkAndStartWithKeysBackupVersion(keyBackupVersion: KeysVersionResult?) {
Timber.v("checkAndStartWithKeyBackupVersion: ${keyBackupVersion?.version}")
keysBackupVersion = keyBackupVersion
cryptoCoroutineScope.launch(coroutineDispatchers.io) {
keysBackupVersion = keyBackupVersion
if (keyBackupVersion == null) {
Timber.v("checkAndStartWithKeysBackupVersion: Found no key backup version on the homeserver")
resetKeysBackupData()
keysBackupStateManager.state = KeysBackupState.Disabled
} else {
getKeysBackupTrust(keyBackupVersion, object : MatrixCallback<KeysBackupVersionTrust> {
override fun onSuccess(data: KeysBackupVersionTrust) {
val versionInStore = cryptoStore.getKeyBackupVersion()
if (keyBackupVersion == null) {
Timber.v("checkAndStartWithKeysBackupVersion: Found no key backup version on the homeserver")
resetKeysBackupData()
keysBackupStateManager.state = KeysBackupState.Disabled
} else {
getKeysBackupTrust(keyBackupVersion, object : MatrixCallback<KeysBackupVersionTrust> {
override fun onSuccess(data: KeysBackupVersionTrust) {
cryptoCoroutineScope.launch(coroutineDispatchers.io) {
val versionInStore = cryptoStore.getKeyBackupVersion()
if (data.usable) {
Timber.v("checkAndStartWithKeysBackupVersion: Found usable key backup. version: ${keyBackupVersion.version}")
// Check the version we used at the previous app run
if (versionInStore != null && versionInStore != keyBackupVersion.version) {
Timber.v(" -> clean the previously used version $versionInStore")
resetKeysBackupData()
if (data.usable) {
Timber.v("checkAndStartWithKeysBackupVersion: Found usable key backup. version: ${keyBackupVersion.version}")
// Check the version we used at the previous app run
if (versionInStore != null && versionInStore != keyBackupVersion.version) {
Timber.v(" -> clean the previously used version $versionInStore")
resetKeysBackupData()
}
Timber.v(" -> enabling key backups")
enableKeysBackup(keyBackupVersion)
} else {
Timber.v("checkAndStartWithKeysBackupVersion: No usable key backup. version: ${keyBackupVersion.version}")
if (versionInStore != null) {
Timber.v(" -> disabling key backup")
resetKeysBackupData()
}
keysBackupStateManager.state = KeysBackupState.NotTrusted
}
}
Timber.v(" -> enabling key backups")
enableKeysBackup(keyBackupVersion)
} else {
Timber.v("checkAndStartWithKeysBackupVersion: No usable key backup. version: ${keyBackupVersion.version}")
if (versionInStore != null) {
Timber.v(" -> disabling key backup")
resetKeysBackupData()
}
keysBackupStateManager.state = KeysBackupState.NotTrusted
}
}
override fun onFailure(failure: Throwable) {
// Cannot happen
}
})
override fun onFailure(failure: Throwable) {
// Cannot happen
}
})
}
}
}
/* ==========================================================================================
* Private
* ========================================================================================== */
/* ==========================================================================================
* Private
* ========================================================================================== */
/**
* Extract MegolmBackupAuthData data from a backup version.
@@ -1205,6 +1214,7 @@ internal class DefaultKeysBackupService @Inject constructor(
/**
* Enable backing up of keys.
* This method will update the state and will start sending keys in nominal case
* Must be called on the crypto dispatcher.
*
* @param keysVersionResult backup information object as returned by [getCurrentVersion].
*/
@@ -1213,9 +1223,7 @@ internal class DefaultKeysBackupService @Inject constructor(
if (retrievedMegolmBackupAuthData != null) {
keysBackupVersion = keysVersionResult
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
cryptoStore.setKeyBackupVersion(keysVersionResult.version)
}
cryptoStore.setKeyBackupVersion(keysVersionResult.version)
onServerDataRetrieved(keysVersionResult.count, keysVersionResult.hash)
@@ -1270,136 +1278,135 @@ internal class DefaultKeysBackupService @Inject constructor(
/**
* Send a chunk of keys to backup.
*/
@UiThread
private fun backupKeys() {
Timber.v("backupKeys")
// Sanity check, as this method can be called after a delay, the state may have change during the delay
if (!isEnabled() || backupOlmPkEncryption == null || keysBackupVersion == null) {
Timber.v("backupKeys: Invalid configuration")
backupAllGroupSessionsCallback?.onFailure(IllegalStateException("Invalid configuration"))
resetBackupAllGroupSessionsListeners()
return
}
if (getState() === KeysBackupState.BackingUp) {
// Do nothing if we are already backing up
Timber.v("backupKeys: Invalid state: ${getState()}")
return
}
// Get a chunk of keys to backup
val olmInboundGroupSessionWrappers = cryptoStore.inboundGroupSessionsToBackup(KEY_BACKUP_SEND_KEYS_MAX_COUNT)
Timber.v("backupKeys: 1 - ${olmInboundGroupSessionWrappers.size} sessions to back up")
if (olmInboundGroupSessionWrappers.isEmpty()) {
// Backup is up to date
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
backupAllGroupSessionsCallback?.onSuccess(Unit)
resetBackupAllGroupSessionsListeners()
return
}
keysBackupStateManager.state = KeysBackupState.BackingUp
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
withContext(coroutineDispatchers.crypto) {
Timber.v("backupKeys: 2 - Encrypting keys")
// Gather data to send to the homeserver
// roomId -> sessionId -> MXKeyBackupData
val keysBackupData = KeysBackupData()
olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper ->
val roomId = olmInboundGroupSessionWrapper.roomId ?: return@forEach
val olmInboundGroupSession = olmInboundGroupSessionWrapper.session
try {
encryptGroupSession(olmInboundGroupSessionWrapper)
?.let {
keysBackupData.roomIdToRoomKeysBackupData
.getOrPut(roomId) { RoomKeysBackupData() }
.sessionIdToKeyBackupData[olmInboundGroupSession.sessionIdentifier()] = it
}
} catch (e: OlmException) {
Timber.e(e, "OlmException")
}
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
// Sanity check, as this method can be called after a delay, the state may have change during the delay
if (!isEnabled() || backupOlmPkEncryption == null || keysBackupVersion == null) {
Timber.v("backupKeys: Invalid configuration")
uiHandler.post {
backupAllGroupSessionsCallback?.onFailure(IllegalStateException("Invalid configuration"))
resetBackupAllGroupSessionsListeners()
}
return@launch
}
Timber.v("backupKeys: 4 - Sending request")
if (getState() === KeysBackupState.BackingUp) {
// Do nothing if we are already backing up
Timber.v("backupKeys: Invalid state: ${getState()}")
return@launch
}
// Make the request
val version = keysBackupVersion?.version ?: return@withContext
// Get a chunk of keys to backup
val olmInboundGroupSessionWrappers = cryptoStore.inboundGroupSessionsToBackup(KEY_BACKUP_SEND_KEYS_MAX_COUNT)
storeSessionDataTask
.configureWith(StoreSessionsDataTask.Params(version, keysBackupData)) {
this.callback = object : MatrixCallback<BackupKeysResult> {
override fun onSuccess(data: BackupKeysResult) {
uiHandler.post {
Timber.v("backupKeys: 5a - Request complete")
Timber.v("backupKeys: 1 - ${olmInboundGroupSessionWrappers.size} sessions to back up")
// Mark keys as backed up
cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers)
// we can release the sessions now
olmInboundGroupSessionWrappers.onEach { it.session.releaseSession() }
if (olmInboundGroupSessionWrappers.isEmpty()) {
// Backup is up to date
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) {
Timber.v("backupKeys: All keys have been backed up")
onServerDataRetrieved(data.count, data.hash)
uiHandler.post {
backupAllGroupSessionsCallback?.onSuccess(Unit)
resetBackupAllGroupSessionsListeners()
}
return@launch
}
// Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess()
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
} else {
Timber.v("backupKeys: Continue to back up keys")
keysBackupStateManager.state = KeysBackupState.WillBackUp
keysBackupStateManager.state = KeysBackupState.BackingUp
backupKeys()
}
}
Timber.v("backupKeys: 2 - Encrypting keys")
// Gather data to send to the homeserver
// roomId -> sessionId -> MXKeyBackupData
val keysBackupData = KeysBackupData()
olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper ->
val roomId = olmInboundGroupSessionWrapper.roomId ?: return@forEach
val olmInboundGroupSession = olmInboundGroupSessionWrapper.session
try {
encryptGroupSession(olmInboundGroupSessionWrapper)
?.let {
keysBackupData.roomIdToRoomKeysBackupData
.getOrPut(roomId) { RoomKeysBackupData() }
.sessionIdToKeyBackupData[olmInboundGroupSession.sessionIdentifier()] = it
}
} catch (e: OlmException) {
Timber.e(e, "OlmException")
}
}
Timber.v("backupKeys: 4 - Sending request")
// Make the request
val version = keysBackupVersion?.version ?: return@launch
storeSessionDataTask
.configureWith(StoreSessionsDataTask.Params(version, keysBackupData)) {
this.callbackThread = TaskThread.CRYPTO
this.callback = object : MatrixCallback<BackupKeysResult> {
override fun onSuccess(data: BackupKeysResult) {
Timber.v("backupKeys: 5a - Request complete")
// Mark keys as backed up
cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers)
// we can release the sessions now
olmInboundGroupSessionWrappers.onEach { it.session.releaseSession() }
if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) {
Timber.v("backupKeys: All keys have been backed up")
onServerDataRetrieved(data.count, data.hash)
// Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess()
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
} else {
Timber.v("backupKeys: Continue to back up keys")
keysBackupStateManager.state = KeysBackupState.WillBackUp
backupKeys()
}
}
override fun onFailure(failure: Throwable) {
if (failure is Failure.ServerError) {
uiHandler.post {
Timber.e(failure, "backupKeys: backupKeys failed.")
override fun onFailure(failure: Throwable) {
if (failure is Failure.ServerError) {
Timber.e(failure, "backupKeys: backupKeys failed.")
when (failure.error.code) {
MatrixError.M_NOT_FOUND,
MatrixError.M_WRONG_ROOM_KEYS_VERSION -> {
// Backup has been deleted on the server, or we are not using the last backup version
keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion
backupAllGroupSessionsCallback?.onFailure(failure)
resetBackupAllGroupSessionsListeners()
resetKeysBackupData()
keysBackupVersion = null
// Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver
checkAndStartKeysBackup()
}
else ->
// Come back to the ready state so that we will retry on the next received key
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
when (failure.error.code) {
MatrixError.M_NOT_FOUND,
MatrixError.M_WRONG_ROOM_KEYS_VERSION -> {
// Backup has been deleted on the server, or we are not using the last backup version
keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion
uiHandler.post {
backupAllGroupSessionsCallback?.onFailure(failure)
resetBackupAllGroupSessionsListeners()
}
resetKeysBackupData()
keysBackupVersion = null
// Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver
checkAndStartKeysBackup()
}
} else {
uiHandler.post {
backupAllGroupSessionsCallback?.onFailure(failure)
resetBackupAllGroupSessionsListeners()
Timber.e("backupKeys: backupKeys failed.")
// Retry a bit later
else ->
// Come back to the ready state so that we will retry on the next received key
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
maybeBackupKeys()
}
}
} else {
uiHandler.post {
backupAllGroupSessionsCallback?.onFailure(failure)
resetBackupAllGroupSessionsListeners()
}
Timber.e("backupKeys: backupKeys failed.")
// Retry a bit later
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
maybeBackupKeys()
}
}
}
.executeBy(taskExecutor)
}
}
.executeBy(taskExecutor)
}
}
@@ -1553,9 +1560,9 @@ internal class DefaultKeysBackupService @Inject constructor(
private const val KEY_BACKUP_SEND_KEYS_MAX_COUNT = 100
}
/* ==========================================================================================
* DEBUG INFO
* ========================================================================================== */
/* ==========================================================================================
* DEBUG INFO
* ========================================================================================== */
override fun toString() = "KeysBackup for $userId"
}