From 438b456f8e6f5b69db83ddcd3d4614a0213f336c Mon Sep 17 00:00:00 2001 From: valere Date: Thu, 8 Dec 2022 22:53:16 +0100 Subject: [PATCH] quick incremental backup support --- .../session/crypto/model/MXUsersDevicesMap.kt | 6 +-- .../PerSessionBackupQueryRateLimiter.kt | 6 +-- .../sdk/internal/crypto/CryptoModule.kt | 5 +++ .../sdk/internal/crypto/RustCryptoService.kt | 2 +- .../crypto/keysbackup/RustKeyBackupService.kt | 39 ++++++++++------- .../internal/crypto/network/RequestSender.kt | 43 ++++++++++++++++++- 6 files changed, 77 insertions(+), 24 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt index 72380c8fe9..a23204a559 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.session.crypto.model class MXUsersDevicesMap { // A map of maps (userId -> (deviceId -> Object)). - val map = HashMap>() + val map = HashMap>() /** * @return the user Ids @@ -104,8 +104,8 @@ class MXUsersDevicesMap { map.clear() } - fun join(other: Map>) { - map.putAll(other) + fun join(other: Map>) { + map.putAll(other.map { it.key to it.value.toMutableMap() }) } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PerSessionBackupQueryRateLimiter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PerSessionBackupQueryRateLimiter.kt index 040aa1558e..8321c67138 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PerSessionBackupQueryRateLimiter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PerSessionBackupQueryRateLimiter.kt @@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber import javax.inject.Inject @@ -39,7 +38,6 @@ private val loggerTag = LoggerTag("OutgoingGossipingRequestManager", LoggerTag.C internal class PerSessionBackupQueryRateLimiter @Inject constructor( private val coroutineDispatchers: MatrixCoroutineDispatchers, private val keysBackupService: Lazy, - private val cryptoStore: IMXCryptoStore, private val clock: Clock, ) { @@ -68,11 +66,11 @@ internal class PerSessionBackupQueryRateLimiter @Inject constructor( var backupWasCheckedFromServer: Boolean = false val now = clock.epochMillis() - fun refreshBackupInfoIfNeeded(force: Boolean = false) { + suspend fun refreshBackupInfoIfNeeded(force: Boolean = false) { if (backupWasCheckedFromServer && !force) return Timber.tag(loggerTag.value).v("Checking if can access a backup") backupWasCheckedFromServer = true - val knownBackupSecret = cryptoStore.getKeyBackupRecoveryKeyInfo() + val knownBackupSecret = keysBackupService.get().getKeyBackupRecoveryKeyInfo() ?: return Unit.also { Timber.tag(loggerTag.value).v("We don't have the backup secret!") } diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt index a90d0a16d2..876109a2a3 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt @@ -25,8 +25,10 @@ import kotlinx.coroutines.SupervisorJob import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.internal.crypto.api.CryptoApi +import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultCreateKeysBackupVersionTask @@ -248,4 +250,7 @@ internal abstract class CryptoModule { @Binds abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask + + @Binds + abstract fun bindKeysBackupService(service: RustKeyBackupService): KeysBackupService } diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt index ebdea2a168..dc7159ae8e 100755 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt @@ -603,7 +603,7 @@ internal class RustCryptoService @Inject constructor( val sessionId = content.sessionId notifyRoomKeyReceived(roomId, sessionId) - matrixConfiguration.cryptoAnalyticsPlugin?.onRoomKeyImported(sessionId, EventType.FORWARDED_ROOM_KEY) + matrixConfiguration.cryptoAnalyticsPlugin?.onRoomKeyImported(sessionId, EventType.ROOM_KEY) } EventType.FORWARDED_ROOM_KEY -> { val content = event.getClearContent().toModel() ?: return@forEach diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt index ef89a4a5f3..7130e9db49 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt @@ -20,6 +20,7 @@ import android.os.Handler import android.os.Looper import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread +import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async @@ -27,6 +28,7 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import org.matrix.android.sdk.api.extensions.tryOrNull @@ -51,6 +53,7 @@ import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.crypto.MegolmSessionData import org.matrix.android.sdk.internal.crypto.MegolmSessionImportManager import org.matrix.android.sdk.internal.crypto.OlmMachine +import org.matrix.android.sdk.internal.crypto.PerSessionBackupQueryRateLimiter import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmBackupAuthData import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeyBackupData @@ -80,6 +83,8 @@ internal class RustKeyBackupService @Inject constructor( private val coroutineDispatchers: MatrixCoroutineDispatchers, private val megolmSessionImportManager: MegolmSessionImportManager, private val cryptoCoroutineScope: CoroutineScope, + private val matrixConfiguration: MatrixConfiguration, + private val backupQueryRateLimiter: dagger.Lazy, ) : KeysBackupService { companion object { // Maximum delay in ms in {@link maybeBackupKeys} @@ -94,9 +99,7 @@ internal class RustKeyBackupService @Inject constructor( override var keysBackupVersion: KeysVersionResult? = null private set -// private var backupAllGroupSessionsCallback: MatrixCallback? = null - - private val importScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.main) + private val importScope = CoroutineScope(cryptoCoroutineScope.coroutineContext + SupervisorJob() + CoroutineName("backupImport")) private var keysBackupStateListener: KeysBackupStateListener? = null @@ -381,20 +384,22 @@ internal class RustKeyBackupService @Inject constructor( if (version != null) { val key = BackupRecoveryKey.fromBase64(secret) 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) - val recoveredKeys = importResult.successfullyNumberOfImportedKeys - Timber.i("onSecretKeyGossip: Recovered keys $recoveredKeys out of ${importResult.totalNumberOfKeys}") - } catch (failure: Throwable) { - // fail silently.. - Timber.e(failure, "onSecretKeyGossip: Failed to import keys from backup") - } - } // we can save, it's valid saveBackupRecoveryKey(key, version.version) + importScope.launch { + backupQueryRateLimiter.get().refreshBackupInfoIfNeeded(true) + } + // we don't want to wait for that +// importScope.launch { +// try { +// val importResult = restoreBackup(version, key, null, null, null) +// val recoveredKeys = importResult.successfullyNumberOfImportedKeys +// Timber.i("onSecretKeyGossip: Recovered keys $recoveredKeys out of ${importResult.totalNumberOfKeys}") +// } catch (failure: Throwable) { +// // fail silently.. +// Timber.e(failure, "onSecretKeyGossip: Failed to import keys from backup") +// } +// } } else { Timber.d("Invalid recovery key") } @@ -549,6 +554,10 @@ internal class RustKeyBackupService @Inject constructor( } val result = olmMachine.importDecryptedKeys(sessionsData, progressListener).also { + sessionsData.onEach { sessionData -> + matrixConfiguration.cryptoAnalyticsPlugin + ?.onRoomKeyImported(sessionData.sessionId.orEmpty(), keysVersionResult.algorithm) + } megolmSessionImportManager.dispatchKeyImportResults(it) } diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/network/RequestSender.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/network/RequestSender.kt index 77de8cbbac..982f711a2f 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/network/RequestSender.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/network/RequestSender.kt @@ -19,17 +19,24 @@ package org.matrix.android.sdk.internal.crypto.network import com.squareup.moshi.Moshi import com.squareup.moshi.Types import dagger.Lazy +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult +import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.uia.UiaResult import org.matrix.android.sdk.internal.auth.registration.handleUIA +import org.matrix.android.sdk.internal.crypto.PerSessionBackupQueryRateLimiter import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData @@ -87,8 +94,14 @@ internal class RequestSender @Inject constructor( private val getRoomSessionsDataTask: GetRoomSessionsDataTask, private val getRoomSessionDataTask: GetRoomSessionDataTask, private val moshi: Moshi, + private val cryptoCoroutineScope: CoroutineScope, + private val rateLimiter: PerSessionBackupQueryRateLimiter, ) { + private val scope = CoroutineScope( + cryptoCoroutineScope.coroutineContext + SupervisorJob() + CoroutineName("backupRequest") + ) + suspend fun claimKeys(request: Request.KeysClaim): String { val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(request.oneTimeKeys) val response = oneTimeKeysForUsersDeviceTask.execute(claimParams) @@ -211,9 +224,37 @@ internal class RequestSender @Inject constructor( .newBuilder() .add(CheckNumberType.JSON_ADAPTER_FACTORY) .build() - .adapter>>(Map::class.java) + .adapter>>(Map::class.java) val jsonBody = adapter.fromJson(body)!! + if (eventType == EventType.ROOM_KEY_REQUEST) { + scope.launch { + Timber.v("Intercepting key request, try backup") + /** + * It's a bit hacky, check how this can be better integrated with rust? + */ + try { + jsonBody.forEach { (_, deviceToContent) -> + deviceToContent.forEach { (_, content) -> + val hashMap = content as? Map<*, *> + val action = hashMap?.get("action")?.toString() + if (GossipingToDeviceObject.ACTION_SHARE_REQUEST == action) { + val body = hashMap.get("body") as? Map<*, *> + val roomId = body?.get("room_id") as? String + val sessionId = body?.get("session_id") as? String + if (roomId != null && sessionId != null) { + rateLimiter.tryFromBackupIfPossible(sessionId, roomId) + } + } + } + } + Timber.v("Intercepting key request, try backup") + } catch (failure: Throwable) { + Timber.v(failure, "Failed to use backup") + } + } + } + val userMap = MXUsersDevicesMap() userMap.join(jsonBody)