From b63e3d69ed49f339356e240792118b5fe6c84602 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Aug 2022 17:26:04 +0200 Subject: [PATCH] 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 aad9e90780..37141870c0 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 e3e2ea2b17..eebce9af49 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 0000000000..ccc8afd6d6 --- /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 fd1ef9748d..fa0c10f852 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 0000000000..b5bafe9e99 --- /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 ea0970f677..006ad99a79 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 0344665ed5..fda1d7c410 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 a2e3c0f501..926ea8f96c 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 4a3ee60d32..6045f85088 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 0e15a6eed6..dc53831f39 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 cf1abe831b..e926414784 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 85f75a61e2..0000000000 --- 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 6125f0bde3..be1c465458 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() } - }