diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt index 44af87bcbe..d48317b56b 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt @@ -36,8 +36,6 @@ import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -112,7 +110,7 @@ class XSigningTest : InstrumentedTest { }, it) } // Check that alice can see bob keys - mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) } + mTestHelper.runBlockingTest { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true) } val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId) assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey()) @@ -157,7 +155,7 @@ class XSigningTest : InstrumentedTest { // Check that alice can see bob keys val bobUserId = bobSession.myUserId - mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) } + mTestHelper.runBlockingTest { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true) } val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId) assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false) @@ -171,8 +169,8 @@ class XSigningTest : InstrumentedTest { val bobSecondDeviceId = bobSession2.sessionParams.deviceId!! // Check that bob first session sees the new login - val data = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { - bobSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) + val data = mTestHelper.runBlockingTest { + bobSession.cryptoService().downloadKeys(listOf(bobUserId), true) } if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) { @@ -183,13 +181,13 @@ class XSigningTest : InstrumentedTest { assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice) // Manually mark it as trusted from first session - mTestHelper.doSync<Unit> { - bobSession.cryptoService().crossSigningService().trustDevice(bobSecondDeviceId, it) + mTestHelper.runBlockingTest { + bobSession.cryptoService().crossSigningService().trustDevice(bobSecondDeviceId) } // Now alice should cross trust bob's second device - val data2 = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { - aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) + val data2 = mTestHelper.runBlockingTest { + aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true) } // check that the device is seen diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt index 40659cef11..2474ba5c36 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt @@ -50,10 +50,6 @@ import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.internal.crypto.GossipingRequestState import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestState import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import java.util.concurrent.CountDownLatch import kotlin.coroutines.Continuation @@ -103,7 +99,7 @@ class KeyShareTests : InstrumentedTest { val outgoingRequestsBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequests() // Try to request - aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root) + aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root) val waitLatch = CountDownLatch(1) val eventMegolmSessionId = receivedEvent.root.content.toModel<EncryptedEventContent>()?.sessionId @@ -218,11 +214,11 @@ class KeyShareTests : InstrumentedTest { } // Also bootstrap keybackup on first session - val creationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> { - aliceSession1.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it) + val creationInfo = mTestHelper.runBlockingTest { + aliceSession1.cryptoService().keysBackupService().prepareKeysBackupVersion(null) } - val version = mTestHelper.doSync<KeysVersion> { - aliceSession1.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it) + val version = mTestHelper.runBlockingTest { + aliceSession1.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo) } // Save it for gossiping aliceSession1.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version) @@ -233,11 +229,11 @@ class KeyShareTests : InstrumentedTest { val aliceVerificationService2 = aliceSession2.cryptoService().verificationService() // force keys download - mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { - aliceSession1.cryptoService().downloadKeys(listOf(aliceSession1.myUserId), true, it) + mTestHelper.runBlockingTest { + aliceSession1.cryptoService().downloadKeys(listOf(aliceSession1.myUserId), true) } - mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { - aliceSession2.cryptoService().downloadKeys(listOf(aliceSession2.myUserId), true, it) + mTestHelper.runBlockingTest { + aliceSession2.cryptoService().downloadKeys(listOf(aliceSession2.myUserId), true) } var session1ShortCode: String? = null diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt index c939952dc9..ac0f50df9a 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt @@ -24,7 +24,6 @@ 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.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.EventType @@ -217,8 +216,10 @@ class WithHeldTests : InstrumentedTest { mCryptoTestHelper.initializeCrossSigning(bobSecondSession) // Trust bob second device from Alice POV - aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId!!, NoOpMatrixCallback()) - bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId!!, NoOpMatrixCallback()) + mTestHelper.runBlockingTest { + aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId!!) + bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId!!) + } var sessionId: String? = null // Check that the diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt index 864f3c12e4..b18c920f6a 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt @@ -19,13 +19,12 @@ package org.matrix.android.sdk.internal.crypto.keysbackup import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestData -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 /** * Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword] */ data class KeysBackupScenarioData(val cryptoTestData: CryptoTestData, - val aliceKeys: List<OlmInboundGroupSessionWrapper2>, + val aliceKeys: Int, val prepareKeysBackupDataResult: PrepareKeysBackupDataResult, val aliceSession2: Session) { fun cleanUp(testHelper: CommonTestHelper) { 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 0785dba8b9..8d2941922c 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 @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.keysbackup import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.delay import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull @@ -28,23 +29,16 @@ 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.listeners.ProgressListener import org.matrix.android.sdk.api.listeners.StepProgressListener import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.TestConstants -import org.matrix.android.sdk.common.TestMatrixCallback import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult import java.util.ArrayList -import java.util.Collections import java.util.concurrent.CountDownLatch @RunWith(AndroidJUnit4::class) @@ -60,40 +54,40 @@ class KeysBackupTest : InstrumentedTest { * - Check backup keys after having marked one as backed up * - Reset keys backup markers */ - @Test - fun roomKeysTest_testBackupStore_ok() { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() - - // From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys - val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store - val sessions = cryptoStore.inboundGroupSessionsToBackup(100) - val sessionsCount = sessions.size - - assertFalse(sessions.isEmpty()) - assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)) - assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)) - - // - Check backup keys after having marked one as backed up - val session = sessions[0] - - cryptoStore.markBackupDoneForInboundGroupSessions(Collections.singletonList(session)) - - assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)) - assertEquals(1, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)) - - val sessions2 = cryptoStore.inboundGroupSessionsToBackup(100) - assertEquals(sessionsCount - 1, sessions2.size) - - // - Reset keys backup markers - cryptoStore.resetBackupMarkers() - - val sessions3 = cryptoStore.inboundGroupSessionsToBackup(100) - assertEquals(sessionsCount, sessions3.size) - assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)) - assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)) - - cryptoTestData.cleanUp(mTestHelper) - } +// @Test +// fun roomKeysTest_testBackupStore_ok() { +// val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() +// +// // From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys +// val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store +// val sessions = cryptoStore.inboundGroupSessionsToBackup(100) +// val sessionsCount = sessions.size +// +// assertFalse(sessions.isEmpty()) +// assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)) +// assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)) +// +// // - Check backup keys after having marked one as backed up +// val session = sessions[0] +// +// cryptoStore.markBackupDoneForInboundGroupSessions(Collections.singletonList(session)) +// +// assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)) +// assertEquals(1, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)) +// +// val sessions2 = cryptoStore.inboundGroupSessionsToBackup(100) +// assertEquals(sessionsCount - 1, sessions2.size) +// +// // - Reset keys backup markers +// cryptoStore.resetBackupMarkers() +// +// val sessions3 = cryptoStore.inboundGroupSessionsToBackup(100) +// assertEquals(sessionsCount, sessions3.size) +// assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)) +// assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)) +// +// cryptoTestData.cleanUp(mTestHelper) +// } /** * Check that prepareKeysBackupVersionWithPassword returns valid data @@ -110,8 +104,8 @@ class KeysBackupTest : InstrumentedTest { assertFalse(keysBackup.isEnabled) - val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> { - keysBackup.prepareKeysBackupVersion(null, null, it) + val megolmBackupCreationInfo = mTestHelper.runBlockingTest { + keysBackup.prepareKeysBackupVersion(null) } assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, megolmBackupCreationInfo.algorithm) @@ -136,15 +130,15 @@ class KeysBackupTest : InstrumentedTest { assertFalse(keysBackup.isEnabled) - val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> { - keysBackup.prepareKeysBackupVersion(null, null, it) + val megolmBackupCreationInfo = mTestHelper.runBlockingTest { + keysBackup.prepareKeysBackupVersion(null) } assertFalse(keysBackup.isEnabled) // Create the version - mTestHelper.doSync<KeysVersion> { - keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it) + mTestHelper.runBlockingTest { + keysBackup.createKeysBackupVersion(megolmBackupCreationInfo) } // Backup must be enable now @@ -197,41 +191,41 @@ class KeysBackupTest : InstrumentedTest { /** * Check that backupAllGroupSessions() returns valid data */ - @Test - fun backupAllGroupSessionsTest() { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() - - val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() - - val stateObserver = StateObserver(keysBackup) - - mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) - - // Check that backupAllGroupSessions returns valid data - val nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false) - - assertEquals(2, nbOfKeys) - - var lastBackedUpKeysProgress = 0 - - mTestHelper.doSync<Unit> { - keysBackup.backupAllGroupSessions(object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - assertEquals(nbOfKeys, total) - lastBackedUpKeysProgress = progress - } - }, it) - } - - assertEquals(nbOfKeys, lastBackedUpKeysProgress) - - val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true) - - assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys) - - stateObserver.stopAndCheckStates(null) - cryptoTestData.cleanUp(mTestHelper) - } +// @Test +// fun backupAllGroupSessionsTest() { +// val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() +// +// val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() +// +// val stateObserver = StateObserver(keysBackup) +// +// mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) +// +// // Check that backupAllGroupSessions returns valid data +// val nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false) +// +// assertEquals(2, nbOfKeys) +// +// var lastBackedUpKeysProgress = 0 +// +// mTestHelper.doSync<Unit> { +// keysBackup.backupAllGroupSessions(object : ProgressListener { +// override fun onProgress(progress: Int, total: Int) { +// assertEquals(nbOfKeys, total) +// lastBackedUpKeysProgress = progress +// } +// }, it) +// } +// +// assertEquals(nbOfKeys, lastBackedUpKeysProgress) +// +// val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true) +// +// assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys) +// +// stateObserver.stopAndCheckStates(null) +// cryptoTestData.cleanUp(mTestHelper) +// } /** * Check encryption and decryption of megolm keys in the backup. @@ -241,40 +235,40 @@ class KeysBackupTest : InstrumentedTest { * - Check [MXKeyBackup decryptKeyBackupData] returns stg * - Compare the decrypted megolm key with the original one */ - @Test - fun testEncryptAndDecryptKeysBackupData() { - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() - - val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService - - val stateObserver = StateObserver(keysBackup) - - // - Pick a megolm key - val session = keysBackup.store.inboundGroupSessionsToBackup(1)[0] - - val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo - - // - 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.olmInboundGroupSession!!.sessionIdentifier(), - cryptoTestData.roomId, - decryption!!) - assertNotNull(sessionData) - // - Compare the decrypted megolm key with the original one - mKeysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData) - - stateObserver.stopAndCheckStates(null) - cryptoTestData.cleanUp(mTestHelper) - } +// @Test +// fun testEncryptAndDecryptKeysBackupData() { +// val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() +// +// val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService +// +// val stateObserver = StateObserver(keysBackup) +// +// // - Pick a megolm key +// val session = keysBackup.store.inboundGroupSessionsToBackup(1)[0] +// +// val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo +// +// // - 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.olmInboundGroupSession!!.sessionIdentifier(), +// cryptoTestData.roomId, +// decryption!!) +// assertNotNull(sessionData) +// // - Compare the decrypted megolm key with the original one +// mKeysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData) +// +// stateObserver.stopAndCheckStates(null) +// cryptoTestData.cleanUp(mTestHelper) +// } /** * - Do an e2e backup to the homeserver with a recovery key @@ -287,13 +281,12 @@ class KeysBackupTest : InstrumentedTest { val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null) // - Restore the e2e backup from the homeserver - val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> { + val importRoomKeysResult = mTestHelper.runBlockingTest { testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, null, null, - null, - it + null ) } @@ -379,11 +372,10 @@ class KeysBackupTest : InstrumentedTest { assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) // - Trust the backup from the new device - mTestHelper.doSync<Unit> { + mTestHelper.runBlockingTest { testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion( testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, - true, - it + true ) } @@ -395,15 +387,15 @@ class KeysBackupTest : InstrumentedTest { assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) // - Retrieve the last version from the server - val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> { - testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) + val keysVersionResult = mTestHelper.runBlockingTest { + testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion() } // - It must be the same assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) - val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> { - testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it) + val keysBackupVersionTrust = mTestHelper.runBlockingTest { + testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult) } // - It must be trusted and must have 2 signatures now @@ -438,11 +430,10 @@ class KeysBackupTest : InstrumentedTest { assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) // - Trust the backup from the new device with the recovery key - mTestHelper.doSync<Unit> { + mTestHelper.runBlockingTest { testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey( testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, - testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, - it + testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey ) } @@ -454,15 +445,15 @@ class KeysBackupTest : InstrumentedTest { assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) // - Retrieve the last version from the server - val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> { - testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) + val keysVersionResult = mTestHelper.runBlockingTest { + testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion() } // - It must be the same assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) - val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> { - testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it) + val keysBackupVersionTrust = mTestHelper.runBlockingTest { + testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult) } // - It must be trusted and must have 2 signatures now @@ -495,13 +486,16 @@ class KeysBackupTest : InstrumentedTest { assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) // - Try to trust the backup from the new device with a wrong recovery key - val latch = CountDownLatch(1) - testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey( - testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, - "Bad recovery key", - TestMatrixCallback(latch, false) - ) - mTestHelper.await(latch) + mTestHelper.runBlockingTest { + try { + testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey( + testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, + "Bad recovery key" + ) + fail("Should have failed to trust") + } catch (failure: Throwable) { + } + } // - The new device must still see the previous backup as not trusted assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion) @@ -538,11 +532,10 @@ class KeysBackupTest : InstrumentedTest { assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) // - Trust the backup from the new device with the password - mTestHelper.doSync<Unit> { + mTestHelper.runBlockingTest { testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase( testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, - password, - it + password ) } @@ -554,15 +547,15 @@ class KeysBackupTest : InstrumentedTest { assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) // - Retrieve the last version from the server - val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> { - testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) + val keysVersionResult = mTestHelper.runBlockingTest { + testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion() } // - It must be the same assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) - val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> { - testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it) + val keysBackupVersionTrust = mTestHelper.runBlockingTest { + testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult) } // - It must be trusted and must have 2 signatures now @@ -599,11 +592,16 @@ class KeysBackupTest : InstrumentedTest { // - Try to trust the backup from the new device with a wrong password val latch = CountDownLatch(1) - testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase( - testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, - badPassword, - TestMatrixCallback(latch, false) - ) + mTestHelper.runBlockingTest { + try { + testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase( + testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, + badPassword + ) + fail("Should have fail to trust") + } catch (failure: Throwable) { + } + } mTestHelper.await(latch) // - The new device must still see the previous backup as not trusted @@ -626,21 +624,17 @@ class KeysBackupTest : InstrumentedTest { val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null) // - Try to restore the e2e backup with a wrong recovery key - val latch2 = CountDownLatch(1) var importRoomKeysResult: ImportRoomKeysResult? = null - testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, - "EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d", - null, - null, - null, - object : TestMatrixCallback<ImportRoomKeysResult>(latch2, false) { - override fun onSuccess(data: ImportRoomKeysResult) { - importRoomKeysResult = data - super.onSuccess(data) - } - } - ) - mTestHelper.await(latch2) + mTestHelper.runBlockingTest { + testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, + "EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d", + null, + null, + null + ).let { data -> + importRoomKeysResult = data + } + } // onSuccess may not have been called assertNull(importRoomKeysResult) @@ -663,7 +657,7 @@ class KeysBackupTest : InstrumentedTest { // - Restore the e2e backup with the password val steps = ArrayList<StepProgressListener.Step>() - val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> { + val importRoomKeysResult = mTestHelper.runBlockingTest { testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, password, null, @@ -672,8 +666,7 @@ class KeysBackupTest : InstrumentedTest { override fun onStepProgress(step: StepProgressListener.Step) { steps.add(step) } - }, - it + } ) } @@ -719,20 +712,16 @@ class KeysBackupTest : InstrumentedTest { // - Try to restore the e2e backup with a wrong password val latch2 = CountDownLatch(1) var importRoomKeysResult: ImportRoomKeysResult? = null - testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, - wrongPassword, - null, - null, - null, - object : TestMatrixCallback<ImportRoomKeysResult>(latch2, false) { - override fun onSuccess(data: ImportRoomKeysResult) { - importRoomKeysResult = data - super.onSuccess(data) - } - } - ) - mTestHelper.await(latch2) - + mTestHelper.runBlockingTest { + testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, + wrongPassword, + null, + null, + null + ).let { + importRoomKeysResult = it + } + } // onSuccess may not have been called assertNull(importRoomKeysResult) @@ -752,13 +741,12 @@ class KeysBackupTest : InstrumentedTest { val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password) // - Restore the e2e backup with the recovery key. - val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> { + val importRoomKeysResult = mTestHelper.runBlockingTest { testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, null, null, - null, - it + null ) } @@ -780,18 +768,16 @@ class KeysBackupTest : InstrumentedTest { // - Try to restore the e2e backup with a password val latch2 = CountDownLatch(1) var importRoomKeysResult: ImportRoomKeysResult? = null - testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, - "password", - null, - null, - null, - object : TestMatrixCallback<ImportRoomKeysResult>(latch2, false) { - override fun onSuccess(data: ImportRoomKeysResult) { - importRoomKeysResult = data - super.onSuccess(data) - } - } - ) + mTestHelper.runBlockingTest { + testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, + "password", + null, + null, + null + ).let { + importRoomKeysResult = it + } + } mTestHelper.await(latch2) // onSuccess may not have been called @@ -817,13 +803,13 @@ class KeysBackupTest : InstrumentedTest { mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) // Get key backup version from the homeserver - val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> { - keysBackup.getCurrentVersion(it) + val keysVersionResult = mTestHelper.runBlockingTest { + keysBackup.getCurrentVersion() } // - Check the returned KeyBackupVersion is trusted - val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> { - keysBackup.getKeysBackupTrust(keysVersionResult!!, it) + val keysBackupVersionTrust = mTestHelper.runBlockingTest { + keysBackup.getKeysBackupTrust(keysVersionResult!!) } assertNotNull(keysBackupVersionTrust) @@ -908,64 +894,64 @@ class KeysBackupTest : InstrumentedTest { * - Make alice back up all her keys again * -> That must fail and her backup state must be WrongBackUpVersion */ - @Test - fun testBackupWhenAnotherBackupWasCreated() { - // - Create a backup version - val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() - - val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() - - val stateObserver = StateObserver(keysBackup) - - assertFalse(keysBackup.isEnabled) - - // Wait for keys backup to be finished - val latch0 = CountDownLatch(1) - var count = 0 - keysBackup.addListener(object : KeysBackupStateListener { - override fun onStateChange(newState: KeysBackupState) { - // Check the backup completes - if (newState == KeysBackupState.ReadyToBackUp) { - count++ - - if (count == 2) { - // Remove itself from the list of listeners - keysBackup.removeListener(this) - - latch0.countDown() - } - } - } - }) - - // - Make alice back up her keys to her homeserver - mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) - - assertTrue(keysBackup.isEnabled) - - mTestHelper.await(latch0) - - // - Create a new backup with fake data on the homeserver, directly using the rest client - val megolmBackupCreationInfo = mCryptoTestHelper.createFakeMegolmBackupCreationInfo() - mTestHelper.doSync<KeysVersion> { - (keysBackup as DefaultKeysBackupService).createFakeKeysBackupVersion(megolmBackupCreationInfo, it) - } - - // Reset the store backup status for keys - (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store.resetBackupMarkers() - - // - Make alice back up all her keys again - val latch2 = CountDownLatch(1) - keysBackup.backupAllGroupSessions(null, TestMatrixCallback(latch2, false)) - mTestHelper.await(latch2) - - // -> That must fail and her backup state must be WrongBackUpVersion - assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.state) - assertFalse(keysBackup.isEnabled) - - stateObserver.stopAndCheckStates(null) - cryptoTestData.cleanUp(mTestHelper) - } +// @Test +// fun testBackupWhenAnotherBackupWasCreated() { +// // - Create a backup version +// val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() +// +// val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() +// +// val stateObserver = StateObserver(keysBackup) +// +// assertFalse(keysBackup.isEnabled) +// +// // Wait for keys backup to be finished +// val latch0 = CountDownLatch(1) +// var count = 0 +// keysBackup.addListener(object : KeysBackupStateListener { +// override fun onStateChange(newState: KeysBackupState) { +// // Check the backup completes +// if (newState == KeysBackupState.ReadyToBackUp) { +// count++ +// +// if (count == 2) { +// // Remove itself from the list of listeners +// keysBackup.removeListener(this) +// +// latch0.countDown() +// } +// } +// } +// }) +// +// // - Make alice back up her keys to her homeserver +// mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) +// +// assertTrue(keysBackup.isEnabled) +// +// mTestHelper.await(latch0) +// +// // - Create a new backup with fake data on the homeserver, directly using the rest client +// val megolmBackupCreationInfo = mCryptoTestHelper.createFakeMegolmBackupCreationInfo() +// mTestHelper.doSync<KeysVersion> { +// (keysBackup as DefaultKeysBackupService).createFakeKeysBackupVersion(megolmBackupCreationInfo, it) +// } +// +// // Reset the store backup status for keys +// (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store.resetBackupMarkers() +// +// // - Make alice back up all her keys again +// val latch2 = CountDownLatch(1) +// keysBackup.backupAllGroupSessions(null, TestMatrixCallback(latch2, false)) +// mTestHelper.await(latch2) +// +// // -> That must fail and her backup state must be WrongBackUpVersion +// assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.state) +// assertFalse(keysBackup.isEnabled) +// +// stateObserver.stopAndCheckStates(null) +// cryptoTestData.cleanUp(mTestHelper) +// } /** * - Do an e2e backup to the homeserver @@ -992,9 +978,18 @@ class KeysBackupTest : InstrumentedTest { mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) // Wait for keys backup to finish by asking again to backup keys. - mTestHelper.doSync<Unit> { - keysBackup.backupAllGroupSessions(null, it) + mTestHelper.runBlockingTest { + keysBackup.checkAndStartKeysBackup() + delay(1000) } + mTestHelper.waitWithLatch { + mTestHelper.retryPeriodicallyWithLatch(it) { + keysBackup.state == KeysBackupState.ReadyToBackUp + } + } +// mTestHelper.doSync<Unit> { +// keysBackup.backupAllGroupSessions(null, it) +// } val oldDeviceId = cryptoTestData.firstSession.sessionParams.deviceId!! val oldKeyBackupVersion = keysBackup.currentBackupVersion @@ -1016,16 +1011,26 @@ class KeysBackupTest : InstrumentedTest { val stateObserver2 = StateObserver(keysBackup2) var isSuccessful = false - val latch2 = CountDownLatch(1) - keysBackup2.backupAllGroupSessions( - null, - object : TestMatrixCallback<Unit>(latch2, false) { - override fun onSuccess(data: Unit) { - isSuccessful = true - super.onSuccess(data) - } - }) - mTestHelper.await(latch2) +// val latch2 = CountDownLatch(1) + mTestHelper.runBlockingTest { + keysBackup2.checkAndStartKeysBackup() + delay(1000) + } + mTestHelper.waitWithLatch { + mTestHelper.retryPeriodicallyWithLatch(it) { + keysBackup2.state == KeysBackupState.ReadyToBackUp + } + } + +// keysBackup2.backupAllGroupSessions( +// null, +// object : TestMatrixCallback<Unit>(latch2, false) { +// override fun onSuccess(data: Unit) { +// isSuccessful = true +// super.onSuccess(data) +// } +// }) +// mTestHelper.await(latch2) assertFalse(isSuccessful) @@ -1054,8 +1059,17 @@ class KeysBackupTest : InstrumentedTest { // -> It must use the same backup version assertEquals(oldKeyBackupVersion, aliceSession2.cryptoService().keysBackupService().currentBackupVersion) - mTestHelper.doSync<Unit> { - aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, it) +// mTestHelper.doSync<Unit> { +// aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, it) +// } + mTestHelper.runBlockingTest { + aliceSession2.cryptoService().keysBackupService().checkAndStartKeysBackup() + delay(1000) + } + mTestHelper.waitWithLatch { + mTestHelper.retryPeriodicallyWithLatch(it) { + aliceSession2.cryptoService().keysBackupService().state == KeysBackupState.ReadyToBackUp + } } // -> It must success @@ -1087,7 +1101,7 @@ class KeysBackupTest : InstrumentedTest { assertTrue(keysBackup.isEnabled) // Delete the backup - mTestHelper.doSync<Unit> { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) } + mTestHelper.runBlockingTest { keysBackup.deleteBackup(keyBackupCreationInfo.version) } // Backup is now disabled assertFalse(keysBackup.isEnabled) 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 a625ffc0e9..d2742bbec3 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 @@ -16,8 +16,8 @@ package org.matrix.android.sdk.internal.crypto.keysbackup +import kotlinx.coroutines.delay 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.KeysBackupService import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState @@ -27,8 +27,6 @@ import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.assertDictEquals import org.matrix.android.sdk.common.assertListEquals import org.matrix.android.sdk.internal.crypto.MegolmSessionData -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion import java.util.concurrent.CountDownLatch class KeysBackupTestHelper( @@ -45,29 +43,38 @@ class KeysBackupTestHelper( fun createKeysBackupScenarioWithPassword(password: String?): KeysBackupScenarioData { val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() - val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store +// val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() val stateObserver = StateObserver(keysBackup) - val aliceKeys = cryptoStore.inboundGroupSessionsToBackup(100) +// val aliceKeys = cryptoStore.inboundGroupSessionsToBackup(100) // - Do an e2e backup to the homeserver val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password) - var lastProgress = 0 - var lastTotal = 0 - mTestHelper.doSync<Unit> { - keysBackup.backupAllGroupSessions(object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - lastProgress = progress - lastTotal = total - } - }, it) +// var lastProgress = 0 +// var lastTotal = 0 +// mTestHelper.doSync<Unit> { +// keysBackup.backupAllGroupSessions(object : ProgressListener { +// override fun onProgress(progress: Int, total: Int) { +// lastProgress = progress +// lastTotal = total +// } +// }, it) +// } + + mTestHelper.runBlockingTest { + keysBackup.checkAndStartKeysBackup() + delay(1000) + } + mTestHelper.waitWithLatch { + mTestHelper.retryPeriodicallyWithLatch(it) { + keysBackup.state == KeysBackupState.ReadyToBackUp + } } - Assert.assertEquals(2, lastProgress) - Assert.assertEquals(2, lastTotal) + Assert.assertEquals(2, cryptoTestData.firstSession.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys()) val aliceUserId = cryptoTestData.firstSession.myUserId @@ -83,7 +90,7 @@ class KeysBackupTestHelper( stateObserver.stopAndCheckStates(null) return KeysBackupScenarioData(cryptoTestData, - aliceKeys, + aliceSession2.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys(), prepareKeysBackupDataResult, aliceSession2) } @@ -92,8 +99,8 @@ class KeysBackupTestHelper( password: String? = null): PrepareKeysBackupDataResult { val stateObserver = StateObserver(keysBackup) - val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> { - keysBackup.prepareKeysBackupVersion(password, null, it) + val megolmBackupCreationInfo = mTestHelper.runBlockingTest { + keysBackup.prepareKeysBackupVersion(password) } Assert.assertNotNull(megolmBackupCreationInfo) @@ -101,8 +108,8 @@ class KeysBackupTestHelper( Assert.assertFalse(keysBackup.isEnabled) // Create the version - val keysVersion = mTestHelper.doSync<KeysVersion> { - keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it) + val keysVersion = mTestHelper.runBlockingTest { + keysBackup.createKeysBackupVersion(megolmBackupCreationInfo) } Assert.assertNotNull(keysVersion.version) @@ -165,18 +172,19 @@ class KeysBackupTestHelper( total: Int, imported: Int) { // - Imported keys number must be correct - Assert.assertEquals(testData.aliceKeys.size, total) + Assert.assertEquals(testData.aliceKeys, total) Assert.assertEquals(total, imported) // - The new device must have the same count of megolm keys - Assert.assertEquals(testData.aliceKeys.size, testData.aliceSession2.cryptoService().inboundGroupSessionsCount(false)) + Assert.assertEquals(testData.aliceKeys, testData.aliceSession2.cryptoService().inboundGroupSessionsCount(false)) // - Alice must have the same keys on both devices - for (aliceKey1 in testData.aliceKeys) { - val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store - .getInboundGroupSession(aliceKey1.olmInboundGroupSession!!.sessionIdentifier(), aliceKey1.senderKey!!) - Assert.assertNotNull(aliceKey2) - assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys()) - } +// +// for (aliceKey1 in testData.aliceKeys) { +// val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store +// .getInboundGroupSession(aliceKey1.olmInboundGroupSession!!.sessionIdentifier(), aliceKey1.senderKey!!) +// Assert.assertNotNull(aliceKey2) +// assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys()) +// } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/listeners/StepProgressListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/listeners/StepProgressListener.kt index 0fabfed2ff..423885299f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/listeners/StepProgressListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/listeners/StepProgressListener.kt @@ -24,6 +24,7 @@ interface StepProgressListener { sealed class Step { data class ComputingKey(val progress: Int, val total: Int) : Step() object DownloadingKey : Step() + data class DecryptingKey(val progress: Int, val total: Int) : Step() data class ImportingKey(val progress: Int, val total: Int) : Step() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt index 7c3a0101c8..1d97488ebc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt @@ -61,7 +61,7 @@ interface CrossSigningService { * This will check if the injected private cross signing keys match the public ones provided * by the server and if they do so */ - fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?, + suspend fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?, uskKeyPrivateKey: String?, sskPrivateKey: String?): UserTrustResult @@ -102,8 +102,8 @@ interface CrossSigningService { /** * Sign one of your devices and upload the signature */ - fun trustDevice(deviceId: String, - callback: MatrixCallback<Unit>) + @Throws + suspend fun trustDevice(deviceId: String) suspend fun shieldForGroup(userIds: List<String>): RoomEncryptionTrustLevel 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 4464427b90..6bb4dbc620 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 @@ -16,7 +16,6 @@ package org.matrix.android.sdk.api.session.crypto.keysbackup -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.listeners.StepProgressListener import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust @@ -31,18 +30,17 @@ interface KeysBackupService { * Retrieve the current version of the backup from the homeserver * * It can be different than keysBackupVersion. - * @param callback onSuccess(null) will be called if there is no backup on the server */ - fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) + suspend fun getCurrentVersion(): KeysVersionResult? /** * Create a new keys backup version and enable it, using the information return from [prepareKeysBackupVersion]. * * @param keysBackupCreationInfo the info object from [prepareKeysBackupVersion]. - * @param callback Asynchronous callback + * @return KeysVersion */ - fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, - callback: MatrixCallback<KeysVersion>) + @Throws + suspend fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo): KeysVersion /** * Facility method to get the total number of locally stored keys @@ -54,23 +52,21 @@ interface KeysBackupService { */ fun getTotalNumbersOfBackedUpKeys(): Int - /** - * Start to back up keys immediately. - * - * @param progressListener the callback to follow the progress - * @param callback the main callback - */ - fun backupAllGroupSessions(progressListener: ProgressListener?, - callback: MatrixCallback<Unit>?) +// /** +// * Start to back up keys immediately. +// * +// * @param progressListener the callback to follow the progress +// * @param callback the main callback +// */ +// fun backupAllGroupSessions(progressListener: ProgressListener?, +// callback: MatrixCallback<Unit>?) /** * Check trust on a key backup version. * * @param keysBackupVersion the backup version to check. - * @param callback block called when the operations completes. */ - fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult, - callback: MatrixCallback<KeysBackupVersionTrust>) + suspend fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult): KeysBackupVersionTrust /** * Return the current progress of the backup @@ -82,18 +78,16 @@ interface KeysBackupService { * * It can be different than keysBackupVersion. * @param version the backup version - * @param callback */ - fun getVersion(version: String, - callback: MatrixCallback<KeysVersionResult?>) + suspend fun getVersion(version: String): KeysVersionResult? /** * This method fetches the last backup version on the server, then compare to the currently backup version use. * If versions are not the same, the current backup is deleted (on server or locally), then the backup may be started again, using the last version. * - * @param callback true if backup is already using the last version, and false if it is not the case + * @return true if backup is already using the last version, and false if it is not the case */ - fun forceUsingLastVersion(callback: MatrixCallback<Boolean>) + suspend fun forceUsingLastVersion(): Boolean /** * Check the server for an active key backup. @@ -101,7 +95,7 @@ interface KeysBackupService { * If one is present and has a valid signature from one of the user's verified * devices, start backing up to it. */ - fun checkAndStartKeysBackup() + suspend fun checkAndStartKeysBackup() fun addListener(listener: KeysBackupStateListener) @@ -119,19 +113,16 @@ interface KeysBackupService { * @param progressListener a progress listener, as generating private key from password may take a while * @param callback Asynchronous callback */ - fun prepareKeysBackupVersion(password: String?, - progressListener: ProgressListener?, - callback: MatrixCallback<MegolmBackupCreationInfo>) + suspend fun prepareKeysBackupVersion(password: String?): MegolmBackupCreationInfo /** * Delete a keys backup version. It will delete all backed up keys on the server, and the backup itself. * If we are backing up to this version. Backup will be stopped. * * @param version the backup version to delete. - * @param callback Asynchronous callback */ - fun deleteBackup(version: String, - callback: MatrixCallback<Unit>?) + @Throws + suspend fun deleteBackup(version: String) /** * Ask if the backup on the server contains keys that we may do not have locally. @@ -145,35 +136,29 @@ interface KeysBackupService { * * @param keysBackupVersion the backup version to check. * @param trust the trust to set to the keys backup. - * @param callback block called when the operations completes. */ - fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, - trust: Boolean, - callback: MatrixCallback<Unit>) + @Throws + suspend fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, trust: Boolean) /** * Set trust on a keys backup version. * * @param keysBackupVersion the backup version to check. * @param recoveryKey the recovery key to challenge with the key backup public key. - * @param callback block called when the operations completes. */ - fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, - recoveryKey: String, - callback: MatrixCallback<Unit>) + suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, + recoveryKey: String) /** * Set trust on a keys backup version. * * @param keysBackupVersion the backup version to check. * @param password the pass phrase to challenge with the keyBackupVersion public key. - * @param callback block called when the operations completes. */ - fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, - password: String, - callback: MatrixCallback<Unit>) + suspend fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, + password: String) - fun onSecretKeyGossip(secret: String) + suspend fun onSecretKeyGossip(secret: String) /** * Restore a backup with a recovery key from a given backup version stored on the homeserver. @@ -185,11 +170,10 @@ interface KeysBackupService { * @param stepProgressListener the step progress listener * @param callback Callback. It provides the number of found keys and the number of successfully imported keys. */ - fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, + suspend fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, recoveryKey: String, roomId: String?, sessionId: String?, - stepProgressListener: StepProgressListener?, - callback: MatrixCallback<ImportRoomKeysResult>) + stepProgressListener: StepProgressListener?): ImportRoomKeysResult /** * Restore a backup with a password from a given backup version stored on the homeserver. @@ -201,12 +185,11 @@ interface KeysBackupService { * @param stepProgressListener the step progress listener * @param callback Callback. It provides the number of found keys and the number of successfully imported keys. */ - fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, + suspend fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, password: String, roomId: String?, sessionId: String?, - stepProgressListener: StepProgressListener?, - callback: MatrixCallback<ImportRoomKeysResult>) + stepProgressListener: StepProgressListener?): ImportRoomKeysResult val keysBackupVersion: KeysVersionResult? val currentBackupVersion: String? @@ -218,5 +201,5 @@ interface KeysBackupService { fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? - fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback<Boolean>) + suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 3c24df1e0f..d970517c9a 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -90,6 +90,7 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject import kotlin.math.max +import kotlin.system.measureTimeMillis /** * A `CryptoService` class instance manages the end-to-end crypto for a session. @@ -134,6 +135,7 @@ internal class DefaultCryptoService @Inject constructor( private val crossSigningService: CrossSigningService, private val verificationService: RustVerificationService, private val keysBackupService: RustKeyBackupService, + private val megolmSessionImportManager: MegolmSessionImportManager, private val olmMachineProvider: OlmMachineProvider ) : CryptoService { @@ -152,9 +154,6 @@ internal class DefaultCryptoService @Inject constructor( private val outgoingRequestsLock: Mutex = Mutex() private val roomKeyShareLocks: ConcurrentHashMap<String, Mutex> = ConcurrentHashMap() - // TODO does this need to be concurrent? - private val newSessionListeners = ArrayList<NewSessionListener>() - fun onStateEvent(roomId: String, event: Event) { when (event.getClearType()) { EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) @@ -256,7 +255,12 @@ internal class DefaultCryptoService @Inject constructor( } override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int { - return cryptoStore.inboundGroupSessionsCount(onlyBackedUp) + return if (onlyBackedUp) { + keysBackupService.getTotalNumbersOfBackedUpKeys() + } else { + keysBackupService.getTotalNumbersOfKeys() + } + // return cryptoStore.inboundGroupSessionsCount(onlyBackedUp) } /** @@ -331,8 +335,10 @@ internal class DefaultCryptoService @Inject constructor( // We try to enable key backups, if the backup version on the server is trusted, // we're gonna continue backing up. - tryOrNull { - keysBackupService.checkAndStartKeysBackup() + cryptoCoroutineScope.launch { + tryOrNull { + keysBackupService.checkAndStartKeysBackup() + } } // Open the store @@ -537,7 +543,11 @@ internal class DefaultCryptoService @Inject constructor( val t0 = System.currentTimeMillis() Timber.tag(loggerTag.value).v("encryptEventContent() starts") runCatching { - preshareRoomKey(roomId, userIds) + measureTimeMillis { + preshareRoomKey(roomId, userIds) + }.also { + Timber.d("Shared room key in room $roomId took $it ms") + } val content = encrypt(roomId, eventType, eventContent) Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms") MXEncryptEventContentResult(content, EventType.ENCRYPTED) @@ -668,17 +678,7 @@ internal class DefaultCryptoService @Inject constructor( roomId: String, sessionId: String, ) { - // The sender key is actually unused since it's unimportant for megolm - // Our events don't contain the info so pass an empty string until we - // change the listener definition - val senderKey = "" - - newSessionListeners.forEach { - try { - it.onNewSession(roomId, senderKey, sessionId) - } catch (e: Throwable) { - } - } + megolmSessionImportManager.dispatchNewSession(roomId, sessionId) } suspend fun receiveSyncChanges( @@ -879,7 +879,9 @@ internal class DefaultCryptoService @Inject constructor( override suspend fun importRoomKeys(roomKeysAsArray: ByteArray, password: String, progressListener: ProgressListener?): ImportRoomKeysResult { - val result = olmMachine.importKeys(roomKeysAsArray, password, progressListener) + val result = olmMachine.importKeys(roomKeysAsArray, password, progressListener).also { + megolmSessionImportManager.dispatchKeyImportResults(it) + } keysBackupService.maybeBackupKeys() return result @@ -1023,11 +1025,11 @@ internal class DefaultCryptoService @Inject constructor( } override fun addNewSessionListener(newSessionListener: NewSessionListener) { - if (!newSessionListeners.contains(newSessionListener)) newSessionListeners.add(newSessionListener) + megolmSessionImportManager.addListener(newSessionListener) } override fun removeSessionListener(listener: NewSessionListener) { - newSessionListeners.remove(listener) + megolmSessionImportManager.removeListener(listener) } /* ========================================================================================== * DEBUG INFO diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt index 220f25ec80..cf7a373258 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt @@ -44,6 +44,7 @@ import timber.log.Timber import java.util.concurrent.Executors import javax.inject.Inject +@Deprecated("rust") @SessionScope internal class IncomingGossipingRequestManager @Inject constructor( @SessionId private val sessionId: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionImportManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionImportManager.kt new file mode 100644 index 0000000000..93f0c12caf --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionImportManager.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2021 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 + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult +import org.matrix.android.sdk.internal.session.SessionScope +import javax.inject.Inject + +/** + * Helper that allows listeners to be notified when a new megolm session + * has been added to the crypto layer (could be via room keys or forward keys via sync + * or after importing keys from key backup or manual import). + * Can be used to refresh display when the keys are received after the message + */ +@SessionScope +internal class MegolmSessionImportManager @Inject constructor( + private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val cryptoCoroutineScope: CoroutineScope +) { + + private val newSessionsListeners = mutableListOf<NewSessionListener>() + + fun addListener(listener: NewSessionListener) { + synchronized(newSessionsListeners) { + if (!newSessionsListeners.contains(listener)) { + newSessionsListeners.add(listener) + } + } + } + + fun removeListener(listener: NewSessionListener) { + synchronized(newSessionsListeners) { + newSessionsListeners.remove(listener) + } + } + + fun dispatchNewSession(roomId: String?, sessionId: String) { + val copy = synchronized(newSessionsListeners) { + newSessionsListeners.toList() + } + cryptoCoroutineScope.launch(coroutineDispatchers.computation) { + copy.forEach { + tryOrNull("Failed to dispatch new session import") { + it.onNewSession(roomId, sessionId) + } + } + } + } + + fun dispatchKeyImportResults(result: ImportRoomKeysResult) { + result.importedSessionInfo.forEach { (roomId, senderToSessionIdMap) -> + senderToSessionIdMap.values.forEach { sessionList -> + sessionList.forEach { sessionId -> + dispatchNewSession(roomId, sessionId) + } + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/NewSessionListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/NewSessionListener.kt index 301729680c..19f89b2f1e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/NewSessionListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/NewSessionListener.kt @@ -16,5 +16,5 @@ package org.matrix.android.sdk.internal.crypto interface NewSessionListener { - fun onNewSession(roomId: String?, senderKey: String, sessionId: String) + fun onNewSession(roomId: String?, sessionId: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt index e10778bfbd..16ca58a8fa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt @@ -75,13 +75,9 @@ class CryptoLogger : Logger { } } -private class CryptoProgressListener(listener: ProgressListener?) : RustProgressListener { - private val inner: ProgressListener? = listener - +private class CryptoProgressListener(private val listener: ProgressListener?) : RustProgressListener { override fun onProgress(progress: Int, total: Int) { - if (this.inner != null) { - this.inner.onProgress(progress, total) - } + listener?.onProgress(progress, total) } } @@ -514,8 +510,7 @@ internal class OlmMachine( val result = inner.importKeys(decodedKeys, passphrase, rustListener) - // TODO do we want to remove the cast here? - ImportRoomKeysResult(result.total.toInt(), result.imported.toInt()) + ImportRoomKeysResult.fromOlm(result) } @Throws(CryptoStoreException::class) @@ -525,13 +520,30 @@ internal class OlmMachine( ): ImportRoomKeysResult = withContext(Dispatchers.IO) { val adapter = MoshiProvider.providesMoshi().adapter(List::class.java) - val encodedKeys = adapter.toJson(keys) - val rustListener = CryptoProgressListener(listener) + // If the key backup is too big we take the risk of causing OOM + // when serializing to json + // so let's chunk to avoid it + var totalImported = 0L + var accTotal = 0L + val details = mutableMapOf<String, Map<String, List<String>>>() + keys.chunked(500) + .forEach { keysSlice -> + val encodedKeys = adapter.toJson(keysSlice) + val rustListener = object : RustProgressListener { + override fun onProgress(progress: Int, total: Int) { + val accProgress = (accTotal + progress).toInt() + listener?.onProgress(accProgress, keys.size) + } + } - val result = inner.importDecryptedKeys(encodedKeys, rustListener) - - ImportRoomKeysResult(result.total.toInt(), result.imported.toInt()) + inner.importDecryptedKeys(encodedKeys, rustListener).let { + totalImported += it.imported + accTotal += it.total + details.putAll(it.keys) + } + } + ImportRoomKeysResult(totalImported.toInt(), accTotal.toInt(), details) } @Throws(CryptoStoreException::class) @@ -554,8 +566,14 @@ internal class OlmMachine( UserIdentity(identity.userId, masterKey, selfSigningKey, this, this.requestSender) } is RustUserIdentity.Own -> { - val masterKey = adapter.fromJson(identity.masterKey)!!.toCryptoModel() - val selfSigningKey = adapter.fromJson(identity.selfSigningKey)!!.toCryptoModel() + val verified = this.inner().isIdentityVerified(userId) + + val masterKey = adapter.fromJson(identity.masterKey)!!.toCryptoModel().apply { + trustLevel = DeviceTrustLevel(verified, verified) + } + val selfSigningKey = adapter.fromJson(identity.selfSigningKey)!!.toCryptoModel().apply { + trustLevel = DeviceTrustLevel(verified, verified) + } val userSigningKey = adapter.fromJson(identity.userSigningKey)!!.toCryptoModel() OwnUserIdentity( @@ -823,13 +841,28 @@ internal class OlmMachine( suspend fun importCrossSigningKeys(export: PrivateKeysInfo): UserTrustResult { val rustExport = CrossSigningKeyExport(export.master, export.selfSigned, export.user) + var result: UserTrustResult withContext(Dispatchers.IO) { - inner.importCrossSigningKeys(rustExport) - } + result = try { + inner.importCrossSigningKeys(rustExport) - this.updateLivePrivateKeys() - // TODO map the errors from importCrossSigningKeys to the UserTrustResult - return UserTrustResult.Success + // Sign the cross signing keys with our device + // Fail silently if signature upload fails?? + try { + getIdentity(userId())?.verify() + } catch (failure: Throwable) { + Timber.e(failure, "Failed to sign x-keys with own device") + } + UserTrustResult.Success + } catch (failure: Exception) { + // KeyImportError? + UserTrustResult.Failure(failure.localizedMessage) + } + } + withContext(Dispatchers.Main) { + this@OlmMachine.updateLivePrivateKeys() + } + return result } suspend fun sign(message: String): Map<String, Map<String, String>> { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RequestSender.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RequestSender.kt index 6ce0fea71e..45644d951f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RequestSender.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RequestSender.kt @@ -213,13 +213,14 @@ internal class RequestSender @Inject constructor( suspend fun getKeyBackupVersion(version: String? = null): KeysVersionResult? { return try { if (version != null) { - getKeysBackupVersionTask.execute(version) + getKeysBackupVersionTask.executeRetry(version, 3) } else { - getKeysBackupLastVersionTask.execute(Unit) + getKeysBackupLastVersionTask.executeRetry(Unit, 3) } } catch (failure: Throwable) { if (failure is Failure.ServerError && failure.error.code == MatrixError.M_NOT_FOUND) { + // Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup null } else { throw failure diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt index 89fb43ef2e..dd81eae99a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt @@ -72,11 +72,11 @@ internal class RoomDecryptorProvider @Inject constructor( val alg = when (algorithm) { MXCRYPTO_ALGORITHM_MEGOLM -> megolmDecryptionFactory.create().apply { this.newSessionListener = object : NewSessionListener { - override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { + override fun onNewSession(roomId: String?, sessionId: String) { // PR reviewer: the parameter has been renamed so is now in conflict with the parameter of getOrCreateRoomDecryptor newSessionListeners.forEach { try { - it.onNewSession(roomId, senderKey, sessionId) + it.onNewSession(roomId, sessionId) } catch (e: Throwable) { } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RustCrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RustCrossSigningService.kt index 4c9d62f610..07918870d3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RustCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RustCrossSigningService.kt @@ -73,7 +73,7 @@ internal class RustCrossSigningService @Inject constructor( if (verified) { UserTrustResult.Success } else { - UserTrustResult.UnknownCrossSignatureInfo(otherUserId) + UserTrustResult.Failure("failed to verify $otherUserId") } } else { UserTrustResult.CrossSigningNotConfigured(otherUserId) @@ -94,15 +94,13 @@ internal class RustCrossSigningService @Inject constructor( * This will check if the injected private cross signing keys match the public ones provided * by the server and if they do so */ - override fun checkTrustFromPrivateKeys( + override suspend fun checkTrustFromPrivateKeys( masterKeyPrivateKey: String?, uskKeyPrivateKey: String?, sskPrivateKey: String? ): UserTrustResult { val export = PrivateKeysInfo(masterKeyPrivateKey, sskPrivateKey, uskKeyPrivateKey) - return runBlocking { - olmMachineProvider.olmMachine.importCrossSigningKeys(export) - } + return olmMachine.importCrossSigningKeys(export) } /** @@ -171,19 +169,19 @@ internal class RustCrossSigningService @Inject constructor( /** * Sign one of your devices and upload the signature */ - override fun trustDevice(deviceId: String, callback: MatrixCallback<Unit>) { - val device = runBlocking { olmMachine.getDevice(olmMachine.userId(), deviceId) } + override suspend fun trustDevice(deviceId: String) { + val device = olmMachine.getDevice(olmMachine.userId(), deviceId) return if (device != null) { - val verified = runBlocking { device.verify() } + val verified = device.verify() if (verified) { - callback.onSuccess(Unit) + return } else { - callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours")) + throw IllegalArgumentException("This device [$deviceId] is not known, or not yours") } } else { - callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known")) + throw IllegalArgumentException("This device [$deviceId] is not known") } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt index e0748a0d1f..6c8c1c4637 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt @@ -103,6 +103,6 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi Timber.v("## importMegolmSessionsData : sessions import " + (t1 - t0) + " ms (" + megolmSessionsData.size + " sessions)") - return ImportRoomKeysResult(totalNumbersOfKeys, totalNumbersOfImportedKeys) + return ImportRoomKeysResult(totalNumbersOfKeys, totalNumbersOfImportedKeys, emptyMap()) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt index b9cfd942ce..54ccbf016f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt @@ -21,7 +21,6 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest import org.matrix.android.sdk.internal.crypto.IncomingSecretShareRequest import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult -import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService /** * An interface for decrypting data @@ -44,7 +43,7 @@ internal interface IMXDecrypting { * * @param event the key event. */ - fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {} + fun onRoomKeyEvent(event: Event/*, defaultKeysBackupService: DefaultKeysBackupService*/) {} /** * Check if the some messages can be decrypted with a new session diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index 8bbc71543c..20658f7db4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -34,7 +34,6 @@ import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevice import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting import org.matrix.android.sdk.internal.crypto.algorithms.IMXWithHeldExtension -import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent @@ -232,7 +231,7 @@ internal class MXMegolmDecryption(private val userId: String, * * @param event the key event. */ - override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) { + override fun onRoomKeyEvent(event: Event/*, defaultKeysBackupService: DefaultKeysBackupService*/) { Timber.tag(loggerTag.value).v("onRoomKeyEvent()") var exportFormat = false val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return @@ -295,7 +294,7 @@ internal class MXMegolmDecryption(private val userId: String, exportFormat) if (added) { - defaultKeysBackupService.maybeBackupKeys() + // defaultKeysBackupService.maybeBackupKeys() val content = RoomKeyRequestBody( algorithm = roomKeyContent.algorithm, @@ -318,7 +317,7 @@ internal class MXMegolmDecryption(private val userId: String, */ override fun onNewSession(senderKey: String, sessionId: String) { Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey") - newSessionListener?.onNewSession(null, senderKey, sessionId) + newSessionListener?.onNewSession(null, sessionId) } override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index 44b59b208a..bc479f0537 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -31,7 +31,7 @@ import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevice import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting import org.matrix.android.sdk.internal.crypto.algorithms.IMXGroupEncryption -import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService +// import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent @@ -53,7 +53,7 @@ internal class MXMegolmEncryption( // The id of the room we will be sending to. private val roomId: String, private val olmDevice: MXOlmDevice, - private val defaultKeysBackupService: DefaultKeysBackupService, +// private val defaultKeysBackupService: DefaultKeysBackupService, private val cryptoStore: IMXCryptoStore, private val deviceListManager: DeviceListManager, private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, @@ -149,7 +149,7 @@ internal class MXMegolmEncryption( olmDevice.addInboundGroupSession(sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!, emptyList(), keysClaimedMap, false) - defaultKeysBackupService.maybeBackupKeys() +// defaultKeysBackupService.maybeBackupKeys() return MXOutboundSessionInfo(sessionId, SharedWithHelper(roomId, sessionId, cryptoStore)) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt index 136fdc05f5..3797f3a588 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt @@ -22,7 +22,6 @@ import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter -import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask @@ -32,7 +31,7 @@ import javax.inject.Inject internal class MXMegolmEncryptionFactory @Inject constructor( private val olmDevice: MXOlmDevice, - private val defaultKeysBackupService: DefaultKeysBackupService, +// private val defaultKeysBackupService: DefaultKeysBackupService, private val cryptoStore: IMXCryptoStore, private val deviceListManager: DeviceListManager, private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, @@ -48,7 +47,7 @@ internal class MXMegolmEncryptionFactory @Inject constructor( return MXMegolmEncryption( roomId = roomId, olmDevice = olmDevice, - defaultKeysBackupService = defaultKeysBackupService, +// defaultKeysBackupService = defaultKeysBackupService, cryptoStore = cryptoStore, deviceListManager = deviceListManager, ensureOlmSessionsForDevicesAction = ensureOlmSessionsForDevicesAction, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UserTrustResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UserTrustResult.kt index 20e7ca09ab..e86c9c377a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UserTrustResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UserTrustResult.kt @@ -16,9 +16,6 @@ package org.matrix.android.sdk.internal.crypto.crosssigning -import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo -import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey - sealed class UserTrustResult { object Success : UserTrustResult() @@ -26,10 +23,11 @@ sealed class UserTrustResult { // data class UnknownDevice(val deviceID: String) : UserTrustResult() data class CrossSigningNotConfigured(val userID: String) : UserTrustResult() - data class UnknownCrossSignatureInfo(val userID: String) : UserTrustResult() - data class KeysNotTrusted(val key: MXCrossSigningInfo) : UserTrustResult() - data class KeyNotSigned(val key: CryptoCrossSigningKey) : UserTrustResult() - data class InvalidSignature(val key: CryptoCrossSigningKey, val signature: String) : UserTrustResult() + data class Failure(val message: String) : UserTrustResult() +// data class UnknownCrossSignatureInfo(val userID: String) : UserTrustResult() +// data class KeysNotTrusted(val key: MXCrossSigningInfo) : UserTrustResult() +// data class KeyNotSigned(val key: CryptoCrossSigningKey) : UserTrustResult() +// data class InvalidSignature(val key: CryptoCrossSigningKey, val signature: String) : UserTrustResult() } fun UserTrustResult.isVerified() = this is UserTrustResult.Success 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 deleted file mode 100644 index b0c7c8b610..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ /dev/null @@ -1,1438 +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 - -import android.os.Handler -import android.os.Looper -import androidx.annotation.UiThread -import androidx.annotation.VisibleForTesting -import androidx.annotation.WorkerThread -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -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.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.KeysBackupService -import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState -import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP -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.fromBase64 -import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust -import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrustSignature -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo -import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmBackupAuthData -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 -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.RoomKeysBackupData -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteBackupTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteRoomSessionDataTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteRoomSessionsDataTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteSessionsDataTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupLastVersionTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionDataTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionsDataTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetSessionsDataTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionDataTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask -import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask -import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey -import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey -import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo -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 -import org.matrix.android.sdk.internal.task.Task -import org.matrix.android.sdk.internal.task.TaskExecutor -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.android.sdk.internal.util.awaitCallback -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 - -/** - * A DefaultKeysBackupService class instance manage incremental backup of e2e keys (megolm keys) - * to the user's homeserver. - */ -@SessionScope -@Deprecated("use rust") -internal class DefaultKeysBackupService @Inject constructor( - @UserId private val userId: String, - private val credentials: Credentials, - private val cryptoStore: IMXCryptoStore, - private val olmDevice: MXOlmDevice, - private val objectSigner: ObjectSigner, - // Actions - private val megolmSessionDataImporter: MegolmSessionDataImporter, - // Tasks - private val createKeysBackupVersionTask: CreateKeysBackupVersionTask, - private val deleteBackupTask: DeleteBackupTask, - private val deleteRoomSessionDataTask: DeleteRoomSessionDataTask, - private val deleteRoomSessionsDataTask: DeleteRoomSessionsDataTask, - private val deleteSessionDataTask: DeleteSessionsDataTask, - private val getKeysBackupLastVersionTask: GetKeysBackupLastVersionTask, - private val getKeysBackupVersionTask: GetKeysBackupVersionTask, - private val getRoomSessionDataTask: GetRoomSessionDataTask, - private val getRoomSessionsDataTask: GetRoomSessionsDataTask, - private val getSessionsDataTask: GetSessionsDataTask, - private val storeRoomSessionDataTask: StoreRoomSessionDataTask, - private val storeSessionsDataTask: StoreRoomSessionsDataTask, - private val storeSessionDataTask: StoreSessionsDataTask, - private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask, - // Task executor - private val taskExecutor: TaskExecutor, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val cryptoCoroutineScope: CoroutineScope -) : 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 backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null - - private var keysBackupStateListener: KeysBackupStateListener? = null - - override val isEnabled: Boolean - get() = keysBackupStateManager.isEnabled - - override val isStucked: Boolean - get() = keysBackupStateManager.isStucked - - override val state: KeysBackupState - get() = keysBackupStateManager.state - - override val currentBackupVersion: String? - get() = keysBackupVersion?.version - - override fun addListener(listener: KeysBackupStateListener) { - keysBackupStateManager.addListener(listener) - } - - override fun removeListener(listener: KeysBackupStateListener) { - keysBackupStateManager.removeListener(listener) - } - - override fun prepareKeysBackupVersion(password: String?, - progressListener: ProgressListener?, - callback: MatrixCallback<MegolmBackupCreationInfo>) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - withContext(coroutineDispatchers.crypto) { - 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 signedMegolmBackupAuthData = MegolmBackupAuthData( - publicKey = signalableMegolmBackupAuthData.publicKey, - privateKeySalt = signalableMegolmBackupAuthData.privateKeySalt, - privateKeyIterations = signalableMegolmBackupAuthData.privateKeyIterations, - signatures = objectSigner.signObject(canonicalJson) - ) - - MegolmBackupCreationInfo( - algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, - authData = signedMegolmBackupAuthData, - recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey()) - ) - } - }.foldToCallback(callback) - } - } - - override fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, - callback: MatrixCallback<KeysVersion>) { - @Suppress("UNCHECKED_CAST") - val createKeysBackupVersionBody = CreateKeysBackupVersionBody( - algorithm = keysBackupCreationInfo.algorithm, - authData = keysBackupCreationInfo.authData.toJsonDict() - ) - - keysBackupStateManager.state = KeysBackupState.Enabling - - createKeysBackupVersionTask - .configureWith(createKeysBackupVersionBody) { - this.callback = object : MatrixCallback<KeysVersion> { - override fun onSuccess(data: KeysVersion) { - // Reset backup markers. - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - // move tx out of UI thread - cryptoStore.resetBackupMarkers() - } - - val keyBackupVersion = KeysVersionResult( - algorithm = createKeysBackupVersionBody.algorithm, - authData = createKeysBackupVersionBody.authData, - version = data.version, - // We can consider that the server does not have keys yet - count = 0, - hash = "" - ) - - enableKeysBackup(keyBackupVersion) - - callback.onSuccess(data) - } - - override fun onFailure(failure: Throwable) { - keysBackupStateManager.state = KeysBackupState.Disabled - callback.onFailure(failure) - } - } - } - .executeBy(taskExecutor) - } - - override fun deleteBackup(version: String, callback: MatrixCallback<Unit>?) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - withContext(coroutineDispatchers.crypto) { - // If we're currently backing up to this backup... stop. - // (We start using it automatically in createKeysBackupVersion so this is symmetrical). - if (keysBackupVersion != null && version == keysBackupVersion?.version) { - resetKeysBackupData() - keysBackupVersion = null - keysBackupStateManager.state = KeysBackupState.Unknown - } - - deleteBackupTask - .configureWith(DeleteBackupTask.Params(version)) { - this.callback = object : MatrixCallback<Unit> { - private fun eventuallyRestartBackup() { - // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver - if (state == KeysBackupState.Unknown) { - checkAndStartKeysBackup() - } - } - - override fun onSuccess(data: Unit) { - eventuallyRestartBackup() - - uiHandler.post { callback?.onSuccess(Unit) } - } - - override fun onFailure(failure: Throwable) { - eventuallyRestartBackup() - - uiHandler.post { callback?.onFailure(failure) } - } - } - } - .executeBy(taskExecutor) - } - } - } - - override fun canRestoreKeys(): Boolean { - // Server contains more keys than locally - val totalNumberOfKeysLocally = getTotalNumbersOfKeys() - - val keysBackupData = cryptoStore.getKeysBackupData() - - val totalNumberOfKeysServer = keysBackupData?.backupLastServerNumberOfKeys ?: -1 - // Not used for the moment - // val hashServer = keysBackupData?.backupLastServerHash - - return when { - totalNumberOfKeysLocally < totalNumberOfKeysServer -> { - // Server contains more keys than this device - true - } - totalNumberOfKeysLocally == totalNumberOfKeysServer -> { - // Same number, compare hash? - // TODO We have not found any algorithm to determine if a restore is recommended here. Return false for the moment - false - } - else -> false - } - } - - override fun getTotalNumbersOfKeys(): Int { - return cryptoStore.inboundGroupSessionsCount(false) - } - - override fun getTotalNumbersOfBackedUpKeys(): Int { - return cryptoStore.inboundGroupSessionsCount(true) - } - - override fun backupAllGroupSessions(progressListener: ProgressListener?, - callback: MatrixCallback<Unit>?) { - // Get a status right now - getBackupProgress(object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - // Reset previous listeners if any - resetBackupAllGroupSessionsListeners() - Timber.v("backupAllGroupSessions: backupProgress: $progress/$total") - try { - progressListener?.onProgress(progress, total) - } catch (e: Exception) { - Timber.e(e, "backupAllGroupSessions: onProgress failure") - } - - if (progress == total) { - Timber.v("backupAllGroupSessions: complete") - callback?.onSuccess(Unit) - return - } - - backupAllGroupSessionsCallback = callback - - // Listen to `state` change to determine when to call onBackupProgress and onComplete - keysBackupStateListener = object : KeysBackupStateListener { - override fun onStateChange(newState: KeysBackupState) { - getBackupProgress(object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - try { - progressListener?.onProgress(progress, total) - } catch (e: Exception) { - Timber.e(e, "backupAllGroupSessions: onProgress failure 2") - } - - // If backup is finished, notify the main listener - if (state === KeysBackupState.ReadyToBackUp) { - backupAllGroupSessionsCallback?.onSuccess(Unit) - resetBackupAllGroupSessionsListeners() - } - } - }) - } - }.also { keysBackupStateManager.addListener(it) } - - backupKeys() - } - }) - } - - override fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult, - callback: MatrixCallback<KeysBackupVersionTrust>) { - // TODO Validate with François that this is correct - object : Task<KeysVersionResult, KeysBackupVersionTrust> { - override suspend fun execute(params: KeysVersionResult): KeysBackupVersionTrust { - return getKeysBackupTrustBg(params) - } - } - .configureWith(keysBackupVersion) { - this.callback = callback - this.executionThread = TaskThread.COMPUTATION - } - .executeBy(taskExecutor) - } - - /** - * Check trust on a key backup version. - * This has to be called on background thread. - * - * @param keysBackupVersion the backup version to check. - * @return a KeysBackupVersionTrust object - */ - @WorkerThread - private fun getKeysBackupTrustBg(keysBackupVersion: KeysVersionResult): KeysBackupVersionTrust { - val keysBackupVersionTrust = KeysBackupVersionTrust() - val authData = keysBackupVersion.getAuthDataAsMegolmBackupAuthData() - - if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isNullOrEmpty()) { - Timber.v("getKeysBackupTrust: Key backup is absent or missing required data") - return keysBackupVersionTrust - } - - val mySigs = authData.signatures[userId] - if (mySigs.isNullOrEmpty()) { - Timber.v("getKeysBackupTrust: Ignoring key backup because it lacks any signatures from this user") - return keysBackupVersionTrust - } - - for ((keyId, mySignature) in mySigs) { - // XXX: is this how we're supposed to get the device id? - var deviceId: String? = null - val components = keyId.split(":") - if (components.size == 2) { - deviceId = components[1] - } - - if (deviceId != null) { - val device = cryptoStore.getUserDevice(userId, deviceId) - var isSignatureValid = false - - if (device == null) { - Timber.v("getKeysBackupTrust: Signature from unknown device $deviceId") - } else { - val fingerprint = device.fingerprint() - if (fingerprint != null) { - try { - olmDevice.verifySignature(fingerprint, authData.signalableJSONDictionary(), mySignature) - isSignatureValid = true - } catch (e: OlmException) { - Timber.w(e, "getKeysBackupTrust: Bad signature from device ${device.deviceId}") - } - } - - if (isSignatureValid && device.isVerified) { - keysBackupVersionTrust.usable = true - } - } - - val signature = KeysBackupVersionTrustSignature() - signature.device = device - signature.valid = isSignatureValid - signature.deviceId = deviceId - keysBackupVersionTrust.signatures.add(signature) - } - } - - return keysBackupVersionTrust - } - - override fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, - trust: Boolean, - callback: MatrixCallback<Unit>) { - Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}") - - // Get auth data to update it - val authData = getMegolmBackupAuthData(keysBackupVersion) - - if (authData == null) { - Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data") - - callback.onFailure(IllegalArgumentException("Missing element")) - } else { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) { - // Get current signatures, or create an empty set - val myUserSignatures = authData.signatures?.get(userId).orEmpty().toMutableMap() - - if (trust) { - // Add current device signature - val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary()) - - val deviceSignatures = objectSigner.signObject(canonicalJson) - - deviceSignatures[userId]?.forEach { entry -> - myUserSignatures[entry.key] = entry.value - } - } else { - // Remove current device signature - myUserSignatures.remove("ed25519:${credentials.deviceId}") - } - - // Create an updated version of KeysVersionResult - val newMegolmBackupAuthData = authData.copy() - - val newSignatures = newMegolmBackupAuthData.signatures.orEmpty().toMutableMap() - newSignatures[userId] = myUserSignatures - - val newMegolmBackupAuthDataWithNewSignature = newMegolmBackupAuthData.copy( - signatures = newSignatures - ) - - @Suppress("UNCHECKED_CAST") - UpdateKeysBackupVersionBody( - algorithm = keysBackupVersion.algorithm, - authData = newMegolmBackupAuthDataWithNewSignature.toJsonDict(), - version = keysBackupVersion.version) - } - - // And send it to the homeserver - updateKeysBackupVersionTask - .configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version, updateKeysBackupVersionBody)) { - this.callback = object : MatrixCallback<Unit> { - override fun onSuccess(data: Unit) { - // Relaunch the state machine on this updated backup version - val newKeysBackupVersion = KeysVersionResult( - algorithm = keysBackupVersion.algorithm, - authData = updateKeysBackupVersionBody.authData, - version = keysBackupVersion.version, - hash = keysBackupVersion.hash, - count = keysBackupVersion.count - ) - - checkAndStartWithKeysBackupVersion(newKeysBackupVersion) - - callback.onSuccess(data) - } - - override fun onFailure(failure: Throwable) { - callback.onFailure(failure) - } - } - } - .executeBy(taskExecutor) - } - } - } - - override fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, - recoveryKey: String, - callback: MatrixCallback<Unit>) { - Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}") - - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - val isValid = withContext(coroutineDispatchers.crypto) { - isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion) - } - - if (!isValid) { - Timber.w("trustKeyBackupVersionWithRecoveryKey: Invalid recovery key.") - - callback.onFailure(IllegalArgumentException("Invalid recovery key or password")) - } else { - trustKeysBackupVersion(keysBackupVersion, true, callback) - } - } - } - - override fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, - password: String, - callback: MatrixCallback<Unit>) { - Timber.v("trustKeysBackupVersionWithPassphrase: version ${keysBackupVersion.version}") - - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - val recoveryKey = withContext(coroutineDispatchers.crypto) { - recoveryKeyFromPassword(password, keysBackupVersion, null) - } - - if (recoveryKey == null) { - Timber.w("trustKeysBackupVersionWithPassphrase: Key backup is missing required data") - - callback.onFailure(IllegalArgumentException("Missing element")) - } else { - // Check trust using the recovery key - trustKeysBackupVersionWithRecoveryKey(keysBackupVersion, recoveryKey, callback) - } - } - } - - override fun onSecretKeyGossip(secret: String) { - Timber.i("## CrossSigning - onSecretKeyGossip") - - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - try { - val keysBackupVersion = getKeysBackupLastVersionTask.execute(Unit) - val recoveryKey = computeRecoveryKey(secret.fromBase64()) - if (isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)) { - awaitCallback<Unit> { - trustKeysBackupVersion(keysBackupVersion, true, it) - } - val importResult = awaitCallback<ImportRoomKeysResult> { - restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, null, null, null, it) - } - withContext(coroutineDispatchers.crypto) { - cryptoStore.saveBackupRecoveryKey(recoveryKey, keysBackupVersion.version) - } - Timber.i("onSecretKeyGossip: Recovered keys ${importResult.successfullyNumberOfImportedKeys} out of ${importResult.totalNumberOfKeys}") - } else { - Timber.e("onSecretKeyGossip: Recovery key is not valid ${keysBackupVersion.version}") - } - } catch (failure: Throwable) { - Timber.e("onSecretKeyGossip: failed to trust key backup version ${keysBackupVersion?.version}") - } - } - } - - /** - * 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 - - keysBackupStateListener?.let { - keysBackupStateManager.removeListener(it) - } - - keysBackupStateListener = null - } - - override fun getBackupProgress(progressListener: ProgressListener) { - val backedUpKeys = cryptoStore.inboundGroupSessionsCount(true) - val total = cryptoStore.inboundGroupSessionsCount(false) - - progressListener.onProgress(backedUpKeys, total) - } - - override fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, - recoveryKey: String, - roomId: String?, - sessionId: String?, - stepProgressListener: StepProgressListener?, - callback: MatrixCallback<ImportRoomKeysResult>) { - Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}") - - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - val decryption = withContext(coroutineDispatchers.crypto) { - // 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") - } - - 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<MegolmSessionData>() - // 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) - } - } - } - Timber.v("restoreKeysWithRecoveryKey: Decrypted ${sessionsData.size} keys out" + - " of $sessionsFromHsCount from the backup store on the homeserver") - - // Do not trigger a backup for them if they come from the backup version we are using - val backUp = keysVersionResult.version != keysBackupVersion?.version - if (backUp) { - Timber.v("restoreKeysWithRecoveryKey: Those keys will be backed up" + - " to backup version: ${keysBackupVersion?.version}") - } - - // Import them into the crypto store - val progressListener = if (stepProgressListener != null) { - object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - // Note: no need to post to UI thread, importMegolmSessionsData() will do it - stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total)) - } - } - } else { - null - } - - val result = megolmSessionDataImporter.handle(sessionsData, !backUp, progressListener) - - // Do not back up the key if it comes from a backup recovery - if (backUp) { - maybeBackupKeys() - } - // Save for next time and for gossiping - saveBackupRecoveryKey(recoveryKey, keysVersionResult.version) - result - } - }.foldToCallback(callback) - } - } - - override fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, - password: String, - roomId: String?, - sessionId: String?, - stepProgressListener: StepProgressListener?, - callback: MatrixCallback<ImportRoomKeysResult>) { - Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}") - - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - val progressListener = if (stepProgressListener != null) { - object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - uiHandler.post { - stepProgressListener.onStepProgress(StepProgressListener.Step.ComputingKey(progress, total)) - } - } - } - } else { - null - } - - val recoveryKey = withContext(coroutineDispatchers.crypto) { - recoveryKeyFromPassword(password, keysBackupVersion, progressListener) - } - if (recoveryKey == null) { - Timber.v("backupKeys: Invalid configuration") - throw IllegalStateException("Invalid configuration") - } else { - awaitCallback<ImportRoomKeysResult> { - restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener, it) - } - } - }.foldToCallback(callback) - } - } - - /** - * Same method as [RoomKeysRestClient.getRoomKey] except that it accepts nullable - * parameters and always returns a KeysBackupData object through the Callback - */ - private suspend fun getKeys(sessionId: String?, - roomId: String?, - version: String): KeysBackupData { - return if (roomId != null && sessionId != null) { - // Get key for the room and for the session - val data = getRoomSessionDataTask.execute(GetRoomSessionDataTask.Params(roomId, sessionId, version)) - // Convert to KeysBackupData - KeysBackupData(mutableMapOf( - roomId to RoomKeysBackupData(mutableMapOf( - sessionId to data - )) - )) - } else if (roomId != null) { - // Get all keys for the room - val data = getRoomSessionsDataTask.execute(GetRoomSessionsDataTask.Params(roomId, version)) - // Convert to KeysBackupData - KeysBackupData(mutableMapOf(roomId to data)) - } else { - // Get all keys - getSessionsDataTask.execute(GetSessionsDataTask.Params(version)) - } - } - - @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 - */ - fun maybeBackupKeys() { - when { - isStucked -> { - // If not already done, or in error case, check for a valid backup version on the homeserver. - // If there is one, maybeBackupKeys will be called again. - checkAndStartKeysBackup() - } - state == KeysBackupState.ReadyToBackUp -> { - keysBackupStateManager.state = KeysBackupState.WillBackUp - - // Wait between 0 and 10 seconds, to avoid backup requests from - // different clients hitting the server all at the same time when a - // new key is sent - val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS) - - cryptoCoroutineScope.launch { - delay(delayInMs) - uiHandler.post { backupKeys() } - } - } - else -> { - Timber.v("maybeBackupKeys: Skip it because state: $state") - } - } - } - - override fun getVersion(version: String, - callback: MatrixCallback<KeysVersionResult?>) { - getKeysBackupVersionTask - .configureWith(version) { - this.callback = object : MatrixCallback<KeysVersionResult> { - override fun onSuccess(data: KeysVersionResult) { - callback.onSuccess(data) - } - - override fun onFailure(failure: Throwable) { - if (failure is Failure.ServerError && - failure.error.code == MatrixError.M_NOT_FOUND) { - // Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup - callback.onSuccess(null) - } else { - // Transmit the error - callback.onFailure(failure) - } - } - } - } - .executeBy(taskExecutor) - } - - override fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) { - getKeysBackupLastVersionTask - .configureWith { - this.callback = object : MatrixCallback<KeysVersionResult> { - override fun onSuccess(data: KeysVersionResult) { - callback.onSuccess(data) - } - - override fun onFailure(failure: Throwable) { - if (failure is Failure.ServerError && - failure.error.code == MatrixError.M_NOT_FOUND) { - // Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup - callback.onSuccess(null) - } else { - // Transmit the error - callback.onFailure(failure) - } - } - } - } - .executeBy(taskExecutor) - } - - override fun forceUsingLastVersion(callback: MatrixCallback<Boolean>) { - getCurrentVersion(object : MatrixCallback<KeysVersionResult?> { - override fun onSuccess(data: KeysVersionResult?) { - val localBackupVersion = keysBackupVersion?.version - val serverBackupVersion = data?.version - - if (serverBackupVersion == null) { - if (localBackupVersion == null) { - // No backup on the server, and backup is not active - callback.onSuccess(true) - } else { - // No backup on the server, and we are currently backing up, so stop backing up - callback.onSuccess(false) - resetKeysBackupData() - keysBackupVersion = null - keysBackupStateManager.state = KeysBackupState.Disabled - } - } else { - if (localBackupVersion == null) { - // backup on the server, and backup is not active - callback.onSuccess(false) - // Do a check - checkAndStartWithKeysBackupVersion(data) - } else { - // Backup on the server, and we are currently backing up, compare version - if (localBackupVersion == serverBackupVersion) { - // We are already using the last version of the backup - callback.onSuccess(true) - } else { - // We are not using the last version, so delete the current version we are using on the server - callback.onSuccess(false) - - // This will automatically check for the last version then - deleteBackup(localBackupVersion, null) - } - } - } - } - - override fun onFailure(failure: Throwable) { - callback.onFailure(failure) - } - }) - } - - override fun checkAndStartKeysBackup() { - if (!isStucked) { - // Try to start or restart the backup only if it is in unknown or bad state - Timber.w("checkAndStartKeysBackup: invalid state: $state") - - return - } - - keysBackupVersion = null - keysBackupStateManager.state = KeysBackupState.CheckingBackUpOnHomeserver - - getCurrentVersion(object : MatrixCallback<KeysVersionResult?> { - override fun onSuccess(data: KeysVersionResult?) { - checkAndStartWithKeysBackupVersion(data) - } - - override fun onFailure(failure: Throwable) { - Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version") - keysBackupStateManager.state = KeysBackupState.Unknown - } - }) - } - - private fun checkAndStartWithKeysBackupVersion(keyBackupVersion: KeysVersionResult?) { - Timber.v("checkAndStartWithKeyBackupVersion: ${keyBackupVersion?.version}") - - keysBackupVersion = keyBackupVersion - - if (keyBackupVersion == null) { - Timber.v("checkAndStartWithKeysBackupVersion: Found no key backup version on the homeserver") - resetKeysBackupData() - keysBackupStateManager.state = KeysBackupState.Disabled - } else { - getKeysBackupTrust(keyBackupVersion, object : MatrixCallback<KeysBackupVersionTrust> { - override fun onSuccess(data: KeysBackupVersionTrust) { - val versionInStore = cryptoStore.getKeyBackupVersion() - - if (data.usable) { - Timber.v("checkAndStartWithKeysBackupVersion: Found usable key backup. version: ${keyBackupVersion.version}") - // Check the version we used at the previous app run - if (versionInStore != null && versionInStore != keyBackupVersion.version) { - Timber.v(" -> clean the previously used version $versionInStore") - resetKeysBackupData() - } - - Timber.v(" -> enabling key backups") - enableKeysBackup(keyBackupVersion) - } else { - Timber.v("checkAndStartWithKeysBackupVersion: No usable key backup. version: ${keyBackupVersion.version}") - if (versionInStore != null) { - Timber.v(" -> disabling key backup") - resetKeysBackupData() - } - - keysBackupStateManager.state = KeysBackupState.NotTrusted - } - } - - override fun onFailure(failure: Throwable) { - // Cannot happen - } - }) - } - } - -/* ========================================================================================== - * Private - * ========================================================================================== */ - - /** - * 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_MEGOLM_BACKUP } - ?.getAuthDataAsMegolmBackupAuthData() - ?.takeIf { it.publicKey.isNotEmpty() } - } - - /** - * Compute the recovery key from a password and key backup version. - * - * @param password the password. - * @param keysBackupData the backup and its auth data. - * - * @return the recovery key if successful, null in other cases - */ - @WorkerThread - private fun recoveryKeyFromPassword(password: String, keysBackupData: KeysVersionResult, progressListener: ProgressListener?): String? { - val authData = getMegolmBackupAuthData(keysBackupData) - - if (authData == null) { - Timber.w("recoveryKeyFromPassword: invalid parameter") - return null - } - - if (authData.privateKeySalt.isNullOrBlank() || - authData.privateKeyIterations == null) { - Timber.w("recoveryKeyFromPassword: Salt and/or iterations not found in key backup auth data") - - return null - } - - // Extract the recovery key from the passphrase - val data = retrievePrivateKeyWithPassword(password, authData.privateKeySalt, authData.privateKeyIterations, progressListener) - - return computeRecoveryKey(data) - } - - /** - * Check if a recovery key matches key backup authentication data. - * - * @param recoveryKey the recovery key to challenge. - * @param keysBackupData the backup and its auth data. - * - * @return true if successful. - */ - @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 - } - - // 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<Boolean>) { - val safeKeysBackupVersion = keysBackupVersion ?: return Unit.also { callback.onSuccess(false) } - - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - isValidRecoveryKeyForKeysBackupVersion(recoveryKey, safeKeysBackupVersion).let { - callback.onSuccess(it) - } - } - } - - /** - * Enable backing up of keys. - * This method will update the state and will start sending keys in nominal case - * - * @param keysVersionResult backup information object as returned by [getCurrentVersion]. - */ - private fun enableKeysBackup(keysVersionResult: KeysVersionResult) { - val retrievedMegolmBackupAuthData = keysVersionResult.getAuthDataAsMegolmBackupAuthData() - - if (retrievedMegolmBackupAuthData != null) { - keysBackupVersion = keysVersionResult - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - cryptoStore.setKeyBackupVersion(keysVersionResult.version) - } - - onServerDataRetrieved(keysVersionResult.count, keysVersionResult.hash) - - try { - backupOlmPkEncryption = OlmPkEncryption().apply { - setRecipientKey(retrievedMegolmBackupAuthData.publicKey) - } - } catch (e: OlmException) { - Timber.e(e, "OlmException") - keysBackupStateManager.state = KeysBackupState.Disabled - return - } - - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp - - maybeBackupKeys() - } else { - Timber.e("Invalid authentication data") - keysBackupStateManager.state = KeysBackupState.Disabled - } - } - - /** - * Update the DB with data fetch from the server - */ - private fun onServerDataRetrieved(count: Int?, etag: String?) { - cryptoStore.setKeysBackupData(KeysBackupDataEntity() - .apply { - backupLastServerNumberOfKeys = count - backupLastServerHash = etag - } - ) - } - - /** - * Reset all local key backup data. - * - * Note: This method does not update the state - */ - private fun resetKeysBackupData() { - resetBackupAllGroupSessionsListeners() - - cryptoStore.setKeyBackupVersion(null) - cryptoStore.setKeysBackupData(null) - backupOlmPkEncryption?.releaseEncryption() - backupOlmPkEncryption = null - - // Reset backup markers - cryptoStore.resetBackupMarkers() - } - - /** - * Send a chunk of keys to backup - */ - @UiThread - private fun backupKeys() { - 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) { - Timber.v("backupKeys: Invalid configuration") - backupAllGroupSessionsCallback?.onFailure(IllegalStateException("Invalid configuration")) - resetBackupAllGroupSessionsListeners() - - return - } - - if (state === KeysBackupState.BackingUp) { - // Do nothing if we are already backing up - Timber.v("backupKeys: Invalid state: $state") - return - } - - // Get a chunk of keys to backup - val olmInboundGroupSessionWrappers = cryptoStore.inboundGroupSessionsToBackup(KEY_BACKUP_SEND_KEYS_MAX_COUNT) - - Timber.v("backupKeys: 1 - ${olmInboundGroupSessionWrappers.size} sessions to back up") - - if (olmInboundGroupSessionWrappers.isEmpty()) { - // Backup is up to date - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp - - backupAllGroupSessionsCallback?.onSuccess(Unit) - resetBackupAllGroupSessionsListeners() - return - } - - keysBackupStateManager.state = KeysBackupState.BackingUp - - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - withContext(coroutineDispatchers.crypto) { - Timber.v("backupKeys: 2 - Encrypting keys") - - // Gather data to send to the homeserver - // roomId -> sessionId -> MXKeyBackupData - val keysBackupData = KeysBackupData() - - olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper -> - val roomId = olmInboundGroupSessionWrapper.roomId ?: return@forEach - val olmInboundGroupSession = olmInboundGroupSessionWrapper.olmInboundGroupSession ?: return@forEach - - try { - encryptGroupSession(olmInboundGroupSessionWrapper) - ?.let { - keysBackupData.roomIdToRoomKeysBackupData - .getOrPut(roomId) { RoomKeysBackupData() } - .sessionIdToKeyBackupData[olmInboundGroupSession.sessionIdentifier()] = it - } - } catch (e: OlmException) { - Timber.e(e, "OlmException") - } - } - - Timber.v("backupKeys: 4 - Sending request") - - // Make the request - val version = keysBackupVersion?.version ?: return@withContext - - storeSessionDataTask - .configureWith(StoreSessionsDataTask.Params(version, keysBackupData)) { - this.callback = object : MatrixCallback<BackupKeysResult> { - override fun onSuccess(data: BackupKeysResult) { - uiHandler.post { - Timber.v("backupKeys: 5a - Request complete") - - // Mark keys as backed up - cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers) - - if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) { - Timber.v("backupKeys: All keys have been backed up") - onServerDataRetrieved(data.count, data.hash) - - // Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess() - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp - } else { - Timber.v("backupKeys: Continue to back up keys") - keysBackupStateManager.state = KeysBackupState.WillBackUp - - backupKeys() - } - } - } - - override fun onFailure(failure: Throwable) { - if (failure is Failure.ServerError) { - uiHandler.post { - Timber.e(failure, "backupKeys: backupKeys failed.") - - when (failure.error.code) { - MatrixError.M_NOT_FOUND, - MatrixError.M_WRONG_ROOM_KEYS_VERSION -> { - // Backup has been deleted on the server, or we are not using the last backup version - keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion - backupAllGroupSessionsCallback?.onFailure(failure) - resetBackupAllGroupSessionsListeners() - resetKeysBackupData() - keysBackupVersion = null - - // Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver - checkAndStartKeysBackup() - } - else -> - // Come back to the ready state so that we will retry on the next received key - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp - } - } - } else { - uiHandler.post { - backupAllGroupSessionsCallback?.onFailure(failure) - resetBackupAllGroupSessionsListeners() - - Timber.e("backupKeys: backupKeys failed.") - - // Retry a bit later - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp - maybeBackupKeys() - } - } - } - } - } - .executeBy(taskExecutor) - } - } - } - - @VisibleForTesting - @WorkerThread - fun encryptGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2): KeyBackupData? { - // Gather information for each key - val device = olmInboundGroupSessionWrapper.senderKey?.let { cryptoStore.deviceWithIdentityKey(it) } - - // Build the m.megolm_backup.v1.curve25519-aes-sha2 data as defined at - // https://github.com/uhoreg/matrix-doc/blob/e2e_backup/proposals/1219-storing-megolm-keys-serverside.md#mmegolm_backupv1curve25519-aes-sha2-key-format - val sessionData = olmInboundGroupSessionWrapper.exportKeys() ?: 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) - - val json = MoshiProvider.providesMoshi() - .adapter(Map::class.java) - .toJson(sessionBackupData) - - val encryptedSessionBackupData = try { - backupOlmPkEncryption?.encrypt(json) - } catch (e: OlmException) { - Timber.e(e, "OlmException") - null - } - ?: return null - - // Build backup data for that key - return KeyBackupData( - firstMessageIndex = try { - olmInboundGroupSessionWrapper.olmInboundGroupSession?.firstKnownIndex ?: 0 - } catch (e: OlmException) { - Timber.e(e, "OlmException") - 0L - }, - forwardedCount = olmInboundGroupSessionWrapper.forwardingCurve25519KeyChain.orEmpty().size, - isVerified = device?.isVerified == true, - - sessionData = mapOf( - "ciphertext" to encryptedSessionBackupData.mCipherText, - "mac" to encryptedSessionBackupData.mMac, - "ephemeral" to encryptedSessionBackupData.mEphemeralKey) - ) - } - - @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 - * ========================================================================================== */ - - // Direct access for test only - @VisibleForTesting - val store - get() = cryptoStore - - @VisibleForTesting - fun createFakeKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, - callback: MatrixCallback<KeysVersion>) { - @Suppress("UNCHECKED_CAST") - val createKeysBackupVersionBody = CreateKeysBackupVersionBody( - algorithm = keysBackupCreationInfo.algorithm, - authData = keysBackupCreationInfo.authData.toJsonDict() - ) - - createKeysBackupVersionTask - .configureWith(createKeysBackupVersionBody) { - this.callback = callback - } - .executeBy(taskExecutor) - } - - override fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? { - return cryptoStore.getKeyBackupRecoveryKeyInfo() - } - - override fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) { - cryptoStore.saveBackupRecoveryKey(recoveryKey, version) - } - - companion object { - // Maximum delay in ms in {@link maybeBackupKeys} - private const val KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS = 10_000L - - // Maximum number of keys to send at a time to the homeserver. - private const val KEY_BACKUP_SEND_KEYS_MAX_COUNT = 100 - } - -/* ========================================================================================== - * DEBUG INFO - * ========================================================================================== */ - - override fun toString() = "KeysBackup for $userId" -} 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 78ef958bbf..1dbccf15bd 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 @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.keysbackup import android.os.Handler +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener import timber.log.Timber @@ -33,11 +34,13 @@ internal class KeysBackupStateManager(private val uiHandler: Handler) { field = newState // Notify listeners about the state change, on the ui thread - uiHandler.post { - synchronized(listeners) { - listeners.forEach { + synchronized(listeners) { + listeners.forEach { + uiHandler.post { // Use newState because state may have already changed again - it.onStateChange(newState) + tryOrNull { + it.onStateChange(newState) + } } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt index 4134c0e8aa..071e1995c2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt @@ -18,16 +18,18 @@ 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 import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCoroutineDispatchers +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 @@ -37,6 +39,7 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import org.matrix.android.sdk.internal.crypto.MegolmSessionData +import org.matrix.android.sdk.internal.crypto.MegolmSessionImportManager import org.matrix.android.sdk.internal.crypto.OlmMachineProvider import org.matrix.android.sdk.internal.crypto.RequestSender import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust @@ -52,10 +55,8 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBa import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo import org.matrix.android.sdk.internal.di.MoshiProvider -import org.matrix.android.sdk.internal.extensions.foldToCallback import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.util.JsonCanonicalizer -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.olm.OlmException import timber.log.Timber import uniffi.olm.BackupRecoveryKey @@ -74,6 +75,7 @@ internal class RustKeyBackupService @Inject constructor( olmMachineProvider: OlmMachineProvider, private val sender: RequestSender, private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val megolmSessionImportManager: MegolmSessionImportManager, private val cryptoCoroutineScope: CoroutineScope, ) : KeysBackupService { companion object { @@ -91,7 +93,9 @@ internal class RustKeyBackupService @Inject constructor( override var keysBackupVersion: KeysVersionResult? = null private set - private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null +// private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null + + private val importScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) private var keysBackupStateListener: KeysBackupStateListener? = null @@ -115,64 +119,57 @@ internal class RustKeyBackupService @Inject constructor( keysBackupStateManager.removeListener(listener) } - override fun prepareKeysBackupVersion(password: String?, - progressListener: ProgressListener?, - callback: MatrixCallback<MegolmBackupCreationInfo>) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - withContext(coroutineDispatchers.crypto) { - val key = if (password != null) { - BackupRecoveryKey.newFromPassphrase(password) - } else { - BackupRecoveryKey() - } + override suspend fun prepareKeysBackupVersion(password: String?): MegolmBackupCreationInfo { + return withContext(coroutineDispatchers.computation) { + val key = if (password != null) { + // this might be a bit slow as it's stretching the password + BackupRecoveryKey.newFromPassphrase(password) + } else { + BackupRecoveryKey() + } - val publicKey = key.megolmV1PublicKey() - val backupAuthData = SignalableMegolmBackupAuthData( - publicKey = publicKey.publicKey, - privateKeySalt = publicKey.passphraseInfo?.privateKeySalt, - privateKeyIterations = publicKey.passphraseInfo?.privateKeyIterations - ) - val canonicalJson = JsonCanonicalizer.getCanonicalJson( - Map::class.java, - backupAuthData.signalableJSONDictionary() - ) + val publicKey = key.megolmV1PublicKey() + val backupAuthData = SignalableMegolmBackupAuthData( + publicKey = publicKey.publicKey, + privateKeySalt = publicKey.passphraseInfo?.privateKeySalt, + privateKeyIterations = publicKey.passphraseInfo?.privateKeyIterations + ) + val canonicalJson = JsonCanonicalizer.getCanonicalJson( + Map::class.java, + backupAuthData.signalableJSONDictionary() + ) - val signedMegolmBackupAuthData = MegolmBackupAuthData( - publicKey = backupAuthData.publicKey, - privateKeySalt = backupAuthData.privateKeySalt, - privateKeyIterations = backupAuthData.privateKeyIterations, - signatures = olmMachine.sign(canonicalJson) - ) + val signedMegolmBackupAuthData = MegolmBackupAuthData( + publicKey = backupAuthData.publicKey, + privateKeySalt = backupAuthData.privateKeySalt, + privateKeyIterations = backupAuthData.privateKeyIterations, + signatures = olmMachine.sign(canonicalJson) + ) - MegolmBackupCreationInfo( - algorithm = publicKey.backupAlgorithm, - authData = signedMegolmBackupAuthData, - recoveryKey = key.toBase58() - ) - } - }.foldToCallback(callback) + MegolmBackupCreationInfo( + algorithm = publicKey.backupAlgorithm, + authData = signedMegolmBackupAuthData, + recoveryKey = key.toBase58() + ) } } - override fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, - callback: MatrixCallback<KeysVersion>) { - @Suppress("UNCHECKED_CAST") - val createKeysBackupVersionBody = CreateKeysBackupVersionBody( - algorithm = keysBackupCreationInfo.algorithm, - authData = keysBackupCreationInfo.authData.toJsonDict() - ) + override suspend fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo): KeysVersion { + return withContext(coroutineDispatchers.crypto) { + val createKeysBackupVersionBody = CreateKeysBackupVersionBody( + algorithm = keysBackupCreationInfo.algorithm, + authData = keysBackupCreationInfo.authData.toJsonDict() + ) - keysBackupStateManager.state = KeysBackupState.Enabling + keysBackupStateManager.state = KeysBackupState.Enabling - cryptoCoroutineScope.launch(coroutineDispatchers.main) { try { - val data = sender.createKeyBackup(createKeysBackupVersionBody) + val data = withContext(coroutineDispatchers.io) { + sender.createKeyBackup(createKeysBackupVersionBody) + } // Reset backup markers. // Don't we need to join the task here? Isn't this a race condition? - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - olmMachine.disableBackup() - } + olmMachine.disableBackup() val keyBackupVersion = KeysVersionResult( algorithm = createKeysBackupVersionBody.algorithm, @@ -182,13 +179,11 @@ internal class RustKeyBackupService @Inject constructor( count = 0, hash = "" ) - enableKeysBackup(keyBackupVersion) - - callback.onSuccess(data) + data } catch (failure: Throwable) { keysBackupStateManager.state = KeysBackupState.Disabled - callback.onFailure(failure) + throw failure } } } @@ -200,7 +195,7 @@ internal class RustKeyBackupService @Inject constructor( } private fun resetBackupAllGroupSessionsListeners() { - backupAllGroupSessionsCallback = null +// backupAllGroupSessionsCallback = null keysBackupStateListener?.let { keysBackupStateManager.removeListener(it) @@ -219,29 +214,24 @@ internal class RustKeyBackupService @Inject constructor( olmMachine.disableBackup() } - override fun deleteBackup(version: String, callback: MatrixCallback<Unit>?) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - withContext(coroutineDispatchers.crypto) { - if (keysBackupVersion != null && version == keysBackupVersion?.version) { - resetKeysBackupData() - keysBackupVersion = null - keysBackupStateManager.state = KeysBackupState.Unknown - } + override suspend fun deleteBackup(version: String) { + withContext(coroutineDispatchers.crypto) { + if (keysBackupVersion != null && version == keysBackupVersion?.version) { + resetKeysBackupData() + keysBackupVersion = null + keysBackupStateManager.state = KeysBackupState.Unknown + } - fun eventuallyRestartBackup() { - // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver - if (state == KeysBackupState.Unknown) { - checkAndStartKeysBackup() - } + try { + sender.deleteKeyBackup(version) + // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver + if (state == KeysBackupState.Unknown) { + checkAndStartKeysBackup() } - - try { - sender.deleteKeyBackup(version) - eventuallyRestartBackup() - uiHandler.post { callback?.onSuccess(Unit) } - } catch (failure: Throwable) { - eventuallyRestartBackup() - uiHandler.post { callback?.onFailure(failure) } + } catch (failure: Throwable) { + // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver + if (state == KeysBackupState.Unknown) { + checkAndStartKeysBackup() } } } @@ -264,12 +254,12 @@ internal class RustKeyBackupService @Inject constructor( return olmMachine.roomKeyCounts().backedUp.toInt() } - override fun backupAllGroupSessions(progressListener: ProgressListener?, - callback: MatrixCallback<Unit>?) { - // This is only used in tests? While it's fine have methods that are - // only used for tests, this one has a lot of logic that is nowhere else used. - TODO() - } +// override fun backupAllGroupSessions(progressListener: ProgressListener?, +// callback: MatrixCallback<Unit>?) { +// // This is only used in tests? While it's fine have methods that are +// // only used for tests, this one has a lot of logic that is nowhere else used. +// TODO() +// } private suspend fun checkBackupTrust(authData: MegolmBackupAuthData?): KeysBackupVersionTrust { return if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isNullOrEmpty()) { @@ -280,82 +270,68 @@ internal class RustKeyBackupService @Inject constructor( } } - override fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult, - callback: MatrixCallback<KeysBackupVersionTrust>) { + override suspend fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult): KeysBackupVersionTrust { val authData = keysBackupVersion.getAuthDataAsMegolmBackupAuthData() - - cryptoCoroutineScope.launch { - try { - callback.onSuccess(checkBackupTrust(authData)) - } catch (exception: Throwable) { - callback.onFailure(exception) - } + return withContext(coroutineDispatchers.crypto) { + checkBackupTrust(authData) } } - override fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, - trust: Boolean, - callback: MatrixCallback<Unit>) { - Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}") + override suspend fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, trust: Boolean) { + withContext(coroutineDispatchers.crypto) { + Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}") - // Get auth data to update it - val authData = getMegolmBackupAuthData(keysBackupVersion) + // Get auth data to update it + val authData = getMegolmBackupAuthData(keysBackupVersion) - if (authData == null) { - Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data") + if (authData == null) { + Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data") + throw IllegalArgumentException("Missing element") + } else { + // Get current signatures, or create an empty set + val userId = olmMachine.userId() + val signatures = authData.signatures?.get(userId).orEmpty().toMutableMap() - callback.onFailure(IllegalArgumentException("Missing element")) - } else { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - val body = withContext(coroutineDispatchers.crypto) { - // Get current signatures, or create an empty set - val userId = olmMachine.userId() - val signatures = authData.signatures?.get(userId).orEmpty().toMutableMap() + if (trust) { + // Add current device signature + val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary()) + val deviceSignature = olmMachine.sign(canonicalJson) - if (trust) { - // Add current device signature - val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary()) - val deviceSignature = olmMachine.sign(canonicalJson) - - deviceSignature[userId]?.forEach { entry -> - signatures[entry.key] = entry.value - } - } else { - signatures.remove("ed25519:${olmMachine.deviceId()}") + deviceSignature[userId]?.forEach { entry -> + signatures[entry.key] = entry.value } - - val newAuthData = authData.copy() - val newSignatures = newAuthData.signatures.orEmpty().toMutableMap() - newSignatures[userId] = signatures - - @Suppress("UNCHECKED_CAST") - UpdateKeysBackupVersionBody( - algorithm = keysBackupVersion.algorithm, - authData = newAuthData.copy(signatures = newSignatures).toJsonDict(), - version = keysBackupVersion.version) + } else { + signatures.remove("ed25519:${olmMachine.deviceId()}") } - try { + + val newAuthData = authData.copy() + val newSignatures = newAuthData.signatures.orEmpty().toMutableMap() + newSignatures[userId] = signatures + + val body = UpdateKeysBackupVersionBody( + algorithm = keysBackupVersion.algorithm, + authData = newAuthData.copy(signatures = newSignatures).toJsonDict(), + version = keysBackupVersion.version) + + withContext(coroutineDispatchers.io) { sender.updateBackup(keysBackupVersion, body) - - val newKeysBackupVersion = KeysVersionResult( - algorithm = keysBackupVersion.algorithm, - authData = body.authData, - version = keysBackupVersion.version, - hash = keysBackupVersion.hash, - count = keysBackupVersion.count - ) - - checkAndStartWithKeysBackupVersion(newKeysBackupVersion) - callback.onSuccess(Unit) - } catch (exception: Throwable) { - callback.onFailure(exception) } + + val newKeysBackupVersion = KeysVersionResult( + algorithm = keysBackupVersion.algorithm, + authData = body.authData, + version = keysBackupVersion.version, + hash = keysBackupVersion.hash, + count = keysBackupVersion.count + ) + + checkAndStartWithKeysBackupVersion(newKeysBackupVersion) } } } // Check that the recovery key matches to the public key that we downloaded from the server. - // If they match, we can trust the public key and enable backups since we have the private key. +// If they match, we can trust the public key and enable backups since we have the private key. private fun checkRecoveryKey(recoveryKey: BackupRecoveryKey, keysBackupData: KeysVersionResult) { val backupKey = recoveryKey.megolmV1PublicKey() val authData = getMegolmBackupAuthData(keysBackupData) @@ -376,60 +352,50 @@ internal class RustKeyBackupService @Inject constructor( } } - override fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, - recoveryKey: String, - callback: MatrixCallback<Unit>) { + override suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, recoveryKey: String) { Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}") - - cryptoCoroutineScope.launch { - try { - // This is ~nowhere mentioned, the string here is actually a base58 encoded key. - // This not really supported by the spec for the backup key, the 4S key supports - // base58 encoding and the same method seems to be used here. - val key = BackupRecoveryKey.fromBase58(recoveryKey) - checkRecoveryKey(key, keysBackupVersion) - trustKeysBackupVersion(keysBackupVersion, true, callback) - } catch (exception: Throwable) { - callback.onFailure(exception) - } + withContext(coroutineDispatchers.crypto) { + // This is ~nowhere mentioned, the string here is actually a base58 encoded key. + // This not really supported by the spec for the backup key, the 4S key supports + // base58 encoding and the same method seems to be used here. + val key = BackupRecoveryKey.fromBase58(recoveryKey) + checkRecoveryKey(key, keysBackupVersion) + trustKeysBackupVersion(keysBackupVersion, true) } } - override fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, - password: String, - callback: MatrixCallback<Unit>) { - cryptoCoroutineScope.launch { - try { - val key = recoveryKeyFromPassword(password, keysBackupVersion) - checkRecoveryKey(key, keysBackupVersion) - trustKeysBackupVersion(keysBackupVersion, true, callback) - } catch (exception: Throwable) { - Timber.w(exception) - callback.onFailure(exception) - } + override suspend fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, password: String) { + withContext(coroutineDispatchers.crypto) { + val key = recoveryKeyFromPassword(password, keysBackupVersion) + checkRecoveryKey(key, keysBackupVersion) + trustKeysBackupVersion(keysBackupVersion, true) } } - override fun onSecretKeyGossip(secret: String) { + override suspend fun onSecretKeyGossip(curveKeyBase64: String) { Timber.i("## CrossSigning - onSecretKeyGossip") - cryptoCoroutineScope.launch(coroutineDispatchers.main) { + withContext(coroutineDispatchers.crypto) { try { val version = sender.getKeyBackupVersion() if (version != null) { - val key = BackupRecoveryKey.fromBase64(secret) + val key = BackupRecoveryKey.fromBase64(curveKeyBase64) + if (isValidRecoveryKey(key, version)) { + trustKeysBackupVersion(version, true) + // we don't want to wait for that + importScope.launch { + try { + val importResult = restoreBackup(version, key, null, null, null) - awaitCallback<Unit> { - trustKeysBackupVersion(version, true, it) - } - val importResult = awaitCallback<ImportRoomKeysResult> { - cryptoCoroutineScope.launch { - restoreBackup(version, key, null, null, null) + Timber.i("onSecretKeyGossip: Recovered keys ${importResult.successfullyNumberOfImportedKeys} out of ${importResult.totalNumberOfKeys}") + } catch (failure: Throwable) { + // fail silently.. + Timber.e(failure, "onSecretKeyGossip: Failed to import keys from backup") + } } + // we can save, it's valid + saveBackupRecoveryKey(key.toBase64(), version.version) } - Timber.i("onSecretKeyGossip: Recovered keys ${importResult.successfullyNumberOfImportedKeys} out of ${importResult.totalNumberOfKeys}") - - saveBackupRecoveryKey(secret, version.version) } else { Timber.e("onSecretKeyGossip: Failed to import backup recovery key, no backup version was found on the server") } @@ -511,30 +477,48 @@ internal class RustKeyBackupService @Inject constructor( Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version") throw InvalidParameterException("Invalid recovery key") } + + // Save for next time and for gossiping + saveBackupRecoveryKey(recoveryKey.toBase64(), keysVersionResult.version) } - stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey) + withContext(coroutineDispatchers.main) { + stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey) + } // Get backed up keys from the homeserver val data = getKeys(sessionId, roomId, keysVersionResult.version) return withContext(coroutineDispatchers.computation) { - val sessionsData = ArrayList<MegolmSessionData>() - // 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, recoveryKey) - - sessionData?.let { - sessionsData.add(it) - } - } + withContext(Dispatchers.Main) { + stepProgressListener?.onStepProgress(StepProgressListener.Step.DecryptingKey(0, data.roomIdToRoomKeysBackupData.size)) } + // Decrypting by chunk of 500 keys in parallel + // we loose proper progress report but tested 3x faster on big backup + val sessionsData = data.roomIdToRoomKeysBackupData + .mapValues { + it.value.sessionIdToKeyBackupData + } + .flatMap { flat -> + flat.value.entries.map { flat.key to it } + } + .chunked(500) + .map { slice -> + async { + slice.mapNotNull { pair -> + decryptKeyBackupData(pair.second.value, pair.second.key, pair.first, recoveryKey) + } + } + } + .awaitAll() + .flatten() + + withContext(Dispatchers.Main) { + stepProgressListener?.onStepProgress(StepProgressListener.Step.DecryptingKey(data.roomIdToRoomKeysBackupData.size, data.roomIdToRoomKeysBackupData.size)) + } + Timber.v("restoreKeysWithRecoveryKey: Decrypted ${sessionsData.size} keys out" + - " of $sessionsFromHsCount from the backup store on the homeserver") + " of ${data.roomIdToRoomKeysBackupData.size} rooms from the backup store on the homeserver") // Do not trigger a backup for them if they come from the backup version we are using val backUp = keysVersionResult.version != keysBackupVersion?.version @@ -547,147 +531,138 @@ internal class RustKeyBackupService @Inject constructor( 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)) + cryptoCoroutineScope.launch(Dispatchers.Main) { + stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total)) + } } } } else { null } - val result = olmMachine.importDecryptedKeys(sessionsData, progressListener) + val result = olmMachine.importDecryptedKeys(sessionsData, progressListener).also { + megolmSessionImportManager.dispatchKeyImportResults(it) + } // Do not back up the key if it comes from a backup recovery if (backUp) { maybeBackupKeys() } - // Save for next time and for gossiping - saveBackupRecoveryKey(recoveryKey.toBase64(), keysVersionResult.version) result } } - override fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, - recoveryKey: String, - roomId: String?, - sessionId: String?, - stepProgressListener: StepProgressListener?, - callback: MatrixCallback<ImportRoomKeysResult>) { + override suspend fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, + recoveryKey: String, + roomId: String?, + sessionId: String?, + stepProgressListener: StepProgressListener?): ImportRoomKeysResult { Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}") - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - val key = BackupRecoveryKey.fromBase58(recoveryKey) - restoreBackup(keysVersionResult, key, roomId, sessionId, stepProgressListener) - }.foldToCallback(callback) - } + + val key = BackupRecoveryKey.fromBase58(recoveryKey) + + return restoreBackup(keysVersionResult, key, roomId, sessionId, stepProgressListener) } - override fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, - password: String, - roomId: String?, - sessionId: String?, - stepProgressListener: StepProgressListener?, - callback: MatrixCallback<ImportRoomKeysResult>) { + override suspend fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, + password: String, + roomId: String?, + sessionId: String?, + stepProgressListener: StepProgressListener?): ImportRoomKeysResult { Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}") - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - val recoveryKey = withContext(coroutineDispatchers.crypto) { - recoveryKeyFromPassword(password, keysBackupVersion) - } + val recoveryKey = withContext(coroutineDispatchers.crypto) { + recoveryKeyFromPassword(password, keysBackupVersion) + } - restoreBackup(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener) - }.foldToCallback(callback) + return restoreBackup(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener) + } + + override suspend fun getVersion(version: String): KeysVersionResult? { + return withContext(coroutineDispatchers.io) { + sender.getKeyBackupVersion(version) } } - override fun getVersion(version: String, callback: MatrixCallback<KeysVersionResult?>) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - sender.getKeyBackupVersion(version) - }.foldToCallback(callback) + @Throws + override suspend fun getCurrentVersion(): KeysVersionResult? { + return withContext(coroutineDispatchers.io) { + sender.getKeyBackupVersion() } } - override fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - sender.getKeyBackupVersion() - }.foldToCallback(callback) + override suspend fun forceUsingLastVersion(): Boolean { + val response = withContext(coroutineDispatchers.io) { + sender.getKeyBackupVersion() } - } - private suspend fun forceUsingLastVersionHelper(): Boolean { - val response = sender.getKeyBackupVersion() - val serverBackupVersion = response?.version - val localBackupVersion = keysBackupVersion?.version + return withContext(coroutineDispatchers.crypto) { + val serverBackupVersion = response?.version + val localBackupVersion = keysBackupVersion?.version - Timber.d("BACKUP: $serverBackupVersion") + Timber.d("BACKUP: $serverBackupVersion") - return if (serverBackupVersion == null) { - if (localBackupVersion == null) { - // No backup on the server, and backup is not active - true - } else { - // No backup on the server, and we are currently backing up, so stop backing up - resetKeysBackupData() - keysBackupVersion = null - keysBackupStateManager.state = KeysBackupState.Disabled - false - } - } else { - if (localBackupVersion == null) { - // Do a check - checkAndStartWithKeysBackupVersion(response) - // backup on the server, and backup is not active - false - } else { - // Backup on the server, and we are currently backing up, compare version - if (localBackupVersion == serverBackupVersion) { - // We are already using the last version of the backup + if (serverBackupVersion == null) { + if (localBackupVersion == null) { + // No backup on the server, and backup is not active true } else { - // This will automatically check for the last version then - deleteBackup(localBackupVersion, null) - // We are not using the last version, so delete the current version we are using on the server + // No backup on the server, and we are currently backing up, so stop backing up + resetKeysBackupData() + keysBackupVersion = null + keysBackupStateManager.state = KeysBackupState.Disabled false } + } else { + if (localBackupVersion == null) { + // Do a check + checkAndStartWithKeysBackupVersion(response) + // backup on the server, and backup is not active + false + } else { + // Backup on the server, and we are currently backing up, compare version + if (localBackupVersion == serverBackupVersion) { + // We are already using the last version of the backup + true + } else { + // This will automatically check for the last version then + tryOrNull("Failed to automatically check for the last version") { + deleteBackup(localBackupVersion) + } + // We are not using the last version, so delete the current version we are using on the server + false + } + } } } } - override fun forceUsingLastVersion(callback: MatrixCallback<Boolean>) { - cryptoCoroutineScope.launch { - runCatching { - forceUsingLastVersionHelper() - }.foldToCallback(callback) + override suspend fun checkAndStartKeysBackup() { + withContext(coroutineDispatchers.crypto) { + if (!isStucked) { + // Try to start or restart the backup only if it is in unknown or bad state + Timber.w("checkAndStartKeysBackup: invalid state: $state") + return@withContext + } + + keysBackupVersion = null + keysBackupStateManager.state = KeysBackupState.CheckingBackUpOnHomeserver + + withContext(coroutineDispatchers.io) { + try { + val data = getCurrentVersion() + withContext(coroutineDispatchers.crypto) { + checkAndStartWithKeysBackupVersion(data) + } + } catch (failure: Throwable) { + Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version") + keysBackupStateManager.state = KeysBackupState.Unknown + } + } } } - override fun checkAndStartKeysBackup() { - if (!isStucked) { - // Try to start or restart the backup only if it is in unknown or bad state - Timber.w("checkAndStartKeysBackup: invalid state: $state") - - return - } - - keysBackupVersion = null - keysBackupStateManager.state = KeysBackupState.CheckingBackUpOnHomeserver - - getCurrentVersion(object : MatrixCallback<KeysVersionResult?> { - override fun onSuccess(data: KeysVersionResult?) { - checkAndStartWithKeysBackupVersion(data) - } - - override fun onFailure(failure: Throwable) { - Timber.e(failure, "checkAndStartKeysBackup: Failed to get current version") - keysBackupStateManager.state = KeysBackupState.Unknown - } - }) - } - - private fun checkAndStartWithKeysBackupVersion(keyBackupVersion: KeysVersionResult?) { + private suspend fun checkAndStartWithKeysBackupVersion(keyBackupVersion: KeysVersionResult?) { Timber.v("checkAndStartWithKeyBackupVersion: ${keyBackupVersion?.version}") keysBackupVersion = keyBackupVersion @@ -697,37 +672,34 @@ internal class RustKeyBackupService @Inject constructor( resetKeysBackupData() keysBackupStateManager.state = KeysBackupState.Disabled } else { - getKeysBackupTrust(keyBackupVersion, object : MatrixCallback<KeysBackupVersionTrust> { - override fun onSuccess(data: KeysBackupVersionTrust) { - val versionInStore = getKeyBackupRecoveryKeyInfo()?.version + try { + val data = getKeysBackupTrust(keyBackupVersion) + val versionInStore = getKeyBackupRecoveryKeyInfo()?.version - if (data.usable) { - Timber.v("checkAndStartWithKeysBackupVersion: Found usable key backup. version: ${keyBackupVersion.version}") - // Check the version we used at the previous app run - if (versionInStore != null && versionInStore != keyBackupVersion.version) { - Timber.v(" -> clean the previously used version $versionInStore") - resetKeysBackupData() - } - - Timber.v(" -> enabling key backups") - cryptoCoroutineScope.launch { - enableKeysBackup(keyBackupVersion) - } - } else { - Timber.v("checkAndStartWithKeysBackupVersion: No usable key backup. version: ${keyBackupVersion.version}") - if (versionInStore != null) { - Timber.v(" -> disabling key backup") - resetKeysBackupData() - } - - keysBackupStateManager.state = KeysBackupState.NotTrusted + if (data.usable) { + Timber.v("checkAndStartWithKeysBackupVersion: Found usable key backup. version: ${keyBackupVersion.version}") + // Check the version we used at the previous app run + if (versionInStore != null && versionInStore != keyBackupVersion.version) { + Timber.v(" -> clean the previously used version $versionInStore") + resetKeysBackupData() } - } - override fun onFailure(failure: Throwable) { - // Cannot happen + Timber.v(" -> enabling key backups") + cryptoCoroutineScope.launch { + enableKeysBackup(keyBackupVersion) + } + } else { + Timber.v("checkAndStartWithKeysBackupVersion: No usable key backup. version: ${keyBackupVersion.version}") + if (versionInStore != null) { + Timber.v(" -> disabling key backup") + resetKeysBackupData() + } + + keysBackupStateManager.state = KeysBackupState.NotTrusted } - }) + } catch (failure: Throwable) { + Timber.e(failure, "Failed to checkAndStartWithKeysBackupVersion $keyBackupVersion") + } } } @@ -737,14 +709,17 @@ internal class RustKeyBackupService @Inject constructor( return authData.publicKey == publicKey } - override fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback<Boolean>) { - val keysBackupVersion = keysBackupVersion ?: return Unit.also { callback.onSuccess(false) } + override suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean { + return withContext(coroutineDispatchers.crypto) { + val keysBackupVersion = keysBackupVersion ?: return@withContext false - try { val key = BackupRecoveryKey.fromBase64(recoveryKey) - callback.onSuccess(isValidRecoveryKey(key, keysBackupVersion)) - } catch (error: Throwable) { - callback.onFailure(error) + try { + isValidRecoveryKey(key, keysBackupVersion) + } catch (failure: Throwable) { + Timber.i("isValidRecoveryKeyForCurrentVersion: Invalid recovery key") + false + } } } @@ -822,31 +797,30 @@ internal class RustKeyBackupService @Inject constructor( /** * Do a backup if there are new keys, with a delay */ - fun maybeBackupKeys() { - when { - isStucked -> { - // If not already done, or in error case, check for a valid backup version on the homeserver. - // If there is one, maybeBackupKeys will be called again. - checkAndStartKeysBackup() - } - state == KeysBackupState.ReadyToBackUp -> { - keysBackupStateManager.state = KeysBackupState.WillBackUp + suspend fun maybeBackupKeys() { + withContext(coroutineDispatchers.crypto) { + when { + isStucked -> { + // If not already done, or in error case, check for a valid backup version on the homeserver. + // If there is one, maybeBackupKeys will be called again. + checkAndStartKeysBackup() + } + state == KeysBackupState.ReadyToBackUp -> { + keysBackupStateManager.state = KeysBackupState.WillBackUp - // Wait between 0 and 10 seconds, to avoid backup requests from - // different clients hitting the server all at the same time when a - // new key is sent - val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS) + // Wait between 0 and 10 seconds, to avoid backup requests from + // different clients hitting the server all at the same time when a + // new key is sent + val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS) - cryptoCoroutineScope.launch { - delay(delayInMs) - // TODO is this correct? we used to call uiHandler.post() instead of this - withContext(Dispatchers.Main) { - backupKeys() + importScope.launch { + delay(delayInMs) + tryOrNull("AUTO backup failed") { backupKeys() } } } - } - else -> { - Timber.v("maybeBackupKeys: Skip it because state: $state") + else -> { + Timber.v("maybeBackupKeys: Skip it because state: $state") + } } } } @@ -854,84 +828,79 @@ internal class RustKeyBackupService @Inject constructor( /** * Send a chunk of keys to backup */ - @UiThread private suspend fun backupKeys(forceRecheck: Boolean = false) { Timber.v("backupKeys") + withContext(coroutineDispatchers.crypto) { + // Sanity check, as this method can be called after a delay, the state may have change during the delay + if (!isEnabled || !olmMachine.backupEnabled() || keysBackupVersion == null) { + Timber.v("backupKeys: Invalid configuration $isEnabled ${olmMachine.backupEnabled()} $keysBackupVersion") +// backupAllGroupSessionsCallback?.onFailure(IllegalStateException("Invalid configuration")) + resetBackupAllGroupSessionsListeners() - // Sanity check, as this method can be called after a delay, the state may have change during the delay - if (!isEnabled || !olmMachine.backupEnabled() || keysBackupVersion == null) { - Timber.v("backupKeys: Invalid configuration $isEnabled ${olmMachine.backupEnabled()} $keysBackupVersion") - backupAllGroupSessionsCallback?.onFailure(IllegalStateException("Invalid configuration")) - resetBackupAllGroupSessionsListeners() + return@withContext + } - return - } + if (state === KeysBackupState.BackingUp && !forceRecheck) { + // Do nothing if we are already backing up + Timber.v("backupKeys: Invalid state: $state") + return@withContext + } - if (state === KeysBackupState.BackingUp && !forceRecheck) { - // Do nothing if we are already backing up - Timber.v("backupKeys: Invalid state: $state") - return - } + Timber.d("BACKUP: CREATING REQUEST") - Timber.d("BACKUP: CREATING REQUEST") + val request = olmMachine.backupRoomKeys() - val request = olmMachine.backupRoomKeys() + Timber.d("BACKUP: GOT REQUEST $request") - Timber.d("BACKUP: GOT REQUEST $request") + if (request == null) { + // Backup is up to date + // Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess() + keysBackupStateManager.state = KeysBackupState.ReadyToBackUp - if (request == null) { - // Backup is up to date - // Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess() - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp +// backupAllGroupSessionsCallback?.onSuccess(Unit) + resetBackupAllGroupSessionsListeners() + } else { + try { + if (request is Request.KeysBackup) { + keysBackupStateManager.state = KeysBackupState.BackingUp - backupAllGroupSessionsCallback?.onSuccess(Unit) - resetBackupAllGroupSessionsListeners() - } else { - try { - if (request is Request.KeysBackup) { - keysBackupStateManager.state = KeysBackupState.BackingUp + Timber.d("BACKUP SENDING REQUEST") + val response = withContext(coroutineDispatchers.io) { sender.backupRoomKeys(request) } + Timber.d("BACKUP GOT RESPONSE $response") + olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_BACKUP, response) + Timber.d("BACKUP MARKED REQUEST AS SENT") - Timber.d("BACKUP SENDING REQUEST") - val response = sender.backupRoomKeys(request) - Timber.d("BACKUP GOT RESPONSE $response") - olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_BACKUP, response) - Timber.d("BACKUP MARKED REQUEST AS SENT") - - // TODO, again is this correct? - withContext(Dispatchers.Main) { backupKeys(true) + } else { + // Can't happen, do we want to panic? } - } else { - // Can't happen, do we want to panic? - } - } catch (failure: Throwable) { - if (failure is Failure.ServerError) { - withContext(Dispatchers.Main) { - Timber.e(failure, "backupKeys: backupKeys failed.") + } catch (failure: Throwable) { + if (failure is Failure.ServerError) { + withContext(Dispatchers.Main) { + Timber.e(failure, "backupKeys: backupKeys failed.") - when (failure.error.code) { - MatrixError.M_NOT_FOUND, - MatrixError.M_WRONG_ROOM_KEYS_VERSION -> { - // Backup has been deleted on the server, or we are not using - // the last backup version - keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion - backupAllGroupSessionsCallback?.onFailure(failure) - resetBackupAllGroupSessionsListeners() - resetKeysBackupData() - keysBackupVersion = null + when (failure.error.code) { + MatrixError.M_NOT_FOUND, + MatrixError.M_WRONG_ROOM_KEYS_VERSION -> { + // Backup has been deleted on the server, or we are not using + // the last backup version + keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion +// backupAllGroupSessionsCallback?.onFailure(failure) + resetBackupAllGroupSessionsListeners() + resetKeysBackupData() + keysBackupVersion = null - // Do not stay in KeysBackupState.WrongBackUpVersion but check what - // is available on the homeserver - checkAndStartKeysBackup() + // Do not stay in KeysBackupState.WrongBackUpVersion but check what + // is available on the homeserver + checkAndStartKeysBackup() + } + else -> + // Come back to the ready state so that we will retry on the next received key + keysBackupStateManager.state = KeysBackupState.ReadyToBackUp } - else -> - // Come back to the ready state so that we will retry on the next received key - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp } - } - } else { - withContext(Dispatchers.Main) { - backupAllGroupSessionsCallback?.onFailure(failure) + } else { +// backupAllGroupSessionsCallback?.onFailure(failure) resetBackupAllGroupSessionsListeners() Timber.e("backupKeys: backupKeys failed: $failure") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/ImportRoomKeysResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/ImportRoomKeysResult.kt index e9d2a1bcd8..d04936bdde 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/ImportRoomKeysResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/ImportRoomKeysResult.kt @@ -16,5 +16,21 @@ package org.matrix.android.sdk.internal.crypto.model +import uniffi.olm.KeysImportResult + data class ImportRoomKeysResult(val totalNumberOfKeys: Int, - val successfullyNumberOfImportedKeys: Int) + val successfullyNumberOfImportedKeys: Int, + /**It's a map from room id to a map of the sender key to a list of session*/ + val importedSessionInfo: Map<String, Map<String, List<String>>> +) { + + companion object { + fun fromOlm(result: KeysImportResult): ImportRoomKeysResult { + return ImportRoomKeysResult( + result.total.toInt(), + result.imported.toInt(), + result.keys + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt index bdfe818c62..6620c1c6cc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt @@ -60,6 +60,7 @@ internal class DefaultSendEventTask @Inject constructor( eventType = event.type ?: "" ) } + Timber.d("Event sent to ${event.roomId} with event id ${response.eventId}") localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENT) return response.eventId.also { Timber.d("Event: $it just sent in ${params.event.roomId}") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt index 75d02dfd98..f93f72d312 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt @@ -40,7 +40,7 @@ internal class TimelineEventDecryptor @Inject constructor( ) { private val newSessionListener = object : NewSessionListener { - override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { + override fun onNewSession(roomId: String?, sessionId: String) { synchronized(unknownSessionsFailure) { unknownSessionsFailure[sessionId] ?.toList() diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt index bdae975846..0f532e4fea 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt @@ -83,11 +83,6 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() { .show() } - if (viewModel.keyVersionResult.value == null) { - // We need to fetch from API - viewModel.getLatestVersion() - } - viewModel.navigateEvent.observeEvent(this) { uxStateEvent -> when (uxStateEvent) { KeysBackupRestoreSharedViewModel.NAVIGATE_TO_RECOVER_WITH_KEY -> { diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt index 8362a3566e..62c13e3a00 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt @@ -23,9 +23,11 @@ import im.vector.app.R import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.LiveEvent +import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.listeners.StepProgressListener import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME @@ -35,7 +37,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult -import org.matrix.android.sdk.internal.util.awaitCallback import timber.log.Timber import javax.inject.Inject @@ -75,10 +76,15 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( var importRoomKeysFinishWithResult: MutableLiveData<LiveEvent<ImportRoomKeysResult>> = MutableLiveData() fun initSession(session: Session) { - this.session = session + if (!this::session.isInitialized) { + this.session = session + viewModelScope.launch { + getLatestVersion() + } + } } - val progressObserver = object : StepProgressListener { + private val progressObserver = object : StepProgressListener { override fun onStepProgress(step: StepProgressListener.Step) { when (step) { is StepProgressListener.Step.ComputingKey -> { @@ -106,62 +112,71 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( step.total)) } } + is StepProgressListener.Step.DecryptingKey -> { + if (step.progress == 0) { + loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restoring_waiting_message) + + "\n" + stringProvider.getString(R.string.keys_backup_restoring_decrypting_keys_waiting_message), + isIndeterminate = true)) + } else { + loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restoring_waiting_message) + + "\n" + stringProvider.getString(R.string.keys_backup_restoring_decrypting_keys_waiting_message), + step.progress, + step.total)) + } + } } } } - fun getLatestVersion() { + private suspend fun getLatestVersion() { val keysBackup = session.cryptoService().keysBackupService() - loadingEvent.value = WaitingViewData(stringProvider.getString(R.string.keys_backup_restore_is_getting_backup_version)) + loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restore_is_getting_backup_version))) - viewModelScope.launch(Dispatchers.IO) { - try { - val version = awaitCallback<KeysVersionResult?> { - keysBackup.getCurrentVersion(it) - } - if (version?.version == null) { - loadingEvent.postValue(null) - _keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, ""))) - return@launch - } + try { + val version = keysBackup.getCurrentVersion() + if (version?.version == null) { + loadingEvent.postValue(null) + _keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, ""))) + return + } - keyVersionResult.postValue(version) - // Let's check if there is quads - val isBackupKeyInQuadS = isBackupKeyInQuadS() + keyVersionResult.postValue(version) + // Let's check if there is quads + val isBackupKeyInQuadS = isBackupKeyInQuadS() - val savedSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() - if (savedSecret != null && savedSecret.version == version.version) { - // key is in memory! - keySourceModel.postValue( - KeySource(isInMemory = true, isInQuadS = true) - ) - // Go and use it!! - try { - recoverUsingBackupRecoveryKey(savedSecret.recoveryKey) - } catch (failure: Throwable) { - keySourceModel.postValue( - KeySource(isInMemory = false, isInQuadS = true) - ) - } - } else if (isBackupKeyInQuadS) { - // key is in QuadS! + val savedSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() + if (savedSecret != null && savedSecret.version == version.version) { + // key is in memory! + keySourceModel.postValue( + KeySource(isInMemory = true, isInQuadS = true) + ) + // Go and use it!! + try { + recoverUsingBackupRecoveryKey(computeRecoveryKey(savedSecret.recoveryKey.fromBase64()), version) + } catch (failure: Throwable) { + Timber.e(failure, "## recoverUsingBackupRecoveryKey FAILED") keySourceModel.postValue( KeySource(isInMemory = false, isInQuadS = true) ) - _navigateEvent.postValue(LiveEvent(NAVIGATE_TO_4S)) - } else { - // we need to restore directly - keySourceModel.postValue( - KeySource(isInMemory = false, isInQuadS = false) - ) } - - loadingEvent.postValue(null) - } catch (failure: Throwable) { - loadingEvent.postValue(null) - _keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, failure.localizedMessage))) + } else if (isBackupKeyInQuadS) { + // key is in QuadS! + keySourceModel.postValue( + KeySource(isInMemory = false, isInQuadS = true) + ) + _navigateEvent.postValue(LiveEvent(NAVIGATE_TO_4S)) + } else { + // we need to restore directly + keySourceModel.postValue( + KeySource(isInMemory = false, isInQuadS = false) + ) } + + loadingEvent.postValue(null) + } catch (failure: Throwable) { + loadingEvent.postValue(null) + _keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, failure.localizedMessage))) } } @@ -176,7 +191,7 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( ) return } - loadingEvent.value = WaitingViewData(stringProvider.getString(R.string.keys_backup_restore_is_getting_backup_version)) + loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restore_is_getting_backup_version))) viewModelScope.launch(Dispatchers.IO) { try { @@ -202,15 +217,12 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading))) try { - val result = awaitCallback<ImportRoomKeysResult> { - keysBackup.restoreKeyBackupWithPassword(keyVersion, - passphrase, - null, - session.myUserId, - progressObserver, - it - ) - } + val result = keysBackup.restoreKeyBackupWithPassword(keyVersion, + passphrase, + null, + session.myUserId, + progressObserver + ) loadingEvent.postValue(null) didRecoverSucceed(result) trustOnDecrypt(keysBackup, keyVersion) @@ -220,26 +232,27 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( } } - suspend fun recoverUsingBackupRecoveryKey(recoveryKey: String) { + suspend fun recoverUsingBackupRecoveryKey(recoveryKey: String, keyVersion: KeysVersionResult? = null) { val keysBackup = session.cryptoService().keysBackupService() - val keyVersion = keyVersionResult.value ?: return + // This is badddddd + val version = keyVersion ?: keyVersionResult.value ?: return loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading))) try { - val result = awaitCallback<ImportRoomKeysResult> { - keysBackup.restoreKeysWithRecoveryKey(keyVersion, - recoveryKey, - null, - session.myUserId, - progressObserver, - it - ) - } + val result = keysBackup.restoreKeysWithRecoveryKey(version, + recoveryKey, + null, + session.myUserId, + progressObserver + ) loadingEvent.postValue(null) - didRecoverSucceed(result) - trustOnDecrypt(keysBackup, keyVersion) + withContext(Dispatchers.Main) { + didRecoverSucceed(result) + trustOnDecrypt(keysBackup, version) + } } catch (failure: Throwable) { + Timber.e(failure, "## restoreKeysWithRecoveryKey failure") loadingEvent.postValue(null) throw failure } @@ -258,19 +271,19 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( } private fun trustOnDecrypt(keysBackup: KeysBackupService, keysVersionResult: KeysVersionResult) { - keysBackup.trustKeysBackupVersion(keysVersionResult, true, - object : MatrixCallback<Unit> { - override fun onSuccess(data: Unit) { - Timber.v("##### trustKeysBackupVersion onSuccess") - } - }) + // do that on session scope because could happen outside of view model lifecycle + session.coroutineScope.launch { + tryOrNull("## Failed to trustKeysBackupVersion") { + keysBackup.trustKeysBackupVersion(keysVersionResult, true) + } + } } fun moveToRecoverWithKey() { - _navigateEvent.value = LiveEvent(NAVIGATE_TO_RECOVER_WITH_KEY) + _navigateEvent.postValue(LiveEvent(NAVIGATE_TO_RECOVER_WITH_KEY)) } - fun didRecoverSucceed(result: ImportRoomKeysResult) { + private fun didRecoverSucceed(result: ImportRoomKeysResult) { importKeyResult = result _navigateEvent.postValue(LiveEvent(NAVIGATE_TO_SUCCESS)) } 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 9a5c62f2c8..51213fcd38 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 @@ -27,13 +27,11 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.NoOpMatrixCallback +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener -import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust import timber.log.Timber class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState, @@ -70,7 +68,9 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS } private fun init() { - keysBackupService.forceUsingLastVersion(NoOpMatrixCallback()) + viewModelScope.launch { + keysBackupService.forceUsingLastVersion() + } } private fun getKeysBackupTrust() = withState { state -> @@ -86,26 +86,24 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS } Timber.d("BACKUP: HEEEEEEE TWO") - keysBackupService - .getKeysBackupTrust(versionResult, object : MatrixCallback<KeysBackupVersionTrust> { - override fun onSuccess(data: KeysBackupVersionTrust) { - Timber.d("BACKUP: HEEEE suceeeded $data") - setState { - copy( - keysBackupVersionTrust = Success(data) - ) - } - } - - override fun onFailure(failure: Throwable) { - Timber.d("BACKUP: HEEEE FAILED $failure") - setState { - copy( - keysBackupVersionTrust = Fail(failure) - ) - } - } - }) + viewModelScope.launch { + try { + val data = keysBackupService.getKeysBackupTrust(versionResult) + Timber.d("BACKUP: HEEEE suceeeded $data") + setState { + copy( + keysBackupVersionTrust = Success(data) + ) + } + } catch (failure: Throwable) { + Timber.d("BACKUP: HEEEE FAILED $failure") + setState { + copy( + keysBackupVersionTrust = Fail(failure) + ) + } + } + } } } @@ -128,15 +126,16 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS private fun deleteCurrentBackup() { val keysBackupService = keysBackupService - if (keysBackupService.currentBackupVersion != null) { + val currentBackupVersion = keysBackupService.currentBackupVersion + if (currentBackupVersion != null) { setState { copy( deleteBackupRequest = Loading() ) } - - keysBackupService.deleteBackup(keysBackupService.currentBackupVersion!!, object : MatrixCallback<Unit> { - override fun onSuccess(data: Unit) { + viewModelScope.launch { + try { + keysBackupService.deleteBackup(currentBackupVersion) setState { copy( keysBackupVersion = null, @@ -145,16 +144,14 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS deleteBackupRequest = Uninitialized ) } - } - - override fun onFailure(failure: Throwable) { + } catch (failure: Throwable) { setState { copy( deleteBackupRequest = Fail(failure) ) } } - }) + } } } 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 1141886689..bc3479860a 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 @@ -19,17 +19,16 @@ package im.vector.app.features.crypto.keysbackup.setup import android.content.Context import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.nulabinc.zxcvbn.Strength import im.vector.app.R import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.utils.LiveEvent -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.listeners.ProgressListener +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult import timber.log.Timber import javax.inject.Inject @@ -89,48 +88,30 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() { recoveryKey.value = null prepareRecoverFailError.value = null - session.let { mxSession -> - val requestedId = currentRequestId.value!! + val requestedId = currentRequestId.value!! + viewModelScope.launch { + try { + val data = session.cryptoService().keysBackupService().prepareKeysBackupVersion(withPassphrase) + if (requestedId != currentRequestId.value) { + // this is an old request, we can't cancel but we can ignore + return@launch + } + recoveryKey.postValue(data.recoveryKey) + megolmBackupCreationInfo = data + copyHasBeenMade = false - mxSession.cryptoService().keysBackupService().prepareKeysBackupVersion(withPassphrase, - 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 - return - } + val keyBackup = session.cryptoService().keysBackupService() + createKeysBackup(context, keyBackup) + } catch (failure: Throwable) { + if (requestedId != currentRequestId.value) { + // this is an old request, we can't cancel but we can ignore + return@launch + } - loadingStatus.value = WaitingViewData(context.getString(R.string.keys_backup_setup_step3_generating_key_status), - progress, - total) - } - }, - object : MatrixCallback<MegolmBackupCreationInfo> { - override fun onSuccess(data: MegolmBackupCreationInfo) { - if (requestedId != currentRequestId.value) { - // this is an old request, we can't cancel but we can ignore - return - } - recoveryKey.value = data.recoveryKey - megolmBackupCreationInfo = data - copyHasBeenMade = false - - val keyBackup = session.cryptoService().keysBackupService() - createKeysBackup(context, keyBackup) - } - - override fun onFailure(failure: Throwable) { - if (requestedId != currentRequestId.value) { - // this is an old request, we can't cancel but we can ignore - return - } - - loadingStatus.value = null - - isCreatingBackupVersion.value = false - prepareRecoverFailError.value = failure - } - }) + loadingStatus.postValue(null) + isCreatingBackupVersion.postValue(false) + prepareRecoverFailError.postValue(failure) + } } } @@ -140,9 +121,11 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() { } fun stopAndKeepAfterDetectingExistingOnServer() { - loadingStatus.value = null - navigateEvent.value = LiveEvent(NAVIGATE_FINISH) - session.cryptoService().keysBackupService().checkAndStartKeysBackup() + loadingStatus.postValue(null) + navigateEvent.postValue(LiveEvent(NAVIGATE_FINISH)) + viewModelScope.launch { + session.cryptoService().keysBackupService().checkAndStartKeysBackup() + } } private fun createKeysBackup(context: Context, keysBackup: KeysBackupService, forceOverride: Boolean = false) { @@ -150,45 +133,35 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() { creatingBackupError.value = null - keysBackup.getCurrentVersion(object : MatrixCallback<KeysVersionResult?> { - override fun onSuccess(data: KeysVersionResult?) { + viewModelScope.launch { + try { + val data = keysBackup.getCurrentVersion() if (data?.version.isNullOrBlank() || forceOverride) { - processOnCreate() + processOnCreate(keysBackup) } else { - loadingStatus.value = null + loadingStatus.postValue(null) // we should prompt - isCreatingBackupVersion.value = false - navigateEvent.value = LiveEvent(NAVIGATE_PROMPT_REPLACE) + isCreatingBackupVersion.postValue(false) + navigateEvent.postValue(LiveEvent(NAVIGATE_PROMPT_REPLACE)) } + } catch (failure: Throwable) { } + } + } - override fun onFailure(failure: Throwable) { - Timber.e(failure, "## createKeyBackupVersion") - loadingStatus.value = null + suspend fun processOnCreate(keysBackup: KeysBackupService) { + try { + loadingStatus.postValue(null) + val created = keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!) + isCreatingBackupVersion.postValue(false) + keysVersion.postValue(created) + navigateEvent.value = LiveEvent(NAVIGATE_TO_STEP_3) + } catch (failure: Throwable) { + Timber.e(failure, "## createKeyBackupVersion") + loadingStatus.postValue(null) - isCreatingBackupVersion.value = false - creatingBackupError.value = failure - } - - fun processOnCreate() { - keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!, object : MatrixCallback<KeysVersion> { - override fun onSuccess(data: KeysVersion) { - loadingStatus.value = null - - isCreatingBackupVersion.value = false - keysVersion.value = data - navigateEvent.value = LiveEvent(NAVIGATE_TO_STEP_3) - } - - override fun onFailure(failure: Throwable) { - Timber.e(failure, "## createKeyBackupVersion") - loadingStatus.value = null - - isCreatingBackupVersion.value = false - creatingBackupError.value = failure - } - }) - } - }) + isCreatingBackupVersion.postValue(false) + creatingBackupError.postValue(failure) + } } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt index 74bab9b0b6..a962dbcf6c 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt @@ -20,7 +20,9 @@ import im.vector.app.R import im.vector.app.core.platform.ViewModelTask import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.resources.StringProvider -import org.matrix.android.sdk.api.NoOpMatrixCallback +import im.vector.app.features.session.coroutineScope +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME @@ -32,7 +34,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding import org.matrix.android.sdk.internal.crypto.keysbackup.deriveKey import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey -import org.matrix.android.sdk.internal.util.awaitCallback import timber.log.Timber import java.util.UUID import javax.inject.Inject @@ -87,9 +88,7 @@ class BackupToQuadSMigrationTask @Inject constructor( reportProgress(params, R.string.bootstrap_progress_compute_curve_key) val recoveryKey = computeRecoveryKey(curveKey) - val isValid = awaitCallback<Boolean> { - keysBackupService.isValidRecoveryKeyForCurrentVersion(recoveryKey, it) - } + val isValid = keysBackupService.isValidRecoveryKeyForCurrentVersion(recoveryKey) if (!isValid) return Result.InvalidRecoverySecret @@ -141,14 +140,17 @@ class BackupToQuadSMigrationTask @Inject constructor( keysBackupService.saveBackupRecoveryKey(recoveryKey, version.version) // while we are there let's restore, but do not block - session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( - version, - recoveryKey, - null, - null, - null, - NoOpMatrixCallback() - ) + session.coroutineScope.launch { + tryOrNull { + session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( + version, + recoveryKey, + null, + null, + null + ) + } + } return Result.Success } catch (failure: Throwable) { 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 cc863346aa..de627e1f78 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 @@ -33,9 +33,6 @@ import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageServi import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey import org.matrix.android.sdk.internal.util.awaitCallback import timber.log.Timber @@ -221,9 +218,7 @@ class BootstrapCrossSigningTask @Inject constructor( Timber.d("## BootstrapCrossSigningTask: Creating 4S - Checking megolm backup") // First ensure that in sync - var serverVersion = awaitCallback<KeysVersionResult?> { - session.cryptoService().keysBackupService().getCurrentVersion(it) - } + var serverVersion = session.cryptoService().keysBackupService().getCurrentVersion() val knownMegolmSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() val isMegolmBackupSecretKnown = knownMegolmSecret != null && knownMegolmSecret.version == serverVersion?.version @@ -233,21 +228,14 @@ class BootstrapCrossSigningTask @Inject constructor( if (shouldCreateKeyBackup) { // clear all existing backups while (serverVersion != null) { - awaitCallback<Unit> { - session.cryptoService().keysBackupService().deleteBackup(serverVersion!!.version, it) - } - serverVersion = awaitCallback { - session.cryptoService().keysBackupService().getCurrentVersion(it) - } + session.cryptoService().keysBackupService().deleteBackup(serverVersion.version) + serverVersion = session.cryptoService().keysBackupService().getCurrentVersion() } Timber.d("## BootstrapCrossSigningTask: Creating 4S - Create megolm backup") - val creationInfo = awaitCallback<MegolmBackupCreationInfo> { - session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it) - } - val version = awaitCallback<KeysVersion> { - session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it) - } + val creationInfo = session.cryptoService().keysBackupService().prepareKeysBackupVersion(null) + val version = session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo) + // Save it for gossiping Timber.d("## BootstrapCrossSigningTask: Creating 4S - Save megolm backup key for gossiping") session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version) @@ -264,12 +252,10 @@ class BootstrapCrossSigningTask @Inject constructor( // ensure we store existing backup secret if we have it! if (isMegolmBackupSecretKnown) { // check it matches - val isValid = awaitCallback<Boolean> { - session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownMegolmSecret!!.recoveryKey, it) - } + val isValid = session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownMegolmSecret!!.recoveryKey) if (isValid) { Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key valid and known") - extractCurveKeyFromRecoveryKey(knownMegolmSecret!!.recoveryKey)?.toBase64NoPadding()?.let { secret -> + extractCurveKeyFromRecoveryKey(knownMegolmSecret.recoveryKey)?.toBase64NoPadding()?.let { secret -> ssssService.storeSecret( KEYBACKUP_SECRET_SSSS_NAME, secret, diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt index f75ab634b8..1e7eab64e7 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt @@ -42,14 +42,13 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth -import org.matrix.android.sdk.internal.util.awaitCallback import java.io.OutputStream import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -104,9 +103,8 @@ class BootstrapSharedViewModel @AssistedInject constructor( // We need to check if there is an existing backup viewModelScope.launch(Dispatchers.IO) { - val version = awaitCallback<KeysVersionResult?> { - session.cryptoService().keysBackupService().getCurrentVersion(it) - } + val version = tryOrNull { session.cryptoService().keysBackupService().getCurrentVersion() } + if (version == null) { // we just resume plain bootstrap doesKeyBackupExist = false @@ -115,8 +113,8 @@ class BootstrapSharedViewModel @AssistedInject constructor( } } else { // we need to get existing backup passphrase/key and convert to SSSS - val keyVersion = awaitCallback<KeysVersionResult?> { - session.cryptoService().keysBackupService().getVersion(version.version, it) + val keyVersion = tryOrNull { + session.cryptoService().keysBackupService().getVersion(version.version) } if (keyVersion == null) { // strange case... just finish? diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt index 03940e89e2..ee3bbe8f31 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -31,9 +31,10 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME @@ -52,10 +53,7 @@ import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey -import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult -import org.matrix.android.sdk.internal.util.awaitCallback import timber.log.Timber data class VerificationBottomSheetViewState( @@ -372,7 +370,8 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( try { action.cypherData.fromBase64().inputStream().use { ins -> val res = session.loadSecureSecret<Map<String, String>>(ins, action.alias) - val trustResult = session.cryptoService().crossSigningService().checkTrustFromPrivateKeys( + val crossSigningService = session.cryptoService().crossSigningService() + val trustResult = crossSigningService.checkTrustFromPrivateKeys( res?.get(MASTER_KEY_SSSS_NAME), res?.get(USER_SIGNING_KEY_SSSS_NAME), res?.get(SELF_SIGNING_KEY_SSSS_NAME) @@ -380,12 +379,11 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( if (trustResult.isVerified()) { // Sign this device and upload the signature session.sessionParams.deviceId?.let { deviceId -> - session.cryptoService() - .crossSigningService().trustDevice(deviceId, object : MatrixCallback<Unit> { - override fun onFailure(failure: Throwable) { - Timber.w(failure, "Failed to sign my device after recovery") - } - }) + try { + crossSigningService.trustDevice(deviceId) + } catch (failure: Exception) { + Timber.w(failure, "Failed to sign my device after recovery") + } } setState { @@ -420,30 +418,27 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( } private fun tentativeRestoreBackup(res: Map<String, String>?) { - viewModelScope.launch(Dispatchers.IO) { + // on session scope because will happen after viewmodel is cleared + session.coroutineScope.launch { try { val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) ?: return@launch Unit.also { Timber.v("## Keybackup secret not restored from SSSS") } - val version = awaitCallback<KeysVersionResult?> { - session.cryptoService().keysBackupService().getCurrentVersion(it) - } ?: return@launch + val version = tryOrNull { session.cryptoService().keysBackupService().getCurrentVersion() } + ?: return@launch - awaitCallback<ImportRoomKeysResult> { - session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( - version, - computeRecoveryKey(secret.fromBase64()), - null, - null, - null, - it - ) - } + // TODO maybe mark as trusted earlier by checking recovery key early, then download? - awaitCallback<Unit> { - session.cryptoService().keysBackupService().trustKeysBackupVersion(version, true, it) - } + session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( + version, + computeRecoveryKey(secret.fromBase64()), + null, + null, + null + ) + + session.cryptoService().keysBackupService().trustKeysBackupVersion(version, true) } catch (failure: Throwable) { // Just ignore for now Timber.e(failure, "## Failed to restore backup after SSSS recovery") diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt index c8b595a5ed..b99d1f0aff 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt @@ -261,9 +261,7 @@ class DevicesViewModel @AssistedInject constructor( viewModelScope.launch { if (state.hasAccountCrossSigning) { try { - awaitCallback<Unit> { - session.cryptoService().crossSigningService().trustDevice(action.cryptoDeviceInfo.deviceId, it) - } + session.cryptoService().crossSigningService().trustDevice(action.cryptoDeviceInfo.deviceId) } catch (failure: Throwable) { Timber.e("Failed to manually cross sign device ${action.cryptoDeviceInfo.deviceId} : ${failure.localizedMessage}") _viewEvents.post(DevicesViewEvents.Failure(failure)) diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt index 8fb5b27376..ded42e23e0 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt @@ -155,7 +155,9 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS fun refreshRemoteStateIfNeeded() { if (keysBackupState.value == KeysBackupState.Disabled) { - session.cryptoService().keysBackupService().checkAndStartKeysBackup() + viewModelScope.launch { + session.cryptoService().keysBackupService().checkAndStartKeysBackup() + } } } diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt index 4daaef6fe1..0fbbb3812e 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt @@ -73,7 +73,9 @@ class SignoutCheckViewModel @AssistedInject constructor( init { session.cryptoService().keysBackupService().addListener(this) - session.cryptoService().keysBackupService().checkAndStartKeysBackup() + viewModelScope.launch { + session.cryptoService().keysBackupService().checkAndStartKeysBackup() + } val quad4SIsSetup = session.sharedSecretStorageService.isRecoverySetup() val allKeysKnown = session.cryptoService().crossSigningService().allPrivateKeysKnown() @@ -112,7 +114,9 @@ class SignoutCheckViewModel @AssistedInject constructor( fun refreshRemoteStateIfNeeded() = withState { state -> if (state.keysBackupState == KeysBackupState.Disabled) { - session.cryptoService().keysBackupService().checkAndStartKeysBackup() + viewModelScope.launch { + session.cryptoService().keysBackupService().checkAndStartKeysBackup() + } } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index dec9e6e8db..6bec52f1b1 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2041,6 +2041,7 @@ <string name="keys_backup_restoring_computing_key_waiting_message">Computing recovery key…</string> <string name="keys_backup_restoring_downloading_backup_waiting_message">Downloading keys…</string> <string name="keys_backup_restoring_importing_keys_waiting_message">Importing keys…</string> + <string name="keys_backup_restoring_decrypting_keys_waiting_message">Decrypting keys…</string> <string name="keys_backup_unlock_button">Unlock History</string> <string name="keys_backup_recovery_code_empty_error_message">Please enter a recovery key</string> <string name="keys_backup_recovery_code_error_decrypt">Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key.</string> @@ -3442,6 +3443,7 @@ <string name="command_description_join_space">Join the Space with the given id</string> <string name="command_description_leave_room">Leave room with given id (or current room if null)</string> <string name="command_description_upgrade_room">Upgrades a room to a new version</string> + <string name="command_description_gen_keys">Gen keys</string> <string name="event_status_a11y_sending">Sending</string> <string name="event_status_a11y_sent">Sent</string>