From 3b93d6b08ce999bb37ee590758e411cdc04b0950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 22 Oct 2021 10:44:07 +0200 Subject: [PATCH] crypto: Fill out all the methods to support backups --- .../internal/crypto/DefaultCryptoService.kt | 57 +++----- .../android/sdk/internal/crypto/Device.kt | 16 +-- .../android/sdk/internal/crypto/OlmMachine.kt | 53 ++++--- .../sdk/internal/crypto/QrCodeVerification.kt | 4 +- .../sdk/internal/crypto/SasVerification.kt | 4 +- .../sdk/internal/crypto/UserIdentities.kt | 18 +-- .../keysbackup/DefaultKeysBackupService.kt | 1 + .../crypto/keysbackup/RustKeyBackupService.kt | 134 ++++++++++++++++-- rust-sdk/Cargo.toml | 6 +- rust-sdk/src/lib.rs | 2 - rust-sdk/src/machine.rs | 8 ++ rust-sdk/src/olm.udl | 4 + 12 files changed, 221 insertions(+), 86 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index e853237e2f..e9c28376ff 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -20,6 +20,7 @@ import android.content.Context import androidx.annotation.VisibleForTesting import androidx.lifecycle.LiveData import androidx.paging.PagedList +import com.squareup.moshi.Types import dagger.Lazy import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope @@ -57,17 +58,20 @@ import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.auth.registration.handleUIA import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService +import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData 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 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.model.rest.RoomKeysBackupData +import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteBackupTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupLastVersionTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask +import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult @@ -137,6 +141,7 @@ internal class RequestSender @Inject constructor( private val deleteBackupTask: DeleteBackupTask, private val createKeysBackupVersionTask: CreateKeysBackupVersionTask, private val backupRoomKeysTask: StoreSessionsDataTask, + private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask, ) { companion object { const val REQUEST_RETRY_COUNT = 3 @@ -297,44 +302,25 @@ internal class RequestSender @Inject constructor( val adapter = MoshiProvider .providesMoshi() .newBuilder() + .add(CheckNumberType.JSON_ADAPTER_FACTORY) .build() - .adapter>(MutableMap::class.java) + .adapter>( + Types.newParameterizedType( + Map::class.java, + String::class.java, + RoomKeysBackupData::class.java + )) val keys = adapter.fromJson(request.rooms)!! - Timber.d("BACKUP: CONVERTED KEYS TO HASHMAP $keys") - /* - val keyAdapter = MoshiProvider.providesMoshi().adapter(KeyBackupData::class.java) - val keysBackupData = KeysBackupData() - for (room in keys) { - val sessions = room.value.getOrDefault("sessions", mapOf()) - - for (session in sessions) { - Timber.d("BACKUP: HEEELOO CONVERTING KEY ${session.value}") - val key = keyAdapter.fromJson(session.value)!! - Timber.d("BACKUP: HEEELOO CONVERTED KEY $key") - - keysBackupData - .roomIdToRoomKeysBackupData - .getOrPut(room.key, { RoomKeysBackupData() }) - .sessionIdToKeyBackupData[session.key] = key - } - } - - - */ - /* - for ((roomId, backupData) in keys) { - val roomData = backup.roomIdToRoomKeysBackupData.getOrPut(roomId, { RoomKeysBackupData() }) - for ((sessionId, key) in backupData.sessionIdToKeyBackupData) { - Timber.d("BACKUP INSERTING KEY $key") - roomData.sessionIdToKeyBackupData[sessionId] = key - } - } - */ - val params = StoreSessionsDataTask.Params(request.version, KeysBackupData()) - val response = backupRoomKeysTask.execute(params) + val params = StoreSessionsDataTask.Params(request.version, KeysBackupData(keys)) + val response = backupRoomKeysTask.executeRetry(params, REQUEST_RETRY_COUNT) val responseAdapter = MoshiProvider.providesMoshi().adapter(BackupKeysResult::class.java) return responseAdapter.toJson(response)!! } + + suspend fun updateBackup(keysBackupVersion: KeysVersionResult, body: UpdateKeysBackupVersionBody) { + val params = UpdateKeysBackupVersionTask.Params(keysBackupVersion.version, body) + updateKeysBackupVersionTask.executeRetry(params, REQUEST_RETRY_COUNT) + } } /** @@ -566,6 +552,8 @@ internal class DefaultCryptoService @Inject constructor( Timber.v("Failed create an Olm machine: $throwable") } + // We try to enable key backups, if the backup version on the server is trusted, + // we're gonna continue backing up. tryOrNull { keysBackupService!!.checkAndStartKeysBackup() } @@ -1075,7 +1063,8 @@ internal class DefaultCryptoService @Inject constructor( } is Request.KeysBackup -> { async { - TODO() + // The rust-sdk won't ever produce KeysBackup requests here, + // those only get explicitly created. } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/Device.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/Device.kt index 58a742f174..e1db5ddbe9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/Device.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/Device.kt @@ -24,10 +24,10 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo import org.matrix.android.sdk.internal.crypto.verification.prepareMethods -import uniffi.olm.CryptoStoreErrorException +import uniffi.olm.CryptoStoreException import uniffi.olm.Device as InnerDevice import uniffi.olm.OlmMachine -import uniffi.olm.SignatureErrorException +import uniffi.olm.SignatureException import uniffi.olm.VerificationRequest /** Class representing a device that supports E2EE in the Matrix world @@ -41,7 +41,7 @@ internal class Device( private val sender: RequestSender, private val listeners: ArrayList, ) { - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) private suspend fun refreshData() { val device = withContext(Dispatchers.IO) { machine.getDevice(inner.userId, inner.deviceId) @@ -63,7 +63,7 @@ internal class Device( * [org.matrix.android.sdk.internal.crypto.OwnUserIdentity.requestVerification] * method can be used instead. */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun requestVerification(methods: List): VerificationRequest? { val stringMethods = prepareMethods(methods) val result = withContext(Dispatchers.IO) { @@ -87,7 +87,7 @@ internal class Device( * The [requestVerification] method should be used instead. * */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun startVerification(): SasVerification? { val result = withContext(Dispatchers.IO) { machine.startSasWithDevice(inner.userId, inner.deviceId) @@ -109,7 +109,7 @@ internal class Device( * This won't upload any signatures, it will only mark the device as trusted * in the local database. */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun markAsTrusted() { withContext(Dispatchers.IO) { machine.markDeviceAsTrusted(inner.userId, inner.deviceId) @@ -125,7 +125,7 @@ internal class Device( * This will fail if the device doesn't belong to use or if we don't have the * private part of our self-signing key. */ - @Throws(SignatureErrorException::class) + @Throws(SignatureException::class) suspend fun verify(): Boolean { val request = withContext(Dispatchers.IO) { machine.verifyDevice(inner.userId, inner.deviceId) @@ -139,7 +139,7 @@ internal class Device( /** * Get the DeviceTrustLevel of this device */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun trustLevel(): DeviceTrustLevel { refreshData() return DeviceTrustLevel(crossSigningVerified = inner.crossSigningTrusted, locallyVerified = inner.locallyTrusted) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt index 59b5ea51f0..e4f01c842b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import com.squareup.moshi.Types import java.io.File import java.nio.charset.Charset import java.util.concurrent.ConcurrentHashMap @@ -37,6 +38,8 @@ import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult +import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData +import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.RoomKeysBackupData import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap @@ -44,16 +47,18 @@ import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.internal.network.parsing.CheckNumberType import org.matrix.android.sdk.internal.session.sync.model.DeviceListResponse import org.matrix.android.sdk.internal.session.sync.model.DeviceOneTimeKeysCountSyncResponse import org.matrix.android.sdk.internal.session.sync.model.ToDeviceSyncResponse +import org.matrix.android.sdk.internal.util.JsonCanonicalizer import timber.log.Timber import uniffi.olm.BackupKey import uniffi.olm.BackupKeys import uniffi.olm.CrossSigningKeyExport import uniffi.olm.CrossSigningStatus -import uniffi.olm.CryptoStoreErrorException -import uniffi.olm.DecryptionErrorException +import uniffi.olm.CryptoStoreException +import uniffi.olm.DecryptionException import uniffi.olm.DeviceLists import uniffi.olm.KeyRequestPair import uniffi.olm.Logger @@ -262,7 +267,7 @@ internal class OlmMachine( * * @param responseBody The body of the response that was received. */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun markRequestAsSent( requestId: String, requestType: RequestType, @@ -290,7 +295,7 @@ internal class OlmMachine( * * @param keyCounts The map of uploaded one-time key types and counts. */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun receiveSyncChanges( toDevice: ToDeviceSyncResponse?, deviceChanges: DeviceListResponse?, @@ -342,7 +347,7 @@ internal class OlmMachine( * * @return A [Request.KeysClaim] request that needs to be sent out to the server. */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun getMissingSessions(users: List): Request? = withContext(Dispatchers.IO) { inner.getMissingSessions(users) } @@ -364,7 +369,7 @@ internal class OlmMachine( * * @return The list of [Request.ToDevice] that need to be sent out. */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun shareRoomKey(roomId: String, users: List): List = withContext(Dispatchers.IO) { inner.shareRoomKey(roomId, users) } @@ -398,7 +403,7 @@ internal class OlmMachine( * * @return The encrypted version of the [Content] */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun encrypt(roomId: String, eventType: String, content: Content): Content = withContext(Dispatchers.IO) { val adapter = MoshiProvider.providesMoshi().adapter(Map::class.java) @@ -453,7 +458,7 @@ internal class OlmMachine( * request itself. The cancellation *must* be sent out before the request, otherwise devices * will ignore the key request. */ - @Throws(DecryptionErrorException::class) + @Throws(DecryptionException::class) suspend fun requestRoomKey(event: Event): KeyRequestPair = withContext(Dispatchers.IO) { val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java) @@ -472,7 +477,7 @@ internal class OlmMachine( * * @return the encrypted key export as a bytearray. */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun exportKeys(passphrase: String, rounds: Int): ByteArray = withContext(Dispatchers.IO) { inner.exportKeys(passphrase, rounds).toByteArray() } @@ -485,7 +490,7 @@ internal class OlmMachine( * * @param listener A callback that can be used to introspect the progress of the key import. */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun importKeys( keys: ByteArray, passphrase: String, @@ -501,7 +506,7 @@ internal class OlmMachine( ImportRoomKeysResult(result.total, result.imported) } - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun getIdentity(userId: String): UserIdentities? { val identity = withContext(Dispatchers.IO) { inner.getIdentity(userId) @@ -545,7 +550,7 @@ internal class OlmMachine( * * @return The Device if it found one. */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun getCryptoDeviceInfo(userId: String, deviceId: String): CryptoDeviceInfo? { return if (userId == userId() && deviceId == deviceId()) { // Our own device isn't part of our store on the Rust side, return it @@ -556,7 +561,7 @@ internal class OlmMachine( } } - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun getDevice(userId: String, deviceId: String): Device? { val device = withContext(Dispatchers.IO) { inner.getDevice(userId, deviceId) @@ -578,7 +583,7 @@ internal class OlmMachine( * * @return The list of Devices or an empty list if there aren't any. */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun getCryptoDeviceInfo(userId: String): List { val devices = this.getUserDevices(userId).map { it.toCryptoDeviceInfo() }.toMutableList() @@ -658,7 +663,7 @@ internal class OlmMachine( } /** Discard the currently active room key for the given room if there is one. */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) fun discardRoomKey(roomId: String) { runBlocking { inner.discardRoomKey(roomId) } } @@ -770,7 +775,7 @@ internal class OlmMachine( } } - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun enableBackup(key: String, version: String) { return withContext(Dispatchers.Default) { val backupKey = BackupKey(key, mapOf(), null) @@ -797,7 +802,7 @@ internal class OlmMachine( inner.saveRecoveryKey(key, version) } - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun backupRoomKeys(): Request? { return withContext(Dispatchers.Default) { Timber.d("BACKUP CREATING REQUEST") @@ -806,4 +811,18 @@ internal class OlmMachine( request } } + + @Throws(CryptoStoreException::class) + suspend fun checkAuthDataSignature(authData: MegolmBackupAuthData): Boolean { + return withContext(Dispatchers.Default) { + val adapter = MoshiProvider + .providesMoshi() + .newBuilder() + .add(CheckNumberType.JSON_ADAPTER_FACTORY) + .build() + .adapter(MegolmBackupAuthData::class.java) + val serializedAuthData = adapter.toJson(authData) + inner.verifyBackup(serializedAuthData) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/QrCodeVerification.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/QrCodeVerification.kt index 337d9ded6b..fefabf2bd4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/QrCodeVerification.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/QrCodeVerification.kt @@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxStat import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher -import uniffi.olm.CryptoStoreErrorException +import uniffi.olm.CryptoStoreException import uniffi.olm.OlmMachine import uniffi.olm.QrCode import uniffi.olm.Verification @@ -172,7 +172,7 @@ internal class QrCodeVerification( * The method turns into a noop if we're not yet ready to confirm the scanning, * i.e. we didn't yet receive a m.key.verification.start event from the other side. */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) private suspend fun confirm() { val result = withContext(Dispatchers.IO) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SasVerification.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SasVerification.kt index 34b3e9fed6..53c7fef231 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SasVerification.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SasVerification.kt @@ -27,7 +27,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxStat import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher import org.matrix.android.sdk.internal.crypto.verification.getEmojiForCode -import uniffi.olm.CryptoStoreErrorException +import uniffi.olm.CryptoStoreException import uniffi.olm.OlmMachine import uniffi.olm.Sas import uniffi.olm.Verification @@ -202,7 +202,7 @@ internal class SasVerification( } } - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) private suspend fun confirm() { val result = withContext(Dispatchers.IO) { machine.confirmVerification(inner.otherUserId, inner.flowId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/UserIdentities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/UserIdentities.kt index 1373e1bbe7..4d21d796c5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/UserIdentities.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/UserIdentities.kt @@ -25,8 +25,8 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey import org.matrix.android.sdk.internal.crypto.verification.prepareMethods -import uniffi.olm.CryptoStoreErrorException -import uniffi.olm.SignatureErrorException +import uniffi.olm.CryptoStoreException +import uniffi.olm.SignatureException /** * A sealed class representing user identities. @@ -46,7 +46,7 @@ sealed class UserIdentities { * * @return True if the identity is considered to be verified and trusted, false otherwise. */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) abstract suspend fun verified(): Boolean /** @@ -59,7 +59,7 @@ sealed class UserIdentities { * Throws a SignatureErrorException if we can't sign the identity, * if for example we don't have access to our user-signing key. */ - @Throws(SignatureErrorException::class) + @Throws(SignatureException::class) abstract suspend fun verify() /** @@ -93,7 +93,7 @@ internal class OwnUserIdentity( * * To perform an interactive verification user the [requestVerification] method instead. */ - @Throws(SignatureErrorException::class) + @Throws(SignatureException::class) override suspend fun verify() { val request = withContext(Dispatchers.Default) { olmMachine.inner().verifyIdentity(userId) } this.requestSender.sendSignatureUpload(request) @@ -104,7 +104,7 @@ internal class OwnUserIdentity( * * @return True if the identity is considered to be verified and trusted, false otherwise. */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) override suspend fun verified(): Boolean { return withContext(Dispatchers.IO) { olmMachine.inner().isIdentityVerified(userId) } } @@ -130,7 +130,7 @@ internal class OwnUserIdentity( * @param methods The list of [VerificationMethod] that we wish to advertise to the other * side as being supported. */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun requestVerification(methods: List): VerificationRequest { val stringMethods = prepareMethods(methods) val result = this.olmMachine.inner().requestSelfVerification(stringMethods) @@ -187,7 +187,7 @@ internal class UserIdentity( * * To perform an interactive verification user the [requestVerification] method instead. */ - @Throws(SignatureErrorException::class) + @Throws(SignatureException::class) override suspend fun verify() { val request = withContext(Dispatchers.Default) { olmMachine.inner().verifyIdentity(userId) } this.requestSender.sendSignatureUpload(request) @@ -225,7 +225,7 @@ internal class UserIdentity( * @param transactionId The transaction id that should be used for the request that sends * the `m.key.verification.request` to the room. */ - @Throws(CryptoStoreErrorException::class) + @Throws(CryptoStoreException::class) suspend fun requestVerification( methods: List, roomId: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt index fbcf5cfdeb..8c721068d5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -84,6 +84,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import okhttp3.internal.wait import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmBackupAuthData import org.matrix.olm.OlmException import org.matrix.olm.OlmPkDecryption diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt index 55c074916d..645ea4f63c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt @@ -42,6 +42,7 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmB import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody 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.model.rest.UpdateKeysBackupVersionBody import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo import org.matrix.android.sdk.internal.extensions.foldToCallback @@ -266,34 +267,148 @@ internal class RustKeyBackupService @Inject constructor( override fun backupAllGroupSessions(progressListener: ProgressListener?, callback: MatrixCallback?) { + // This is only used in tests? TODO() } + private suspend fun checkBackupTrust(authData: MegolmBackupAuthData?): KeysBackupVersionTrust { + return if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isEmpty()) { + Timber.v("getKeysBackupTrust: Key backup is absent or missing required data") + KeysBackupVersionTrust() + } else { + KeysBackupVersionTrust(olmMachine.checkAuthDataSignature(authData)) + } + } + override fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult, callback: MatrixCallback) { - Timber.d("BACKUP: HELLOO TRYING TO CHECK THE TRUST") - // TODO - callback.onSuccess(KeysBackupVersionTrust(false)) + val authData = keysBackupVersion.getAuthDataAsMegolmBackupAuthData() + + cryptoCoroutineScope.launch { + try { + callback.onSuccess(checkBackupTrust(authData)) + } catch (exception: Throwable) { + callback.onFailure(exception) + } + } } override fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, trust: Boolean, callback: MatrixCallback) { Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}") - TODO() + + // Get auth data to update it + val authData = getMegolmBackupAuthData(keysBackupVersion) + + if (authData == null) { + Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data") + + callback.onFailure(IllegalArgumentException("Missing element")) + } else { + cryptoCoroutineScope.launch(coroutineDispatchers.main) { + val body = withContext(coroutineDispatchers.crypto) { + // Get current signatures, or create an empty set + val userId = olmMachine.userId() + val signatures = authData.signatures[userId].orEmpty().toMutableMap() + + if (trust) { + // Add current device signature + val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary()) + val deviceSignature = olmMachine.sign(canonicalJson) + + deviceSignature[userId]?.forEach { entry -> + signatures[entry.key] = entry.value + } + } else { + signatures.remove("ed25519:${olmMachine.deviceId()}") + } + + val newAuthData = authData.copy() + val newSignatures = newAuthData.signatures.toMutableMap() + newSignatures[userId] = signatures + + @Suppress("UNCHECKED_CAST") + UpdateKeysBackupVersionBody( + algorithm = keysBackupVersion.algorithm, + authData = newAuthData.copy(signatures = newSignatures).toJsonDict(), + version = keysBackupVersion.version) + } + try { + 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) + } + } + } + } + + // 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. + private fun checkRecoveryKey(recoveryKey: BackupRecoveryKey, keysBackupData: KeysVersionResult) { + val backupKey = recoveryKey.publicKey() + val authData = getMegolmBackupAuthData(keysBackupData) + + when { + authData == null -> { + Timber.w("isValidRecoveryKeyForKeysBackupVersion: Key backup is missing required data") + throw IllegalArgumentException("Missing element") + + } + backupKey.publicKey != authData.publicKey -> { + Timber.w("isValidRecoveryKeyForKeysBackupVersion: Public keys mismatch") + throw IllegalArgumentException("Invalid recovery key or password") + } + else -> { + // This case is fine, the public key on the server matches the public key the + // recovery key produced. + } + } } override fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, recoveryKey: String, callback: MatrixCallback) { Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}") - TODO() + + cryptoCoroutineScope.launch { + try { + // This is ~nowhere mentioned, the string here is actually a base58 encoded key. + // This not really supported by the spec for the backup key, the 4S key supports + // base58 encoding and the same method seems to be used here. + val key = BackupRecoveryKey.fromBase58(recoveryKey) + checkRecoveryKey(key, keysBackupVersion) + trustKeysBackupVersion(keysBackupVersion, true, callback) + + } catch (exception: Throwable) { + callback.onFailure(exception) + } + } } override fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, password: String, callback: MatrixCallback) { - TODO() + cryptoCoroutineScope.launch { + try { + val key = BackupRecoveryKey.fromPassphrase(password) + checkRecoveryKey(key, keysBackupVersion) + trustKeysBackupVersion(keysBackupVersion, true, callback) + } catch (exception: Throwable) { + callback.onFailure(exception) + } + } } override fun onSecretKeyGossip(secret: String) { @@ -437,8 +552,9 @@ internal class RustKeyBackupService @Inject constructor( } Timber.v(" -> enabling key backups") - // TODO - // enableKeysBackup(keyBackupVersion) + cryptoCoroutineScope.launch { + enableKeysBackup(keyBackupVersion) + } } else { Timber.v("checkAndStartWithKeysBackupVersion: No usable key backup. version: ${keyBackupVersion.version}") if (versionInStore != null) { @@ -595,7 +711,7 @@ internal class RustKeyBackupService @Inject constructor( olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_BACKUP, response) Timber.d("BACKUP MARKED REQUEST AS SENT") - // TODO again is this correct? + // TODO, again is this correct? withContext(Dispatchers.Main) { backupKeys() } diff --git a/rust-sdk/Cargo.toml b/rust-sdk/Cargo.toml index 0b8d791281..566baa71cb 100644 --- a/rust-sdk/Cargo.toml +++ b/rust-sdk/Cargo.toml @@ -17,7 +17,7 @@ base64 = "0.13.0" thiserror = "1.0.25" tracing = "0.1.26" tracing-subscriber = "0.2.18" -uniffi = "0.12.0" +uniffi = "0.14.0" pbkdf2 = "0.8.0" sha2 = "0.9.5" rand = "0.8.4" @@ -28,14 +28,14 @@ version = "0.2.1" features = ["lax_deserialize"] [dependencies.matrix-sdk-common] -path = "/home/poljar/werk/priv/nio-rust/crates/matrix-sdk-common/" +path = "/home/poljar/werk/matrix/nio-rust/crates/matrix-sdk-common/" # git = "https://github.com/matrix-org/matrix-rust-sdk/" # rev = "9bae87b0ac213f9d37c033e76ea3a336e164cf02" [dependencies.matrix-sdk-crypto] # git = "https://github.com/matrix-org/matrix-rust-sdk/" # rev = "9bae87b0ac213f9d37c033e76ea3a336e164cf02" -path = "/home/poljar/werk/priv/nio-rust/crates/matrix-sdk-crypto/" +path = "/home/poljar/werk/matrix/nio-rust/crates/matrix-sdk-crypto/" features = ["sled_cryptostore", "qrcode", "backups_v1"] [dependencies.tokio] diff --git a/rust-sdk/src/lib.rs b/rust-sdk/src/lib.rs index c489b71602..6a2ca6a819 100644 --- a/rust-sdk/src/lib.rs +++ b/rust-sdk/src/lib.rs @@ -1,11 +1,9 @@ #![deny( dead_code, - missing_docs, trivial_casts, trivial_numeric_casts, unused_extern_crates, unused_import_braces, - unused_qualifications )] //! TODO diff --git a/rust-sdk/src/machine.rs b/rust-sdk/src/machine.rs index 4f420e13d1..e61c97b365 100644 --- a/rust-sdk/src/machine.rs +++ b/rust-sdk/src/machine.rs @@ -1321,4 +1321,12 @@ impl OlmMachine { }) .collect() } + + /// TODO + pub fn verify_backup(&self, auth_data: &str) -> Result { + let auth_data = serde_json::from_str(auth_data)?; + Ok(self + .runtime + .block_on(self.inner.backup_machine().verify_backup(auth_data))?) + } } diff --git a/rust-sdk/src/olm.udl b/rust-sdk/src/olm.udl index 5d9cfd48e0..e1b9f06e05 100644 --- a/rust-sdk/src/olm.udl +++ b/rust-sdk/src/olm.udl @@ -360,6 +360,8 @@ interface OlmMachine { [Throws=CryptoStoreError] BackupKeys? get_backup_keys(); boolean backup_enabled(); + [Throws=CryptoStoreError] + boolean verify_backup([ByRef] string auth_data); }; dictionary PassphraseInfo { @@ -389,6 +391,8 @@ interface BackupRecoveryKey { constructor(string key); [Name=from_passphrase] constructor(string key); + [Name=from_base58] + constructor(string key); string to_base58(); BackupKey public_key(); };