From 2c1dc053edc2e07c9fa32b465b7d46c07535f93b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 30 Jun 2021 15:48:24 +0200 Subject: [PATCH] crypto: Support answering in-room verifications --- .../internal/crypto/DefaultCryptoService.kt | 44 ++- .../android/sdk/internal/crypto/OlmMachine.kt | 2 +- .../verification/RustVerificationService.kt | 300 ++++++++---------- rust-sdk/Cargo.toml | 10 +- rust-sdk/src/machine.rs | 2 +- rust-sdk/src/olm.udl | 1 + rust-sdk/src/responses.rs | 24 +- 7 files changed, 201 insertions(+), 182 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 5764f078f7..a8d382df25 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -20,6 +20,7 @@ import android.content.Context import androidx.annotation.VisibleForTesting import androidx.lifecycle.LiveData import androidx.paging.PagedList +import dagger.Lazy import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async @@ -44,6 +45,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic 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.LocalEcho import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility @@ -68,11 +70,13 @@ import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask +import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask 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.tasks.SetDeviceNameTask import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask import org.matrix.android.sdk.internal.crypto.verification.RustVerificationService @@ -101,16 +105,34 @@ import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject import kotlin.math.max -internal class RequestSender( +internal class RequestSender @Inject constructor( private val sendToDeviceTask: SendToDeviceTask, -) { + private val sendVerificationMessageTask: Lazy, + ) { + suspend fun sendVerificationRequest(request: OutgoingVerificationRequest) { when (request) { - is OutgoingVerificationRequest.InRoom -> TODO() + is OutgoingVerificationRequest.InRoom -> sendRoomMessage(request) is OutgoingVerificationRequest.ToDevice -> sendToDevice(request) } } + suspend fun sendRoomMessage(request: OutgoingVerificationRequest.InRoom) { + sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId) + } + + suspend fun sendRoomMessage(request: Request.RoomMessage) { + sendRoomMessage(request.eventType, request.roomId, request.content, request.requestId) + } + + suspend fun sendRoomMessage(eventType: String, roomId: String, content: String, transactionId: String) { + val adapter = MoshiProvider.providesMoshi().adapter(Map::class.java) + val jsonContent = adapter.fromJson(content) + val event = Event(eventType, transactionId, jsonContent, roomId = roomId) + val params = SendVerificationMessageTask.Params(event) + this.sendVerificationMessageTask.get().execute(params) + } + suspend fun sendToDevice(request: Request.ToDevice) { sendToDevice(request.eventType, request.body) } @@ -176,11 +198,11 @@ internal class DefaultCryptoService @Inject constructor( private val coroutineDispatchers: MatrixCoroutineDispatchers, private val taskExecutor: TaskExecutor, private val cryptoCoroutineScope: CoroutineScope, + private val sender: RequestSender, ) : CryptoService { private val isStarting = AtomicBoolean(false) private val isStarted = AtomicBoolean(false) - private val sender = RequestSender(this.sendToDeviceTask) private var olmMachine: OlmMachine? = null // The verification service. @@ -348,8 +370,7 @@ internal class DefaultCryptoService @Inject constructor( setRustLogger() val machine = OlmMachine(userId, deviceId!!, dataDir, deviceObserver) this.olmMachine = machine - this.verificationService = - RustVerificationService(machine, this.sender) + this.verificationService = RustVerificationService(machine, this.sender) Timber.v( "## CRYPTO | Successfully started up an Olm machine for " + "${userId}, ${deviceId}, identity keys: ${this.olmMachine?.identityKeys()}") @@ -859,8 +880,15 @@ internal class DefaultCryptoService @Inject constructor( sendToDevice(it) } } - else -> { - async {} + is Request.KeysClaim -> { + async { + claimKeys(it) + } + } + is Request.RoomMessage -> { + async { + sender.sendRoomMessage(it) + } } } }.joinAll() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt index ddd7cc4134..2b9de6004e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt @@ -228,7 +228,7 @@ internal class QrCodeVerification( runBlocking { when (request) { is OutgoingVerificationRequest.ToDevice -> { - sender.sendToDevice(request.eventType, request.body) + sender.sendToDevice(request) } is OutgoingVerificationRequest.InRoom -> TODO() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/RustVerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/RustVerificationService.kt index 4ba58ab9fc..673f91c176 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/RustVerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/RustVerificationService.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright 2021 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. @@ -18,7 +18,8 @@ package org.matrix.android.sdk.internal.crypto.verification import android.os.Handler import android.os.Looper -import javax.inject.Inject +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass import kotlinx.coroutines.runBlocking import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod @@ -27,30 +28,38 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransa 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.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.internal.crypto.OlmMachine import org.matrix.android.sdk.internal.crypto.QrCodeVerification import org.matrix.android.sdk.internal.crypto.RequestSender import org.matrix.android.sdk.internal.crypto.SasVerification import org.matrix.android.sdk.internal.crypto.VerificationRequest -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.KeyVerificationKey -import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationMac -import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationRequest -import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart import org.matrix.android.sdk.internal.session.SessionScope import timber.log.Timber import uniffi.olm.Verification +private fun getFlowId(event: Event): String? { + @JsonClass(generateAdapter = true) + data class ToDeviceVerificationEvent( + @Json(name = "sender") val sender: String?, + @Json(name = "transaction_id") val transactionId: String, + ) + + return if (event.eventId != null) { + val relatesTo = event.content.toModel()?.relatesTo + relatesTo?.eventId + } else { + val content = event.getClearContent().toModel() ?: return null + content.transactionId + } +} + @SessionScope -internal class RustVerificationService -@Inject -constructor( +internal class RustVerificationService( private val olmMachine: OlmMachine, private val requestSender: RequestSender, ) : DefaultVerificationTransaction.Listener, VerificationService { - private val uiHandler = Handler(Looper.getMainLooper()) private var listeners = ArrayList() @@ -103,43 +112,31 @@ constructor( } } - override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) { - // TODO this doesn't seem to be used anymore? - runBlocking { olmMachine.markDeviceAsTrusted(userId, deviceID) } - } - suspend fun onEvent(event: Event) = when (event.getClearType()) { - // TODO most of those methods do the same, we just need to get the - // flow id and the sender from the event, can we add a generic method for this? - EventType.KEY_VERIFICATION_START -> onStart(event) - EventType.KEY_VERIFICATION_CANCEL -> onCancel(event) - EventType.KEY_VERIFICATION_ACCEPT -> {} - EventType.KEY_VERIFICATION_KEY -> onKey(event) - EventType.KEY_VERIFICATION_MAC -> onMac(event) - EventType.KEY_VERIFICATION_READY -> {} - EventType.KEY_VERIFICATION_DONE -> onDone(event) MessageType.MSGTYPE_VERIFICATION_REQUEST -> onRequest(event) - else -> {} + EventType.KEY_VERIFICATION_START -> onStart(event) + EventType.KEY_VERIFICATION_READY, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_KEY, + EventType.KEY_VERIFICATION_MAC, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_DONE -> onUpdate(event) + else -> { + } } - private fun getAndDispatch(sender: String, flowId: String) { + private fun onUpdate(event: Event) { + val sender = event.senderId ?: return + val flowId = getFlowId(event) ?: return + + this.getVerificationRequest(sender, flowId)?.dispatchRequestUpdated() val verification = this.getExistingTransaction(sender, flowId) ?: return dispatchTxUpdated(verification) } - private fun onCancel(event: Event) { - val content = event.getClearContent().toModel() ?: return - val flowId = content.transactionId ?: return - val sender = event.senderId ?: return - - this.getVerificationRequest(sender, flowId)?.dispatchRequestUpdated() - getAndDispatch(sender, flowId) - } - private suspend fun onStart(event: Event) { - val content = event.getClearContent().toModel() ?: return - val flowId = content.transactionId ?: return val sender = event.senderId ?: return + val flowId = getFlowId(event) ?: return val verification = this.getExistingTransaction(sender, flowId) ?: return val request = this.getVerificationRequest(sender, flowId) @@ -150,12 +147,12 @@ constructor( // accepted the request, otherwise it's a QR code verification, just dispatch an update. if (verification is SasVerification) { // Accept dispatches an update, no need to do it twice. + Timber.d("## Verification: Auto accepting SAS verification with $sender") verification.accept() } else { dispatchTxUpdated(verification) } } else { - Timber.d("HELLOOOOO DISPATCHING NEW VERIFICATIONO $verification") // This didn't originate from a request, so tell our listeners that // this is a new verification. dispatchTxAdded(verification) @@ -163,37 +160,10 @@ constructor( // so let's trigger an update after the addition as well. dispatchTxUpdated(verification) } - - - } - - private fun onDone(event: Event) { - val content = event.getClearContent().toModel() ?: return - val flowId = content.transactionId ?: return - val sender = event.senderId ?: return - - getAndDispatch(sender, flowId) - } - - private fun onKey(event: Event) { - val content = event.getClearContent().toModel() ?: return - val flowId = content.transactionId ?: return - val sender = event.senderId ?: return - - getAndDispatch(sender, flowId) - } - - private fun onMac(event: Event) { - val content = event.getClearContent().toModel() ?: return - val flowId = content.transactionId ?: return - val sender = event.senderId ?: return - - getAndDispatch(sender, flowId) } private fun onRequest(event: Event) { - val content = event.getClearContent().toModel() ?: return - val flowId = content.transactionId + val flowId = getFlowId(event) ?: return val sender = event.senderId ?: return val request = this.getExistingVerificationRequest(sender, flowId) ?: return @@ -201,6 +171,26 @@ constructor( dispatchRequestAdded(request) } + private fun getVerificationRequest(otherUserId: String, transactionId: String): VerificationRequest? { + val request = this.olmMachine.getVerificationRequest(otherUserId, transactionId) + + return if (request != null) { + VerificationRequest( + this.olmMachine.inner(), + request, + requestSender, + listeners, + ) + } else { + null + } + } + + override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) { + // TODO this doesn't seem to be used anymore? + runBlocking { olmMachine.markDeviceAsTrusted(userId, deviceID) } + } + override fun onPotentiallyInterestingEventRoomFailToDecrypt(event: Event) { // TODO This should be handled inside the rust-sdk decryption method } @@ -219,8 +209,13 @@ constructor( override fun getExistingVerificationRequests( otherUserId: String ): List { - return this.getVerificationRequests(otherUserId).map { - it.toPendingVerificationRequest() + return this.olmMachine.getVerificationRequests(otherUserId).map { + VerificationRequest( + this.olmMachine.inner(), + it, + this.requestSender, + this.listeners, + ).toPendingVerificationRequest() } } @@ -239,9 +234,67 @@ constructor( roomId: String, tid: String? ): PendingVerificationRequest? { + // This is only used in `RoomDetailViewModel` to resume the verification. TODO() } + override fun requestKeyVerification( + methods: List, + otherUserId: String, + otherDevices: List? + ): PendingVerificationRequest { + // This was mostly a copy paste of the InDMs method, do the same here + TODO() + } + + override fun requestKeyVerificationInDMs( + methods: List, + otherUserId: String, + roomId: String, + localId: String? + ): PendingVerificationRequest { + Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId") + + // TODO cancel other active requests, create a new request here and + // dispatch it + TODO() + } + + override fun readyPendingVerification( + methods: List, + otherUserId: String, + transactionId: String + ): Boolean { + val request = this.getVerificationRequest(otherUserId, transactionId) + + return if (request != null) { + runBlocking { request.acceptWithMethods(methods) } + + if (request.isReady()) { + val qrcode = request.startQrVerification() + + if (qrcode != null) { + dispatchTxAdded(qrcode) + } + + true + } else { + false + } + } else { + false + } + } + + override fun readyPendingVerificationInDMs( + methods: List, + otherUserId: String, + roomId: String, + transactionId: String + ): Boolean { + return readyPendingVerification(methods, otherUserId, transactionId) + } + override fun beginKeyVerification( method: VerificationMethod, otherUserId: String, @@ -277,26 +330,17 @@ constructor( } } - override fun requestKeyVerificationInDMs( - methods: List, - otherUserId: String, + override fun beginKeyVerificationInDMs( + method: VerificationMethod, + transactionId: String, roomId: String, - localId: String? - ): PendingVerificationRequest { - Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId") - - // TODO cancel other active requests, create a new request here and - // dispatch it - TODO() - } - - override fun requestKeyVerification( - methods: List, otherUserId: String, - otherDevices: List? - ): PendingVerificationRequest { - // This was mostly a copy paste of the InDMs method, do the same here - TODO() + otherDeviceId: String + ): String { + beginKeyVerification(method, otherUserId, otherDeviceId, transactionId) + // TODO what's the point of returning the same ID we got as an argument? + // We do this because the old verification service did so + return transactionId } override fun cancelVerificationRequest(request: PendingVerificationRequest) { @@ -309,86 +353,12 @@ constructor( transactionId: String, roomId: String ) { - // TODO get an existing verification request out of the olm machine and - // cancel it. update the pending request afterwards - } - - override fun beginKeyVerificationInDMs( - method: VerificationMethod, - transactionId: String, - roomId: String, - otherUserId: String, - otherDeviceId: String - ): String { - // TODO fetch out the verification request nad start SAS, return the - // flow id - return "" - } - - override fun readyPendingVerificationInDMs( - methods: List, - otherUserId: String, - roomId: String, - transactionId: String - ): Boolean { - Timber.e("## TRYING TO READY PENDING ROOM VERIFICATION") - // TODO do the same as readyPendingVerification - TODO() - } - - private fun getVerificationRequest(otherUserId: String, transactionId: String): VerificationRequest? { - val request = this.olmMachine.getVerificationRequest(otherUserId, transactionId) - - return if (request != null) { - VerificationRequest( - this.olmMachine.inner(), - request, - requestSender, - listeners, - ) - } else { - null - } - } - - private fun getVerificationRequests(userId: String): List { - return this.olmMachine.getVerificationRequests(userId).map { - VerificationRequest( - this.olmMachine.inner(), - it, - this.requestSender, - this.listeners, - ) - } - } - - override fun readyPendingVerification( - methods: List, - otherUserId: String, - transactionId: String - ): Boolean { - val request = this.getVerificationRequest(otherUserId, transactionId) - - return if (request != null) { - runBlocking { request.acceptWithMethods(methods) } - - if (request.isReady()) { - val qrcode = request.startQrVerification() - - if (qrcode != null) { - dispatchTxAdded(qrcode) - } - - true - } else { - false - } - } else { - false - } + val verificationRequest = this.getVerificationRequest(otherUserId, transactionId) + runBlocking { verificationRequest?.cancel() } } override fun transactionUpdated(tx: VerificationTransaction) { + // TODO this isn't really used anymore dispatchTxUpdated(tx) } } diff --git a/rust-sdk/Cargo.toml b/rust-sdk/Cargo.toml index 963eec3424..1266b97814 100644 --- a/rust-sdk/Cargo.toml +++ b/rust-sdk/Cargo.toml @@ -24,12 +24,14 @@ version = "0.2.1" features = ["lax_deserialize"] [dependencies.matrix-sdk-common] -git = "https://github.com/matrix-org/matrix-rust-sdk/" -rev = "d2e4b3f3bbcdc139560cdacbdf62dedab6f156b9" +path = "/home/poljar/werk/matrix/nio-rust/matrix_sdk_common" +# git = "https://github.com/matrix-org/matrix-rust-sdk/" +# rev = "d2e4b3f3bbcdc139560cdacbdf62dedab6f156b9" [dependencies.matrix-sdk-crypto] -git = "https://github.com/matrix-org/matrix-rust-sdk/" -rev = "d2e4b3f3bbcdc139560cdacbdf62dedab6f156b9" +path = "/home/poljar/werk/matrix/nio-rust/matrix_sdk_crypto" +# git = "https://github.com/matrix-org/matrix-rust-sdk/" +# rev = "d2e4b3f3bbcdc139560cdacbdf62dedab6f156b9" features = ["sled_cryptostore"] [dependencies.tokio] diff --git a/rust-sdk/src/machine.rs b/rust-sdk/src/machine.rs index 7deb409c45..44dd783e4d 100644 --- a/rust-sdk/src/machine.rs +++ b/rust-sdk/src/machine.rs @@ -718,7 +718,7 @@ impl OlmMachine { pub fn request_verification(&self, user_id: &str) { let _user_id = UserId::try_from(user_id).unwrap(); - todo!() + todo!("Requesting key verification isn't yet supported") } pub fn get_verification(&self, user_id: &str, flow_id: &str) -> Option { diff --git a/rust-sdk/src/olm.udl b/rust-sdk/src/olm.udl index d3297b84c5..452a02279d 100644 --- a/rust-sdk/src/olm.udl +++ b/rust-sdk/src/olm.udl @@ -140,6 +140,7 @@ interface Request { KeysUpload(string request_id, string body); KeysQuery(string request_id, sequence users); KeysClaim(string request_id, record> one_time_keys); + RoomMessage(string request_id, string room_id, string event_type, string content); }; enum RequestType { diff --git a/rust-sdk/src/responses.rs b/rust-sdk/src/responses.rs index 4cecd4981b..f1204900db 100644 --- a/rust-sdk/src/responses.rs +++ b/rust-sdk/src/responses.rs @@ -19,7 +19,7 @@ use ruma::{ use matrix_sdk_crypto::{ IncomingResponse, OutgoingRequest, OutgoingVerificationRequest as SdkVerificationRequest, - ToDeviceRequest, + ToDeviceRequest, RoomMessageRequest, }; pub enum OutgoingVerificationRequest { @@ -73,6 +73,12 @@ pub enum Request { request_id: String, one_time_keys: HashMap>, }, + RoomMessage { + request_id: String, + room_id: String, + event_type: String, + content: String, + }, } impl From for Request { @@ -100,8 +106,8 @@ impl From for Request { } } ToDeviceRequest(t) => Request::from(t), - SignatureUpload(_) => todo!(), - RoomMessage(_) => todo!(), + SignatureUpload(_) => todo!("Uploading signatures isn't yet supported"), + RoomMessage(r) => Request::from(r), } } } @@ -126,6 +132,18 @@ impl From<&ToDeviceRequest> for Request { } } +impl From<&RoomMessageRequest> for Request { + fn from(r: &RoomMessageRequest) -> Self { + Self::RoomMessage { + request_id: r.txn_id.to_string(), + room_id: r.room_id.to_string(), + event_type: r.content.event_type().to_string(), + content: serde_json::to_string(&r.content) + .expect("Can't serialize message content"), + } + } +} + pub(crate) fn response_from_string(body: &str) -> Response> { Response::builder() .status(200)