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,
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
msg.deferred.complete(it)
|
.getExistingTransaction(msg.fromUser, msg.transactionId)
|
||||||
}
|
?.let {
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
// Bob’s device ensures that it has a copy of Alice’s device key.
|
// 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) {
|
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(
|
|||||||
|
|
||||||
// Alice’s device creates an ephemeral Curve25519 key pair (dA,QA),
|
// 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
|
// 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)
|
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 Alice’s m.key.verification.request message, if Bob’s device does not understand any of the methods,
|
// 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.
|
// 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.
|
// 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
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>,
|
// 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>,
|
||||||
|
@ -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
|
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"
|
||||||
|
@ -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"
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 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
|
@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}")
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user