diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt index dce0a58278..612b551ef2 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt @@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_S import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME +import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey @@ -322,7 +323,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { return MegolmBackupCreationInfo( algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, authData = createFakeMegolmBackupAuthData(), - recoveryKey = "fake" + recoveryKey = BackupRecoveryKey.fromBase58("3cnTdW") ) } @@ -518,7 +519,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { // Save it for gossiping session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version) - extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret -> + extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey.toBase58())?.toBase64NoPadding()?.let { secret -> ssssService.storeSecret( KEYBACKUP_SECRET_SSSS_NAME, secret, diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt index 74420d01b7..8aa1f37e3e 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt @@ -597,6 +597,7 @@ class E2eeSanityTests : InstrumentedTest { // for the test we just accept? oldCode = sasTx.getDecimalCodeRepresentation() testHelper.runBlockingTest { + delay(500) sasTx.userHasVerifiedShortCode() } } @@ -623,11 +624,12 @@ class E2eeSanityTests : InstrumentedTest { val sasTx = tx as SasVerificationTransaction when (sasTx.state) { VerificationTxState.ShortCodeReady -> { + newCode = sasTx.getDecimalCodeRepresentation() if (matchOnce) { testHelper.runBlockingTest { + delay(500) sasTx.userHasVerifiedShortCode() } - newCode = sasTx.getDecimalCodeRepresentation() matchOnce = false } } @@ -669,6 +671,7 @@ class E2eeSanityTests : InstrumentedTest { aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() != null } } + testHelper.runBlockingTest { assertEquals( "MSK Private parts should be the same", 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 1e600c7588..ecca618158 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 @@ -34,6 +34,7 @@ import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import org.matrix.android.sdk.api.listeners.StepProgressListener +import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey 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.api.session.crypto.keysbackup.toKeysVersionResult @@ -505,7 +506,7 @@ class KeysBackupTest : InstrumentedTest { try { testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey( testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, - "Bad recovery key" + BackupRecoveryKey.fromBase58("Bad recovery key") ) fail("Should have failed to trust") } catch (failure: Throwable) { @@ -645,7 +646,7 @@ class KeysBackupTest : InstrumentedTest { var importRoomKeysResult: ImportRoomKeysResult? = null testHelper.runBlockingTest { testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, - "EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d", + BackupRecoveryKey.fromBase58("EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d"), null, null, null diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt index a484730949..693167893c 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.verification import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -40,10 +41,9 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper -import org.matrix.android.sdk.internal.crypto.model.rest.toValue import timber.log.Timber import java.util.concurrent.CountDownLatch @@ -322,8 +322,6 @@ class SASTest : InstrumentedTest { */ } - - // any two devices may only have at most one key verification in flight at a time. // If a device has two verifications in progress with the same device, then it should cancel both verifications. @Test @@ -449,7 +447,8 @@ class SASTest : InstrumentedTest { aliceSession.cryptoService().verificationService().beginKeyVerification(VerificationMethod.SAS, bobSession.myUserId, transactionId) } testHelper.await(latch) - val aliceTx = aliceSession.cryptoService().verificationService().getExistingTransaction(bobSession.myUserId, transactionId) as SasVerificationTransaction + val aliceTx = + aliceSession.cryptoService().verificationService().getExistingTransaction(bobSession.myUserId, transactionId) as SasVerificationTransaction val bobTx = bobSession.cryptoService().verificationService().getExistingTransaction(aliceSession.myUserId, transactionId) as SasVerificationTransaction assertEquals("Should have same SAS", aliceTx.getDecimalCodeRepresentation(), bobTx.getDecimalCodeRepresentation()) @@ -470,62 +469,71 @@ class SASTest : InstrumentedTest { val aliceVerificationService = aliceSession.cryptoService().verificationService() val bobVerificationService = bobSession!!.cryptoService().verificationService() - val aliceSASLatch = CountDownLatch(1) + val verifiedLatch = CountDownLatch(2) val aliceListener = object : VerificationService.Listener { override fun verificationRequestUpdated(pr: PendingVerificationRequest) { Timber.v("RequestUpdated pr=$pr") } - var matchOnce = true + var matched = false + var verified = false override fun transactionUpdated(tx: VerificationTransaction) { - Timber.v("Alice transactionUpdated: ${tx.state}") + Timber.v("Alice transactionUpdated: ${tx.state} on thread:${Thread.currentThread()}") if (tx !is SasVerificationTransaction) return when (tx.state) { - VerificationTxState.ShortCodeReady -> testHelper.runBlockingTest { - tx.userHasVerifiedShortCode() - } - VerificationTxState.Verified -> { - if (matchOnce) { - matchOnce = false - aliceSASLatch.countDown() + VerificationTxState.ShortCodeReady -> testHelper.runBlockingTest { + if (!matched) { + matched = true + delay(500) + tx.userHasVerifiedShortCode() } } - else -> Unit + VerificationTxState.Verified -> { + if (!verified) { + verified = true + verifiedLatch.countDown() + } + } + else -> Unit } } } aliceVerificationService.addListener(aliceListener) - val bobSASLatch = CountDownLatch(1) val bobListener = object : VerificationService.Listener { - var acceptOnce = true - var matchOnce = true + var accepted = false + var matched = false + var verified = false override fun verificationRequestUpdated(pr: PendingVerificationRequest) { Timber.v("RequestUpdated: pr=$pr") } override fun transactionUpdated(tx: VerificationTransaction) { - Timber.v("Bob transactionUpdated: ${tx.state}") + Timber.v("Bob transactionUpdated: ${tx.state} on thread: ${Thread.currentThread()}") if (tx !is SasVerificationTransaction) return when (tx.state) { - VerificationTxState.OnStarted -> testHelper.runBlockingTest { - if (acceptOnce) { - acceptOnce = false + VerificationTxState.OnStarted -> testHelper.runBlockingTest { + if (!accepted) { + accepted = true tx.acceptVerification() } } - VerificationTxState.ShortCodeReady -> testHelper.runBlockingTest { - if (matchOnce) { - matchOnce = false + VerificationTxState.ShortCodeReady -> testHelper.runBlockingTest { + if (!matched) { + matched = true + delay(500) tx.userHasVerifiedShortCode() } } - VerificationTxState.ShortCodeAccepted -> { - bobSASLatch.countDown() + VerificationTxState.Verified -> { + if (!verified) { + verified = true + verifiedLatch.countDown() + } } - else -> Unit + else -> Unit } } } @@ -538,8 +546,8 @@ class SASTest : InstrumentedTest { testHelper.runBlockingTest { aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, transactionId) } - testHelper.await(aliceSASLatch) - testHelper.await(bobSASLatch) + Timber.v("Await after beginKey ${Thread.currentThread()}") + testHelper.await(verifiedLatch) // Assert that devices are verified val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = testHelper.runBlockingTest { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/BackupRecoveryKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/BackupRecoveryKey.kt new file mode 100644 index 0000000000..87a4c933b2 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/BackupRecoveryKey.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.crypto.keysbackup + +import uniffi.olm.BackupRecoveryKey as InnerBackupRecoveryKey + +class BackupRecoveryKey internal constructor(internal val inner: InnerBackupRecoveryKey) { + + constructor() : this(InnerBackupRecoveryKey()) + + companion object { + + fun fromBase58(key: String): BackupRecoveryKey { + val inner = InnerBackupRecoveryKey.fromBase58(key) + return BackupRecoveryKey(inner) + } + + fun fromBase64(key: String): BackupRecoveryKey { + val inner = InnerBackupRecoveryKey.fromBase64(key) + return BackupRecoveryKey(inner) + } + + fun fromPassphrase(passphrase: String, salt: String, rounds: Int): BackupRecoveryKey { + val inner = InnerBackupRecoveryKey.fromPassphrase(passphrase, salt, rounds) + return BackupRecoveryKey(inner) + } + + fun newFromPassphrase(passphrase: String): BackupRecoveryKey { + val inner = InnerBackupRecoveryKey.newFromPassphrase(passphrase) + return BackupRecoveryKey(inner) + } + } + + override fun equals(other: Any?): Boolean { + if (other !is BackupRecoveryKey) return false + return this.toBase58() == other.toBase58() + } + + fun toBase58() = inner.toBase58() + + fun toBase64() = inner.toBase64() + + fun decryptV1(ephemeralKey: String, mac: String, ciphertext: String) = inner.decryptV1(ephemeralKey, mac, ciphertext) + + fun megolmV1PublicKey() = inner.megolmV1PublicKey() +} 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 eea0c60451..bd684dd90d 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 @@ -142,8 +142,7 @@ interface KeysBackupService { * @param keysBackupVersion the backup version to check. * @param recoveryKey the recovery key to challenge with the key backup public key. */ - suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, - recoveryKey: String) + suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, recoveryKey: BackupRecoveryKey) /** * Set trust on a keys backup version. @@ -167,7 +166,7 @@ interface KeysBackupService { * @param callback Callback. It provides the number of found keys and the number of successfully imported keys. */ suspend fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, - recoveryKey: String, roomId: String?, + recoveryKey: BackupRecoveryKey, roomId: String?, sessionId: String?, stepProgressListener: StepProgressListener?): ImportRoomKeysResult @@ -194,10 +193,10 @@ interface KeysBackupService { val state: KeysBackupState // For gossiping - fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) + fun saveBackupRecoveryKey(recoveryKey: BackupRecoveryKey?, version: String?) suspend fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? - suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean + suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: BackupRecoveryKey): Boolean fun computePrivateKey(passphrase: String, privateKeySalt: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupCreationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupCreationInfo.kt index 0d708b8d73..8761665c6e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupCreationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupCreationInfo.kt @@ -31,7 +31,7 @@ data class MegolmBackupCreationInfo( val authData: MegolmBackupAuthData, /** - * The Base58 recovery key. + * The recovery key. */ - val recoveryKey: String + val recoveryKey: BackupRecoveryKey ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/SavedKeyBackupKeyInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/SavedKeyBackupKeyInfo.kt index 7f90fea9af..ee063eda51 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/SavedKeyBackupKeyInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/SavedKeyBackupKeyInfo.kt @@ -17,6 +17,6 @@ package org.matrix.android.sdk.api.session.crypto.keysbackup data class SavedKeyBackupKeyInfo( - val recoveryKey: String, + val recoveryKey: BackupRecoveryKey, val version: String ) 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 8a207628fc..f6ff6e9a60 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 @@ -65,7 +65,7 @@ import org.matrix.android.sdk.api.session.sync.model.DeviceListResponse import org.matrix.android.sdk.api.session.sync.model.DeviceOneTimeKeysCountSyncResponse import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService -import org.matrix.android.sdk.internal.crypto.network.NetworkRequestsProcessor +import org.matrix.android.sdk.internal.crypto.network.OutgoingRequestsProcessor import org.matrix.android.sdk.internal.crypto.network.RequestSender import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore @@ -131,7 +131,7 @@ internal class DefaultCryptoService @Inject constructor( private val olmMachine by lazy { olmMachineProvider.olmMachine } - private val outgoingRequestsProcessor = NetworkRequestsProcessor( + private val outgoingRequestsProcessor = OutgoingRequestsProcessor( requestSender = requestSender, coroutineScope = cryptoCoroutineScope, cryptoSessionInfoProvider = cryptoSessionInfoProvider, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PerSessionBackupQueryRateLimiter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PerSessionBackupQueryRateLimiter.kt index 8fcdb4168b..10ac0cced0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PerSessionBackupQueryRateLimiter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PerSessionBackupQueryRateLimiter.kt @@ -20,10 +20,9 @@ import dagger.Lazy import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.logger.LoggerTag +import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo -import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult -import org.matrix.android.sdk.api.util.awaitCallback import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.util.time.Clock @@ -101,12 +100,12 @@ internal class PerSessionBackupQueryRateLimiter @Inject constructor( (now - lastTry.timestamp) > MIN_TRY_BACKUP_PERIOD_MILLIS if (!shouldQuery) return false - + val recoveryKey = savedKeyBackupKeyInfo?.recoveryKey ?: return false val successfullyImported = withContext(coroutineDispatchers.io) { try { keysBackupService.get().restoreKeysWithRecoveryKey( currentVersion, - savedKeyBackupKeyInfo?.recoveryKey ?: "", + recoveryKey, roomId, sessionId, null, 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 37ef4ea29b..54ca9be784 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 @@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.listeners.StepProgressListener +import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState @@ -60,7 +61,6 @@ import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.util.JsonCanonicalizer import org.matrix.olm.OlmException import timber.log.Timber -import uniffi.olm.BackupRecoveryKey import uniffi.olm.Request import uniffi.olm.RequestType import java.security.InvalidParameterException @@ -150,7 +150,7 @@ internal class RustKeyBackupService @Inject constructor( MegolmBackupCreationInfo( algorithm = publicKey.backupAlgorithm, authData = signedMegolmBackupAuthData, - recoveryKey = key.toBase58() + recoveryKey = key ) } } @@ -189,9 +189,11 @@ internal class RustKeyBackupService @Inject constructor( } } - override fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) { + override fun saveBackupRecoveryKey(recoveryKey: BackupRecoveryKey?, version: String?) { cryptoCoroutineScope.launch { - olmMachine.saveRecoveryKey(recoveryKey, version) + val recoveryKeyStr = recoveryKey?.toBase64() + // TODO : change rust API to use BackupRecoveryKey + olmMachine.saveRecoveryKey(recoveryKeyStr, version) } } @@ -312,7 +314,8 @@ internal class RustKeyBackupService @Inject constructor( val body = UpdateKeysBackupVersionBody( algorithm = keysBackupVersion.algorithm, authData = newAuthData.copy(signatures = newSignatures).toJsonDict(), - version = keysBackupVersion.version) + version = keysBackupVersion.version + ) withContext(coroutineDispatchers.io) { sender.updateBackup(keysBackupVersion, body) @@ -353,14 +356,13 @@ internal class RustKeyBackupService @Inject constructor( } } - override suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, recoveryKey: String) { + override suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, recoveryKey: BackupRecoveryKey) { Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}") 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) + checkRecoveryKey(recoveryKey, keysBackupVersion) trustKeysBackupVersion(keysBackupVersion, true) } } @@ -378,7 +380,7 @@ internal class RustKeyBackupService @Inject constructor( withContext(coroutineDispatchers.crypto) { try { val version = sender.getKeyBackupLastVersion()?.toKeysVersionResult() - + Timber.v("Keybackup version: $version") if (version != null) { val key = BackupRecoveryKey.fromBase64(secret) if (isValidRecoveryKey(key, version)) { @@ -395,7 +397,9 @@ internal class RustKeyBackupService @Inject constructor( } } // we can save, it's valid - saveBackupRecoveryKey(key.toBase64(), version.version) + saveBackupRecoveryKey(key, version.version) + } else { + Timber.d("Invalid recovery key") } } else { Timber.e("onSecretKeyGossip: Failed to import backup recovery key, no backup version was found on the server") @@ -480,7 +484,7 @@ internal class RustKeyBackupService @Inject constructor( } // Save for next time and for gossiping - saveBackupRecoveryKey(recoveryKey.toBase64(), keysVersionResult.version) + saveBackupRecoveryKey(recoveryKey, keysVersionResult.version) } withContext(coroutineDispatchers.main) { @@ -519,14 +523,18 @@ internal class RustKeyBackupService @Inject constructor( stepProgressListener?.onStepProgress(stepProgress) } - Timber.v("restoreKeysWithRecoveryKey: Decrypted ${sessionsData.size} keys out" + - " of ${data.roomIdToRoomKeysBackupData.size} rooms from the backup store on the homeserver") + Timber.v( + "restoreKeysWithRecoveryKey: Decrypted ${sessionsData.size} keys out" + + " 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 if (backUp) { - Timber.v("restoreKeysWithRecoveryKey: Those keys will be backed up" + - " to backup version: ${keysBackupVersion?.version}") + Timber.v( + "restoreKeysWithRecoveryKey: Those keys will be backed up" + + " to backup version: ${keysBackupVersion?.version}" + ) } // Import them into the crypto store @@ -557,15 +565,12 @@ internal class RustKeyBackupService @Inject constructor( } override suspend fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, - recoveryKey: String, + recoveryKey: BackupRecoveryKey, roomId: String?, sessionId: String?, stepProgressListener: StepProgressListener?): ImportRoomKeysResult { Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}") - - val key = BackupRecoveryKey.fromBase58(recoveryKey) - - return restoreBackup(keysVersionResult, key, roomId, sessionId, stepProgressListener) + return restoreBackup(keysVersionResult, recoveryKey, roomId, sessionId, stepProgressListener) } override suspend fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, @@ -577,7 +582,6 @@ internal class RustKeyBackupService @Inject constructor( val recoveryKey = withContext(coroutineDispatchers.crypto) { recoveryKeyFromPassword(password, keysBackupVersion) } - return restoreBackup(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener) } @@ -703,16 +707,15 @@ internal class RustKeyBackupService @Inject constructor( private fun isValidRecoveryKey(recoveryKey: BackupRecoveryKey, version: KeysVersionResult): Boolean { val publicKey = recoveryKey.megolmV1PublicKey().publicKey val authData = getMegolmBackupAuthData(version) ?: return false + Timber.v("recoveryKey.megolmV1PublicKey().publicKey $publicKey == getMegolmBackupAuthData(version).publicKey ${authData.publicKey}") return authData.publicKey == publicKey } - override suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean { + override suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: BackupRecoveryKey): Boolean { return withContext(coroutineDispatchers.crypto) { val keysBackupVersion = keysBackupVersion ?: return@withContext false - - val key = BackupRecoveryKey.fromBase64(recoveryKey) try { - isValidRecoveryKey(key, keysBackupVersion) + isValidRecoveryKey(recoveryKey, keysBackupVersion) } catch (failure: Throwable) { Timber.i("isValidRecoveryKeyForCurrentVersion: Invalid recovery key") false @@ -726,7 +729,9 @@ internal class RustKeyBackupService @Inject constructor( override suspend fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? { val info = olmMachine.getBackupKeys() ?: return null - return SavedKeyBackupKeyInfo(info.recoveryKey, info.backupVersion) + // TODO change rust ffi to return BackupRecoveryKey instead of base64 string + val backupRecoveryKey = BackupRecoveryKey.fromBase64(info.recoveryKey) + return SavedKeyBackupKeyInfo(backupRecoveryKey, info.backupVersion) } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/network/NetworkRequestsProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/network/OutgoingRequestsProcessor.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/network/NetworkRequestsProcessor.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/network/OutgoingRequestsProcessor.kt index 85c4bc8821..f06a30e18a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/network/NetworkRequestsProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/network/OutgoingRequestsProcessor.kt @@ -34,10 +34,10 @@ import uniffi.olm.RequestType private val loggerTag = LoggerTag("OutgoingRequestsProcessor", LoggerTag.CRYPTO) -internal class NetworkRequestsProcessor(private val requestSender: RequestSender, - private val coroutineScope: CoroutineScope, - private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, - private val shieldComputer: ShieldComputer,) { +internal class OutgoingRequestsProcessor(private val requestSender: RequestSender, + private val coroutineScope: CoroutineScope, + private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, + private val shieldComputer: ShieldComputer,) { fun interface ShieldComputer { suspend fun compute(userIds: List): RoomEncryptionTrustLevel diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index d5750a2e2a..c7a0b5abf1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo +import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo @@ -469,7 +470,8 @@ internal class RealmCryptoStore @Inject constructor( val key = it.keyBackupRecoveryKey val version = it.keyBackupRecoveryKeyVersion if (!key.isNullOrBlank() && !version.isNullOrBlank()) { - SavedKeyBackupKeyInfo(recoveryKey = key, version = version) + val backupRecoveryKey = BackupRecoveryKey.fromBase58(key) + SavedKeyBackupKeyInfo(recoveryKey = backupRecoveryKey, version = version) } else { null } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index fb1aac6a51..fe6368364b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -520,10 +520,9 @@ internal class RoomSyncHandler @Inject constructor( private fun decryptIfNeeded(event: Event, roomId: String) { try { - // Event from sync does not have roomId, so add it to the event first // note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching val result = runBlocking { - cryptoService.decryptEvent(event.copy(roomId = roomId), "") + cryptoService.decryptEvent(event, "") } event.mxDecryptionResult = OlmDecryptionResult( payload = result.clearEvent, diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt index a0a6a138dc..6e96d7c857 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt @@ -23,6 +23,7 @@ import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.resources.StringProvider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey import javax.inject.Inject class KeysBackupRestoreFromKeyViewModel @Inject constructor( @@ -42,7 +43,7 @@ class KeysBackupRestoreFromKeyViewModel @Inject constructor( sharedViewModel.loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading))) recoveryCodeErrorText.value = null viewModelScope.launch(Dispatchers.IO) { - val recoveryKey = recoveryCode.value!! + val recoveryKey = BackupRecoveryKey.fromBase58(recoveryCode.value!!) try { sharedViewModel.recoverUsingBackupRecoveryKey(recoveryKey) } catch (failure: Throwable) { 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 4c577d0a15..7fc8105dab 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 @@ -31,6 +31,7 @@ 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 +import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey @@ -88,7 +89,7 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( private val progressObserver = object : StepProgressListener { override fun onStepProgress(step: StepProgressListener.Step) { when (step) { - is StepProgressListener.Step.ComputingKey -> { + is StepProgressListener.Step.ComputingKey -> { loadingEvent.postValue( WaitingViewData( stringProvider.getString(R.string.keys_backup_restoring_waiting_message) + @@ -107,7 +108,7 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( ) ) } - is StepProgressListener.Step.ImportingKey -> { + is StepProgressListener.Step.ImportingKey -> { Timber.d("backupKeys.ImportingKey.progress: ${step.progress}") // Progress 0 can take a while, display an indeterminate progress in this case if (step.progress == 0) { @@ -131,14 +132,22 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( } 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)) + 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)) + 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 + ) + ) } } } @@ -170,7 +179,7 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( ) // Go and use it!! try { - recoverUsingBackupRecoveryKey(computeRecoveryKey(savedSecret.recoveryKey.fromBase64()), version) + recoverUsingBackupRecoveryKey(savedSecret.recoveryKey, version) } catch (failure: Throwable) { Timber.e(failure, "## recoverUsingBackupRecoveryKey FAILED") keySourceModel.postValue( @@ -212,7 +221,9 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( viewModelScope.launch(Dispatchers.IO) { try { - recoverUsingBackupRecoveryKey(computeRecoveryKey(secret.fromBase64())) + val computedRecoveryKey = computeRecoveryKey(secret.fromBase64()) + val backupRecoveryKey = BackupRecoveryKey.fromBase58(computedRecoveryKey) + recoverUsingBackupRecoveryKey(backupRecoveryKey) } catch (failure: Throwable) { _navigateEvent.postValue( LiveEvent(NAVIGATE_FAILED_TO_LOAD_4S) @@ -234,7 +245,8 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading))) try { - val result = keysBackup.restoreKeyBackupWithPassword(keyVersion, + val result = keysBackup.restoreKeyBackupWithPassword( + keyVersion, passphrase, null, session.myUserId, @@ -249,7 +261,7 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( } } - suspend fun recoverUsingBackupRecoveryKey(recoveryKey: String, keyVersion: KeysVersionResult? = null) { + suspend fun recoverUsingBackupRecoveryKey(recoveryKey: BackupRecoveryKey, keyVersion: KeysVersionResult? = null) { val keysBackup = session.cryptoService().keysBackupService() // This is badddddd val version = keyVersion ?: keyVersionResult.value ?: return @@ -257,7 +269,8 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading))) try { - val result = keysBackup.restoreKeysWithRecoveryKey(version, + val result = keysBackup.restoreKeysWithRecoveryKey( + version, recoveryKey, null, session.myUserId, 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 5209e5edc3..62f5e305e9 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 @@ -27,6 +27,7 @@ import im.vector.app.core.time.Clock import im.vector.app.core.utils.LiveEvent import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo @@ -71,7 +72,7 @@ class KeysBackupSetupSharedViewModel @Inject constructor( // Step 3 // Var to ignore events from previous request(s) to generate a recovery key private var currentRequestId: MutableLiveData = MutableLiveData() - var recoveryKey: MutableLiveData = MutableLiveData(null) + var recoveryKey: MutableLiveData = MutableLiveData(null) var prepareRecoverFailError: MutableLiveData = MutableLiveData(null) var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null var copyHasBeenMade = false diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt index 61148f3aab..fa8725c943 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt @@ -66,7 +66,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment(R.id.keys_backup_recovery_key_text)?.let { it.isVisible = true - it.text = recoveryKey.replace(" ", "") + it.text = recoveryKey.toBase58() + .replace(" ", "") .chunked(16) .joinToString("\n") { it @@ -123,7 +124,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment + extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey.toBase58())?.toBase64NoPadding()?.let { secret -> ssssService.storeSecret( KEYBACKUP_SECRET_SSSS_NAME, secret, @@ -251,7 +251,7 @@ class BootstrapCrossSigningTask @Inject constructor( 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.toBase58())?.toBase64NoPadding()?.let { secret -> ssssService.storeSecret( KEYBACKUP_SECRET_SSSS_NAME, secret, 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 4910c74e59..9f66080111 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 @@ -37,6 +37,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NA import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified +import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult import org.matrix.android.sdk.api.session.crypto.verification.CancelCode @@ -431,9 +432,10 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( val version = session.cryptoService().keysBackupService().getCurrentVersion()?.toKeysVersionResult() ?: return@launch val recoveryKey = computeRecoveryKey(secret.fromBase64()) - val isValid = session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(recoveryKey) + val backupRecoveryKey = BackupRecoveryKey.fromBase58(recoveryKey) + val isValid = session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(backupRecoveryKey) if (isValid) { - session.cryptoService().keysBackupService().saveBackupRecoveryKey(recoveryKey, version.version) + session.cryptoService().keysBackupService().saveBackupRecoveryKey(backupRecoveryKey, version.version) } session.cryptoService().keysBackupService().trustKeysBackupVersion(version, true) } catch (failure: Throwable) { @@ -502,6 +504,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( } override fun transactionUpdated(tx: VerificationTransaction) = withState { state -> + Timber.v("transactionUpdated: $tx") handleTransactionUpdate(state, tx) } @@ -510,7 +513,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( } override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state -> - + Timber.v("VerificationRequestUpdated: $pr") if (state.selfVerificationMode && state.pendingRequest.invoke() == null && state.transactionId == null) { // is this an incoming with that user if (pr.isIncoming && pr.otherUserId == state.otherUserMxItem?.id) {