diff --git a/bindings/matrix-sdk-crypto-ffi/src/machine.rs b/bindings/matrix-sdk-crypto-ffi/src/machine.rs index 28b689132..63cc5e9df 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/machine.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/machine.rs @@ -6,7 +6,6 @@ use std::{ time::Duration, }; -use base64::{decode_config, encode, STANDARD_NO_PAD}; use js_int::UInt; use matrix_sdk_common::deserialized_responses::AlgorithmInfo; use matrix_sdk_crypto::{ @@ -15,10 +14,9 @@ use matrix_sdk_crypto::{ SignatureVerification as RustSignatureCheckResult, }, decrypt_room_key_export, encrypt_room_key_export, - matrix_sdk_qrcode::QrVerificationData, olm::ExportedRoomKey, store::RecoveryKey, - LocalTrust, OlmMachine as InnerMachine, UserIdentities, Verification as RustVerification, + LocalTrust, OlmMachine as InnerMachine, UserIdentities, }; use ruma::{ api::{ @@ -48,12 +46,12 @@ use zeroize::Zeroize; use crate::{ error::{CryptoStoreError, DecryptionError, SecretImportError, SignatureError}, parse_user_id, - responses::{response_from_string, OutgoingVerificationRequest, OwnedResponse}, - BackupKeys, BackupRecoveryKey, BootstrapCrossSigningResult, ConfirmVerificationResult, - CrossSigningKeyExport, CrossSigningStatus, DecodeError, DecryptedEvent, Device, DeviceLists, - EncryptionSettings, KeyImportError, KeysImportResult, MegolmV1BackupKey, ProgressListener, - QrCode, Request, RequestType, RequestVerificationResult, RoomKeyCounts, ScanResult, - SignatureUploadRequest, StartSasResult, UserIdentity, Verification, VerificationRequest, + responses::{response_from_string, OwnedResponse}, + BackupKeys, BackupRecoveryKey, BootstrapCrossSigningResult, CrossSigningKeyExport, + CrossSigningStatus, DecodeError, DecryptedEvent, Device, DeviceLists, EncryptionSettings, + KeyImportError, KeysImportResult, MegolmV1BackupKey, ProgressListener, Request, RequestType, + RequestVerificationResult, RoomKeyCounts, Sas, SignatureUploadRequest, StartSasResult, + UserIdentity, Verification, VerificationRequest, }; /// A high level state machine that handles E2EE for Matrix. @@ -842,12 +840,18 @@ impl OlmMachine { /// /// * `user_id` - The ID of the user for which we would like to fetch the /// verification requests. - pub fn get_verification_requests(&self, user_id: &str) -> Vec { + pub fn get_verification_requests(&self, user_id: &str) -> Vec> { let Ok(user_id) = UserId::parse(user_id) else { return vec![]; }; - self.inner.get_verification_requests(&user_id).into_iter().map(|v| v.into()).collect() + self.inner + .get_verification_requests(&user_id) + .into_iter() + .map(|v| { + VerificationRequest { inner: v, runtime: self.runtime.handle().to_owned() }.into() + }) + .collect() } /// Get a verification requests that we share with the given user with the @@ -863,40 +867,12 @@ impl OlmMachine { &self, user_id: &str, flow_id: &str, - ) -> Option { + ) -> Option> { let user_id = UserId::parse(user_id).ok()?; - self.inner.get_verification_request(&user_id, flow_id).map(|v| v.into()) - } - - /// Accept a verification requests that we share with the given user with - /// the given flow id. - /// - /// This will move the verification request into the ready state. - /// - /// # Arguments - /// - /// * `user_id` - The ID of the user for which we would like to accept the - /// verification requests. - /// - /// * `flow_id` - The ID that uniquely identifies the verification flow. - /// - /// * `methods` - A list of verification methods that we want to advertise - /// as supported. - pub fn accept_verification_request( - &self, - user_id: &str, - flow_id: &str, - methods: Vec, - ) -> Option { - let user_id = UserId::parse(user_id).ok()?; - let methods = methods.into_iter().map(VerificationMethod::from).collect(); - - if let Some(verification) = self.inner.get_verification_request(&user_id, flow_id) { - verification.accept_with_methods(methods).map(|r| r.into()) - } else { - None - } + self.inner.get_verification_request(&user_id, flow_id).map(|v| { + VerificationRequest { inner: v, runtime: self.runtime.handle().to_owned() }.into() + }) } /// Get an m.key.verification.request content for the given user. @@ -954,7 +930,7 @@ impl OlmMachine { room_id: &str, event_id: &str, methods: Vec, - ) -> Result, CryptoStoreError> { + ) -> Result>, CryptoStoreError> { let user_id = parse_user_id(user_id)?; let event_id = EventId::parse(event_id)?; let room_id = RoomId::parse(room_id)?; @@ -970,7 +946,10 @@ impl OlmMachine { Some(methods), )); - Some(request.into()) + Some( + VerificationRequest { inner: request, runtime: self.runtime.handle().to_owned() } + .into(), + ) } else { None }) @@ -1005,7 +984,11 @@ impl OlmMachine { self.runtime.block_on(device.request_verification_with_methods(methods)); Some(RequestVerificationResult { - verification: verification.into(), + verification: VerificationRequest { + inner: verification, + runtime: self.runtime.handle().to_owned(), + } + .into(), request: request.into(), }) } else { @@ -1033,7 +1016,11 @@ impl OlmMachine { let (verification, request) = self.runtime.block_on(identity.request_verification_with_methods(methods))?; Some(RequestVerificationResult { - verification: verification.into(), + verification: VerificationRequest { + inner: verification, + runtime: self.runtime.handle().to_owned(), + } + .into(), request: request.into(), }) } else { @@ -1050,206 +1037,12 @@ impl OlmMachine { /// verification. /// /// * `flow_id` - The ID that uniquely identifies the verification flow. - pub fn get_verification(&self, user_id: &str, flow_id: &str) -> Option { + pub fn get_verification(&self, user_id: &str, flow_id: &str) -> Option> { let user_id = UserId::parse(user_id).ok()?; - self.inner.get_verification(&user_id, flow_id).map(|v| match v { - RustVerification::SasV1(s) => Verification::SasV1 { sas: s.into() }, - RustVerification::QrV1(qr) => Verification::QrCodeV1 { qrcode: qr.into() }, - _ => unreachable!(), - }) - } - - /// Cancel a verification for the given user with the given flow id using - /// the given cancel code. - /// - /// # Arguments - /// - /// * `user_id` - The ID of the user for which we would like to cancel the - /// verification. - /// - /// * `flow_id` - The ID that uniquely identifies the verification flow. - /// - /// * `cancel_code` - The error code for why the verification was cancelled, - /// manual cancellatio usually happens with `m.user` cancel code. The full - /// list of cancel codes can be found in the [spec] - /// - /// [spec]: https://spec.matrix.org/unstable/client-server-api/#mkeyverificationcancel - pub fn cancel_verification( - &self, - user_id: &str, - flow_id: &str, - cancel_code: &str, - ) -> Option { - let user_id = UserId::parse(user_id).ok()?; - - if let Some(request) = self.inner.get_verification_request(&user_id, flow_id) { - request.cancel().map(|r| r.into()) - } else if let Some(verification) = self.inner.get_verification(&user_id, flow_id) { - match verification { - RustVerification::SasV1(v) => { - v.cancel_with_code(cancel_code.into()).map(|r| r.into()) - } - RustVerification::QrV1(v) => { - v.cancel_with_code(cancel_code.into()).map(|r| r.into()) - } - _ => unreachable!(), - } - } else { - None - } - } - - /// Confirm a verification was successful. - /// - /// This method should be called either if a short auth string should be - /// confirmed as matching, or if we want to confirm that the other side has - /// scanned our QR code. - /// - /// # Arguments - /// - /// * `user_id` - The ID of the user for which we would like to confirm the - /// verification. - /// - /// * `flow_id` - The ID that uniquely identifies the verification flow. - pub fn confirm_verification( - &self, - user_id: &str, - flow_id: &str, - ) -> Result, CryptoStoreError> { - let user_id = parse_user_id(user_id)?; - - Ok(if let Some(verification) = self.inner.get_verification(&user_id, flow_id) { - match verification { - RustVerification::SasV1(v) => { - let (requests, signature_request) = self.runtime.block_on(v.confirm())?; - - let requests = requests.into_iter().map(|r| r.into()).collect(); - - Some(ConfirmVerificationResult { - requests, - signature_request: signature_request.map(|s| s.into()), - }) - } - RustVerification::QrV1(v) => v.confirm_scanning().map(|r| { - ConfirmVerificationResult { requests: vec![r.into()], signature_request: None } - }), - _ => unreachable!(), - } - } else { - None - }) - } - - /// Transition from a verification request into QR code verification. - /// - /// This method should be called when one wants to display a QR code so the - /// other side can scan it and move the QR code verification forward. - /// - /// # Arguments - /// - /// * `user_id` - The ID of the user for which we would like to start the - /// QR code verification. - /// - /// * `flow_id` - The ID of the verification request that initiated the - /// verification flow. - pub fn start_qr_verification( - &self, - user_id: &str, - flow_id: &str, - ) -> Result, CryptoStoreError> { - let user_id = parse_user_id(user_id)?; - - if let Some(verification) = self.inner.get_verification_request(&user_id, flow_id) { - Ok(self.runtime.block_on(verification.generate_qr_code())?.map(|qr| qr.into())) - } else { - Ok(None) - } - } - - /// Generate data that should be encoded as a QR code. - /// - /// This method should be called right before a QR code should be displayed, - /// the returned data is base64 encoded (without padding) and needs to be - /// decoded on the other side before it can be put through a QR code - /// generator. - /// - /// *Note*: You'll need to call [start_qr_verification()] before calling - /// this method, otherwise `None` will be returned. - /// - /// # Arguments - /// - /// * `user_id` - The ID of the user for which we would like to start the - /// QR code verification. - /// - /// * `flow_id` - The ID that uniquely identifies the verification flow. - /// - /// [start_qr_verification()]: #method.start_qr_verification - pub fn generate_qr_code(&self, user_id: &str, flow_id: &str) -> Option { - let user_id = UserId::parse(user_id).ok()?; self.inner .get_verification(&user_id, flow_id) - .and_then(|v| v.qr_v1().and_then(|qr| qr.to_bytes().map(encode).ok())) - } - - /// Pass data from a scanned QR code to an active verification request and - /// transition into QR code verification. - /// - /// This requires an active `VerificationRequest` to succeed, returns `None` - /// if no `VerificationRequest` is found or if the QR code data is invalid. - /// - /// # Arguments - /// - /// * `user_id` - The ID of the user for which we would like to start the - /// QR code verification. - /// - /// * `flow_id` - The ID of the verification request that initiated the - /// verification flow. - /// - /// * `data` - The data that was extracted from the scanned QR code as an - /// base64 encoded string, without padding. - pub fn scan_qr_code(&self, user_id: &str, flow_id: &str, data: &str) -> Option { - let user_id = UserId::parse(user_id).ok()?; - 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 - } - } - - /// Transition from a verification request into short auth string based - /// verification. - /// - /// # Arguments - /// - /// * `user_id` - The ID of the user for which we would like to start the - /// SAS verification. - /// - /// * `flow_id` - The ID of the verification request that initiated the - /// verification flow. - pub fn start_sas_verification( - &self, - user_id: &str, - flow_id: &str, - ) -> Result, CryptoStoreError> { - let user_id = parse_user_id(user_id)?; - - Ok(if let Some(verification) = self.inner.get_verification_request(&user_id, flow_id) { - self.runtime - .block_on(verification.start_sas())? - .map(|(sas, r)| StartSasResult { sas: sas.into(), request: r.into() }) - } else { - None - }) + .map(|v| Verification { inner: v, runtime: self.runtime.handle().to_owned() }.into()) } /// Start short auth string verification with a device without going @@ -1279,74 +1072,16 @@ impl OlmMachine { { let (sas, request) = self.runtime.block_on(device.start_verification())?; - Some(StartSasResult { sas: sas.into(), request: request.into() }) + Some(StartSasResult { + sas: Sas { inner: sas, runtime: self.runtime.handle().to_owned() }.into(), + request: request.into(), + }) } else { None }, ) } - /// Accept that we're going forward with the short auth string verification. - /// - /// # Arguments - /// - /// * `user_id` - The ID of the user for which we would like to accept the - /// SAS verification. - /// - /// * `flow_id` - The ID that uniquely identifies the verification flow. - pub fn accept_sas_verification( - &self, - user_id: &str, - flow_id: &str, - ) -> Option { - let user_id = UserId::parse(user_id).ok()?; - - self.inner.get_verification(&user_id, flow_id)?.sas_v1()?.accept().map(|r| r.into()) - } - - /// Get a list of emoji indices of the emoji representation of the short - /// auth string. - /// - /// *Note*: A SAS verification needs to be started and in the presentable - /// state for this to return the list of emoji indices, otherwise returns - /// `None`. - /// - /// # Arguments - /// - /// * `user_id` - The ID of the user for which we would like to get the - /// short auth string. - /// - /// * `flow_id` - The ID that uniquely identifies the verification flow. - pub fn get_emoji_index(&self, user_id: &str, flow_id: &str) -> Option> { - let user_id = UserId::parse(user_id).ok()?; - - self.inner.get_verification(&user_id, flow_id).and_then(|s| { - s.sas_v1() - .and_then(|s| s.emoji_index().map(|v| v.iter().map(|i| (*i).into()).collect())) - }) - } - - /// Get the decimal representation of the short auth string. - /// - /// *Note*: A SAS verification needs to be started and in the presentable - /// state for this to return the list of decimals, otherwise returns - /// `None`. - /// - /// # Arguments - /// - /// * `user_id` - The ID of the user for which we would like to get the - /// short auth string. - /// - /// * `flow_id` - The ID that uniquely identifies the verification flow. - pub fn get_decimals(&self, user_id: &str, flow_id: &str) -> Option> { - let user_id = UserId::parse(user_id).ok()?; - - self.inner.get_verification(&user_id, flow_id).and_then(|s| { - s.sas_v1() - .and_then(|s| s.decimals().map(|v| [v.0.into(), v.1.into(), v.2.into()].to_vec())) - }) - } - /// Create a new private cross signing identity and create a request to /// upload the public part of it to the server. pub fn bootstrap_cross_signing(&self) -> Result { diff --git a/bindings/matrix-sdk-crypto-ffi/src/olm.udl b/bindings/matrix-sdk-crypto-ffi/src/olm.udl index 4d595bf87..bfab4abd6 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/olm.udl +++ b/bindings/matrix-sdk-crypto-ffi/src/olm.udl @@ -146,19 +146,21 @@ dictionary StartSasResult { OutgoingVerificationRequest request; }; -dictionary Sas { - string other_user_id; - string other_device_id; - string flow_id; - string? room_id; - boolean we_started; - boolean has_been_accepted; - boolean can_be_presented; - boolean supports_emoji; - boolean have_we_confirmed; - boolean is_done; - boolean is_cancelled; - CancelInfo? cancel_info; +interface Sas { + string other_user_id(); + string other_device_id(); + string flow_id(); + string? room_id(); + boolean we_started(); + boolean is_done(); + + OutgoingVerificationRequest? accept(); + [Throws=CryptoStoreError] + ConfirmVerificationResult? confirm(); + OutgoingVerificationRequest? cancel([ByRef] string cancel_code); + + sequence? get_emoji_indices(); + sequence? get_decimals(); }; dictionary ScanResult { @@ -166,34 +168,44 @@ dictionary ScanResult { OutgoingVerificationRequest request; }; -dictionary QrCode { - string other_user_id; - string other_device_id; - string flow_id; - string? room_id; - boolean we_started; - boolean other_side_scanned; - boolean has_been_confirmed; - boolean reciprocated; - boolean is_done; - boolean is_cancelled; - CancelInfo? cancel_info; +interface QrCode { + string other_user_id(); + string other_device_id(); + string flow_id(); + string? room_id(); + boolean we_started(); + boolean is_done(); + CancelInfo? cancel_info(); + + boolean reciprocated(); + boolean has_been_scanned(); + + ConfirmVerificationResult? confirm(); + OutgoingVerificationRequest? cancel([ByRef] string cancel_code); + string? generate_qr_code(); }; -dictionary VerificationRequest { - string other_user_id; - string? other_device_id; - string flow_id; - string? room_id; - boolean we_started; - boolean is_ready; - boolean is_passive; - boolean is_done; - boolean is_cancelled; - CancelInfo? cancel_info; - sequence? their_methods; - sequence? our_methods; +interface VerificationRequest { + string other_user_id(); + string? other_device_id(); + string flow_id(); + string? room_id(); + boolean we_started(); + boolean is_ready(); + boolean is_done(); + boolean is_passive(); + sequence? their_supported_methods(); + sequence? our_supported_methods(); + + OutgoingVerificationRequest? accept(sequence methods); + + [Throws=CryptoStoreError] + StartSasResult? start_sas_verification(); + + [Throws=CryptoStoreError] + QrCode? start_qr_verification(); + ScanResult? scan_qr_code([ByRef] string data); }; dictionary RequestVerificationResult { @@ -206,10 +218,9 @@ dictionary ConfirmVerificationResult { SignatureUploadRequest? signature_request; }; -[Enum] interface Verification { - SasV1(Sas sas); - QrCodeV1(QrCode qrcode); + QrCode? as_qr(); + Sas? as_sas(); }; dictionary KeyRequestPair { @@ -361,32 +372,8 @@ interface OlmMachine { sequence methods ); - OutgoingVerificationRequest? accept_verification_request( - [ByRef] string user_id, - [ByRef] string flow_id, - sequence methods - ); - - [Throws=CryptoStoreError] - ConfirmVerificationResult? confirm_verification([ByRef] string user_id, [ByRef] string flow_id); - OutgoingVerificationRequest? cancel_verification( - [ByRef] string user_id, - [ByRef] string flow_id, - [ByRef] string cancel_code - ); - [Throws=CryptoStoreError] StartSasResult? start_sas_with_device([ByRef] string user_id, [ByRef] string device_id); - [Throws=CryptoStoreError] - StartSasResult? start_sas_verification([ByRef] string user_id, [ByRef] string flow_id); - OutgoingVerificationRequest? accept_sas_verification([ByRef] string user_id, [ByRef] string flow_id); - sequence? get_emoji_index([ByRef] string user_id, [ByRef] string flow_id); - sequence? get_decimals([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); [Throws=DecryptionError] KeyRequestPair request_room_key([ByRef] string event, [ByRef] string room_id); diff --git a/bindings/matrix-sdk-crypto-ffi/src/verification.rs b/bindings/matrix-sdk-crypto-ffi/src/verification.rs index ad4ce52c6..e82cda5d9 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/verification.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/verification.rs @@ -1,101 +1,224 @@ +use std::sync::Arc; + +use base64::{decode_config, encode_config, STANDARD_NO_PAD}; use matrix_sdk_crypto::{ - CancelInfo as RustCancelInfo, QrVerification as InnerQr, Sas as InnerSas, + matrix_sdk_qrcode::QrVerificationData, CancelInfo as RustCancelInfo, QrVerification as InnerQr, + Sas as InnerSas, Verification as InnerVerification, VerificationRequest as InnerVerificationRequest, }; +use ruma::events::key::verification::VerificationMethod; +use tokio::runtime::Handle; -use crate::{OutgoingVerificationRequest, SignatureUploadRequest}; +use crate::{CryptoStoreError, OutgoingVerificationRequest, SignatureUploadRequest}; /// Enum representing the different verification flows we support. -pub enum Verification { - /// The `m.sas.v1` verification flow. - SasV1 { - #[allow(missing_docs)] - sas: Sas, - }, - /// The `m.qr_code.scan.v1`, `m.qr_code.show.v1`, and `m.reciprocate.v1` - /// verification flow. - QrCodeV1 { - #[allow(missing_docs)] - qrcode: QrCode, - }, +pub struct Verification { + pub(crate) inner: InnerVerification, + pub(crate) runtime: Handle, +} + +impl Verification { + /// Try to represent the `Verification` as an `Sas` verification object, + /// returns `None` if the verification is not a `Sas` verification. + pub fn as_sas(&self) -> Option> { + if let InnerVerification::SasV1(sas) = &self.inner { + Some(Sas { inner: sas.to_owned(), runtime: self.runtime.to_owned() }.into()) + } else { + None + } + } + + /// Try to represent the `Verification` as an `QrCode` verification object, + /// returns `None` if the verification is not a `QrCode` verification. + pub fn as_qr(&self) -> Option> { + if let InnerVerification::QrV1(qr) = &self.inner { + Some(QrCode { inner: qr.to_owned() }.into()) + } else { + None + } + } } /// The `m.sas.v1` verification flow. pub struct Sas { - /// The other user that is participating in the verification flow - pub other_user_id: String, - /// The other user's device that is participating in the verification flow - pub other_device_id: String, - /// The unique ID of this verification flow, will be a random string for - /// to-device events or a event ID for in-room events. - pub flow_id: String, - /// The room ID where this verification is happening, will be `None` if the - /// verification is going through to-device messages - pub room_id: Option, - /// Did we initiate the verification flow - pub we_started: bool, - /// Has the non-initiating side accepted the verification flow - pub has_been_accepted: bool, - /// Can the short auth string be presented - pub can_be_presented: bool, - /// Does the flow support the emoji representation of the short auth string - pub supports_emoji: bool, - /// Have we confirmed that the short auth strings match - pub have_we_confirmed: bool, - /// Has the verification completed successfully - pub is_done: bool, - /// Has the flow been cancelled - pub is_cancelled: bool, - /// Information about the cancellation of the flow, will be `None` if the - /// flow hasn't been cancelled - pub cancel_info: Option, + pub(crate) inner: InnerSas, + pub(crate) runtime: Handle, +} + +impl Sas { + /// Get the user id of the other side. + pub fn other_user_id(&self) -> String { + self.inner.other_user_id().to_string() + } + + /// Get the device ID of the other side. + pub fn other_device_id(&self) -> String { + self.inner.other_device_id().to_string() + } + + /// Get the unique ID that identifies this SAS verification flow. + pub fn flow_id(&self) -> String { + self.inner.flow_id().as_str().to_owned() + } + + /// Get the room id if the verification is happening inside a room. + pub fn room_id(&self) -> Option { + self.inner.room_id().map(|r| r.to_string()) + } + + /// Is the SAS flow done. + pub fn is_done(&self) -> bool { + self.inner.is_done() + } + + /// Did we initiate the verification flow. + pub fn we_started(&self) -> bool { + self.inner.we_started() + } + + /// Accept that we're going forward with the short auth string verification. + pub fn accept(&self) -> Option { + self.inner.accept().map(|r| r.into()) + } + + /// Confirm a verification was successful. + /// + /// This method should be called if a short auth string should be confirmed + /// as matching. + pub fn confirm(&self) -> Result, CryptoStoreError> { + let (requests, signature_request) = self.runtime.block_on(self.inner.confirm())?; + + let requests = requests.into_iter().map(|r| r.into()).collect(); + + Ok(Some(ConfirmVerificationResult { + requests, + signature_request: signature_request.map(|s| s.into()), + })) + } + + /// Cancel the SAS verification using the given cancel code. + /// + /// # Arguments + /// + /// * `cancel_code` - The error code for why the verification was cancelled, + /// manual cancellatio usually happens with `m.user` cancel code. The full + /// list of cancel codes can be found in the [spec] + /// + /// [spec]: https://spec.matrix.org/unstable/client-server-api/#mkeyverificationcancel + pub fn cancel(&self, cancel_code: &str) -> Option { + self.inner.cancel_with_code(cancel_code.into()).map(|r| r.into()) + } + + /// Get a list of emoji indices of the emoji representation of the short + /// auth string. + /// + /// *Note*: A SAS verification needs to be started and in the presentable + /// state for this to return the list of emoji indices, otherwise returns + /// `None`. + pub fn get_emoji_indices(&self) -> Option> { + self.inner.emoji_index().map(|v| v.iter().map(|i| (*i).into()).collect()) + } + + /// Get the decimal representation of the short auth string. + /// + /// *Note*: A SAS verification needs to be started and in the presentable + /// state for this to return the list of decimals, otherwise returns + /// `None`. + pub fn get_decimals(&self) -> Option> { + self.inner.decimals().map(|v| [v.0.into(), v.1.into(), v.2.into()].to_vec()) + } } /// The `m.qr_code.scan.v1`, `m.qr_code.show.v1`, and `m.reciprocate.v1` /// verification flow. pub struct QrCode { - /// The other user that is participating in the verification flow - pub other_user_id: String, - /// The other user's device that is participating in the verification flow - pub other_device_id: String, - /// The unique ID of this verification flow, will be a random string for - /// to-device events or a event ID for in-room events. - pub flow_id: String, - /// The room ID where this verification is happening, will be `None` if the - /// verification is going through to-device messages - pub room_id: Option, - /// Did we initiate the verification flow - pub we_started: bool, - /// Has the QR code been scanned by the other side - pub other_side_scanned: bool, - /// Has the scanning of the QR code been confirmed by us - pub has_been_confirmed: bool, - /// Did we scan the QR code and sent out a reciprocation - pub reciprocated: bool, - /// Has the verification completed successfully - pub is_done: bool, - /// Has the flow been cancelled - pub is_cancelled: bool, - /// Information about the cancellation of the flow, will be `None` if the - /// flow hasn't been cancelled - pub cancel_info: Option, + pub(crate) inner: InnerQr, } -impl From for QrCode { - fn from(qr: InnerQr) -> Self { - Self { - other_user_id: qr.other_user_id().to_string(), - flow_id: qr.flow_id().as_str().to_owned(), - is_cancelled: qr.is_cancelled(), - is_done: qr.is_done(), - cancel_info: qr.cancel_info().map(|c| c.into()), - reciprocated: qr.reciprocated(), - we_started: qr.we_started(), - other_side_scanned: qr.has_been_scanned(), - has_been_confirmed: qr.has_been_confirmed(), - other_device_id: qr.other_device_id().to_string(), - room_id: qr.room_id().map(|r| r.to_string()), - } +impl QrCode { + /// Get the user id of the other side. + pub fn other_user_id(&self) -> String { + self.inner.other_user_id().to_string() + } + + /// Get the device ID of the other side. + pub fn other_device_id(&self) -> String { + self.inner.other_device_id().to_string() + } + + /// Get the unique ID that identifies this QR code verification flow. + pub fn flow_id(&self) -> String { + self.inner.flow_id().as_str().to_owned() + } + + /// Get the room id if the verification is happening inside a room. + pub fn room_id(&self) -> Option { + self.inner.room_id().map(|r| r.to_string()) + } + + /// Is the QR code verification done. + pub fn is_done(&self) -> bool { + self.inner.is_done() + } + + /// Did we initiate the verification flow. + pub fn we_started(&self) -> bool { + self.inner.we_started() + } + + /// Get the CancelInfo of this QR code verification object. + /// + /// Will be `None` if the flow has not been cancelled. + pub fn cancel_info(&self) -> Option { + self.inner.cancel_info().map(|c| c.into()) + } + + /// Has the QR verification been scanned by the other side. + /// + /// When the verification object is in this state it's required that the + /// user confirms that the other side has scanned the QR code. + pub fn has_been_scanned(&self) -> bool { + self.inner.has_been_scanned() + } + + /// Have we successfully scanned the QR code and are able to send a + /// reciprocation event. + pub fn reciprocated(&self) -> bool { + self.inner.reciprocated() + } + + /// Cancel the QR code verification using the given cancel code. + /// + /// # Arguments + /// + /// * `cancel_code` - The error code for why the verification was cancelled, + /// manual cancellatio usually happens with `m.user` cancel code. The full + /// list of cancel codes can be found in the [spec] + /// + /// [spec]: https://spec.matrix.org/unstable/client-server-api/#mkeyverificationcancel + pub fn cancel(&self, cancel_code: &str) -> Option { + self.inner.cancel_with_code(cancel_code.into()).map(|r| r.into()) + } + + /// Confirm a verification was successful. + /// + /// This method should be called if we want to confirm that the other side + /// has scanned our QR code. + pub fn confirm(&self) -> Option { + self.inner.confirm_scanning().map(|r| ConfirmVerificationResult { + requests: vec![r.into()], + signature_request: None, + }) + } + + /// Generate data that should be encoded as a QR code. + /// + /// This method should be called right before a QR code should be displayed, + /// the returned data is base64 encoded (without padding) and needs to be + /// decoded on the other side before it can be put through a QR code + /// generator. + pub fn generate_qr_code(&self) -> Option { + self.inner.to_bytes().map(|data| encode_config(data, STANDARD_NO_PAD)).ok() } } @@ -122,7 +245,7 @@ impl From for CancelInfo { /// A result type for starting SAS verifications. pub struct StartSasResult { /// The SAS verification object that got created. - pub sas: Sas, + pub sas: Arc, /// The request that needs to be sent out to notify the other side that a /// SAS verification should start. pub request: OutgoingVerificationRequest, @@ -131,35 +254,16 @@ pub struct StartSasResult { /// A result type for scanning QR codes. pub struct ScanResult { /// The QR code verification object that got created. - pub qr: QrCode, + pub qr: Arc, /// The request that needs to be sent out to notify the other side that a /// QR code verification should start. pub request: OutgoingVerificationRequest, } -impl From for Sas { - fn from(sas: InnerSas) -> Self { - Self { - other_user_id: sas.other_user_id().to_string(), - other_device_id: sas.other_device_id().to_string(), - flow_id: sas.flow_id().as_str().to_owned(), - is_cancelled: sas.is_cancelled(), - is_done: sas.is_done(), - can_be_presented: sas.can_be_presented(), - supports_emoji: sas.supports_emoji(), - have_we_confirmed: sas.have_we_confirmed(), - we_started: sas.we_started(), - room_id: sas.room_id().map(|r| r.to_string()), - has_been_accepted: sas.has_been_accepted(), - cancel_info: sas.cancel_info().map(|c| c.into()), - } - } -} - /// A result type for requesting verifications. pub struct RequestVerificationResult { /// The verification request object that got created. - pub verification: VerificationRequest, + pub verification: Arc, /// The request that needs to be sent out to notify the other side that /// we're requesting verification to begin. pub request: OutgoingVerificationRequest, @@ -178,55 +282,155 @@ pub struct ConfirmVerificationResult { /// The verificatoin request object which then can transition into some concrete /// verification method pub struct VerificationRequest { - /// The other user that is participating in the verification flow - pub other_user_id: String, - /// The other user's device that is participating in the verification flow - pub other_device_id: Option, - /// The unique ID of this verification flow, will be a random string for - /// to-device events or a event ID for in-room events. - pub flow_id: String, - /// The room ID where this verification is happening, will be `None` if the - /// verification is going through to-device messages - pub room_id: Option, - /// Did we initiate the verification flow - pub we_started: bool, - /// Did both parties aggree to verification - pub is_ready: bool, - /// Did another device respond to the verification request - pub is_passive: bool, - /// Has the verification completed successfully - pub is_done: bool, - /// Has the flow been cancelled - pub is_cancelled: bool, - /// The list of verification methods that the other side advertised as - /// supported - pub their_methods: Option>, - /// The list of verification methods that we advertised as supported - pub our_methods: Option>, - /// Information about the cancellation of the flow, will be `None` if the - /// flow hasn't been cancelled - pub cancel_info: Option, + pub(crate) inner: InnerVerificationRequest, + pub(crate) runtime: Handle, } -impl From for VerificationRequest { - fn from(v: InnerVerificationRequest) -> Self { - Self { - other_user_id: v.other_user().to_string(), - other_device_id: v.other_device_id().map(|d| d.to_string()), - flow_id: v.flow_id().as_str().to_owned(), - is_cancelled: v.is_cancelled(), - is_done: v.is_done(), - is_ready: v.is_ready(), - room_id: v.room_id().map(|r| r.to_string()), - we_started: v.we_started(), - is_passive: v.is_passive(), - cancel_info: v.cancel_info().map(|c| c.into()), - their_methods: v - .their_supported_methods() - .map(|v| v.into_iter().map(|m| m.to_string()).collect()), - our_methods: v - .our_supported_methods() - .map(|v| v.into_iter().map(|m| m.to_string()).collect()), +impl VerificationRequest { + /// The id of the other user that is participating in this verification + /// request. + pub fn other_user_id(&self) -> String { + self.inner.other_user().to_string() + } + + /// The id of the other device that is participating in this verification. + pub fn other_device_id(&self) -> Option { + self.inner.other_device_id().map(|d| d.to_string()) + } + + /// Get the unique ID of this verification request + pub fn flow_id(&self) -> String { + self.inner.flow_id().as_str().to_owned() + } + + /// Get the room id if the verification is happening inside a room. + pub fn room_id(&self) -> Option { + self.inner.room_id().map(|r| r.to_string()) + } + + /// Has the verification flow that was started with this request finished. + pub fn is_done(&self) -> bool { + self.inner.is_done() + } + + /// Is the verification request ready to start a verification flow. + pub fn is_ready(&self) -> bool { + self.inner.is_ready() + } + + /// Did we initiate the verification request + pub fn we_started(&self) -> bool { + self.inner.we_started() + } + + /// Has the verification request been answered by another device. + pub fn is_passive(&self) -> bool { + self.inner.is_passive() + } + + /// Get the supported verification methods of the other side. + /// + /// Will be present only if the other side requested the verification or if + /// we're in the ready state. + pub fn their_supported_methods(&self) -> Option> { + self.inner.their_supported_methods().map(|m| m.iter().map(|m| m.to_string()).collect()) + } + + /// Get our own supported verification methods that we advertised. + /// + /// Will be present only we requested the verification or if we're in the + /// ready state. + pub fn our_supported_methods(&self) -> Option> { + self.inner.our_supported_methods().map(|m| m.iter().map(|m| m.to_string()).collect()) + } + + /// Accept a verification requests that we share with the given user with + /// the given flow id. + /// + /// This will move the verification request into the ready state. + /// + /// # Arguments + /// + /// * `user_id` - The ID of the user for which we would like to accept the + /// verification requests. + /// + /// * `flow_id` - The ID that uniquely identifies the verification flow. + /// + /// * `methods` - A list of verification methods that we want to advertise + /// as supported. + pub fn accept(&self, methods: Vec) -> Option { + let methods = methods.into_iter().map(VerificationMethod::from).collect(); + self.inner.accept_with_methods(methods).map(|r| r.into()) + } + + /// Cancel a verification for the given user with the given flow id using + /// the given cancel code. + pub fn cancel(&self) -> Option { + self.inner.cancel().map(|r| r.into()) + } + + /// Transition from a verification request into short auth string based + /// verification. + /// + /// # Arguments + /// + /// * `user_id` - The ID of the user for which we would like to start the + /// SAS verification. + /// + /// * `flow_id` - The ID of the verification request that initiated the + /// verification flow. + pub fn start_sas_verification(&self) -> Result, CryptoStoreError> { + Ok(self.runtime.block_on(self.inner.start_sas())?.map(|(sas, r)| StartSasResult { + sas: Arc::new(Sas { inner: sas, runtime: self.runtime.clone() }), + request: r.into(), + })) + } + + /// Transition from a verification request into QR code verification. + /// + /// This method should be called when one wants to display a QR code so the + /// other side can scan it and move the QR code verification forward. + /// + /// # Arguments + /// + /// * `user_id` - The ID of the user for which we would like to start the + /// QR code verification. + /// + /// * `flow_id` - The ID of the verification request that initiated the + /// verification flow. + pub fn start_qr_verification(&self) -> Result>, CryptoStoreError> { + Ok(self + .runtime + .block_on(self.inner.generate_qr_code())? + .map(|qr| QrCode { inner: qr }.into())) + } + + /// Pass data from a scanned QR code to an active verification request and + /// transition into QR code verification. + /// + /// This requires an active `VerificationRequest` to succeed, returns `None` + /// if no `VerificationRequest` is found or if the QR code data is invalid. + /// + /// # Arguments + /// + /// * `user_id` - The ID of the user for which we would like to start the + /// QR code verification. + /// + /// * `flow_id` - The ID of the verification request that initiated the + /// verification flow. + /// + /// * `data` - The data that was extracted from the scanned QR code as an + /// base64 encoded string, without padding. + pub fn scan_qr_code(&self, data: &str) -> Option { + let data = decode_config(data, STANDARD_NO_PAD).ok()?; + let data = QrVerificationData::from_bytes(data).ok()?; + + if let Some(qr) = self.runtime.block_on(self.inner.scan_qr_code(data)).ok()? { + let request = qr.reciprocate()?; + + Some(ScanResult { qr: QrCode { inner: qr }.into(), request: request.into() }) + } else { + None } } }