mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-29 06:28:45 +03:00
Actor unit test setup
This commit is contained in:
parent
5c82bdba38
commit
0c1e439313
8 changed files with 792 additions and 144 deletions
|
@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto.verification
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
|
@ -89,13 +88,7 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
private val stateMachine: VerificationActor
|
private val stateMachine: VerificationActor
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val channel = Channel<VerificationIntent>(
|
stateMachine = verificationActorFactory.create(executorScope)
|
||||||
capacity = Channel.UNLIMITED,
|
|
||||||
)
|
|
||||||
stateMachine = verificationActorFactory.create(channel)
|
|
||||||
executorScope.launch {
|
|
||||||
for (msg in channel) stateMachine.onReceive(msg)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// It's obselete but not deprecated
|
// It's obselete but not deprecated
|
||||||
// It's ok as it will be replaced by rust implementation
|
// It's ok as it will be replaced by rust implementation
|
||||||
|
|
|
@ -16,11 +16,15 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto.verification
|
package org.matrix.android.sdk.internal.crypto.verification
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.BuildConfig
|
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
|
||||||
|
@ -31,8 +35,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_S
|
||||||
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.model.MXUsersDevicesMap
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject
|
|
||||||
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.PendingVerificationRequest
|
||||||
|
@ -45,12 +47,9 @@ 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.VerificationTransaction
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf
|
import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
|
||||||
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.LocalEcho
|
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
import org.matrix.android.sdk.api.session.events.model.UnsignedData
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationCancelContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationCancelContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationDoneContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationDoneContent
|
||||||
|
@ -69,14 +68,11 @@ import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_REC
|
||||||
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.store.IMXCryptoStore
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
|
|
||||||
import org.matrix.android.sdk.internal.crypto.tools.withOlmUtility
|
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
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
|
||||||
import org.matrix.android.sdk.internal.util.time.Clock
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
@ -91,21 +87,34 @@ import java.util.Locale
|
||||||
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 channel: Channel<VerificationIntent>,
|
@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 cryptoStore: IMXCryptoStore,
|
||||||
private val sendVerificationMessageTask: SendVerificationMessageTask,
|
|
||||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
|
||||||
private val sendToDeviceTask: SendToDeviceTask,
|
|
||||||
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
private val setDeviceVerificationAction: SetDeviceVerificationAction,
|
||||||
private val crossSigningService: dagger.Lazy<CrossSigningService>,
|
private val crossSigningService: dagger.Lazy<CrossSigningService>,
|
||||||
private val secretShareManager: SecretShareManager,
|
private val secretShareManager: SecretShareManager,
|
||||||
|
private val transportLayer: VerificationTransportLayer,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(channel: Channel<VerificationIntent>): VerificationActor
|
fun create(scope: CoroutineScope): VerificationActor
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
val channel = Channel<VerificationIntent>(
|
||||||
|
capacity = Channel.UNLIMITED,
|
||||||
|
)
|
||||||
|
|
||||||
|
init {
|
||||||
|
scope.launch {
|
||||||
|
Timber.e("VALR BEFORE")
|
||||||
|
for (msg in channel) {
|
||||||
|
onReceive(msg)
|
||||||
|
}
|
||||||
|
Timber.e("VALR NNNNNNNN")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// map [sender : [transaction]]
|
// map [sender : [transaction]]
|
||||||
|
@ -121,7 +130,10 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
*/
|
*/
|
||||||
private val pendingRequests = HashMap<String, MutableList<KotlinVerificationRequest>>()
|
private val pendingRequests = HashMap<String, MutableList<KotlinVerificationRequest>>()
|
||||||
|
|
||||||
val eventFlow = MutableSharedFlow<VerificationEvent>(replay = 0)
|
// Replaces the typical list of listeners pattern. Looks to me as the sane setup, not sure if more than 1 is needed as extraBufferCapacity
|
||||||
|
// We don't want to use emit as it would block if no listener is subscribed
|
||||||
|
// So we should use try emit using extraBufferCapacity, we use drop_oldest instead of suspend.
|
||||||
|
val eventFlow = MutableSharedFlow<VerificationEvent>(extraBufferCapacity = 4, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||||
|
|
||||||
suspend fun send(intent: VerificationIntent) {
|
suspend fun send(intent: VerificationIntent) {
|
||||||
channel.send(intent)
|
channel.send(intent)
|
||||||
|
@ -230,7 +242,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
handleIncomingRequest(msg)
|
handleIncomingRequest(msg)
|
||||||
}
|
}
|
||||||
is VerificationIntent.ActionReadyRequest -> {
|
is VerificationIntent.ActionReadyRequest -> {
|
||||||
handleReadyRequest(msg)
|
handleActionReadyRequest(msg)
|
||||||
}
|
}
|
||||||
is VerificationIntent.ActionStartSasVerification -> {
|
is VerificationIntent.ActionStartSasVerification -> {
|
||||||
handleSasStart(msg)
|
handleSasStart(msg)
|
||||||
|
@ -323,9 +335,9 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
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)
|
txMap[msg.fromUser]?.remove(msg.validCancel.transactionId)
|
||||||
eventFlow.emit(VerificationEvent.TransactionUpdated(existingTx))
|
dispatchUpdate(VerificationEvent.TransactionUpdated(existingTx))
|
||||||
}
|
}
|
||||||
eventFlow.emit(VerificationEvent.RequestUpdated(request.toPendingVerificationRequest()))
|
dispatchUpdate(VerificationEvent.RequestUpdated(request.toPendingVerificationRequest()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is VerificationIntent.OnReadyByAnotherOfMySessionReceived -> {
|
is VerificationIntent.OnReadyByAnotherOfMySessionReceived -> {
|
||||||
|
@ -334,6 +346,12 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun dispatchUpdate(update: VerificationEvent) {
|
||||||
|
// We don't want to block on emit.
|
||||||
|
// If no subscriber there is a small buffer and too old would be dropped
|
||||||
|
eventFlow.tryEmit(update)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun handleIncomingRequest(msg: VerificationIntent.OnVerificationRequestReceived) {
|
private suspend fun handleIncomingRequest(msg: VerificationIntent.OnVerificationRequestReceived) {
|
||||||
val pendingVerificationRequest = KotlinVerificationRequest(
|
val pendingVerificationRequest = KotlinVerificationRequest(
|
||||||
requestId = msg.validRequestInfo.transactionId,
|
requestId = msg.validRequestInfo.transactionId,
|
||||||
|
@ -395,7 +413,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
matchingRequest.state = EVerificationState.Started
|
matchingRequest.state = EVerificationState.Started
|
||||||
eventFlow.emit(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleReceiveStartForQR(request: KotlinVerificationRequest, reciprocate: ValidVerificationInfoStart.ReciprocateVerificationInfoStart) {
|
private suspend fun handleReceiveStartForQR(request: KotlinVerificationRequest, reciprocate: ValidVerificationInfoStart.ReciprocateVerificationInfoStart) {
|
||||||
|
@ -518,7 +536,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
|
|
||||||
// cancel if network error (would not send back a cancel but at least current user will see feedback?)
|
// cancel if network error (would not send back a cancel but at least current user will see feedback?)
|
||||||
try {
|
try {
|
||||||
sendToOther(request, EventType.KEY_VERIFICATION_ACCEPT, accept)
|
transportLayer.sendToOther(request, EventType.KEY_VERIFICATION_ACCEPT, accept)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
Timber.tag(loggerTag.value)
|
Timber.tag(loggerTag.value)
|
||||||
.v("[${myUserId.take(8)}] Failed to send accept for ${request.requestId}")
|
.v("[${myUserId.take(8)}] Failed to send accept for ${request.requestId}")
|
||||||
|
@ -569,7 +587,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
Timber.tag(loggerTag.value)
|
Timber.tag(loggerTag.value)
|
||||||
.v("[${myUserId.take(8)}]: Sending my key $pubKey")
|
.v("[${myUserId.take(8)}]: Sending my key $pubKey")
|
||||||
}
|
}
|
||||||
sendToOther(
|
transportLayer.sendToOther(
|
||||||
matchingRequest,
|
matchingRequest,
|
||||||
EventType.KEY_VERIFICATION_KEY,
|
EventType.KEY_VERIFICATION_KEY,
|
||||||
keyMessage,
|
keyMessage,
|
||||||
|
@ -578,13 +596,13 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
existing.state = SasTransactionState.Cancelled(CancelCode.UserError, true)
|
existing.state = SasTransactionState.Cancelled(CancelCode.UserError, true)
|
||||||
matchingRequest.cancelCode = CancelCode.UserError
|
matchingRequest.cancelCode = CancelCode.UserError
|
||||||
matchingRequest.state = EVerificationState.Cancelled
|
matchingRequest.state = EVerificationState.Cancelled
|
||||||
eventFlow.emit(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
||||||
eventFlow.emit(VerificationEvent.TransactionUpdated(existing))
|
dispatchUpdate(VerificationEvent.TransactionUpdated(existing))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
existing.accepted = accept
|
existing.accepted = accept
|
||||||
existing.state = SasTransactionState.SasKeySent
|
existing.state = SasTransactionState.SasKeySent
|
||||||
eventFlow.emit(VerificationEvent.TransactionUpdated(existing))
|
dispatchUpdate(VerificationEvent.TransactionUpdated(existing))
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleSasStart(msg: VerificationIntent.ActionStartSasVerification) {
|
private suspend fun handleSasStart(msg: VerificationIntent.ActionStartSasVerification) {
|
||||||
|
@ -617,7 +635,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
requestId = msg.requestId
|
requestId = msg.requestId
|
||||||
)
|
)
|
||||||
|
|
||||||
sendToOther(
|
transportLayer.sendToOther(
|
||||||
matchingRequest,
|
matchingRequest,
|
||||||
EventType.KEY_VERIFICATION_START,
|
EventType.KEY_VERIFICATION_START,
|
||||||
startMessage,
|
startMessage,
|
||||||
|
@ -643,7 +661,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
matchingRequest.state = EVerificationState.WeStarted
|
matchingRequest.state = EVerificationState.WeStarted
|
||||||
eventFlow.emit(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
||||||
addTransaction(tx)
|
addTransaction(tx)
|
||||||
|
|
||||||
msg.deferred.complete(tx)
|
msg.deferred.complete(tx)
|
||||||
|
@ -805,7 +823,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
sendToOther(matchingRequest, EventType.KEY_VERIFICATION_START, message)
|
transportLayer.sendToOther(matchingRequest, EventType.KEY_VERIFICATION_START, message)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
Timber.tag(loggerTag.value)
|
Timber.tag(loggerTag.value)
|
||||||
.d("[${myUserId.take(8)}] Failed to send reciprocate message")
|
.d("[${myUserId.take(8)}] Failed to send reciprocate message")
|
||||||
|
@ -814,7 +832,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
matchingRequest.state = EVerificationState.WeStarted
|
matchingRequest.state = EVerificationState.WeStarted
|
||||||
eventFlow.emit(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
||||||
|
|
||||||
val tx = KotlinQRVerification(
|
val tx = KotlinQRVerification(
|
||||||
channel = this.channel,
|
channel = this.channel,
|
||||||
|
@ -881,7 +899,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
val pubKey = existing.getSAS().publicKey
|
val pubKey = existing.getSAS().publicKey
|
||||||
val keyMessage = KotlinSasTransaction.sasKeyMessage(matchingRequest.roomId != null, requestId, pubKey)
|
val keyMessage = KotlinSasTransaction.sasKeyMessage(matchingRequest.roomId != null, requestId, pubKey)
|
||||||
try {
|
try {
|
||||||
sendToOther(
|
transportLayer.sendToOther(
|
||||||
matchingRequest,
|
matchingRequest,
|
||||||
EventType.KEY_VERIFICATION_KEY,
|
EventType.KEY_VERIFICATION_KEY,
|
||||||
keyMessage,
|
keyMessage,
|
||||||
|
@ -898,13 +916,13 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
Timber.tag(loggerTag.value)
|
Timber.tag(loggerTag.value)
|
||||||
.v("[${myUserId.take(8)}]:i EMOJI CODE ${existing.getEmojiCodeRepresentation().joinToString(" ") { it.emoji }}")
|
.v("[${myUserId.take(8)}]:i EMOJI CODE ${existing.getEmojiCodeRepresentation().joinToString(" ") { it.emoji }}")
|
||||||
}
|
}
|
||||||
eventFlow.emit(VerificationEvent.TransactionUpdated(existing))
|
dispatchUpdate(VerificationEvent.TransactionUpdated(existing))
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
existing.state = SasTransactionState.Cancelled(CancelCode.UserError, true)
|
existing.state = SasTransactionState.Cancelled(CancelCode.UserError, true)
|
||||||
matchingRequest.state = EVerificationState.Cancelled
|
matchingRequest.state = EVerificationState.Cancelled
|
||||||
matchingRequest.cancelCode = CancelCode.UserError
|
matchingRequest.cancelCode = CancelCode.UserError
|
||||||
eventFlow.emit(VerificationEvent.TransactionUpdated(existing))
|
dispatchUpdate(VerificationEvent.TransactionUpdated(existing))
|
||||||
eventFlow.emit(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -931,7 +949,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
existing.calculateSASBytes(otherKey)
|
existing.calculateSASBytes(otherKey)
|
||||||
existing.state = SasTransactionState.SasShortCodeReady
|
existing.state = SasTransactionState.SasShortCodeReady
|
||||||
eventFlow.emit(VerificationEvent.TransactionUpdated(existing))
|
dispatchUpdate(VerificationEvent.TransactionUpdated(existing))
|
||||||
if (BuildConfig.LOG_PRIVATE_DATA) {
|
if (BuildConfig.LOG_PRIVATE_DATA) {
|
||||||
Timber.tag(loggerTag.value)
|
Timber.tag(loggerTag.value)
|
||||||
.v("[${myUserId.take(8)}]:o CODE ${existing.getDecimalCodeRepresentation()}")
|
.v("[${myUserId.take(8)}]:o CODE ${existing.getDecimalCodeRepresentation()}")
|
||||||
|
@ -966,7 +984,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
// I can start verify, store it
|
// I can start verify, store it
|
||||||
existing.theirMac = msg.validMac
|
existing.theirMac = msg.validMac
|
||||||
existing.state = SasTransactionState.SasMacReceived(false)
|
existing.state = SasTransactionState.SasMacReceived(false)
|
||||||
eventFlow.emit(VerificationEvent.TransactionUpdated(existing))
|
dispatchUpdate(VerificationEvent.TransactionUpdated(existing))
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// it's a wrong state should cancel?
|
// it's a wrong state should cancel?
|
||||||
|
@ -1026,12 +1044,12 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
|
|
||||||
if (isCorrectState) {
|
if (isCorrectState) {
|
||||||
existing.state = SasTransactionState.Done(true)
|
existing.state = SasTransactionState.Done(true)
|
||||||
eventFlow.emit(VerificationEvent.TransactionUpdated(existing))
|
dispatchUpdate(VerificationEvent.TransactionUpdated(existing))
|
||||||
// we can forget about it
|
// we can forget about it
|
||||||
txMap[matchingRequest.otherUserId()]?.remove(matchingRequest.requestId)
|
txMap[matchingRequest.otherUserId()]?.remove(matchingRequest.requestId)
|
||||||
// XXX whatabout waiting for done?
|
// XXX whatabout waiting for done?
|
||||||
matchingRequest.state = EVerificationState.Done
|
matchingRequest.state = EVerificationState.Done
|
||||||
eventFlow.emit(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
||||||
} else {
|
} else {
|
||||||
// TODO cancel?
|
// TODO cancel?
|
||||||
Timber.tag(loggerTag.value)
|
Timber.tag(loggerTag.value)
|
||||||
|
@ -1048,7 +1066,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
QRCodeVerificationState.WaitingForOtherDone -> {
|
QRCodeVerificationState.WaitingForOtherDone -> {
|
||||||
matchingRequest.state = EVerificationState.Done
|
matchingRequest.state = EVerificationState.Done
|
||||||
eventFlow.emit(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Timber.tag(loggerTag.value)
|
Timber.tag(loggerTag.value)
|
||||||
|
@ -1101,7 +1119,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendToOther(
|
transportLayer.sendToOther(
|
||||||
matchingRequest,
|
matchingRequest,
|
||||||
EventType.KEY_VERIFICATION_DONE,
|
EventType.KEY_VERIFICATION_DONE,
|
||||||
if (matchingRequest.roomId != null) {
|
if (matchingRequest.roomId != null) {
|
||||||
|
@ -1117,11 +1135,11 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
existing.state = QRCodeVerificationState.Done
|
existing.state = QRCodeVerificationState.Done
|
||||||
eventFlow.emit(VerificationEvent.TransactionUpdated(existing))
|
dispatchUpdate(VerificationEvent.TransactionUpdated(existing))
|
||||||
// we can forget about it
|
// we can forget about it
|
||||||
txMap[matchingRequest.otherUserId()]?.remove(matchingRequest.requestId)
|
txMap[matchingRequest.otherUserId()]?.remove(matchingRequest.requestId)
|
||||||
matchingRequest.state = EVerificationState.WaitingForDone
|
matchingRequest.state = EVerificationState.WaitingForDone
|
||||||
eventFlow.emit(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
||||||
|
|
||||||
if (shouldRequestSecret) {
|
if (shouldRequestSecret) {
|
||||||
matchingRequest.otherDeviceId()?.let { otherDeviceId ->
|
matchingRequest.otherDeviceId()?.let { otherDeviceId ->
|
||||||
|
@ -1167,7 +1185,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
|
|
||||||
val macMsg = KotlinSasTransaction.sasMacMessage(matchingRequest.roomId != null, transactionId, macInfo)
|
val macMsg = KotlinSasTransaction.sasMacMessage(matchingRequest.roomId != null, transactionId, macInfo)
|
||||||
try {
|
try {
|
||||||
sendToOther(matchingRequest, EventType.KEY_VERIFICATION_MAC, macMsg)
|
transportLayer.sendToOther(matchingRequest, EventType.KEY_VERIFICATION_MAC, macMsg)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
// it's a network problem, we don't need to cancel, user can retry?
|
// it's a network problem, we don't need to cancel, user can retry?
|
||||||
msg.deferred.completeExceptionally(failure)
|
msg.deferred.completeExceptionally(failure)
|
||||||
|
@ -1180,7 +1198,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
finalizeSasTransaction(existing, theirMac, matchingRequest, transactionId)
|
finalizeSasTransaction(existing, theirMac, matchingRequest, transactionId)
|
||||||
} else {
|
} else {
|
||||||
existing.state = SasTransactionState.SasMacSent
|
existing.state = SasTransactionState.SasMacSent
|
||||||
eventFlow.emit(VerificationEvent.TransactionUpdated(existing))
|
dispatchUpdate(VerificationEvent.TransactionUpdated(existing))
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.deferred.complete(Unit)
|
msg.deferred.complete(Unit)
|
||||||
|
@ -1237,7 +1255,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
// we should send done and wait for done
|
// we should send done and wait for done
|
||||||
sendToOther(
|
transportLayer.sendToOther(
|
||||||
matchingRequest,
|
matchingRequest,
|
||||||
EventType.KEY_VERIFICATION_DONE,
|
EventType.KEY_VERIFICATION_DONE,
|
||||||
if (matchingRequest.roomId != null) {
|
if (matchingRequest.roomId != null) {
|
||||||
|
@ -1253,11 +1271,11 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
existing.state = SasTransactionState.Done(false)
|
existing.state = SasTransactionState.Done(false)
|
||||||
eventFlow.emit(VerificationEvent.TransactionUpdated(existing))
|
dispatchUpdate(VerificationEvent.TransactionUpdated(existing))
|
||||||
pastTransactions.getOrPut(transactionId) { mutableMapOf() }[transactionId] = existing
|
pastTransactions.getOrPut(transactionId) { mutableMapOf() }[transactionId] = existing
|
||||||
txMap[matchingRequest.otherUserId]?.remove(transactionId)
|
txMap[matchingRequest.otherUserId]?.remove(transactionId)
|
||||||
matchingRequest.state = EVerificationState.WaitingForDone
|
matchingRequest.state = EVerificationState.WaitingForDone
|
||||||
eventFlow.emit(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
||||||
}
|
}
|
||||||
KotlinSasTransaction.MacVerificationResult.MismatchKeys,
|
KotlinSasTransaction.MacVerificationResult.MismatchKeys,
|
||||||
KotlinSasTransaction.MacVerificationResult.MismatchMacCrossSigning,
|
KotlinSasTransaction.MacVerificationResult.MismatchMacCrossSigning,
|
||||||
|
@ -1268,7 +1286,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleReadyRequest(msg: VerificationIntent.ActionReadyRequest) {
|
private suspend fun handleActionReadyRequest(msg: VerificationIntent.ActionReadyRequest) {
|
||||||
val existing = pendingRequests
|
val existing = pendingRequests
|
||||||
.flatMap { it.value }
|
.flatMap { it.value }
|
||||||
.firstOrNull { it.requestId == msg.transactionId }
|
.firstOrNull { it.requestId == msg.transactionId }
|
||||||
|
@ -1317,7 +1335,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
fromDevice = cryptoStore.getDeviceId()
|
fromDevice = cryptoStore.getDeviceId()
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
sendToOther(existing, EventType.KEY_VERIFICATION_READY, message)
|
transportLayer.sendToOther(existing, EventType.KEY_VERIFICATION_READY, message)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
msg.deferred.completeExceptionally(failure)
|
msg.deferred.completeExceptionally(failure)
|
||||||
return
|
return
|
||||||
|
@ -1326,7 +1344,9 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
existing.readyInfo = readyInfo
|
existing.readyInfo = readyInfo
|
||||||
existing.qrCodeData = qrCodeData
|
existing.qrCodeData = qrCodeData
|
||||||
existing.state = EVerificationState.Ready
|
existing.state = EVerificationState.Ready
|
||||||
eventFlow.emit(VerificationEvent.RequestUpdated(existing.toPendingVerificationRequest()))
|
|
||||||
|
// We want to try emit, if not this will suspend until someone consume the flow
|
||||||
|
dispatchUpdate(VerificationEvent.RequestUpdated(existing.toPendingVerificationRequest()))
|
||||||
|
|
||||||
Timber.tag(loggerTag.value).v("Request ${msg.transactionId} updated $existing")
|
Timber.tag(loggerTag.value).v("Request ${msg.transactionId} updated $existing")
|
||||||
msg.deferred.complete(existing.toPendingVerificationRequest())
|
msg.deferred.complete(existing.toPendingVerificationRequest())
|
||||||
|
@ -1424,13 +1444,11 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
timestamp = validInfo.timestamp,
|
timestamp = validInfo.timestamp,
|
||||||
methods = validInfo.methods
|
methods = validInfo.methods
|
||||||
)
|
)
|
||||||
val event = createEventAndLocalEcho(
|
val eventId = transportLayer.sendInRoom(
|
||||||
localId = validLocalId,
|
|
||||||
type = EventType.MESSAGE,
|
type = EventType.MESSAGE,
|
||||||
roomId = msg.roomId,
|
roomId = msg.roomId,
|
||||||
content = info.toContent()
|
content = info.toContent()
|
||||||
)
|
)
|
||||||
val eventId = sendEventInRoom(event)
|
|
||||||
val request = KotlinVerificationRequest(
|
val request = KotlinVerificationRequest(
|
||||||
requestId = eventId,
|
requestId = eventId,
|
||||||
incoming = false,
|
incoming = false,
|
||||||
|
@ -1443,10 +1461,10 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
requestsForUser.add(request)
|
requestsForUser.add(request)
|
||||||
msg.deferred.complete(request.toPendingVerificationRequest())
|
msg.deferred.complete(request.toPendingVerificationRequest())
|
||||||
eventFlow.emit(VerificationEvent.RequestAdded(request.toPendingVerificationRequest()))
|
dispatchUpdate(VerificationEvent.RequestAdded(request.toPendingVerificationRequest()))
|
||||||
} else {
|
} else {
|
||||||
val requestId = LocalEcho.createLocalEchoId()
|
val requestId = LocalEcho.createLocalEchoId()
|
||||||
sendToDeviceEvent(
|
transportLayer.sendToDeviceEvent(
|
||||||
messageType = EventType.KEY_VERIFICATION_REQUEST,
|
messageType = EventType.KEY_VERIFICATION_REQUEST,
|
||||||
toSendToDeviceObject = KeyVerificationRequest(
|
toSendToDeviceObject = KeyVerificationRequest(
|
||||||
transactionId = requestId,
|
transactionId = requestId,
|
||||||
|
@ -1470,7 +1488,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
requestsForUser.add(request)
|
requestsForUser.add(request)
|
||||||
msg.deferred.complete(request.toPendingVerificationRequest())
|
msg.deferred.complete(request.toPendingVerificationRequest())
|
||||||
eventFlow.emit(VerificationEvent.RequestAdded(request.toPendingVerificationRequest()))
|
dispatchUpdate(VerificationEvent.RequestAdded(request.toPendingVerificationRequest()))
|
||||||
}
|
}
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
// some network problem
|
// some network problem
|
||||||
|
@ -1499,13 +1517,13 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
Timber.tag(loggerTag.value)
|
Timber.tag(loggerTag.value)
|
||||||
.v("[${myUserId.take(8)}]: ready from another of my devices, make inactive")
|
.v("[${myUserId.take(8)}]: ready from another of my devices, make inactive")
|
||||||
matchingRequest.state = EVerificationState.HandledByOtherSession
|
matchingRequest.state = EVerificationState.HandledByOtherSession
|
||||||
eventFlow.emit(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
matchingRequest.readyInfo = msg.readyInfo
|
matchingRequest.readyInfo = msg.readyInfo
|
||||||
matchingRequest.state = EVerificationState.Ready
|
matchingRequest.state = EVerificationState.Ready
|
||||||
eventFlow.emit(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
||||||
|
|
||||||
// if (matchingRequest.readyInfo != null) {
|
// if (matchingRequest.readyInfo != null) {
|
||||||
// // TODO we already received a ready, cancel? or ignore
|
// // TODO we already received a ready, cancel? or ignore
|
||||||
|
@ -1530,7 +1548,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
sendToDeviceEvent(
|
transportLayer.sendToDeviceEvent(
|
||||||
EventType.KEY_VERIFICATION_CANCEL,
|
EventType.KEY_VERIFICATION_CANCEL,
|
||||||
KeyVerificationCancel(
|
KeyVerificationCancel(
|
||||||
msg.transactionId,
|
msg.transactionId,
|
||||||
|
@ -1556,7 +1574,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
Timber.tag(loggerTag.value)
|
Timber.tag(loggerTag.value)
|
||||||
.v("[${myUserId.take(8)}]: ready from another of my devices, make inactive")
|
.v("[${myUserId.take(8)}]: ready from another of my devices, make inactive")
|
||||||
matchingRequest.state = EVerificationState.HandledByOtherSession
|
matchingRequest.state = EVerificationState.HandledByOtherSession
|
||||||
eventFlow.emit(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
dispatchUpdate(VerificationEvent.RequestUpdated(matchingRequest.toPendingVerificationRequest()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1570,12 +1588,12 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
// requestsForUser.removeAt(index)
|
// requestsForUser.removeAt(index)
|
||||||
// }
|
// }
|
||||||
// requestsForUser.add(updated)
|
// requestsForUser.add(updated)
|
||||||
// eventFlow.emit(VerificationEvent.RequestUpdated(updated))
|
// dispatchUpdate(VerificationEvent.RequestUpdated(updated))
|
||||||
// }
|
// }
|
||||||
|
|
||||||
private suspend fun dispatchRequestAdded(tx: KotlinVerificationRequest) {
|
private suspend fun dispatchRequestAdded(tx: KotlinVerificationRequest) {
|
||||||
Timber.v("## SAS dispatchRequestAdded txId:${tx.requestId}")
|
Timber.v("## SAS dispatchRequestAdded txId:${tx.requestId}")
|
||||||
eventFlow.emit(VerificationEvent.RequestAdded(tx.toPendingVerificationRequest()))
|
dispatchUpdate(VerificationEvent.RequestAdded(tx.toPendingVerificationRequest()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utilities
|
// Utilities
|
||||||
|
@ -1655,77 +1673,77 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
|
// private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
|
||||||
return Event(
|
// return Event(
|
||||||
roomId = roomId,
|
// roomId = roomId,
|
||||||
originServerTs = clock.epochMillis(),
|
// originServerTs = clock.epochMillis(),
|
||||||
senderId = myUserId,
|
// senderId = myUserId,
|
||||||
eventId = localId,
|
// eventId = localId,
|
||||||
type = type,
|
// type = type,
|
||||||
content = content,
|
// content = content,
|
||||||
unsignedData = UnsignedData(age = null, transactionId = localId)
|
// unsignedData = UnsignedData(age = null, transactionId = localId)
|
||||||
).also {
|
// ).also {
|
||||||
localEchoEventFactory.createLocalEcho(it)
|
// localEchoEventFactory.createLocalEcho(it)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
private suspend fun sendEventInRoom(event: Event): String {
|
// private suspend fun sendEventInRoom(event: Event): String {
|
||||||
return sendVerificationMessageTask.execute(SendVerificationMessageTask.Params(event, 5)).eventId
|
// return sendVerificationMessageTask.execute(SendVerificationMessageTask.Params(event, 5)).eventId
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
private suspend fun sendToDeviceEvent(messageType: String, toSendToDeviceObject: SendToDeviceObject, otherUserId: String, targetDevices: List<String>) {
|
// private suspend fun sendToDeviceEvent(messageType: String, toSendToDeviceObject: SendToDeviceObject, otherUserId: String, targetDevices: List<String>) {
|
||||||
// TODO currently to device verification messages are sent unencrypted
|
// // TODO currently to device verification messages are sent unencrypted
|
||||||
// as per spec not recommended
|
// // as per spec not recommended
|
||||||
// > verification messages may be sent unencrypted, though this is not encouraged.
|
// // > verification messages may be sent unencrypted, though this is not encouraged.
|
||||||
|
//
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
// val contentMap = MXUsersDevicesMap<Any>()
|
||||||
|
//
|
||||||
targetDevices.forEach {
|
// targetDevices.forEach {
|
||||||
contentMap.setObject(otherUserId, it, toSendToDeviceObject)
|
// contentMap.setObject(otherUserId, it, toSendToDeviceObject)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
sendToDeviceTask
|
// sendToDeviceTask
|
||||||
.execute(SendToDeviceTask.Params(messageType, contentMap))
|
// .execute(SendToDeviceTask.Params(messageType, contentMap))
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
suspend fun sendToOther(
|
// suspend fun sendToOther(
|
||||||
request: KotlinVerificationRequest,
|
// request: KotlinVerificationRequest,
|
||||||
type: String,
|
// type: String,
|
||||||
verificationInfo: VerificationInfo<*>,
|
// verificationInfo: VerificationInfo<*>,
|
||||||
) {
|
// ) {
|
||||||
val roomId = request.roomId
|
// val roomId = request.roomId
|
||||||
if (roomId != null) {
|
// if (roomId != null) {
|
||||||
val event = createEventAndLocalEcho(
|
// val event = createEventAndLocalEcho(
|
||||||
type = type,
|
// type = type,
|
||||||
roomId = roomId,
|
// roomId = roomId,
|
||||||
content = verificationInfo.toEventContent()!!
|
// content = verificationInfo.toEventContent()!!
|
||||||
)
|
// )
|
||||||
sendEventInRoom(event)
|
// sendEventInRoom(event)
|
||||||
} else {
|
// } else {
|
||||||
sendToDeviceEvent(
|
// sendToDeviceEvent(
|
||||||
type,
|
// type,
|
||||||
verificationInfo.toSendToDeviceObject()!!,
|
// verificationInfo.toSendToDeviceObject()!!,
|
||||||
request.otherUserId,
|
// request.otherUserId,
|
||||||
request.otherDeviceId()?.let { listOf(it) }.orEmpty()
|
// 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
|
||||||
eventFlow.emit(VerificationEvent.RequestUpdated(request.toPendingVerificationRequest()))
|
dispatchUpdate(VerificationEvent.RequestUpdated(request.toPendingVerificationRequest()))
|
||||||
|
|
||||||
// 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)
|
txMap[request.otherUserId]?.remove(request.requestId)
|
||||||
eventFlow.emit(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)
|
txMap[request.otherUserId]?.remove(request.requestId)
|
||||||
eventFlow.emit(VerificationEvent.TransactionUpdated(it))
|
dispatchUpdate(VerificationEvent.TransactionUpdated(it))
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelRequest(request.requestId, request.roomId, request.otherUserId, request.otherDeviceId(), code)
|
cancelRequest(request.requestId, request.roomId, request.otherUserId, request.otherDeviceId(), code)
|
||||||
|
@ -1756,21 +1774,26 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
private suspend fun cancelTransactionToDevice(transactionId: String, otherUserId: String, otherUserDeviceId: String?, code: CancelCode) {
|
private suspend fun cancelTransactionToDevice(transactionId: String, otherUserId: String, otherUserDeviceId: String?, code: CancelCode) {
|
||||||
Timber.d("## SAS canceling transaction $transactionId for reason $code")
|
Timber.d("## SAS canceling transaction $transactionId for reason $code")
|
||||||
val cancelMessage = KeyVerificationCancel.create(transactionId, code)
|
val cancelMessage = KeyVerificationCancel.create(transactionId, code)
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
// val contentMap = MXUsersDevicesMap<Any>()
|
||||||
contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage)
|
// contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage)
|
||||||
sendToDeviceTask
|
transportLayer.sendToDeviceEvent(
|
||||||
.execute(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap))
|
messageType = EventType.KEY_VERIFICATION_CANCEL,
|
||||||
|
toSendToDeviceObject = cancelMessage,
|
||||||
|
otherUserId = otherUserId,
|
||||||
|
targetDevices = otherUserDeviceId?.let { listOf(it) } ?: emptyList()
|
||||||
|
)
|
||||||
|
// sendToDeviceTask
|
||||||
|
// .execute(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap))
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun cancelTransactionInRoom(roomId: String, transactionId: String, code: CancelCode) {
|
private suspend fun cancelTransactionInRoom(roomId: String, transactionId: String, code: CancelCode) {
|
||||||
Timber.d("## SAS canceling transaction $transactionId for reason $code")
|
Timber.d("## SAS canceling transaction $transactionId for reason $code")
|
||||||
val cancelMessage = MessageVerificationCancelContent.create(transactionId, code)
|
val cancelMessage = MessageVerificationCancelContent.create(transactionId, code)
|
||||||
val event = createEventAndLocalEcho(
|
transportLayer.sendInRoom(
|
||||||
type = EventType.KEY_VERIFICATION_CANCEL,
|
type = EventType.KEY_VERIFICATION_CANCEL,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
content = cancelMessage.toEventContent()
|
content = cancelMessage.toEventContent()
|
||||||
)
|
)
|
||||||
sendEventInRoom(event)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hashUsingAgreedHashMethod(hashMethod: String?, toHash: String): String {
|
private fun hashUsingAgreedHashMethod(hashMethod: String?, toHash: String): String {
|
||||||
|
@ -1785,7 +1808,7 @@ internal class VerificationActor @AssistedInject constructor(
|
||||||
private suspend fun addTransaction(tx: VerificationTransaction) {
|
private suspend fun addTransaction(tx: VerificationTransaction) {
|
||||||
val txInnerMap = txMap.getOrPut(tx.otherUserId) { mutableMapOf() }
|
val txInnerMap = txMap.getOrPut(tx.otherUserId) { mutableMapOf() }
|
||||||
txInnerMap[tx.transactionId] = tx
|
txInnerMap[tx.transactionId] = tx
|
||||||
eventFlow.emit(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? {
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* 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.model.MXUsersDevicesMap
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.UnsignedData
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
|
||||||
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
||||||
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class VerificationTransportLayer @Inject constructor(
|
||||||
|
@UserId private val myUserId: String,
|
||||||
|
private val sendVerificationMessageTask: SendVerificationMessageTask,
|
||||||
|
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||||
|
private val sendToDeviceTask: SendToDeviceTask,
|
||||||
|
private val clock: Clock,
|
||||||
|
) {
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun sendInRoom(localId: String = LocalEcho.createLocalEchoId(),
|
||||||
|
type: String,
|
||||||
|
roomId: String,
|
||||||
|
content: Content): String {
|
||||||
|
val event = createEventAndLocalEcho(
|
||||||
|
type = type,
|
||||||
|
roomId = roomId,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
return sendEventInRoom(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun sendEventInRoom(event: Event): String {
|
||||||
|
return sendVerificationMessageTask.execute(SendVerificationMessageTask.Params(event, 5)).eventId
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,7 @@ object CredentialsFixture {
|
||||||
accessToken,
|
accessToken,
|
||||||
refreshToken,
|
refreshToken,
|
||||||
homeServer,
|
homeServer,
|
||||||
deviceId,
|
deviceId ?: "",
|
||||||
discoveryInformation,
|
discoveryInformation,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,231 @@
|
||||||
|
/*
|
||||||
|
* 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 io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.KeyUsage
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo
|
||||||
|
import org.matrix.android.sdk.internal.crypto.MXCryptoAlgorithms
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||||
|
|
||||||
|
enum class StoreMode {
|
||||||
|
Alice,
|
||||||
|
Bob
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class FakeCryptoStoreForVerification(private val mode: StoreMode) {
|
||||||
|
|
||||||
|
val instance = mockk<IMXCryptoStore>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
every { instance.getDeviceId() } answers {
|
||||||
|
when (mode) {
|
||||||
|
StoreMode.Alice -> aliceDevice1Id
|
||||||
|
StoreMode.Bob -> bobDeviceId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// order matters here but can't find any info in doc about that
|
||||||
|
every { instance.getUserDevice(any(), any()) } returns null
|
||||||
|
every { instance.getUserDevice(aliceMxId, aliceDevice1Id) } returns aliceFirstDevice
|
||||||
|
every { instance.getUserDevice(bobDeviceId, bobDeviceId) } returns aBobDevice
|
||||||
|
|
||||||
|
every { instance.getCrossSigningInfo(aliceMxId) } answers {
|
||||||
|
|
||||||
|
when (mode) {
|
||||||
|
StoreMode.Alice -> {
|
||||||
|
MXCrossSigningInfo(
|
||||||
|
aliceMxId,
|
||||||
|
listOf(
|
||||||
|
aliceMSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
|
||||||
|
aliceUSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
|
||||||
|
aliceSSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
|
||||||
|
),
|
||||||
|
wasTrustedOnce = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
StoreMode.Bob -> {
|
||||||
|
MXCrossSigningInfo(
|
||||||
|
aliceMxId,
|
||||||
|
listOf(
|
||||||
|
aliceMSKBase.copy(trustLevel = DeviceTrustLevel(false, false)),
|
||||||
|
aliceUSKBase.copy(trustLevel = DeviceTrustLevel(false, false)),
|
||||||
|
),
|
||||||
|
wasTrustedOnce = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
every { instance.getCrossSigningInfo(bobMxId) } answers {
|
||||||
|
|
||||||
|
when (mode) {
|
||||||
|
StoreMode.Alice -> {
|
||||||
|
MXCrossSigningInfo(
|
||||||
|
bobMxId,
|
||||||
|
listOf(
|
||||||
|
bobMSKBase.copy(trustLevel = DeviceTrustLevel(false, false)),
|
||||||
|
bobUSKBase.copy(trustLevel = DeviceTrustLevel(false, false)),
|
||||||
|
),
|
||||||
|
wasTrustedOnce = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
StoreMode.Bob -> {
|
||||||
|
MXCrossSigningInfo(
|
||||||
|
bobMxId,
|
||||||
|
listOf(
|
||||||
|
bobMSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
|
||||||
|
bobUSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
|
||||||
|
bobSSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
|
||||||
|
),
|
||||||
|
wasTrustedOnce = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
every { instance.getMyCrossSigningInfo() } answers {
|
||||||
|
when (mode) {
|
||||||
|
StoreMode.Alice -> MXCrossSigningInfo(
|
||||||
|
aliceMxId,
|
||||||
|
listOf(
|
||||||
|
aliceMSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
|
||||||
|
aliceUSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
|
||||||
|
aliceSSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
|
||||||
|
),
|
||||||
|
wasTrustedOnce = false
|
||||||
|
)
|
||||||
|
StoreMode.Bob -> MXCrossSigningInfo(
|
||||||
|
bobMxId,
|
||||||
|
listOf(
|
||||||
|
bobMSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
|
||||||
|
bobUSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
|
||||||
|
bobSSKBase.copy(trustLevel = DeviceTrustLevel(true, true)),
|
||||||
|
),
|
||||||
|
wasTrustedOnce = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
val aliceMxId = "alice@example.com"
|
||||||
|
val bobMxId = "bob@example.com"
|
||||||
|
val bobDeviceId = "MKRJDSLYGA"
|
||||||
|
|
||||||
|
private val aliceDevice1Id = "MGDAADVDMG"
|
||||||
|
|
||||||
|
private val aliceMSK = "Ru4ni66dbQ6FZgUoHyyBtmjKecOHMvMSsSBZ2SABtt0"
|
||||||
|
private val aliceSSK = "Rw6MiEn5do57mBWlWUvL6VDZJ7vAfGrTC58UXVyA0eo"
|
||||||
|
private val aliceUSK = "3XpDI8J5T1Wy2NoGePkDiVhqZlVeVPHM83q9sUJuRcc"
|
||||||
|
|
||||||
|
private val bobMSK = "/ZK6paR+wBkKcazPx2xijn/0g+m2KCRqdCUZ6agzaaE"
|
||||||
|
private val bobSSK = "3/u3SRYywxRl2ul9OiRJK5zFeFnGXd0TrkcnVh1Bebk"
|
||||||
|
private val bobUSK = "601KhaiAhDTyFDS87leWc8/LB+EAUjKgjJvPMWNLP08"
|
||||||
|
|
||||||
|
private val aliceFirstDevice = CryptoDeviceInfo(
|
||||||
|
deviceId = aliceDevice1Id,
|
||||||
|
userId = aliceMxId,
|
||||||
|
algorithms = MXCryptoAlgorithms.supportedAlgorithms(),
|
||||||
|
keys = mapOf(
|
||||||
|
"curve25519:$aliceDevice1Id" to "yDa6cWOZ/WGBqm/JMUfTUCdEbAIzKHhuIcdDbnPEhDU",
|
||||||
|
"ed25519:$aliceDevice1Id" to "XTge+TDwfm+WW10IGnaqEyLTSukPPzg3R1J1YvO1SBI",
|
||||||
|
),
|
||||||
|
signatures = mapOf(
|
||||||
|
aliceMxId to mapOf(
|
||||||
|
"ed25519:$aliceDevice1Id"
|
||||||
|
to "bPOAqM40+QSMgeEzUbYbPSZZccDDMUG00lCNdSXCoaS1gKKBGkSEaHO1OcibISIabjLYzmhp9mgtivz32fbABQ",
|
||||||
|
"ed25519:$aliceMSK"
|
||||||
|
to "owzUsQ4Pvn35uEIc5FdVnXVRPzsVYBV8uJRUSqr4y8r5tp0DvrMArtJukKETgYEAivcZMT1lwNihHIN9xh06DA"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
unsigned = UnsignedDeviceInfo(deviceDisplayName = "Element Web"),
|
||||||
|
trustLevel = DeviceTrustLevel(crossSigningVerified = true, locallyVerified = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val aBobDevice = CryptoDeviceInfo(
|
||||||
|
deviceId = bobDeviceId,
|
||||||
|
userId = bobMxId,
|
||||||
|
algorithms = MXCryptoAlgorithms.supportedAlgorithms(),
|
||||||
|
keys = mapOf(
|
||||||
|
"curve25519:$bobDeviceId" to "tWwg63Yfn//61Ylhir6z4QGejvo193E6MVHmURtYVn0",
|
||||||
|
"ed25519:$bobDeviceId" to "pS5NJ1LiVksQFX+p58NlphqMxE705laRVtUtZpYIAfs",
|
||||||
|
),
|
||||||
|
signatures = mapOf(
|
||||||
|
bobMxId to mapOf(
|
||||||
|
"ed25519:$bobDeviceId" to "zAJqsmOSzkx8EWXcrynCsWtbgWZifN7A6DLyEBs+ZPPLnNuPN5Jwzc1Rg+oZWZaRPvOPcSL0cgcxRegSBU0NBA",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
unsigned = UnsignedDeviceInfo(deviceDisplayName = "Element Ios")
|
||||||
|
)
|
||||||
|
|
||||||
|
private val aliceMSKBase = CryptoCrossSigningKey(
|
||||||
|
userId = aliceMxId,
|
||||||
|
usages = listOf(KeyUsage.MASTER.value),
|
||||||
|
keys = mapOf(
|
||||||
|
"ed25519$aliceMSK" to aliceMSK
|
||||||
|
),
|
||||||
|
trustLevel = DeviceTrustLevel(true, true),
|
||||||
|
signatures = emptyMap()
|
||||||
|
)
|
||||||
|
|
||||||
|
private val aliceSSKBase = CryptoCrossSigningKey(
|
||||||
|
userId = aliceMxId,
|
||||||
|
usages = listOf(KeyUsage.SELF_SIGNING.value),
|
||||||
|
keys = mapOf(
|
||||||
|
"ed25519$aliceSSK" to aliceSSK
|
||||||
|
),
|
||||||
|
trustLevel = null,
|
||||||
|
signatures = emptyMap()
|
||||||
|
)
|
||||||
|
|
||||||
|
private val aliceUSKBase = CryptoCrossSigningKey(
|
||||||
|
userId = aliceMxId,
|
||||||
|
usages = listOf(KeyUsage.USER_SIGNING.value),
|
||||||
|
keys = mapOf(
|
||||||
|
"ed25519$aliceUSK" to aliceUSK
|
||||||
|
),
|
||||||
|
trustLevel = null,
|
||||||
|
signatures = emptyMap()
|
||||||
|
)
|
||||||
|
|
||||||
|
val bobMSKBase = aliceMSKBase.copy(
|
||||||
|
userId = bobMxId,
|
||||||
|
keys = mapOf(
|
||||||
|
"ed25519$bobMSK" to bobMSK
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val bobUSKBase = aliceMSKBase.copy(
|
||||||
|
userId = bobMxId,
|
||||||
|
keys = mapOf(
|
||||||
|
"ed25519$bobUSK" to bobUSK
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val bobSSKBase = aliceMSKBase.copy(
|
||||||
|
userId = bobMxId,
|
||||||
|
keys = mapOf(
|
||||||
|
"ed25519$bobSSK" to bobSSK
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,293 @@
|
||||||
|
/*
|
||||||
|
* 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.org.matrix.android.sdk.internal.crypto.verification
|
||||||
|
|
||||||
|
import android.util.Base64
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkStatic
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.channels.SendChannel
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.IVerificationRequest
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoReady
|
||||||
|
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.events.model.Content
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationReadyContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
|
||||||
|
import org.matrix.android.sdk.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.crypto.verification.FakeCryptoStoreForVerification
|
||||||
|
import org.matrix.android.sdk.internal.crypto.verification.StoreMode
|
||||||
|
import org.matrix.android.sdk.internal.crypto.verification.VerificationActor
|
||||||
|
import org.matrix.android.sdk.internal.crypto.verification.VerificationInfo
|
||||||
|
import org.matrix.android.sdk.internal.crypto.verification.VerificationIntent
|
||||||
|
import org.matrix.android.sdk.internal.crypto.verification.VerificationTransportLayer
|
||||||
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class VerificationActorTest {
|
||||||
|
|
||||||
|
val transportScope = CoroutineScope(SupervisorJob())
|
||||||
|
val actorAScope = CoroutineScope(SupervisorJob())
|
||||||
|
val actorBScope = CoroutineScope(SupervisorJob())
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
// QR code needs that
|
||||||
|
mockkStatic(Base64::class)
|
||||||
|
every {
|
||||||
|
Base64.encodeToString(any(), any())
|
||||||
|
} answers {
|
||||||
|
val array = firstArg<ByteArray>()
|
||||||
|
java.util.Base64.getEncoder().encodeToString(array)
|
||||||
|
}
|
||||||
|
|
||||||
|
every {
|
||||||
|
Base64.decode(any<String>(), any())
|
||||||
|
} answers {
|
||||||
|
val array = firstArg<String>()
|
||||||
|
java.util.Base64.getDecoder().decode(array)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Request and accept`() = runTest {
|
||||||
|
var bobChannel: SendChannel<VerificationIntent>? = null
|
||||||
|
var aliceChannel: SendChannel<VerificationIntent>? = null
|
||||||
|
|
||||||
|
val aliceTransportLayer = mockTransportTo(FakeCryptoStoreForVerification.aliceMxId) { bobChannel }
|
||||||
|
val bobTransportLayer = mockTransportTo(FakeCryptoStoreForVerification.bobMxId) { aliceChannel }
|
||||||
|
|
||||||
|
val aliceActor = fakeActor(
|
||||||
|
actorAScope,
|
||||||
|
FakeCryptoStoreForVerification.aliceMxId,
|
||||||
|
FakeCryptoStoreForVerification(StoreMode.Alice).instance,
|
||||||
|
aliceTransportLayer,
|
||||||
|
mockk<dagger.Lazy<CrossSigningService>> {
|
||||||
|
every {
|
||||||
|
get()
|
||||||
|
} returns mockk<CrossSigningService>(relaxed = true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
aliceChannel = aliceActor.channel
|
||||||
|
|
||||||
|
val bobActor = fakeActor(
|
||||||
|
actorBScope,
|
||||||
|
FakeCryptoStoreForVerification.aliceMxId,
|
||||||
|
FakeCryptoStoreForVerification(StoreMode.Alice).instance,
|
||||||
|
bobTransportLayer,
|
||||||
|
mockk<dagger.Lazy<CrossSigningService>> {
|
||||||
|
every {
|
||||||
|
get()
|
||||||
|
} returns mockk<CrossSigningService>(relaxed = true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
bobChannel = bobActor.channel
|
||||||
|
|
||||||
|
val completableDeferred = CompletableDeferred<PendingVerificationRequest>()
|
||||||
|
|
||||||
|
transportScope.launch {
|
||||||
|
bobActor.eventFlow.collect {
|
||||||
|
if (it is VerificationEvent.RequestAdded) {
|
||||||
|
completableDeferred.complete(it.request)
|
||||||
|
return@collect cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
awaitDeferrable<PendingVerificationRequest> {
|
||||||
|
aliceActor.send(
|
||||||
|
VerificationIntent.ActionRequestVerification(
|
||||||
|
otherUserId = FakeCryptoStoreForVerification.bobMxId,
|
||||||
|
roomId = "aRoom",
|
||||||
|
methods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SHOW, VerificationMethod.QR_CODE_SCAN),
|
||||||
|
deferred = it
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val bobIncomingRequest = completableDeferred.await()
|
||||||
|
bobIncomingRequest.state shouldBeEqualTo EVerificationState.Requested
|
||||||
|
|
||||||
|
val aliceReadied = CompletableDeferred<PendingVerificationRequest>()
|
||||||
|
|
||||||
|
val theJob = transportScope.launch {
|
||||||
|
aliceActor.eventFlow.collect {
|
||||||
|
if (it is VerificationEvent.RequestUpdated && it.request.state == EVerificationState.Ready) {
|
||||||
|
aliceReadied.complete(it.request)
|
||||||
|
return@collect cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// test ready
|
||||||
|
awaitDeferrable<PendingVerificationRequest?> {
|
||||||
|
bobActor.send(
|
||||||
|
VerificationIntent.ActionReadyRequest(
|
||||||
|
bobIncomingRequest.transactionId,
|
||||||
|
methods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SHOW, VerificationMethod.QR_CODE_SCAN),
|
||||||
|
it
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val readiedAliceSide = aliceReadied.await()
|
||||||
|
|
||||||
|
println("transporte scope active? ${transportScope.isActive}")
|
||||||
|
println("the job? ${theJob.isActive}")
|
||||||
|
|
||||||
|
readiedAliceSide.isSasSupported shouldBeEqualTo true
|
||||||
|
readiedAliceSide.otherCanScanQrCode shouldBeEqualTo true
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun <T> awaitDeferrable(block: suspend ((CompletableDeferred<T>) -> Unit)): T {
|
||||||
|
val deferred = CompletableDeferred<T>()
|
||||||
|
block.invoke(deferred)
|
||||||
|
return deferred.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mockTransportTo(fromUser: String, otherChannel: (() -> SendChannel<VerificationIntent>?)): VerificationTransportLayer {
|
||||||
|
return mockk<VerificationTransportLayer> {
|
||||||
|
coEvery { sendToOther(any(), any(), any()) } answers {
|
||||||
|
val request = firstArg<IVerificationRequest>()
|
||||||
|
val type = secondArg<String>()
|
||||||
|
val info = thirdArg<VerificationInfo<*>>()
|
||||||
|
|
||||||
|
transportScope.launch(Dispatchers.IO) {
|
||||||
|
when (type) {
|
||||||
|
EventType.KEY_VERIFICATION_READY -> {
|
||||||
|
val readyContent = info.asValidObject()
|
||||||
|
otherChannel()?.send(
|
||||||
|
VerificationIntent.OnReadyReceived(
|
||||||
|
transactionId = request.requestId(),
|
||||||
|
fromUser = fromUser,
|
||||||
|
viaRoom = request.roomId(),
|
||||||
|
readyInfo = readyContent as ValidVerificationInfoReady,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
coEvery { sendInRoom(any(), any(), any(), any()) } answers {
|
||||||
|
val type = secondArg<String>()
|
||||||
|
val roomId = thirdArg<String>()
|
||||||
|
val content = arg<Content>(3)
|
||||||
|
|
||||||
|
val fakeEventId = UUID.randomUUID().toString()
|
||||||
|
transportScope.launch(Dispatchers.IO) {
|
||||||
|
when (type) {
|
||||||
|
EventType.MESSAGE -> {
|
||||||
|
val requestContent = content.toModel<MessageVerificationRequestContent>()?.copy(
|
||||||
|
transactionId = fakeEventId
|
||||||
|
)?.asValidObject()
|
||||||
|
otherChannel()?.send(
|
||||||
|
VerificationIntent.OnVerificationRequestReceived(
|
||||||
|
requestContent!!,
|
||||||
|
senderId = FakeCryptoStoreForVerification.aliceMxId,
|
||||||
|
roomId = roomId,
|
||||||
|
timeStamp = 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
EventType.KEY_VERIFICATION_READY -> {
|
||||||
|
val readyContent = content.toModel<MessageVerificationReadyContent>()
|
||||||
|
?.asValidObject()
|
||||||
|
otherChannel()?.send(
|
||||||
|
VerificationIntent.OnReadyReceived(
|
||||||
|
transactionId = readyContent!!.transactionId,
|
||||||
|
fromUser = fromUser,
|
||||||
|
viaRoom = roomId,
|
||||||
|
readyInfo = readyContent,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fakeEventId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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")}")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fakeActor(
|
||||||
|
scope: CoroutineScope,
|
||||||
|
userId: String,
|
||||||
|
cryptoStore: IMXCryptoStore,
|
||||||
|
transportLayer: VerificationTransportLayer,
|
||||||
|
crossSigningService: dagger.Lazy<CrossSigningService>,
|
||||||
|
): VerificationActor {
|
||||||
|
return VerificationActor(
|
||||||
|
scope,
|
||||||
|
// channel = channel,
|
||||||
|
clock = mockk<Clock> {
|
||||||
|
every { epochMillis() } returns System.currentTimeMillis()
|
||||||
|
},
|
||||||
|
myUserId = userId,
|
||||||
|
cryptoStore = cryptoStore,
|
||||||
|
secretShareManager = mockk<SecretShareManager> {},
|
||||||
|
transportLayer = transportLayer,
|
||||||
|
crossSigningService = crossSigningService,
|
||||||
|
setDeviceVerificationAction = SetDeviceVerificationAction(
|
||||||
|
cryptoStore = cryptoStore,
|
||||||
|
userId = userId,
|
||||||
|
defaultKeysBackupService = mockk {
|
||||||
|
coEvery { checkAndStartKeysBackup() } coAnswers { }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,17 +16,17 @@
|
||||||
|
|
||||||
package im.vector.app.test.fakes
|
package im.vector.app.test.fakes
|
||||||
|
|
||||||
import io.mockk.every
|
import io.mockk.coEvery
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
||||||
|
|
||||||
class FakeCrossSigningService : CrossSigningService by mockk() {
|
class FakeCrossSigningService : CrossSigningService by mockk() {
|
||||||
|
|
||||||
fun givenIsCrossSigningInitializedReturns(isInitialized: Boolean) {
|
fun givenIsCrossSigningInitializedReturns(isInitialized: Boolean) {
|
||||||
every { isCrossSigningInitialized() } returns isInitialized
|
coEvery { isCrossSigningInitialized() } returns isInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
fun givenIsCrossSigningVerifiedReturns(isVerified: Boolean) {
|
fun givenIsCrossSigningVerifiedReturns(isVerified: Boolean) {
|
||||||
every { isCrossSigningVerified() } returns isVerified
|
coEvery { isCrossSigningVerified() } returns isVerified
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.app.test.fakes
|
package im.vector.app.test.fakes
|
||||||
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import io.mockk.coEvery
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.slot
|
import io.mockk.slot
|
||||||
|
@ -55,22 +56,19 @@ class FakeCryptoService(
|
||||||
override fun getMyDevicesInfoLive(deviceId: String) = myDevicesInfoWithIdLiveData
|
override fun getMyDevicesInfoLive(deviceId: String) = myDevicesInfoWithIdLiveData
|
||||||
|
|
||||||
fun givenSetDeviceNameSucceeds() {
|
fun givenSetDeviceNameSucceeds() {
|
||||||
val matrixCallback = slot<MatrixCallback<Unit>>()
|
coEvery { setDeviceName(any(), any()) } answers {
|
||||||
every { setDeviceName(any(), any(), capture(matrixCallback)) } answers {
|
Unit
|
||||||
thirdArg<MatrixCallback<Unit>>().onSuccess(Unit)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun givenSetDeviceNameFailsWithError(error: Exception) {
|
fun givenSetDeviceNameFailsWithError(error: Exception) {
|
||||||
val matrixCallback = slot<MatrixCallback<Unit>>()
|
coEvery { setDeviceName(any(), any()) } answers {
|
||||||
every { setDeviceName(any(), any(), capture(matrixCallback)) } answers {
|
throw error
|
||||||
thirdArg<MatrixCallback<Unit>>().onFailure(error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun givenDeleteDeviceSucceeds(deviceId: String) {
|
fun givenDeleteDeviceSucceeds(deviceId: String) {
|
||||||
val matrixCallback = slot<MatrixCallback<Unit>>()
|
coEvery { deleteDevice(deviceId, any()) } answers {
|
||||||
every { deleteDevice(deviceId, any(), capture(matrixCallback)) } answers {
|
|
||||||
thirdArg<MatrixCallback<Unit>>().onSuccess(Unit)
|
thirdArg<MatrixCallback<Unit>>().onSuccess(Unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue