diff --git a/CHANGES.md b/CHANGES.md index fe88aed5ab..2967946366 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,6 +24,7 @@ Bugfix 🐛: - Messages encrypted with no way to decrypt after SDK update from 0.18 to 1.0.0 (#2252) - Incoming call continues to ring if call is answered on another device (#1921) - Search Result | scroll jumps after pagination (#2238) + - KeysBackup: Avoid using `!!` (#2262) Translations 🗣: - diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt index b67172a908..1a9165ade4 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt @@ -245,7 +245,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { fun createFakeMegolmBackupCreationInfo(): MegolmBackupCreationInfo { return MegolmBackupCreationInfo( algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, - authData = createFakeMegolmBackupAuthData() + authData = createFakeMegolmBackupAuthData(), + recoveryKey = "fake" ) } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt index ca8993fb00..606f57b467 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt @@ -115,9 +115,8 @@ class KeysBackupTest : InstrumentedTest { } assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, megolmBackupCreationInfo.algorithm) - assertNotNull(megolmBackupCreationInfo.authData) - assertNotNull(megolmBackupCreationInfo.authData!!.publicKey) - assertNotNull(megolmBackupCreationInfo.authData!!.signatures) + assertNotNull(megolmBackupCreationInfo.authData.publicKey) + assertNotNull(megolmBackupCreationInfo.authData.signatures) assertNotNull(megolmBackupCreationInfo.recoveryKey) stateObserver.stopAndCheckStates(null) @@ -258,14 +257,14 @@ class KeysBackupTest : InstrumentedTest { // - Check encryptGroupSession() returns stg val keyBackupData = keysBackup.encryptGroupSession(session) assertNotNull(keyBackupData) - assertNotNull(keyBackupData.sessionData) + assertNotNull(keyBackupData!!.sessionData) // - Check pkDecryptionFromRecoveryKey() is able to create a OlmPkDecryption val decryption = keysBackup.pkDecryptionFromRecoveryKey(keyBackupCreationInfo.recoveryKey) assertNotNull(decryption) // - Check decryptKeyBackupData() returns stg val sessionData = keysBackup - .decryptKeyBackupData(keyBackupData, + .decryptKeyBackupData(keyBackupData!!, session.olmInboundGroupSession!!.sessionIdentifier(), cryptoTestData.roomId, decryption!!) 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 3abbf9b16e..fbcf5cfdeb 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 @@ -30,7 +30,6 @@ import org.matrix.android.sdk.api.listeners.StepProgressListener 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.KeysBackupStateListener -import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.MegolmSessionData @@ -85,6 +84,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmBackupAuthData import org.matrix.olm.OlmException import org.matrix.olm.OlmPkDecryption import org.matrix.olm.OlmPkEncryption @@ -170,7 +170,7 @@ internal class DefaultKeysBackupService @Inject constructor( runCatching { withContext(coroutineDispatchers.crypto) { val olmPkDecryption = OlmPkDecryption() - val megolmBackupAuthData = if (password != null) { + val signalableMegolmBackupAuthData = if (password != null) { // Generate a private key from the password val backgroundProgressListener = if (progressListener == null) { null @@ -189,7 +189,7 @@ internal class DefaultKeysBackupService @Inject constructor( } val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener) - MegolmBackupAuthData( + SignalableMegolmBackupAuthData( publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey), privateKeySalt = generatePrivateKeyResult.salt, privateKeyIterations = generatePrivateKeyResult.iterations @@ -197,14 +197,17 @@ internal class DefaultKeysBackupService @Inject constructor( } else { val publicKey = olmPkDecryption.generateKey() - MegolmBackupAuthData( + SignalableMegolmBackupAuthData( publicKey = publicKey ) } - val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, megolmBackupAuthData.signalableJSONDictionary()) + val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableMegolmBackupAuthData.signalableJSONDictionary()) - val signedMegolmBackupAuthData = megolmBackupAuthData.copy( + val signedMegolmBackupAuthData = MegolmBackupAuthData( + publicKey = signalableMegolmBackupAuthData.publicKey, + privateKeySalt = signalableMegolmBackupAuthData.privateKeySalt, + privateKeyIterations = signalableMegolmBackupAuthData.privateKeyIterations, signatures = objectSigner.signObject(canonicalJson) ) @@ -223,8 +226,7 @@ internal class DefaultKeysBackupService @Inject constructor( @Suppress("UNCHECKED_CAST") val createKeysBackupVersionBody = CreateKeysBackupVersionBody( algorithm = keysBackupCreationInfo.algorithm, - authData = MoshiProvider.providesMoshi().adapter(Map::class.java) - .fromJson(keysBackupCreationInfo.authData?.toJsonString() ?: "") as JsonDict? + authData = keysBackupCreationInfo.authData.toJsonDict() ) keysBackupStateManager.state = KeysBackupState.Enabling @@ -245,7 +247,7 @@ internal class DefaultKeysBackupService @Inject constructor( version = data.version, // We can consider that the server does not have keys yet count = 0, - hash = null + hash = "" ) enableKeysBackup(keyBackupVersion) @@ -267,7 +269,7 @@ internal class DefaultKeysBackupService @Inject constructor( withContext(coroutineDispatchers.crypto) { // If we're currently backing up to this backup... stop. // (We start using it automatically in createKeysBackupVersion so this is symmetrical). - if (keysBackupVersion != null && version == keysBackupVersion!!.version) { + if (keysBackupVersion != null && version == keysBackupVersion?.version) { resetKeysBackupData() keysBackupVersion = null keysBackupStateManager.state = KeysBackupState.Unknown @@ -408,10 +410,7 @@ internal class DefaultKeysBackupService @Inject constructor( val keysBackupVersionTrust = KeysBackupVersionTrust() val authData = keysBackupVersion.getAuthDataAsMegolmBackupAuthData() - if (keysBackupVersion.algorithm == null - || authData == null - || authData.publicKey.isEmpty() - || authData.signatures.isNullOrEmpty()) { + if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isEmpty()) { Timber.v("getKeysBackupTrust: Key backup is absent or missing required data") return keysBackupVersionTrust } @@ -479,7 +478,7 @@ internal class DefaultKeysBackupService @Inject constructor( cryptoCoroutineScope.launch(coroutineDispatchers.main) { val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) { // Get current signatures, or create an empty set - val myUserSignatures = authData.signatures?.get(userId)?.toMutableMap() ?: HashMap() + val myUserSignatures = authData.signatures[userId].orEmpty().toMutableMap() if (trust) { // Add current device signature @@ -498,26 +497,23 @@ internal class DefaultKeysBackupService @Inject constructor( // Create an updated version of KeysVersionResult val newMegolmBackupAuthData = authData.copy() - val newSignatures = newMegolmBackupAuthData.signatures!!.toMutableMap() + val newSignatures = newMegolmBackupAuthData.signatures.toMutableMap() newSignatures[userId] = myUserSignatures val newMegolmBackupAuthDataWithNewSignature = newMegolmBackupAuthData.copy( signatures = newSignatures ) - val moshi = MoshiProvider.providesMoshi() - val adapter = moshi.adapter(Map::class.java) - @Suppress("UNCHECKED_CAST") UpdateKeysBackupVersionBody( algorithm = keysBackupVersion.algorithm, - authData = adapter.fromJson(newMegolmBackupAuthDataWithNewSignature.toJsonString()) as Map?, - version = keysBackupVersion.version!!) + authData = newMegolmBackupAuthDataWithNewSignature.toJsonDict(), + version = keysBackupVersion.version) } // And send it to the homeserver updateKeysBackupVersionTask - .configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version!!, updateKeysBackupVersionBody)) { + .configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version, updateKeysBackupVersionBody)) { this.callback = object : MatrixCallback { override fun onSuccess(data: Unit) { // Relaunch the state machine on this updated backup version @@ -688,7 +684,7 @@ internal class DefaultKeysBackupService @Inject constructor( stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey) // Get backed up keys from the homeserver - val data = getKeys(sessionId, roomId, keysVersionResult.version!!) + val data = getKeys(sessionId, roomId, keysVersionResult.version) withContext(coroutineDispatchers.computation) { val sessionsData = ArrayList() @@ -1023,19 +1019,10 @@ internal class DefaultKeysBackupService @Inject constructor( * @return the authentication if found and valid, null in other case */ private fun getMegolmBackupAuthData(keysBackupData: KeysVersionResult): MegolmBackupAuthData? { - if (keysBackupData.version.isNullOrBlank() - || keysBackupData.algorithm != MXCRYPTO_ALGORITHM_MEGOLM_BACKUP - || keysBackupData.authData == null) { - return null - } - - val authData = keysBackupData.getAuthDataAsMegolmBackupAuthData() - - if (authData?.signatures == null || authData.publicKey.isBlank()) { - return null - } - - return authData + return keysBackupData + .takeIf { it.version.isNotEmpty() && it.algorithm == MXCRYPTO_ALGORITHM_MEGOLM_BACKUP } + ?.getAuthDataAsMegolmBackupAuthData() + ?.takeIf { it.publicKey.isNotEmpty() } } /** @@ -1123,34 +1110,29 @@ internal class DefaultKeysBackupService @Inject constructor( * @param keysVersionResult backup information object as returned by [getCurrentVersion]. */ private fun enableKeysBackup(keysVersionResult: KeysVersionResult) { - if (keysVersionResult.authData != null) { - val retrievedMegolmBackupAuthData = keysVersionResult.getAuthDataAsMegolmBackupAuthData() + val retrievedMegolmBackupAuthData = keysVersionResult.getAuthDataAsMegolmBackupAuthData() - if (retrievedMegolmBackupAuthData != null) { - keysBackupVersion = keysVersionResult - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - cryptoStore.setKeyBackupVersion(keysVersionResult.version) - } - - onServerDataRetrieved(keysVersionResult.count, keysVersionResult.hash) - - try { - backupOlmPkEncryption = OlmPkEncryption().apply { - setRecipientKey(retrievedMegolmBackupAuthData.publicKey) - } - } catch (e: OlmException) { - Timber.e(e, "OlmException") - keysBackupStateManager.state = KeysBackupState.Disabled - return - } - - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp - - maybeBackupKeys() - } else { - Timber.e("Invalid authentication data") - keysBackupStateManager.state = KeysBackupState.Disabled + if (retrievedMegolmBackupAuthData != null) { + keysBackupVersion = keysVersionResult + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + cryptoStore.setKeyBackupVersion(keysVersionResult.version) } + + onServerDataRetrieved(keysVersionResult.count, keysVersionResult.hash) + + try { + backupOlmPkEncryption = OlmPkEncryption().apply { + setRecipientKey(retrievedMegolmBackupAuthData.publicKey) + } + } catch (e: OlmException) { + Timber.e(e, "OlmException") + keysBackupStateManager.state = KeysBackupState.Disabled + return + } + + keysBackupStateManager.state = KeysBackupState.ReadyToBackUp + + maybeBackupKeys() } else { Timber.e("Invalid authentication data") keysBackupStateManager.state = KeysBackupState.Disabled @@ -1160,11 +1142,11 @@ internal class DefaultKeysBackupService @Inject constructor( /** * Update the DB with data fetch from the server */ - private fun onServerDataRetrieved(count: Int?, hash: String?) { + private fun onServerDataRetrieved(count: Int?, etag: String?) { cryptoStore.setKeysBackupData(KeysBackupDataEntity() .apply { backupLastServerNumberOfKeys = count - backupLastServerHash = hash + backupLastServerHash = etag } ) } @@ -1179,6 +1161,7 @@ internal class DefaultKeysBackupService @Inject constructor( cryptoStore.setKeyBackupVersion(null) cryptoStore.setKeysBackupData(null) + backupOlmPkEncryption?.releaseEncryption() backupOlmPkEncryption = null // Reset backup markers @@ -1229,22 +1212,19 @@ internal class DefaultKeysBackupService @Inject constructor( // Gather data to send to the homeserver // roomId -> sessionId -> MXKeyBackupData - val keysBackupData = KeysBackupData( - roomIdToRoomKeysBackupData = HashMap() - ) + val keysBackupData = KeysBackupData() - for (olmInboundGroupSessionWrapper in olmInboundGroupSessionWrappers) { - val keyBackupData = encryptGroupSession(olmInboundGroupSessionWrapper) - if (keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId] == null) { - val roomKeysBackupData = RoomKeysBackupData( - sessionIdToKeyBackupData = HashMap() - ) - keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId!!] = roomKeysBackupData - } + olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper -> + val roomId = olmInboundGroupSessionWrapper.roomId ?: return@forEach + val olmInboundGroupSession = olmInboundGroupSessionWrapper.olmInboundGroupSession ?: return@forEach try { - keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId]!! - .sessionIdToKeyBackupData[olmInboundGroupSessionWrapper.olmInboundGroupSession!!.sessionIdentifier()] = keyBackupData + encryptGroupSession(olmInboundGroupSessionWrapper) + ?.let { + keysBackupData.roomIdToRoomKeysBackupData + .getOrPut(roomId) { RoomKeysBackupData() } + .sessionIdToKeyBackupData[olmInboundGroupSession.sessionIdentifier()] = it + } } catch (e: OlmException) { Timber.e(e, "OlmException") } @@ -1252,71 +1232,71 @@ internal class DefaultKeysBackupService @Inject constructor( Timber.v("backupKeys: 4 - Sending request") - val sendingRequestCallback = object : MatrixCallback { - override fun onSuccess(data: BackupKeysResult) { - uiHandler.post { - Timber.v("backupKeys: 5a - Request complete") + // Make the request + val version = keysBackupVersion?.version ?: return@withContext - // Mark keys as backed up - cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers) + storeSessionDataTask + .configureWith(StoreSessionsDataTask.Params(version, keysBackupData)) { + this.callback = object : MatrixCallback { + override fun onSuccess(data: BackupKeysResult) { + uiHandler.post { + Timber.v("backupKeys: 5a - Request complete") - if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) { - Timber.v("backupKeys: All keys have been backed up") - onServerDataRetrieved(data.count, data.hash) + // Mark keys as backed up + cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers) - // 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 + if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) { + Timber.v("backupKeys: All keys have been backed up") + onServerDataRetrieved(data.count, data.hash) - backupKeys() - } - } - } + // 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 - override fun onFailure(failure: Throwable) { - if (failure is Failure.ServerError) { - uiHandler.post { - 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() + backupKeys() + } + } + } + + override fun onFailure(failure: Throwable) { + if (failure is Failure.ServerError) { + uiHandler.post { + 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 + } + } + } else { + uiHandler.post { + backupAllGroupSessionsCallback?.onFailure(failure) + resetBackupAllGroupSessionsListeners() + + Timber.e("backupKeys: backupKeys failed.") + + // Retry a bit later + keysBackupStateManager.state = KeysBackupState.ReadyToBackUp + maybeBackupKeys() + } } - else -> - // Come back to the ready state so that we will retry on the next received key - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp } } - } else { - uiHandler.post { - backupAllGroupSessionsCallback?.onFailure(failure) - resetBackupAllGroupSessionsListeners() - - Timber.e("backupKeys: backupKeys failed.") - - // Retry a bit later - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp - maybeBackupKeys() - } - } - } - } - - // Make the request - storeSessionDataTask - .configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData)) { - this.callback = sendingRequestCallback } .executeBy(taskExecutor) } @@ -1325,47 +1305,45 @@ internal class DefaultKeysBackupService @Inject constructor( @VisibleForTesting @WorkerThread - fun encryptGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2): KeyBackupData { + fun encryptGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2): KeyBackupData? { // Gather information for each key - val device = cryptoStore.deviceWithIdentityKey(olmInboundGroupSessionWrapper.senderKey!!) + val device = olmInboundGroupSessionWrapper.senderKey?.let { cryptoStore.deviceWithIdentityKey(it) } // Build the m.megolm_backup.v1.curve25519-aes-sha2 data as defined at // https://github.com/uhoreg/matrix-doc/blob/e2e_backup/proposals/1219-storing-megolm-keys-serverside.md#mmegolm_backupv1curve25519-aes-sha2-key-format - val sessionData = olmInboundGroupSessionWrapper.exportKeys() + val sessionData = olmInboundGroupSessionWrapper.exportKeys() ?: return null val sessionBackupData = mapOf( - "algorithm" to sessionData!!.algorithm, + "algorithm" to sessionData.algorithm, "sender_key" to sessionData.senderKey, "sender_claimed_keys" to sessionData.senderClaimedKeys, - "forwarding_curve25519_key_chain" to (sessionData.forwardingCurve25519KeyChain - ?: ArrayList()), + "forwarding_curve25519_key_chain" to (sessionData.forwardingCurve25519KeyChain.orEmpty()), "session_key" to sessionData.sessionKey) - var encryptedSessionBackupData: OlmPkMessage? = null + val json = MoshiProvider.providesMoshi() + .adapter(Map::class.java) + .toJson(sessionBackupData) - val moshi = MoshiProvider.providesMoshi() - val adapter = moshi.adapter(Map::class.java) - - try { - val json = adapter.toJson(sessionBackupData) - - encryptedSessionBackupData = backupOlmPkEncryption?.encrypt(json) + val encryptedSessionBackupData = try { + backupOlmPkEncryption?.encrypt(json) } catch (e: OlmException) { Timber.e(e, "OlmException") + null } + ?: return null // Build backup data for that key return KeyBackupData( firstMessageIndex = try { - olmInboundGroupSessionWrapper.olmInboundGroupSession!!.firstKnownIndex + olmInboundGroupSessionWrapper.olmInboundGroupSession?.firstKnownIndex ?: 0 } catch (e: OlmException) { Timber.e(e, "OlmException") 0L }, - forwardedCount = olmInboundGroupSessionWrapper.forwardingCurve25519KeyChain!!.size, + forwardedCount = olmInboundGroupSessionWrapper.forwardingCurve25519KeyChain.orEmpty().size, isVerified = device?.isVerified == true, sessionData = mapOf( - "ciphertext" to encryptedSessionBackupData!!.mCipherText, + "ciphertext" to encryptedSessionBackupData.mCipherText, "mac" to encryptedSessionBackupData.mMac, "ephemeral" to encryptedSessionBackupData.mEphemeralKey) ) @@ -1378,9 +1356,9 @@ internal class DefaultKeysBackupService @Inject constructor( val jsonObject = keyBackupData.sessionData - val ciphertext = jsonObject?.get("ciphertext")?.toString() - val mac = jsonObject?.get("mac")?.toString() - val ephemeralKey = jsonObject?.get("ephemeral")?.toString() + val ciphertext = jsonObject["ciphertext"]?.toString() + val mac = jsonObject["mac"]?.toString() + val ephemeralKey = jsonObject["ephemeral"]?.toString() if (ciphertext != null && mac != null && ephemeralKey != null) { val encrypted = OlmPkMessage() @@ -1425,8 +1403,7 @@ internal class DefaultKeysBackupService @Inject constructor( @Suppress("UNCHECKED_CAST") val createKeysBackupVersionBody = CreateKeysBackupVersionBody( algorithm = keysBackupCreationInfo.algorithm, - authData = MoshiProvider.providesMoshi().adapter(Map::class.java) - .fromJson(keysBackupCreationInfo.authData?.toJsonString() ?: "") as JsonDict? + authData = keysBackupCreationInfo.authData.toJsonDict() ) createKeysBackupVersionTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt index ed5383d6eb..3f8333528f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt @@ -35,7 +35,7 @@ import retrofit2.http.Path import retrofit2.http.Query /** - * Ref: https://github.com/uhoreg/matrix-doc/blob/e2e_backup/proposals/1219-storing-megolm-keys-serverside.md + * Ref: https://matrix.org/docs/spec/client_server/unstable#server-side-key-backups */ internal interface RoomKeysApi { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt index 9df5f29294..54b92546e9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.di.MoshiProvider /** @@ -30,7 +31,7 @@ data class MegolmBackupAuthData( * The curve25519 public key used to encrypt the backups. */ @Json(name = "public_key") - val publicKey: String = "", + val publicKey: String, /** * In case of a backup created from a password, the salt associated with the backup @@ -50,20 +51,38 @@ data class MegolmBackupAuthData( * userId -> (deviceSignKeyId -> signature) */ @Json(name = "signatures") - val signatures: Map>? = null + val signatures: Map> ) { - fun toJsonString(): String { - return MoshiProvider.providesMoshi() + fun toJsonDict(): JsonDict { + val moshi = MoshiProvider.providesMoshi() + val adapter = moshi.adapter(Map::class.java) + + return moshi .adapter(MegolmBackupAuthData::class.java) .toJson(this) + .let { + @Suppress("UNCHECKED_CAST") + adapter.fromJson(it) as JsonDict + } } - /** - * Same as the parent [MXJSONModel JSONDictionary] but return only - * data that must be signed. - */ - fun signalableJSONDictionary(): Map = HashMap().apply { + fun signalableJSONDictionary(): JsonDict { + return SignalableMegolmBackupAuthData( + publicKey = publicKey, + privateKeySalt = privateKeySalt, + privateKeyIterations = privateKeyIterations + ) + .signalableJSONDictionary() + } +} + +internal data class SignalableMegolmBackupAuthData( + val publicKey: String, + val privateKeySalt: String? = null, + val privateKeyIterations: Int? = null +) { + fun signalableJSONDictionary(): JsonDict = HashMap().apply { put("public_key", publicKey) privateKeySalt?.let { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupCreationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupCreationInfo.kt index 1414d0e0d7..c668e78a9e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupCreationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupCreationInfo.kt @@ -23,15 +23,15 @@ data class MegolmBackupCreationInfo( /** * The algorithm used for storing backups [org.matrix.androidsdk.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP]. */ - val algorithm: String = "", + val algorithm: String, /** * Authentication data. */ - val authData: MegolmBackupAuthData? = null, + val authData: MegolmBackupAuthData, /** * The Base58 recovery key. */ - val recoveryKey: String = "" + val recoveryKey: String ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/BackupKeysResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/BackupKeysResult.kt index a84ba7427b..3710a2d7d9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/BackupKeysResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/BackupKeysResult.kt @@ -16,15 +16,16 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.model.rest +import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -data class BackupKeysResult( - +internal data class BackupKeysResult( // The hash value which is an opaque string representing stored keys in the backup - var hash: String? = null, + @Json(name = "etag") + val hash: String, // The number of keys stored in the backup. - var count: Int? = null - + @Json(name = "count") + val count: Int ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/CreateKeysBackupVersionBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/CreateKeysBackupVersionBody.kt index a7831b38f1..a6bd8f8aaa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/CreateKeysBackupVersionBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/CreateKeysBackupVersionBody.kt @@ -21,17 +21,17 @@ import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.util.JsonDict @JsonClass(generateAdapter = true) -data class CreateKeysBackupVersionBody( +internal data class CreateKeysBackupVersionBody( /** * The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined */ @Json(name = "algorithm") - override val algorithm: String? = null, + override val algorithm: String, /** * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" * see [org.matrix.android.sdk.internal.crypto.keysbackup.MegolmBackupAuthData] */ @Json(name = "auth_data") - override val authData: JsonDict? = null + override val authData: JsonDict ) : KeysAlgorithmAndData diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt index 46eaa586a7..3f8129b8f6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.network.parsing.ForceToBoolean /** @@ -30,13 +30,13 @@ data class KeyBackupData( * Required. The index of the first message in the session that the key can decrypt. */ @Json(name = "first_message_index") - val firstMessageIndex: Long = 0, + val firstMessageIndex: Long, /** * Required. The number of times this key has been forwarded. */ @Json(name = "forwarded_count") - val forwardedCount: Int = 0, + val forwardedCount: Int, /** * Whether the device backing up the key has verified the device that the key is from. @@ -44,16 +44,11 @@ data class KeyBackupData( */ @ForceToBoolean @Json(name = "is_verified") - val isVerified: Boolean = false, + val isVerified: Boolean, /** * Algorithm-dependent data. */ @Json(name = "session_data") - val sessionData: Map? = null -) { - - fun toJsonString(): String { - return MoshiProvider.providesMoshi().adapter(KeyBackupData::class.java).toJson(this) - } -} + val sessionData: JsonDict +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysAlgorithmAndData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysAlgorithmAndData.kt index 117d4dce70..e098aa0440 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysAlgorithmAndData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysAlgorithmAndData.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.model.rest import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData import org.matrix.android.sdk.internal.di.MoshiProvider @@ -37,24 +38,25 @@ import org.matrix.android.sdk.internal.di.MoshiProvider * } * */ -interface KeysAlgorithmAndData { +internal interface KeysAlgorithmAndData { /** * The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined */ - val algorithm: String? + val algorithm: String /** * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" see [org.matrix.android.sdk.internal.crypto.keysbackup.MegolmBackupAuthData] */ - val authData: JsonDict? + val authData: JsonDict /** * Facility method to convert authData to a MegolmBackupAuthData object */ fun getAuthDataAsMegolmBackupAuthData(): MegolmBackupAuthData? { return MoshiProvider.providesMoshi() - .adapter(MegolmBackupAuthData::class.java) - .fromJsonValue(authData) + .takeIf { algorithm == MXCRYPTO_ALGORITHM_MEGOLM_BACKUP } + ?.adapter(MegolmBackupAuthData::class.java) + ?.fromJsonValue(authData) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysVersion.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysVersion.kt index 146c98b017..7a4c3415fc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysVersion.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysVersion.kt @@ -23,5 +23,5 @@ import com.squareup.moshi.JsonClass data class KeysVersion( // the keys backup version @Json(name = "version") - val version: String? = null + val version: String ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysVersionResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysVersionResult.kt index 0844c58d2e..485fd48a8c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysVersionResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysVersionResult.kt @@ -26,24 +26,24 @@ data class KeysVersionResult( * The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined */ @Json(name = "algorithm") - override val algorithm: String? = null, + override val algorithm: String, /** * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" * see [org.matrix.android.sdk.internal.crypto.keysbackup.MegolmBackupAuthData] */ @Json(name = "auth_data") - override val authData: JsonDict? = null, + override val authData: JsonDict, // the backup version @Json(name = "version") - val version: String? = null, + val version: String, // The hash value which is an opaque string representing stored keys in the backup - @Json(name = "hash") - val hash: String? = null, + @Json(name = "etag") + val hash: String, // The number of keys stored in the backup. @Json(name = "count") - val count: Int? = null + val count: Int ) : KeysAlgorithmAndData diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt index 65f0c1a845..4512ed7a55 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt @@ -26,16 +26,16 @@ data class UpdateKeysBackupVersionBody( * The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined */ @Json(name = "algorithm") - override val algorithm: String? = null, + override val algorithm: String, /** * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" * see [org.matrix.android.sdk.internal.crypto.keysbackup.MegolmBackupAuthData] */ @Json(name = "auth_data") - override val authData: JsonDict? = null, + override val authData: JsonDict, - // the backup version, mandatory + // Optional. The backup version. If present, must be the same as the path parameter. @Json(name = "version") - val version: String + val version: String? = null ) : KeysAlgorithmAndData diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt index 478d55d077..1dc27c75ca 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt @@ -48,15 +48,12 @@ class OlmInboundGroupSessionWrapper2 : Serializable { */ val firstKnownIndex: Long? get() { - if (null != olmInboundGroupSession) { - try { - return olmInboundGroupSession!!.firstKnownIndex - } catch (e: Exception) { - Timber.e(e, "## getFirstKnownIndex() : getFirstKnownIndex failed") - } + return try { + olmInboundGroupSession?.firstKnownIndex + } catch (e: Exception) { + Timber.e(e, "## getFirstKnownIndex() : getFirstKnownIndex failed") + null } - - return null } /** @@ -90,11 +87,13 @@ class OlmInboundGroupSessionWrapper2 : Serializable { @Throws(Exception::class) constructor(megolmSessionData: MegolmSessionData) { try { - olmInboundGroupSession = OlmInboundGroupSession.importSession(megolmSessionData.sessionKey!!) - - if (olmInboundGroupSession!!.sessionIdentifier() != megolmSessionData.sessionId) { - throw Exception("Mismatched group session Id") - } + val safeSessionKey = megolmSessionData.sessionKey ?: throw Exception("invalid data") + olmInboundGroupSession = OlmInboundGroupSession.importSession(safeSessionKey) + .also { + if (it.sessionIdentifier() != megolmSessionData.sessionId) { + throw Exception("Mismatched group session Id") + } + } senderKey = megolmSessionData.senderKey keysClaimed = megolmSessionData.senderClaimedKeys @@ -120,16 +119,18 @@ class OlmInboundGroupSessionWrapper2 : Serializable { return null } - val wantedIndex = index ?: olmInboundGroupSession!!.firstKnownIndex + val safeOlmInboundGroupSession = olmInboundGroupSession ?: return null + + val wantedIndex = index ?: safeOlmInboundGroupSession.firstKnownIndex MegolmSessionData( senderClaimedEd25519Key = keysClaimed?.get("ed25519"), - forwardingCurve25519KeyChain = ArrayList(forwardingCurve25519KeyChain!!), + forwardingCurve25519KeyChain = forwardingCurve25519KeyChain?.toList().orEmpty(), senderKey = senderKey, senderClaimedKeys = keysClaimed, roomId = roomId, - sessionId = olmInboundGroupSession!!.sessionIdentifier(), - sessionKey = olmInboundGroupSession!!.export(wantedIndex), + sessionId = safeOlmInboundGroupSession.sessionIdentifier(), + sessionKey = safeOlmInboundGroupSession.export(wantedIndex), algorithm = MXCRYPTO_ALGORITHM_MEGOLM ) } catch (e: Exception) { @@ -145,14 +146,11 @@ class OlmInboundGroupSessionWrapper2 : Serializable { * @return the exported data */ fun exportSession(messageIndex: Long): String? { - if (null != olmInboundGroupSession) { - try { - return olmInboundGroupSession!!.export(messageIndex) - } catch (e: Exception) { - Timber.e(e, "## exportSession() : export failed") - } + return try { + return olmInboundGroupSession?.export(messageIndex) + } catch (e: Exception) { + Timber.e(e, "## exportSession() : export failed") + null } - - return null } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt index 72b767e12f..2e5097fdb7 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt @@ -103,7 +103,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( } else { // we need to get existing backup passphrase/key and convert to SSSS val keyVersion = awaitCallback { - session.cryptoService().keysBackupService().getVersion(version.version ?: "", it) + session.cryptoService().keysBackupService().getVersion(version.version, it) } if (keyVersion == null) { // strange case... just finish?