Update verification signaling & handing
fix encryption hindering verification
This commit is contained in:
parent
02dc13e38d
commit
3f29c55479
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3f303e8830bb4bd7005b2a166118d7771ed07259822ebb6f888abb0ed459f0cc
|
oid sha256:a5b58eecbbaa8354901a57c7df727eb2ad6e401dd286ecf049d7a438788ac6ef
|
||||||
size 50458247
|
size 16077430
|
||||||
|
|
|
@ -17,6 +17,14 @@
|
||||||
package org.matrix.android.sdk.common
|
package org.matrix.android.sdk.common
|
||||||
|
|
||||||
import android.util.Log
|
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.amshove.kluent.fail
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNotNull
|
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.keysbackup.MegolmBackupCreationInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
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.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.SasVerificationTransaction
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
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.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
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) {
|
suspend fun verifySASCrossSign(alice: Session, bob: Session, roomId: String) {
|
||||||
|
val scope = CoroutineScope(SupervisorJob())
|
||||||
|
|
||||||
assertTrue(alice.cryptoService().crossSigningService().canCrossSign())
|
assertTrue(alice.cryptoService().crossSigningService().canCrossSign())
|
||||||
assertTrue(bob.cryptoService().crossSigningService().canCrossSign())
|
assertTrue(bob.cryptoService().crossSigningService().canCrossSign())
|
||||||
|
|
||||||
val aliceVerificationService = alice.cryptoService().verificationService()
|
val aliceVerificationService = alice.cryptoService().verificationService()
|
||||||
val bobVerificationService = bob.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(
|
val requestID = aliceVerificationService.requestKeyVerificationInDMs(
|
||||||
localId = localId,
|
|
||||||
methods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
methods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||||
otherUserId = bob.myUserId,
|
otherUserId = bob.myUserId,
|
||||||
roomId = roomId
|
roomId = roomId
|
||||||
).transactionId
|
).transactionId
|
||||||
|
|
||||||
testHelper.retryWithBackoff(
|
bobSeesVerification.await()
|
||||||
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")
|
|
||||||
bobVerificationService.readyPendingVerification(
|
bobVerificationService.readyPendingVerification(
|
||||||
listOf(VerificationMethod.SAS),
|
listOf(VerificationMethod.SAS),
|
||||||
alice.myUserId,
|
alice.myUserId,
|
||||||
incomingRequest.transactionId
|
requestID
|
||||||
)
|
)
|
||||||
|
aliceReady.await()
|
||||||
|
bobReady.await()
|
||||||
|
|
||||||
// wait for it to be readied
|
val bobCode = CompletableDeferred<SasVerificationTransaction>()
|
||||||
testHelper.retryWithBackoff(
|
|
||||||
onFail = {
|
scope.launch(Dispatchers.IO) {
|
||||||
fail("Alice should see the verification in ready state")
|
bobVerificationService.requestEventFlow()
|
||||||
}
|
.cancellable()
|
||||||
) {
|
.collect {
|
||||||
val outgoingRequest = aliceVerificationService.getExistingVerificationRequest(bob.myUserId, requestID)
|
val transaction = it.getTransaction()
|
||||||
outgoingRequest?.state == EVerificationState.Ready
|
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")
|
Timber.v("#TEST let alice start the verification")
|
||||||
aliceVerificationService.startKeyVerification(
|
val id = aliceVerificationService.startKeyVerification(
|
||||||
VerificationMethod.SAS,
|
VerificationMethod.SAS,
|
||||||
bob.myUserId,
|
bob.myUserId,
|
||||||
requestID,
|
requestID,
|
||||||
)
|
)
|
||||||
|
Timber.v("#TEST alice started: $id")
|
||||||
|
|
||||||
// we should reach SHOW SAS on both
|
val bobTx = bobCode.await()
|
||||||
var alicePovTx: SasVerificationTransaction? = null
|
val aliceTx = aliceCode.await()
|
||||||
var bobPovTx: SasVerificationTransaction? = null
|
assertEquals("SAS code do not match", aliceTx.getDecimalCodeRepresentation()!!, bobTx.getDecimalCodeRepresentation())
|
||||||
|
|
||||||
testHelper.retryWithBackoff(
|
val aliceDone = CompletableDeferred<Unit>()
|
||||||
onFail = {
|
scope.launch(Dispatchers.IO) {
|
||||||
fail("Alice should should see a verification code")
|
aliceVerificationService.requestEventFlow()
|
||||||
}
|
.cancellable()
|
||||||
) {
|
.collect {
|
||||||
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID)
|
val request = it.getRequest()
|
||||||
as? SasVerificationTransaction
|
if (request?.state == EVerificationState.Done) {
|
||||||
Log.v("TEST", "== alicePovTx id:${requestID} is ${alicePovTx?.state()}")
|
aliceDone.complete(Unit)
|
||||||
alicePovTx?.getDecimalCodeRepresentation() != null
|
return@collect cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// wait for alice to get the ready
|
val bobDone = CompletableDeferred<Unit>()
|
||||||
testHelper.retryWithBackoff(
|
scope.launch(Dispatchers.IO) {
|
||||||
onFail = {
|
bobVerificationService.requestEventFlow()
|
||||||
fail("Bob should should see a verification code")
|
.cancellable()
|
||||||
}
|
.collect {
|
||||||
) {
|
val request = it.getRequest()
|
||||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID)
|
if (request?.state == EVerificationState.Done) {
|
||||||
as? SasVerificationTransaction
|
bobDone.complete(Unit)
|
||||||
Log.v("TEST", "== bobPovTx is ${bobPovTx?.state()}")
|
return@collect cancel()
|
||||||
// bobPovTx?.state == VerificationTxState.ShortCodeReady
|
}
|
||||||
bobPovTx?.getDecimalCodeRepresentation() != null
|
}
|
||||||
}
|
}
|
||||||
|
bobTx.userHasVerifiedShortCode()
|
||||||
|
aliceTx.userHasVerifiedShortCode()
|
||||||
|
|
||||||
assertEquals("SAS code do not match", alicePovTx!!.getDecimalCodeRepresentation(), bobPovTx!!.getDecimalCodeRepresentation())
|
bobDone.await()
|
||||||
|
aliceDone.await()
|
||||||
|
|
||||||
bobPovTx!!.userHasVerifiedShortCode()
|
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
|
||||||
alicePovTx!!.userHasVerifiedShortCode()
|
bob.cryptoService().crossSigningService().isUserTrusted(alice.myUserId)
|
||||||
|
|
||||||
testHelper.retryWithBackoff {
|
scope.cancel()
|
||||||
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
|
|
||||||
}
|
|
||||||
|
|
||||||
testHelper.retryWithBackoff {
|
|
||||||
bob.cryptoService().crossSigningService().isUserTrusted(alice.myUserId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun doE2ETestWithManyMembers(numberOfMembers: Int): CryptoTestData {
|
suspend fun doE2ETestWithManyMembers(numberOfMembers: Int): CryptoTestData {
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import org.amshove.kluent.fail
|
import org.amshove.kluent.fail
|
||||||
import org.amshove.kluent.internal.assertEquals
|
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.Membership
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
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.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.api.session.room.timeline.TimelineSettings
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper
|
import org.matrix.android.sdk.common.CommonTestHelper
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
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")
|
Log.v("#E2E TEST", "Alice is sending the message")
|
||||||
|
|
||||||
val text = "This is my 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)
|
Assert.assertTrue("Message should be sent", sentEventId != null)
|
||||||
|
|
||||||
// All should be able to decrypt
|
// All should be able to decrypt
|
||||||
|
@ -140,7 +143,7 @@ class E2eeSanityTests : InstrumentedTest {
|
||||||
Log.v("#E2E TEST", "Alice sends a new message")
|
Log.v("#E2E TEST", "Alice sends a new message")
|
||||||
|
|
||||||
val secondMessage = "2 This is my 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
|
// new members should be able to decrypt it
|
||||||
newAccount.forEach { otherSession ->
|
newAccount.forEach { otherSession ->
|
||||||
|
@ -203,7 +206,7 @@ class E2eeSanityTests : InstrumentedTest {
|
||||||
val sentEventIds = mutableListOf<String>()
|
val sentEventIds = mutableListOf<String>()
|
||||||
val messagesText = listOf("1. Hello", "2. Bob", "3. Good morning")
|
val messagesText = listOf("1. Hello", "2. Bob", "3. Good morning")
|
||||||
messagesText.forEach { text ->
|
messagesText.forEach { text ->
|
||||||
val sentEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!.also {
|
val sentEventId = sendMessageInRoom(aliceRoomPOV, text)!!.also {
|
||||||
sentEventIds.add(it)
|
sentEventIds.add(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,7 +321,7 @@ class E2eeSanityTests : InstrumentedTest {
|
||||||
|
|
||||||
Log.v("#E2E TEST", "Alice sends some messages")
|
Log.v("#E2E TEST", "Alice sends some messages")
|
||||||
messagesText.forEach { text ->
|
messagesText.forEach { text ->
|
||||||
val sentEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!.also {
|
val sentEventId = sendMessageInRoom(aliceRoomPOV, text)!!.also {
|
||||||
sentEventIds.add(it)
|
sentEventIds.add(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,45 +345,24 @@ class E2eeSanityTests : InstrumentedTest {
|
||||||
Log.v("#E2E TEST", "Create a new session for Bob")
|
Log.v("#E2E TEST", "Create a new session for Bob")
|
||||||
val newBobSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
|
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
|
// check that new bob can't currently decrypt
|
||||||
Log.v("#E2E TEST", "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)
|
cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null)
|
||||||
|
|
||||||
// Try to request
|
// Try to request
|
||||||
|
//
|
||||||
Log.v("#E2E TEST", "Let bob re-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)
|
|
||||||
// sentEventIds.forEach { sentEventId ->
|
// sentEventIds.forEach { sentEventId ->
|
||||||
// val megolmSessionId = newBobSession.getRoom(e2eRoomID)!!
|
// val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
|
||||||
// .getTimelineEvent(sentEventId)!!
|
// newBobSession.cryptoService().reRequestRoomKeyForEvent(event)
|
||||||
// .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
|
|
||||||
// }
|
|
||||||
// }
|
// }
|
||||||
|
//
|
||||||
// */
|
// 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
|
// Now mark new bob session as verified
|
||||||
|
|
||||||
|
@ -422,7 +404,7 @@ class E2eeSanityTests : InstrumentedTest {
|
||||||
|
|
||||||
Log.v("#E2E TEST", "Alice sends some messages")
|
Log.v("#E2E TEST", "Alice sends some messages")
|
||||||
firstMessage.let { text ->
|
firstMessage.let { text ->
|
||||||
firstEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
|
firstEventId = sendMessageInRoom(aliceRoomPOV, text)!!
|
||||||
|
|
||||||
testHelper.retryWithBackoff {
|
testHelper.retryWithBackoff {
|
||||||
val timeLineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId)
|
val timeLineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId)
|
||||||
|
@ -448,7 +430,7 @@ class E2eeSanityTests : InstrumentedTest {
|
||||||
|
|
||||||
Log.v("#E2E TEST", "Alice sends some messages")
|
Log.v("#E2E TEST", "Alice sends some messages")
|
||||||
secondMessage.let { text ->
|
secondMessage.let { text ->
|
||||||
secondEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
|
secondEventId = sendMessageInRoom(aliceRoomPOV, text)!!
|
||||||
|
|
||||||
testHelper.retryWithBackoff {
|
testHelper.retryWithBackoff {
|
||||||
val timeLineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId)
|
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? {
|
private suspend fun sendMessageInRoom(aliceRoomPOV: Room, text: String): String? {
|
||||||
var sentEventId: String? = null
|
|
||||||
aliceRoomPOV.sendService().sendTextMessage(text)
|
aliceRoomPOV.sendService().sendTextMessage(text)
|
||||||
|
|
||||||
val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60))
|
val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60))
|
||||||
timeline.start()
|
timeline.start()
|
||||||
testHelper.retryWithBackoff {
|
|
||||||
val decryptedMsg = timeline.getSnapshot()
|
val messageSent = CompletableDeferred<String>()
|
||||||
.filter { it.root.getClearType() == EventType.MESSAGE }
|
timeline.addListener(object : Timeline.Listener {
|
||||||
.also { list ->
|
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||||
val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" }
|
val decryptedMsg = timeline.getSnapshot()
|
||||||
Log.v("#E2E TEST", "Timeline snapshot is $message")
|
.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||||
}
|
.also { list ->
|
||||||
.filter { it.root.sendState == SendState.SYNCED }
|
val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" }
|
||||||
.firstOrNull { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(text) == true }
|
Log.v("#E2E TEST", "Timeline snapshot is $message")
|
||||||
sentEventId = decryptedMsg?.eventId
|
}
|
||||||
decryptedMsg != null
|
.filter { it.root.sendState == SendState.SYNCED }
|
||||||
}
|
.firstOrNull { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(text) == true }
|
||||||
timeline.dispose()
|
if (decryptedMsg != null) {
|
||||||
return sentEventId
|
timeline.dispose()
|
||||||
|
messageSent.complete(decryptedMsg.eventId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return messageSent.await()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -646,7 +632,7 @@ class E2eeSanityTests : InstrumentedTest {
|
||||||
|
|
||||||
val roomFromAlicePOV = aliceSession.getRoom(cryptoTestData.roomId)!!
|
val roomFromAlicePOV = aliceSession.getRoom(cryptoTestData.roomId)!!
|
||||||
Timber.v("#TEST: Send a first message that should be withheld")
|
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
|
// wait for it to be synced back the other side
|
||||||
Timber.v("#TEST: Wait for message to be synced back")
|
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")
|
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")
|
Timber.v("#TEST: Wait for message to be synced back")
|
||||||
testHelper.retryWithBackoff {
|
testHelper.retryWithBackoff {
|
||||||
bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null
|
bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.crypto.verification
|
package org.matrix.android.sdk.api.session.crypto.verification
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.internal.crypto.verification.qrcode.QrCodeVerification
|
||||||
|
|
||||||
interface VerificationTransaction {
|
interface VerificationTransaction {
|
||||||
|
|
||||||
val method: VerificationMethod
|
val method: VerificationMethod
|
||||||
|
@ -38,3 +40,11 @@ interface VerificationTransaction {
|
||||||
|
|
||||||
fun isSuccessful(): Boolean
|
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.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
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.VerificationEvent
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
|
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 org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class VerificationListenersHolder @Inject constructor(
|
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 scope = CoroutineScope(SupervisorJob() + coroutineDispatchers.dmVerif)
|
||||||
val eventFlow = MutableSharedFlow<VerificationEvent>(extraBufferCapacity = 20, onBufferOverflow = BufferOverflow.SUSPEND)
|
val eventFlow = MutableSharedFlow<VerificationEvent>(extraBufferCapacity = 20, onBufferOverflow = BufferOverflow.SUSPEND)
|
||||||
|
|
||||||
fun dispatchTxAdded(tx: VerificationTransaction) {
|
fun dispatchTxAdded(tx: VerificationTransaction) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
Timber.v("## SAS [$myUserId] dispatchTxAdded txId:${tx.transactionId} | ${tx.dbgState()}")
|
||||||
eventFlow.emit(VerificationEvent.TransactionAdded(tx))
|
eventFlow.emit(VerificationEvent.TransactionAdded(tx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dispatchTxUpdated(tx: VerificationTransaction) {
|
fun dispatchTxUpdated(tx: VerificationTransaction) {
|
||||||
scope.launch {
|
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))
|
eventFlow.emit(VerificationEvent.TransactionUpdated(tx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dispatchRequestAdded(verificationRequest: PendingVerificationRequest) {
|
fun dispatchRequestAdded(verificationRequest: VerificationRequest) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
Timber.v("## SAS dispatchRequestAdded txId:${verificationRequest.transactionId} $verificationRequest")
|
Timber.v("## SAS [$myUserId] dispatchRequestAdded txId:${verificationRequest.flowId()} state:${verificationRequest.innerState()}")
|
||||||
eventFlow.emit(VerificationEvent.RequestAdded(verificationRequest))
|
eventFlow.emit(VerificationEvent.RequestAdded(verificationRequest.toPendingVerificationRequest()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dispatchRequestUpdated(verificationRequest: PendingVerificationRequest) {
|
fun dispatchRequestUpdated(verificationRequest: VerificationRequest) {
|
||||||
Timber.v("## SAS dispatchRequestUpdated txId:${verificationRequest.transactionId} $verificationRequest")
|
|
||||||
scope.launch {
|
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.crypto.model.MXEncryptEventContentResult
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
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.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 org.matrix.android.sdk.internal.util.time.Clock
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -36,9 +38,24 @@ internal class EncryptEventContentUseCase @Inject constructor(
|
||||||
eventType: String,
|
eventType: String,
|
||||||
roomId: String): MXEncryptEventContentResult {
|
roomId: String): MXEncryptEventContentResult {
|
||||||
val t0 = clock.epochMillis()
|
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)
|
val content = olmMachine.encrypt(roomId, eventType, eventContent)
|
||||||
Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms")
|
Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms")
|
||||||
return MXEncryptEventContentResult(content, EventType.ENCRYPTED)
|
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)
|
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
|
val adapter = moshi
|
||||||
.newBuilder()
|
|
||||||
.add(CheckNumberType.JSON_ADAPTER_FACTORY)
|
|
||||||
.build()
|
|
||||||
.adapter(Event::class.java)
|
.adapter(Event::class.java)
|
||||||
val serializedEvent = adapter.toJson(event)
|
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 keyClaimLock: Mutex = Mutex()
|
||||||
private val roomKeyShareLocks: ConcurrentHashMap<String, Mutex> = ConcurrentHashMap()
|
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) {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date")
|
Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date")
|
||||||
// Ensure to load all room members
|
// Ensure to load all room members
|
||||||
|
@ -76,7 +76,7 @@ internal class PrepareToEncryptUseCase @Inject constructor(
|
||||||
Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason")
|
Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason")
|
||||||
throw IllegalArgumentException("Missing algorithm")
|
throw IllegalArgumentException("Missing algorithm")
|
||||||
}
|
}
|
||||||
preshareRoomKey(roomId, userIds)
|
preshareRoomKey(roomId, userIds, forceDistributeToUnverified)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ internal class PrepareToEncryptUseCase @Inject constructor(
|
||||||
return cryptoStore.getRoomAlgorithm(roomId)
|
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)
|
claimMissingKeys(roomMembers)
|
||||||
val keyShareLock = roomKeyShareLocks.getOrPut(roomId) { Mutex() }
|
val keyShareLock = roomKeyShareLocks.getOrPut(roomId) { Mutex() }
|
||||||
var sharedKey = false
|
var sharedKey = false
|
||||||
|
@ -97,7 +97,12 @@ internal class PrepareToEncryptUseCase @Inject constructor(
|
||||||
}
|
}
|
||||||
val settings = EncryptionSettings(
|
val settings = EncryptionSettings(
|
||||||
algorithm = EventEncryptionAlgorithm.MEGOLM_V1_AES_SHA2,
|
algorithm = EventEncryptionAlgorithm.MEGOLM_V1_AES_SHA2,
|
||||||
onlyAllowTrustedDevices = info.blacklistUnverifiedDevices,
|
onlyAllowTrustedDevices = if (forceDistributeToUnverified) {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
cryptoStore.getGlobalBlacklistUnverifiedDevices() ||
|
||||||
|
info.blacklistUnverifiedDevices
|
||||||
|
},
|
||||||
rotationPeriod = info.rotationPeriodMs.toULong(),
|
rotationPeriod = info.rotationPeriodMs.toULong(),
|
||||||
rotationPeriodMsgs = info.rotationPeriodMsgs.toULong(),
|
rotationPeriodMsgs = info.rotationPeriodMsgs.toULong(),
|
||||||
historyVisibility = if (info.shouldShareHistory) {
|
historyVisibility = if (info.shouldShareHistory) {
|
||||||
|
|
|
@ -109,7 +109,7 @@ private val loggerTag = LoggerTag("RustCryptoService", LoggerTag.CRYPTO)
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class RustCryptoService @Inject constructor(
|
internal class RustCryptoService @Inject constructor(
|
||||||
@UserId private val userId: String,
|
@UserId private val myUserId: String,
|
||||||
@DeviceId private val deviceId: String,
|
@DeviceId private val deviceId: String,
|
||||||
// the crypto store
|
// the crypto store
|
||||||
private val cryptoStore: IMXCryptoStore,
|
private val cryptoStore: IMXCryptoStore,
|
||||||
|
@ -167,7 +167,7 @@ internal class RustCryptoService @Inject constructor(
|
||||||
val params = SetDeviceNameTask.Params(deviceId, deviceName)
|
val params = SetDeviceNameTask.Params(deviceId, deviceName)
|
||||||
setDeviceNameTask.execute(params)
|
setDeviceNameTask.execute(params)
|
||||||
try {
|
try {
|
||||||
downloadKeysIfNeeded(listOf(userId), true)
|
downloadKeysIfNeeded(listOf(myUserId), true)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
Timber.tag(loggerTag.value).w(failure, "setDeviceName: Failed to refresh of crypto device")
|
Timber.tag(loggerTag.value).w(failure, "setDeviceName: Failed to refresh of crypto device")
|
||||||
}
|
}
|
||||||
|
@ -257,7 +257,7 @@ internal class RustCryptoService @Inject constructor(
|
||||||
setRustLogger()
|
setRustLogger()
|
||||||
Timber.tag(loggerTag.value).v(
|
Timber.tag(loggerTag.value).v(
|
||||||
"## CRYPTO | Successfully started up an Olm machine for " +
|
"## 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) {
|
} catch (throwable: Throwable) {
|
||||||
Timber.tag(loggerTag.value).v("Failed create an Olm machine: $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>> {
|
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {
|
||||||
return getLiveCryptoDeviceInfo(listOf(userId))
|
return getLiveCryptoDeviceInfo(listOf(myUserId))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>> {
|
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>> {
|
override fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
|
||||||
return olmMachine.getLiveDevices(listOf(userId)).map {
|
return olmMachine.getLiveDevices(listOf(myUserId)).map {
|
||||||
it.filter { it.userId == userId }
|
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.
|
// Notify the our listeners about room keys so decryption is retried.
|
||||||
toDeviceEvents.events.orEmpty().forEach { event ->
|
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) {
|
if (event.getClearType() == EventType.ENCRYPTED) {
|
||||||
// rust failed to decrypt it
|
// rust failed to decrypt it
|
||||||
|
@ -832,7 +832,7 @@ internal class RustCryptoService @Inject constructor(
|
||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "DefaultCryptoService of $userId ($deviceId)"
|
return "DefaultCryptoService of $myUserId ($deviceId)"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getOutgoingRoomKeyRequests(): List<OutgoingKeyRequest> {
|
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.UploadSignaturesTask
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.UploadSigningKeysTask
|
import org.matrix.android.sdk.internal.crypto.tasks.UploadSigningKeysTask
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
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.DEFAULT_REQUEST_RETRY_COUNT
|
||||||
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
|
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.android.sdk.internal.session.room.send.SendResponse
|
||||||
import org.matrix.rustcomponents.sdk.crypto.OutgoingVerificationRequest
|
import org.matrix.rustcomponents.sdk.crypto.OutgoingVerificationRequest
|
||||||
import org.matrix.rustcomponents.sdk.crypto.Request
|
import org.matrix.rustcomponents.sdk.crypto.Request
|
||||||
|
@ -77,6 +79,8 @@ import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RequestSender @Inject constructor(
|
internal class RequestSender @Inject constructor(
|
||||||
|
@UserId
|
||||||
|
private val myUserId: String,
|
||||||
private val sendToDeviceTask: SendToDeviceTask,
|
private val sendToDeviceTask: SendToDeviceTask,
|
||||||
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask,
|
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask,
|
||||||
private val uploadKeysTask: UploadKeysTask,
|
private val uploadKeysTask: UploadKeysTask,
|
||||||
|
@ -94,8 +98,9 @@ internal class RequestSender @Inject constructor(
|
||||||
private val getRoomSessionsDataTask: GetRoomSessionsDataTask,
|
private val getRoomSessionsDataTask: GetRoomSessionsDataTask,
|
||||||
private val getRoomSessionDataTask: GetRoomSessionDataTask,
|
private val getRoomSessionDataTask: GetRoomSessionDataTask,
|
||||||
private val moshi: Moshi,
|
private val moshi: Moshi,
|
||||||
private val cryptoCoroutineScope: CoroutineScope,
|
cryptoCoroutineScope: CoroutineScope,
|
||||||
private val rateLimiter: PerSessionBackupQueryRateLimiter,
|
private val rateLimiter: PerSessionBackupQueryRateLimiter,
|
||||||
|
private val localEchoRepository: LocalEchoRepository
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val scope = CoroutineScope(
|
private val scope = CoroutineScope(
|
||||||
|
@ -136,7 +141,13 @@ internal class RequestSender @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun sendRoomMessage(request: OutgoingVerificationRequest.InRoom, retryCount: Int): SendResponse {
|
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 {
|
suspend fun sendRoomMessage(request: Request.RoomMessage, retryCount: Int = DEFAULT_REQUEST_RETRY_COUNT): String {
|
||||||
|
@ -153,7 +164,13 @@ internal class RequestSender @Inject constructor(
|
||||||
): SendResponse {
|
): SendResponse {
|
||||||
val paramsAdapter = moshi.adapter<Content>(Map::class.java)
|
val paramsAdapter = moshi.adapter<Content>(Map::class.java)
|
||||||
val jsonContent = paramsAdapter.fromJson(content)
|
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)
|
val params = SendVerificationMessageTask.Params(event, retryCount)
|
||||||
return sendVerificationMessageTask.get().execute(params)
|
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
|
* All verification related events should be forwarded through this method to
|
||||||
* the verification service.
|
* the verification service.
|
||||||
*
|
*
|
||||||
* If the verification event is not encrypted it should be provided to the olmMachine.
|
* This method mainly just fetches the appropriate rust object that will be created or updated by the event and
|
||||||
* 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
|
|
||||||
* dispatches updates to our listeners.
|
* dispatches updates to our listeners.
|
||||||
*/
|
*/
|
||||||
internal suspend fun onEvent(roomId: String?, event: Event) {
|
internal suspend fun onEvent(roomId: String?, event: Event) {
|
||||||
if (roomId != null && !event.isEncrypted()) {
|
if (roomId != null && event.unsignedData?.transactionId == null) {
|
||||||
if (isVerificationEvent(event)) {
|
if (isVerificationEvent(event)) {
|
||||||
try {
|
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) {
|
} 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 {
|
private fun isVerificationEvent(event: Event): Boolean {
|
||||||
val eventType = event.type ?: return false
|
val eventType = event.getClearType()
|
||||||
val eventContent = event.content ?: return false
|
val eventContent = event.getClearContent() ?: return false
|
||||||
return EventType.isVerificationEvent(eventType) ||
|
return EventType.isVerificationEvent(eventType) ||
|
||||||
(eventType == EventType.MESSAGE &&
|
(eventType == EventType.MESSAGE &&
|
||||||
eventContent[MessageContent.MSG_TYPE_JSON_KEY] == MessageType.MSGTYPE_VERIFICATION_REQUEST)
|
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 */
|
/** Dispatch updates after a verification event has been received */
|
||||||
private suspend fun onUpdate(event: Event) {
|
private suspend fun onUpdate(event: Event) {
|
||||||
|
Timber.v("[${olmMachine.userId().take(6)}] Verification on event ${event.getClearType()}")
|
||||||
val sender = event.senderId ?: return
|
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)
|
val verificationRequest = olmMachine.getVerificationRequest(sender, flowId)
|
||||||
if (event.getClearType() == EventType.KEY_VERIFICATION_READY) {
|
if (event.getClearType() == EventType.KEY_VERIFICATION_READY) {
|
||||||
|
// we start the qr here in order to display the code
|
||||||
verificationRequest?.startQrCode()
|
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 */
|
/** Check if the start event created new verification objects and dispatch updates */
|
||||||
private suspend fun onStart(event: Event) {
|
private suspend fun onStart(event: Event) {
|
||||||
if (event.unsignedData?.transactionId != null) return // remote echo
|
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 sender = event.senderId ?: return
|
||||||
val flowId = getFlowId(event) ?: return
|
val flowId = getFlowId(event) ?: return
|
||||||
|
|
||||||
|
@ -167,7 +174,6 @@ internal class RustVerificationService @Inject constructor(
|
||||||
|
|
||||||
Timber.d("## Verification: start for $sender")
|
Timber.d("## Verification: start for $sender")
|
||||||
// update the request as the start updates it's state
|
// update the request as the start updates it's state
|
||||||
request.dispatchRequestUpdated()
|
|
||||||
verificationListenersHolder.dispatchTxUpdated(transaction)
|
verificationListenersHolder.dispatchTxUpdated(transaction)
|
||||||
} else {
|
} else {
|
||||||
// This didn't originate from a request, so tell our listeners that
|
// 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
|
event.getClearContent().toModel<ToDeviceVerificationEvent>()?.transactionId
|
||||||
} ?: return
|
} ?: return
|
||||||
val sender = event.senderId ?: return
|
val sender = event.senderId ?: return
|
||||||
val request = getExistingVerificationRequest(sender, flowId) ?: return
|
val request = olmMachine.getVerificationRequest(sender, flowId) ?: return
|
||||||
|
|
||||||
verificationListenersHolder.dispatchRequestAdded(request)
|
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) {
|
override suspend fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
|
||||||
olmMachine.getDevice(userId, deviceID)?.markAsTrusted()
|
olmMachine.getDevice(userId, deviceID)?.markAsTrusted()
|
||||||
}
|
}
|
||||||
|
@ -269,14 +267,14 @@ internal class RustVerificationService @Inject constructor(
|
||||||
roomId: String,
|
roomId: String,
|
||||||
localId: String?
|
localId: String?
|
||||||
): PendingVerificationRequest {
|
): 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)) {
|
val verification = when (val identity = olmMachine.getIdentity(otherUserId)) {
|
||||||
is UserIdentity -> identity.requestVerification(methods, roomId, localId!!)
|
is UserIdentity -> identity.requestVerification(methods, roomId, localId!!)
|
||||||
is OwnUserIdentity -> throw IllegalArgumentException("This method doesn't support verification of our own user")
|
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")
|
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()
|
return verification.toPendingVerificationRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,13 +317,15 @@ internal class RustVerificationService @Inject constructor(
|
||||||
override suspend fun startKeyVerification(method: VerificationMethod, otherUserId: String, requestId: String): String? {
|
override suspend fun startKeyVerification(method: VerificationMethod, otherUserId: String, requestId: String): String? {
|
||||||
return if (method == VerificationMethod.SAS) {
|
return if (method == VerificationMethod.SAS) {
|
||||||
val request = olmMachine.getVerificationRequest(otherUserId, requestId)
|
val request = olmMachine.getVerificationRequest(otherUserId, requestId)
|
||||||
|
?: throw IllegalArgumentException("Unknown request with id: $requestId")
|
||||||
|
|
||||||
val sas = request?.startSasVerification()
|
val sas = request.startSasVerification()
|
||||||
|
|
||||||
if (sas != null) {
|
if (sas != null) {
|
||||||
verificationListenersHolder.dispatchTxAdded(sas)
|
verificationListenersHolder.dispatchTxAdded(sas)
|
||||||
sas.transactionId
|
sas.transactionId
|
||||||
} else {
|
} else {
|
||||||
|
Timber.w("Failed to start verification with method $method")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -338,7 +338,6 @@ internal class RustVerificationService @Inject constructor(
|
||||||
?: return null
|
?: return null
|
||||||
val qrVerification = matchingRequest.scanQrCode(scannedData)
|
val qrVerification = matchingRequest.scanQrCode(scannedData)
|
||||||
?: return null
|
?: return null
|
||||||
verificationListenersHolder.dispatchRequestUpdated(matchingRequest.toPendingVerificationRequest())
|
|
||||||
verificationListenersHolder.dispatchTxAdded(qrVerification)
|
verificationListenersHolder.dispatchTxAdded(qrVerification)
|
||||||
return qrVerification.transactionId
|
return qrVerification.transactionId
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,20 +83,6 @@ internal class SasVerification @AssistedInject constructor(
|
||||||
SasState.Done -> SasTransactionState.Done(true)
|
SasState.Done -> SasTransactionState.Done(true)
|
||||||
is SasState.Cancelled -> SasTransactionState.Cancelled(safeValueOf(state.cancelInfo.cancelCode), state.cancelInfo.cancelledByUs)
|
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 */
|
/** 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.network.RequestSender
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.qrcode.QrCodeVerification
|
import org.matrix.android.sdk.internal.crypto.verification.qrcode.QrCodeVerification
|
||||||
import org.matrix.android.sdk.internal.util.time.Clock
|
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
|
import org.matrix.rustcomponents.sdk.crypto.VerificationRequest as InnerVerificationRequest
|
||||||
|
|
||||||
fun InnerVerificationRequest.dbgString(): String {
|
fun InnerVerificationRequest.dbgString(): String {
|
||||||
val that = this
|
val that = this
|
||||||
return buildString {
|
return buildString {
|
||||||
append("InnerVerificationRequest(")
|
append("(")
|
||||||
append("isDone=${that.isDone()},")
|
append("flowId=${that.flowId()}")
|
||||||
append("isReady=${that.isReady()},")
|
append("state=${that.state()},")
|
||||||
append("isPassive=${that.isPassive()},")
|
|
||||||
append("weStarted=${that.weStarted()},")
|
|
||||||
append("isCancelled=${that.isCancelled()}")
|
|
||||||
append(")")
|
append(")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ internal class VerificationRequest @AssistedInject constructor(
|
||||||
private val sasVerificationFactory: SasVerification.Factory,
|
private val sasVerificationFactory: SasVerification.Factory,
|
||||||
private val qrCodeVerificationFactory: QrCodeVerification.Factory,
|
private val qrCodeVerificationFactory: QrCodeVerification.Factory,
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
) {
|
) : VerificationRequestListener {
|
||||||
|
|
||||||
private val innerOlmMachine = olmMachine.inner()
|
private val innerOlmMachine = olmMachine.inner()
|
||||||
|
|
||||||
|
@ -78,14 +78,18 @@ internal class VerificationRequest @AssistedInject constructor(
|
||||||
fun create(innerVerificationRequest: InnerVerificationRequest): VerificationRequest
|
fun create(innerVerificationRequest: InnerVerificationRequest): VerificationRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
innerVerificationRequest.setChangesListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
fun startQrCode() {
|
fun startQrCode() {
|
||||||
innerVerificationRequest.startQrVerification()
|
innerVerificationRequest.startQrVerification()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun dispatchRequestUpdated() {
|
// internal fun dispatchRequestUpdated() {
|
||||||
val tx = toPendingVerificationRequest()
|
// val tx = toPendingVerificationRequest()
|
||||||
verificationListenersHolder.dispatchRequestUpdated(tx)
|
// verificationListenersHolder.dispatchRequestUpdated(tx)
|
||||||
}
|
// }
|
||||||
|
|
||||||
/** Get the flow ID of this verification request
|
/** Get the flow ID of this verification request
|
||||||
*
|
*
|
||||||
|
@ -97,6 +101,8 @@ internal class VerificationRequest @AssistedInject constructor(
|
||||||
return innerVerificationRequest.flowId()
|
return innerVerificationRequest.flowId()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun innerState() = innerVerificationRequest.state()
|
||||||
|
|
||||||
/** The user ID of the other user that is participating in this verification flow */
|
/** The user ID of the other user that is participating in this verification flow */
|
||||||
internal fun otherUser(): String {
|
internal fun otherUser(): String {
|
||||||
return innerVerificationRequest.otherUserId()
|
return innerVerificationRequest.otherUserId()
|
||||||
|
@ -108,7 +114,6 @@ internal class VerificationRequest @AssistedInject constructor(
|
||||||
* didn't yet accept the verification flow.
|
* didn't yet accept the verification flow.
|
||||||
* */
|
* */
|
||||||
internal fun otherDeviceId(): String? {
|
internal fun otherDeviceId(): String? {
|
||||||
refreshData()
|
|
||||||
return innerVerificationRequest.otherDeviceId()
|
return innerVerificationRequest.otherDeviceId()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,13 +137,11 @@ internal class VerificationRequest @AssistedInject constructor(
|
||||||
* verification.
|
* verification.
|
||||||
*/
|
*/
|
||||||
internal fun isReady(): Boolean {
|
internal fun isReady(): Boolean {
|
||||||
refreshData()
|
|
||||||
return innerVerificationRequest.isReady()
|
return innerVerificationRequest.isReady()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Did we advertise that we're able to scan QR codes */
|
/** Did we advertise that we're able to scan QR codes */
|
||||||
internal fun canScanQrCodes(): Boolean {
|
internal fun canScanQrCodes(): Boolean {
|
||||||
refreshData()
|
|
||||||
return innerVerificationRequest.ourSupportedMethods()?.contains(VERIFICATION_METHOD_QR_CODE_SCAN) ?: false
|
return innerVerificationRequest.ourSupportedMethods()?.contains(VERIFICATION_METHOD_QR_CODE_SCAN) ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,12 +164,7 @@ internal class VerificationRequest @AssistedInject constructor(
|
||||||
val request = innerVerificationRequest.accept(stringMethods)
|
val request = innerVerificationRequest.accept(stringMethods)
|
||||||
?: return // should throw here?
|
?: return // should throw here?
|
||||||
try {
|
try {
|
||||||
dispatchRequestUpdated()
|
|
||||||
requestSender.sendVerificationRequest(request)
|
requestSender.sendVerificationRequest(request)
|
||||||
|
|
||||||
// if (innerVerificationRequest.isReady()) {
|
|
||||||
// activeQRCode = innerVerificationRequest.startQrVerification()
|
|
||||||
// }
|
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
|
@ -190,9 +188,9 @@ internal class VerificationRequest @AssistedInject constructor(
|
||||||
internal suspend fun startSasVerification(): SasVerification? {
|
internal suspend fun startSasVerification(): SasVerification? {
|
||||||
return withContext(coroutineDispatchers.io) {
|
return withContext(coroutineDispatchers.io) {
|
||||||
val result = innerVerificationRequest.startSasVerification()
|
val result = innerVerificationRequest.startSasVerification()
|
||||||
?: return@withContext null
|
?: return@withContext null.also {
|
||||||
// sasStartResult.request
|
Timber.w("Failed to start verification")
|
||||||
// val result = innerOlmMachine.startSasVerification(innerVerificationRequest.otherUserId, innerVerificationRequest.flowId) ?: return@withContext null
|
}
|
||||||
try {
|
try {
|
||||||
requestSender.sendVerificationRequest(result.request)
|
requestSender.sendVerificationRequest(result.request)
|
||||||
sasVerificationFactory.create(result.sas)
|
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.
|
* The method turns into a noop, if the verification flow has already been cancelled.
|
||||||
*/
|
*/
|
||||||
internal suspend fun cancel() = withContext(NonCancellable) {
|
internal suspend fun cancel() = withContext(NonCancellable) {
|
||||||
// TODO damir how to add the code?
|
|
||||||
val request = innerVerificationRequest.cancel() ?: return@withContext
|
val request = innerVerificationRequest.cancel() ?: return@withContext
|
||||||
dispatchRequestUpdated()
|
|
||||||
tryOrNull("Fail to send cancel request") {
|
tryOrNull("Fail to send cancel request") {
|
||||||
requestSender.sendVerificationRequest(request, retryCount = Int.MAX_VALUE)
|
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 {
|
private fun state(): EVerificationState {
|
||||||
if (innerVerificationRequest.isCancelled()) {
|
Timber.v("Verification state() ${innerVerificationRequest.state()}")
|
||||||
return if (innerVerificationRequest.cancelInfo()?.cancelCode == CancelCode.AcceptedByAnotherDevice.value) {
|
when (innerVerificationRequest.state()) {
|
||||||
EVerificationState.HandledByOtherSession
|
VerificationRequestState.Requested -> {
|
||||||
} else {
|
return if (weStarted()) {
|
||||||
EVerificationState.Cancelled
|
EVerificationState.WaitingForReady
|
||||||
}
|
|
||||||
}
|
|
||||||
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 {
|
} else {
|
||||||
EVerificationState.Started
|
EVerificationState.Requested
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val asQR = started.asQr()
|
is VerificationRequestState.Ready -> {
|
||||||
if (asQR != null) {
|
val started = innerOlmMachine.getVerification(otherUser(), flowId())
|
||||||
// Timber.w("VALR: weStarted ${asQR.weStarted()}")
|
if (started != null) {
|
||||||
// Timber.w("VALR: reciprocated ${asQR.reciprocated()}")
|
val asSas = started.asSas()
|
||||||
// Timber.w("VALR: isDone ${asQR.isDone()}")
|
if (asSas != null) {
|
||||||
// Timber.w("VALR: hasBeenScanned ${asQR.hasBeenScanned()}")
|
return if (asSas.weStarted()) {
|
||||||
if (asQR.reciprocated() || asQR.hasBeenScanned()) {
|
EVerificationState.WeStarted
|
||||||
return if (weStarted()) {
|
} else {
|
||||||
EVerificationState.WeStarted
|
EVerificationState.Started
|
||||||
} 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
|
// if (innerVerificationRequest.isCancelled()) {
|
||||||
}
|
// return if (innerVerificationRequest.cancelInfo()?.cancelCode == CancelCode.AcceptedByAnotherDevice.value) {
|
||||||
return if (weStarted()) {
|
// EVerificationState.HandledByOtherSession
|
||||||
EVerificationState.WaitingForReady
|
// } else {
|
||||||
} else {
|
// EVerificationState.Cancelled
|
||||||
EVerificationState.Requested
|
// }
|
||||||
}
|
// }
|
||||||
|
// 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
|
/** Convert the VerificationRequest into a PendingVerificationRequest
|
||||||
|
@ -317,7 +338,6 @@ internal class VerificationRequest @AssistedInject constructor(
|
||||||
* @return The PendingVerificationRequest that matches data from this VerificationRequest.
|
* @return The PendingVerificationRequest that matches data from this VerificationRequest.
|
||||||
*/
|
*/
|
||||||
internal fun toPendingVerificationRequest(): PendingVerificationRequest {
|
internal fun toPendingVerificationRequest(): PendingVerificationRequest {
|
||||||
refreshData()
|
|
||||||
val cancelInfo = innerVerificationRequest.cancelInfo()
|
val cancelInfo = innerVerificationRequest.cancelInfo()
|
||||||
val cancelCode =
|
val cancelCode =
|
||||||
if (cancelInfo != null) {
|
if (cancelInfo != null) {
|
||||||
|
@ -364,6 +384,10 @@ internal class VerificationRequest @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onChange(state: VerificationRequestState) {
|
||||||
|
verificationListenersHolder.dispatchRequestUpdated(this)
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return super.toString() + "\n${innerVerificationRequest.dbgString()}"
|
return super.toString() + "\n${innerVerificationRequest.dbgString()}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -331,22 +331,28 @@ class UserVerificationViewModel @AssistedInject constructor(
|
||||||
val roomId = session.roomService().getExistingDirectRoomWithUser(initialState.otherUserId)
|
val roomId = session.roomService().getExistingDirectRoomWithUser(initialState.otherUserId)
|
||||||
?: session.roomService().createDirectRoom(initialState.otherUserId)
|
?: session.roomService().createDirectRoom(initialState.otherUserId)
|
||||||
|
|
||||||
val request = session.cryptoService().verificationService()
|
try {
|
||||||
.requestKeyVerificationInDMs(
|
val request = session.cryptoService().verificationService()
|
||||||
supportedVerificationMethodsProvider.provide(),
|
.requestKeyVerificationInDMs(
|
||||||
initialState.otherUserId,
|
methods = supportedVerificationMethodsProvider.provide(),
|
||||||
roomId,
|
otherUserId = initialState.otherUserId,
|
||||||
|
roomId = roomId,
|
||||||
|
)
|
||||||
|
|
||||||
|
currentTransactionId = request.transactionId
|
||||||
|
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
pendingRequest = Success(request),
|
||||||
|
transactionId = request.transactionId
|
||||||
)
|
)
|
||||||
|
}
|
||||||
currentTransactionId = request.transactionId
|
} catch (failure: Throwable) {
|
||||||
|
setState {
|
||||||
Timber.w("VALR started request is $request")
|
copy(
|
||||||
|
pendingRequest = Fail(failure),
|
||||||
setState {
|
)
|
||||||
copy(
|
}
|
||||||
pendingRequest = Success(request),
|
|
||||||
transactionId = request.transactionId
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue