refactor for easy unit tests
This commit is contained in:
parent
bed2c221e3
commit
4ce6a25c70
@ -62,7 +62,8 @@ internal class KotlinSasTransaction(
|
||||
override val isIncoming: Boolean,
|
||||
val startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null,
|
||||
val isToDevice: Boolean,
|
||||
var state: SasTransactionState
|
||||
var state: SasTransactionState,
|
||||
val olmSAS: OlmSAS,
|
||||
) : SasVerificationTransaction {
|
||||
|
||||
override val method: VerificationMethod
|
||||
@ -183,11 +184,8 @@ internal class KotlinSasTransaction(
|
||||
}
|
||||
}
|
||||
|
||||
private var olmSas: OlmSAS? = null
|
||||
|
||||
fun getSAS(): OlmSAS {
|
||||
if (olmSas == null) olmSas = OlmSAS()
|
||||
return olmSas!!
|
||||
override fun toString(): String {
|
||||
return "KotlinSasTransaction(transactionId=$transactionId, state=$state, otherUserId=$otherUserId, otherDeviceId=$otherDeviceId, isToDevice=$isToDevice)"
|
||||
}
|
||||
|
||||
// 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() {
|
||||
// finalization logic
|
||||
olmSas?.releaseSas()
|
||||
olmSas = null
|
||||
olmSAS.releaseSas()
|
||||
}
|
||||
|
||||
var accepted: ValidVerificationInfoAccept? = null
|
||||
@ -206,6 +203,7 @@ internal class KotlinSasTransaction(
|
||||
var shortCodeBytes: ByteArray? = null
|
||||
var myMac: ValidVerificationInfoMac? = null
|
||||
var theirMac: ValidVerificationInfoMac? = null
|
||||
var verifiedSuccessInfo: MacVerificationResult.Success? = null
|
||||
|
||||
override fun state() = this.state
|
||||
|
||||
@ -262,7 +260,7 @@ internal class KotlinSasTransaction(
|
||||
|
||||
fun calculateSASBytes(otherKey: String) {
|
||||
this.otherKey = otherKey
|
||||
getSAS().setTheirPublicKey(otherKey)
|
||||
olmSAS.setTheirPublicKey(otherKey)
|
||||
shortCodeBytes = when (accepted!!.keyAgreementProtocol) {
|
||||
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,
|
||||
@ -280,7 +278,7 @@ internal class KotlinSasTransaction(
|
||||
append(otherDeviceId)
|
||||
append(myUserId)
|
||||
append(myDeviceId)
|
||||
append(getSAS().publicKey)
|
||||
append(olmSAS.publicKey)
|
||||
} else {
|
||||
append(myUserId)
|
||||
append(myDeviceId)
|
||||
@ -291,7 +289,7 @@ internal class KotlinSasTransaction(
|
||||
}
|
||||
// decimal: generate five bytes by using HKDF.
|
||||
// emoji: generate six bytes by using HKDF.
|
||||
getSAS().generateShortCode(sasInfo, 6)
|
||||
olmSAS.generateShortCode(sasInfo, 6)
|
||||
}
|
||||
KEY_AGREEMENT_V2 -> {
|
||||
val sasInfo = buildString {
|
||||
@ -302,18 +300,18 @@ internal class KotlinSasTransaction(
|
||||
append(otherKey).append('|')
|
||||
append(myUserId).append('|')
|
||||
append(myDeviceId).append('|')
|
||||
append(getSAS().publicKey).append('|')
|
||||
append(olmSAS.publicKey).append('|')
|
||||
} else {
|
||||
append(myUserId).append('|')
|
||||
append(myDeviceId).append('|')
|
||||
append(getSAS().publicKey).append('|')
|
||||
append(olmSAS.publicKey).append('|')
|
||||
append(otherUserId).append('|')
|
||||
append(otherDeviceId).append('|')
|
||||
append(otherKey).append('|')
|
||||
}
|
||||
append(transactionId)
|
||||
}
|
||||
getSAS().generateShortCode(sasInfo, 6)
|
||||
olmSAS.generateShortCode(sasInfo, 6)
|
||||
}
|
||||
else -> {
|
||||
// Protocol has been checked earlier
|
||||
@ -463,13 +461,16 @@ internal class KotlinSasTransaction(
|
||||
return MacVerificationResult.Success(
|
||||
verifiedDevices,
|
||||
otherMasterKeyIsVerified
|
||||
)
|
||||
).also {
|
||||
// store and will persist when transaction is actually done
|
||||
verifiedSuccessInfo = it
|
||||
}
|
||||
}
|
||||
|
||||
private fun macUsingAgreedMethod(message: String, info: String): String? {
|
||||
return when (accepted?.messageAuthenticationCode?.lowercase(Locale.ROOT)) {
|
||||
SAS_MAC_SHA256_LONGKDF -> getSAS().calculateMacLongKdf(message, info)
|
||||
SAS_MAC_SHA256 -> getSAS().calculateMac(message, info)
|
||||
SAS_MAC_SHA256_LONGKDF -> olmSAS.calculateMacLongKdf(message, info)
|
||||
SAS_MAC_SHA256 -> olmSAS.calculateMac(message, info)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
* 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.tryOrNull
|
||||
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.MASTER_KEY_SSSS_NAME
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
||||
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.SasTransactionState
|
||||
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.relation.RelationDefaultContent
|
||||
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.KeyVerificationDone
|
||||
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_SAS
|
||||
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.generateSharedSecretV2
|
||||
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 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)
|
||||
|
||||
internal class VerificationActor @AssistedInject constructor(
|
||||
@Assisted private val scope: CoroutineScope,
|
||||
private val clock: Clock,
|
||||
@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 transportLayer: VerificationTransportLayer,
|
||||
private val verificationRequestsStore: VerificationRequestsStore,
|
||||
private val olmPrimitiveProvider: VerificationCryptoPrimitiveProvider,
|
||||
private val verificationTrustBackend: VerificationTrustBackend,
|
||||
) {
|
||||
|
||||
@AssistedFactory
|
||||
@ -109,31 +96,15 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
|
||||
init {
|
||||
scope.launch {
|
||||
Timber.e("VALR BEFORE")
|
||||
for (msg in channel) {
|
||||
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.
|
||||
// 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 = 4, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
val eventFlow = MutableSharedFlow<VerificationEvent>(extraBufferCapacity = 20, onBufferOverflow = BufferOverflow.SUSPEND)
|
||||
|
||||
suspend fun send(intent: VerificationIntent) {
|
||||
channel.send(intent)
|
||||
@ -144,8 +115,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
requestId: String,
|
||||
block: suspend ((KotlinVerificationRequest) -> Unit)
|
||||
) {
|
||||
val matchingRequest = pendingRequests[otherUserId]
|
||||
?.firstOrNull { it.requestId == requestId }
|
||||
val matchingRequest = verificationRequestsStore.getExistingRequest(otherUserId, requestId)
|
||||
?: return Unit.also {
|
||||
// Receive a transaction event with no matching request.. should ignore.
|
||||
// Not supported any more to do raw start
|
||||
@ -173,8 +143,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
viaRoom: String?,
|
||||
block: suspend ((KotlinVerificationRequest) -> Unit)
|
||||
) {
|
||||
val matchingRequest = pendingRequests[otherUserId]
|
||||
?.firstOrNull { it.requestId == requestId }
|
||||
val matchingRequest = verificationRequestsStore.getExistingRequest(otherUserId, requestId)
|
||||
?: return Unit.also {
|
||||
// Receive a transaction event with no matching request.. should ignore.
|
||||
// Not supported any more to do raw start
|
||||
@ -219,23 +188,11 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
is VerificationIntent.OnReadyReceived -> {
|
||||
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 -> {
|
||||
// updatePendingRequest(msg.request)
|
||||
// }
|
||||
is VerificationIntent.GetExistingRequestInRoom -> {
|
||||
val existing = pendingRequests.flatMap { entry ->
|
||||
entry.value.filter { it.roomId == msg.roomId && it.requestId == msg.transactionId }
|
||||
}.firstOrNull()
|
||||
val existing = verificationRequestsStore.getExistingRequestInRoom(msg.transactionId, msg.roomId)
|
||||
msg.deferred.complete(existing?.toPendingVerificationRequest())
|
||||
}
|
||||
is VerificationIntent.OnVerificationRequestReceived -> {
|
||||
@ -286,9 +243,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
is VerificationIntent.ActionCancel -> {
|
||||
pendingRequests
|
||||
.flatMap { it.value }
|
||||
.firstOrNull { it.requestId == msg.transactionId }
|
||||
verificationRequestsStore.getExistingRequestWithRequestId(msg.transactionId)
|
||||
?.let { matchingRequest ->
|
||||
try {
|
||||
cancelRequest(matchingRequest, CancelCode.User)
|
||||
@ -300,25 +255,27 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
}
|
||||
is VerificationIntent.OnUnableToDecryptVerificationEvent -> {
|
||||
// at least if request was sent by me, I can safely cancel without interfering
|
||||
val matchingRequest = pendingRequests[msg.fromUser]
|
||||
?.firstOrNull { it.requestId == msg.transactionId } ?: return
|
||||
val matchingRequest = verificationRequestsStore.getExistingRequest(msg.fromUser, msg.transactionId)
|
||||
?: return
|
||||
if (matchingRequest.state != EVerificationState.HandledByOtherSession) {
|
||||
cancelRequest(matchingRequest, CancelCode.InvalidMessage)
|
||||
}
|
||||
}
|
||||
is VerificationIntent.GetExistingRequestsForUser -> {
|
||||
pendingRequests[msg.userId].orEmpty().let { requests ->
|
||||
verificationRequestsStore.getExistingRequestsForUser(msg.userId).let { requests ->
|
||||
msg.deferred.complete(requests.map { it.toPendingVerificationRequest() })
|
||||
}
|
||||
}
|
||||
is VerificationIntent.GetExistingTransaction -> {
|
||||
txMap[msg.fromUser]?.get(msg.transactionId)?.let {
|
||||
msg.deferred.complete(it)
|
||||
}
|
||||
verificationRequestsStore
|
||||
.getExistingTransaction(msg.fromUser, msg.transactionId)
|
||||
?.let {
|
||||
msg.deferred.complete(it)
|
||||
}
|
||||
}
|
||||
is VerificationIntent.GetExistingRequest -> {
|
||||
pendingRequests[msg.otherUserId]
|
||||
?.firstOrNull { msg.transactionId == it.requestId }
|
||||
verificationRequestsStore
|
||||
.getExistingRequest(msg.otherUserId, msg.transactionId)
|
||||
?.let {
|
||||
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)
|
||||
if (existingTx != null) {
|
||||
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.RequestUpdated(request.toPendingVerificationRequest()))
|
||||
@ -348,8 +305,10 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
|
||||
private fun dispatchUpdate(update: VerificationEvent) {
|
||||
// We don't want to block on emit.
|
||||
// If no subscriber there is a small buffer and too old would be dropped
|
||||
eventFlow.tryEmit(update)
|
||||
// If no subscriber there is a small buffer
|
||||
scope.launch {
|
||||
eventFlow.emit(update)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleIncomingRequest(msg: VerificationIntent.OnVerificationRequestReceived) {
|
||||
@ -363,15 +322,14 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
requestInfo = msg.validRequestInfo
|
||||
roomId = msg.roomId
|
||||
}
|
||||
|
||||
pendingRequests.getOrPut(msg.senderId) { mutableListOf() }
|
||||
.add(pendingVerificationRequest)
|
||||
verificationRequestsStore.addRequest(msg.senderId, pendingVerificationRequest)
|
||||
dispatchRequestAdded(pendingVerificationRequest)
|
||||
}
|
||||
|
||||
private suspend fun onStartReceived(msg: VerificationIntent.OnStartReceived) {
|
||||
val requestId = msg.validVerificationInfoStart.transactionId
|
||||
val matchingRequest = pendingRequests[msg.fromUser]?.firstOrNull { it.requestId == requestId }
|
||||
val matchingRequest = verificationRequestsStore
|
||||
.getExistingRequestWithRequestId(msg.validVerificationInfoStart.transactionId)
|
||||
?: return Unit.also {
|
||||
// Receive a start with no matching request.. should ignore.
|
||||
// Not supported any more to do raw start
|
||||
@ -493,7 +451,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
cancelRequest(request, CancelCode.UnknownMethod)
|
||||
}
|
||||
// Bob’s device ensures that it has a copy of Alice’s device key.
|
||||
val mxDeviceInfo = cryptoStore.getUserDevice(userId = request.otherUserId, deviceId = otherDeviceId)
|
||||
val mxDeviceInfo = verificationTrustBackend.getUserDevice(request.otherUserId, otherDeviceId)
|
||||
|
||||
if (mxDeviceInfo?.fingerprint() == null) {
|
||||
Timber.e("## SAS Failed to find device key ")
|
||||
@ -509,19 +467,17 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
state = SasTransactionState.None,
|
||||
otherUserId = request.otherUserId,
|
||||
myUserId = myUserId,
|
||||
myTrustedMSK = cryptoStore.getMyCrossSigningInfo()
|
||||
?.takeIf { it.isTrusted() }
|
||||
?.masterKey()
|
||||
?.unpaddedBase64PublicKey,
|
||||
myTrustedMSK = verificationTrustBackend.getMyTrustedMasterKeyBase64(),
|
||||
otherDeviceId = request.otherDeviceId(),
|
||||
myDeviceId = cryptoStore.getDeviceId(),
|
||||
myDeviceFingerprint = cryptoStore.getUserDevice(myUserId, cryptoStore.getDeviceId())?.fingerprint().orEmpty(),
|
||||
myDeviceId = verificationTrustBackend.getMyDeviceId(),
|
||||
myDeviceFingerprint = verificationTrustBackend.getMyDevice().fingerprint().orEmpty(),
|
||||
startReq = sasStart,
|
||||
isIncoming = true,
|
||||
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 accept = KotlinSasTransaction.sasAccept(
|
||||
@ -544,6 +500,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
}
|
||||
|
||||
sasTx.accepted = accept.asValidObject()
|
||||
sasTx.state = SasTransactionState.SasAccepted
|
||||
|
||||
addTransaction(sasTx)
|
||||
}
|
||||
@ -578,7 +535,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
|
||||
// Alice’s 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 Alice’s public key QA
|
||||
val pubKey = existing.getSAS().publicKey
|
||||
val pubKey = existing.olmSAS.publicKey
|
||||
|
||||
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) {
|
||||
val matchingRequest = pendingRequests
|
||||
.flatMap { entry ->
|
||||
entry.value.filter { it.requestId == msg.requestId }
|
||||
}.firstOrNull()
|
||||
val matchingRequest = verificationRequestsStore.getExistingRequestWithRequestId(msg.requestId)
|
||||
?: return Unit.also {
|
||||
msg.deferred.completeExceptionally(java.lang.IllegalArgumentException("Unknown request"))
|
||||
}
|
||||
@ -631,7 +585,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
}
|
||||
val startMessage = KotlinSasTransaction.sasStart(
|
||||
inRoom = matchingRequest.roomId != null,
|
||||
fromDevice = cryptoStore.getDeviceId(),
|
||||
fromDevice = verificationTrustBackend.getMyDeviceId(),
|
||||
requestId = msg.requestId
|
||||
)
|
||||
|
||||
@ -648,16 +602,14 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
state = SasTransactionState.SasStarted,
|
||||
otherUserId = msg.otherUserId,
|
||||
myUserId = myUserId,
|
||||
myTrustedMSK = cryptoStore.getMyCrossSigningInfo()
|
||||
?.takeIf { it.isTrusted() }
|
||||
?.masterKey()
|
||||
?.unpaddedBase64PublicKey,
|
||||
myTrustedMSK = verificationTrustBackend.getMyTrustedMasterKeyBase64(),
|
||||
otherDeviceId = otherDeviceId,
|
||||
myDeviceId = cryptoStore.getDeviceId(),
|
||||
myDeviceFingerprint = cryptoStore.getUserDevice(myUserId, cryptoStore.getDeviceId())?.fingerprint().orEmpty(),
|
||||
myDeviceId = verificationTrustBackend.getMyDeviceId(),
|
||||
myDeviceFingerprint = verificationTrustBackend.getMyDevice().fingerprint().orEmpty(),
|
||||
startReq = startMessage.asValidObject() as ValidVerificationInfoStart.SasVerificationInfoStart,
|
||||
isIncoming = false,
|
||||
isToDevice = matchingRequest.roomId == null
|
||||
isToDevice = matchingRequest.roomId == null,
|
||||
olmSAS = olmPrimitiveProvider.provideOlmSas()
|
||||
)
|
||||
|
||||
matchingRequest.state = EVerificationState.WeStarted
|
||||
@ -670,10 +622,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
private suspend fun handleActionReciprocateQR(msg: VerificationIntent.ActionReciprocateQrVerification) {
|
||||
Timber.tag(loggerTag.value)
|
||||
.d("[${myUserId.take(8)}] handle reciprocate for ${msg.requestId}")
|
||||
val matchingRequest = pendingRequests
|
||||
.flatMap { entry ->
|
||||
entry.value.filter { it.requestId == msg.requestId }
|
||||
}.firstOrNull()
|
||||
val matchingRequest = verificationRequestsStore.getExistingRequestWithRequestId(msg.requestId)
|
||||
?: return Unit.also {
|
||||
Timber.tag(loggerTag.value)
|
||||
.d("[${myUserId.take(8)}] No matching request, abort ${msg.requestId}")
|
||||
@ -700,8 +649,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
return
|
||||
}
|
||||
|
||||
val myMasterKey = crossSigningService.get()
|
||||
.getUserCrossSigningKeys(myUserId)?.masterKey()?.unpaddedBase64PublicKey
|
||||
val myMasterKey = verificationTrustBackend.getUserMasterKeyBase64(myUserId)
|
||||
|
||||
// Check the other device view of my MSK
|
||||
val otherQrCodeData = msg.scannedData.toQrCodeData()
|
||||
@ -725,9 +673,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
return
|
||||
}
|
||||
|
||||
val whatIThinkOtherMskIs = crossSigningService.get().getUserCrossSigningKeys(matchingRequest.otherUserId)
|
||||
?.masterKey()
|
||||
?.unpaddedBase64PublicKey
|
||||
val whatIThinkOtherMskIs = verificationTrustBackend.getUserMasterKeyBase64(matchingRequest.otherUserId)
|
||||
if (whatIThinkOtherMskIs != otherQrCodeData.userMasterCrossSigningPublicKey) {
|
||||
Timber.tag(loggerTag.value)
|
||||
.d("[${myUserId.take(8)}] ## Verification QR: Invalid other master key ${otherQrCodeData.otherUserMasterCrossSigningPublicKey}")
|
||||
@ -756,7 +702,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
return
|
||||
}
|
||||
val whatOtherThinkMyDeviceKeyIs = otherQrCodeData.otherDeviceKey
|
||||
val myDeviceKey = cryptoStore.getUserDevice(myUserId, cryptoStore.getDeviceId())?.fingerprint()
|
||||
val myDeviceKey = verificationTrustBackend.getMyDevice().fingerprint()
|
||||
if (whatOtherThinkMyDeviceKeyIs != myDeviceKey) {
|
||||
Timber.tag(loggerTag.value)
|
||||
.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
|
||||
// If not -> Cancel
|
||||
val otherDeclaredDeviceKey = otherQrCodeData.deviceKey
|
||||
val whatIThinkItIs = cryptoStore.getUserDevice(myUserId, otherDeviceId)?.fingerprint()
|
||||
val whatIThinkItIs = verificationTrustBackend.getUserDevice(matchingRequest.otherUserId, otherDeviceId)?.fingerprint()
|
||||
|
||||
if (otherDeclaredDeviceKey != whatIThinkItIs) {
|
||||
Timber.tag(loggerTag.value)
|
||||
@ -802,7 +748,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
// qrCodeData.sharedSecret will be used to send the start request
|
||||
val message = if (matchingRequest.roomId != null) {
|
||||
MessageVerificationStartContent(
|
||||
fromDevice = cryptoStore.getDeviceId(),
|
||||
fromDevice = verificationTrustBackend.getMyDeviceId(),
|
||||
hashes = null,
|
||||
keyAgreementProtocols = null,
|
||||
messageAuthenticationCodes = null,
|
||||
@ -816,7 +762,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
)
|
||||
} else {
|
||||
KeyVerificationStart(
|
||||
fromDevice = cryptoStore.getDeviceId(),
|
||||
fromDevice = verificationTrustBackend.getMyDeviceId(),
|
||||
sharedSecret = otherQrCodeData.sharedSecret,
|
||||
method = VERIFICATION_METHOD_RECIPROCATE,
|
||||
)
|
||||
@ -896,7 +842,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
val otherKey = msg.validKey.key
|
||||
if (existing.isIncoming) {
|
||||
// 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)
|
||||
try {
|
||||
transportLayer.sendToOther(
|
||||
@ -945,7 +891,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
if (otherCommitment == existing.accepted?.commitment) {
|
||||
if (BuildConfig.LOG_PRIVATE_DATA) {
|
||||
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.state = SasTransactionState.SasShortCodeReady
|
||||
@ -997,7 +943,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
|
||||
private suspend fun handleSasCodeDoesNotMatch(msg: VerificationIntent.ActionSASCodeDoesNotMatch) {
|
||||
val transactionId = msg.transactionId
|
||||
val matchingRequest = pendingRequests.flatMap { it.value }.firstOrNull { it.requestId == transactionId }
|
||||
val matchingRequest = verificationRequestsStore.getExistingRequestWithRequestId(msg.transactionId)
|
||||
?: return Unit.also {
|
||||
msg.deferred.completeExceptionally(IllegalStateException("Unknown Request"))
|
||||
}
|
||||
@ -1046,7 +992,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
existing.state = SasTransactionState.Done(true)
|
||||
dispatchUpdate(VerificationEvent.TransactionUpdated(existing))
|
||||
// we can forget about it
|
||||
txMap[matchingRequest.otherUserId]?.remove(matchingRequest.requestId)
|
||||
verificationRequestsStore.deleteTransaction(matchingRequest.otherUserId, matchingRequest.requestId)
|
||||
// XXX whatabout waiting for done?
|
||||
matchingRequest.state = EVerificationState.Done
|
||||
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
||||
@ -1090,7 +1036,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
// let's trust him
|
||||
// it's his code scanned so user is him and other me
|
||||
try {
|
||||
crossSigningService.get().trustUser(matchingRequest.otherUserId)
|
||||
verificationTrustBackend.trustUser(matchingRequest.otherUserId)
|
||||
} catch (failure: Throwable) {
|
||||
// fail silently?
|
||||
// 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
|
||||
secretShareManager.onVerificationCompleteForDevice(matchingRequest.otherDeviceId()!!)
|
||||
try {
|
||||
crossSigningService.get().trustDevice(matchingRequest.otherDeviceId()!!)
|
||||
verificationTrustBackend.trustOwnDevice(matchingRequest.otherDeviceId()!!)
|
||||
} catch (failure: Throwable) {
|
||||
// network problem??
|
||||
Timber.w("## Verification: Failed to sign new device ${matchingRequest.otherDeviceId()}, ${failure.localizedMessage}")
|
||||
@ -1111,7 +1057,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
}
|
||||
is QrCodeData.SelfVerifyingMasterKeyTrusted -> {
|
||||
// I can trust my MSK
|
||||
crossSigningService.get().markMyMasterKeyAsTrusted()
|
||||
verificationTrustBackend.markMyMasterKeyAsTrusted()
|
||||
shouldRequestSecret = true
|
||||
}
|
||||
null -> {
|
||||
@ -1137,7 +1083,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
existing.state = QRCodeVerificationState.Done
|
||||
dispatchUpdate(VerificationEvent.TransactionUpdated(existing))
|
||||
// we can forget about it
|
||||
txMap[matchingRequest.otherUserId]?.remove(matchingRequest.requestId)
|
||||
verificationRequestsStore.deleteTransaction(matchingRequest.otherUserId, matchingRequest.requestId)
|
||||
matchingRequest.state = EVerificationState.WaitingForDone
|
||||
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
||||
|
||||
@ -1153,7 +1099,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
|
||||
private suspend fun handleSasCodeMatch(msg: VerificationIntent.ActionSASCodeMatches) {
|
||||
val transactionId = msg.transactionId
|
||||
val matchingRequest = pendingRequests.flatMap { it.value }.firstOrNull { it.requestId == transactionId }
|
||||
val matchingRequest = verificationRequestsStore.getExistingRequestWithRequestId(msg.transactionId)
|
||||
?: return Unit.also {
|
||||
msg.deferred.completeExceptionally(IllegalStateException("Unknown Request"))
|
||||
}
|
||||
@ -1212,8 +1158,8 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
) {
|
||||
val result = existing.verifyMacs(
|
||||
theirMac,
|
||||
cryptoStore.getUserDeviceList(matchingRequest.otherUserId).orEmpty(),
|
||||
cryptoStore.getCrossSigningInfo(matchingRequest.otherUserId)?.masterKey()?.unpaddedBase64PublicKey
|
||||
verificationTrustBackend.getUserDeviceList(matchingRequest.otherUserId),
|
||||
verificationTrustBackend.getUserMasterKeyBase64(matchingRequest.otherUserId)
|
||||
)
|
||||
|
||||
Timber.tag(loggerTag.value)
|
||||
@ -1222,20 +1168,13 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
is KotlinSasTransaction.MacVerificationResult.Success -> {
|
||||
// mark the devices as locally trusted
|
||||
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
|
||||
try {
|
||||
crossSigningService.get().trustDevice(deviceId)
|
||||
verificationTrustBackend.trustOwnDevice(deviceId)
|
||||
} catch (failure: Throwable) {
|
||||
// network problem??
|
||||
Timber.w("## Verification: Failed to sign new device $deviceId, ${failure.localizedMessage}")
|
||||
@ -1245,11 +1184,11 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
|
||||
if (result.otherMskTrusted) {
|
||||
if (matchingRequest.otherUserId == myUserId) {
|
||||
cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
|
||||
verificationTrustBackend.markMyMasterKeyAsTrusted()
|
||||
} else {
|
||||
// what should we do if this fails :/
|
||||
if (crossSigningService.get().canCrossSign()) {
|
||||
crossSigningService.get().trustUser(matchingRequest.otherUserId)
|
||||
if (verificationTrustBackend.canCrossSign()) {
|
||||
verificationTrustBackend.trustUser(matchingRequest.otherUserId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1272,8 +1211,8 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
|
||||
existing.state = SasTransactionState.Done(false)
|
||||
dispatchUpdate(VerificationEvent.TransactionUpdated(existing))
|
||||
pastTransactions.getOrPut(transactionId) { mutableMapOf() }[transactionId] = existing
|
||||
txMap[matchingRequest.otherUserId]?.remove(transactionId)
|
||||
verificationRequestsStore.rememberPastSuccessfulTransaction(existing)
|
||||
verificationRequestsStore.deleteTransaction(matchingRequest.otherUserId, transactionId)
|
||||
matchingRequest.state = EVerificationState.WaitingForDone
|
||||
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
||||
}
|
||||
@ -1287,9 +1226,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
}
|
||||
|
||||
private suspend fun handleActionReadyRequest(msg: VerificationIntent.ActionReadyRequest) {
|
||||
val existing = pendingRequests
|
||||
.flatMap { it.value }
|
||||
.firstOrNull { it.requestId == msg.transactionId }
|
||||
val existing = verificationRequestsStore.getExistingRequestWithRequestId(msg.transactionId)
|
||||
?: return Unit.also {
|
||||
Timber.tag(loggerTag.value).v("Request ${msg.transactionId} not found!")
|
||||
msg.deferred.complete(null)
|
||||
@ -1308,13 +1245,11 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
)
|
||||
if (commonMethods.isEmpty()) {
|
||||
Timber.tag(loggerTag.value).v("Request ${msg.transactionId} no common methods")
|
||||
cancelRequest(existing, CancelCode.UnknownMethod)
|
||||
// Upon receipt of Alice’s m.key.verification.request message, if Bob’s device does not understand any of the methods,
|
||||
// it should not cancel the request as one of his other devices may support the request.
|
||||
|
||||
// XXX How to o that??
|
||||
// Instead, Bob’s 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
|
||||
}
|
||||
|
||||
@ -1329,7 +1264,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
|
||||
val readyInfo = ValidVerificationInfoReady(
|
||||
msg.transactionId,
|
||||
cryptoStore.getDeviceId(),
|
||||
verificationTrustBackend.getMyDeviceId(),
|
||||
commonMethods
|
||||
)
|
||||
|
||||
@ -1337,7 +1272,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
inRoom = existing.roomId != null,
|
||||
requestId = msg.transactionId,
|
||||
methods = commonMethods,
|
||||
fromDevice = cryptoStore.getDeviceId()
|
||||
fromDevice = verificationTrustBackend.getMyDeviceId()
|
||||
)
|
||||
try {
|
||||
transportLayer.sendToOther(existing, EventType.KEY_VERIFICATION_READY, message)
|
||||
@ -1357,11 +1292,11 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
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 {
|
||||
myUserId != otherUserId ->
|
||||
createQrCodeDataForDistinctUser(requestId, otherUserId)
|
||||
cryptoStore.getMyCrossSigningInfo()?.isTrusted().orFalse() ->
|
||||
verificationTrustBackend.getMyTrustedMasterKeyBase64() != null ->
|
||||
// This is a self verification and I am the old device (Osborne2)
|
||||
createQrCodeDataForVerifiedDevice(requestId, otherUserId, otherDeviceId)
|
||||
else ->
|
||||
@ -1410,7 +1345,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
}
|
||||
|
||||
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
|
||||
requestsForUser.toList().forEach { existingRequest ->
|
||||
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
|
||||
// Not sure if it ok to do that (?)
|
||||
val reciprocateMethod = msg.methods
|
||||
@ -1436,7 +1371,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
|
||||
val validInfo = ValidVerificationInfoRequest(
|
||||
transactionId = "",
|
||||
fromDevice = cryptoStore.getDeviceId(),
|
||||
fromDevice = verificationTrustBackend.getMyDeviceId(),
|
||||
methods = methodValues,
|
||||
timestamp = clock.epochMillis()
|
||||
)
|
||||
@ -1466,7 +1401,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
roomId = msg.roomId
|
||||
requestInfo = validInfo.copy(transactionId = eventId)
|
||||
}
|
||||
requestsForUser.add(request)
|
||||
verificationRequestsStore.addRequest(msg.otherUserId, request)
|
||||
msg.deferred.complete(request.toPendingVerificationRequest())
|
||||
dispatchUpdate(VerificationEvent.RequestAdded(request.toPendingVerificationRequest()))
|
||||
} else {
|
||||
@ -1475,7 +1410,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
messageType = EventType.KEY_VERIFICATION_REQUEST,
|
||||
toSendToDeviceObject = KeyVerificationRequest(
|
||||
transactionId = requestId,
|
||||
fromDevice = cryptoStore.getDeviceId(),
|
||||
fromDevice = verificationTrustBackend.getMyDeviceId(),
|
||||
methods = validInfo.methods,
|
||||
timestamp = validInfo.timestamp
|
||||
),
|
||||
@ -1493,7 +1428,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
roomId = null
|
||||
requestInfo = validInfo.copy(transactionId = requestId)
|
||||
}
|
||||
requestsForUser.add(request)
|
||||
verificationRequestsStore.addRequest(msg.otherUserId, request)
|
||||
msg.deferred.complete(request.toPendingVerificationRequest())
|
||||
dispatchUpdate(VerificationEvent.RequestAdded(request.toPendingVerificationRequest()))
|
||||
}
|
||||
@ -1505,13 +1440,13 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
}
|
||||
|
||||
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 {
|
||||
Timber.tag(loggerTag.value)
|
||||
.v("[${myUserId.take(8)}]: No matching request to ready tId:${msg.transactionId}")
|
||||
// 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) {
|
||||
cancelRequest(matchingRequest, CancelCode.UnexpectedMessage)
|
||||
@ -1552,11 +1487,11 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
if (msg.viaRoom == null) {
|
||||
// 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
|
||||
val deviceIds = cryptoStore.getUserDevices(matchingRequest.otherUserId)?.keys
|
||||
?.filter { it != msg.readyInfo.fromDevice }
|
||||
val deviceIds = verificationTrustBackend.getUserDeviceList(matchingRequest.otherUserId)
|
||||
.filter { it.deviceId != msg.readyInfo.fromDevice }
|
||||
// if it's me we don't want to send self cancel
|
||||
?.filter { it != myDevice }
|
||||
.orEmpty()
|
||||
.filter { it.deviceId != myDevice }
|
||||
.map { it.deviceId }
|
||||
|
||||
try {
|
||||
transportLayer.sendToDeviceEvent(
|
||||
@ -1577,7 +1512,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// 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))
|
||||
// }
|
||||
|
||||
private suspend fun dispatchRequestAdded(tx: KotlinVerificationRequest) {
|
||||
private fun dispatchRequestAdded(tx: KotlinVerificationRequest) {
|
||||
Timber.v("## SAS dispatchRequestAdded txId:${tx.requestId}")
|
||||
dispatchUpdate(VerificationEvent.RequestAdded(tx.toPendingVerificationRequest()))
|
||||
}
|
||||
|
||||
// Utilities
|
||||
|
||||
private fun createQrCodeDataForDistinctUser(requestId: String, otherUserId: String): QrCodeData.VerifyingAnotherUser? {
|
||||
val myMasterKey = cryptoStore.getMyCrossSigningInfo()
|
||||
?.masterKey()
|
||||
?.unpaddedBase64PublicKey
|
||||
private suspend fun createQrCodeDataForDistinctUser(requestId: String, otherUserId: String): QrCodeData.VerifyingAnotherUser? {
|
||||
val myMasterKey = verificationTrustBackend.getMyTrustedMasterKeyBase64()
|
||||
?: run {
|
||||
Timber.w("## Unable to get my master key")
|
||||
return null
|
||||
}
|
||||
|
||||
val otherUserMasterKey = cryptoStore.getCrossSigningInfo(otherUserId)
|
||||
?.masterKey()
|
||||
?.unpaddedBase64PublicKey
|
||||
val otherUserMasterKey = verificationTrustBackend.getUserMasterKeyBase64(otherUserId)
|
||||
?: run {
|
||||
Timber.w("## Unable to get other user master key")
|
||||
return null
|
||||
@ -1635,10 +1566,8 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
}
|
||||
|
||||
// Create a QR code to display on the old device (Osborne2)
|
||||
private fun createQrCodeDataForVerifiedDevice(requestId: String, otherUserId: String, otherDeviceId: String?): QrCodeData.SelfVerifyingMasterKeyTrusted? {
|
||||
val myMasterKey = cryptoStore.getMyCrossSigningInfo()
|
||||
?.masterKey()
|
||||
?.unpaddedBase64PublicKey
|
||||
private suspend fun createQrCodeDataForVerifiedDevice(requestId: String, otherUserId: String, otherDeviceId: String?): QrCodeData.SelfVerifyingMasterKeyTrusted? {
|
||||
val myMasterKey = verificationTrustBackend.getUserMasterKeyBase64(myUserId)
|
||||
?: run {
|
||||
Timber.w("## Unable to get my master key")
|
||||
return null
|
||||
@ -1646,7 +1575,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
|
||||
val otherDeviceKey = otherDeviceId
|
||||
?.let {
|
||||
cryptoStore.getUserDevice(otherUserId, otherDeviceId)?.fingerprint()
|
||||
verificationTrustBackend.getUserDevice(otherUserId, otherDeviceId)?.fingerprint()
|
||||
}
|
||||
?: run {
|
||||
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)
|
||||
private fun createQrCodeDataForUnVerifiedDevice(requestId: String): QrCodeData.SelfVerifyingMasterKeyNotTrusted? {
|
||||
val myMasterKey = cryptoStore.getMyCrossSigningInfo()
|
||||
?.masterKey()
|
||||
?.unpaddedBase64PublicKey
|
||||
private suspend fun createQrCodeDataForUnVerifiedDevice(requestId: String): QrCodeData.SelfVerifyingMasterKeyNotTrusted? {
|
||||
val myMasterKey = verificationTrustBackend.getUserMasterKeyBase64(myUserId)
|
||||
?: run {
|
||||
Timber.w("## Unable to get my master key")
|
||||
return null
|
||||
}
|
||||
|
||||
val myDeviceKey = cryptoStore.getUserDevice(myUserId, cryptoStore.getDeviceId())?.fingerprint()
|
||||
val myDeviceKey = verificationTrustBackend.getUserDevice(myUserId, verificationTrustBackend.getMyDeviceId())?.fingerprint()
|
||||
?: return null.also {
|
||||
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) {
|
||||
request.state = EVerificationState.Cancelled
|
||||
request.cancelCode = code
|
||||
@ -1748,12 +1619,12 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
// should also update SAS/QR transaction
|
||||
getExistingTransaction<KotlinSasTransaction>(request.otherUserId, request.requestId)?.let {
|
||||
it.state = SasTransactionState.Cancelled(code, true)
|
||||
txMap[request.otherUserId]?.remove(request.requestId)
|
||||
verificationRequestsStore.deleteTransaction(request.otherUserId, request.requestId)
|
||||
dispatchUpdate(VerificationEvent.TransactionUpdated(it))
|
||||
}
|
||||
getExistingTransaction<KotlinQRVerification>(request.otherUserId, request.requestId)?.let {
|
||||
it.state = QRCodeVerificationState.Cancelled
|
||||
txMap[request.otherUserId]?.remove(request.requestId)
|
||||
verificationRequestsStore.deleteTransaction(request.otherUserId, request.requestId)
|
||||
dispatchUpdate(VerificationEvent.TransactionUpdated(it))
|
||||
}
|
||||
|
||||
@ -1809,30 +1680,29 @@ internal class VerificationActor @AssistedInject constructor(
|
||||
|
||||
private fun hashUsingAgreedHashMethod(hashMethod: String?, toHash: String): String {
|
||||
if ("sha256" == hashMethod?.lowercase(Locale.ROOT)) {
|
||||
return withOlmUtility {
|
||||
it.sha256(toHash)
|
||||
}
|
||||
return olmPrimitiveProvider.sha256(toHash)
|
||||
}
|
||||
throw java.lang.IllegalArgumentException("Unsupported hash method $hashMethod")
|
||||
}
|
||||
|
||||
private suspend fun addTransaction(tx: VerificationTransaction) {
|
||||
val txInnerMap = txMap.getOrPut(tx.otherUserId) { mutableMapOf() }
|
||||
txInnerMap[tx.transactionId] = tx
|
||||
verificationRequestsStore.addTransaction(tx)
|
||||
dispatchUpdate(VerificationEvent.TransactionAdded(tx))
|
||||
}
|
||||
|
||||
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? {
|
||||
txMap.forEach {
|
||||
val match = it.value.values
|
||||
.firstOrNull { it.transactionId == transactionId }
|
||||
?.takeIf { it is T }
|
||||
if (match != null) return match as? T
|
||||
}
|
||||
return null
|
||||
return verificationRequestsStore.getExistingTransaction(transactionId)
|
||||
.takeIf { it is T } as? T
|
||||
// txMap.forEach {
|
||||
// val match = it.value.values
|
||||
// .firstOrNull { it.transactionId == transactionId }
|
||||
// ?.takeIf { it is T }
|
||||
// if (match != null) return match as? T
|
||||
// }
|
||||
// return null
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -42,14 +42,6 @@ internal sealed class VerificationIntent {
|
||||
// val deferred: CompletableDeferred<IVerificationRequest>,
|
||||
) : VerificationIntent()
|
||||
|
||||
data class FailToSendRequest(
|
||||
val request: PendingVerificationRequest,
|
||||
) : VerificationIntent()
|
||||
|
||||
// data class UpdateRequest(
|
||||
// val request: IVerificationRequest,
|
||||
// ) : VerificationIntent()
|
||||
|
||||
data class ActionReadyRequest(
|
||||
val transactionId: String,
|
||||
val methods: List<VerificationMethod>,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -16,16 +16,15 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.verification
|
||||
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
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.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.UnsignedDeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.MXCryptoAlgorithms
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
|
||||
enum class StoreMode {
|
||||
Alice,
|
||||
@ -34,10 +33,10 @@ enum class StoreMode {
|
||||
|
||||
internal class FakeCryptoStoreForVerification(private val mode: StoreMode) {
|
||||
|
||||
val instance = mockk<IMXCryptoStore>()
|
||||
val instance = mockk<VerificationTrustBackend>()
|
||||
|
||||
init {
|
||||
every { instance.getDeviceId() } answers {
|
||||
every { instance.getMyDeviceId() } answers {
|
||||
when (mode) {
|
||||
StoreMode.Alice -> aliceDevice1Id
|
||||
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
|
||||
every { instance.getUserDevice(any(), any()) } returns null
|
||||
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) {
|
||||
StoreMode.Alice -> {
|
||||
MXCrossSigningInfo(
|
||||
aliceMxId,
|
||||
listOf(
|
||||
aliceMSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
|
||||
aliceUSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
|
||||
aliceSSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
|
||||
),
|
||||
wasTrustedOnce = true
|
||||
)
|
||||
aliceMSK
|
||||
}
|
||||
StoreMode.Bob -> {
|
||||
MXCrossSigningInfo(
|
||||
aliceMxId,
|
||||
listOf(
|
||||
aliceMSKBase.copy(trustLevel = DeviceTrustLevel(false, false)),
|
||||
aliceUSKBase.copy(trustLevel = DeviceTrustLevel(false, false)),
|
||||
),
|
||||
wasTrustedOnce = false
|
||||
)
|
||||
bobMSK
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
StoreMode.Alice -> {
|
||||
MXCrossSigningInfo(
|
||||
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
|
||||
)
|
||||
}
|
||||
StoreMode.Alice -> aliceDevice1Id
|
||||
StoreMode.Bob -> bobDeviceId
|
||||
}
|
||||
}
|
||||
|
||||
every { instance.getMyCrossSigningInfo() } answers {
|
||||
coEvery { instance.getMyDevice() } answers {
|
||||
when (mode) {
|
||||
StoreMode.Alice -> MXCrossSigningInfo(
|
||||
aliceMxId,
|
||||
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
|
||||
)
|
||||
StoreMode.Alice -> aliceFirstDevice
|
||||
StoreMode.Bob -> aBobDevice
|
||||
}
|
||||
}
|
||||
|
||||
coEvery {
|
||||
instance.trustOwnDevice(any())
|
||||
} returns Unit
|
||||
|
||||
coEvery {
|
||||
instance.trustUser(any())
|
||||
} returns Unit
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ -133,7 +101,7 @@ internal class FakeCryptoStoreForVerification(private val mode: StoreMode) {
|
||||
val bobMxId = "bob@example.com"
|
||||
val bobDeviceId = "MKRJDSLYGA"
|
||||
|
||||
private val aliceDevice1Id = "MGDAADVDMG"
|
||||
val aliceDevice1Id = "MGDAADVDMG"
|
||||
|
||||
private val aliceMSK = "Ru4ni66dbQ6FZgUoHyyBtmjKecOHMvMSsSBZ2SABtt0"
|
||||
private val aliceSSK = "Rw6MiEn5do57mBWlWUvL6VDZJ7vAfGrTC58UXVyA0eo"
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.verification
|
||||
|
||||
import dagger.Lazy
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
@ -25,17 +24,16 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.channels.SendChannel
|
||||
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.events.model.Content
|
||||
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.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.ValidVerificationDone
|
||||
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.olm.OlmSAS
|
||||
import java.util.UUID
|
||||
|
||||
internal class VerificationActorHelper {
|
||||
@ -43,6 +41,8 @@ internal class VerificationActorHelper {
|
||||
data class TestData(
|
||||
val aliceActor: VerificationActor,
|
||||
val bobActor: VerificationActor,
|
||||
val aliceStore: FakeCryptoStoreForVerification,
|
||||
val bobStore: FakeCryptoStoreForVerification,
|
||||
)
|
||||
|
||||
val actorAScope = CoroutineScope(SupervisorJob())
|
||||
@ -56,35 +56,29 @@ internal class VerificationActorHelper {
|
||||
val aliceTransportLayer = mockTransportTo(FakeCryptoStoreForVerification.aliceMxId) { bobChannel }
|
||||
val bobTransportLayer = mockTransportTo(FakeCryptoStoreForVerification.bobMxId) { aliceChannel }
|
||||
|
||||
val fakeAliceStore = FakeCryptoStoreForVerification(StoreMode.Alice)
|
||||
val aliceActor = fakeActor(
|
||||
actorAScope,
|
||||
FakeCryptoStoreForVerification.aliceMxId,
|
||||
FakeCryptoStoreForVerification(StoreMode.Alice).instance,
|
||||
fakeAliceStore.instance,
|
||||
aliceTransportLayer,
|
||||
mockk<Lazy<CrossSigningService>> {
|
||||
every {
|
||||
get()
|
||||
} returns mockk<CrossSigningService>(relaxed = true)
|
||||
}
|
||||
)
|
||||
aliceChannel = aliceActor.channel
|
||||
|
||||
val fakeBobStore = FakeCryptoStoreForVerification(StoreMode.Bob)
|
||||
val bobActor = fakeActor(
|
||||
actorBScope,
|
||||
FakeCryptoStoreForVerification.aliceMxId,
|
||||
FakeCryptoStoreForVerification(StoreMode.Alice).instance,
|
||||
bobTransportLayer,
|
||||
mockk<Lazy<CrossSigningService>> {
|
||||
every {
|
||||
get()
|
||||
} returns mockk<CrossSigningService>(relaxed = true)
|
||||
}
|
||||
FakeCryptoStoreForVerification.bobMxId,
|
||||
fakeBobStore.instance,
|
||||
bobTransportLayer
|
||||
)
|
||||
bobChannel = bobActor.channel
|
||||
|
||||
return TestData(
|
||||
aliceActor,
|
||||
bobActor
|
||||
aliceActor = aliceActor,
|
||||
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(
|
||||
scope: CoroutineScope,
|
||||
userId: String,
|
||||
cryptoStore: IMXCryptoStore,
|
||||
cryptoStore: VerificationTrustBackend,
|
||||
transportLayer: VerificationTransportLayer,
|
||||
crossSigningService: dagger.Lazy<CrossSigningService>,
|
||||
): VerificationActor {
|
||||
return VerificationActor(
|
||||
scope,
|
||||
@ -165,17 +208,19 @@ internal class VerificationActorHelper {
|
||||
every { epochMillis() } returns System.currentTimeMillis()
|
||||
},
|
||||
myUserId = userId,
|
||||
cryptoStore = cryptoStore,
|
||||
verificationTrustBackend = cryptoStore,
|
||||
secretShareManager = mockk<SecretShareManager> {},
|
||||
transportLayer = transportLayer,
|
||||
crossSigningService = crossSigningService,
|
||||
setDeviceVerificationAction = SetDeviceVerificationAction(
|
||||
cryptoStore = cryptoStore,
|
||||
userId = userId,
|
||||
defaultKeysBackupService = mockk {
|
||||
coEvery { checkAndStartKeysBackup() } coAnswers { }
|
||||
}
|
||||
)
|
||||
verificationRequestsStore = VerificationRequestsStore(),
|
||||
olmPrimitiveProvider = mockk<VerificationCryptoPrimitiveProvider> {
|
||||
every { provideOlmSas() } returns mockk<OlmSAS> {
|
||||
every { publicKey } returns "Tm9JRGVhRmFrZQo="
|
||||
every { setTheirPublicKey(any()) } returns Unit
|
||||
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"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,11 @@
|
||||
package org.matrix.android.sdk.internal.crypto.verification.org.matrix.android.sdk.internal.crypto.verification
|
||||
|
||||
import android.util.Base64
|
||||
import io.mockk.clearAllMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkConstructor
|
||||
import io.mockk.mockkStatic
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -26,6 +30,9 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
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.test.runTest
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -34,23 +41,29 @@ import org.amshove.kluent.internal.assertEquals
|
||||
import org.amshove.kluent.internal.assertNotEquals
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
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.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.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.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.VerificationActor
|
||||
import org.matrix.android.sdk.internal.crypto.verification.VerificationActorHelper
|
||||
import org.matrix.android.sdk.internal.crypto.verification.VerificationIntent
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class VerificationActorTest {
|
||||
class VerificationActorTest : MatrixTest {
|
||||
|
||||
val transportScope = CoroutineScope(SupervisorJob())
|
||||
// val actorAScope = CoroutineScope(SupervisorJob())
|
||||
// val actorBScope = CoroutineScope(SupervisorJob())
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
@ -69,6 +82,20 @@ class VerificationActorTest {
|
||||
val array = firstArg<String>()
|
||||
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
|
||||
@ -170,6 +197,39 @@ class VerificationActorTest {
|
||||
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 Alice’s m.key.verification.request message, if Bob’s 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
|
||||
fun `Test bob can show but not scan QR`() = runTest {
|
||||
val testData = VerificationActorHelper().setUpActors()
|
||||
@ -215,6 +275,199 @@ class VerificationActorTest {
|
||||
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 {
|
||||
return awaitDeferrable<PendingVerificationRequest> {
|
||||
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}")
|
||||
// }
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user