crypto: Add support to scan QR codes during verification

This commit is contained in:
Damir Jelić 2021-07-10 20:48:57 +02:00
parent b26aba9fc0
commit 7650e43362
7 changed files with 137 additions and 34 deletions

View File

@ -160,7 +160,8 @@ internal class Device(
internal class QrCodeVerification( internal class QrCodeVerification(
private val machine: uniffi.olm.OlmMachine, 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 sender: RequestSender,
private val listeners: ArrayList<VerificationService.Listener>, private val listeners: ArrayList<VerificationService.Listener>,
) : QrCodeVerificationTransaction { ) : QrCodeVerificationTransaction {
@ -180,14 +181,17 @@ internal class QrCodeVerification(
override val qrCodeText: String? override val qrCodeText: String?
get() { 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? // 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) return data?.fromBase64()?.toString(Charsets.ISO_8859_1)
} }
override fun userHasScannedOtherQrCode(otherQrCodeText: String) { override fun userHasScannedOtherQrCode(otherQrCodeText: String) {
TODO("Not yet implemented") runBlocking {
request.scanQrCode(otherQrCodeText)
}
dispatchTxUpdated()
} }
override fun otherUserScannedMyQrCode() { override fun otherUserScannedMyQrCode() {
@ -203,37 +207,43 @@ internal class QrCodeVerification(
override var state: VerificationTxState override var state: VerificationTxState
get() { get() {
refreshData() refreshData()
val inner = this.inner
return when { return if (inner != null) {
inner.isDone -> VerificationTxState.Verified when {
inner.hasBeenConfirmed -> VerificationTxState.WaitingOtherReciprocateConfirm inner.isDone -> VerificationTxState.Verified
inner.otherSideScanned -> VerificationTxState.QrScannedByOther inner.reciprocated -> VerificationTxState.Started
inner.isCancelled -> { inner.hasBeenConfirmed -> VerificationTxState.WaitingOtherReciprocateConfirm
val cancelCode = safeValueOf(inner.cancelCode) inner.otherSideScanned -> VerificationTxState.QrScannedByOther
val byMe = inner.cancelledByUs ?: false inner.isCancelled -> {
VerificationTxState.Cancelled(cancelCode, byMe) val cancelCode = safeValueOf(inner.cancelCode)
} val byMe = inner.cancelledByUs ?: false
else -> { VerificationTxState.Cancelled(cancelCode, byMe)
VerificationTxState.None }
else -> {
VerificationTxState.None
}
} }
} else {
VerificationTxState.None
} }
} }
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
set(value) {} set(value) {}
override val transactionId: String override val transactionId: String
get() = this.inner.flowId get() = this.request.flowId()
override val otherUserId: String override val otherUserId: String
get() = this.inner.otherUserId get() = this.request.otherUser()
override var otherDeviceId: String? override var otherDeviceId: String?
get() = this.inner.otherDeviceId get() = this.request.otherDeviceId()
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
set(value) {} set(value) {}
override val isIncoming: Boolean override val isIncoming: Boolean
get() = !this.inner.weStarted get() = !this.request.weStarted()
override fun cancel() { override fun cancel() {
cancelHelper(CancelCode.User) cancelHelper(CancelCode.User)
@ -244,13 +254,13 @@ internal class QrCodeVerification(
} }
override fun isToDeviceTransport(): Boolean { override fun isToDeviceTransport(): Boolean {
return this.inner.roomId == null return this.request.roomId() == null
} }
@Throws(CryptoStoreErrorException::class) @Throws(CryptoStoreErrorException::class)
suspend fun confirm(): OutgoingVerificationRequest? = suspend fun confirm(): OutgoingVerificationRequest? =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
machine.confirmVerification(inner.otherUserId, inner.flowId) machine.confirmVerification(request.otherUser(), request.flowId())
} }
private fun sendRequest(request: OutgoingVerificationRequest) { private fun sendRequest(request: OutgoingVerificationRequest) {
@ -268,7 +278,7 @@ internal class QrCodeVerification(
} }
private fun cancelHelper(code: CancelCode) { 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) { if (request != null) {
sendRequest(request) sendRequest(request)
@ -276,11 +286,12 @@ internal class QrCodeVerification(
} }
private fun refreshData() { private fun refreshData() {
when (val verification = this.machine.getVerification(this.inner.otherUserId, this.inner.flowId)) { when (val verification = this.machine.getVerification(this.request.otherUser(), this.request.flowId())) {
is Verification.QrCodeV1 -> { is Verification.QrCodeV1 -> {
this.inner = verification.qrcode this.inner = verification.qrcode
} }
else -> {} else -> {
}
} }
return return

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import com.sun.jna.Native.toByteArray
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode 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.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService 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.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_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_QR_CODE_SHOW
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
@ -76,17 +78,49 @@ internal class VerificationRequest(
return this.inner.isDone 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 { fun isReady(): Boolean {
refreshData() refreshData()
return this.inner.isReady 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? { internal fun startQrVerification(): QrCodeVerification? {
val qrcode = this.machine.startQrVerification(this.inner.otherUserId, this.inner.flowId) val qrcode = this.machine.startQrVerification(this.inner.otherUserId, this.inner.flowId)
return if (qrcode != null) { return if (qrcode != null) {
QrCodeVerification( QrCodeVerification(
this.machine, this.machine,
this,
qrcode, qrcode,
this.sender, this.sender,
this.listeners, 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? { suspend fun startSasVerification(): SasVerification? {
refreshData() refreshData()

View File

@ -219,15 +219,27 @@ internal class RustVerificationService(
otherUserId: String, otherUserId: String,
tid: String, tid: String,
): VerificationTransaction? { ): VerificationTransaction? {
val verification = this.olmMachine.getVerification(otherUserId, tid) ?: return null return when (val verification = this.olmMachine.getVerification(otherUserId, tid)) {
return when (verification) {
is Verification.QrCodeV1 -> { 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 -> { is Verification.SasV1 -> {
SasVerification(this.olmMachine.inner(), verification.sas, this.requestSender, this.listeners) 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
}
}
} }
} }

View File

@ -25,11 +25,11 @@ features = ["lax_deserialize"]
[dependencies.matrix-sdk-common] [dependencies.matrix-sdk-common]
git = "https://github.com/matrix-org/matrix-rust-sdk/" git = "https://github.com/matrix-org/matrix-rust-sdk/"
rev = "c5df7c5356c2540ede95f41e3565e91ca7de5777" rev = "b53518d1b8dd93ac447d1318c2e8aa4e2004dd2f"
[dependencies.matrix-sdk-crypto] [dependencies.matrix-sdk-crypto]
git = "https://github.com/matrix-org/matrix-rust-sdk/" git = "https://github.com/matrix-org/matrix-rust-sdk/"
rev = "c5df7c5356c2540ede95f41e3565e91ca7de5777" rev = "b53518d1b8dd93ac447d1318c2e8aa4e2004dd2f"
features = ["sled_cryptostore"] features = ["sled_cryptostore"]
[dependencies.tokio] [dependencies.tokio]

View File

@ -8,7 +8,7 @@ pub use device::Device;
pub use error::{CryptoStoreError, DecryptionError, KeyImportError, MachineCreationError}; pub use error::{CryptoStoreError, DecryptionError, KeyImportError, MachineCreationError};
pub use logger::{set_logger, Logger}; pub use logger::{set_logger, Logger};
pub use machine::{ pub use machine::{
KeyRequestPair, OlmMachine, QrCode, RequestVerificationResult, Sas, StartSasResult, KeyRequestPair, OlmMachine, QrCode, RequestVerificationResult, Sas, ScanResult, StartSasResult,
Verification, VerificationRequest, Verification, VerificationRequest,
}; };
pub use responses::{ pub use responses::{

View File

@ -4,7 +4,7 @@ use std::{
io::Cursor, io::Cursor,
}; };
use base64::encode; use base64::{decode_config, encode, STANDARD_NO_PAD};
use js_int::UInt; use js_int::UInt;
use ruma::{ use ruma::{
api::{ api::{
@ -30,8 +30,8 @@ use tokio::runtime::Runtime;
use matrix_sdk_common::{deserialized_responses::AlgorithmInfo, uuid::Uuid}; use matrix_sdk_common::{deserialized_responses::AlgorithmInfo, uuid::Uuid};
use matrix_sdk_crypto::{ use matrix_sdk_crypto::{
decrypt_key_export, encrypt_key_export, EncryptionSettings, LocalTrust, decrypt_key_export, encrypt_key_export, matrix_qrcode::QrVerificationData, EncryptionSettings,
OlmMachine as InnerMachine, QrVerification as InnerQr, Sas as InnerSas, LocalTrust, OlmMachine as InnerMachine, QrVerification as InnerQr, Sas as InnerSas,
Verification as RustVerification, VerificationRequest as InnerVerificationRequest, Verification as RustVerification, VerificationRequest as InnerVerificationRequest,
}; };
@ -80,6 +80,7 @@ pub struct QrCode {
pub we_started: bool, pub we_started: bool,
pub other_side_scanned: bool, pub other_side_scanned: bool,
pub has_been_confirmed: bool, pub has_been_confirmed: bool,
pub reciprocated: bool,
pub cancel_code: Option<String>, pub cancel_code: Option<String>,
pub cancelled_by_us: Option<bool>, pub cancelled_by_us: Option<bool>,
} }
@ -93,6 +94,7 @@ impl From<InnerQr> for QrCode {
is_done: qr.is_done(), is_done: qr.is_done(),
cancel_code: qr.cancel_code().map(|c| c.to_string()), cancel_code: qr.cancel_code().map(|c| c.to_string()),
cancelled_by_us: qr.cancelled_by_us(), cancelled_by_us: qr.cancelled_by_us(),
reciprocated: qr.reciprocated(),
we_started: qr.we_started(), we_started: qr.we_started(),
other_side_scanned: qr.has_been_scanned(), other_side_scanned: qr.has_been_scanned(),
has_been_confirmed: qr.has_been_confirmed(), has_been_confirmed: qr.has_been_confirmed(),
@ -107,6 +109,11 @@ pub struct StartSasResult {
pub request: OutgoingVerificationRequest, pub request: OutgoingVerificationRequest,
} }
pub struct ScanResult {
pub qr: QrCode,
pub request: OutgoingVerificationRequest,
}
impl From<InnerSas> for Sas { impl From<InnerSas> for Sas {
fn from(sas: InnerSas) -> Self { fn from(sas: InnerSas) -> Self {
Self { Self {
@ -758,6 +765,32 @@ impl OlmMachine {
}) })
} }
pub fn scan_qr_code(&self, user_id: &str, flow_id: &str, data: &str) -> Option<ScanResult> {
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( pub fn request_verification(
&self, &self,
user_id: &str, user_id: &str,

View File

@ -87,12 +87,19 @@ dictionary Sas {
boolean supports_emoji; boolean supports_emoji;
}; };
dictionary ScanResult {
QrCode qr;
OutgoingVerificationRequest request;
};
dictionary QrCode { dictionary QrCode {
string other_user_id; string other_user_id;
string other_device_id; string other_device_id;
string flow_id; string flow_id;
string? cancel_code; string? cancel_code;
string? room_id; string? room_id;
boolean reciprocated;
boolean we_started; boolean we_started;
boolean has_been_confirmed; boolean has_been_confirmed;
boolean? cancelled_by_us; boolean? cancelled_by_us;
@ -232,6 +239,7 @@ interface OlmMachine {
OutgoingVerificationRequest? accept_sas_verification([ByRef] string user_id, [ByRef] string flow_id); OutgoingVerificationRequest? accept_sas_verification([ByRef] string user_id, [ByRef] string flow_id);
[Throws=CryptoStoreError] [Throws=CryptoStoreError]
QrCode? start_qr_verification([ByRef] string user_id, [ByRef] string flow_id); 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); string? generate_qr_code([ByRef] string user_id, [ByRef] string flow_id);
sequence<i32>? get_emoji_index([ByRef] string user_id, [ByRef] string flow_id); sequence<i32>? get_emoji_index([ByRef] string user_id, [ByRef] string flow_id);