added prepare and tests

This commit is contained in:
Valere 2022-08-31 17:26:04 +02:00
parent 1db5c662d2
commit b63e3d69ed
13 changed files with 245 additions and 100 deletions

View File

@ -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)
}

View File

@ -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<KeysVersion> {
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
}
keysBackup.saveBackupRecoveryKey(megolmBackupCreationInfo.recoveryKey, version = keysVersion.version)
Assert.assertNotNull("Key backup version should not be null", keysVersion.version)

View File

@ -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)
)
}

View File

@ -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"

View File

@ -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<String> = listOf(MXCRYPTO_ALGORITHM_CURVE_25519_BACKUP)
) {
fun isAlgorithmSupported(alg: String) = supportedAlgorithms.contains(alg)
}

View File

@ -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.
*

View File

@ -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<KeysVersion>
) {
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<ImportRoomKeysResult>
) {
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)

View File

@ -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<MegolmBackupCreationInfo>
) = 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<MegolmBackupCreationInfo>) {
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<String, MutableMap<String, String>>()
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<MegolmBackupCreationInfo>) {
private fun signKeyBackup(authData: MegolmBackupAuthData): MutableMap<String, MutableMap<String, String>> {
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, authData.toSignalableJsonDict())
val signatures = mutableMapOf<String, MutableMap<String, String>>()
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<MegolmBackupCreationInfo>) {
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)
}
}
}
}

View File

@ -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)

View File

@ -59,4 +59,3 @@ internal fun MegolmSessionData.asBackupJson(): String {
.adapter(Map::class.java)
.toJson(sessionBackupData)
}

View File

@ -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()

View File

@ -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<String, Any>().apply {
put("public_key", publicKey)
privateKeySalt?.let {
put("private_key_salt", it)
}
privateKeyIterations?.let {
put("private_key_iterations", it)
}
}
}

View File

@ -148,5 +148,4 @@ internal object AesHmacSha2 {
}
private fun zeroByteArray(size: Int): ByteArray = ByteArray(size) { 0.toByte() }
}