quick incremental backup support

This commit is contained in:
valere 2022-12-08 22:53:16 +01:00
parent 4766bc709d
commit 438b456f8e
6 changed files with 77 additions and 24 deletions

View File

@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.session.crypto.model
class MXUsersDevicesMap<E> { class MXUsersDevicesMap<E> {
// A map of maps (userId -> (deviceId -> Object)). // A map of maps (userId -> (deviceId -> Object)).
val map = HashMap<String /* userId */, HashMap<String /* deviceId */, E>>() val map = HashMap<String /* userId */, MutableMap<String /* deviceId */, E>>()
/** /**
* @return the user Ids * @return the user Ids
@ -104,8 +104,8 @@ class MXUsersDevicesMap<E> {
map.clear() map.clear()
} }
fun join(other: Map<out String, HashMap<String, E>>) { fun join(other: Map<out String, Map<String, E>>) {
map.putAll(other) map.putAll(other.map { it.key to it.value.toMutableMap() })
} }
/** /**

View File

@ -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.KeysBackupService
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult 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.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.util.time.Clock import org.matrix.android.sdk.internal.util.time.Clock
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -39,7 +38,6 @@ private val loggerTag = LoggerTag("OutgoingGossipingRequestManager", LoggerTag.C
internal class PerSessionBackupQueryRateLimiter @Inject constructor( internal class PerSessionBackupQueryRateLimiter @Inject constructor(
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val keysBackupService: Lazy<KeysBackupService>, private val keysBackupService: Lazy<KeysBackupService>,
private val cryptoStore: IMXCryptoStore,
private val clock: Clock, private val clock: Clock,
) { ) {
@ -68,11 +66,11 @@ internal class PerSessionBackupQueryRateLimiter @Inject constructor(
var backupWasCheckedFromServer: Boolean = false var backupWasCheckedFromServer: Boolean = false
val now = clock.epochMillis() val now = clock.epochMillis()
fun refreshBackupInfoIfNeeded(force: Boolean = false) { suspend fun refreshBackupInfoIfNeeded(force: Boolean = false) {
if (backupWasCheckedFromServer && !force) return if (backupWasCheckedFromServer && !force) return
Timber.tag(loggerTag.value).v("Checking if can access a backup") Timber.tag(loggerTag.value).v("Checking if can access a backup")
backupWasCheckedFromServer = true backupWasCheckedFromServer = true
val knownBackupSecret = cryptoStore.getKeyBackupRecoveryKeyInfo() val knownBackupSecret = keysBackupService.get().getKeyBackupRecoveryKeyInfo()
?: return Unit.also { ?: return Unit.also {
Timber.tag(loggerTag.value).v("We don't have the backup secret!") Timber.tag(loggerTag.value).v("We don't have the backup secret!")
} }

View File

@ -25,8 +25,10 @@ import kotlinx.coroutines.SupervisorJob
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.session.crypto.CryptoService 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.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.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.internal.crypto.api.CryptoApi 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.api.RoomKeysApi
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultCreateKeysBackupVersionTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultCreateKeysBackupVersionTask
@ -248,4 +250,7 @@ internal abstract class CryptoModule {
@Binds @Binds
abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask
@Binds
abstract fun bindKeysBackupService(service: RustKeyBackupService): KeysBackupService
} }

View File

@ -603,7 +603,7 @@ internal class RustCryptoService @Inject constructor(
val sessionId = content.sessionId val sessionId = content.sessionId
notifyRoomKeyReceived(roomId, sessionId) notifyRoomKeyReceived(roomId, sessionId)
matrixConfiguration.cryptoAnalyticsPlugin?.onRoomKeyImported(sessionId, EventType.FORWARDED_ROOM_KEY) matrixConfiguration.cryptoAnalyticsPlugin?.onRoomKeyImported(sessionId, EventType.ROOM_KEY)
} }
EventType.FORWARDED_ROOM_KEY -> { EventType.FORWARDED_ROOM_KEY -> {
val content = event.getClearContent().toModel<ForwardedRoomKeyContent>() ?: return@forEach val content = event.getClearContent().toModel<ForwardedRoomKeyContent>() ?: return@forEach

View File

@ -20,6 +20,7 @@ import android.os.Handler
import android.os.Looper import android.os.Looper
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async import kotlinx.coroutines.async
@ -27,6 +28,7 @@ import kotlinx.coroutines.awaitAll
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.MatrixConfiguration
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
import org.matrix.android.sdk.api.extensions.tryOrNull 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.MegolmSessionData
import org.matrix.android.sdk.internal.crypto.MegolmSessionImportManager import org.matrix.android.sdk.internal.crypto.MegolmSessionImportManager
import org.matrix.android.sdk.internal.crypto.OlmMachine 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.SignalableMegolmBackupAuthData
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeyBackupData import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeyBackupData
@ -80,6 +83,8 @@ internal class RustKeyBackupService @Inject constructor(
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val megolmSessionImportManager: MegolmSessionImportManager, private val megolmSessionImportManager: MegolmSessionImportManager,
private val cryptoCoroutineScope: CoroutineScope, private val cryptoCoroutineScope: CoroutineScope,
private val matrixConfiguration: MatrixConfiguration,
private val backupQueryRateLimiter: dagger.Lazy<PerSessionBackupQueryRateLimiter>,
) : KeysBackupService { ) : KeysBackupService {
companion object { companion object {
// Maximum delay in ms in {@link maybeBackupKeys} // Maximum delay in ms in {@link maybeBackupKeys}
@ -94,9 +99,7 @@ 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 val importScope = CoroutineScope(cryptoCoroutineScope.coroutineContext + SupervisorJob() + CoroutineName("backupImport"))
private val importScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.main)
private var keysBackupStateListener: KeysBackupStateListener? = null private var keysBackupStateListener: KeysBackupStateListener? = null
@ -381,20 +384,22 @@ internal class RustKeyBackupService @Inject constructor(
if (version != null) { if (version != null) {
val key = BackupRecoveryKey.fromBase64(secret) val key = BackupRecoveryKey.fromBase64(secret)
if (isValidRecoveryKey(key, version)) { 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 // we can save, it's valid
saveBackupRecoveryKey(key, version.version) 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 { } else {
Timber.d("Invalid recovery key") Timber.d("Invalid recovery key")
} }
@ -549,6 +554,10 @@ internal class RustKeyBackupService @Inject constructor(
} }
val result = olmMachine.importDecryptedKeys(sessionsData, progressListener).also { val result = olmMachine.importDecryptedKeys(sessionsData, progressListener).also {
sessionsData.onEach { sessionData ->
matrixConfiguration.cryptoAnalyticsPlugin
?.onRoomKeyImported(sessionData.sessionId.orEmpty(), keysVersionResult.algorithm)
}
megolmSessionImportManager.dispatchKeyImportResults(it) megolmSessionImportManager.dispatchKeyImportResults(it)
} }

View File

@ -19,17 +19,24 @@ package org.matrix.android.sdk.internal.crypto.network
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import com.squareup.moshi.Types import com.squareup.moshi.Types
import dagger.Lazy 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.auth.UserInteractiveAuthInterceptor
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.session.crypto.keysbackup.KeysBackupLastVersionResult 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.KeysVersion
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult 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.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.api.session.events.model.Content 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.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.api.session.uia.UiaResult
import org.matrix.android.sdk.internal.auth.registration.handleUIA 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.BackupKeysResult
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData 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 getRoomSessionsDataTask: GetRoomSessionsDataTask,
private val getRoomSessionDataTask: GetRoomSessionDataTask, private val getRoomSessionDataTask: GetRoomSessionDataTask,
private val moshi: Moshi, 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 { suspend fun claimKeys(request: Request.KeysClaim): String {
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(request.oneTimeKeys) val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(request.oneTimeKeys)
val response = oneTimeKeysForUsersDeviceTask.execute(claimParams) val response = oneTimeKeysForUsersDeviceTask.execute(claimParams)
@ -211,9 +224,37 @@ internal class RequestSender @Inject constructor(
.newBuilder() .newBuilder()
.add(CheckNumberType.JSON_ADAPTER_FACTORY) .add(CheckNumberType.JSON_ADAPTER_FACTORY)
.build() .build()
.adapter<Map<String, HashMap<String, Any>>>(Map::class.java) .adapter<Map<String, Map<String, Any>>>(Map::class.java)
val jsonBody = adapter.fromJson(body)!! 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<Any>() val userMap = MXUsersDevicesMap<Any>()
userMap.join(jsonBody) userMap.join(jsonBody)