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(
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<VerificationService.Listener>,
) : 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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<String>,
pub cancelled_by_us: Option<bool>,
}
@ -93,6 +94,7 @@ impl From<InnerQr> 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<InnerSas> 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<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(
&self,
user_id: &str,

View File

@ -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<i32>? get_emoji_index([ByRef] string user_id, [ByRef] string flow_id);