mirror of
https://github.com/element-hq/element-android
synced 2024-11-23 01:45:36 +03:00
Update verification signaling & handing
fix encryption hindering verification
This commit is contained in:
parent
02dc13e38d
commit
3f29c55479
15 changed files with 405 additions and 325 deletions
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3f303e8830bb4bd7005b2a166118d7771ed07259822ebb6f888abb0ed459f0cc
|
||||
size 50458247
|
||||
oid sha256:a5b58eecbbaa8354901a57c7df727eb2ad6e401dd286ecf049d7a438788ac6ef
|
||||
size 16077430
|
||||
|
|
|
@ -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<PendingVerificationRequest>()
|
||||
scope.launch(Dispatchers.IO) {
|
||||
bobVerificationService.requestEventFlow()
|
||||
.cancellable()
|
||||
.collect {
|
||||
val request = it.getRequest()
|
||||
if (request != null) {
|
||||
bobSeesVerification.complete(request)
|
||||
return@collect cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val aliceReady = CompletableDeferred<PendingVerificationRequest>()
|
||||
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<PendingVerificationRequest>()
|
||||
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<SasVerificationTransaction>()
|
||||
|
||||
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<SasVerificationTransaction>()
|
||||
|
||||
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<Unit>()
|
||||
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<Unit>()
|
||||
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 {
|
||||
|
|
|
@ -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<String>()
|
||||
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<EncryptedEventContent>()!!.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<MessageContent>()?.body?.startsWith(text) == true }
|
||||
sentEventId = decryptedMsg?.eventId
|
||||
decryptedMsg != null
|
||||
}
|
||||
timeline.dispose()
|
||||
return sentEventId
|
||||
|
||||
val messageSent = CompletableDeferred<String>()
|
||||
timeline.addListener(object : Timeline.Listener {
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
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<MessageContent>()?.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
|
||||
|
|
|
@ -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 -> "??"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
// }
|
||||
// }
|
|
@ -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<VerificationEvent>(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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -57,7 +57,7 @@ internal class PrepareToEncryptUseCase @Inject constructor(
|
|||
private val keyClaimLock: Mutex = Mutex()
|
||||
private val roomKeyShareLocks: ConcurrentHashMap<String, Mutex> = 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<String>) {
|
||||
private suspend fun preshareRoomKey(roomId: String, roomMembers: List<String>, 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) {
|
||||
|
|
|
@ -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<List<CryptoDeviceInfo>> {
|
||||
return getLiveCryptoDeviceInfo(listOf(userId))
|
||||
return getLiveCryptoDeviceInfo(listOf(myUserId))
|
||||
}
|
||||
|
||||
override fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>> {
|
||||
|
@ -350,8 +350,8 @@ internal class RustCryptoService @Inject constructor(
|
|||
}
|
||||
|
||||
override fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
|
||||
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<OutgoingKeyRequest> {
|
||||
|
|
|
@ -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<Content>(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)
|
||||
}
|
||||
|
|
|
@ -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<ToDeviceVerificationEvent>()?.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
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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()}"
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue