From 7650e43362a36899fedf03759ff8eacacef36c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Sat, 10 Jul 2021 20:48:57 +0200 Subject: [PATCH] crypto: Add support to scan QR codes during verification --- .../android/sdk/internal/crypto/OlmMachine.kt | 59 +++++++++++-------- .../internal/crypto/VerificationRequest.kt | 39 ++++++++++++ .../verification/RustVerificationService.kt | 20 +++++-- rust-sdk/Cargo.toml | 4 +- rust-sdk/src/lib.rs | 2 +- rust-sdk/src/machine.rs | 39 +++++++++++- rust-sdk/src/olm.udl | 8 +++ 7 files changed, 137 insertions(+), 34 deletions(-) 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 3d555cc8e1..77ea896b26 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 @@ -160,7 +160,8 @@ internal class Device( internal class QrCodeVerification( private val machine: uniffi.olm.OlmMachine, - private var inner: QrCode, + private var request: org.matrix.android.sdk.internal.crypto.VerificationRequest, + private var inner: QrCode?, private val sender: RequestSender, private val listeners: ArrayList, ) : QrCodeVerificationTransaction { @@ -180,14 +181,17 @@ internal class QrCodeVerification( override val qrCodeText: String? get() { - val data = this.machine.generateQrCode(this.inner.otherUserId, this.inner.flowId) + val data = this.inner?.let { this.machine.generateQrCode(it.otherUserId, it.flowId) } // TODO Why are we encoding this to ISO_8859_1? If we're going to encode, why not base64? return data?.fromBase64()?.toString(Charsets.ISO_8859_1) } override fun userHasScannedOtherQrCode(otherQrCodeText: String) { - TODO("Not yet implemented") + runBlocking { + request.scanQrCode(otherQrCodeText) + } + dispatchTxUpdated() } override fun otherUserScannedMyQrCode() { @@ -203,37 +207,43 @@ internal class QrCodeVerification( override var state: VerificationTxState get() { refreshData() + val inner = this.inner - return when { - inner.isDone -> VerificationTxState.Verified - inner.hasBeenConfirmed -> VerificationTxState.WaitingOtherReciprocateConfirm - inner.otherSideScanned -> VerificationTxState.QrScannedByOther - inner.isCancelled -> { - val cancelCode = safeValueOf(inner.cancelCode) - val byMe = inner.cancelledByUs ?: false - VerificationTxState.Cancelled(cancelCode, byMe) - } - else -> { - VerificationTxState.None + return if (inner != null) { + when { + inner.isDone -> VerificationTxState.Verified + inner.reciprocated -> VerificationTxState.Started + inner.hasBeenConfirmed -> VerificationTxState.WaitingOtherReciprocateConfirm + inner.otherSideScanned -> VerificationTxState.QrScannedByOther + inner.isCancelled -> { + val cancelCode = safeValueOf(inner.cancelCode) + val byMe = inner.cancelledByUs ?: false + VerificationTxState.Cancelled(cancelCode, byMe) + } + else -> { + VerificationTxState.None + } } + } else { + VerificationTxState.None } } @Suppress("UNUSED_PARAMETER") set(value) {} override val transactionId: String - get() = this.inner.flowId + get() = this.request.flowId() override val otherUserId: String - get() = this.inner.otherUserId + get() = this.request.otherUser() override var otherDeviceId: String? - get() = this.inner.otherDeviceId + get() = this.request.otherDeviceId() @Suppress("UNUSED_PARAMETER") set(value) {} override val isIncoming: Boolean - get() = !this.inner.weStarted + get() = !this.request.weStarted() override fun cancel() { cancelHelper(CancelCode.User) @@ -244,13 +254,13 @@ internal class QrCodeVerification( } override fun isToDeviceTransport(): Boolean { - return this.inner.roomId == null + return this.request.roomId() == null } @Throws(CryptoStoreErrorException::class) suspend fun confirm(): OutgoingVerificationRequest? = withContext(Dispatchers.IO) { - machine.confirmVerification(inner.otherUserId, inner.flowId) + machine.confirmVerification(request.otherUser(), request.flowId()) } private fun sendRequest(request: OutgoingVerificationRequest) { @@ -268,7 +278,7 @@ internal class QrCodeVerification( } private fun cancelHelper(code: CancelCode) { - val request = this.machine.cancelVerification(this.inner.otherUserId, inner.flowId, code.value) + val request = this.machine.cancelVerification(this.request.otherUser(), this.request.flowId(), code.value) if (request != null) { sendRequest(request) @@ -276,11 +286,12 @@ internal class QrCodeVerification( } private fun refreshData() { - when (val verification = this.machine.getVerification(this.inner.otherUserId, this.inner.flowId)) { - is Verification.QrCodeV1 -> { + when (val verification = this.machine.getVerification(this.request.otherUser(), this.request.flowId())) { + is Verification.QrCodeV1 -> { this.inner = verification.qrcode } - else -> {} + else -> { + } } return diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/VerificationRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/VerificationRequest.kt index 2dd9e6f6fa..458fabf284 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/VerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/VerificationRequest.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto import android.os.Handler import android.os.Looper +import com.sun.jna.Native.toByteArray import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.session.crypto.verification.CancelCode @@ -27,6 +28,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationI import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf +import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE @@ -76,17 +78,49 @@ internal class VerificationRequest( return this.inner.isDone } + fun flowId(): String { + return this.inner.flowId + } + + fun otherUser(): String { + return this.inner.otherUserId + } + + fun otherDeviceId(): String? { + refreshData() + return this.inner.otherDeviceId + } + + fun weStarted(): Boolean { + return this.inner.weStarted + } + + fun roomId(): String? { + return this.inner.roomId + } + fun isReady(): Boolean { refreshData() return this.inner.isReady } + internal suspend fun scanQrCode(data: String): QrCodeVerification? { + // TODO again, what's the deal with ISO_8859_1? + val byteArray = data.toByteArray(Charsets.ISO_8859_1) + val encodedData = byteArray.toBase64NoPadding() + val result = this.machine.scanQrCode(this.otherUser(), this.flowId(), encodedData) ?: return null + + this.sender.sendVerificationRequest(result.request) + return QrCodeVerification(this.machine, this, result.qr, this.sender, this.listeners) + } + internal fun startQrVerification(): QrCodeVerification? { val qrcode = this.machine.startQrVerification(this.inner.otherUserId, this.inner.flowId) return if (qrcode != null) { QrCodeVerification( this.machine, + this, qrcode, this.sender, this.listeners, @@ -125,6 +159,11 @@ internal class VerificationRequest( } } + fun canScanQrCodes(): Boolean { + refreshData() + return this.inner.ourMethods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN) ?: false + } + suspend fun startSasVerification(): SasVerification? { refreshData() 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 dc59845048..ed1363c6e1 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 @@ -219,15 +219,27 @@ internal class RustVerificationService( otherUserId: String, tid: String, ): VerificationTransaction? { - val verification = this.olmMachine.getVerification(otherUserId, tid) ?: return null - - return when (verification) { + return when (val verification = this.olmMachine.getVerification(otherUserId, tid)) { is Verification.QrCodeV1 -> { - QrCodeVerification(this.olmMachine.inner(), verification.qrcode, this.requestSender, this.listeners) + val request = getVerificationRequest(otherUserId, tid) ?: return null + QrCodeVerification(this.olmMachine.inner(), request, verification.qrcode, this.requestSender, this.listeners) } is Verification.SasV1 -> { SasVerification(this.olmMachine.inner(), verification.sas, this.requestSender, this.listeners) } + null -> { + // This branch exists because scanning a QR code is tied to the QrCodeVerification, + // i.e. instead of branching into a scanned QR code verification from the verification request, + // like it's done for SAS verifications, the public API expects us to create an empty dummy + // QrCodeVerification object that gets populated once a QR code is scanned. + val request = getVerificationRequest(otherUserId, tid) ?: return null + + if (request.canScanQrCodes()) { + QrCodeVerification(this.olmMachine.inner(), request, null, this.requestSender, this.listeners) + } else { + null + } + } } } diff --git a/rust-sdk/Cargo.toml b/rust-sdk/Cargo.toml index 29d19c41a6..f4f9a74826 100644 --- a/rust-sdk/Cargo.toml +++ b/rust-sdk/Cargo.toml @@ -25,11 +25,11 @@ features = ["lax_deserialize"] [dependencies.matrix-sdk-common] git = "https://github.com/matrix-org/matrix-rust-sdk/" -rev = "c5df7c5356c2540ede95f41e3565e91ca7de5777" +rev = "b53518d1b8dd93ac447d1318c2e8aa4e2004dd2f" [dependencies.matrix-sdk-crypto] git = "https://github.com/matrix-org/matrix-rust-sdk/" -rev = "c5df7c5356c2540ede95f41e3565e91ca7de5777" +rev = "b53518d1b8dd93ac447d1318c2e8aa4e2004dd2f" features = ["sled_cryptostore"] [dependencies.tokio] diff --git a/rust-sdk/src/lib.rs b/rust-sdk/src/lib.rs index fe02dc3ab7..7d2aef13e0 100644 --- a/rust-sdk/src/lib.rs +++ b/rust-sdk/src/lib.rs @@ -8,7 +8,7 @@ pub use device::Device; pub use error::{CryptoStoreError, DecryptionError, KeyImportError, MachineCreationError}; pub use logger::{set_logger, Logger}; pub use machine::{ - KeyRequestPair, OlmMachine, QrCode, RequestVerificationResult, Sas, StartSasResult, + KeyRequestPair, OlmMachine, QrCode, RequestVerificationResult, Sas, ScanResult, StartSasResult, Verification, VerificationRequest, }; pub use responses::{ diff --git a/rust-sdk/src/machine.rs b/rust-sdk/src/machine.rs index 7b7a204357..6bacc250e8 100644 --- a/rust-sdk/src/machine.rs +++ b/rust-sdk/src/machine.rs @@ -4,7 +4,7 @@ use std::{ io::Cursor, }; -use base64::encode; +use base64::{decode_config, encode, STANDARD_NO_PAD}; use js_int::UInt; use ruma::{ api::{ @@ -30,8 +30,8 @@ use tokio::runtime::Runtime; use matrix_sdk_common::{deserialized_responses::AlgorithmInfo, uuid::Uuid}; use matrix_sdk_crypto::{ - decrypt_key_export, encrypt_key_export, EncryptionSettings, LocalTrust, - OlmMachine as InnerMachine, QrVerification as InnerQr, Sas as InnerSas, + decrypt_key_export, encrypt_key_export, matrix_qrcode::QrVerificationData, EncryptionSettings, + LocalTrust, OlmMachine as InnerMachine, QrVerification as InnerQr, Sas as InnerSas, Verification as RustVerification, VerificationRequest as InnerVerificationRequest, }; @@ -80,6 +80,7 @@ pub struct QrCode { pub we_started: bool, pub other_side_scanned: bool, pub has_been_confirmed: bool, + pub reciprocated: bool, pub cancel_code: Option, pub cancelled_by_us: Option, } @@ -93,6 +94,7 @@ impl From for QrCode { is_done: qr.is_done(), cancel_code: qr.cancel_code().map(|c| c.to_string()), cancelled_by_us: qr.cancelled_by_us(), + reciprocated: qr.reciprocated(), we_started: qr.we_started(), other_side_scanned: qr.has_been_scanned(), has_been_confirmed: qr.has_been_confirmed(), @@ -107,6 +109,11 @@ pub struct StartSasResult { pub request: OutgoingVerificationRequest, } +pub struct ScanResult { + pub qr: QrCode, + pub request: OutgoingVerificationRequest, +} + impl From for Sas { fn from(sas: InnerSas) -> Self { Self { @@ -758,6 +765,32 @@ impl OlmMachine { }) } + pub fn scan_qr_code(&self, user_id: &str, flow_id: &str, data: &str) -> Option { + let user_id = UserId::try_from(user_id).ok()?; + // TODO create a custom error type + let data = decode_config(data, STANDARD_NO_PAD).ok()?; + let data = QrVerificationData::from_bytes(data).ok()?; + + if let Some(verification) = self.inner.get_verification_request(&user_id, flow_id) { + if let Some(qr) = self + .runtime + .block_on(verification.scan_qr_code(data)) + .ok()? + { + let request = qr.reciprocate()?; + + Some(ScanResult { + qr: qr.into(), + request: request.into(), + }) + } else { + None + } + } else { + None + } + } + pub fn request_verification( &self, user_id: &str, diff --git a/rust-sdk/src/olm.udl b/rust-sdk/src/olm.udl index 62c36f9a9a..9f02558e43 100644 --- a/rust-sdk/src/olm.udl +++ b/rust-sdk/src/olm.udl @@ -87,12 +87,19 @@ dictionary Sas { boolean supports_emoji; }; +dictionary ScanResult { + QrCode qr; + OutgoingVerificationRequest request; +}; + + dictionary QrCode { string other_user_id; string other_device_id; string flow_id; string? cancel_code; string? room_id; + boolean reciprocated; boolean we_started; boolean has_been_confirmed; boolean? cancelled_by_us; @@ -232,6 +239,7 @@ interface OlmMachine { OutgoingVerificationRequest? accept_sas_verification([ByRef] string user_id, [ByRef] string flow_id); [Throws=CryptoStoreError] QrCode? start_qr_verification([ByRef] string user_id, [ByRef] string flow_id); + ScanResult? scan_qr_code([ByRef] string user_id, [ByRef] string flow_id, [ByRef] string data); string? generate_qr_code([ByRef] string user_id, [ByRef] string flow_id); sequence? get_emoji_index([ByRef] string user_id, [ByRef] string flow_id);