refactor for easy unit tests

This commit is contained in:
Valere 2022-11-21 15:16:34 +01:00
parent bed2c221e3
commit 4ce6a25c70
9 changed files with 725 additions and 436 deletions

View File

@ -62,7 +62,8 @@ internal class KotlinSasTransaction(
override val isIncoming: Boolean, override val isIncoming: Boolean,
val startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null, val startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null,
val isToDevice: Boolean, val isToDevice: Boolean,
var state: SasTransactionState var state: SasTransactionState,
val olmSAS: OlmSAS,
) : SasVerificationTransaction { ) : SasVerificationTransaction {
override val method: VerificationMethod override val method: VerificationMethod
@ -183,11 +184,8 @@ internal class KotlinSasTransaction(
} }
} }
private var olmSas: OlmSAS? = null override fun toString(): String {
return "KotlinSasTransaction(transactionId=$transactionId, state=$state, otherUserId=$otherUserId, otherDeviceId=$otherDeviceId, isToDevice=$isToDevice)"
fun getSAS(): OlmSAS {
if (olmSas == null) olmSas = OlmSAS()
return olmSas!!
} }
// To override finalize(), all you need to do is simply declare it, without using the override keyword: // To override finalize(), all you need to do is simply declare it, without using the override keyword:
@ -197,8 +195,7 @@ internal class KotlinSasTransaction(
private fun releaseSAS() { private fun releaseSAS() {
// finalization logic // finalization logic
olmSas?.releaseSas() olmSAS.releaseSas()
olmSas = null
} }
var accepted: ValidVerificationInfoAccept? = null var accepted: ValidVerificationInfoAccept? = null
@ -206,6 +203,7 @@ internal class KotlinSasTransaction(
var shortCodeBytes: ByteArray? = null var shortCodeBytes: ByteArray? = null
var myMac: ValidVerificationInfoMac? = null var myMac: ValidVerificationInfoMac? = null
var theirMac: ValidVerificationInfoMac? = null var theirMac: ValidVerificationInfoMac? = null
var verifiedSuccessInfo: MacVerificationResult.Success? = null
override fun state() = this.state override fun state() = this.state
@ -262,7 +260,7 @@ internal class KotlinSasTransaction(
fun calculateSASBytes(otherKey: String) { fun calculateSASBytes(otherKey: String) {
this.otherKey = otherKey this.otherKey = otherKey
getSAS().setTheirPublicKey(otherKey) olmSAS.setTheirPublicKey(otherKey)
shortCodeBytes = when (accepted!!.keyAgreementProtocol) { shortCodeBytes = when (accepted!!.keyAgreementProtocol) {
KEY_AGREEMENT_V1 -> { KEY_AGREEMENT_V1 -> {
// (Note: In all of the following HKDF is as defined in RFC 5869, and uses the previously agreed-on hash function as the hash function, // (Note: In all of the following HKDF is as defined in RFC 5869, and uses the previously agreed-on hash function as the hash function,
@ -280,7 +278,7 @@ internal class KotlinSasTransaction(
append(otherDeviceId) append(otherDeviceId)
append(myUserId) append(myUserId)
append(myDeviceId) append(myDeviceId)
append(getSAS().publicKey) append(olmSAS.publicKey)
} else { } else {
append(myUserId) append(myUserId)
append(myDeviceId) append(myDeviceId)
@ -291,7 +289,7 @@ internal class KotlinSasTransaction(
} }
// decimal: generate five bytes by using HKDF. // decimal: generate five bytes by using HKDF.
// emoji: generate six bytes by using HKDF. // emoji: generate six bytes by using HKDF.
getSAS().generateShortCode(sasInfo, 6) olmSAS.generateShortCode(sasInfo, 6)
} }
KEY_AGREEMENT_V2 -> { KEY_AGREEMENT_V2 -> {
val sasInfo = buildString { val sasInfo = buildString {
@ -302,18 +300,18 @@ internal class KotlinSasTransaction(
append(otherKey).append('|') append(otherKey).append('|')
append(myUserId).append('|') append(myUserId).append('|')
append(myDeviceId).append('|') append(myDeviceId).append('|')
append(getSAS().publicKey).append('|') append(olmSAS.publicKey).append('|')
} else { } else {
append(myUserId).append('|') append(myUserId).append('|')
append(myDeviceId).append('|') append(myDeviceId).append('|')
append(getSAS().publicKey).append('|') append(olmSAS.publicKey).append('|')
append(otherUserId).append('|') append(otherUserId).append('|')
append(otherDeviceId).append('|') append(otherDeviceId).append('|')
append(otherKey).append('|') append(otherKey).append('|')
} }
append(transactionId) append(transactionId)
} }
getSAS().generateShortCode(sasInfo, 6) olmSAS.generateShortCode(sasInfo, 6)
} }
else -> { else -> {
// Protocol has been checked earlier // Protocol has been checked earlier
@ -463,13 +461,16 @@ internal class KotlinSasTransaction(
return MacVerificationResult.Success( return MacVerificationResult.Success(
verifiedDevices, verifiedDevices,
otherMasterKeyIsVerified otherMasterKeyIsVerified
) ).also {
// store and will persist when transaction is actually done
verifiedSuccessInfo = it
}
} }
private fun macUsingAgreedMethod(message: String, info: String): String? { private fun macUsingAgreedMethod(message: String, info: String): String? {
return when (accepted?.messageAuthenticationCode?.lowercase(Locale.ROOT)) { return when (accepted?.messageAuthenticationCode?.lowercase(Locale.ROOT)) {
SAS_MAC_SHA256_LONGKDF -> getSAS().calculateMacLongKdf(message, info) SAS_MAC_SHA256_LONGKDF -> olmSAS.calculateMacLongKdf(message, info)
SAS_MAC_SHA256 -> getSAS().calculateMac(message, info) SAS_MAC_SHA256 -> olmSAS.calculateMac(message, info)
else -> null else -> null
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright 2022 The Matrix.org Foundation C.I.C.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -29,15 +29,12 @@ import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
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.QRCodeVerificationState import org.matrix.android.sdk.api.session.crypto.verification.QRCodeVerificationState
import org.matrix.android.sdk.api.session.crypto.verification.SasTransactionState 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
@ -57,7 +54,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVerification
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationStartContent import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationStartContent
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
import org.matrix.android.sdk.internal.crypto.SecretShareManager import org.matrix.android.sdk.internal.crypto.SecretShareManager
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationDone import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationDone
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationRequest import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationRequest
@ -67,8 +63,6 @@ import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
import org.matrix.android.sdk.internal.crypto.model.rest.toValue import org.matrix.android.sdk.internal.crypto.model.rest.toValue
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tools.withOlmUtility
import org.matrix.android.sdk.internal.crypto.verification.qrcode.QrCodeData import org.matrix.android.sdk.internal.crypto.verification.qrcode.QrCodeData
import org.matrix.android.sdk.internal.crypto.verification.qrcode.generateSharedSecretV2 import org.matrix.android.sdk.internal.crypto.verification.qrcode.generateSharedSecretV2
import org.matrix.android.sdk.internal.crypto.verification.qrcode.toQrCodeData import org.matrix.android.sdk.internal.crypto.verification.qrcode.toQrCodeData
@ -77,24 +71,17 @@ import org.matrix.android.sdk.internal.util.time.Clock
import timber.log.Timber import timber.log.Timber
import java.util.Locale import java.util.Locale
// data class AddRequestActions(
// val request: PendingVerificationRequest,
// // only allow one active verification between two users
// // so if there are already active requests they should be canceled
// val toCancel: List<PendingVerificationRequest>
// )
private val loggerTag = LoggerTag("Verification", LoggerTag.CRYPTO) private val loggerTag = LoggerTag("Verification", LoggerTag.CRYPTO)
internal class VerificationActor @AssistedInject constructor( internal class VerificationActor @AssistedInject constructor(
@Assisted private val scope: CoroutineScope, @Assisted private val scope: CoroutineScope,
private val clock: Clock, private val clock: Clock,
@UserId private val myUserId: String, @UserId private val myUserId: String,
private val cryptoStore: IMXCryptoStore,
private val setDeviceVerificationAction: SetDeviceVerificationAction,
private val crossSigningService: dagger.Lazy<CrossSigningService>,
private val secretShareManager: SecretShareManager, private val secretShareManager: SecretShareManager,
private val transportLayer: VerificationTransportLayer, private val transportLayer: VerificationTransportLayer,
private val verificationRequestsStore: VerificationRequestsStore,
private val olmPrimitiveProvider: VerificationCryptoPrimitiveProvider,
private val verificationTrustBackend: VerificationTrustBackend,
) { ) {
@AssistedFactory @AssistedFactory
@ -109,31 +96,15 @@ internal class VerificationActor @AssistedInject constructor(
init { init {
scope.launch { scope.launch {
Timber.e("VALR BEFORE")
for (msg in channel) { for (msg in channel) {
onReceive(msg) onReceive(msg)
} }
Timber.e("VALR NNNNNNNN")
} }
} }
// map [sender : [transaction]]
private val txMap = HashMap<String, MutableMap<String, VerificationTransaction>>()
// we need to keep track of finished transaction
// It will be used for gossiping (to send request after request is completed and 'done' by other)
private val pastTransactions = HashMap<String, MutableMap<String, VerificationTransaction>>()
/**
* Map [sender: [PendingVerificationRequest]]
* For now we keep all requests (even terminated ones) during the lifetime of the app.
*/
private val pendingRequests = HashMap<String, MutableList<KotlinVerificationRequest>>()
// Replaces the typical list of listeners pattern. // Replaces the typical list of listeners pattern.
// Looks to me as the sane setup, not sure if more than 1 is needed as extraBufferCapacity // Looks to me as the sane setup, not sure if more than 1 is needed as extraBufferCapacity
// So we should use try emit using extraBufferCapacity, we use drop_oldest instead of suspend. val eventFlow = MutableSharedFlow<VerificationEvent>(extraBufferCapacity = 20, onBufferOverflow = BufferOverflow.SUSPEND)
val eventFlow = MutableSharedFlow<VerificationEvent>(extraBufferCapacity = 4, onBufferOverflow = BufferOverflow.DROP_OLDEST)
suspend fun send(intent: VerificationIntent) { suspend fun send(intent: VerificationIntent) {
channel.send(intent) channel.send(intent)
@ -144,8 +115,7 @@ internal class VerificationActor @AssistedInject constructor(
requestId: String, requestId: String,
block: suspend ((KotlinVerificationRequest) -> Unit) block: suspend ((KotlinVerificationRequest) -> Unit)
) { ) {
val matchingRequest = pendingRequests[otherUserId] val matchingRequest = verificationRequestsStore.getExistingRequest(otherUserId, requestId)
?.firstOrNull { it.requestId == requestId }
?: return Unit.also { ?: return Unit.also {
// Receive a transaction event with no matching request.. should ignore. // Receive a transaction event with no matching request.. should ignore.
// Not supported any more to do raw start // Not supported any more to do raw start
@ -173,8 +143,7 @@ internal class VerificationActor @AssistedInject constructor(
viaRoom: String?, viaRoom: String?,
block: suspend ((KotlinVerificationRequest) -> Unit) block: suspend ((KotlinVerificationRequest) -> Unit)
) { ) {
val matchingRequest = pendingRequests[otherUserId] val matchingRequest = verificationRequestsStore.getExistingRequest(otherUserId, requestId)
?.firstOrNull { it.requestId == requestId }
?: return Unit.also { ?: return Unit.also {
// Receive a transaction event with no matching request.. should ignore. // Receive a transaction event with no matching request.. should ignore.
// Not supported any more to do raw start // Not supported any more to do raw start
@ -219,23 +188,11 @@ internal class VerificationActor @AssistedInject constructor(
is VerificationIntent.OnReadyReceived -> { is VerificationIntent.OnReadyReceived -> {
handleReadyReceived(msg) handleReadyReceived(msg)
} }
is VerificationIntent.FailToSendRequest -> {
// just delete it?
val requestsForUser = pendingRequests.getOrPut(msg.request.otherUserId) { mutableListOf() }
val index = requestsForUser.indexOfFirst {
it.requestId == msg.request.transactionId
}
if (index != -1) {
requestsForUser.removeAt(index)
}
}
// is VerificationIntent.UpdateRequest -> { // is VerificationIntent.UpdateRequest -> {
// updatePendingRequest(msg.request) // updatePendingRequest(msg.request)
// } // }
is VerificationIntent.GetExistingRequestInRoom -> { is VerificationIntent.GetExistingRequestInRoom -> {
val existing = pendingRequests.flatMap { entry -> val existing = verificationRequestsStore.getExistingRequestInRoom(msg.transactionId, msg.roomId)
entry.value.filter { it.roomId == msg.roomId && it.requestId == msg.transactionId }
}.firstOrNull()
msg.deferred.complete(existing?.toPendingVerificationRequest()) msg.deferred.complete(existing?.toPendingVerificationRequest())
} }
is VerificationIntent.OnVerificationRequestReceived -> { is VerificationIntent.OnVerificationRequestReceived -> {
@ -286,9 +243,7 @@ internal class VerificationActor @AssistedInject constructor(
} }
} }
is VerificationIntent.ActionCancel -> { is VerificationIntent.ActionCancel -> {
pendingRequests verificationRequestsStore.getExistingRequestWithRequestId(msg.transactionId)
.flatMap { it.value }
.firstOrNull { it.requestId == msg.transactionId }
?.let { matchingRequest -> ?.let { matchingRequest ->
try { try {
cancelRequest(matchingRequest, CancelCode.User) cancelRequest(matchingRequest, CancelCode.User)
@ -300,25 +255,27 @@ internal class VerificationActor @AssistedInject constructor(
} }
is VerificationIntent.OnUnableToDecryptVerificationEvent -> { is VerificationIntent.OnUnableToDecryptVerificationEvent -> {
// at least if request was sent by me, I can safely cancel without interfering // at least if request was sent by me, I can safely cancel without interfering
val matchingRequest = pendingRequests[msg.fromUser] val matchingRequest = verificationRequestsStore.getExistingRequest(msg.fromUser, msg.transactionId)
?.firstOrNull { it.requestId == msg.transactionId } ?: return ?: return
if (matchingRequest.state != EVerificationState.HandledByOtherSession) { if (matchingRequest.state != EVerificationState.HandledByOtherSession) {
cancelRequest(matchingRequest, CancelCode.InvalidMessage) cancelRequest(matchingRequest, CancelCode.InvalidMessage)
} }
} }
is VerificationIntent.GetExistingRequestsForUser -> { is VerificationIntent.GetExistingRequestsForUser -> {
pendingRequests[msg.userId].orEmpty().let { requests -> verificationRequestsStore.getExistingRequestsForUser(msg.userId).let { requests ->
msg.deferred.complete(requests.map { it.toPendingVerificationRequest() }) msg.deferred.complete(requests.map { it.toPendingVerificationRequest() })
} }
} }
is VerificationIntent.GetExistingTransaction -> { is VerificationIntent.GetExistingTransaction -> {
txMap[msg.fromUser]?.get(msg.transactionId)?.let { verificationRequestsStore
.getExistingTransaction(msg.fromUser, msg.transactionId)
?.let {
msg.deferred.complete(it) msg.deferred.complete(it)
} }
} }
is VerificationIntent.GetExistingRequest -> { is VerificationIntent.GetExistingRequest -> {
pendingRequests[msg.otherUserId] verificationRequestsStore
?.firstOrNull { msg.transactionId == it.requestId } .getExistingRequest(msg.otherUserId, msg.transactionId)
?.let { ?.let {
msg.deferred.complete(it.toPendingVerificationRequest()) msg.deferred.complete(it.toPendingVerificationRequest())
} }
@ -334,7 +291,7 @@ internal class VerificationActor @AssistedInject constructor(
getExistingTransaction(msg.validCancel.transactionId) // txMap[msg.fromUser]?.get(msg.validCancel.transactionId) getExistingTransaction(msg.validCancel.transactionId) // txMap[msg.fromUser]?.get(msg.validCancel.transactionId)
if (existingTx != null) { if (existingTx != null) {
existingTx.state = SasTransactionState.Cancelled(cancelCode, false) existingTx.state = SasTransactionState.Cancelled(cancelCode, false)
txMap[msg.fromUser]?.remove(msg.validCancel.transactionId) verificationRequestsStore.deleteTransaction(msg.fromUser, msg.validCancel.transactionId)
dispatchUpdate(VerificationEvent.TransactionUpdated(existingTx)) dispatchUpdate(VerificationEvent.TransactionUpdated(existingTx))
} }
dispatchUpdate(VerificationEvent.RequestUpdated(request.toPendingVerificationRequest())) dispatchUpdate(VerificationEvent.RequestUpdated(request.toPendingVerificationRequest()))
@ -348,8 +305,10 @@ internal class VerificationActor @AssistedInject constructor(
private fun dispatchUpdate(update: VerificationEvent) { private fun dispatchUpdate(update: VerificationEvent) {
// We don't want to block on emit. // We don't want to block on emit.
// If no subscriber there is a small buffer and too old would be dropped // If no subscriber there is a small buffer
eventFlow.tryEmit(update) scope.launch {
eventFlow.emit(update)
}
} }
private suspend fun handleIncomingRequest(msg: VerificationIntent.OnVerificationRequestReceived) { private suspend fun handleIncomingRequest(msg: VerificationIntent.OnVerificationRequestReceived) {
@ -363,15 +322,14 @@ internal class VerificationActor @AssistedInject constructor(
requestInfo = msg.validRequestInfo requestInfo = msg.validRequestInfo
roomId = msg.roomId roomId = msg.roomId
} }
verificationRequestsStore.addRequest(msg.senderId, pendingVerificationRequest)
pendingRequests.getOrPut(msg.senderId) { mutableListOf() }
.add(pendingVerificationRequest)
dispatchRequestAdded(pendingVerificationRequest) dispatchRequestAdded(pendingVerificationRequest)
} }
private suspend fun onStartReceived(msg: VerificationIntent.OnStartReceived) { private suspend fun onStartReceived(msg: VerificationIntent.OnStartReceived) {
val requestId = msg.validVerificationInfoStart.transactionId val requestId = msg.validVerificationInfoStart.transactionId
val matchingRequest = pendingRequests[msg.fromUser]?.firstOrNull { it.requestId == requestId } val matchingRequest = verificationRequestsStore
.getExistingRequestWithRequestId(msg.validVerificationInfoStart.transactionId)
?: return Unit.also { ?: return Unit.also {
// Receive a start with no matching request.. should ignore. // Receive a start with no matching request.. should ignore.
// Not supported any more to do raw start // Not supported any more to do raw start
@ -493,7 +451,7 @@ internal class VerificationActor @AssistedInject constructor(
cancelRequest(request, CancelCode.UnknownMethod) cancelRequest(request, CancelCode.UnknownMethod)
} }
// Bobs device ensures that it has a copy of Alices device key. // Bobs device ensures that it has a copy of Alices device key.
val mxDeviceInfo = cryptoStore.getUserDevice(userId = request.otherUserId, deviceId = otherDeviceId) val mxDeviceInfo = verificationTrustBackend.getUserDevice(request.otherUserId, otherDeviceId)
if (mxDeviceInfo?.fingerprint() == null) { if (mxDeviceInfo?.fingerprint() == null) {
Timber.e("## SAS Failed to find device key ") Timber.e("## SAS Failed to find device key ")
@ -509,19 +467,17 @@ internal class VerificationActor @AssistedInject constructor(
state = SasTransactionState.None, state = SasTransactionState.None,
otherUserId = request.otherUserId, otherUserId = request.otherUserId,
myUserId = myUserId, myUserId = myUserId,
myTrustedMSK = cryptoStore.getMyCrossSigningInfo() myTrustedMSK = verificationTrustBackend.getMyTrustedMasterKeyBase64(),
?.takeIf { it.isTrusted() }
?.masterKey()
?.unpaddedBase64PublicKey,
otherDeviceId = request.otherDeviceId(), otherDeviceId = request.otherDeviceId(),
myDeviceId = cryptoStore.getDeviceId(), myDeviceId = verificationTrustBackend.getMyDeviceId(),
myDeviceFingerprint = cryptoStore.getUserDevice(myUserId, cryptoStore.getDeviceId())?.fingerprint().orEmpty(), myDeviceFingerprint = verificationTrustBackend.getMyDevice().fingerprint().orEmpty(),
startReq = sasStart, startReq = sasStart,
isIncoming = true, isIncoming = true,
isToDevice = msg.viaRoom == null, isToDevice = msg.viaRoom == null,
olmSAS = olmPrimitiveProvider.provideOlmSas()
) )
val concat = sasTx.getSAS().publicKey + sasStart.canonicalJson val concat = sasTx.olmSAS.publicKey + sasStart.canonicalJson
val commitment = hashUsingAgreedHashMethod(agreedHash, concat) val commitment = hashUsingAgreedHashMethod(agreedHash, concat)
val accept = KotlinSasTransaction.sasAccept( val accept = KotlinSasTransaction.sasAccept(
@ -544,6 +500,7 @@ internal class VerificationActor @AssistedInject constructor(
} }
sasTx.accepted = accept.asValidObject() sasTx.accepted = accept.asValidObject()
sasTx.state = SasTransactionState.SasAccepted
addTransaction(sasTx) addTransaction(sasTx)
} }
@ -578,7 +535,7 @@ internal class VerificationActor @AssistedInject constructor(
// Alices device creates an ephemeral Curve25519 key pair (dA,QA), // Alices device creates an ephemeral Curve25519 key pair (dA,QA),
// and replies with a to_device message with type set to “m.key.verification.key”, sending Alices public key QA // and replies with a to_device message with type set to “m.key.verification.key”, sending Alices public key QA
val pubKey = existing.getSAS().publicKey val pubKey = existing.olmSAS.publicKey
val keyMessage = KotlinSasTransaction.sasKeyMessage(matchingRequest.roomId != null, requestId, pubKey) val keyMessage = KotlinSasTransaction.sasKeyMessage(matchingRequest.roomId != null, requestId, pubKey)
@ -606,10 +563,7 @@ internal class VerificationActor @AssistedInject constructor(
} }
private suspend fun handleSasStart(msg: VerificationIntent.ActionStartSasVerification) { private suspend fun handleSasStart(msg: VerificationIntent.ActionStartSasVerification) {
val matchingRequest = pendingRequests val matchingRequest = verificationRequestsStore.getExistingRequestWithRequestId(msg.requestId)
.flatMap { entry ->
entry.value.filter { it.requestId == msg.requestId }
}.firstOrNull()
?: return Unit.also { ?: return Unit.also {
msg.deferred.completeExceptionally(java.lang.IllegalArgumentException("Unknown request")) msg.deferred.completeExceptionally(java.lang.IllegalArgumentException("Unknown request"))
} }
@ -631,7 +585,7 @@ internal class VerificationActor @AssistedInject constructor(
} }
val startMessage = KotlinSasTransaction.sasStart( val startMessage = KotlinSasTransaction.sasStart(
inRoom = matchingRequest.roomId != null, inRoom = matchingRequest.roomId != null,
fromDevice = cryptoStore.getDeviceId(), fromDevice = verificationTrustBackend.getMyDeviceId(),
requestId = msg.requestId requestId = msg.requestId
) )
@ -648,16 +602,14 @@ internal class VerificationActor @AssistedInject constructor(
state = SasTransactionState.SasStarted, state = SasTransactionState.SasStarted,
otherUserId = msg.otherUserId, otherUserId = msg.otherUserId,
myUserId = myUserId, myUserId = myUserId,
myTrustedMSK = cryptoStore.getMyCrossSigningInfo() myTrustedMSK = verificationTrustBackend.getMyTrustedMasterKeyBase64(),
?.takeIf { it.isTrusted() }
?.masterKey()
?.unpaddedBase64PublicKey,
otherDeviceId = otherDeviceId, otherDeviceId = otherDeviceId,
myDeviceId = cryptoStore.getDeviceId(), myDeviceId = verificationTrustBackend.getMyDeviceId(),
myDeviceFingerprint = cryptoStore.getUserDevice(myUserId, cryptoStore.getDeviceId())?.fingerprint().orEmpty(), myDeviceFingerprint = verificationTrustBackend.getMyDevice().fingerprint().orEmpty(),
startReq = startMessage.asValidObject() as ValidVerificationInfoStart.SasVerificationInfoStart, startReq = startMessage.asValidObject() as ValidVerificationInfoStart.SasVerificationInfoStart,
isIncoming = false, isIncoming = false,
isToDevice = matchingRequest.roomId == null isToDevice = matchingRequest.roomId == null,
olmSAS = olmPrimitiveProvider.provideOlmSas()
) )
matchingRequest.state = EVerificationState.WeStarted matchingRequest.state = EVerificationState.WeStarted
@ -670,10 +622,7 @@ internal class VerificationActor @AssistedInject constructor(
private suspend fun handleActionReciprocateQR(msg: VerificationIntent.ActionReciprocateQrVerification) { private suspend fun handleActionReciprocateQR(msg: VerificationIntent.ActionReciprocateQrVerification) {
Timber.tag(loggerTag.value) Timber.tag(loggerTag.value)
.d("[${myUserId.take(8)}] handle reciprocate for ${msg.requestId}") .d("[${myUserId.take(8)}] handle reciprocate for ${msg.requestId}")
val matchingRequest = pendingRequests val matchingRequest = verificationRequestsStore.getExistingRequestWithRequestId(msg.requestId)
.flatMap { entry ->
entry.value.filter { it.requestId == msg.requestId }
}.firstOrNull()
?: return Unit.also { ?: return Unit.also {
Timber.tag(loggerTag.value) Timber.tag(loggerTag.value)
.d("[${myUserId.take(8)}] No matching request, abort ${msg.requestId}") .d("[${myUserId.take(8)}] No matching request, abort ${msg.requestId}")
@ -700,8 +649,7 @@ internal class VerificationActor @AssistedInject constructor(
return return
} }
val myMasterKey = crossSigningService.get() val myMasterKey = verificationTrustBackend.getUserMasterKeyBase64(myUserId)
.getUserCrossSigningKeys(myUserId)?.masterKey()?.unpaddedBase64PublicKey
// Check the other device view of my MSK // Check the other device view of my MSK
val otherQrCodeData = msg.scannedData.toQrCodeData() val otherQrCodeData = msg.scannedData.toQrCodeData()
@ -725,9 +673,7 @@ internal class VerificationActor @AssistedInject constructor(
return return
} }
val whatIThinkOtherMskIs = crossSigningService.get().getUserCrossSigningKeys(matchingRequest.otherUserId) val whatIThinkOtherMskIs = verificationTrustBackend.getUserMasterKeyBase64(matchingRequest.otherUserId)
?.masterKey()
?.unpaddedBase64PublicKey
if (whatIThinkOtherMskIs != otherQrCodeData.userMasterCrossSigningPublicKey) { if (whatIThinkOtherMskIs != otherQrCodeData.userMasterCrossSigningPublicKey) {
Timber.tag(loggerTag.value) Timber.tag(loggerTag.value)
.d("[${myUserId.take(8)}] ## Verification QR: Invalid other master key ${otherQrCodeData.otherUserMasterCrossSigningPublicKey}") .d("[${myUserId.take(8)}] ## Verification QR: Invalid other master key ${otherQrCodeData.otherUserMasterCrossSigningPublicKey}")
@ -756,7 +702,7 @@ internal class VerificationActor @AssistedInject constructor(
return return
} }
val whatOtherThinkMyDeviceKeyIs = otherQrCodeData.otherDeviceKey val whatOtherThinkMyDeviceKeyIs = otherQrCodeData.otherDeviceKey
val myDeviceKey = cryptoStore.getUserDevice(myUserId, cryptoStore.getDeviceId())?.fingerprint() val myDeviceKey = verificationTrustBackend.getMyDevice().fingerprint()
if (whatOtherThinkMyDeviceKeyIs != myDeviceKey) { if (whatOtherThinkMyDeviceKeyIs != myDeviceKey) {
Timber.tag(loggerTag.value) Timber.tag(loggerTag.value)
.d("[${myUserId.take(8)}] ## Verification QR: Invalid other device key ${otherQrCodeData.userMasterCrossSigningPublicKey}") .d("[${myUserId.take(8)}] ## Verification QR: Invalid other device key ${otherQrCodeData.userMasterCrossSigningPublicKey}")
@ -777,7 +723,7 @@ internal class VerificationActor @AssistedInject constructor(
// Let's check that it's the good one // Let's check that it's the good one
// If not -> Cancel // If not -> Cancel
val otherDeclaredDeviceKey = otherQrCodeData.deviceKey val otherDeclaredDeviceKey = otherQrCodeData.deviceKey
val whatIThinkItIs = cryptoStore.getUserDevice(myUserId, otherDeviceId)?.fingerprint() val whatIThinkItIs = verificationTrustBackend.getUserDevice(matchingRequest.otherUserId, otherDeviceId)?.fingerprint()
if (otherDeclaredDeviceKey != whatIThinkItIs) { if (otherDeclaredDeviceKey != whatIThinkItIs) {
Timber.tag(loggerTag.value) Timber.tag(loggerTag.value)
@ -802,7 +748,7 @@ internal class VerificationActor @AssistedInject constructor(
// qrCodeData.sharedSecret will be used to send the start request // qrCodeData.sharedSecret will be used to send the start request
val message = if (matchingRequest.roomId != null) { val message = if (matchingRequest.roomId != null) {
MessageVerificationStartContent( MessageVerificationStartContent(
fromDevice = cryptoStore.getDeviceId(), fromDevice = verificationTrustBackend.getMyDeviceId(),
hashes = null, hashes = null,
keyAgreementProtocols = null, keyAgreementProtocols = null,
messageAuthenticationCodes = null, messageAuthenticationCodes = null,
@ -816,7 +762,7 @@ internal class VerificationActor @AssistedInject constructor(
) )
} else { } else {
KeyVerificationStart( KeyVerificationStart(
fromDevice = cryptoStore.getDeviceId(), fromDevice = verificationTrustBackend.getMyDeviceId(),
sharedSecret = otherQrCodeData.sharedSecret, sharedSecret = otherQrCodeData.sharedSecret,
method = VERIFICATION_METHOD_RECIPROCATE, method = VERIFICATION_METHOD_RECIPROCATE,
) )
@ -896,7 +842,7 @@ internal class VerificationActor @AssistedInject constructor(
val otherKey = msg.validKey.key val otherKey = msg.validKey.key
if (existing.isIncoming) { if (existing.isIncoming) {
// ok i can now send my key and compute the sas code // ok i can now send my key and compute the sas code
val pubKey = existing.getSAS().publicKey val pubKey = existing.olmSAS.publicKey
val keyMessage = KotlinSasTransaction.sasKeyMessage(matchingRequest.roomId != null, requestId, pubKey) val keyMessage = KotlinSasTransaction.sasKeyMessage(matchingRequest.roomId != null, requestId, pubKey)
try { try {
transportLayer.sendToOther( transportLayer.sendToOther(
@ -945,7 +891,7 @@ internal class VerificationActor @AssistedInject constructor(
if (otherCommitment == existing.accepted?.commitment) { if (otherCommitment == existing.accepted?.commitment) {
if (BuildConfig.LOG_PRIVATE_DATA) { if (BuildConfig.LOG_PRIVATE_DATA) {
Timber.tag(loggerTag.value) Timber.tag(loggerTag.value)
.v("[${myUserId.take(8)}]:o calculate SAS my key ${existing.getSAS().publicKey} their Key: $otherKey") .v("[${myUserId.take(8)}]:o calculate SAS my key ${existing.olmSAS.publicKey} their Key: $otherKey")
} }
existing.calculateSASBytes(otherKey) existing.calculateSASBytes(otherKey)
existing.state = SasTransactionState.SasShortCodeReady existing.state = SasTransactionState.SasShortCodeReady
@ -997,7 +943,7 @@ internal class VerificationActor @AssistedInject constructor(
private suspend fun handleSasCodeDoesNotMatch(msg: VerificationIntent.ActionSASCodeDoesNotMatch) { private suspend fun handleSasCodeDoesNotMatch(msg: VerificationIntent.ActionSASCodeDoesNotMatch) {
val transactionId = msg.transactionId val transactionId = msg.transactionId
val matchingRequest = pendingRequests.flatMap { it.value }.firstOrNull { it.requestId == transactionId } val matchingRequest = verificationRequestsStore.getExistingRequestWithRequestId(msg.transactionId)
?: return Unit.also { ?: return Unit.also {
msg.deferred.completeExceptionally(IllegalStateException("Unknown Request")) msg.deferred.completeExceptionally(IllegalStateException("Unknown Request"))
} }
@ -1046,7 +992,7 @@ internal class VerificationActor @AssistedInject constructor(
existing.state = SasTransactionState.Done(true) existing.state = SasTransactionState.Done(true)
dispatchUpdate(VerificationEvent.TransactionUpdated(existing)) dispatchUpdate(VerificationEvent.TransactionUpdated(existing))
// we can forget about it // we can forget about it
txMap[matchingRequest.otherUserId]?.remove(matchingRequest.requestId) verificationRequestsStore.deleteTransaction(matchingRequest.otherUserId, matchingRequest.requestId)
// XXX whatabout waiting for done? // XXX whatabout waiting for done?
matchingRequest.state = EVerificationState.Done matchingRequest.state = EVerificationState.Done
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest())) dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
@ -1090,7 +1036,7 @@ internal class VerificationActor @AssistedInject constructor(
// let's trust him // let's trust him
// it's his code scanned so user is him and other me // it's his code scanned so user is him and other me
try { try {
crossSigningService.get().trustUser(matchingRequest.otherUserId) verificationTrustBackend.trustUser(matchingRequest.otherUserId)
} catch (failure: Throwable) { } catch (failure: Throwable) {
// fail silently? // fail silently?
// at least it will be marked as trusted locally? // at least it will be marked as trusted locally?
@ -1103,7 +1049,7 @@ internal class VerificationActor @AssistedInject constructor(
// Also notify the secret share manager for the soon to come secret share requests // Also notify the secret share manager for the soon to come secret share requests
secretShareManager.onVerificationCompleteForDevice(matchingRequest.otherDeviceId()!!) secretShareManager.onVerificationCompleteForDevice(matchingRequest.otherDeviceId()!!)
try { try {
crossSigningService.get().trustDevice(matchingRequest.otherDeviceId()!!) verificationTrustBackend.trustOwnDevice(matchingRequest.otherDeviceId()!!)
} catch (failure: Throwable) { } catch (failure: Throwable) {
// network problem?? // network problem??
Timber.w("## Verification: Failed to sign new device ${matchingRequest.otherDeviceId()}, ${failure.localizedMessage}") Timber.w("## Verification: Failed to sign new device ${matchingRequest.otherDeviceId()}, ${failure.localizedMessage}")
@ -1111,7 +1057,7 @@ internal class VerificationActor @AssistedInject constructor(
} }
is QrCodeData.SelfVerifyingMasterKeyTrusted -> { is QrCodeData.SelfVerifyingMasterKeyTrusted -> {
// I can trust my MSK // I can trust my MSK
crossSigningService.get().markMyMasterKeyAsTrusted() verificationTrustBackend.markMyMasterKeyAsTrusted()
shouldRequestSecret = true shouldRequestSecret = true
} }
null -> { null -> {
@ -1137,7 +1083,7 @@ internal class VerificationActor @AssistedInject constructor(
existing.state = QRCodeVerificationState.Done existing.state = QRCodeVerificationState.Done
dispatchUpdate(VerificationEvent.TransactionUpdated(existing)) dispatchUpdate(VerificationEvent.TransactionUpdated(existing))
// we can forget about it // we can forget about it
txMap[matchingRequest.otherUserId]?.remove(matchingRequest.requestId) verificationRequestsStore.deleteTransaction(matchingRequest.otherUserId, matchingRequest.requestId)
matchingRequest.state = EVerificationState.WaitingForDone matchingRequest.state = EVerificationState.WaitingForDone
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest())) dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
@ -1153,7 +1099,7 @@ internal class VerificationActor @AssistedInject constructor(
private suspend fun handleSasCodeMatch(msg: VerificationIntent.ActionSASCodeMatches) { private suspend fun handleSasCodeMatch(msg: VerificationIntent.ActionSASCodeMatches) {
val transactionId = msg.transactionId val transactionId = msg.transactionId
val matchingRequest = pendingRequests.flatMap { it.value }.firstOrNull { it.requestId == transactionId } val matchingRequest = verificationRequestsStore.getExistingRequestWithRequestId(msg.transactionId)
?: return Unit.also { ?: return Unit.also {
msg.deferred.completeExceptionally(IllegalStateException("Unknown Request")) msg.deferred.completeExceptionally(IllegalStateException("Unknown Request"))
} }
@ -1212,8 +1158,8 @@ internal class VerificationActor @AssistedInject constructor(
) { ) {
val result = existing.verifyMacs( val result = existing.verifyMacs(
theirMac, theirMac,
cryptoStore.getUserDeviceList(matchingRequest.otherUserId).orEmpty(), verificationTrustBackend.getUserDeviceList(matchingRequest.otherUserId),
cryptoStore.getCrossSigningInfo(matchingRequest.otherUserId)?.masterKey()?.unpaddedBase64PublicKey verificationTrustBackend.getUserMasterKeyBase64(matchingRequest.otherUserId)
) )
Timber.tag(loggerTag.value) Timber.tag(loggerTag.value)
@ -1222,20 +1168,13 @@ internal class VerificationActor @AssistedInject constructor(
is KotlinSasTransaction.MacVerificationResult.Success -> { is KotlinSasTransaction.MacVerificationResult.Success -> {
// mark the devices as locally trusted // mark the devices as locally trusted
result.verifiedDeviceId.forEach { deviceId -> result.verifiedDeviceId.forEach { deviceId ->
val actualTrustLevel = cryptoStore.getUserDevice(matchingRequest.otherUserId, deviceId)?.trustLevel
setDeviceVerificationAction.handle(
trustLevel = DeviceTrustLevel(
actualTrustLevel?.crossSigningVerified == true,
true
),
userId = matchingRequest.otherUserId,
deviceId = deviceId
)
if (matchingRequest.otherUserId == myUserId && crossSigningService.get().canCrossSign()) { verificationTrustBackend.locallyTrustDevice(matchingRequest.otherUserId, deviceId)
if (matchingRequest.otherUserId == myUserId && verificationTrustBackend.canCrossSign()) {
// If me it's reasonable to sign and upload the device signature for the other part // If me it's reasonable to sign and upload the device signature for the other part
try { try {
crossSigningService.get().trustDevice(deviceId) verificationTrustBackend.trustOwnDevice(deviceId)
} catch (failure: Throwable) { } catch (failure: Throwable) {
// network problem?? // network problem??
Timber.w("## Verification: Failed to sign new device $deviceId, ${failure.localizedMessage}") Timber.w("## Verification: Failed to sign new device $deviceId, ${failure.localizedMessage}")
@ -1245,11 +1184,11 @@ internal class VerificationActor @AssistedInject constructor(
if (result.otherMskTrusted) { if (result.otherMskTrusted) {
if (matchingRequest.otherUserId == myUserId) { if (matchingRequest.otherUserId == myUserId) {
cryptoStore.markMyMasterKeyAsLocallyTrusted(true) verificationTrustBackend.markMyMasterKeyAsTrusted()
} else { } else {
// what should we do if this fails :/ // what should we do if this fails :/
if (crossSigningService.get().canCrossSign()) { if (verificationTrustBackend.canCrossSign()) {
crossSigningService.get().trustUser(matchingRequest.otherUserId) verificationTrustBackend.trustUser(matchingRequest.otherUserId)
} }
} }
} }
@ -1272,8 +1211,8 @@ internal class VerificationActor @AssistedInject constructor(
existing.state = SasTransactionState.Done(false) existing.state = SasTransactionState.Done(false)
dispatchUpdate(VerificationEvent.TransactionUpdated(existing)) dispatchUpdate(VerificationEvent.TransactionUpdated(existing))
pastTransactions.getOrPut(transactionId) { mutableMapOf() }[transactionId] = existing verificationRequestsStore.rememberPastSuccessfulTransaction(existing)
txMap[matchingRequest.otherUserId]?.remove(transactionId) verificationRequestsStore.deleteTransaction(matchingRequest.otherUserId, transactionId)
matchingRequest.state = EVerificationState.WaitingForDone matchingRequest.state = EVerificationState.WaitingForDone
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest())) dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
} }
@ -1287,9 +1226,7 @@ internal class VerificationActor @AssistedInject constructor(
} }
private suspend fun handleActionReadyRequest(msg: VerificationIntent.ActionReadyRequest) { private suspend fun handleActionReadyRequest(msg: VerificationIntent.ActionReadyRequest) {
val existing = pendingRequests val existing = verificationRequestsStore.getExistingRequestWithRequestId(msg.transactionId)
.flatMap { it.value }
.firstOrNull { it.requestId == msg.transactionId }
?: return Unit.also { ?: return Unit.also {
Timber.tag(loggerTag.value).v("Request ${msg.transactionId} not found!") Timber.tag(loggerTag.value).v("Request ${msg.transactionId} not found!")
msg.deferred.complete(null) msg.deferred.complete(null)
@ -1308,13 +1245,11 @@ internal class VerificationActor @AssistedInject constructor(
) )
if (commonMethods.isEmpty()) { if (commonMethods.isEmpty()) {
Timber.tag(loggerTag.value).v("Request ${msg.transactionId} no common methods") Timber.tag(loggerTag.value).v("Request ${msg.transactionId} no common methods")
cancelRequest(existing, CancelCode.UnknownMethod)
// Upon receipt of Alices m.key.verification.request message, if Bobs device does not understand any of the methods, // Upon receipt of Alices m.key.verification.request message, if Bobs device does not understand any of the methods,
// it should not cancel the request as one of his other devices may support the request. // it should not cancel the request as one of his other devices may support the request.
// XXX How to o that??
// Instead, Bobs device should tell Bob that no supported method was found, and allow him to manually reject the request. // Instead, Bobs device should tell Bob that no supported method was found, and allow him to manually reject the request.
msg.deferred.complete(null) msg.deferred.completeExceptionally(IllegalStateException("Cannot understand any of the methods"))
return return
} }
@ -1329,7 +1264,7 @@ internal class VerificationActor @AssistedInject constructor(
val readyInfo = ValidVerificationInfoReady( val readyInfo = ValidVerificationInfoReady(
msg.transactionId, msg.transactionId,
cryptoStore.getDeviceId(), verificationTrustBackend.getMyDeviceId(),
commonMethods commonMethods
) )
@ -1337,7 +1272,7 @@ internal class VerificationActor @AssistedInject constructor(
inRoom = existing.roomId != null, inRoom = existing.roomId != null,
requestId = msg.transactionId, requestId = msg.transactionId,
methods = commonMethods, methods = commonMethods,
fromDevice = cryptoStore.getDeviceId() fromDevice = verificationTrustBackend.getMyDeviceId()
) )
try { try {
transportLayer.sendToOther(existing, EventType.KEY_VERIFICATION_READY, message) transportLayer.sendToOther(existing, EventType.KEY_VERIFICATION_READY, message)
@ -1357,11 +1292,11 @@ internal class VerificationActor @AssistedInject constructor(
msg.deferred.complete(existing.toPendingVerificationRequest()) msg.deferred.complete(existing.toPendingVerificationRequest())
} }
private fun createQrCodeData(requestId: String, otherUserId: String, otherDeviceId: String?): QrCodeData? { private suspend fun createQrCodeData(requestId: String, otherUserId: String, otherDeviceId: String?): QrCodeData? {
return when { return when {
myUserId != otherUserId -> myUserId != otherUserId ->
createQrCodeDataForDistinctUser(requestId, otherUserId) createQrCodeDataForDistinctUser(requestId, otherUserId)
cryptoStore.getMyCrossSigningInfo()?.isTrusted().orFalse() -> verificationTrustBackend.getMyTrustedMasterKeyBase64() != null ->
// This is a self verification and I am the old device (Osborne2) // This is a self verification and I am the old device (Osborne2)
createQrCodeDataForVerifiedDevice(requestId, otherUserId, otherDeviceId) createQrCodeDataForVerifiedDevice(requestId, otherUserId, otherDeviceId)
else -> else ->
@ -1410,7 +1345,7 @@ internal class VerificationActor @AssistedInject constructor(
} }
private suspend fun handleActionRequestVerification(msg: VerificationIntent.ActionRequestVerification) { private suspend fun handleActionRequestVerification(msg: VerificationIntent.ActionRequestVerification) {
val requestsForUser = pendingRequests.getOrPut(msg.otherUserId) { mutableListOf() } val requestsForUser = verificationRequestsStore.getExistingRequestsForUser(msg.otherUserId)
// there can only be one active request per user, so cancel existing ones // there can only be one active request per user, so cancel existing ones
requestsForUser.toList().forEach { existingRequest -> requestsForUser.toList().forEach { existingRequest ->
if (!existingRequest.isFinished()) { if (!existingRequest.isFinished()) {
@ -1419,7 +1354,7 @@ internal class VerificationActor @AssistedInject constructor(
} }
} }
val methodValues = if (cryptoStore.getMyCrossSigningInfo()?.isTrusted().orFalse()) { val methodValues = if (verificationTrustBackend.getMyTrustedMasterKeyBase64() != null) {
// Add reciprocate method if application declares it can scan or show QR codes // Add reciprocate method if application declares it can scan or show QR codes
// Not sure if it ok to do that (?) // Not sure if it ok to do that (?)
val reciprocateMethod = msg.methods val reciprocateMethod = msg.methods
@ -1436,7 +1371,7 @@ internal class VerificationActor @AssistedInject constructor(
val validInfo = ValidVerificationInfoRequest( val validInfo = ValidVerificationInfoRequest(
transactionId = "", transactionId = "",
fromDevice = cryptoStore.getDeviceId(), fromDevice = verificationTrustBackend.getMyDeviceId(),
methods = methodValues, methods = methodValues,
timestamp = clock.epochMillis() timestamp = clock.epochMillis()
) )
@ -1466,7 +1401,7 @@ internal class VerificationActor @AssistedInject constructor(
roomId = msg.roomId roomId = msg.roomId
requestInfo = validInfo.copy(transactionId = eventId) requestInfo = validInfo.copy(transactionId = eventId)
} }
requestsForUser.add(request) verificationRequestsStore.addRequest(msg.otherUserId, request)
msg.deferred.complete(request.toPendingVerificationRequest()) msg.deferred.complete(request.toPendingVerificationRequest())
dispatchUpdate(VerificationEvent.RequestAdded(request.toPendingVerificationRequest())) dispatchUpdate(VerificationEvent.RequestAdded(request.toPendingVerificationRequest()))
} else { } else {
@ -1475,7 +1410,7 @@ internal class VerificationActor @AssistedInject constructor(
messageType = EventType.KEY_VERIFICATION_REQUEST, messageType = EventType.KEY_VERIFICATION_REQUEST,
toSendToDeviceObject = KeyVerificationRequest( toSendToDeviceObject = KeyVerificationRequest(
transactionId = requestId, transactionId = requestId,
fromDevice = cryptoStore.getDeviceId(), fromDevice = verificationTrustBackend.getMyDeviceId(),
methods = validInfo.methods, methods = validInfo.methods,
timestamp = validInfo.timestamp timestamp = validInfo.timestamp
), ),
@ -1493,7 +1428,7 @@ internal class VerificationActor @AssistedInject constructor(
roomId = null roomId = null
requestInfo = validInfo.copy(transactionId = requestId) requestInfo = validInfo.copy(transactionId = requestId)
} }
requestsForUser.add(request) verificationRequestsStore.addRequest(msg.otherUserId, request)
msg.deferred.complete(request.toPendingVerificationRequest()) msg.deferred.complete(request.toPendingVerificationRequest())
dispatchUpdate(VerificationEvent.RequestAdded(request.toPendingVerificationRequest())) dispatchUpdate(VerificationEvent.RequestAdded(request.toPendingVerificationRequest()))
} }
@ -1505,13 +1440,13 @@ internal class VerificationActor @AssistedInject constructor(
} }
private suspend fun handleReadyReceived(msg: VerificationIntent.OnReadyReceived) { private suspend fun handleReadyReceived(msg: VerificationIntent.OnReadyReceived) {
val matchingRequest = pendingRequests[msg.fromUser]?.firstOrNull { it.requestId == msg.transactionId } val matchingRequest = verificationRequestsStore.getExistingRequest(msg.fromUser, msg.transactionId)
?: return Unit.also { ?: return Unit.also {
Timber.tag(loggerTag.value) Timber.tag(loggerTag.value)
.v("[${myUserId.take(8)}]: No matching request to ready tId:${msg.transactionId}") .v("[${myUserId.take(8)}]: No matching request to ready tId:${msg.transactionId}")
// cancelRequest(msg.transactionId, msg.viaRoom, msg.fromUser, msg.readyInfo.fromDevice, CancelCode.UnknownTransaction) // cancelRequest(msg.transactionId, msg.viaRoom, msg.fromUser, msg.readyInfo.fromDevice, CancelCode.UnknownTransaction)
} }
val myDevice = cryptoStore.getDeviceId() val myDevice = verificationTrustBackend.getMyDeviceId()
if (matchingRequest.state != EVerificationState.WaitingForReady) { if (matchingRequest.state != EVerificationState.WaitingForReady) {
cancelRequest(matchingRequest, CancelCode.UnexpectedMessage) cancelRequest(matchingRequest, CancelCode.UnexpectedMessage)
@ -1552,11 +1487,11 @@ internal class VerificationActor @AssistedInject constructor(
if (msg.viaRoom == null) { if (msg.viaRoom == null) {
// we should cancel to others if it was requested via to_device // we should cancel to others if it was requested via to_device
// via room the other session will see the ready in room an mark the transaction as inactive for them // via room the other session will see the ready in room an mark the transaction as inactive for them
val deviceIds = cryptoStore.getUserDevices(matchingRequest.otherUserId)?.keys val deviceIds = verificationTrustBackend.getUserDeviceList(matchingRequest.otherUserId)
?.filter { it != msg.readyInfo.fromDevice } .filter { it.deviceId != msg.readyInfo.fromDevice }
// if it's me we don't want to send self cancel // if it's me we don't want to send self cancel
?.filter { it != myDevice } .filter { it.deviceId != myDevice }
.orEmpty() .map { it.deviceId }
try { try {
transportLayer.sendToDeviceEvent( transportLayer.sendToDeviceEvent(
@ -1577,7 +1512,7 @@ internal class VerificationActor @AssistedInject constructor(
} }
private suspend fun handleReadyByAnotherOfMySessionReceived(msg: VerificationIntent.OnReadyByAnotherOfMySessionReceived) { private suspend fun handleReadyByAnotherOfMySessionReceived(msg: VerificationIntent.OnReadyByAnotherOfMySessionReceived) {
val matchingRequest = pendingRequests[msg.fromUser]?.firstOrNull { it.requestId == msg.transactionId } val matchingRequest = verificationRequestsStore.getExistingRequest(msg.fromUser, msg.transactionId)
?: return ?: return
// it's a ready from another of my devices, so we should just // it's a ready from another of my devices, so we should just
@ -1602,25 +1537,21 @@ internal class VerificationActor @AssistedInject constructor(
// dispatchUpdate(VerificationEvent.RequestUpdated(updated)) // dispatchUpdate(VerificationEvent.RequestUpdated(updated))
// } // }
private suspend fun dispatchRequestAdded(tx: KotlinVerificationRequest) { private fun dispatchRequestAdded(tx: KotlinVerificationRequest) {
Timber.v("## SAS dispatchRequestAdded txId:${tx.requestId}") Timber.v("## SAS dispatchRequestAdded txId:${tx.requestId}")
dispatchUpdate(VerificationEvent.RequestAdded(tx.toPendingVerificationRequest())) dispatchUpdate(VerificationEvent.RequestAdded(tx.toPendingVerificationRequest()))
} }
// Utilities // Utilities
private fun createQrCodeDataForDistinctUser(requestId: String, otherUserId: String): QrCodeData.VerifyingAnotherUser? { private suspend fun createQrCodeDataForDistinctUser(requestId: String, otherUserId: String): QrCodeData.VerifyingAnotherUser? {
val myMasterKey = cryptoStore.getMyCrossSigningInfo() val myMasterKey = verificationTrustBackend.getMyTrustedMasterKeyBase64()
?.masterKey()
?.unpaddedBase64PublicKey
?: run { ?: run {
Timber.w("## Unable to get my master key") Timber.w("## Unable to get my master key")
return null return null
} }
val otherUserMasterKey = cryptoStore.getCrossSigningInfo(otherUserId) val otherUserMasterKey = verificationTrustBackend.getUserMasterKeyBase64(otherUserId)
?.masterKey()
?.unpaddedBase64PublicKey
?: run { ?: run {
Timber.w("## Unable to get other user master key") Timber.w("## Unable to get other user master key")
return null return null
@ -1635,10 +1566,8 @@ internal class VerificationActor @AssistedInject constructor(
} }
// Create a QR code to display on the old device (Osborne2) // Create a QR code to display on the old device (Osborne2)
private fun createQrCodeDataForVerifiedDevice(requestId: String, otherUserId: String, otherDeviceId: String?): QrCodeData.SelfVerifyingMasterKeyTrusted? { private suspend fun createQrCodeDataForVerifiedDevice(requestId: String, otherUserId: String, otherDeviceId: String?): QrCodeData.SelfVerifyingMasterKeyTrusted? {
val myMasterKey = cryptoStore.getMyCrossSigningInfo() val myMasterKey = verificationTrustBackend.getUserMasterKeyBase64(myUserId)
?.masterKey()
?.unpaddedBase64PublicKey
?: run { ?: run {
Timber.w("## Unable to get my master key") Timber.w("## Unable to get my master key")
return null return null
@ -1646,7 +1575,7 @@ internal class VerificationActor @AssistedInject constructor(
val otherDeviceKey = otherDeviceId val otherDeviceKey = otherDeviceId
?.let { ?.let {
cryptoStore.getUserDevice(otherUserId, otherDeviceId)?.fingerprint() verificationTrustBackend.getUserDevice(otherUserId, otherDeviceId)?.fingerprint()
} }
?: run { ?: run {
Timber.w("## Unable to get other device data") Timber.w("## Unable to get other device data")
@ -1662,16 +1591,14 @@ internal class VerificationActor @AssistedInject constructor(
} }
// Create a QR code to display on the new device (Dynabook) // Create a QR code to display on the new device (Dynabook)
private fun createQrCodeDataForUnVerifiedDevice(requestId: String): QrCodeData.SelfVerifyingMasterKeyNotTrusted? { private suspend fun createQrCodeDataForUnVerifiedDevice(requestId: String): QrCodeData.SelfVerifyingMasterKeyNotTrusted? {
val myMasterKey = cryptoStore.getMyCrossSigningInfo() val myMasterKey = verificationTrustBackend.getUserMasterKeyBase64(myUserId)
?.masterKey()
?.unpaddedBase64PublicKey
?: run { ?: run {
Timber.w("## Unable to get my master key") Timber.w("## Unable to get my master key")
return null return null
} }
val myDeviceKey = cryptoStore.getUserDevice(myUserId, cryptoStore.getDeviceId())?.fingerprint() val myDeviceKey = verificationTrustBackend.getUserDevice(myUserId, verificationTrustBackend.getMyDeviceId())?.fingerprint()
?: return null.also { ?: return null.also {
Timber.w("## Unable to get my fingerprint") Timber.w("## Unable to get my fingerprint")
} }
@ -1684,62 +1611,6 @@ internal class VerificationActor @AssistedInject constructor(
) )
} }
// private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
// return Event(
// roomId = roomId,
// originServerTs = clock.epochMillis(),
// senderId = myUserId,
// eventId = localId,
// type = type,
// content = content,
// unsignedData = UnsignedData(age = null, transactionId = localId)
// ).also {
// localEchoEventFactory.createLocalEcho(it)
// }
// }
//
// private suspend fun sendEventInRoom(event: Event): String {
// return sendVerificationMessageTask.execute(SendVerificationMessageTask.Params(event, 5)).eventId
// }
//
// private suspend fun sendToDeviceEvent(messageType: String, toSendToDeviceObject: SendToDeviceObject, otherUserId: String, targetDevices: List<String>) {
// // TODO currently to device verification messages are sent unencrypted
// // as per spec not recommended
// // > verification messages may be sent unencrypted, though this is not encouraged.
//
// val contentMap = MXUsersDevicesMap<Any>()
//
// targetDevices.forEach {
// contentMap.setObject(otherUserId, it, toSendToDeviceObject)
// }
//
// sendToDeviceTask
// .execute(SendToDeviceTask.Params(messageType, contentMap))
// }
//
// suspend fun sendToOther(
// request: KotlinVerificationRequest,
// type: String,
// verificationInfo: VerificationInfo<*>,
// ) {
// val roomId = request.roomId
// if (roomId != null) {
// val event = createEventAndLocalEcho(
// type = type,
// roomId = roomId,
// content = verificationInfo.toEventContent()!!
// )
// sendEventInRoom(event)
// } else {
// sendToDeviceEvent(
// type,
// verificationInfo.toSendToDeviceObject()!!,
// request.otherUserId,
// request.otherDeviceId()?.let { listOf(it) }.orEmpty()
// )
// }
// }
private suspend fun cancelRequest(request: KotlinVerificationRequest, code: CancelCode) { private suspend fun cancelRequest(request: KotlinVerificationRequest, code: CancelCode) {
request.state = EVerificationState.Cancelled request.state = EVerificationState.Cancelled
request.cancelCode = code request.cancelCode = code
@ -1748,12 +1619,12 @@ internal class VerificationActor @AssistedInject constructor(
// should also update SAS/QR transaction // should also update SAS/QR transaction
getExistingTransaction<KotlinSasTransaction>(request.otherUserId, request.requestId)?.let { getExistingTransaction<KotlinSasTransaction>(request.otherUserId, request.requestId)?.let {
it.state = SasTransactionState.Cancelled(code, true) it.state = SasTransactionState.Cancelled(code, true)
txMap[request.otherUserId]?.remove(request.requestId) verificationRequestsStore.deleteTransaction(request.otherUserId, request.requestId)
dispatchUpdate(VerificationEvent.TransactionUpdated(it)) dispatchUpdate(VerificationEvent.TransactionUpdated(it))
} }
getExistingTransaction<KotlinQRVerification>(request.otherUserId, request.requestId)?.let { getExistingTransaction<KotlinQRVerification>(request.otherUserId, request.requestId)?.let {
it.state = QRCodeVerificationState.Cancelled it.state = QRCodeVerificationState.Cancelled
txMap[request.otherUserId]?.remove(request.requestId) verificationRequestsStore.deleteTransaction(request.otherUserId, request.requestId)
dispatchUpdate(VerificationEvent.TransactionUpdated(it)) dispatchUpdate(VerificationEvent.TransactionUpdated(it))
} }
@ -1809,30 +1680,29 @@ internal class VerificationActor @AssistedInject constructor(
private fun hashUsingAgreedHashMethod(hashMethod: String?, toHash: String): String { private fun hashUsingAgreedHashMethod(hashMethod: String?, toHash: String): String {
if ("sha256" == hashMethod?.lowercase(Locale.ROOT)) { if ("sha256" == hashMethod?.lowercase(Locale.ROOT)) {
return withOlmUtility { return olmPrimitiveProvider.sha256(toHash)
it.sha256(toHash)
}
} }
throw java.lang.IllegalArgumentException("Unsupported hash method $hashMethod") throw java.lang.IllegalArgumentException("Unsupported hash method $hashMethod")
} }
private suspend fun addTransaction(tx: VerificationTransaction) { private suspend fun addTransaction(tx: VerificationTransaction) {
val txInnerMap = txMap.getOrPut(tx.otherUserId) { mutableMapOf() } verificationRequestsStore.addTransaction(tx)
txInnerMap[tx.transactionId] = tx
dispatchUpdate(VerificationEvent.TransactionAdded(tx)) dispatchUpdate(VerificationEvent.TransactionAdded(tx))
} }
private inline fun <reified T : VerificationTransaction> getExistingTransaction(otherUserId: String, transactionId: String): T? { private inline fun <reified T : VerificationTransaction> getExistingTransaction(otherUserId: String, transactionId: String): T? {
return txMap[otherUserId]?.get(transactionId) as? T return verificationRequestsStore.getExistingTransaction(otherUserId, transactionId) as? T
} }
private inline fun <reified T : VerificationTransaction> getExistingTransaction(transactionId: String): T? { private inline fun <reified T : VerificationTransaction> getExistingTransaction(transactionId: String): T? {
txMap.forEach { return verificationRequestsStore.getExistingTransaction(transactionId)
val match = it.value.values .takeIf { it is T } as? T
.firstOrNull { it.transactionId == transactionId } // txMap.forEach {
?.takeIf { it is T } // val match = it.value.values
if (match != null) return match as? T // .firstOrNull { it.transactionId == transactionId }
} // ?.takeIf { it is T }
return null // if (match != null) return match as? T
// }
// return null
} }
} }

View File

@ -0,0 +1,35 @@
/*
* Copyright 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.verification
import org.matrix.android.sdk.internal.crypto.tools.withOlmUtility
import org.matrix.olm.OlmSAS
import javax.inject.Inject
// Mainly for testing purpose to ease mocking
internal class VerificationCryptoPrimitiveProvider @Inject constructor() {
fun provideOlmSas(): OlmSAS {
return OlmSAS()
}
fun sha256(toHash: String): String {
return withOlmUtility {
it.sha256(toHash)
}
}
}

View File

@ -42,14 +42,6 @@ internal sealed class VerificationIntent {
// val deferred: CompletableDeferred<IVerificationRequest>, // val deferred: CompletableDeferred<IVerificationRequest>,
) : VerificationIntent() ) : VerificationIntent()
data class FailToSendRequest(
val request: PendingVerificationRequest,
) : VerificationIntent()
// data class UpdateRequest(
// val request: IVerificationRequest,
// ) : VerificationIntent()
data class ActionReadyRequest( data class ActionReadyRequest(
val transactionId: String, val transactionId: String,
val methods: List<VerificationMethod>, val methods: List<VerificationMethod>,

View File

@ -0,0 +1,103 @@
/*
* Copyright 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.verification
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
import javax.inject.Inject
internal class VerificationRequestsStore @Inject constructor() {
// map [sender : [transaction]]
private val txMap = HashMap<String, MutableMap<String, VerificationTransaction>>()
// we need to keep track of finished transaction
// It will be used for gossiping (to send request after request is completed and 'done' by other)
private val pastTransactions = HashMap<String, MutableMap<String, VerificationTransaction>>()
/**
* Map [sender: [PendingVerificationRequest]]
* For now we keep all requests (even terminated ones) during the lifetime of the app.
*/
private val pendingRequests = HashMap<String, MutableList<KotlinVerificationRequest>>()
fun getExistingRequest(fromUser: String, requestId: String): KotlinVerificationRequest? {
return pendingRequests[fromUser]?.firstOrNull { it.requestId == requestId }
}
fun getExistingRequestsForUser(fromUser: String): List<KotlinVerificationRequest> {
return pendingRequests[fromUser].orEmpty()
}
fun getExistingRequestInRoom(requestId: String, roomId: String): KotlinVerificationRequest? {
return pendingRequests.flatMap { entry ->
entry.value.filter { it.roomId == roomId && it.requestId == requestId }
}.firstOrNull()
}
fun getExistingRequestWithRequestId(requestId: String): KotlinVerificationRequest? {
return pendingRequests
.flatMap { it.value }
.firstOrNull { it.requestId == requestId }
}
fun getExistingTransaction(fromUser: String, transactionId: String): VerificationTransaction? {
return txMap[fromUser]?.get(transactionId)
}
fun getExistingTransaction(transactionId: String): VerificationTransaction? {
txMap.forEach {
val match = it.value.values
.firstOrNull { it.transactionId == transactionId }
if (match != null) return match
}
return null
}
fun deleteTransaction(fromUser: String, transactionId: String) {
txMap[fromUser]?.remove(transactionId)
}
fun deleteRequest(request: PendingVerificationRequest) {
val requestsForUser = pendingRequests.getOrPut(request.otherUserId) { mutableListOf() }
val index = requestsForUser.indexOfFirst {
it.requestId == request.transactionId
}
if (index != -1) {
requestsForUser.removeAt(index)
}
}
// fun deleteRequest(otherUserId: String, transactionId: String) {
// txMap[otherUserId]?.remove(transactionId)
// }
fun addRequest(otherUserId: String, request: KotlinVerificationRequest) {
pendingRequests.getOrPut(otherUserId) { mutableListOf() }
.add(request)
}
fun addTransaction(transaction: VerificationTransaction) {
val txInnerMap = txMap.getOrPut(transaction.otherUserId) { mutableMapOf() }
txInnerMap[transaction.transactionId] = transaction
}
fun rememberPastSuccessfulTransaction(transaction: VerificationTransaction) {
val transactionId = transaction.transactionId
pastTransactions.getOrPut(transactionId) { mutableMapOf() }[transactionId] = transaction
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright 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.verification
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.di.DeviceId
import org.matrix.android.sdk.internal.di.UserId
import javax.inject.Inject
internal class VerificationTrustBackend @Inject constructor(
private val crossSigningService: dagger.Lazy<CrossSigningService>,
private val setDeviceVerificationAction: SetDeviceVerificationAction,
private val cryptoStore: IMXCryptoStore,
@UserId private val myUserId: String,
@DeviceId private val myDeviceId: String,
) {
suspend fun getUserMasterKeyBase64(userId: String): String? {
return crossSigningService.get()?.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey
}
suspend fun getMyTrustedMasterKeyBase64(): String? {
return cryptoStore.getMyCrossSigningInfo()
?.takeIf { it.isTrusted() }
?.masterKey()
?.unpaddedBase64PublicKey
}
fun canCrossSign(): Boolean {
return crossSigningService.get().canCrossSign()
}
suspend fun trustUser(userId: String) {
crossSigningService.get().trustUser(userId)
}
suspend fun trustOwnDevice(deviceId: String) {
crossSigningService.get().trustDevice(deviceId)
}
suspend fun locallyTrustDevice(otherUserId: String, deviceId: String) {
val actualTrustLevel = getUserDevice(otherUserId, deviceId)?.trustLevel
setDeviceVerificationAction.handle(
trustLevel = DeviceTrustLevel(
actualTrustLevel?.crossSigningVerified == true,
true
),
userId = otherUserId,
deviceId = deviceId
)
}
suspend fun markMyMasterKeyAsTrusted() {
crossSigningService.get().markMyMasterKeyAsTrusted()
}
fun getUserDevice(userId: String, deviceId: String): CryptoDeviceInfo? {
return cryptoStore.getUserDevice(userId, deviceId)
}
fun getMyDevice(): CryptoDeviceInfo {
return getUserDevice(myUserId, myDeviceId)!!
}
fun getUserDeviceList(userId: String): List<CryptoDeviceInfo> {
return cryptoStore.getUserDeviceList(userId).orEmpty()
}
//
// suspend fun areMyCrossSigningKeysTrusted() : Boolean {
// return crossSigningService.get().isUserTrusted(myUserId)
// }
fun getMyDeviceId() = myDeviceId
}

View File

@ -16,16 +16,15 @@
package org.matrix.android.sdk.internal.crypto.verification package org.matrix.android.sdk.internal.crypto.verification
import io.mockk.coEvery
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.api.session.crypto.crosssigning.KeyUsage import org.matrix.android.sdk.api.session.crypto.crosssigning.KeyUsage
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo
import org.matrix.android.sdk.internal.crypto.MXCryptoAlgorithms import org.matrix.android.sdk.internal.crypto.MXCryptoAlgorithms
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
enum class StoreMode { enum class StoreMode {
Alice, Alice,
@ -34,10 +33,10 @@ enum class StoreMode {
internal class FakeCryptoStoreForVerification(private val mode: StoreMode) { internal class FakeCryptoStoreForVerification(private val mode: StoreMode) {
val instance = mockk<IMXCryptoStore>() val instance = mockk<VerificationTrustBackend>()
init { init {
every { instance.getDeviceId() } answers { every { instance.getMyDeviceId() } answers {
when (mode) { when (mode) {
StoreMode.Alice -> aliceDevice1Id StoreMode.Alice -> aliceDevice1Id
StoreMode.Bob -> bobDeviceId StoreMode.Bob -> bobDeviceId
@ -47,84 +46,53 @@ internal class FakeCryptoStoreForVerification(private val mode: StoreMode) {
// order matters here but can't find any info in doc about that // order matters here but can't find any info in doc about that
every { instance.getUserDevice(any(), any()) } returns null every { instance.getUserDevice(any(), any()) } returns null
every { instance.getUserDevice(aliceMxId, aliceDevice1Id) } returns aliceFirstDevice every { instance.getUserDevice(aliceMxId, aliceDevice1Id) } returns aliceFirstDevice
every { instance.getUserDevice(bobDeviceId, bobDeviceId) } returns aBobDevice every { instance.getUserDevice(bobMxId, bobDeviceId) } returns aBobDevice
every { instance.getCrossSigningInfo(aliceMxId) } answers { every { instance.getUserDeviceList(aliceMxId) } returns listOf(aliceFirstDevice)
every { instance.getUserDeviceList(bobMxId) } returns listOf(aBobDevice)
coEvery { instance.locallyTrustDevice(any(), any()) } returns Unit
coEvery { instance.getMyTrustedMasterKeyBase64() } answers {
when (mode) { when (mode) {
StoreMode.Alice -> { StoreMode.Alice -> {
MXCrossSigningInfo( aliceMSK
aliceMxId,
listOf(
aliceMSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
aliceUSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
aliceSSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
),
wasTrustedOnce = true
)
} }
StoreMode.Bob -> { StoreMode.Bob -> {
MXCrossSigningInfo( bobMSK
aliceMxId,
listOf(
aliceMSKBase.copy(trustLevel = DeviceTrustLevel(false, false)),
aliceUSKBase.copy(trustLevel = DeviceTrustLevel(false, false)),
),
wasTrustedOnce = false
)
} }
} }
} }
every { instance.getCrossSigningInfo(bobMxId) } answers { coEvery { instance.getUserMasterKeyBase64(any()) } answers {
val mxId = firstArg<String>()
when (mxId) {
aliceMxId -> aliceMSK
bobMxId -> bobMSK
else -> null
}
}
coEvery { instance.getMyDeviceId() } answers {
when (mode) { when (mode) {
StoreMode.Alice -> { StoreMode.Alice -> aliceDevice1Id
MXCrossSigningInfo( StoreMode.Bob -> bobDeviceId
bobMxId,
listOf(
bobMSKBase.copy(trustLevel = DeviceTrustLevel(false, false)),
bobUSKBase.copy(trustLevel = DeviceTrustLevel(false, false)),
),
wasTrustedOnce = true
)
}
StoreMode.Bob -> {
MXCrossSigningInfo(
bobMxId,
listOf(
bobMSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
bobUSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
bobSSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
),
wasTrustedOnce = false
)
}
} }
} }
every { instance.getMyCrossSigningInfo() } answers { coEvery { instance.getMyDevice() } answers {
when (mode) { when (mode) {
StoreMode.Alice -> MXCrossSigningInfo( StoreMode.Alice -> aliceFirstDevice
aliceMxId, StoreMode.Bob -> aBobDevice
listOf(
aliceMSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
aliceUSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
aliceSSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
),
wasTrustedOnce = false
)
StoreMode.Bob -> MXCrossSigningInfo(
bobMxId,
listOf(
bobMSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
bobUSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
bobSSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
),
wasTrustedOnce = false
)
} }
} }
coEvery {
instance.trustOwnDevice(any())
} returns Unit
coEvery {
instance.trustUser(any())
} returns Unit
} }
companion object { companion object {
@ -133,7 +101,7 @@ internal class FakeCryptoStoreForVerification(private val mode: StoreMode) {
val bobMxId = "bob@example.com" val bobMxId = "bob@example.com"
val bobDeviceId = "MKRJDSLYGA" val bobDeviceId = "MKRJDSLYGA"
private val aliceDevice1Id = "MGDAADVDMG" val aliceDevice1Id = "MGDAADVDMG"
private val aliceMSK = "Ru4ni66dbQ6FZgUoHyyBtmjKecOHMvMSsSBZ2SABtt0" private val aliceMSK = "Ru4ni66dbQ6FZgUoHyyBtmjKecOHMvMSsSBZ2SABtt0"
private val aliceSSK = "Rw6MiEn5do57mBWlWUvL6VDZJ7vAfGrTC58UXVyA0eo" private val aliceSSK = "Rw6MiEn5do57mBWlWUvL6VDZJ7vAfGrTC58UXVyA0eo"

View File

@ -16,7 +16,6 @@
package org.matrix.android.sdk.internal.crypto.verification package org.matrix.android.sdk.internal.crypto.verification
import dagger.Lazy
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
@ -25,17 +24,16 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoReady import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoReady
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.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationReadyContent import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationReadyContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
import org.matrix.android.sdk.api.session.room.model.message.ValidVerificationDone
import org.matrix.android.sdk.internal.crypto.SecretShareManager import org.matrix.android.sdk.internal.crypto.SecretShareManager
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.util.time.Clock import org.matrix.android.sdk.internal.util.time.Clock
import org.matrix.olm.OlmSAS
import java.util.UUID import java.util.UUID
internal class VerificationActorHelper { internal class VerificationActorHelper {
@ -43,6 +41,8 @@ internal class VerificationActorHelper {
data class TestData( data class TestData(
val aliceActor: VerificationActor, val aliceActor: VerificationActor,
val bobActor: VerificationActor, val bobActor: VerificationActor,
val aliceStore: FakeCryptoStoreForVerification,
val bobStore: FakeCryptoStoreForVerification,
) )
val actorAScope = CoroutineScope(SupervisorJob()) val actorAScope = CoroutineScope(SupervisorJob())
@ -56,35 +56,29 @@ internal class VerificationActorHelper {
val aliceTransportLayer = mockTransportTo(FakeCryptoStoreForVerification.aliceMxId) { bobChannel } val aliceTransportLayer = mockTransportTo(FakeCryptoStoreForVerification.aliceMxId) { bobChannel }
val bobTransportLayer = mockTransportTo(FakeCryptoStoreForVerification.bobMxId) { aliceChannel } val bobTransportLayer = mockTransportTo(FakeCryptoStoreForVerification.bobMxId) { aliceChannel }
val fakeAliceStore = FakeCryptoStoreForVerification(StoreMode.Alice)
val aliceActor = fakeActor( val aliceActor = fakeActor(
actorAScope, actorAScope,
FakeCryptoStoreForVerification.aliceMxId, FakeCryptoStoreForVerification.aliceMxId,
FakeCryptoStoreForVerification(StoreMode.Alice).instance, fakeAliceStore.instance,
aliceTransportLayer, aliceTransportLayer,
mockk<Lazy<CrossSigningService>> {
every {
get()
} returns mockk<CrossSigningService>(relaxed = true)
}
) )
aliceChannel = aliceActor.channel aliceChannel = aliceActor.channel
val fakeBobStore = FakeCryptoStoreForVerification(StoreMode.Bob)
val bobActor = fakeActor( val bobActor = fakeActor(
actorBScope, actorBScope,
FakeCryptoStoreForVerification.aliceMxId, FakeCryptoStoreForVerification.bobMxId,
FakeCryptoStoreForVerification(StoreMode.Alice).instance, fakeBobStore.instance,
bobTransportLayer, bobTransportLayer
mockk<Lazy<CrossSigningService>> {
every {
get()
} returns mockk<CrossSigningService>(relaxed = true)
}
) )
bobChannel = bobActor.channel bobChannel = bobActor.channel
return TestData( return TestData(
aliceActor, aliceActor = aliceActor,
bobActor bobActor = bobActor,
aliceStore = fakeAliceStore,
bobStore = fakeBobStore
) )
} }
@ -108,6 +102,56 @@ internal class VerificationActorHelper {
) )
) )
} }
EventType.KEY_VERIFICATION_START -> {
val startContent = info.asValidObject()
otherChannel()?.send(
VerificationIntent.OnStartReceived(
fromUser = fromUser,
viaRoom = request.roomId,
validVerificationInfoStart = startContent as ValidVerificationInfoStart,
)
)
}
EventType.KEY_VERIFICATION_ACCEPT -> {
val content = info.asValidObject()
otherChannel()?.send(
VerificationIntent.OnAcceptReceived(
fromUser = fromUser,
viaRoom = request.roomId,
validAccept = content as ValidVerificationInfoAccept,
)
)
}
EventType.KEY_VERIFICATION_KEY -> {
val content = info.asValidObject()
otherChannel()?.send(
VerificationIntent.OnKeyReceived(
fromUser = fromUser,
viaRoom = request.roomId,
validKey = content as ValidVerificationInfoKey,
)
)
}
EventType.KEY_VERIFICATION_MAC -> {
val content = info.asValidObject()
otherChannel()?.send(
VerificationIntent.OnMacReceived(
fromUser = fromUser,
viaRoom = request.roomId,
validMac = content as ValidVerificationInfoMac,
)
)
}
EventType.KEY_VERIFICATION_DONE -> {
val content = info.asValidObject()
otherChannel()?.send(
VerificationIntent.OnDoneReceived(
fromUser = fromUser,
viaRoom = request.roomId,
transactionId = (content as ValidVerificationDone).transactionId,
)
)
}
} }
} }
} }
@ -154,9 +198,8 @@ internal class VerificationActorHelper {
private fun fakeActor( private fun fakeActor(
scope: CoroutineScope, scope: CoroutineScope,
userId: String, userId: String,
cryptoStore: IMXCryptoStore, cryptoStore: VerificationTrustBackend,
transportLayer: VerificationTransportLayer, transportLayer: VerificationTransportLayer,
crossSigningService: dagger.Lazy<CrossSigningService>,
): VerificationActor { ): VerificationActor {
return VerificationActor( return VerificationActor(
scope, scope,
@ -165,17 +208,19 @@ internal class VerificationActorHelper {
every { epochMillis() } returns System.currentTimeMillis() every { epochMillis() } returns System.currentTimeMillis()
}, },
myUserId = userId, myUserId = userId,
cryptoStore = cryptoStore, verificationTrustBackend = cryptoStore,
secretShareManager = mockk<SecretShareManager> {}, secretShareManager = mockk<SecretShareManager> {},
transportLayer = transportLayer, transportLayer = transportLayer,
crossSigningService = crossSigningService, verificationRequestsStore = VerificationRequestsStore(),
setDeviceVerificationAction = SetDeviceVerificationAction( olmPrimitiveProvider = mockk<VerificationCryptoPrimitiveProvider> {
cryptoStore = cryptoStore, every { provideOlmSas() } returns mockk<OlmSAS> {
userId = userId, every { publicKey } returns "Tm9JRGVhRmFrZQo="
defaultKeysBackupService = mockk { every { setTheirPublicKey(any()) } returns Unit
coEvery { checkAndStartKeysBackup() } coAnswers { } every { generateShortCode(any(), any()) } returns byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
every { calculateMac(any(), any()) } returns "mic mac mec"
}
every { sha256(any()) } returns "fake_hash"
} }
) )
)
} }
} }

View File

@ -17,7 +17,11 @@
package org.matrix.android.sdk.internal.crypto.verification.org.matrix.android.sdk.internal.crypto.verification package org.matrix.android.sdk.internal.crypto.verification.org.matrix.android.sdk.internal.crypto.verification
import android.util.Base64 import android.util.Base64
import io.mockk.clearAllMocks
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every import io.mockk.every
import io.mockk.mockkConstructor
import io.mockk.mockkStatic import io.mockk.mockkStatic
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -26,6 +30,9 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -34,23 +41,29 @@ import org.amshove.kluent.internal.assertEquals
import org.amshove.kluent.internal.assertNotEquals import org.amshove.kluent.internal.assertNotEquals
import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldNotBe import org.amshove.kluent.shouldNotBe
import org.amshove.kluent.shouldNotBeEqualTo
import org.json.JSONObject
import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.matrix.android.sdk.MatrixTest
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.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.VerificationEvent import org.matrix.android.sdk.api.session.crypto.verification.VerificationEvent
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.VerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.getRequest
import org.matrix.android.sdk.internal.crypto.verification.FakeCryptoStoreForVerification import org.matrix.android.sdk.internal.crypto.verification.FakeCryptoStoreForVerification
import org.matrix.android.sdk.internal.crypto.verification.VerificationActor import org.matrix.android.sdk.internal.crypto.verification.VerificationActor
import org.matrix.android.sdk.internal.crypto.verification.VerificationActorHelper import org.matrix.android.sdk.internal.crypto.verification.VerificationActorHelper
import org.matrix.android.sdk.internal.crypto.verification.VerificationIntent import org.matrix.android.sdk.internal.crypto.verification.VerificationIntent
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class VerificationActorTest { class VerificationActorTest : MatrixTest {
val transportScope = CoroutineScope(SupervisorJob()) val transportScope = CoroutineScope(SupervisorJob())
// val actorAScope = CoroutineScope(SupervisorJob())
// val actorBScope = CoroutineScope(SupervisorJob())
@Before @Before
fun setUp() { fun setUp() {
@ -69,6 +82,20 @@ class VerificationActorTest {
val array = firstArg<String>() val array = firstArg<String>()
java.util.Base64.getDecoder().decode(array) java.util.Base64.getDecoder().decode(array)
} }
// to mock canonical json
mockkConstructor(JSONObject::class)
every { anyConstructed<JSONObject>().keys() } returns emptyList<String>().listIterator()
// mockkConstructor(KotlinSasTransaction::class)
// every { anyConstructed<KotlinSasTransaction>().getSAS() } returns mockk<OlmSAS>() {
// every { publicKey } returns "Tm9JRGVhRmFrZQo="
// }
}
@After
fun tearDown() {
clearAllMocks()
} }
@Test @Test
@ -170,6 +197,39 @@ class VerificationActorTest {
assertEquals("Bob should not show QR as alice can scan", false, bobReady.weShouldDisplayQRCode) assertEquals("Bob should not show QR as alice can scan", false, bobReady.weShouldDisplayQRCode)
} }
@Test
fun `If Bobs device does not understand any of the methods, it should not cancel the request`() = runTest {
val testData = VerificationActorHelper().setUpActors()
val aliceActor = testData.aliceActor
val bobActor = testData.bobActor
val outgoingRequest = aliceActor.requestVerification(
listOf(VerificationMethod.SAS)
)
// wait for bob to get it
waitForBobToSeeIncomingRequest(bobActor, outgoingRequest)
println("let bob ready it")
try {
bobActor.readyVerification(
outgoingRequest.transactionId,
listOf(VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW)
)
// Upon receipt of Alices m.key.verification.request message, if Bobs device does not understand any of the methods,
// it should not cancel the request as one of his other devices may support the request
fail("Ready should fail as no common methods")
} catch (failure: Throwable) {
// should throw
}
val bodSide = awaitDeferrable<PendingVerificationRequest?> {
bobActor.send(VerificationIntent.GetExistingRequest(outgoingRequest.transactionId, FakeCryptoStoreForVerification.aliceMxId, it))
}!!
bodSide.state shouldNotBeEqualTo EVerificationState.Cancelled
}
@Test @Test
fun `Test bob can show but not scan QR`() = runTest { fun `Test bob can show but not scan QR`() = runTest {
val testData = VerificationActorHelper().setUpActors() val testData = VerificationActorHelper().setUpActors()
@ -215,6 +275,199 @@ class VerificationActorTest {
assertEquals("Bob should show QR as alice can scan", true, bobReady.weShouldDisplayQRCode) assertEquals("Bob should show QR as alice can scan", true, bobReady.weShouldDisplayQRCode)
} }
@Test
fun `Test verify to users that trust their cross signing keys`() = runTest {
val testData = VerificationActorHelper().setUpActors()
val aliceActor = testData.aliceActor
val bobActor = testData.bobActor
coEvery { testData.bobStore.instance.canCrossSign() } returns true
coEvery { testData.aliceStore.instance.canCrossSign() } returns true
fullSasVerification(bobActor, aliceActor)
coVerify {
testData.bobStore.instance.locallyTrustDevice(
FakeCryptoStoreForVerification.aliceMxId,
FakeCryptoStoreForVerification.aliceDevice1Id,
)
}
coVerify {
testData.bobStore.instance.trustUser(
FakeCryptoStoreForVerification.aliceMxId
)
}
coVerify {
testData.aliceStore.instance.locallyTrustDevice(
FakeCryptoStoreForVerification.bobMxId,
FakeCryptoStoreForVerification.bobDeviceId,
)
}
coVerify {
testData.aliceStore.instance.trustUser(
FakeCryptoStoreForVerification.bobMxId
)
}
}
@Test
fun `Test user verification when alice do not trust her keys`() = runTest {
val testData = VerificationActorHelper().setUpActors()
val aliceActor = testData.aliceActor
val bobActor = testData.bobActor
coEvery { testData.bobStore.instance.canCrossSign() } returns true
coEvery { testData.aliceStore.instance.canCrossSign() } returns false
coEvery { testData.aliceStore.instance.getMyTrustedMasterKeyBase64() } returns null
fullSasVerification(bobActor, aliceActor)
coVerify {
testData.bobStore.instance.locallyTrustDevice(
FakeCryptoStoreForVerification.aliceMxId,
FakeCryptoStoreForVerification.aliceDevice1Id,
)
}
coVerify(exactly = 0) {
testData.bobStore.instance.trustUser(
FakeCryptoStoreForVerification.aliceMxId
)
}
coVerify {
testData.aliceStore.instance.locallyTrustDevice(
FakeCryptoStoreForVerification.bobMxId,
FakeCryptoStoreForVerification.bobDeviceId,
)
}
coVerify(exactly = 0) {
testData.aliceStore.instance.trustUser(
FakeCryptoStoreForVerification.bobMxId
)
}
}
private suspend fun fullSasVerification(bobActor: VerificationActor, aliceActor: VerificationActor) {
transportScope.launch {
bobActor.eventFlow
.collect {
println("Bob flow 1 event $it")
if (it is VerificationEvent.RequestAdded) {
// auto accept
bobActor.readyVerification(
it.transactionId,
listOf(VerificationMethod.SAS)
)
// then start
bobActor.send(
VerificationIntent.ActionStartSasVerification(
FakeCryptoStoreForVerification.aliceMxId,
it.transactionId,
CompletableDeferred()
)
)
}
return@collect cancel()
}
}
val aliceCode = CompletableDeferred<SasVerificationTransaction>()
val bobCode = CompletableDeferred<SasVerificationTransaction>()
aliceActor.eventFlow.onEach {
println("Alice flow event $it")
if (it is VerificationEvent.TransactionUpdated) {
val sasVerificationTransaction = it.transaction as SasVerificationTransaction
if (sasVerificationTransaction.state() is SasTransactionState.SasShortCodeReady) {
aliceCode.complete(sasVerificationTransaction)
}
}
}.launchIn(transportScope)
bobActor.eventFlow.onEach {
println("Bob flow event $it")
if (it is VerificationEvent.TransactionUpdated) {
val sasVerificationTransaction = it.transaction as SasVerificationTransaction
if (sasVerificationTransaction.state() is SasTransactionState.SasShortCodeReady) {
bobCode.complete(sasVerificationTransaction)
}
}
}.launchIn(transportScope)
println("Alice sends a request")
val outgoingRequest = aliceActor.requestVerification(
listOf(VerificationMethod.SAS)
)
// asserting the code won't help much here as all is mocked
// we are checking state progression
// Both transaction should be in sas ready
val aliceCodeReadyTx = aliceCode.await()
bobCode.await()
// If alice accept the code, bob should pass to state mac received but code not comfirmed
aliceCodeReadyTx.userHasVerifiedShortCode()
retryUntil {
val tx = bobActor.getTransactionBobPov(outgoingRequest.transactionId)
val sasTx = tx as? SasVerificationTransaction
val state = sasTx?.state()
(state is SasTransactionState.SasMacReceived && !state.codeConfirmed)
}
val bobTransaction = bobActor.getTransactionBobPov(outgoingRequest.transactionId) as SasVerificationTransaction
val bobDone = CompletableDeferred(Unit)
val aliceDone = CompletableDeferred(Unit)
transportScope.launch {
bobActor.eventFlow
.collect {
println("Bob flow 1 event $it")
it.getRequest()?.let {
if (it.transactionId == outgoingRequest.transactionId && it.state == EVerificationState.Done) {
bobDone.complete(Unit)
return@collect cancel()
}
}
}
}
transportScope.launch {
aliceActor.eventFlow
.collect {
println("Bob flow 1 event $it")
it.getRequest()?.let {
if (it.transactionId == outgoingRequest.transactionId && it.state == EVerificationState.Done) {
bobDone.complete(Unit)
return@collect cancel()
}
}
}
}
// mark as verified from bob side
bobTransaction.userHasVerifiedShortCode()
aliceDone.await()
bobDone.await()
}
internal suspend fun VerificationActor.getTransactionBobPov(transactionId: String): VerificationTransaction? {
return awaitDeferrable<VerificationTransaction?> {
channel.send(
VerificationIntent.GetExistingTransaction(
transactionId = transactionId,
fromUser = FakeCryptoStoreForVerification.aliceMxId,
it
)
)
}
}
private suspend fun VerificationActor.requestVerification(methods: List<VerificationMethod>): PendingVerificationRequest { private suspend fun VerificationActor.requestVerification(methods: List<VerificationMethod>): PendingVerificationRequest {
return awaitDeferrable<PendingVerificationRequest> { return awaitDeferrable<PendingVerificationRequest> {
send( send(
@ -273,74 +526,4 @@ class VerificationActorTest {
) )
}!! }!!
} }
// @Test
// fun `Every testing`() {
// val mockStore = mockk<IMXCryptoStore>()
// every { mockStore.getDeviceId() } returns "A"
// println("every ${mockStore.getDeviceId()}")
// every { mockStore.getDeviceId() } returns "B"
// println("every ${mockStore.getDeviceId()}")
//
// every { mockStore.getDeviceId() } returns "A"
// every { mockStore.getDeviceId() } returns "B"
// println("every ${mockStore.getDeviceId()}")
//
// every { mockStore.getCrossSigningInfo(any()) } returns null
// every { mockStore.getCrossSigningInfo("alice") } returns MXCrossSigningInfo("alice", emptyList(), false)
//
// println("XS ${mockStore.getCrossSigningInfo("alice")}")
// println("XS ${mockStore.getCrossSigningInfo("bob")}")
// }
// @Test
// fun `Basic channel test`() {
// // val sharedFlow = MutableSharedFlow<Int>(replay = 0, extraBufferCapacity = 2, BufferOverflow.DROP_OLDEST)
// val sharedFlow = MutableSharedFlow<Int>(replay = 0)//, extraBufferCapacity = 0, BufferOverflow.DROP_OLDEST)
//
// val scope = CoroutineScope(SupervisorJob())
// val deferred = CompletableDeferred<Unit>()
// val listener = scope.launch {
// sharedFlow.onEach {
// println("L1 : Just collected $it")
// delay(1000)
// println("L1 : Just processed $it")
// if (it == 2) {
// deferred.complete(Unit)
// }
// }.launchIn(scope)
// }
//
// // scope.launch {
// // delay(700)
// println("Pre Emit 1")
// sharedFlow.tryEmit(1)
// println("Emited 1")
// sharedFlow.tryEmit(2)
// println("Emited 2")
// // }
//
// // runBlocking {
// // deferred.await()
// // }
//
// sharedFlow.onEach {
// println("L2: Just collected $it")
// delay(1000)
// println("L2: Just processed $it")
// }.launchIn(scope)
//
//
// runBlocking {
// deferred.await()
// }
//
// val now = System.currentTimeMillis()
// println("Just give some time for execution")
// val job = scope.launch { delay(10_000) }
// runBlocking {
// job.join()
// }
// println("enough ${System.currentTimeMillis() - now}")
// }
} }