crypto: Support answering in-room verifications

This commit is contained in:
Damir Jelić 2021-06-30 15:48:24 +02:00
parent bcfb121215
commit 2c1dc053ed
7 changed files with 201 additions and 182 deletions

View file

@ -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<DefaultSendVerificationMessageTask>,
) {
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<Content>(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()

View file

@ -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()
}

View file

@ -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<MessageRelationContent>()?.relatesTo
relatesTo?.eventId
} else {
val content = event.getClearContent().toModel<ToDeviceVerificationEvent>() ?: 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<VerificationService.Listener>()
@ -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<KeyVerificationCancel>() ?: 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<KeyVerificationStart>() ?: 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<KeyVerificationDone>() ?: return
val flowId = content.transactionId ?: return
val sender = event.senderId ?: return
getAndDispatch(sender, flowId)
}
private fun onKey(event: Event) {
val content = event.getClearContent().toModel<KeyVerificationKey>() ?: return
val flowId = content.transactionId ?: return
val sender = event.senderId ?: return
getAndDispatch(sender, flowId)
}
private fun onMac(event: Event) {
val content = event.getClearContent().toModel<KeyVerificationMac>() ?: return
val flowId = content.transactionId ?: return
val sender = event.senderId ?: return
getAndDispatch(sender, flowId)
}
private fun onRequest(event: Event) {
val content = event.getClearContent().toModel<KeyVerificationRequest>() ?: 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<PendingVerificationRequest> {
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<VerificationMethod>,
otherUserId: String,
otherDevices: List<String>?
): PendingVerificationRequest {
// This was mostly a copy paste of the InDMs method, do the same here
TODO()
}
override fun requestKeyVerificationInDMs(
methods: List<VerificationMethod>,
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<VerificationMethod>,
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<VerificationMethod>,
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<VerificationMethod>,
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<VerificationMethod>,
otherUserId: String,
otherDevices: List<String>?
): 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<VerificationMethod>,
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<VerificationRequest> {
return this.olmMachine.getVerificationRequests(userId).map {
VerificationRequest(
this.olmMachine.inner(),
it,
this.requestSender,
this.listeners,
)
}
}
override fun readyPendingVerification(
methods: List<VerificationMethod>,
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)
}
}

View file

@ -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]

View file

@ -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<Verification> {

View file

@ -140,6 +140,7 @@ interface Request {
KeysUpload(string request_id, string body);
KeysQuery(string request_id, sequence<string> users);
KeysClaim(string request_id, record<DOMString, record<DOMString, string>> one_time_keys);
RoomMessage(string request_id, string room_id, string event_type, string content);
};
enum RequestType {

View file

@ -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<String, HashMap<String, String>>,
},
RoomMessage {
request_id: String,
room_id: String,
event_type: String,
content: String,
},
}
impl From<OutgoingRequest> for Request {
@ -100,8 +106,8 @@ impl From<OutgoingRequest> 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<Vec<u8>> {
Response::builder()
.status(200)