diff --git a/library/rustCrypto/matrix-rust-sdk-crypto.aar b/library/rustCrypto/matrix-rust-sdk-crypto.aar index a645eb775f..2b7a3a32e8 100644 --- a/library/rustCrypto/matrix-rust-sdk-crypto.aar +++ b/library/rustCrypto/matrix-rust-sdk-crypto.aar @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f303e8830bb4bd7005b2a166118d7771ed07259822ebb6f888abb0ed459f0cc -size 50458247 +oid sha256:a5b58eecbbaa8354901a57c7df727eb2ad6e401dd286ecf049d7a438788ac6ef +size 16077430 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 0c2873b675..8dde36cfec 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 @@ -17,6 +17,14 @@ package org.matrix.android.sdk.common import android.util.Log +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.cancellable +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch import org.amshove.kluent.fail import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull @@ -38,8 +46,13 @@ 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.model.OlmDecryptionResult import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState +import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest +import org.matrix.android.sdk.api.session.crypto.verification.SasTransactionState import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod +import org.matrix.android.sdk.api.session.crypto.verification.dbgState +import org.matrix.android.sdk.api.session.crypto.verification.getRequest +import org.matrix.android.sdk.api.session.crypto.verification.getTransaction import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom @@ -359,99 +372,153 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { } suspend fun verifySASCrossSign(alice: Session, bob: Session, roomId: String) { + val scope = CoroutineScope(SupervisorJob()) + assertTrue(alice.cryptoService().crossSigningService().canCrossSign()) assertTrue(bob.cryptoService().crossSigningService().canCrossSign()) val aliceVerificationService = alice.cryptoService().verificationService() val bobVerificationService = bob.cryptoService().verificationService() - val localId = UUID.randomUUID().toString() + val bobSeesVerification = CompletableDeferred() + scope.launch(Dispatchers.IO) { + bobVerificationService.requestEventFlow() + .cancellable() + .collect { + val request = it.getRequest() + if (request != null) { + bobSeesVerification.complete(request) + return@collect cancel() + } + } + } + + val aliceReady = CompletableDeferred() + scope.launch(Dispatchers.IO) { + aliceVerificationService.requestEventFlow() + .cancellable() + .collect { + val request = it.getRequest() + if (request?.state == EVerificationState.Ready) { + aliceReady.complete(request) + return@collect cancel() + } + } + } + val bobReady = CompletableDeferred() + scope.launch(Dispatchers.IO) { + bobVerificationService.requestEventFlow() + .cancellable() + .collect { + val request = it.getRequest() + if (request?.state == EVerificationState.Ready) { + bobReady.complete(request) + return@collect cancel() + } + } + } + val requestID = aliceVerificationService.requestKeyVerificationInDMs( - localId = localId, methods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), otherUserId = bob.myUserId, roomId = roomId ).transactionId - testHelper.retryWithBackoff( - onFail = { - fail("Bob should see an incoming request from alice") - } - ) { - bobVerificationService.getExistingVerificationRequests(alice.myUserId).firstOrNull { - it.otherDeviceId == alice.sessionParams.deviceId - } != null - } - - val incomingRequest = bobVerificationService.getExistingVerificationRequests(alice.myUserId).first { - it.otherDeviceId == alice.sessionParams.deviceId - } - - Timber.v("#TEST Incoming request is $incomingRequest") - - Timber.v("#TEST let bob ready the verification with SAS method") + bobSeesVerification.await() bobVerificationService.readyPendingVerification( listOf(VerificationMethod.SAS), alice.myUserId, - incomingRequest.transactionId + requestID ) + aliceReady.await() + bobReady.await() - // wait for it to be readied - testHelper.retryWithBackoff( - onFail = { - fail("Alice should see the verification in ready state") - } - ) { - val outgoingRequest = aliceVerificationService.getExistingVerificationRequest(bob.myUserId, requestID) - outgoingRequest?.state == EVerificationState.Ready + val bobCode = CompletableDeferred() + + scope.launch(Dispatchers.IO) { + bobVerificationService.requestEventFlow() + .cancellable() + .collect { + val transaction = it.getTransaction() + Timber.d("#TEST flow ${bob.myUserId.take(5)} ${transaction?.transactionId}|${transaction?.dbgState()}") + val tx = transaction as? SasVerificationTransaction + if (tx?.state() == SasTransactionState.SasShortCodeReady) { + bobCode.complete(tx) + return@collect cancel() + } + if (it.getRequest()?.state == EVerificationState.Cancelled) { + bobCode.completeExceptionally(AssertionError("Request as been cancelled")) + return@collect cancel() + } + } + } + + val aliceCode = CompletableDeferred() + + scope.launch(Dispatchers.IO) { + aliceVerificationService.requestEventFlow() + .cancellable() + .collect { + val transaction = it.getTransaction() + Timber.d("#TEST flow ${alice.myUserId.take(5)} ${transaction?.transactionId}|${transaction?.dbgState()}") + val tx = transaction as? SasVerificationTransaction + if (tx?.state() == SasTransactionState.SasShortCodeReady) { + aliceCode.complete(tx) + return@collect cancel() + } + if (it.getRequest()?.state == EVerificationState.Cancelled) { + aliceCode.completeExceptionally(AssertionError("Request as been cancelled")) + return@collect cancel() + } + } } Timber.v("#TEST let alice start the verification") - aliceVerificationService.startKeyVerification( + val id = aliceVerificationService.startKeyVerification( VerificationMethod.SAS, bob.myUserId, requestID, ) + Timber.v("#TEST alice started: $id") - // we should reach SHOW SAS on both - var alicePovTx: SasVerificationTransaction? = null - var bobPovTx: SasVerificationTransaction? = null + val bobTx = bobCode.await() + val aliceTx = aliceCode.await() + assertEquals("SAS code do not match", aliceTx.getDecimalCodeRepresentation()!!, bobTx.getDecimalCodeRepresentation()) - testHelper.retryWithBackoff( - onFail = { - fail("Alice should should see a verification code") - } - ) { - alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID) - as? SasVerificationTransaction - Log.v("TEST", "== alicePovTx id:${requestID} is ${alicePovTx?.state()}") - alicePovTx?.getDecimalCodeRepresentation() != null + val aliceDone = CompletableDeferred() + scope.launch(Dispatchers.IO) { + aliceVerificationService.requestEventFlow() + .cancellable() + .collect { + val request = it.getRequest() + if (request?.state == EVerificationState.Done) { + aliceDone.complete(Unit) + return@collect cancel() + } + } } - // wait for alice to get the ready - testHelper.retryWithBackoff( - onFail = { - fail("Bob should should see a verification code") - } - ) { - bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) - as? SasVerificationTransaction - Log.v("TEST", "== bobPovTx is ${bobPovTx?.state()}") -// bobPovTx?.state == VerificationTxState.ShortCodeReady - bobPovTx?.getDecimalCodeRepresentation() != null + val bobDone = CompletableDeferred() + scope.launch(Dispatchers.IO) { + bobVerificationService.requestEventFlow() + .cancellable() + .collect { + val request = it.getRequest() + if (request?.state == EVerificationState.Done) { + bobDone.complete(Unit) + return@collect cancel() + } + } } + bobTx.userHasVerifiedShortCode() + aliceTx.userHasVerifiedShortCode() - assertEquals("SAS code do not match", alicePovTx!!.getDecimalCodeRepresentation(), bobPovTx!!.getDecimalCodeRepresentation()) + bobDone.await() + aliceDone.await() - bobPovTx!!.userHasVerifiedShortCode() - alicePovTx!!.userHasVerifiedShortCode() + alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId) + bob.cryptoService().crossSigningService().isUserTrusted(alice.myUserId) - testHelper.retryWithBackoff { - alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId) - } - - testHelper.retryWithBackoff { - bob.cryptoService().crossSigningService().isUserTrusted(alice.myUserId) - } + scope.cancel() } suspend fun doE2ETestWithManyMembers(numberOfMembers: Int): CryptoTestData { 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 da6ff8fe25..472446ca9d 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 @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto import android.util.Log import androidx.test.filters.LargeTest +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.delay import org.amshove.kluent.fail import org.amshove.kluent.internal.assertEquals @@ -44,6 +45,8 @@ import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.api.session.room.timeline.Timeline +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest @@ -91,7 +94,7 @@ class E2eeSanityTests : InstrumentedTest { Log.v("#E2E TEST", "Alice is sending the message") val text = "This is my message" - val sentEventId: String? = sendMessageInRoom(testHelper, aliceRoomPOV, text) + val sentEventId: String? = sendMessageInRoom(aliceRoomPOV, text) Assert.assertTrue("Message should be sent", sentEventId != null) // All should be able to decrypt @@ -140,7 +143,7 @@ class E2eeSanityTests : InstrumentedTest { Log.v("#E2E TEST", "Alice sends a new message") val secondMessage = "2 This is my message" - val secondSentEventId: String? = sendMessageInRoom(testHelper, aliceRoomPOV, secondMessage) + val secondSentEventId: String? = sendMessageInRoom(aliceRoomPOV, secondMessage) // new members should be able to decrypt it newAccount.forEach { otherSession -> @@ -203,7 +206,7 @@ class E2eeSanityTests : InstrumentedTest { val sentEventIds = mutableListOf() val messagesText = listOf("1. Hello", "2. Bob", "3. Good morning") messagesText.forEach { text -> - val sentEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!.also { + val sentEventId = sendMessageInRoom(aliceRoomPOV, text)!!.also { sentEventIds.add(it) } @@ -318,7 +321,7 @@ class E2eeSanityTests : InstrumentedTest { Log.v("#E2E TEST", "Alice sends some messages") messagesText.forEach { text -> - val sentEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!.also { + val sentEventId = sendMessageInRoom(aliceRoomPOV, text)!!.also { sentEventIds.add(it) } @@ -342,45 +345,24 @@ class E2eeSanityTests : InstrumentedTest { Log.v("#E2E TEST", "Create a new session for Bob") val newBobSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true)) + // ensure first session is aware of the new one + bobSession.cryptoService().downloadKeysIfNeeded(listOf(bobSession.myUserId), true) + // check that new bob can't currently decrypt Log.v("#E2E TEST", "check that new bob can't currently decrypt") cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null) // Try to request - - Log.v("#E2E TEST", "Let bob re-request") - sentEventIds.forEach { sentEventId -> - val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root - newBobSession.cryptoService().reRequestRoomKeyForEvent(event) - } - - // Ensure that new bob still can't decrypt (keys must have been withheld) +// +// Log.v("#E2E TEST", "Let bob re-request") // sentEventIds.forEach { sentEventId -> -// val megolmSessionId = newBobSession.getRoom(e2eRoomID)!! -// .getTimelineEvent(sentEventId)!! -// .root.content.toModel()!!.sessionId -// testHelper.retryPeriodically { -// val aliceReply = newBobSession.cryptoService().getOutgoingRoomKeyRequests() -// .first { -// it.sessionId == megolmSessionId && -// it.roomId == e2eRoomID -// } -// .results.also { -// Log.w("##TEST", "result list is $it") -// } -// .firstOrNull { it.userId == aliceSession.myUserId } -// ?.result -// aliceReply != null && -// aliceReply is RequestResult.Failure && -// WithHeldCode.UNAUTHORISED == aliceReply.code -// } +// val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root +// newBobSession.cryptoService().reRequestRoomKeyForEvent(event) // } - -// */ - - Log.v("#E2E TEST", "Should not be able to decrypt as not verified") - cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null) +// +// Log.v("#E2E TEST", "Should not be able to decrypt as not verified") +// cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null) // Now mark new bob session as verified @@ -422,7 +404,7 @@ class E2eeSanityTests : InstrumentedTest { Log.v("#E2E TEST", "Alice sends some messages") firstMessage.let { text -> - firstEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!! + firstEventId = sendMessageInRoom(aliceRoomPOV, text)!! testHelper.retryWithBackoff { val timeLineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId) @@ -448,7 +430,7 @@ class E2eeSanityTests : InstrumentedTest { Log.v("#E2E TEST", "Alice sends some messages") secondMessage.let { text -> - secondEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!! + secondEventId = sendMessageInRoom(aliceRoomPOV, text)!! testHelper.retryWithBackoff { val timeLineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId) @@ -511,26 +493,30 @@ class E2eeSanityTests : InstrumentedTest { } } - private suspend fun sendMessageInRoom(testHelper: CommonTestHelper, aliceRoomPOV: Room, text: String): String? { - var sentEventId: String? = null + private suspend fun sendMessageInRoom(aliceRoomPOV: Room, text: String): String? { aliceRoomPOV.sendService().sendTextMessage(text) val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60)) timeline.start() - testHelper.retryWithBackoff { - val decryptedMsg = timeline.getSnapshot() - .filter { it.root.getClearType() == EventType.MESSAGE } - .also { list -> - val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" } - Log.v("#E2E TEST", "Timeline snapshot is $message") - } - .filter { it.root.sendState == SendState.SYNCED } - .firstOrNull { it.root.getClearContent().toModel()?.body?.startsWith(text) == true } - sentEventId = decryptedMsg?.eventId - decryptedMsg != null - } - timeline.dispose() - return sentEventId + + val messageSent = CompletableDeferred() + timeline.addListener(object : Timeline.Listener { + override fun onTimelineUpdated(snapshot: List) { + val decryptedMsg = timeline.getSnapshot() + .filter { it.root.getClearType() == EventType.MESSAGE } + .also { list -> + val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" } + Log.v("#E2E TEST", "Timeline snapshot is $message") + } + .filter { it.root.sendState == SendState.SYNCED } + .firstOrNull { it.root.getClearContent().toModel()?.body?.startsWith(text) == true } + if (decryptedMsg != null) { + timeline.dispose() + messageSent.complete(decryptedMsg.eventId) + } + } + }) + return messageSent.await() } /** @@ -646,7 +632,7 @@ class E2eeSanityTests : InstrumentedTest { val roomFromAlicePOV = aliceSession.getRoom(cryptoTestData.roomId)!! Timber.v("#TEST: Send a first message that should be withheld") - val sentEvent = sendMessageInRoom(testHelper, roomFromAlicePOV, "Hello")!! + val sentEvent = sendMessageInRoom(roomFromAlicePOV, "Hello")!! // wait for it to be synced back the other side Timber.v("#TEST: Wait for message to be synced back") @@ -669,7 +655,7 @@ class E2eeSanityTests : InstrumentedTest { Timber.v("#TEST: Send a second message, outbound session should have rotated and only bob 1rst session should decrypt") - val secondEvent = sendMessageInRoom(testHelper, roomFromAlicePOV, "World")!! + val secondEvent = sendMessageInRoom(roomFromAlicePOV, "World")!! Timber.v("#TEST: Wait for message to be synced back") testHelper.retryWithBackoff { bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationTransaction.kt index ecd8f87898..774924fc7d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationTransaction.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.api.session.crypto.verification +import org.matrix.android.sdk.internal.crypto.verification.qrcode.QrCodeVerification + interface VerificationTransaction { val method: VerificationMethod @@ -38,3 +40,11 @@ interface VerificationTransaction { fun isSuccessful(): Boolean } + +internal fun VerificationTransaction.dbgState(): String? { + return when (this) { + is SasVerificationTransaction -> "${this.state()}" + is QrCodeVerification -> "${this.state()}" + else -> "??" + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EncryptEventContentUseCase.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EncryptEventContentUseCase.kt deleted file mode 100644 index 2fe3c3f55b..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EncryptEventContentUseCase.kt +++ /dev/null @@ -1,46 +0,0 @@ -// /* -// * Copyright (c) 2022 The Matrix.org Foundation C.I.C. -// * -// * Licensed under the Apache License, Version 2.0 (the "License"); -// * you may not use this file except in compliance with the License. -// * You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, software -// * distributed under the License is distributed on an "AS IS" BASIS, -// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// * See the License for the specific language governing permissions and -// * limitations under the License. -// */ -// -// package org.matrix.android.sdk.internal.crypto -// -// import org.matrix.android.sdk.api.logger.LoggerTag -// import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult -// import org.matrix.android.sdk.api.session.events.model.Content -// import org.matrix.android.sdk.api.session.events.model.EventType -// import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction -// import org.matrix.android.sdk.internal.util.time.Clock -// import timber.log.Timber -// import javax.inject.Inject -// -// private val loggerTag = LoggerTag("EncryptEventContentUseCase", LoggerTag.CRYPTO) -// -// internal class EncryptEventContentUseCase @Inject constructor( -// private val olmDevice: MXOlmDevice, -// private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, -// private val clock: Clock) { -// -// suspend operator fun invoke( -// eventContent: Content, -// eventType: String, -// roomId: String): MXEncryptEventContentResult { -// val t0 = clock.epochMillis() -// ensureOlmSessionsForDevicesAction.handle() -// prepareToEncrypt(roomId, ensureAllMembersAreLoaded = false) -// val content = olmMachine.encrypt(roomId, eventType, eventContent) -// Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms") -// return MXEncryptEventContentResult(content, EventType.ENCRYPTED) -// } -// } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationListenersHolder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationListenersHolder.kt index f26214ba34..3b47d908f3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationListenersHolder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationListenersHolder.kt @@ -22,45 +22,50 @@ import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest import org.matrix.android.sdk.api.session.crypto.verification.VerificationEvent import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction +import org.matrix.android.sdk.api.session.crypto.verification.dbgState +import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.SessionScope import timber.log.Timber import javax.inject.Inject @SessionScope internal class VerificationListenersHolder @Inject constructor( - private val coroutineDispatchers: MatrixCoroutineDispatchers + coroutineDispatchers: MatrixCoroutineDispatchers, + @UserId myUserId: String, ) { + val myUserId = myUserId.take(5) + val scope = CoroutineScope(SupervisorJob() + coroutineDispatchers.dmVerif) val eventFlow = MutableSharedFlow(extraBufferCapacity = 20, onBufferOverflow = BufferOverflow.SUSPEND) fun dispatchTxAdded(tx: VerificationTransaction) { scope.launch { + Timber.v("## SAS [$myUserId] dispatchTxAdded txId:${tx.transactionId} | ${tx.dbgState()}") eventFlow.emit(VerificationEvent.TransactionAdded(tx)) } } fun dispatchTxUpdated(tx: VerificationTransaction) { scope.launch { - Timber.v("## SAS dispatchTxUpdated txId:${tx.transactionId} $tx") + Timber.v("## SAS [$myUserId] dispatchTxUpdated txId:${tx.transactionId} | ${tx.dbgState()}") eventFlow.emit(VerificationEvent.TransactionUpdated(tx)) } } - fun dispatchRequestAdded(verificationRequest: PendingVerificationRequest) { + fun dispatchRequestAdded(verificationRequest: VerificationRequest) { scope.launch { - Timber.v("## SAS dispatchRequestAdded txId:${verificationRequest.transactionId} $verificationRequest") - eventFlow.emit(VerificationEvent.RequestAdded(verificationRequest)) + Timber.v("## SAS [$myUserId] dispatchRequestAdded txId:${verificationRequest.flowId()} state:${verificationRequest.innerState()}") + eventFlow.emit(VerificationEvent.RequestAdded(verificationRequest.toPendingVerificationRequest())) } } - fun dispatchRequestUpdated(verificationRequest: PendingVerificationRequest) { - Timber.v("## SAS dispatchRequestUpdated txId:${verificationRequest.transactionId} $verificationRequest") + fun dispatchRequestUpdated(verificationRequest: VerificationRequest) { scope.launch { - eventFlow.emit(VerificationEvent.RequestUpdated(verificationRequest)) + Timber.v("## SAS [$myUserId] dispatchRequestUpdated txId:${verificationRequest.flowId()} state:${verificationRequest.innerState()}") + eventFlow.emit(VerificationEvent.RequestUpdated(verificationRequest.toPendingVerificationRequest())) } } } diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/EncryptEventContentUseCase.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/EncryptEventContentUseCase.kt index 0b1b751c79..ed53b8a289 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/EncryptEventContentUseCase.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/EncryptEventContentUseCase.kt @@ -20,6 +20,8 @@ import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber import javax.inject.Inject @@ -36,9 +38,24 @@ internal class EncryptEventContentUseCase @Inject constructor( eventType: String, roomId: String): MXEncryptEventContentResult { val t0 = clock.epochMillis() - prepareToEncrypt(roomId, ensureAllMembersAreLoaded = false) + + /** + * When using in-room messages and the room has encryption enabled, + * clients should ensure that encryption does not hinder the verification. + * For example, if the verification messages are encrypted, clients must ensure that all the recipient’s + * unverified devices receive the keys necessary to decrypt the messages, + * even if they would normally not be given the keys to decrypt messages in the room. + */ + val shouldSendToUnverified = isVerificationEvent(eventType, eventContent) + + prepareToEncrypt(roomId, ensureAllMembersAreLoaded = false, forceDistributeToUnverified = shouldSendToUnverified) val content = olmMachine.encrypt(roomId, eventType, eventContent) Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms") return MXEncryptEventContentResult(content, EventType.ENCRYPTED) } + + private fun isVerificationEvent(eventType: String, eventContent: Content) = + EventType.isVerificationEvent(eventType) || + (eventType == EventType.MESSAGE && + eventContent.get(MessageContent.MSG_TYPE_JSON_KEY) == MessageType.MSGTYPE_VERIFICATION_REQUEST) } diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt index fc33a44ac9..36e0dcdd8f 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt @@ -298,15 +298,19 @@ internal class OlmMachine @Inject constructor( return ToDeviceSyncResponse(events = response) } +// +// suspend fun receiveUnencryptedVerificationEvent(roomId: String, event: Event) = withContext(coroutineDispatchers.io) { +// val adapter = moshi +// .adapter(Event::class.java) +// val serializedEvent = adapter.toJson(event) +// inner.receiveUnencryptedVerificationEvent(serializedEvent, roomId) +// } - suspend fun receiveUnencryptedVerificationEvent(roomId: String, event: Event) = withContext(coroutineDispatchers.io) { + suspend fun receiveVerificationEvent(roomId: String, event: Event) = withContext(coroutineDispatchers.io) { val adapter = moshi - .newBuilder() - .add(CheckNumberType.JSON_ADAPTER_FACTORY) - .build() .adapter(Event::class.java) val serializedEvent = adapter.toJson(event) - inner.receiveUnencryptedVerificationEvent(serializedEvent, roomId) + inner.receiveVerificationEvent(serializedEvent, roomId) } /** diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/PrepareToEncryptUseCase.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/PrepareToEncryptUseCase.kt index bd55257a44..c54509fb19 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/PrepareToEncryptUseCase.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/PrepareToEncryptUseCase.kt @@ -57,7 +57,7 @@ internal class PrepareToEncryptUseCase @Inject constructor( private val keyClaimLock: Mutex = Mutex() private val roomKeyShareLocks: ConcurrentHashMap = ConcurrentHashMap() - suspend operator fun invoke(roomId: String, ensureAllMembersAreLoaded: Boolean) { + suspend operator fun invoke(roomId: String, ensureAllMembersAreLoaded: Boolean, forceDistributeToUnverified: Boolean = false) { withContext(coroutineDispatchers.crypto) { Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date") // Ensure to load all room members @@ -76,7 +76,7 @@ internal class PrepareToEncryptUseCase @Inject constructor( Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason") throw IllegalArgumentException("Missing algorithm") } - preshareRoomKey(roomId, userIds) + preshareRoomKey(roomId, userIds, forceDistributeToUnverified) } } @@ -84,7 +84,7 @@ internal class PrepareToEncryptUseCase @Inject constructor( return cryptoStore.getRoomAlgorithm(roomId) } - private suspend fun preshareRoomKey(roomId: String, roomMembers: List) { + private suspend fun preshareRoomKey(roomId: String, roomMembers: List, forceDistributeToUnverified: Boolean) { claimMissingKeys(roomMembers) val keyShareLock = roomKeyShareLocks.getOrPut(roomId) { Mutex() } var sharedKey = false @@ -97,7 +97,12 @@ internal class PrepareToEncryptUseCase @Inject constructor( } val settings = EncryptionSettings( algorithm = EventEncryptionAlgorithm.MEGOLM_V1_AES_SHA2, - onlyAllowTrustedDevices = info.blacklistUnverifiedDevices, + onlyAllowTrustedDevices = if (forceDistributeToUnverified) { + false + } else { + cryptoStore.getGlobalBlacklistUnverifiedDevices() || + info.blacklistUnverifiedDevices + }, rotationPeriod = info.rotationPeriodMs.toULong(), rotationPeriodMsgs = info.rotationPeriodMsgs.toULong(), historyVisibility = if (info.shouldShareHistory) { diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt index 693ea14ba2..e3b89fd0bd 100755 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt @@ -109,7 +109,7 @@ private val loggerTag = LoggerTag("RustCryptoService", LoggerTag.CRYPTO) @SessionScope internal class RustCryptoService @Inject constructor( - @UserId private val userId: String, + @UserId private val myUserId: String, @DeviceId private val deviceId: String, // the crypto store private val cryptoStore: IMXCryptoStore, @@ -167,7 +167,7 @@ internal class RustCryptoService @Inject constructor( val params = SetDeviceNameTask.Params(deviceId, deviceName) setDeviceNameTask.execute(params) try { - downloadKeysIfNeeded(listOf(userId), true) + downloadKeysIfNeeded(listOf(myUserId), true) } catch (failure: Throwable) { Timber.tag(loggerTag.value).w(failure, "setDeviceName: Failed to refresh of crypto device") } @@ -257,7 +257,7 @@ internal class RustCryptoService @Inject constructor( setRustLogger() Timber.tag(loggerTag.value).v( "## CRYPTO | Successfully started up an Olm machine for " + - "$userId, $deviceId, identity keys: ${this.olmMachine.identityKeys()}" + "$myUserId, $deviceId, identity keys: ${this.olmMachine.identityKeys()}" ) } catch (throwable: Throwable) { Timber.tag(loggerTag.value).v("Failed create an Olm machine: $throwable") @@ -342,7 +342,7 @@ internal class RustCryptoService @Inject constructor( } override fun getLiveCryptoDeviceInfo(): LiveData> { - return getLiveCryptoDeviceInfo(listOf(userId)) + return getLiveCryptoDeviceInfo(listOf(myUserId)) } override fun getLiveCryptoDeviceInfo(userId: String): LiveData> { @@ -350,8 +350,8 @@ internal class RustCryptoService @Inject constructor( } override fun getLiveCryptoDeviceInfo(userIds: List): LiveData> { - return olmMachine.getLiveDevices(listOf(userId)).map { - it.filter { it.userId == userId } + return olmMachine.getLiveDevices(listOf(myUserId)).map { + it.filter { it.userId == myUserId } } } @@ -609,7 +609,7 @@ internal class RustCryptoService @Inject constructor( // Notify the our listeners about room keys so decryption is retried. toDeviceEvents.events.orEmpty().forEach { event -> - Timber.tag(loggerTag.value).d("Processed ToDevice event msgid:${event.toDeviceTracingId()} id:${event.eventId} type:${event.type}") + Timber.tag(loggerTag.value).d("[${myUserId.take(7)}|${deviceId}] Processed ToDevice event msgid:${event.toDeviceTracingId()} id:${event.eventId} type:${event.type}") if (event.getClearType() == EventType.ENCRYPTED) { // rust failed to decrypt it @@ -832,7 +832,7 @@ internal class RustCryptoService @Inject constructor( * ========================================================================================== */ override fun toString(): String { - return "DefaultCryptoService of $userId ($deviceId)" + return "DefaultCryptoService of $myUserId ($deviceId)" } override fun getOutgoingRoomKeyRequests(): List { diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/network/RequestSender.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/network/RequestSender.kt index cef0629971..483fb98b31 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/network/RequestSender.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/network/RequestSender.kt @@ -66,8 +66,10 @@ import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask import org.matrix.android.sdk.internal.crypto.tasks.UploadSigningKeysTask import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.DEFAULT_REQUEST_RETRY_COUNT import org.matrix.android.sdk.internal.network.parsing.CheckNumberType +import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository import org.matrix.android.sdk.internal.session.room.send.SendResponse import org.matrix.rustcomponents.sdk.crypto.OutgoingVerificationRequest import org.matrix.rustcomponents.sdk.crypto.Request @@ -77,6 +79,8 @@ import timber.log.Timber import javax.inject.Inject internal class RequestSender @Inject constructor( + @UserId + private val myUserId: String, private val sendToDeviceTask: SendToDeviceTask, private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask, private val uploadKeysTask: UploadKeysTask, @@ -94,8 +98,9 @@ internal class RequestSender @Inject constructor( private val getRoomSessionsDataTask: GetRoomSessionsDataTask, private val getRoomSessionDataTask: GetRoomSessionDataTask, private val moshi: Moshi, - private val cryptoCoroutineScope: CoroutineScope, + cryptoCoroutineScope: CoroutineScope, private val rateLimiter: PerSessionBackupQueryRateLimiter, + private val localEchoRepository: LocalEchoRepository ) { private val scope = CoroutineScope( @@ -136,7 +141,13 @@ internal class RequestSender @Inject constructor( } private suspend fun sendRoomMessage(request: OutgoingVerificationRequest.InRoom, retryCount: Int): SendResponse { - return sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId, retryCount) + return sendRoomMessage( + eventType = request.eventType, + roomId = request.roomId, + content = request.content, + transactionId = request.requestId, + retryCount = retryCount + ) } suspend fun sendRoomMessage(request: Request.RoomMessage, retryCount: Int = DEFAULT_REQUEST_RETRY_COUNT): String { @@ -153,7 +164,13 @@ internal class RequestSender @Inject constructor( ): SendResponse { val paramsAdapter = moshi.adapter(Map::class.java) val jsonContent = paramsAdapter.fromJson(content) - val event = Event(eventType, transactionId, jsonContent, roomId = roomId) + val event = Event( + senderId = myUserId, + type = eventType, + eventId = transactionId, + content = jsonContent, + roomId = roomId) + localEchoRepository.createLocalEcho(event) val params = SendVerificationMessageTask.Params(event, retryCount) return sendVerificationMessageTask.get().execute(params) } diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/RustVerificationService.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/RustVerificationService.kt index 3ba71a006d..acb8fcbac7 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/RustVerificationService.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/RustVerificationService.kt @@ -80,19 +80,25 @@ internal class RustVerificationService @Inject constructor( * All verification related events should be forwarded through this method to * the verification service. * - * If the verification event is not encrypted it should be provided to the olmMachine. - * Otherwise events are at this point already handled by the rust-sdk through the receival - * of the to-device events and the decryption of room events. In this case this method mainly just - * fetches the appropriate rust object that will be created or updated by the event and + * This method mainly just fetches the appropriate rust object that will be created or updated by the event and * dispatches updates to our listeners. */ internal suspend fun onEvent(roomId: String?, event: Event) { - if (roomId != null && !event.isEncrypted()) { + if (roomId != null && event.unsignedData?.transactionId == null) { if (isVerificationEvent(event)) { try { - olmMachine.receiveUnencryptedVerificationEvent(roomId, event) + val clearEvent = if (event.isEncrypted()) { + event.copy( + content = event.getDecryptedContent(), + type = event.getDecryptedType(), + roomId = roomId + ) + } else { + event + } + olmMachine.receiveVerificationEvent(roomId, clearEvent) } catch (failure: Throwable) { - Timber.w(failure, "Failed to receiveUnencryptedVerificationEvent") + Timber.w(failure, "Failed to receiveUnencryptedVerificationEvent ${failure.message}") } } } @@ -111,8 +117,8 @@ internal class RustVerificationService @Inject constructor( } private fun isVerificationEvent(event: Event): Boolean { - val eventType = event.type ?: return false - val eventContent = event.content ?: return false + val eventType = event.getClearType() + val eventContent = event.getClearContent() ?: return false return EventType.isVerificationEvent(eventType) || (eventType == EventType.MESSAGE && eventContent[MessageContent.MSG_TYPE_JSON_KEY] == MessageType.MSGTYPE_VERIFICATION_REQUEST) @@ -127,22 +133,23 @@ internal class RustVerificationService @Inject constructor( /** Dispatch updates after a verification event has been received */ private suspend fun onUpdate(event: Event) { + Timber.v("[${olmMachine.userId().take(6)}] Verification on event ${event.getClearType()}") val sender = event.senderId ?: return - val flowId = getFlowId(event) ?: return + val flowId = getFlowId(event) ?: return Unit.also { + Timber.w("onUpdate for unknown flowId senderId ${event.getClearType()}") + } val verificationRequest = olmMachine.getVerificationRequest(sender, flowId) if (event.getClearType() == EventType.KEY_VERIFICATION_READY) { + // we start the qr here in order to display the code verificationRequest?.startQrCode() } - verificationRequest?.dispatchRequestUpdated() - val verification = getExistingTransaction(sender, flowId) ?: return - verificationListenersHolder.dispatchTxUpdated(verification) } /** Check if the start event created new verification objects and dispatch updates */ private suspend fun onStart(event: Event) { if (event.unsignedData?.transactionId != null) return // remote echo - Timber.w("VALR onStart $event") + Timber.w("VALR onStart ${event.eventId}") val sender = event.senderId ?: return val flowId = getFlowId(event) ?: return @@ -167,7 +174,6 @@ internal class RustVerificationService @Inject constructor( Timber.d("## Verification: start for $sender") // update the request as the start updates it's state - request.dispatchRequestUpdated() verificationListenersHolder.dispatchTxUpdated(transaction) } else { // This didn't originate from a request, so tell our listeners that @@ -187,19 +193,11 @@ internal class RustVerificationService @Inject constructor( event.getClearContent().toModel()?.transactionId } ?: return val sender = event.senderId ?: return - val request = getExistingVerificationRequest(sender, flowId) ?: return + val request = olmMachine.getVerificationRequest(sender, flowId) ?: return verificationListenersHolder.dispatchRequestAdded(request) } -// override fun addListener(listener: VerificationService.Listener) { -// verificationListenersHolder.addListener(listener) -// } -// -// override fun removeListener(listener: VerificationService.Listener) { -// verificationListenersHolder.removeListener(listener) -// } - override suspend fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) { olmMachine.getDevice(userId, deviceID)?.markAsTrusted() } @@ -269,14 +267,14 @@ internal class RustVerificationService @Inject constructor( roomId: String, localId: String? ): PendingVerificationRequest { - olmMachine.ensureUsersKeys(listOf(otherUserId)) + Timber.w("verification: requestKeyVerificationInDMs in room $roomId with $otherUserId") + olmMachine.ensureUsersKeys(listOf(otherUserId), true) val verification = when (val identity = olmMachine.getIdentity(otherUserId)) { is UserIdentity -> identity.requestVerification(methods, roomId, localId!!) is OwnUserIdentity -> throw IllegalArgumentException("This method doesn't support verification of our own user") null -> throw IllegalArgumentException("The user that we wish to verify doesn't support cross signing") } - Timber.w("##VALR requestKeyVerificationInDMs ${verification.flowId()} > $verification") return verification.toPendingVerificationRequest() } @@ -319,13 +317,15 @@ internal class RustVerificationService @Inject constructor( override suspend fun startKeyVerification(method: VerificationMethod, otherUserId: String, requestId: String): String? { return if (method == VerificationMethod.SAS) { val request = olmMachine.getVerificationRequest(otherUserId, requestId) + ?: throw IllegalArgumentException("Unknown request with id: $requestId") - val sas = request?.startSasVerification() + val sas = request.startSasVerification() if (sas != null) { verificationListenersHolder.dispatchTxAdded(sas) sas.transactionId } else { + Timber.w("Failed to start verification with method $method") null } } else { @@ -338,7 +338,6 @@ internal class RustVerificationService @Inject constructor( ?: return null val qrVerification = matchingRequest.scanQrCode(scannedData) ?: return null - verificationListenersHolder.dispatchRequestUpdated(matchingRequest.toPendingVerificationRequest()) verificationListenersHolder.dispatchTxAdded(qrVerification) return qrVerification.transactionId } diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/SasVerification.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/SasVerification.kt index 8ac566a99c..12ca5ae6e5 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/SasVerification.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/SasVerification.kt @@ -83,20 +83,6 @@ internal class SasVerification @AssistedInject constructor( SasState.Done -> SasTransactionState.Done(true) is SasState.Cancelled -> SasTransactionState.Cancelled(safeValueOf(state.cancelInfo.cancelCode), state.cancelInfo.cancelledByUs) } -// refreshData() -// val cancelInfo = inner.cancelInfo -// -// return when { -// cancelInfo != null -> { -// val cancelCode = safeValueOf(cancelInfo.cancelCode) -// SasTransactionState.Cancelled(cancelCode, cancelInfo.cancelledByUs) -// } -// inner.isDone -> SasTransactionState.Done(true) -// inner.haveWeConfirmed -> SasTransactionState.SasAccepted -// inner.canBePresented -> SasTransactionState.SasShortCodeReady -// inner.hasBeenAccepted -> SasTransactionState.SasAccepted -// else -> SasTransactionState.SasStarted -// } } /** Get the unique id of this verification */ diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/VerificationRequest.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/VerificationRequest.kt index d46a9f5a38..8d1b3392aa 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/VerificationRequest.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/verification/VerificationRequest.kt @@ -38,17 +38,17 @@ import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS import org.matrix.android.sdk.internal.crypto.network.RequestSender import org.matrix.android.sdk.internal.crypto.verification.qrcode.QrCodeVerification import org.matrix.android.sdk.internal.util.time.Clock +import org.matrix.rustcomponents.sdk.crypto.VerificationRequestListener +import org.matrix.rustcomponents.sdk.crypto.VerificationRequestState +import timber.log.Timber import org.matrix.rustcomponents.sdk.crypto.VerificationRequest as InnerVerificationRequest fun InnerVerificationRequest.dbgString(): String { val that = this return buildString { - append("InnerVerificationRequest(") - append("isDone=${that.isDone()},") - append("isReady=${that.isReady()},") - append("isPassive=${that.isPassive()},") - append("weStarted=${that.weStarted()},") - append("isCancelled=${that.isCancelled()}") + append("(") + append("flowId=${that.flowId()}") + append("state=${that.state()},") append(")") } } @@ -69,7 +69,7 @@ internal class VerificationRequest @AssistedInject constructor( private val sasVerificationFactory: SasVerification.Factory, private val qrCodeVerificationFactory: QrCodeVerification.Factory, private val clock: Clock, -) { +) : VerificationRequestListener { private val innerOlmMachine = olmMachine.inner() @@ -78,14 +78,18 @@ internal class VerificationRequest @AssistedInject constructor( fun create(innerVerificationRequest: InnerVerificationRequest): VerificationRequest } + init { + innerVerificationRequest.setChangesListener(this) + } + fun startQrCode() { innerVerificationRequest.startQrVerification() } - internal fun dispatchRequestUpdated() { - val tx = toPendingVerificationRequest() - verificationListenersHolder.dispatchRequestUpdated(tx) - } +// internal fun dispatchRequestUpdated() { +// val tx = toPendingVerificationRequest() +// verificationListenersHolder.dispatchRequestUpdated(tx) +// } /** Get the flow ID of this verification request * @@ -97,6 +101,8 @@ internal class VerificationRequest @AssistedInject constructor( return innerVerificationRequest.flowId() } + fun innerState() = innerVerificationRequest.state() + /** The user ID of the other user that is participating in this verification flow */ internal fun otherUser(): String { return innerVerificationRequest.otherUserId() @@ -108,7 +114,6 @@ internal class VerificationRequest @AssistedInject constructor( * didn't yet accept the verification flow. * */ internal fun otherDeviceId(): String? { - refreshData() return innerVerificationRequest.otherDeviceId() } @@ -132,13 +137,11 @@ internal class VerificationRequest @AssistedInject constructor( * verification. */ internal fun isReady(): Boolean { - refreshData() return innerVerificationRequest.isReady() } /** Did we advertise that we're able to scan QR codes */ internal fun canScanQrCodes(): Boolean { - refreshData() return innerVerificationRequest.ourSupportedMethods()?.contains(VERIFICATION_METHOD_QR_CODE_SCAN) ?: false } @@ -161,12 +164,7 @@ internal class VerificationRequest @AssistedInject constructor( val request = innerVerificationRequest.accept(stringMethods) ?: return // should throw here? try { - dispatchRequestUpdated() requestSender.sendVerificationRequest(request) - -// if (innerVerificationRequest.isReady()) { -// activeQRCode = innerVerificationRequest.startQrVerification() -// } } catch (failure: Throwable) { cancel() } @@ -190,9 +188,9 @@ internal class VerificationRequest @AssistedInject constructor( internal suspend fun startSasVerification(): SasVerification? { return withContext(coroutineDispatchers.io) { val result = innerVerificationRequest.startSasVerification() - ?: return@withContext null -// sasStartResult.request -// val result = innerOlmMachine.startSasVerification(innerVerificationRequest.otherUserId, innerVerificationRequest.flowId) ?: return@withContext null + ?: return@withContext null.also { + Timber.w("Failed to start verification") + } try { requestSender.sendVerificationRequest(result.request) sasVerificationFactory.create(result.sas) @@ -242,69 +240,92 @@ internal class VerificationRequest @AssistedInject constructor( * The method turns into a noop, if the verification flow has already been cancelled. */ internal suspend fun cancel() = withContext(NonCancellable) { - // TODO damir how to add the code? val request = innerVerificationRequest.cancel() ?: return@withContext - dispatchRequestUpdated() tryOrNull("Fail to send cancel request") { requestSender.sendVerificationRequest(request, retryCount = Int.MAX_VALUE) } } - /** Fetch fresh data from the Rust side for our verification flow */ - private fun refreshData() { - val request = innerOlmMachine.getVerificationRequest(innerVerificationRequest.otherUserId(), innerVerificationRequest.flowId()) - - if (request != null) { - innerVerificationRequest = request - } - } - private fun state(): EVerificationState { - if (innerVerificationRequest.isCancelled()) { - return if (innerVerificationRequest.cancelInfo()?.cancelCode == CancelCode.AcceptedByAnotherDevice.value) { - EVerificationState.HandledByOtherSession - } else { - EVerificationState.Cancelled - } - } - if (innerVerificationRequest.isPassive()) { - return EVerificationState.HandledByOtherSession - } - if (innerVerificationRequest.isDone()) { - return EVerificationState.Done - } - - val started = innerOlmMachine.getVerification(otherUser(), flowId()) - if (started != null) { - val asSas = started.asSas() - if (asSas != null) { - return if (asSas.weStarted()) { - EVerificationState.WeStarted + Timber.v("Verification state() ${innerVerificationRequest.state()}") + when (innerVerificationRequest.state()) { + VerificationRequestState.Requested -> { + return if (weStarted()) { + EVerificationState.WaitingForReady } else { - EVerificationState.Started + EVerificationState.Requested } } - val asQR = started.asQr() - if (asQR != null) { -// Timber.w("VALR: weStarted ${asQR.weStarted()}") -// Timber.w("VALR: reciprocated ${asQR.reciprocated()}") -// Timber.w("VALR: isDone ${asQR.isDone()}") -// Timber.w("VALR: hasBeenScanned ${asQR.hasBeenScanned()}") - if (asQR.reciprocated() || asQR.hasBeenScanned()) { - return if (weStarted()) { - EVerificationState.WeStarted - } else EVerificationState.Started + is VerificationRequestState.Ready -> { + val started = innerOlmMachine.getVerification(otherUser(), flowId()) + if (started != null) { + val asSas = started.asSas() + if (asSas != null) { + return if (asSas.weStarted()) { + EVerificationState.WeStarted + } else { + EVerificationState.Started + } + } + val asQR = started.asQr() + if (asQR != null) { + if (asQR.reciprocated() || asQR.hasBeenScanned()) { + return if (weStarted()) { + EVerificationState.WeStarted + } else EVerificationState.Started + } + } + } + return EVerificationState.Ready + } + VerificationRequestState.Done -> { + return EVerificationState.Done + } + is VerificationRequestState.Cancelled -> { + return if (innerVerificationRequest.cancelInfo()?.cancelCode == CancelCode.AcceptedByAnotherDevice.value) { + EVerificationState.HandledByOtherSession + } else { + EVerificationState.Cancelled } } } - if (innerVerificationRequest.isReady()) { - return EVerificationState.Ready - } - return if (weStarted()) { - EVerificationState.WaitingForReady - } else { - EVerificationState.Requested - } +// +// if (innerVerificationRequest.isCancelled()) { +// return if (innerVerificationRequest.cancelInfo()?.cancelCode == CancelCode.AcceptedByAnotherDevice.value) { +// EVerificationState.HandledByOtherSession +// } else { +// EVerificationState.Cancelled +// } +// } +// if (innerVerificationRequest.isPassive()) { +// return EVerificationState.HandledByOtherSession +// } +// if (innerVerificationRequest.isDone()) { +// return EVerificationState.Done +// } +// +// val started = innerOlmMachine.getVerification(otherUser(), flowId()) +// if (started != null) { +// val asSas = started.asSas() +// if (asSas != null) { +// return if (asSas.weStarted()) { +// EVerificationState.WeStarted +// } else { +// EVerificationState.Started +// } +// } +// val asQR = started.asQr() +// if (asQR != null) { +// if (asQR.reciprocated() || asQR.hasBeenScanned()) { +// return if (weStarted()) { +// EVerificationState.WeStarted +// } else EVerificationState.Started +// } +// } +// } +// if (innerVerificationRequest.isReady()) { +// return EVerificationState.Ready +// } } /** Convert the VerificationRequest into a PendingVerificationRequest @@ -317,7 +338,6 @@ internal class VerificationRequest @AssistedInject constructor( * @return The PendingVerificationRequest that matches data from this VerificationRequest. */ internal fun toPendingVerificationRequest(): PendingVerificationRequest { - refreshData() val cancelInfo = innerVerificationRequest.cancelInfo() val cancelCode = if (cancelInfo != null) { @@ -364,6 +384,10 @@ internal class VerificationRequest @AssistedInject constructor( } } + override fun onChange(state: VerificationRequestState) { + verificationListenersHolder.dispatchRequestUpdated(this) + } + override fun toString(): String { return super.toString() + "\n${innerVerificationRequest.dbgString()}" } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/user/UserVerificationViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/user/UserVerificationViewModel.kt index 8940d056cd..da97251b0c 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/user/UserVerificationViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/user/UserVerificationViewModel.kt @@ -331,22 +331,28 @@ class UserVerificationViewModel @AssistedInject constructor( val roomId = session.roomService().getExistingDirectRoomWithUser(initialState.otherUserId) ?: session.roomService().createDirectRoom(initialState.otherUserId) - val request = session.cryptoService().verificationService() - .requestKeyVerificationInDMs( - supportedVerificationMethodsProvider.provide(), - initialState.otherUserId, - roomId, + try { + val request = session.cryptoService().verificationService() + .requestKeyVerificationInDMs( + methods = supportedVerificationMethodsProvider.provide(), + otherUserId = initialState.otherUserId, + roomId = roomId, + ) + + currentTransactionId = request.transactionId + + setState { + copy( + pendingRequest = Success(request), + transactionId = request.transactionId ) - - currentTransactionId = request.transactionId - - Timber.w("VALR started request is $request") - - setState { - copy( - pendingRequest = Success(request), - transactionId = request.transactionId - ) + } + } catch (failure: Throwable) { + setState { + copy( + pendingRequest = Fail(failure), + ) + } } } }