From f855664504d77367761fb45241f9ef356ed978f6 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 8 Aug 2022 15:49:57 +0200 Subject: [PATCH 01/10] Symmetric backup : rename megolm algorithm constant and introduce new one --- .../org/matrix/android/sdk/common/CryptoTestHelper.kt | 5 +++-- .../sdk/internal/crypto/keysbackup/KeysBackupTest.kt | 4 ++-- .../org/matrix/android/sdk/api/crypto/CryptoConstants.kt | 9 +++++++-- .../crypto/keysbackup/DefaultKeysBackupService.kt | 6 +++--- .../crypto/keysbackup/model/rest/KeysAlgorithmAndData.kt | 4 ++-- 5 files changed, 17 insertions(+), 11 deletions(-) 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 74292daf150..ddf92c1fa7d 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,7 +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_BACKUP +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.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.MXCryptoError @@ -187,7 +188,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { fun createFakeMegolmBackupCreationInfo(): MegolmBackupCreationInfo { return MegolmBackupCreationInfo( - algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, + algorithm = MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP, 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 01c03b80016..7e388cac85f 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 @@ -29,7 +29,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest -import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP +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.listeners.StepProgressListener import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel @@ -122,7 +122,7 @@ class KeysBackupTest : InstrumentedTest { keysBackup.prepareKeysBackupVersion(null, null, it) } - assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, megolmBackupCreationInfo.algorithm) + assertEquals(MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP, megolmBackupCreationInfo.algorithm) assertNotNull(megolmBackupCreationInfo.authData.publicKey) assertNotNull(megolmBackupCreationInfo.authData.signatures) assertNotNull(megolmBackupCreationInfo.recoveryKey) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt index 37b9ac379e2..fd1ef9748d1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt @@ -27,9 +27,14 @@ const val MXCRYPTO_ALGORITHM_OLM = "m.olm.v1.curve25519-aes-sha2" const val MXCRYPTO_ALGORITHM_MEGOLM = "m.megolm.v1.aes-sha2" /** - * Matrix algorithm value for megolm keys backup. + * Matrix algorithm value for CURVE_25519 megolm keys backup. */ -const val MXCRYPTO_ALGORITHM_MEGOLM_BACKUP = "m.megolm_backup.v1.curve25519-aes-sha2" +const val MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP = "m.megolm_backup.v1.curve25519-aes-sha2" + +/** + * Matrix algorithm value for AES-256 megolm keys backup. + */ +const val MXCRYPTO_ALGORITHM_AES_256_BACKUP = "org.matrix.msc3270.v1.aes-hmac-sha2" /** * Secured Shared Storage algorithm constant. 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 e8700b78090..543f1cc67e1 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,7 @@ 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_MEGOLM_BACKUP +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.listeners.ProgressListener @@ -219,7 +219,7 @@ internal class DefaultKeysBackupService @Inject constructor( signatures = signatures ) val creationInfo = MegolmBackupCreationInfo( - algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, + algorithm = MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP, authData = signedMegolmBackupAuthData, recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey()) ) @@ -1109,7 +1109,7 @@ internal class DefaultKeysBackupService @Inject constructor( */ private fun getMegolmBackupAuthData(keysBackupData: KeysVersionResult): MegolmBackupAuthData? { return keysBackupData - .takeIf { it.version.isNotEmpty() && it.algorithm == MXCRYPTO_ALGORITHM_MEGOLM_BACKUP } + .takeIf { it.version.isNotEmpty() && it.algorithm == MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP } ?.getAuthDataAsMegolmBackupAuthData() ?.takeIf { it.publicKey.isNotEmpty() } } 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 19581a686bb..802a2bac923 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,7 +16,7 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.model.rest -import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.di.MoshiProvider @@ -55,7 +55,7 @@ internal interface KeysAlgorithmAndData { */ fun getAuthDataAsMegolmBackupAuthData(): MegolmBackupAuthData? { return MoshiProvider.providesMoshi() - .takeIf { algorithm == MXCRYPTO_ALGORITHM_MEGOLM_BACKUP } + .takeIf { algorithm == MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP } ?.adapter(MegolmBackupAuthData::class.java) ?.fromJsonValue(authData) } From c055f40607ea7df964c6e1419ede9885dc0955b6 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 10 Aug 2022 19:07:47 +0200 Subject: [PATCH 02/10] 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 | 403 ++++-------------- .../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, 790 insertions(+), 417 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 ddf92c1fa7d..020451f5b70 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 cbbc4dc74e9..f3b91ad2efd 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 a36ba8ac028..f92ce396d2c 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 7e388cac85f..4b0f13e5aed 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 10abf93bcb0..e3e2ea2b178 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 8745003f9f5..ea0970f677e 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 3d89bf9e2fe..929cb03f7e5 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 00000000000..c9ee4a44217 --- /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 2a620af843c..97b652621f9 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, - - /** - * 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, - - /** - * 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) +sealed interface MegolmBackupAuthData { + val privateKeySalt: String? + val privateKeyIterations: Int? + val signatures: Map>? - return moshi - .adapter(MegolmBackupAuthData::class.java) - .toJson(this) - .let { - @Suppress("UNCHECKED_CAST") - adapter.fromJson(it) as JsonDict - } - } + fun isValid(): Boolean - 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 00000000000..572c23acd51 --- /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 31ed9a1e85f..64c4482b056 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 ca0bdc8a0eb..e1496a8c9d3 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 543f1cc67e1..f9ce38ed6ed 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)) - } + // 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 } + } else { + null + } - val result = megolmSessionDataImporter.handle(sessionsData, !backUp, progressListener) + val result = megolmSessionDataImporter.handle(sessionsData, !backUp, progressListener) - // Do not back up the key if it comes from a backup recovery - if (backUp) { - maybeBackupKeys() - } - result + // 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 - } - - val authData = getMegolmBackupAuthData(keysBackupData) - - if (authData == null) { - Timber.w("isValidRecoveryKeyForKeysBackupVersion: Key backup is missing required data") - - 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 } - - // 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 0614eceb16e..938f3a17ea4 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 00000000000..a2e3c0f5019 --- /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 00000000000..436ac15ed5f --- /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 00000000000..4e23c7aab07 --- /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 00000000000..eb8ddca2278 --- /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 2d483893c09..b0fb6d37e98 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 802a2bac923..c5d277484b8 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 a4b8f811e46..a9ee5d3f2d0 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 44ec90ed40f..8a22ab3e04b 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 f2f8a5dc044..fc400fe670b 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 3c0d47c79cd..3c83b992cad 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 dfa7d1aaa3f..fd6579889f7 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 c7c367f5ec7..55a5897e218 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) From 92faf3bca38013e13eb6172adc6176f842fbd4f5 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 10 Aug 2022 19:19:20 +0200 Subject: [PATCH 03/10] Backup: introduce Aes256 algorithm --- .../algorithm/KeysBackupAes256Algorithm.kt | 57 +++++++++++++++++++ .../algorithm/KeysBackupAlgorithmFactory.kt | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt new file mode 100644 index 00000000000..148feb6459b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt @@ -0,0 +1,57 @@ +/* + * 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.session.crypto.keysbackup.KeysVersionResult +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.util.JsonDict +import org.matrix.android.sdk.internal.crypto.MegolmSessionData +import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData + +internal class KeysBackupAes256Algorithm(keysVersions: KeysVersionResult) : KeysBackupAlgorithm { + + override val untrusted: Boolean = true + + private val aesAuthData: MegolmBackupAes256AuthData + + init { + if (keysVersions.algorithm != MXCRYPTO_ALGORITHM_AES_256_BACKUP) { + throw IllegalStateException("Algorithm doesn't match") + } + aesAuthData = keysVersions.getAuthDataAsMegolmBackupAuthData() as MegolmBackupAes256AuthData + } + + override fun encryptSession(sessionData: MegolmSessionData): JsonDict? { + TODO("Not yet implemented") + } + + override fun decryptSessions(recoveryKey: String, data: KeysBackupData): List { + TODO("Not yet implemented") + } + + override fun release() { + TODO("Not yet implemented") + } + + override val authData: MegolmBackupAuthData = aesAuthData + + override fun keyMatches(key: String): Boolean { + TODO("Not yet implemented") + } +} 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 index 4e23c7aab07..3db8dd53d1a 100644 --- 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 @@ -29,7 +29,7 @@ internal class KeysBackupAlgorithmFactory @Inject constructor() { KeysBackupCurve25519Algorithm(keysVersion) } MXCRYPTO_ALGORITHM_AES_256_BACKUP -> { - throw IllegalStateException("AES_256 is not yet handled") + KeysBackupAes256Algorithm(keysVersion) } else -> { throw IllegalStateException("Unknown algorithm") From 1d26207df715b4cedbdecb73ee53c0f1223c269a Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 17 Aug 2022 12:40:09 +0200 Subject: [PATCH 04/10] Extract AesHmacSha2 encrypt/decrypt --- .../DefaultSharedSecretStorageService.kt | 91 ++--------- .../sdk/internal/crypto/tools/AesHmacSha2.kt | 152 ++++++++++++++++++ 2 files changed, 165 insertions(+), 78 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/AesHmacSha2.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt index ddb048a912a..70e19f744fe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt @@ -21,7 +21,6 @@ import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2 import org.matrix.android.sdk.api.crypto.SSSS_ALGORITHM_CURVE25519_AES_SHA2 -import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey @@ -43,17 +42,12 @@ import org.matrix.android.sdk.api.util.fromBase64 import org.matrix.android.sdk.api.util.toBase64NoPadding import org.matrix.android.sdk.internal.crypto.SecretShareManager import org.matrix.android.sdk.internal.crypto.keysbackup.generatePrivateKeyWithPassword -import org.matrix.android.sdk.internal.crypto.tools.HkdfSha256 +import org.matrix.android.sdk.internal.crypto.tools.AesHmacSha2 import org.matrix.android.sdk.internal.crypto.tools.withOlmDecryption import org.matrix.android.sdk.internal.di.UserId import org.matrix.olm.OlmPkMessage import java.security.SecureRandom -import javax.crypto.Cipher -import javax.crypto.Mac -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec import javax.inject.Inject -import kotlin.experimental.and internal class DefaultSharedSecretStorageService @Inject constructor( @UserId private val userId: String, @@ -214,84 +208,25 @@ internal class DefaultSharedSecretStorageService @Inject constructor( @Throws private fun encryptAesHmacSha2(secretKey: SsssKeySpec, secretName: String, clearDataBase64: String): EncryptedSecretContent { secretKey as RawBytesKeySpec - val pseudoRandomKey = HkdfSha256.deriveSecret( - secretKey.privateKey, - ByteArray(32) { 0.toByte() }, - secretName.toByteArray(), - 64 - ) - - // The first 32 bytes are used as the AES key, and the next 32 bytes are used as the MAC key - val aesKey = pseudoRandomKey.copyOfRange(0, 32) - val macKey = pseudoRandomKey.copyOfRange(32, 64) - - val secureRandom = SecureRandom() - val iv = ByteArray(16) - secureRandom.nextBytes(iv) - - // clear bit 63 of the salt to stop us hitting the 64-bit counter boundary - // (which would mean we wouldn't be able to decrypt on Android). The loss - // of a single bit of salt is a price we have to pay. - iv[9] = iv[9] and 0x7f - - val cipher = Cipher.getInstance("AES/CTR/NoPadding") - - val secretKeySpec = SecretKeySpec(aesKey, "AES") - val ivParameterSpec = IvParameterSpec(iv) - cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec) - // secret are not that big, just do Final - val cipherBytes = cipher.doFinal(clearDataBase64.toByteArray()) - require(cipherBytes.isNotEmpty()) - - val macKeySpec = SecretKeySpec(macKey, "HmacSHA256") - val mac = Mac.getInstance("HmacSHA256") - mac.init(macKeySpec) - val digest = mac.doFinal(cipherBytes) - + val result = AesHmacSha2.encrypt(secretKey.privateKey, secretName, clearDataBase64) return EncryptedSecretContent( - ciphertext = cipherBytes.toBase64NoPadding(), - initializationVector = iv.toBase64NoPadding(), - mac = digest.toBase64NoPadding() + ciphertext = result.cipherRawBytes.toBase64NoPadding(), + initializationVector = result.initializationVector.toBase64NoPadding(), + mac = result.initializationVector.toBase64NoPadding() ) } private fun decryptAesHmacSha2(secretKey: SsssKeySpec, secretName: String, cipherContent: EncryptedSecretContent): String { secretKey as RawBytesKeySpec - val pseudoRandomKey = HkdfSha256.deriveSecret( - secretKey.privateKey, - ByteArray(32) { 0.toByte() }, - secretName.toByteArray(), - 64 - ) - - // The first 32 bytes are used as the AES key, and the next 32 bytes are used as the MAC key - val aesKey = pseudoRandomKey.copyOfRange(0, 32) - val macKey = pseudoRandomKey.copyOfRange(32, 64) - val iv = cipherContent.initializationVector?.fromBase64() ?: ByteArray(16) - - val cipherRawBytes = cipherContent.ciphertext?.fromBase64() ?: throw SharedSecretStorageError.BadCipherText - - // Check Signature - val macKeySpec = SecretKeySpec(macKey, "HmacSHA256") - val mac = Mac.getInstance("HmacSHA256").apply { init(macKeySpec) } - val digest = mac.doFinal(cipherRawBytes) - - if (!cipherContent.mac?.fromBase64()?.contentEquals(digest).orFalse()) { - throw SharedSecretStorageError.BadMac - } - - val cipher = Cipher.getInstance("AES/CTR/NoPadding") - - val secretKeySpec = SecretKeySpec(aesKey, "AES") - val ivParameterSpec = IvParameterSpec(iv) - cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec) - // secret are not that big, just do Final - val decryptedSecret = cipher.doFinal(cipherRawBytes) - - require(decryptedSecret.isNotEmpty()) - - return String(decryptedSecret, Charsets.UTF_8) + val cipher = cipherContent.ciphertext?.fromBase64() ?: throw SharedSecretStorageError.BadCipherText + val mac = cipherContent.mac?.fromBase64() ?: throw SharedSecretStorageError.BadMac + val encryptedResult = AesHmacSha2.Result( + cipherRawBytes = cipher, + initializationVector = iv, + mac = mac + ) + return AesHmacSha2.decrypt(secretKey.privateKey, secretName, encryptedResult) } override fun getAlgorithmsForSecret(name: String): List { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/AesHmacSha2.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/AesHmacSha2.kt new file mode 100644 index 00000000000..a7fac4ce4ba --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/AesHmacSha2.kt @@ -0,0 +1,152 @@ +/* + * 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.tools + +import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageError +import org.matrix.android.sdk.api.util.fromBase64 +import java.security.SecureRandom +import javax.crypto.Cipher +import javax.crypto.Mac +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec +import kotlin.experimental.and + +internal object AesHmacSha2 { + + class Result( + val cipherRawBytes: ByteArray, + val mac: ByteArray, + val initializationVector: ByteArray + ) + + /** + * Encryption algorithm aes-hmac-sha2 + * Secrets are encrypted using AES-CTR-256 and MACed using HMAC-SHA-256. The data is encrypted and MACed as follows: + * + * Given the secret storage key, generate 64 bytes by performing an HKDF with SHA-256 as the hash, a salt of 32 bytes + * of 0, and with the secret name as the info. + * + * The first 32 bytes are used as the AES key, and the next 32 bytes are used as the MAC key + * + * Generate 16 random bytes, set bit 63 to 0 (in order to work around differences in AES-CTR implementations), and use + * this as the AES initialization vector. + * This becomes the iv property, encoded using base64. + * + * Encrypt the data using AES-CTR-256 using the AES key generated above. + * + * This encrypted data, encoded using base64, becomes the ciphertext property. + * + * Pass the raw encrypted data (prior to base64 encoding) through HMAC-SHA-256 using the MAC key generated above. + * The resulting MAC is base64-encoded and becomes the mac property. + * (We use AES-CTR to match file encryption and key exports.) + */ + @Throws + fun encrypt(privateKey: ByteArray, secretName: String, clearDataBase64: String, ivString: String? = null): Result { + val pseudoRandomKey = HkdfSha256.deriveSecret( + privateKey, + ByteArray(32) { 0.toByte() }, + secretName.toByteArray(), + 64 + ) + + // The first 32 bytes are used as the AES key, and the next 32 bytes are used as the MAC key + val aesKey = pseudoRandomKey.copyOfRange(0, 32) + val macKey = pseudoRandomKey.copyOfRange(32, 64) + + val iv = if (ivString != null) { + ivString.fromBase64() + } else { + val secureRandom = SecureRandom() + ByteArray(16).apply { + secureRandom.nextBytes(this) + } + } + // clear bit 63 of the salt to stop us hitting the 64-bit counter boundary + // (which would mean we wouldn't be able to decrypt on Android). The loss + // of a single bit of salt is a price we have to pay. + iv[9] = iv[9] and 0x7f + + val cipher = Cipher.getInstance("AES/CTR/NoPadding") + + val secretKeySpec = SecretKeySpec(aesKey, "AES") + val ivParameterSpec = IvParameterSpec(iv) + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec) + // secret are not that big, just do Final + val cipherBytes = cipher.doFinal(clearDataBase64.toByteArray()) + require(cipherBytes.isNotEmpty()) + + val macKeySpec = SecretKeySpec(macKey, "HmacSHA256") + val mac = Mac.getInstance("HmacSHA256") + mac.init(macKeySpec) + val digest = mac.doFinal(cipherBytes) + + return Result( + cipherRawBytes = cipherBytes, + initializationVector = iv, + mac = digest + ) + } + + fun decrypt(privateKey: ByteArray, secretName: String, aesHmacSha2Result: Result): String { + val pseudoRandomKey = HkdfSha256.deriveSecret( + privateKey, + zeroByteArray(32), + secretName.toByteArray(), + 64 + ) + + // The first 32 bytes are used as the AES key, and the next 32 bytes are used as the MAC key + val aesKey = pseudoRandomKey.copyOfRange(0, 32) + val macKey = pseudoRandomKey.copyOfRange(32, 64) + + val iv = aesHmacSha2Result.initializationVector + val cipherRawBytes = aesHmacSha2Result.cipherRawBytes + + val macKeySpec = SecretKeySpec(macKey, "HmacSHA256") + val mac = Mac.getInstance("HmacSHA256").apply { init(macKeySpec) } + val digest = mac.doFinal(cipherRawBytes) + if (!aesHmacSha2Result.mac.contentEquals(digest)) { + throw SharedSecretStorageError.BadMac + } + val cipher = Cipher.getInstance("AES/CTR/NoPadding") + + val secretKeySpec = SecretKeySpec(aesKey, "AES") + val ivParameterSpec = IvParameterSpec(iv) + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec) + // secret are not that big, just do Final + val decryptedSecret = cipher.doFinal(cipherRawBytes) + + require(decryptedSecret.isNotEmpty()) + + return String(decryptedSecret, Charsets.UTF_8) + } + + /** Calculate the MAC for checking the key. + * + * @param {ByteArray} [key] the key to use + * @param {string} [iv] The initialization vector as a base64-encoded string. + * If omitted, a random initialization vector will be created. + * @return [Result] An object that contains, `mac` and `iv` properties. + */ + fun calculateKeyCheck(key: ByteArray, iv: String?): Result { + val zerosStr = String(zeroByteArray(32)) + return encrypt(key, "", zerosStr, iv) + } + + private fun zeroByteArray(size: Int): ByteArray = ByteArray(size) { 0.toByte() } + +} From 03272a9c8f23d74c67ab220df8e01cc2d8027115 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 17 Aug 2022 19:43:01 +0200 Subject: [PATCH 05/10] Implement AES backup algorithm (WIP need to check with valere the private key thing) --- .../crypto/keysbackup/KeysBackupTest.kt | 4 +- .../keysbackup/DefaultKeysBackupService.kt | 19 +++- .../algorithm/KeysBackupAes256Algorithm.kt | 94 +++++++++++++++++-- .../algorithm/KeysBackupAlgorithm.kt | 34 ++++++- .../KeysBackupCurve25519Algorithm.kt | 87 ++++++----------- .../DefaultSharedSecretStorageService.kt | 4 +- .../sdk/internal/crypto/tools/AesHmacSha2.kt | 12 +-- 7 files changed, 173 insertions(+), 81 deletions(-) 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 4b0f13e5aed..6eda1b16049 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 @@ -42,6 +42,7 @@ 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.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.session.getRoom @@ -321,7 +322,8 @@ class KeysBackupTest : InstrumentedTest { put(cryptoTestData.roomId, roomKeysBackupData) } ) - val sessionsData = algorithm.decryptSessions(keyBackupCreationInfo.recoveryKey, keysBackupData) + algorithm.setRecoveryKey(keyBackupCreationInfo.recoveryKey) + val sessionsData = algorithm.decryptSessions(keysBackupData) val sessionData = sessionsData.firstOrNull() assertNotNull(sessionData) // - Compare the decrypted megolm key with the original one 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 f9ce38ed6ed..22590b97fe2 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 @@ -29,6 +29,7 @@ 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.extensions.tryOrNull import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.listeners.ProgressListener @@ -45,6 +46,7 @@ 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 @@ -630,8 +632,9 @@ internal class DefaultKeysBackupService @Inject constructor( // Get backed up keys from the homeserver val data = getKeys(sessionId, roomId, keysVersionResult.version) + algorithm?.setRecoveryKey(recoveryKey) val sessionsData = withContext(coroutineDispatchers.computation) { - algorithm?.decryptSessions(recoveryKey, data) + algorithm?.decryptSessions(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 @@ -998,7 +1001,8 @@ internal class DefaultKeysBackupService @Inject constructor( private fun isValidRecoveryKeyForKeysBackupVersion(recoveryKey: String, keysBackupData: KeysVersionResult): Boolean { return try { val algorithm = algorithmFactory.create(keysBackupData) - val isValid = algorithm.keyMatches(recoveryKey) + val privateKey = extractCurveKeyFromRecoveryKey(recoveryKey) ?: return false + val isValid = algorithm.keyMatches(privateKey) algorithm.release() isValid } catch (failure: Throwable) { @@ -1133,7 +1137,8 @@ internal class DefaultKeysBackupService @Inject constructor( // Gather data to send to the homeserver // roomId -> sessionId -> MXKeyBackupData val keysBackupData = KeysBackupData() - + val recoveryKey = cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey + algorithm?.setRecoveryKey(recoveryKey) olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper -> val roomId = olmInboundGroupSessionWrapper.roomId ?: return@forEach val olmInboundGroupSession = olmInboundGroupSessionWrapper.session @@ -1242,7 +1247,9 @@ internal class DefaultKeysBackupService @Inject constructor( } ?: return null - val sessionBackupData = algorithm?.encryptSession(sessionData) ?: return null + val sessionBackupData = tryOrNull { + algorithm?.encryptSession(sessionData) + } ?: return null return KeyBackupData( firstMessageIndex = try { olmInboundGroupSessionWrapper.session.firstKnownIndex @@ -1265,6 +1272,10 @@ internal class DefaultKeysBackupService @Inject constructor( return sessionData.sharedHistory } + private fun getPrivateKey(): ByteArray { + return byteArrayOf() + } + /* ========================================================================================== * For test only * ========================================================================================== */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt index 148feb6459b..87245cfc191 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt @@ -20,15 +20,24 @@ import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_AES_256_BACKUP import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult 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.extractCurveKeyFromRecoveryKey import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.api.util.fromBase64 +import org.matrix.android.sdk.api.util.toBase64NoPadding 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.crypto.tools.AesHmacSha2 +import org.matrix.olm.OlmException +import timber.log.Timber +import java.security.InvalidParameterException +import java.util.Arrays internal class KeysBackupAes256Algorithm(keysVersions: KeysVersionResult) : KeysBackupAlgorithm { - override val untrusted: Boolean = true + override val untrusted: Boolean = false private val aesAuthData: MegolmBackupAes256AuthData + private var privateKey: ByteArray? = null init { if (keysVersions.algorithm != MXCRYPTO_ALGORITHM_AES_256_BACKUP) { @@ -37,21 +46,92 @@ internal class KeysBackupAes256Algorithm(keysVersions: KeysVersionResult) : Keys aesAuthData = keysVersions.getAuthDataAsMegolmBackupAuthData() as MegolmBackupAes256AuthData } + override fun setRecoveryKey(recoveryKey: String?) { + privateKey = extractCurveKeyFromRecoveryKey(recoveryKey) + } + override fun encryptSession(sessionData: MegolmSessionData): JsonDict? { - TODO("Not yet implemented") + val privateKey = privateKey + if (privateKey == null || !keyMatches(privateKey)) { + Timber.e("Key does not match") + throw IllegalStateException("Key does not match") + } + val encryptedSessionBackupData = try { + val sessionDataJson = sessionData.asBackupJson() + AesHmacSha2.encrypt(privateKey = privateKey, secretName = sessionData.sessionId ?: "", sessionDataJson) + } catch (e: OlmException) { + Timber.e(e, "Error while encrypting backup data.") + null + } ?: return null + + return mapOf( + "ciphertext" to encryptedSessionBackupData.cipherRawBytes.toBase64NoPadding(), + "mac" to encryptedSessionBackupData.mac.toBase64NoPadding(), + "iv" to encryptedSessionBackupData.initializationVector.toBase64NoPadding() + ) } - override fun decryptSessions(recoveryKey: String, data: KeysBackupData): List { - TODO("Not yet implemented") + override fun decryptSessions(data: KeysBackupData): List { + val privateKey = privateKey + if (privateKey == null || !keyMatches(privateKey)) { + Timber.e("Invalid recovery key for this keys version") + 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, privateKey) + 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, privateKey: ByteArray): MegolmSessionData? { + + val cipherRawBytes = sessionData["ciphertext"]?.toString()?.fromBase64() ?: return null + val mac = sessionData["mac"]?.toString()?.fromBase64() ?: throw IllegalStateException("Bad mac") + val iv = sessionData["iv"]?.toString()?.fromBase64() ?: ByteArray(16) + + val encryptionInfo = AesHmacSha2.EncryptionInfo( + cipherRawBytes = cipherRawBytes, + mac = mac, + initializationVector = iv + ) + return try { + val decrypted = AesHmacSha2.decrypt(privateKey, sessionId, encryptionInfo) + createMegolmSessionData(decrypted, sessionId, roomId) + } catch (e: Exception) { + Timber.e(e, "Exception while decrypting") + null + } } override fun release() { - TODO("Not yet implemented") + privateKey?.apply { + Arrays.fill(this, 0) + } + privateKey = null } override val authData: MegolmBackupAuthData = aesAuthData - override fun keyMatches(key: String): Boolean { - TODO("Not yet implemented") + override fun keyMatches(privateKey: ByteArray): Boolean { + return if (aesAuthData.mac != null) { + val keyCheckMac = AesHmacSha2.calculateKeyCheck(privateKey, aesAuthData.iv).mac + val authDataMac = aesAuthData.mac.fromBase64() + return keyCheckMac.contentEquals(authDataMac) + } else { + // if we have no information, we have to assume the key is right + true + } } } 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 index 436ac15ed5f..812d34984d2 100644 --- 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 @@ -20,13 +20,43 @@ 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 +import org.matrix.android.sdk.internal.di.MoshiProvider internal interface KeysBackupAlgorithm { + val authData: MegolmBackupAuthData val untrusted: Boolean + fun setRecoveryKey(recoveryKey: String?) fun encryptSession(sessionData: MegolmSessionData): JsonDict? - fun decryptSessions(recoveryKey: String, data: KeysBackupData): List - fun keyMatches(key: String): Boolean + fun decryptSessions(data: KeysBackupData): List + fun keyMatches(privateKey: ByteArray): Boolean fun release() } + +internal fun KeysBackupAlgorithm.createMegolmSessionData(decrypted: String, sessionId: String, roomId: String): MegolmSessionData? { + val moshi = MoshiProvider.providesMoshi() + val adapter = moshi.adapter(MegolmSessionData::class.java) + val sessionBackupData: MegolmSessionData = adapter.fromJson(decrypted) ?: return null + return sessionBackupData.copy( + sessionId = sessionId, + roomId = roomId, + untrusted = untrusted + ) +} + +internal fun MegolmSessionData.asBackupJson(): String { + val sessionBackupData = mapOf( + "algorithm" to algorithm, + "sender_key" to senderKey, + "sender_claimed_keys" to senderClaimedKeys, + "forwarding_curve25519_key_chain" to (forwardingCurve25519KeyChain.orEmpty()), + "session_key" to sessionKey, + "org.matrix.msc3061.shared_history" to sharedHistory, + "untrusted" to untrusted + ) + return MoshiProvider.providesMoshi() + .adapter(Map::class.java) + .toJson(sessionBackupData) +} + 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 index eb8ddca2278..627024991b2 100644 --- 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 @@ -24,7 +24,6 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromR 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 @@ -40,6 +39,7 @@ internal class KeysBackupCurve25519Algorithm(keysVersions: KeysVersionResult) : private val publicKey: String private val encryptionKey: OlmPkEncryption + private var privateKey: ByteArray? = null init { if (keysVersions.algorithm != MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP) { @@ -52,26 +52,17 @@ internal class KeysBackupCurve25519Algorithm(keysVersions: KeysVersionResult) : } } - 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) + override fun setRecoveryKey(recoveryKey: String?) { + privateKey = extractCurveKeyFromRecoveryKey(recoveryKey) + } + override fun encryptSession(sessionData: MegolmSessionData): JsonDict? { val encryptedSessionBackupData = try { - encryptionKey.encrypt(json) + val sessionDataJson = sessionData.asBackupJson() + encryptionKey.encrypt(sessionDataJson) } catch (e: OlmException) { Timber.e(e, "Error while encrypting backup data.") - null + throw e } ?: return null return mapOf( @@ -81,29 +72,25 @@ internal class KeysBackupCurve25519Algorithm(keysVersions: KeysVersionResult) : ) } - 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 + override fun decryptSessions(data: KeysBackupData): List { + fun pkDecryptionFromPrivateKey(privateKey: ByteArray): OlmPkDecryption? { var decryption: OlmPkDecryption? = null - if (privateKey != null) { - try { - decryption = OlmPkDecryption() - decryption.setPrivateKey(privateKey) - } catch (e: OlmException) { - Timber.e(e, "OlmException") - } + try { + decryption = OlmPkDecryption() + decryption.setPrivateKey(privateKey) + } catch (e: OlmException) { + Timber.e(e, "OlmException") } return decryption } - if (!keyMatches(recoveryKey)) { + val privateKey = this.privateKey + if (privateKey == null || !keyMatches(privateKey)) { Timber.e("Invalid recovery key for this keys version") throw InvalidParameterException("Invalid recovery key") } // Get a PK decryption instance - val decryption = pkDecryptionFromRecoveryKey(recoveryKey) + val decryption = pkDecryptionFromPrivateKey(privateKey) if (decryption == null) { // This should not happen anymore Timber.e("Invalid recovery key. Error") @@ -121,6 +108,7 @@ internal class KeysBackupCurve25519Algorithm(keysVersions: KeysVersionResult) : } } } + decryption.releaseDecryption() Timber.v( "Decrypted ${sessionsData.size} keys out of $sessionsFromHsCount from the backup store on the homeserver" ) @@ -128,39 +116,26 @@ internal class KeysBackupCurve25519Algorithm(keysVersions: KeysVersionResult) : } 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) { + return 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) + createMegolmSessionData(decrypted, sessionId, roomId) } catch (e: OlmException) { - Timber.e(e, "OlmException") - } - - if (sessionBackupData != null) { - sessionBackupData = sessionBackupData.copy( - sessionId = sessionId, - roomId = roomId, - untrusted = untrusted - ) + Timber.e(e, "Exception while decrypting") + null } + } else { + null } - - return sessionBackupData } override fun release() { @@ -169,14 +144,8 @@ internal class KeysBackupCurve25519Algorithm(keysVersions: KeysVersionResult) : 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 - } + override fun keyMatches(privateKey: ByteArray): Boolean { + fun pkPublicKeyFromPrivateKey(privateKey: ByteArray): String? { // Built the PK decryption with it val decryption = OlmPkDecryption() val pkPublicKey = try { @@ -189,7 +158,7 @@ internal class KeysBackupCurve25519Algorithm(keysVersions: KeysVersionResult) : return pkPublicKey } - val publicKey = pkPublicKeyFromRecoveryKey(key) + val publicKey = pkPublicKeyFromPrivateKey(privateKey) if (publicKey == null) { Timber.w("Public key is null") return false diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt index 70e19f744fe..bc3388e3a91 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt @@ -221,12 +221,12 @@ internal class DefaultSharedSecretStorageService @Inject constructor( val iv = cipherContent.initializationVector?.fromBase64() ?: ByteArray(16) val cipher = cipherContent.ciphertext?.fromBase64() ?: throw SharedSecretStorageError.BadCipherText val mac = cipherContent.mac?.fromBase64() ?: throw SharedSecretStorageError.BadMac - val encryptedResult = AesHmacSha2.Result( + val encryptionInfo = AesHmacSha2.EncryptionInfo( cipherRawBytes = cipher, initializationVector = iv, mac = mac ) - return AesHmacSha2.decrypt(secretKey.privateKey, secretName, encryptedResult) + return AesHmacSha2.decrypt(secretKey.privateKey, secretName, encryptionInfo) } override fun getAlgorithmsForSecret(name: String): List { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/AesHmacSha2.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/AesHmacSha2.kt index a7fac4ce4ba..6125f0bde30 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/AesHmacSha2.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/AesHmacSha2.kt @@ -27,7 +27,7 @@ import kotlin.experimental.and internal object AesHmacSha2 { - class Result( + class EncryptionInfo( val cipherRawBytes: ByteArray, val mac: ByteArray, val initializationVector: ByteArray @@ -55,7 +55,7 @@ internal object AesHmacSha2 { * (We use AES-CTR to match file encryption and key exports.) */ @Throws - fun encrypt(privateKey: ByteArray, secretName: String, clearDataBase64: String, ivString: String? = null): Result { + fun encrypt(privateKey: ByteArray, secretName: String, clearDataBase64: String, ivString: String? = null): EncryptionInfo { val pseudoRandomKey = HkdfSha256.deriveSecret( privateKey, ByteArray(32) { 0.toByte() }, @@ -94,14 +94,14 @@ internal object AesHmacSha2 { mac.init(macKeySpec) val digest = mac.doFinal(cipherBytes) - return Result( + return EncryptionInfo( cipherRawBytes = cipherBytes, initializationVector = iv, mac = digest ) } - fun decrypt(privateKey: ByteArray, secretName: String, aesHmacSha2Result: Result): String { + fun decrypt(privateKey: ByteArray, secretName: String, aesHmacSha2Result: EncryptionInfo): String { val pseudoRandomKey = HkdfSha256.deriveSecret( privateKey, zeroByteArray(32), @@ -140,9 +140,9 @@ internal object AesHmacSha2 { * @param {ByteArray} [key] the key to use * @param {string} [iv] The initialization vector as a base64-encoded string. * If omitted, a random initialization vector will be created. - * @return [Result] An object that contains, `mac` and `iv` properties. + * @return [EncryptionInfo] An object that contains, `mac` and `iv` properties. */ - fun calculateKeyCheck(key: ByteArray, iv: String?): Result { + fun calculateKeyCheck(key: ByteArray, iv: String?): EncryptionInfo { val zerosStr = String(zeroByteArray(32)) return encrypt(key, "", zerosStr, iv) } From 1db5c662d2ac32b55fafeb90ab39de3956adbcb4 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 22 Aug 2022 17:47:20 +0200 Subject: [PATCH 06/10] Expose setPrivateKey instead of setRecoveryKey as curveKey shouldn't not be used (let valere continue on the privatekey generation) --- .../sdk/internal/crypto/keysbackup/KeysBackupTest.kt | 4 +++- .../crypto/keysbackup/DefaultKeysBackupService.kt | 8 ++++++-- .../keysbackup/algorithm/KeysBackupAes256Algorithm.kt | 4 ++-- .../crypto/keysbackup/algorithm/KeysBackupAlgorithm.kt | 2 +- .../keysbackup/algorithm/KeysBackupCurve25519Algorithm.kt | 4 ++-- 5 files changed, 14 insertions(+), 8 deletions(-) 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 6eda1b16049..aad9e90780d 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 @@ -322,7 +322,9 @@ class KeysBackupTest : InstrumentedTest { put(cryptoTestData.roomId, roomKeysBackupData) } ) - algorithm.setRecoveryKey(keyBackupCreationInfo.recoveryKey) + extractCurveKeyFromRecoveryKey(keyBackupCreationInfo.recoveryKey)?.also { + algorithm.setPrivateKey(it) + } val sessionsData = algorithm.decryptSessions(keysBackupData) val sessionData = sessionsData.firstOrNull() assertNotNull(sessionData) 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 22590b97fe2..0344665ed5d 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 @@ -632,7 +632,9 @@ internal class DefaultKeysBackupService @Inject constructor( // Get backed up keys from the homeserver val data = getKeys(sessionId, roomId, keysVersionResult.version) - algorithm?.setRecoveryKey(recoveryKey) + extractCurveKeyFromRecoveryKey(recoveryKey)?.also { privateKey -> + algorithm?.setPrivateKey(privateKey) + } val sessionsData = withContext(coroutineDispatchers.computation) { algorithm?.decryptSessions(data) }.orEmpty() @@ -1138,7 +1140,9 @@ internal class DefaultKeysBackupService @Inject constructor( // roomId -> sessionId -> MXKeyBackupData val keysBackupData = KeysBackupData() val recoveryKey = cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey - algorithm?.setRecoveryKey(recoveryKey) + extractCurveKeyFromRecoveryKey(recoveryKey)?.also { privateKey -> + algorithm?.setPrivateKey(privateKey) + } olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper -> val roomId = olmInboundGroupSessionWrapper.roomId ?: return@forEach val olmInboundGroupSession = olmInboundGroupSessionWrapper.session diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt index 87245cfc191..4a3ee60d329 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt @@ -46,8 +46,8 @@ internal class KeysBackupAes256Algorithm(keysVersions: KeysVersionResult) : Keys aesAuthData = keysVersions.getAuthDataAsMegolmBackupAuthData() as MegolmBackupAes256AuthData } - override fun setRecoveryKey(recoveryKey: String?) { - privateKey = extractCurveKeyFromRecoveryKey(recoveryKey) + override fun setPrivateKey(privateKey: ByteArray) { + this.privateKey = privateKey } override fun encryptSession(sessionData: MegolmSessionData): JsonDict? { 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 index 812d34984d2..0e15a6eed61 100644 --- 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 @@ -27,7 +27,7 @@ internal interface KeysBackupAlgorithm { val authData: MegolmBackupAuthData val untrusted: Boolean - fun setRecoveryKey(recoveryKey: String?) + fun setPrivateKey(privateKey: ByteArray) fun encryptSession(sessionData: MegolmSessionData): JsonDict? fun decryptSessions(data: KeysBackupData): List fun keyMatches(privateKey: ByteArray): Boolean 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 index 627024991b2..cf1abe831be 100644 --- 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 @@ -52,8 +52,8 @@ internal class KeysBackupCurve25519Algorithm(keysVersions: KeysVersionResult) : } } - override fun setRecoveryKey(recoveryKey: String?) { - privateKey = extractCurveKeyFromRecoveryKey(recoveryKey) + override fun setPrivateKey(privateKey: ByteArray) { + this.privateKey = privateKey } override fun encryptSession(sessionData: MegolmSessionData): JsonDict? { From b63e3d69ed49f339356e240792118b5fe6c84602 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Aug 2022 17:26:04 +0200 Subject: [PATCH 07/10] added prepare and tests --- .../crypto/keysbackup/KeysBackupTest.kt | 58 ++++++---- .../crypto/keysbackup/KeysBackupTestHelper.kt | 13 ++- .../keysbackup/SymmetricKeysBackupTest.kt | 33 ++++++ .../android/sdk/api/crypto/CryptoConstants.kt | 1 + .../crypto/keysbackup/KeyBackupConfig.kt | 26 +++++ .../crypto/keysbackup/KeysBackupService.kt | 7 ++ .../keysbackup/DefaultKeysBackupService.kt | 59 +++++++++- .../keysbackup/PrepareKeysBackupUseCase.kt | 106 +++++++++++++----- .../algorithm/KeysBackupAes256Algorithm.kt | 2 - .../algorithm/KeysBackupAlgorithm.kt | 1 - .../KeysBackupCurve25519Algorithm.kt | 2 - .../model/SignalableMegolmBackupAuthData.kt | 36 ------ .../sdk/internal/crypto/tools/AesHmacSha2.kt | 1 - 13 files changed, 245 insertions(+), 100 deletions(-) create mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/SymmetricKeysBackupTest.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeyBackupConfig.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/SignalableMegolmBackupAuthData.kt 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 aad9e90780d..37141870c0d 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 @@ -33,6 +33,7 @@ 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.listeners.StepProgressListener import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeyBackupConfig import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener @@ -46,9 +47,12 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromR 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 +import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest +import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.RetryTestRule +import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.common.waitFor import org.matrix.android.sdk.internal.crypto.keysbackup.algorithm.KeysBackupAlgorithmFactory @@ -63,10 +67,21 @@ import kotlin.coroutines.resume @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest -class KeysBackupTest : InstrumentedTest { +open class KeysBackupTest : InstrumentedTest { @get:Rule val rule = RetryTestRule(3) + @Test + fun default_config_should_be_assymetric_only() = runSessionTest(context()) { testHelper -> + val session = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + + val defaultConfig = session.cryptoService().keysBackupService().keyBackupConfig + + assertEquals(MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP, defaultConfig.defaultAlgorithm) + assertEquals(1, defaultConfig.supportedAlgorithms.size) + assertTrue(defaultConfig.supportedAlgorithms.contains(MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP)) + } + /** * - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys * - Check backup keys after having marked one as backed up @@ -207,7 +222,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun backupAfterCreateKeysBackupVersionTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val keysBackupTestHelper = createKeysBackupTestHelper(testHelper, cryptoTestHelper) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -248,7 +263,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun backupAllGroupSessionsTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val keysBackupTestHelper = createKeysBackupTestHelper(testHelper, cryptoTestHelper) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -293,7 +308,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun testEncryptAndDecryptKeysBackupData() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val keysBackupTestHelper = createKeysBackupTestHelper(testHelper, cryptoTestHelper) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -342,7 +357,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun restoreKeysBackupTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val keysBackupTestHelper = createKeysBackupTestHelper(testHelper, cryptoTestHelper) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) @@ -428,7 +443,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun trustKeyBackupVersionTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val keysBackupTestHelper = createKeysBackupTestHelper(testHelper, cryptoTestHelper) // - Do an e2e backup to the homeserver with a recovery key // - And log Alice on a new device @@ -488,7 +503,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun trustKeyBackupVersionWithRecoveryKeyTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val keysBackupTestHelper = createKeysBackupTestHelper(testHelper, cryptoTestHelper) // - Do an e2e backup to the homeserver with a recovery key // - And log Alice on a new device @@ -546,7 +561,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun trustKeyBackupVersionWithWrongRecoveryKeyTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val keysBackupTestHelper = createKeysBackupTestHelper(testHelper, cryptoTestHelper) // - Do an e2e backup to the homeserver with a recovery key // - And log Alice on a new device @@ -588,7 +603,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun trustKeyBackupVersionWithPasswordTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val keysBackupTestHelper = createKeysBackupTestHelper(testHelper, cryptoTestHelper) val password = "Password" @@ -648,7 +663,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun trustKeyBackupVersionWithWrongPasswordTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val keysBackupTestHelper = createKeysBackupTestHelper(testHelper, cryptoTestHelper) val password = "Password" val badPassword = "Bad Password" @@ -689,7 +704,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun restoreKeysBackupWithAWrongRecoveryKeyTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val keysBackupTestHelper = createKeysBackupTestHelper(testHelper, cryptoTestHelper) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService() @@ -717,7 +732,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun testBackupWithPassword() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val keysBackupTestHelper = createKeysBackupTestHelper(testHelper, cryptoTestHelper) val password = "password" @@ -773,7 +788,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun restoreKeysBackupWithAWrongPasswordTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val keysBackupTestHelper = createKeysBackupTestHelper(testHelper, cryptoTestHelper) val password = "password" val wrongPassword = "passw0rd" @@ -804,7 +819,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val keysBackupTestHelper = createKeysBackupTestHelper(testHelper, cryptoTestHelper) val password = "password" @@ -833,7 +848,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val keysBackupTestHelper = createKeysBackupTestHelper(testHelper, cryptoTestHelper) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService() @@ -859,7 +874,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun testIsKeysBackupTrusted() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val keysBackupTestHelper = createKeysBackupTestHelper(testHelper, cryptoTestHelper) // - Create a backup version val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -904,7 +919,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun testBackupWhenAnotherBackupWasCreated() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val keysBackupTestHelper = createKeysBackupTestHelper(testHelper, cryptoTestHelper) // - Create a backup version val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -979,7 +994,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun testBackupAfterVerifyingADevice() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val keysBackupTestHelper = createKeysBackupTestHelper(testHelper, cryptoTestHelper) // - Create a backup version val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -1066,7 +1081,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun deleteKeysBackupTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> - val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val keysBackupTestHelper = createKeysBackupTestHelper(testHelper, cryptoTestHelper) // - Create a backup version val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -1089,4 +1104,9 @@ class KeysBackupTest : InstrumentedTest { stateObserver.stopAndCheckStates(null) } + + open var keyBackupConfig: KeyBackupConfig? = null + + private fun createKeysBackupTestHelper(testHelper: CommonTestHelper, cryptoTestHelper: CryptoTestHelper) = + KeysBackupTestHelper(testHelper, cryptoTestHelper, keyBackupConfig) } 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 e3e2ea2b178..eebce9af490 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 @@ -20,6 +20,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine import org.junit.Assert import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeyBackupConfig 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 @@ -34,7 +35,8 @@ import kotlin.coroutines.resume internal class KeysBackupTestHelper( private val testHelper: CommonTestHelper, - private val cryptoTestHelper: CryptoTestHelper + private val cryptoTestHelper: CryptoTestHelper, + private val keyBackupConfig: KeyBackupConfig? = null ) { fun waitForKeybackUpBatching() { @@ -55,6 +57,9 @@ internal class KeysBackupTestHelper( val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() + if (keyBackupConfig != null) { + keysBackup.keyBackupConfig = keyBackupConfig + } val stateObserver = StateObserver(keysBackup) @@ -81,7 +86,10 @@ internal class KeysBackupTestHelper( // - Log Alice on a new device val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync) - + if (keyBackupConfig != null) { + aliceSession2.cryptoService().keysBackupService().keyBackupConfig = keyBackupConfig + } + aliceSession2.cryptoService().keysBackupService().checkAndStartKeysBackup() // Test check: aliceSession2 has no keys at login Assert.assertEquals(0, aliceSession2.cryptoService().inboundGroupSessionsCount(false)) @@ -116,6 +124,7 @@ internal class KeysBackupTestHelper( val keysVersion = testHelper.waitForCallback { keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it) } + keysBackup.saveBackupRecoveryKey(megolmBackupCreationInfo.recoveryKey, version = keysVersion.version) Assert.assertNotNull("Key backup version should not be null", keysVersion.version) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/SymmetricKeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/SymmetricKeysBackupTest.kt new file mode 100644 index 00000000000..ccc8afd6d67 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/SymmetricKeysBackupTest.kt @@ -0,0 +1,33 @@ +/* + * 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 androidx.test.filters.LargeTest +import org.junit.FixMethodOrder +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_AES_256_BACKUP +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeyBackupConfig + +@FixMethodOrder(MethodSorters.JVM) +@LargeTest +class SymmetricKeysBackupTest : KeysBackupTest() { + + override var keyBackupConfig: KeyBackupConfig? = KeyBackupConfig( + defaultAlgorithm = MXCRYPTO_ALGORITHM_AES_256_BACKUP, + supportedAlgorithms = listOf(MXCRYPTO_ALGORITHM_AES_256_BACKUP) + ) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt index fd1ef9748d1..fa0c10f852e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt @@ -33,6 +33,7 @@ const val MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP = "m.megolm_backup.v1.curve25519 /** * Matrix algorithm value for AES-256 megolm keys backup. + * Symmetric megolm backup */ const val MXCRYPTO_ALGORITHM_AES_256_BACKUP = "org.matrix.msc3270.v1.aes-hmac-sha2" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeyBackupConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeyBackupConfig.kt new file mode 100644 index 00000000000..b5bafe9e992 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeyBackupConfig.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 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 org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP + +data class KeyBackupConfig( + val defaultAlgorithm: String = MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP, + val supportedAlgorithms: List = listOf(MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP) +) { + fun isAlgorithmSupported(alg: String) = supportedAlgorithms.contains(alg) +} 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 ea0970f677e..006ad99a791 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 @@ -22,6 +22,13 @@ import org.matrix.android.sdk.api.listeners.StepProgressListener import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult interface KeysBackupService { + + /** + * Define the backup internal configuration, supported algorithm, default algorithm. + * @param keyBackupConfig the config + */ + var keyBackupConfig: KeyBackupConfig + /** * Retrieve the current version of the backup from the homeserver. * 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 0344665ed5d..fda1d7c4109 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 @@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.listeners.StepProgressListener +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeyBackupConfig import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState @@ -87,6 +88,7 @@ import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.JsonCanonicalizer import org.matrix.olm.OlmException import timber.log.Timber +import java.security.InvalidParameterException import javax.inject.Inject import kotlin.random.Random @@ -128,6 +130,11 @@ internal class DefaultKeysBackupService @Inject constructor( private val uiHandler: Handler, ) : KeysBackupService { + override var keyBackupConfig = KeyBackupConfig( + defaultAlgorithm = MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP, + supportedAlgorithms = listOf(MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP) + ) + // The backup version override var keysBackupVersion: KeysVersionResult? = null private set @@ -160,9 +167,10 @@ internal class DefaultKeysBackupService @Inject constructor( ) { cryptoCoroutineScope.launch { prepareKeysBackup( - algorithm = algorithm ?: DEFAULT_ALGORITHM, + algorithm = algorithm ?: keyBackupConfig.defaultAlgorithm, password = password, progressListener = progressListener, + config = keyBackupConfig, callback = callback ) } @@ -172,6 +180,9 @@ internal class DefaultKeysBackupService @Inject constructor( keysBackupCreationInfo: MegolmBackupCreationInfo, callback: MatrixCallback ) { + if (!keyBackupConfig.isAlgorithmSupported(keysBackupCreationInfo.algorithm)) return Unit.also { + callback.onFailure(IllegalArgumentException("Unsupported algorithm")) + } @Suppress("UNCHECKED_CAST") val createKeysBackupVersionBody = CreateKeysBackupVersionBody( algorithm = keysBackupCreationInfo.algorithm, @@ -364,6 +375,10 @@ internal class DefaultKeysBackupService @Inject constructor( private fun getKeysBackupTrustBg(keysBackupVersion: KeysVersionResult): KeysBackupVersionTrust { val authData = keysBackupVersion.getAuthDataAsMegolmBackupAuthData() + if (!keyBackupConfig.isAlgorithmSupported(keysBackupVersion.algorithm)) { + return KeysBackupVersionTrust(usable = false) + } + if (authData == null || authData.signatures.isNullOrEmpty()) { Timber.v("getKeysBackupTrust: Key backup is absent or missing required data") return KeysBackupVersionTrust(usable = false) @@ -453,6 +468,14 @@ internal class DefaultKeysBackupService @Inject constructor( ) { Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}") + if (!keyBackupConfig.isAlgorithmSupported(keysBackupVersion.algorithm)) { + Timber.w("trustKeyBackupVersion:trust unsupported algorithm ${keysBackupVersion.algorithm}") + uiHandler.post { + callback.onFailure(IllegalArgumentException("Missing element")) + } + return + } + // Get auth data to update it val authData = keysBackupVersion.getValidAuthData() @@ -621,23 +644,33 @@ internal class DefaultKeysBackupService @Inject constructor( stepProgressListener: StepProgressListener?, callback: MatrixCallback ) { - Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}") + Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version} alg:${keysVersionResult.algorithm}") cryptoCoroutineScope.launch(coroutineDispatchers.io) { runCatching { + if (!keyBackupConfig.isAlgorithmSupported(keysVersionResult.algorithm)) { + throw IllegalArgumentException("Unsupported algorithm") + } + val backupAlgorithm = algorithmFactory.create(keysVersionResult) + val privateKey = extractCurveKeyFromRecoveryKey(recoveryKey) + if (privateKey == null || !backupAlgorithm.keyMatches(privateKey)) { + Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version") + 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. + backupAlgorithm.setPrivateKey(privateKey) saveBackupRecoveryKey(recoveryKey, keysVersionResult.version) stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey) // Get backed up keys from the homeserver val data = getKeys(sessionId, roomId, keysVersionResult.version) extractCurveKeyFromRecoveryKey(recoveryKey)?.also { privateKey -> - algorithm?.setPrivateKey(privateKey) + backupAlgorithm.setPrivateKey(privateKey) } val sessionsData = withContext(coroutineDispatchers.computation) { - algorithm?.decryptSessions(data) - }.orEmpty() + backupAlgorithm.decryptSessions(data) + } // 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) { @@ -1002,6 +1035,10 @@ internal class DefaultKeysBackupService @Inject constructor( @WorkerThread private fun isValidRecoveryKeyForKeysBackupVersion(recoveryKey: String, keysBackupData: KeysVersionResult): Boolean { return try { + if (!keyBackupConfig.isAlgorithmSupported(keysBackupData.algorithm)) { + Timber.w("isValidRecoveryKeyForKeysBackupVersion: unsupported algorithm ${keysBackupData.algorithm}") + return false + } val algorithm = algorithmFactory.create(keysBackupData) val privateKey = extractCurveKeyFromRecoveryKey(recoveryKey) ?: return false val isValid = algorithm.keyMatches(privateKey) @@ -1040,7 +1077,11 @@ internal class DefaultKeysBackupService @Inject constructor( */ private fun enableKeysBackup(keysVersionResult: KeysVersionResult) { val retrievedMegolmBackupAuthData = keysVersionResult.getAuthDataAsMegolmBackupAuthData() - + if (!keyBackupConfig.isAlgorithmSupported(keysVersionResult.algorithm)) { + Timber.w("enableKeysBackup: unsupported algorithm ${keysVersionResult.algorithm}") + keysBackupStateManager.state = KeysBackupState.Disabled + return + } if (retrievedMegolmBackupAuthData != null) { keysBackupVersion = keysVersionResult cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { @@ -1115,6 +1156,12 @@ internal class DefaultKeysBackupService @Inject constructor( Timber.v("backupKeys: Invalid state: ${getState()}") return } + val recoveryKey = cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey + extractCurveKeyFromRecoveryKey(recoveryKey)?.also { privateKey -> + // symmetric algorithm need private key to backup :/ + // a bit hugly, but all this code needs refactoring + algorithm?.setPrivateKey(privateKey) + } // Get a chunk of keys to backup val olmInboundGroupSessionWrappers = cryptoStore.inboundGroupSessionsToBackup(KEY_BACKUP_SEND_KEYS_MAX_COUNT) 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 index a2e3c0f5019..926ea8f96c2 100644 --- 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 @@ -21,17 +21,23 @@ 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_AES_256_BACKUP 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.KeyBackupConfig +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.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.api.util.toBase64NoPadding 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.crypto.tools.AesHmacSha2 import org.matrix.android.sdk.internal.util.JsonCanonicalizer import org.matrix.olm.OlmPkDecryption import timber.log.Timber +import java.security.SecureRandom import javax.inject.Inject internal class PrepareKeysBackupUseCase @Inject constructor( @@ -46,13 +52,15 @@ internal class PrepareKeysBackupUseCase @Inject constructor( algorithm: String, password: String?, progressListener: ProgressListener?, + config: KeyBackupConfig, callback: MatrixCallback ) = withContext(coroutineDispatchers.io) { + if (!config.isAlgorithmSupported(algorithm)) return@withContext Unit.also { + callback.onFailure(IllegalArgumentException("Unsupported algorithm")) + } 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")) } @@ -62,7 +70,7 @@ internal class PrepareKeysBackupUseCase @Inject constructor( private fun prepareCurve(password: String?, progressListener: ProgressListener?, callback: MatrixCallback) { val olmPkDecryption = OlmPkDecryption() try { - val signalableMegolmBackupAuthData = if (password != null) { + val megolmBackupAuthData = if (password != null) { // Generate a private key from the password val backgroundProgressListener = if (progressListener == null) { null @@ -80,40 +88,20 @@ internal class PrepareKeysBackupUseCase @Inject constructor( } } val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener) - SignalableMegolmBackupAuthData( + MegolmBackupCurve25519AuthData( publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey), privateKeySalt = generatePrivateKeyResult.salt, privateKeyIterations = generatePrivateKeyResult.iterations ) } else { val publicKey = olmPkDecryption.generateKey() - SignalableMegolmBackupAuthData( + MegolmBackupCurve25519AuthData( publicKey = publicKey ) } + val signatures = signKeyBackup(megolmBackupAuthData) - 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, + val signedMegolmBackupCurve25519AuthData = megolmBackupAuthData.copy( signatures = signatures ) val creationInfo = MegolmBackupCreationInfo( @@ -133,9 +121,65 @@ internal class PrepareKeysBackupUseCase @Inject constructor( } } - /* - private fun prepareAES(password: String?, progressListener: ProgressListener?, callback: MatrixCallback) { + private fun signKeyBackup(authData: MegolmBackupAuthData): MutableMap> { + val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, authData.toSignalableJsonDict()) + 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") + } + return signatures } - */ + private fun prepareAES(password: String?, progressListener: ProgressListener?, callback: MatrixCallback) { + try { + val privateKey: ByteArray + val authData: MegolmBackupAes256AuthData + if (password == null) { + privateKey = ByteArray(32).also { + SecureRandom().nextBytes(it) + } + authData = MegolmBackupAes256AuthData() + } else { + val result = generatePrivateKeyWithPassword(password, progressListener) + privateKey = result.privateKey + authData = MegolmBackupAes256AuthData( + privateKeySalt = result.salt, + privateKeyIterations = result.iterations + ) + } + val encInfo = AesHmacSha2.calculateKeyCheck(privateKey, null) + val authDataWithKeyCheck = authData.copy( + iv = encInfo.initializationVector.toBase64NoPadding(), + mac = encInfo.mac.toBase64NoPadding() + ) + + val signatures = signKeyBackup(authDataWithKeyCheck) + val creationInfo = MegolmBackupCreationInfo( + algorithm = MXCRYPTO_ALGORITHM_AES_256_BACKUP, + authData = authDataWithKeyCheck.copy( + signatures = signatures + ), + recoveryKey = computeRecoveryKey(privateKey) + ) + + uiHandler.post { + callback.onSuccess(creationInfo) + } + } catch (failure: Throwable) { + uiHandler.post { + callback.onFailure(failure) + } + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt index 4a3ee60d329..6045f85088e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt @@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_AES_256_BACKUP import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult 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.extractCurveKeyFromRecoveryKey import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.fromBase64 import org.matrix.android.sdk.api.util.toBase64NoPadding @@ -96,7 +95,6 @@ internal class KeysBackupAes256Algorithm(keysVersions: KeysVersionResult) : Keys } private fun decryptSession(sessionData: JsonDict, sessionId: String, roomId: String, privateKey: ByteArray): MegolmSessionData? { - val cipherRawBytes = sessionData["ciphertext"]?.toString()?.fromBase64() ?: return null val mac = sessionData["mac"]?.toString()?.fromBase64() ?: throw IllegalStateException("Bad mac") val iv = sessionData["iv"]?.toString()?.fromBase64() ?: ByteArray(16) 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 index 0e15a6eed61..dc53831f391 100644 --- 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 @@ -59,4 +59,3 @@ internal fun MegolmSessionData.asBackupJson(): String { .adapter(Map::class.java) .toJson(sessionBackupData) } - 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 index cf1abe831be..e926414784e 100644 --- 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 @@ -20,7 +20,6 @@ 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 @@ -116,7 +115,6 @@ internal class KeysBackupCurve25519Algorithm(keysVersions: KeysVersionResult) : } private fun decryptSession(sessionData: JsonDict, sessionId: String, roomId: String, decryption: OlmPkDecryption): MegolmSessionData? { - val ciphertext = sessionData["ciphertext"]?.toString() val mac = sessionData["mac"]?.toString() val ephemeralKey = sessionData["ephemeral"]?.toString() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/SignalableMegolmBackupAuthData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/SignalableMegolmBackupAuthData.kt deleted file mode 100644 index 85f75a61e2d..00000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/SignalableMegolmBackupAuthData.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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.internal.crypto.keysbackup.model - -import org.matrix.android.sdk.api.util.JsonDict - -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 { - put("private_key_salt", it) - } - privateKeyIterations?.let { - put("private_key_iterations", it) - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/AesHmacSha2.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/AesHmacSha2.kt index 6125f0bde30..be1c465458c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/AesHmacSha2.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/AesHmacSha2.kt @@ -148,5 +148,4 @@ internal object AesHmacSha2 { } private fun zeroByteArray(size: Int): ByteArray = ByteArray(size) { 0.toByte() } - } From e5f19945bd15e7db0c753b60a76a90dd2a26672c Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 1 Sep 2022 11:44:45 +0200 Subject: [PATCH 08/10] Fix bad copy paste breaking 4S restore --- .../crypto/secrets/DefaultSharedSecretStorageService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt index bc3388e3a91..7401f3f02cd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt @@ -212,7 +212,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( return EncryptedSecretContent( ciphertext = result.cipherRawBytes.toBase64NoPadding(), initializationVector = result.initializationVector.toBase64NoPadding(), - mac = result.initializationVector.toBase64NoPadding() + mac = result.mac.toBase64NoPadding() ) } From 77bc5e5b7aa0d6410d109f47667a8e11c671aa29 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 1 Sep 2022 12:42:15 +0200 Subject: [PATCH 09/10] fix copyright --- .../sdk/internal/crypto/keysbackup/SymmetricKeysBackupTest.kt | 4 ++-- .../internal/crypto/keysbackup/PrepareKeysBackupUseCase.kt | 2 +- .../crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt | 2 +- .../crypto/keysbackup/algorithm/KeysBackupAlgorithm.kt | 2 +- .../crypto/keysbackup/algorithm/KeysBackupAlgorithmFactory.kt | 2 +- .../keysbackup/algorithm/KeysBackupCurve25519Algorithm.kt | 2 +- .../matrix/android/sdk/internal/crypto/tools/AesHmacSha2.kt | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/SymmetricKeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/SymmetricKeysBackupTest.kt index ccc8afd6d67..2ca3988506c 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/SymmetricKeysBackupTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/SymmetricKeysBackupTest.kt @@ -1,9 +1,9 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2022 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 + * You may obtain a copy of the License atcle * * http://www.apache.org/licenses/LICENSE-2.0 * 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 index 926ea8f96c2..ba1b001d85d 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2022 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt index 6045f85088e..4731ff8a7ec 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/algorithm/KeysBackupAes256Algorithm.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2022 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. 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 index dc53831f391..891edee0490 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2022 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. 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 index 3db8dd53d1a..6ae35ae0326 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2022 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. 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 index e926414784e..b83114ad0fa 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2022 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/AesHmacSha2.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/AesHmacSha2.kt index be1c465458c..e825e010c89 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/AesHmacSha2.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/AesHmacSha2.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2022 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. @@ -58,7 +58,7 @@ internal object AesHmacSha2 { fun encrypt(privateKey: ByteArray, secretName: String, clearDataBase64: String, ivString: String? = null): EncryptionInfo { val pseudoRandomKey = HkdfSha256.deriveSecret( privateKey, - ByteArray(32) { 0.toByte() }, + zeroByteArray(32), secretName.toByteArray(), 64 ) From 07ec899b49dd9a4d7856d6dda2b15b802c6029f2 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 7 Oct 2022 14:17:25 +0200 Subject: [PATCH 10/10] post rebase fix --- .../android/sdk/common/CryptoTestHelper.kt | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) 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 020451f5b70..d9979246c02 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 @@ -20,6 +20,7 @@ import android.util.Log import org.amshove.kluent.fail import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor @@ -43,7 +44,9 @@ import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerific import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState +import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.Membership @@ -53,6 +56,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.securestorage.EmptyKeySigner import org.matrix.android.sdk.api.session.securestorage.KeyRef +import org.matrix.android.sdk.api.util.awaitCallback import org.matrix.android.sdk.api.util.toBase64NoPadding import java.util.UUID import kotlin.coroutines.Continuation @@ -300,10 +304,10 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { ) // set up megolm backup - val creationInfo = testHelper.waitForCallback { - session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it) + val creationInfo = awaitCallback { + session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, null, it) } - val version = testHelper.waitForCallback { + val version = awaitCallback { session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it) } // Save it for gossiping @@ -315,24 +319,6 @@ 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)) - ) - } } }