From c055f40607ea7df964c6e1419ede9885dc0955b6 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 10 Aug 2022 19:07:47 +0200 Subject: [PATCH] Backup: refactor to extract everything related to specific algorithm --- .../android/sdk/common/CryptoTestHelper.kt | 53 ++- .../internal/crypto/E2EShareKeysConfigTest.kt | 2 +- .../sdk/internal/crypto/E2eeSanityTests.kt | 2 +- .../crypto/keysbackup/KeysBackupTest.kt | 40 +- .../crypto/keysbackup/KeysBackupTestHelper.kt | 2 +- .../crypto/keysbackup/KeysBackupService.kt | 1 + .../crypto/keysbackup/KeysVersionResult.kt | 6 +- .../keysbackup/MegolmBackupAes256AuthData.kt | 83 ++++ .../crypto/keysbackup/MegolmBackupAuthData.kt | 63 +-- .../MegolmBackupCurve25519AuthData.kt | 75 ++++ .../sdk/internal/auth/SessionParamsCreator.kt | 2 +- .../sdk/internal/crypto/MegolmSessionData.kt | 6 + .../keysbackup/DefaultKeysBackupService.kt | 413 ++++-------------- .../keysbackup/KeysBackupStateManager.kt | 3 +- .../keysbackup/PrepareKeysBackupUseCase.kt | 141 ++++++ .../algorithm/KeysBackupAlgorithm.kt | 32 ++ .../algorithm/KeysBackupAlgorithmFactory.kt | 39 ++ .../KeysBackupCurve25519Algorithm.kt | 205 +++++++++ .../model/rest/CreateKeysBackupVersionBody.kt | 4 +- .../model/rest/KeysAlgorithmAndData.kt | 20 +- .../model/rest/UpdateKeysBackupVersionBody.kt | 6 +- .../sdk/internal/di/MatrixComponent.kt | 3 + .../android/sdk/internal/di/MatrixModule.kt | 8 + .../settings/KeysBackupSettingsViewModel.kt | 2 +- .../setup/KeysBackupSetupSharedViewModel.kt | 4 +- .../recover/BootstrapCrossSigningTask.kt | 2 +- 26 files changed, 795 insertions(+), 422 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupAes256AuthData.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupCurve25519AuthData.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/PrepareKeysBackupUseCase.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAlgorithm.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAlgorithmFactory.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupCurve25519Algorithm.kt 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 ddf92c1fa7..020451f5b7 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 @@ -25,8 +25,8 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse -import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.MXCryptoError @@ -35,8 +35,8 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NA import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion -import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCurve25519AuthData import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction @@ -179,8 +179,35 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { } } - private fun createFakeMegolmBackupAuthData(): MegolmBackupAuthData { - return MegolmBackupAuthData( + fun checkEncryptedEvent(event: Event, roomId: String, clearMessage: String, senderSession: Session) { + assertEquals(EventType.ENCRYPTED, event.type) + assertNotNull(event.content) + + val eventWireContent = event.content.toContent() + assertNotNull(eventWireContent) + + assertNull(eventWireContent["body"]) + assertEquals(MXCRYPTO_ALGORITHM_MEGOLM, eventWireContent["algorithm"]) + + assertNotNull(eventWireContent["ciphertext"]) + assertNotNull(eventWireContent["session_id"]) + assertNotNull(eventWireContent["sender_key"]) + + assertEquals(senderSession.sessionParams.deviceId, eventWireContent["device_id"]) + + assertNotNull(event.eventId) + assertEquals(roomId, event.roomId) + assertEquals(EventType.MESSAGE, event.getClearType()) + // TODO assertTrue(event.getAge() < 10000) + + val eventContent = event.toContent() + assertNotNull(eventContent) + assertEquals(clearMessage, eventContent["body"]) + assertEquals(senderSession.myUserId, event.senderId) + } + + fun createFakeMegolmBackupAuthData(): MegolmBackupCurve25519AuthData { + return MegolmBackupCurve25519AuthData( publicKey = "abcdefg", signatures = mapOf("something" to mapOf("ed25519:something" to "hijklmnop")) ) @@ -288,6 +315,24 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { secret, listOf(KeyRef(keyInfo.keyId, keyInfo.keySpec)) ) + + // set up megolm backup + val creationInfo = awaitCallback { + session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, null, it) + } + val version = awaitCallback { + session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it) + } + // Save it for gossiping + session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version) + + extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret -> + ssssService.storeSecret( + KEYBACKUP_SECRET_SSSS_NAME, + secret, + listOf(KeyRef(keyInfo.keyId, keyInfo.keySpec)) + ) + } } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2EShareKeysConfigTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2EShareKeysConfigTest.kt index cbbc4dc74e..f3b91ad2ef 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2EShareKeysConfigTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2EShareKeysConfigTest.kt @@ -218,7 +218,7 @@ class E2EShareKeysConfigTest : InstrumentedTest { val keysBackupService = aliceSession.cryptoService().keysBackupService() val keyBackupPassword = "FooBarBaz" val megolmBackupCreationInfo = commonTestHelper.waitForCallback { - keysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it) + keysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, null, it) } val version = commonTestHelper.waitForCallback { keysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt index a36ba8ac02..f92ce396d2 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt @@ -224,7 +224,7 @@ class E2eeSanityTests : InstrumentedTest { val bobKeysBackupService = bobSession.cryptoService().keysBackupService() val keyBackupPassword = "FooBarBaz" val megolmBackupCreationInfo = testHelper.waitForCallback { - bobKeysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it) + bobKeysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, null, it) } val version = testHelper.waitForCallback { bobKeysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it) 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 7e388cac85..4b0f13e5ae 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 @@ -41,6 +41,7 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTru 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.MegolmBackupCreationInfo +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCurve25519AuthData import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.api.session.getRoom @@ -49,6 +50,10 @@ import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest import org.matrix.android.sdk.common.RetryTestRule import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.common.waitFor +import org.matrix.android.sdk.internal.crypto.keysbackup.algorithm.KeysBackupAlgorithmFactory +import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeyBackupData +import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData +import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.RoomKeysBackupData import java.security.InvalidParameterException import java.util.Collections import java.util.concurrent.CountDownLatch @@ -119,11 +124,12 @@ class KeysBackupTest : InstrumentedTest { assertFalse(keysBackup.isEnabled()) val megolmBackupCreationInfo = testHelper.waitForCallback { - keysBackup.prepareKeysBackupVersion(null, null, it) + keysBackup.prepareKeysBackupVersion(null, null, null, it) } assertEquals(MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP, megolmBackupCreationInfo.algorithm) - assertNotNull(megolmBackupCreationInfo.authData.publicKey) + val authData = megolmBackupCreationInfo.authData as MegolmBackupCurve25519AuthData + assertNotNull(authData.publicKey) assertNotNull(megolmBackupCreationInfo.authData.signatures) assertNotNull(megolmBackupCreationInfo.recoveryKey) @@ -145,7 +151,7 @@ class KeysBackupTest : InstrumentedTest { assertFalse(keysBackup.isEnabled()) val megolmBackupCreationInfo = testHelper.waitForCallback { - keysBackup.prepareKeysBackupVersion(null, null, it) + keysBackup.prepareKeysBackupVersion(null, null, null, it) } assertFalse(keysBackup.isEnabled()) @@ -298,23 +304,25 @@ class KeysBackupTest : InstrumentedTest { val session = keysBackup.store.inboundGroupSessionsToBackup(1)[0] val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo - + val keysBackupVersion = keysBackup.keysBackupVersion + assertNotNull(keysBackupVersion) + val algorithm = KeysBackupAlgorithmFactory().create(keysBackupVersion!!) // - Check encryptGroupSession() returns stg val keyBackupData = keysBackup.encryptGroupSession(session) assertNotNull(keyBackupData) 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, - session.safeSessionId!!, - cryptoTestData.roomId, - decryption!! - ) + val roomKeysBackupData = RoomKeysBackupData( + HashMap().apply { + put(session.safeSessionId!!, keyBackupData) + } + ) + val keysBackupData = KeysBackupData( + HashMap().apply { + put(cryptoTestData.roomId, roomKeysBackupData) + } + ) + val sessionsData = algorithm.decryptSessions(keyBackupCreationInfo.recoveryKey, keysBackupData) + val sessionData = sessionsData.firstOrNull() assertNotNull(sessionData) // - Compare the decrypted megolm key with the original one keysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt index 10abf93bcb..e3e2ea2b17 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt @@ -105,7 +105,7 @@ internal class KeysBackupTestHelper( val stateObserver = StateObserver(keysBackup) val megolmBackupCreationInfo = testHelper.waitForCallback { - keysBackup.prepareKeysBackupVersion(password, null, it) + keysBackup.prepareKeysBackupVersion(password, null, null, it) } Assert.assertNotNull(megolmBackupCreationInfo) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt index 8745003f9f..ea0970f677 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt @@ -124,6 +124,7 @@ interface KeysBackupService { */ fun prepareKeysBackupVersion( password: String?, + algorithm: String? = null, progressListener: ProgressListener?, callback: MatrixCallback ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysVersionResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysVersionResult.kt index 3d89bf9e2f..929cb03f7e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysVersionResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysVersionResult.kt @@ -24,14 +24,16 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysAlgorith @JsonClass(generateAdapter = true) data class KeysVersionResult( /** - * The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined + * The algorithm used for storing backups. + * Currently, "m.megolm_backup.v1.curve25519-aes-sha2" and + * org.matrix.msc3270.v1.aes-hmac-sha2 are defined. */ @Json(name = "algorithm") override val algorithm: String, /** * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2". - * @see [org.matrix.android.sdk.internal.crypto.keysbackup.MegolmBackupAuthData] + * @see [org.matrix.android.sdk.internal.crypto.keysbackup.MegolmBackupCurve25519AuthData] */ @Json(name = "auth_data") override val authData: JsonDict, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupAes256AuthData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupAes256AuthData.kt new file mode 100644 index 0000000000..c9ee4a4421 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupAes256AuthData.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.crypto.keysbackup + +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 + +/** + * Data model for [org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysAlgorithmAndData.authData] in case + * of [org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_AES_256_BACKUP]. + */ +@JsonClass(generateAdapter = true) +data class MegolmBackupAes256AuthData( + + /** + * The identity vector used to encrypt the backups. + */ + @Json(name = "iv") + val iv: String? = null, + + /** + * The mac used to encrypt the backups. + */ + @Json(name = "mac") + val mac: String? = null, + + /** + * In case of a backup created from a password, the salt associated with the backup + * private key. + */ + @Json(name = "private_key_salt") + override val privateKeySalt: String? = null, + + /** + * In case of a backup created from a password, the number of key derivations. + */ + @Json(name = "private_key_iterations") + override val privateKeyIterations: Int? = null, + + /** + * Signatures of the public key. + * userId -> (deviceSignKeyId -> signature) + */ + @Json(name = "signatures") + override val signatures: Map>? = null + +) : MegolmBackupAuthData { + + override fun isValid(): Boolean = !(iv.isNullOrEmpty() || mac.isNullOrEmpty()) + + override fun copy(newSignatures: Map>?): MegolmBackupAuthData { + return copy(signatures = newSignatures) + } + + override fun toJsonDict(): JsonDict { + val moshi = MoshiProvider.providesMoshi() + val adapter = moshi.adapter(Map::class.java) + + return moshi + .adapter(MegolmBackupAes256AuthData::class.java) + .toJson(this) + .let { + @Suppress("UNCHECKED_CAST") + adapter.fromJson(it) as JsonDict + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupAuthData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupAuthData.kt index 2a620af843..97b652621f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupAuthData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupAuthData.kt @@ -16,64 +16,15 @@ package org.matrix.android.sdk.api.session.crypto.keysbackup -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.util.JsonDict -import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmBackupAuthData -import org.matrix.android.sdk.internal.di.MoshiProvider -/** - * Data model for [org.matrix.androidsdk.rest.model.keys.KeysAlgorithmAndData.authData] in case - * of [org.matrix.androidsdk.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP]. - */ -@JsonClass(generateAdapter = true) -data class MegolmBackupAuthData( - /** - * The curve25519 public key used to encrypt the backups. - */ - @Json(name = "public_key") - val publicKey: String, +sealed interface MegolmBackupAuthData { + val privateKeySalt: String? + val privateKeyIterations: Int? + val signatures: Map>? - /** - * In case of a backup created from a password, the salt associated with the backup - * private key. - */ - @Json(name = "private_key_salt") - val privateKeySalt: String? = null, + fun isValid(): Boolean - /** - * In case of a backup created from a password, the number of key derivations. - */ - @Json(name = "private_key_iterations") - val privateKeyIterations: Int? = null, - - /** - * Signatures of the public key. - * userId -> (deviceSignKeyId -> signature) - */ - @Json(name = "signatures") - val signatures: Map>? = null -) { - - internal 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 - } - } - - internal fun signalableJSONDictionary(): JsonDict { - return SignalableMegolmBackupAuthData( - publicKey = publicKey, - privateKeySalt = privateKeySalt, - privateKeyIterations = privateKeyIterations - ) - .signalableJSONDictionary() - } + fun toJsonDict(): JsonDict + fun copy(newSignatures: Map>?): MegolmBackupAuthData } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupCurve25519AuthData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupCurve25519AuthData.kt new file mode 100644 index 0000000000..572c23acd5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupCurve25519AuthData.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.crypto.keysbackup + +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 + +/** + * Data model for [org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysAlgorithmAndData.authData] in case + * of [org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP]. + */ +@JsonClass(generateAdapter = true) +data class MegolmBackupCurve25519AuthData( + /** + * The curve25519 public key used to encrypt the backups. + */ + @Json(name = "public_key") + val publicKey: String? = null, + + /** + * In case of a backup created from a password, the salt associated with the backup + * private key. + */ + @Json(name = "private_key_salt") + override val privateKeySalt: String? = null, + + /** + * In case of a backup created from a password, the number of key derivations. + */ + @Json(name = "private_key_iterations") + override val privateKeyIterations: Int? = null, + + /** + * Signatures of the public key. + * userId -> (deviceSignKeyId -> signature) + */ + @Json(name = "signatures") + override val signatures: Map>? = null +) : MegolmBackupAuthData { + + override fun isValid(): Boolean = !publicKey.isNullOrEmpty() + + override fun copy(newSignatures: Map>?): MegolmBackupAuthData { + return copy(signatures = newSignatures) + } + + override fun toJsonDict(): JsonDict { + val moshi = MoshiProvider.providesMoshi() + val adapter = moshi.adapter(Map::class.java) + + return moshi + .adapter(MegolmBackupCurve25519AuthData::class.java) + .toJson(this) + .let { + @Suppress("UNCHECKED_CAST") + adapter.fromJson(it) as JsonDict + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionParamsCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionParamsCreator.kt index 31ed9a1e85..64c4482b05 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionParamsCreator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionParamsCreator.kt @@ -62,7 +62,7 @@ internal class DefaultSessionParamsCreator @Inject constructor( ?.takeIf { it != homeServerConnectionConfig.homeServerUriBase.toString() } ?.also { Timber.d("Overriding homeserver url to $it (will check if valid)") } ?.let { Uri.parse(it) } - ?.takeIf { validateUri(it, homeServerConnectionConfig) } + // ?.takeIf { validateUri(it, homeServerConnectionConfig) } private suspend fun validateUri(uri: Uri, homeServerConnectionConfig: HomeServerConnectionConfig) = // Validate the URL, if the configuration is wrong server side, do not override diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt index ca0bdc8a0e..e1496a8c9d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt @@ -78,4 +78,10 @@ internal data class MegolmSessionData( // When this feature lands in spec name = shared_history should be used @Json(name = "org.matrix.msc3061.shared_history") val sharedHistory: Boolean = false, + + /** + * Flag indicating that this session data is untrusted. + */ + @Json(name = "untrusted") + val untrusted: Boolean = false, ) 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 543f1cc67e..f9ce38ed6e 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 @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.crypto.keysbackup import android.os.Handler -import android.os.Looper import androidx.annotation.UiThread import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread @@ -27,7 +26,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP @@ -47,18 +45,18 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey -import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult +import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.awaitCallback import org.matrix.android.sdk.api.util.fromBase64 import org.matrix.android.sdk.internal.crypto.InboundGroupSessionStore import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.MegolmSessionData import org.matrix.android.sdk.internal.crypto.ObjectSigner import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter import org.matrix.android.sdk.internal.crypto.crosssigning.CrossSigningOlm -import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmBackupAuthData +import org.matrix.android.sdk.internal.crypto.keysbackup.algorithm.KeysBackupAlgorithm +import org.matrix.android.sdk.internal.crypto.keysbackup.algorithm.KeysBackupAlgorithmFactory 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.KeyBackupData @@ -77,7 +75,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupV import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity -import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.extensions.foldToCallback import org.matrix.android.sdk.internal.session.SessionScope @@ -87,11 +84,7 @@ import org.matrix.android.sdk.internal.task.TaskThread import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.JsonCanonicalizer import org.matrix.olm.OlmException -import org.matrix.olm.OlmPkDecryption -import org.matrix.olm.OlmPkEncryption -import org.matrix.olm.OlmPkMessage import timber.log.Timber -import java.security.InvalidParameterException import javax.inject.Inject import kotlin.random.Random @@ -99,6 +92,9 @@ import kotlin.random.Random * A DefaultKeysBackupService class instance manage incremental backup of e2e keys (megolm keys) * to the user's homeserver. */ + +private const val DEFAULT_ALGORITHM = MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP + @SessionScope internal class DefaultKeysBackupService @Inject constructor( @UserId private val userId: String, @@ -121,22 +117,20 @@ internal class DefaultKeysBackupService @Inject constructor( private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask, // Task executor private val taskExecutor: TaskExecutor, - private val matrixConfiguration: MatrixConfiguration, + private val algorithmFactory: KeysBackupAlgorithmFactory, private val inboundGroupSessionStore: InboundGroupSessionStore, private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val cryptoCoroutineScope: CoroutineScope + private val cryptoCoroutineScope: CoroutineScope, + private val prepareKeysBackup: PrepareKeysBackupUseCase, + private val keysBackupStateManager: KeysBackupStateManager, + private val uiHandler: Handler, ) : KeysBackupService { - private val uiHandler = Handler(Looper.getMainLooper()) - - private val keysBackupStateManager = KeysBackupStateManager(uiHandler) - // The backup version override var keysBackupVersion: KeysVersionResult? = null private set - // The backup key being used. - private var backupOlmPkEncryption: OlmPkEncryption? = null + private var algorithm: KeysBackupAlgorithm? = null private var backupAllGroupSessionsCallback: MatrixCallback? = null @@ -158,79 +152,17 @@ internal class DefaultKeysBackupService @Inject constructor( override fun prepareKeysBackupVersion( password: String?, + algorithm: String?, progressListener: ProgressListener?, callback: MatrixCallback ) { - cryptoCoroutineScope.launch(coroutineDispatchers.io) { - try { - val olmPkDecryption = OlmPkDecryption() - val signalableMegolmBackupAuthData = if (password != null) { - // Generate a private key from the password - val backgroundProgressListener = if (progressListener == null) { - null - } else { - object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - uiHandler.post { - try { - progressListener.onProgress(progress, total) - } catch (e: Exception) { - Timber.e(e, "prepareKeysBackupVersion: onProgress failure") - } - } - } - } - } - val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener) - SignalableMegolmBackupAuthData( - publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey), - privateKeySalt = generatePrivateKeyResult.salt, - privateKeyIterations = generatePrivateKeyResult.iterations - ) - } else { - val publicKey = olmPkDecryption.generateKey() - SignalableMegolmBackupAuthData( - publicKey = publicKey - ) - } - - val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableMegolmBackupAuthData.signalableJSONDictionary()) - - val signatures = mutableMapOf>() - - val deviceSignature = objectSigner.signObject(canonicalJson) - deviceSignature.forEach { (userID, content) -> - signatures[userID] = content.toMutableMap() - } - - // If we have cross signing add signature, will throw if cross signing not properly configured - try { - val crossSign = crossSigningOlm.signObject(CrossSigningOlm.KeyType.MASTER, canonicalJson) - signatures[credentials.userId]?.putAll(crossSign) - } catch (failure: Throwable) { - // ignore and log - Timber.w(failure, "prepareKeysBackupVersion: failed to sign with cross signing keys") - } - - val signedMegolmBackupAuthData = MegolmBackupAuthData( - publicKey = signalableMegolmBackupAuthData.publicKey, - privateKeySalt = signalableMegolmBackupAuthData.privateKeySalt, - privateKeyIterations = signalableMegolmBackupAuthData.privateKeyIterations, - signatures = signatures - ) - val creationInfo = MegolmBackupCreationInfo( - algorithm = MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP, - authData = signedMegolmBackupAuthData, - recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey()) - ) - uiHandler.post { - callback.onSuccess(creationInfo) - } - } catch (failure: Throwable) { - uiHandler.post { - callback.onFailure(failure) - } - } + cryptoCoroutineScope.launch { + prepareKeysBackup( + algorithm = algorithm ?: DEFAULT_ALGORITHM, + password = password, + progressListener = progressListener, + callback = callback + ) } } @@ -352,7 +284,7 @@ internal class DefaultKeysBackupService @Inject constructor( progressListener: ProgressListener?, callback: MatrixCallback? ) { - if (!isEnabled() || backupOlmPkEncryption == null || keysBackupVersion == null) { + if (!isEnabled() || algorithm == null || keysBackupVersion == null) { callback?.onFailure(Throwable("Backup not enabled")) return } @@ -430,12 +362,12 @@ internal class DefaultKeysBackupService @Inject constructor( private fun getKeysBackupTrustBg(keysBackupVersion: KeysVersionResult): KeysBackupVersionTrust { val authData = keysBackupVersion.getAuthDataAsMegolmBackupAuthData() - if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isNullOrEmpty()) { + if (authData == null || authData.signatures.isNullOrEmpty()) { Timber.v("getKeysBackupTrust: Key backup is absent or missing required data") return KeysBackupVersionTrust(usable = false) } - - val mySigs = authData.signatures[userId] + val signatures = authData.signatures!! + val mySigs = authData.signatures?.get(userId) if (mySigs.isNullOrEmpty()) { Timber.v("getKeysBackupTrust: Ignoring key backup because it lacks any signatures from this user") return KeysBackupVersionTrust(usable = false) @@ -459,7 +391,7 @@ internal class DefaultKeysBackupService @Inject constructor( var isSignatureValid = false try { - crossSigningOlm.verifySignature(CrossSigningOlm.KeyType.MASTER, authData.signalableJSONDictionary(), authData.signatures) + crossSigningOlm.verifySignature(CrossSigningOlm.KeyType.MASTER, authData.toSignalableJsonDict(), signatures) isSignatureValid = true } catch (failure: Throwable) { Timber.w(failure, "getKeysBackupTrust: Bad signature from my user MSK") @@ -485,7 +417,7 @@ internal class DefaultKeysBackupService @Inject constructor( val fingerprint = device.fingerprint() if (fingerprint != null) { try { - olmDevice.verifySignature(fingerprint, authData.signalableJSONDictionary(), mySignature) + olmDevice.verifySignature(fingerprint, authData.toSignalableJsonDict(), mySignature) isSignatureValid = true } catch (e: OlmException) { Timber.w(e, "getKeysBackupTrust: Bad signature from device ${device.deviceId}") @@ -520,7 +452,7 @@ internal class DefaultKeysBackupService @Inject constructor( Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}") // Get auth data to update it - val authData = getMegolmBackupAuthData(keysBackupVersion) + val authData = keysBackupVersion.getValidAuthData() if (authData == null) { Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data") @@ -535,7 +467,7 @@ internal class DefaultKeysBackupService @Inject constructor( if (trust) { // Add current device signature - val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary()) + val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, authData.toSignalableJsonDict()) val deviceSignatures = objectSigner.signObject(canonicalJson) @@ -548,14 +480,10 @@ internal class DefaultKeysBackupService @Inject constructor( } // Create an updated version of KeysVersionResult - val newMegolmBackupAuthData = authData.copy() - - val newSignatures = newMegolmBackupAuthData.signatures.orEmpty().toMutableMap() + val newSignatures = authData.signatures.orEmpty().toMutableMap() newSignatures[userId] = myUserSignatures - val newMegolmBackupAuthDataWithNewSignature = newMegolmBackupAuthData.copy( - signatures = newSignatures - ) + val newMegolmBackupAuthDataWithNewSignature = authData.copy(newSignatures) @Suppress("UNCHECKED_CAST") UpdateKeysBackupVersionBody( @@ -666,36 +594,6 @@ internal class DefaultKeysBackupService @Inject constructor( } } - /** - * Get public key from a Recovery key. - * - * @param recoveryKey the recovery key - * @return the corresponding public key, from Olm - */ - @WorkerThread - private fun pkPublicKeyFromRecoveryKey(recoveryKey: String): String? { - // Extract the primary key - val privateKey = extractCurveKeyFromRecoveryKey(recoveryKey) - - if (privateKey == null) { - Timber.w("pkPublicKeyFromRecoveryKey: private key is null") - - return null - } - - // Built the PK decryption with it - val pkPublicKey: String - - try { - val decryption = OlmPkDecryption() - pkPublicKey = decryption.setPrivateKey(privateKey) - } catch (e: OlmException) { - return null - } - - return pkPublicKey - } - private fun resetBackupAllGroupSessionsListeners() { backupAllGroupSessionsCallback = null @@ -725,79 +623,44 @@ internal class DefaultKeysBackupService @Inject constructor( cryptoCoroutineScope.launch(coroutineDispatchers.io) { runCatching { - val decryption = withContext(coroutineDispatchers.computation) { - // Check if the recovery is valid before going any further - if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysVersionResult)) { - Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version") - throw InvalidParameterException("Invalid recovery key") - } - // Get a PK decryption instance - pkDecryptionFromRecoveryKey(recoveryKey) - } - if (decryption == null) { - // This should not happen anymore - Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key. Error") - throw InvalidParameterException("Invalid recovery key") - } - // Save for next time and for gossiping // Save now as it's valid, don't wait for the import as it could take long. saveBackupRecoveryKey(recoveryKey, keysVersionResult.version) - stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey) // Get backed up keys from the homeserver val data = getKeys(sessionId, roomId, keysVersionResult.version) - - withContext(coroutineDispatchers.computation) { - val sessionsData = ArrayList() - // Restore that data - var sessionsFromHsCount = 0 - for ((roomIdLoop, backupData) in data.roomIdToRoomKeysBackupData) { - for ((sessionIdLoop, keyBackupData) in backupData.sessionIdToKeyBackupData) { - sessionsFromHsCount++ - - val sessionData = decryptKeyBackupData(keyBackupData, sessionIdLoop, roomIdLoop, decryption) - - sessionData?.let { - sessionsData.add(it) - } - } - } + val sessionsData = withContext(coroutineDispatchers.computation) { + algorithm?.decryptSessions(recoveryKey, data) + }.orEmpty() + // Do not trigger a backup for them if they come from the backup version we are using + val backUp = keysVersionResult.version != keysBackupVersion?.version + if (backUp) { Timber.v( - "restoreKeysWithRecoveryKey: Decrypted ${sessionsData.size} keys out" + - " of $sessionsFromHsCount from the backup store on the homeserver" + "restoreKeysWithRecoveryKey: Those keys will be backed up" + + " to backup version: ${keysBackupVersion?.version}" ) - - // Do not trigger a backup for them if they come from the backup version we are using - val backUp = keysVersionResult.version != keysBackupVersion?.version - if (backUp) { - Timber.v( - "restoreKeysWithRecoveryKey: Those keys will be backed up" + - " to backup version: ${keysBackupVersion?.version}" - ) - } - - // Import them into the crypto store - val progressListener = if (stepProgressListener != null) { - object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - // Note: no need to post to UI thread, importMegolmSessionsData() will do it - stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total)) - } - } - } else { - null - } - - val result = megolmSessionDataImporter.handle(sessionsData, !backUp, progressListener) - - // Do not back up the key if it comes from a backup recovery - if (backUp) { - maybeBackupKeys() - } - result } + + // Import them into the crypto store + val progressListener = if (stepProgressListener != null) { + object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + // Note: no need to post to UI thread, importMegolmSessionsData() will do it + stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total)) + } + } + } else { + null + } + + val result = megolmSessionDataImporter.handle(sessionsData, !backUp, progressListener) + + // Do not back up the key if it comes from a backup recovery + if (backUp) { + maybeBackupKeys() + } + result }.foldToCallback(object : MatrixCallback { override fun onSuccess(data: ImportRoomKeysResult) { uiHandler.post { @@ -902,26 +765,6 @@ internal class DefaultKeysBackupService @Inject constructor( } } - @VisibleForTesting - @WorkerThread - fun pkDecryptionFromRecoveryKey(recoveryKey: String): OlmPkDecryption? { - // Extract the primary key - val privateKey = extractCurveKeyFromRecoveryKey(recoveryKey) - - // Built the PK decryption with it - var decryption: OlmPkDecryption? = null - if (privateKey != null) { - try { - decryption = OlmPkDecryption() - decryption.setPrivateKey(privateKey) - } catch (e: OlmException) { - Timber.e(e, "OlmException") - } - } - - return decryption - } - /** * Do a backup if there are new keys, with a delay. */ @@ -1103,15 +946,11 @@ internal class DefaultKeysBackupService @Inject constructor( /** * Extract MegolmBackupAuthData data from a backup version. * - * @param keysBackupData the key backup data - * * @return the authentication if found and valid, null in other case */ - private fun getMegolmBackupAuthData(keysBackupData: KeysVersionResult): MegolmBackupAuthData? { - return keysBackupData - .takeIf { it.version.isNotEmpty() && it.algorithm == MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP } - ?.getAuthDataAsMegolmBackupAuthData() - ?.takeIf { it.publicKey.isNotEmpty() } + private fun KeysVersionResult.getValidAuthData(): MegolmBackupAuthData? { + return getAuthDataAsMegolmBackupAuthData() + ?.takeIf { it.isValid() } } /** @@ -1125,7 +964,7 @@ internal class DefaultKeysBackupService @Inject constructor( */ @WorkerThread private fun recoveryKeyFromPassword(password: String, keysBackupData: KeysVersionResult, progressListener: ProgressListener?): String? { - val authData = getMegolmBackupAuthData(keysBackupData) + val authData = keysBackupData.getValidAuthData() if (authData == null) { Timber.w("recoveryKeyFromPassword: invalid parameter") @@ -1139,8 +978,10 @@ internal class DefaultKeysBackupService @Inject constructor( return null } + val salt = authData.privateKeySalt!! + val iterations = authData.privateKeyIterations!! // Extract the recovery key from the passphrase - val data = retrievePrivateKeyWithPassword(password, authData.privateKeySalt, authData.privateKeyIterations, progressListener) + val data = retrievePrivateKeyWithPassword(password, salt, iterations, progressListener) return computeRecoveryKey(data) } @@ -1155,32 +996,15 @@ internal class DefaultKeysBackupService @Inject constructor( */ @WorkerThread private fun isValidRecoveryKeyForKeysBackupVersion(recoveryKey: String, keysBackupData: KeysVersionResult): Boolean { - // Build PK decryption instance with the recovery key - val publicKey = pkPublicKeyFromRecoveryKey(recoveryKey) - - if (publicKey == null) { - Timber.w("isValidRecoveryKeyForKeysBackupVersion: public key is null") - - return false + return try { + val algorithm = algorithmFactory.create(keysBackupData) + val isValid = algorithm.keyMatches(recoveryKey) + algorithm.release() + isValid + } catch (failure: Throwable) { + Timber.e(failure, "Can't check validity of recoveryKey") + false } - - val authData = getMegolmBackupAuthData(keysBackupData) - - if (authData == null) { - Timber.w("isValidRecoveryKeyForKeysBackupVersion: Key backup is missing required data") - - return false - } - - // Compare both - if (publicKey != authData.publicKey) { - Timber.w("isValidRecoveryKeyForKeysBackupVersion: Public keys mismatch") - - return false - } - - // Public keys match! - return true } override fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback) { @@ -1220,11 +1044,9 @@ internal class DefaultKeysBackupService @Inject constructor( onServerDataRetrieved(keysVersionResult.count, keysVersionResult.hash) try { - backupOlmPkEncryption = OlmPkEncryption().apply { - setRecipientKey(retrievedMegolmBackupAuthData.publicKey) - } - } catch (e: OlmException) { - Timber.e(e, "OlmException") + algorithm = algorithmFactory.create(keysVersionResult) + } catch (e: Exception) { + Timber.e(e, "Error while creating algorithm") keysBackupStateManager.state = KeysBackupState.Disabled return } @@ -1260,8 +1082,8 @@ internal class DefaultKeysBackupService @Inject constructor( cryptoStore.setKeyBackupVersion(null) cryptoStore.setKeysBackupData(null) - backupOlmPkEncryption?.releaseEncryption() - backupOlmPkEncryption = null + algorithm?.release() + algorithm = null // Reset backup markers cryptoStore.resetBackupMarkers() @@ -1275,7 +1097,7 @@ internal class DefaultKeysBackupService @Inject constructor( Timber.v("backupKeys") // Sanity check, as this method can be called after a delay, the state may have change during the delay - if (!isEnabled() || backupOlmPkEncryption == null || keysBackupVersion == null) { + if (!isEnabled() || algorithm == null || keysBackupVersion == null) { Timber.v("backupKeys: Invalid configuration") backupAllGroupSessionsCallback?.onFailure(IllegalStateException("Invalid configuration")) resetBackupAllGroupSessionsListeners() @@ -1411,8 +1233,6 @@ internal class DefaultKeysBackupService @Inject constructor( // Gather information for each key val device = cryptoStore.deviceWithIdentityKey(olmInboundGroupSessionWrapper.senderKey) - // 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 = inboundGroupSessionStore .getInboundGroupSession(olmInboundGroupSessionWrapper.safeSessionId, olmInboundGroupSessionWrapper.senderKey) ?.let { @@ -1421,30 +1241,8 @@ internal class DefaultKeysBackupService @Inject constructor( } } ?: return null - val sessionBackupData = mapOf( - "algorithm" to sessionData.algorithm, - "sender_key" to sessionData.senderKey, - "sender_claimed_keys" to sessionData.senderClaimedKeys, - "forwarding_curve25519_key_chain" to (sessionData.forwardingCurve25519KeyChain.orEmpty()), - "session_key" to sessionData.sessionKey, - "org.matrix.msc3061.shared_history" to sessionData.sharedHistory - ) - val json = MoshiProvider.providesMoshi() - .adapter(Map::class.java) - .toJson(sessionBackupData) - - val encryptedSessionBackupData = try { - withContext(coroutineDispatchers.computation) { - backupOlmPkEncryption?.encrypt(json) - } - } catch (e: OlmException) { - Timber.e(e, "OlmException") - null - } - ?: return null - - // Build backup data for that key + val sessionBackupData = algorithm?.encryptSession(sessionData) ?: return null return KeyBackupData( firstMessageIndex = try { olmInboundGroupSessionWrapper.session.firstKnownIndex @@ -1455,11 +1253,7 @@ internal class DefaultKeysBackupService @Inject constructor( forwardedCount = olmInboundGroupSessionWrapper.sessionData.forwardingCurve25519KeyChain.orEmpty().size, isVerified = device?.isVerified == true, sharedHistory = olmInboundGroupSessionWrapper.getSharedKey(), - sessionData = mapOf( - "ciphertext" to encryptedSessionBackupData.mCipherText, - "mac" to encryptedSessionBackupData.mMac, - "ephemeral" to encryptedSessionBackupData.mEphemeralKey - ) + sessionData = sessionBackupData ) } @@ -1471,45 +1265,6 @@ internal class DefaultKeysBackupService @Inject constructor( return sessionData.sharedHistory } - @VisibleForTesting - @WorkerThread - fun decryptKeyBackupData(keyBackupData: KeyBackupData, sessionId: String, roomId: String, decryption: OlmPkDecryption): MegolmSessionData? { - var sessionBackupData: MegolmSessionData? = null - - val jsonObject = keyBackupData.sessionData - - 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() - encrypted.mCipherText = ciphertext - encrypted.mMac = mac - encrypted.mEphemeralKey = ephemeralKey - - try { - val decrypted = decryption.decrypt(encrypted) - - val moshi = MoshiProvider.providesMoshi() - val adapter = moshi.adapter(MegolmSessionData::class.java) - - sessionBackupData = adapter.fromJson(decrypted) - } catch (e: OlmException) { - Timber.e(e, "OlmException") - } - - if (sessionBackupData != null) { - sessionBackupData = sessionBackupData.copy( - sessionId = sessionId, - roomId = roomId - ) - } - } - - return sessionBackupData - } - /* ========================================================================================== * For test only * ========================================================================================== */ @@ -1559,3 +1314,9 @@ internal class DefaultKeysBackupService @Inject constructor( override fun toString() = "KeysBackup for $userId" } + +internal fun MegolmBackupAuthData.toSignalableJsonDict(): JsonDict { + return HashMap(toJsonDict()).apply { + remove("signatures") + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupStateManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupStateManager.kt index 0614eceb16..938f3a17ea 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupStateManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupStateManager.kt @@ -20,8 +20,9 @@ import android.os.Handler import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener import timber.log.Timber +import javax.inject.Inject -internal class KeysBackupStateManager(private val uiHandler: Handler) { +internal class KeysBackupStateManager @Inject constructor(private val uiHandler: Handler) { private val listeners = ArrayList() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/PrepareKeysBackupUseCase.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/PrepareKeysBackupUseCase.kt new file mode 100644 index 0000000000..a2e3c0f501 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/PrepareKeysBackupUseCase.kt @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.keysbackup + +import android.os.Handler +import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers +import org.matrix.android.sdk.api.auth.data.Credentials +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP +import org.matrix.android.sdk.api.listeners.ProgressListener +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCurve25519AuthData +import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey +import org.matrix.android.sdk.internal.crypto.ObjectSigner +import org.matrix.android.sdk.internal.crypto.crosssigning.CrossSigningOlm +import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmBackupAuthData +import org.matrix.android.sdk.internal.util.JsonCanonicalizer +import org.matrix.olm.OlmPkDecryption +import timber.log.Timber +import javax.inject.Inject + +internal class PrepareKeysBackupUseCase @Inject constructor( + private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val objectSigner: ObjectSigner, + private val credentials: Credentials, + private val crossSigningOlm: CrossSigningOlm, + private val uiHandler: Handler, +) { + + suspend operator fun invoke( + algorithm: String, + password: String?, + progressListener: ProgressListener?, + callback: MatrixCallback + ) = withContext(coroutineDispatchers.io) { + when (algorithm) { + MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP -> prepareCurve(password, progressListener, callback) + /* + MXCRYPTO_ALGORITHM_AES_256_BACKUP -> prepareAES(password, progressListener, callback) + */ + else -> { + callback.onFailure(IllegalStateException("Unknown algorithm")) + } + } + } + + private fun prepareCurve(password: String?, progressListener: ProgressListener?, callback: MatrixCallback) { + val olmPkDecryption = OlmPkDecryption() + try { + val signalableMegolmBackupAuthData = if (password != null) { + // Generate a private key from the password + val backgroundProgressListener = if (progressListener == null) { + null + } else { + object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + uiHandler.post { + try { + progressListener.onProgress(progress, total) + } catch (e: Exception) { + Timber.e(e, "prepareKeysBackupVersion: onProgress failure") + } + } + } + } + } + val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener) + SignalableMegolmBackupAuthData( + publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey), + privateKeySalt = generatePrivateKeyResult.salt, + privateKeyIterations = generatePrivateKeyResult.iterations + ) + } else { + val publicKey = olmPkDecryption.generateKey() + SignalableMegolmBackupAuthData( + publicKey = publicKey + ) + } + + val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableMegolmBackupAuthData.signalableJSONDictionary()) + + val signatures = mutableMapOf>() + + val deviceSignature = objectSigner.signObject(canonicalJson) + deviceSignature.forEach { (userID, content) -> + signatures[userID] = content.toMutableMap() + } + + // If we have cross signing add signature, will throw if cross signing not properly configured + try { + val crossSign = crossSigningOlm.signObject(CrossSigningOlm.KeyType.MASTER, canonicalJson) + signatures[credentials.userId]?.putAll(crossSign) + } catch (failure: Throwable) { + // ignore and log + Timber.w(failure, "prepareKeysBackupVersion: failed to sign with cross signing keys") + } + + val signedMegolmBackupCurve25519AuthData = MegolmBackupCurve25519AuthData( + publicKey = signalableMegolmBackupAuthData.publicKey, + privateKeySalt = signalableMegolmBackupAuthData.privateKeySalt, + privateKeyIterations = signalableMegolmBackupAuthData.privateKeyIterations, + signatures = signatures + ) + val creationInfo = MegolmBackupCreationInfo( + algorithm = MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP, + authData = signedMegolmBackupCurve25519AuthData, + recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey()) + ) + uiHandler.post { + callback.onSuccess(creationInfo) + } + } catch (failure: Throwable) { + uiHandler.post { + callback.onFailure(failure) + } + } finally { + olmPkDecryption.releaseDecryption() + } + } + + /* + private fun prepareAES(password: String?, progressListener: ProgressListener?, callback: MatrixCallback) { + } + + */ +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAlgorithm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAlgorithm.kt new file mode 100644 index 0000000000..436ac15ed5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAlgorithm.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.keysbackup.algorithm + +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData +import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.internal.crypto.MegolmSessionData +import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData + +internal interface KeysBackupAlgorithm { + val authData: MegolmBackupAuthData + val untrusted: Boolean + + fun encryptSession(sessionData: MegolmSessionData): JsonDict? + fun decryptSessions(recoveryKey: String, data: KeysBackupData): List + fun keyMatches(key: String): Boolean + fun release() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAlgorithmFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAlgorithmFactory.kt new file mode 100644 index 0000000000..4e23c7aab0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAlgorithmFactory.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.keysbackup.algorithm + +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_AES_256_BACKUP +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult +import javax.inject.Inject + +internal class KeysBackupAlgorithmFactory @Inject constructor() { + + fun create(keysVersion: KeysVersionResult): KeysBackupAlgorithm { + return when (keysVersion.algorithm) { + MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP -> { + KeysBackupCurve25519Algorithm(keysVersion) + } + MXCRYPTO_ALGORITHM_AES_256_BACKUP -> { + throw IllegalStateException("AES_256 is not yet handled") + } + else -> { + throw IllegalStateException("Unknown algorithm") + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupCurve25519Algorithm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupCurve25519Algorithm.kt new file mode 100644 index 0000000000..eb8ddca227 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupCurve25519Algorithm.kt @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.keysbackup.algorithm + +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCurve25519AuthData +import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey +import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.internal.crypto.MegolmSessionData +import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData +import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.olm.OlmException +import org.matrix.olm.OlmPkDecryption +import org.matrix.olm.OlmPkEncryption +import org.matrix.olm.OlmPkMessage +import timber.log.Timber +import java.security.InvalidParameterException + +internal class KeysBackupCurve25519Algorithm(keysVersions: KeysVersionResult) : KeysBackupAlgorithm { + + override val untrusted: Boolean = true + + private val curveAuthData: MegolmBackupCurve25519AuthData + private val publicKey: String + + private val encryptionKey: OlmPkEncryption + + init { + if (keysVersions.algorithm != MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP) { + throw IllegalStateException("Algorithm doesn't match") + } + curveAuthData = keysVersions.getAuthDataAsMegolmBackupAuthData() as MegolmBackupCurve25519AuthData + publicKey = curveAuthData.publicKey ?: throw IllegalStateException("No public key") + encryptionKey = OlmPkEncryption().apply { + setRecipientKey(publicKey) + } + } + + override fun encryptSession(sessionData: MegolmSessionData): JsonDict? { + val sessionBackupData = mapOf( + "algorithm" to sessionData.algorithm, + "sender_key" to sessionData.senderKey, + "sender_claimed_keys" to sessionData.senderClaimedKeys, + "forwarding_curve25519_key_chain" to (sessionData.forwardingCurve25519KeyChain.orEmpty()), + "session_key" to sessionData.sessionKey, + "org.matrix.msc3061.shared_history" to sessionData.sharedHistory, + "untrusted" to sessionData.untrusted + ) + + val json = MoshiProvider.providesMoshi() + .adapter(Map::class.java) + .toJson(sessionBackupData) + + val encryptedSessionBackupData = try { + encryptionKey.encrypt(json) + } catch (e: OlmException) { + Timber.e(e, "Error while encrypting backup data.") + null + } ?: return null + + return mapOf( + "ciphertext" to encryptedSessionBackupData.mCipherText, + "mac" to encryptedSessionBackupData.mMac, + "ephemeral" to encryptedSessionBackupData.mEphemeralKey + ) + } + + override fun decryptSessions(recoveryKey: String, data: KeysBackupData): List { + fun pkDecryptionFromRecoveryKey(recoveryKey: String): OlmPkDecryption? { + // Extract the primary key + val privateKey = extractCurveKeyFromRecoveryKey(recoveryKey) + // Built the PK decryption with it + var decryption: OlmPkDecryption? = null + if (privateKey != null) { + try { + decryption = OlmPkDecryption() + decryption.setPrivateKey(privateKey) + } catch (e: OlmException) { + Timber.e(e, "OlmException") + } + } + return decryption + } + + if (!keyMatches(recoveryKey)) { + Timber.e("Invalid recovery key for this keys version") + throw InvalidParameterException("Invalid recovery key") + } + // Get a PK decryption instance + val decryption = pkDecryptionFromRecoveryKey(recoveryKey) + if (decryption == null) { + // This should not happen anymore + Timber.e("Invalid recovery key. Error") + throw InvalidParameterException("Invalid recovery key") + } + val sessionsData = ArrayList() + // Restore that data + var sessionsFromHsCount = 0 + for ((roomIdLoop, backupData) in data.roomIdToRoomKeysBackupData) { + for ((sessionIdLoop, keyBackupData) in backupData.sessionIdToKeyBackupData) { + sessionsFromHsCount++ + val sessionData = decryptSession(keyBackupData.sessionData, sessionIdLoop, roomIdLoop, decryption) + sessionData?.let { + sessionsData.add(it) + } + } + } + Timber.v( + "Decrypted ${sessionsData.size} keys out of $sessionsFromHsCount from the backup store on the homeserver" + ) + return sessionsData + } + + private fun decryptSession(sessionData: JsonDict, sessionId: String, roomId: String, decryption: OlmPkDecryption): MegolmSessionData? { + var sessionBackupData: MegolmSessionData? = null + + val ciphertext = sessionData["ciphertext"]?.toString() + val mac = sessionData["mac"]?.toString() + val ephemeralKey = sessionData["ephemeral"]?.toString() + + if (ciphertext != null && mac != null && ephemeralKey != null) { + val encrypted = OlmPkMessage() + encrypted.mCipherText = ciphertext + encrypted.mMac = mac + encrypted.mEphemeralKey = ephemeralKey + + try { + val decrypted = decryption.decrypt(encrypted) + + val moshi = MoshiProvider.providesMoshi() + val adapter = moshi.adapter(MegolmSessionData::class.java) + + sessionBackupData = adapter.fromJson(decrypted) + } catch (e: OlmException) { + Timber.e(e, "OlmException") + } + + if (sessionBackupData != null) { + sessionBackupData = sessionBackupData.copy( + sessionId = sessionId, + roomId = roomId, + untrusted = untrusted + ) + } + } + + return sessionBackupData + } + + override fun release() { + encryptionKey.releaseEncryption() + } + + override val authData: MegolmBackupAuthData = curveAuthData + + override fun keyMatches(key: String): Boolean { + fun pkPublicKeyFromRecoveryKey(recoveryKey: String): String? { + // Extract the primary key + val privateKey = extractCurveKeyFromRecoveryKey(recoveryKey) + if (privateKey == null) { + Timber.w("pkPublicKeyFromRecoveryKey: private key is null") + return null + } + // Built the PK decryption with it + val decryption = OlmPkDecryption() + val pkPublicKey = try { + decryption.setPrivateKey(privateKey) + } catch (e: OlmException) { + null + } finally { + decryption.releaseDecryption() + } + return pkPublicKey + } + + val publicKey = pkPublicKeyFromRecoveryKey(key) + if (publicKey == null) { + Timber.w("Public key is null") + return false + } + // Compare both + if (publicKey != this.publicKey) { + Timber.w("Public keys mismatch") + return false + } + // Public keys match! + return true + } +} 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 2d483893c0..b0fb6d37e9 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 @@ -23,7 +23,9 @@ import org.matrix.android.sdk.api.util.JsonDict @JsonClass(generateAdapter = true) internal data class CreateKeysBackupVersionBody( /** - * The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined. + * The algorithm used for storing backups. + * Currently, "m.megolm_backup.v1.curve25519-aes-sha2" and + * org.matrix.msc3270.v1.aes-hmac-sha2 are defined. */ @Json(name = "algorithm") override val algorithm: String, 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 802a2bac92..c5d277484b 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 @@ -16,8 +16,11 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.model.rest +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_AES_256_BACKUP import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAes256AuthData import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCurve25519AuthData import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.di.MoshiProvider @@ -41,12 +44,14 @@ import org.matrix.android.sdk.internal.di.MoshiProvider internal interface KeysAlgorithmAndData { /** - * The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined. + * The algorithm used for storing backups. + * Currently, "m.megolm_backup.v1.curve25519-aes-sha2" and + * org.matrix.msc3270.v1.aes-hmac-sha2 are defined. */ val algorithm: String /** - * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" see [org.matrix.android.sdk.internal.crypto.keysbackup.MegolmBackupAuthData]. + * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" * see [org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCurve25519AuthData]. */ val authData: JsonDict @@ -54,9 +59,12 @@ internal interface KeysAlgorithmAndData { * Facility method to convert authData to a MegolmBackupAuthData object. */ fun getAuthDataAsMegolmBackupAuthData(): MegolmBackupAuthData? { - return MoshiProvider.providesMoshi() - .takeIf { algorithm == MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP } - ?.adapter(MegolmBackupAuthData::class.java) - ?.fromJsonValue(authData) + val moshi = MoshiProvider.providesMoshi() + val moshiAdapter = when (algorithm) { + MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP -> moshi.adapter(MegolmBackupCurve25519AuthData::class.java) + MXCRYPTO_ALGORITHM_AES_256_BACKUP -> moshi.adapter(MegolmBackupAes256AuthData::class.java) + else -> null + } + return moshiAdapter?.fromJsonValue(authData) } } 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 a4b8f811e4..a9ee5d3f2d 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 @@ -23,14 +23,16 @@ import org.matrix.android.sdk.api.util.JsonDict @JsonClass(generateAdapter = true) internal data class UpdateKeysBackupVersionBody( /** - * The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined + * The algorithm used for storing backups. + * Currently, "m.megolm_backup.v1.curve25519-aes-sha2" and + * org.matrix.msc3270.v1.aes-hmac-sha2 are defined. */ @Json(name = "algorithm") override val algorithm: String, /** * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2". - * see [org.matrix.android.sdk.internal.crypto.keysbackup.MegolmBackupAuthData] + * see [org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCurve25519AuthData] */ @Json(name = "auth_data") override val authData: JsonDict, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt index 44ec90ed40..8a22ab3e04 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.di import android.content.Context import android.content.res.Resources +import android.os.Handler import com.squareup.moshi.Moshi import dagger.BindsInstance import dagger.Component @@ -66,6 +67,8 @@ internal interface MatrixComponent { fun moshi(): Moshi + fun uiHandler(): Handler + @Unauthenticated fun okHttpClient(): OkHttpClient diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt index f2f8a5dc04..fc400fe670 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.di import android.content.Context import android.content.res.Resources +import android.os.Handler import dagger.Module import dagger.Provides import kotlinx.coroutines.Dispatchers @@ -25,6 +26,7 @@ import kotlinx.coroutines.android.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.createBackgroundHandler +import org.matrix.android.sdk.internal.util.createUIHandler import org.matrix.olm.OlmManager import java.io.File import java.util.concurrent.Executors @@ -51,6 +53,12 @@ internal object MatrixModule { return context.resources } + @JvmStatic + @Provides + fun providesUIHandler(): Handler { + return createUIHandler() + } + @JvmStatic @Provides @CacheDirectory diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt index 3c0d47c79c..3c83b992ca 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt @@ -144,7 +144,7 @@ class KeysBackupSettingsViewModel @AssistedInject constructor( // for the backup and store it in the 4S if (session.sharedSecretStorageService().isRecoverySetup()) { val creationInfo = awaitCallback { - session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it) + session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, null, it) } pendingBackupCreationInfo = creationInfo val recoveryKey = extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding() diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt index dfa7d1aaa3..fd6579889f 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt @@ -97,7 +97,7 @@ class KeysBackupSetupSharedViewModel @Inject constructor( val requestedId = currentRequestId.value!! mxSession.cryptoService().keysBackupService().prepareKeysBackupVersion(withPassphrase, - object : ProgressListener { + progressListener = object : ProgressListener { override fun onProgress(progress: Int, total: Int) { if (requestedId != currentRequestId.value) { // this is an old request, we can't cancel but we can ignore @@ -111,7 +111,7 @@ class KeysBackupSetupSharedViewModel @Inject constructor( ) } }, - object : MatrixCallback { + callback = object : MatrixCallback { override fun onSuccess(data: MegolmBackupCreationInfo) { if (requestedId != currentRequestId.value) { // this is an old request, we can't cancel but we can ignore diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt index c7c367f5ec..55a5897e21 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt @@ -247,7 +247,7 @@ class BootstrapCrossSigningTask @Inject constructor( Timber.d("## BootstrapCrossSigningTask: Creating 4S - Create megolm backup") val creationInfo = awaitCallback { - session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it) + session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, null, it) } val version = awaitCallback { session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)