From 8a2d13feea627f7b1889071a129539e00ff7ccd3 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 29 Jun 2022 13:59:52 +0200 Subject: [PATCH] feat(bindings): Session verification through FFI --- bindings/apple/debug_build_xcframework.sh | 2 +- bindings/matrix-sdk-ffi/Cargo.toml | 2 +- bindings/matrix-sdk-ffi/src/api.udl | 33 +++ bindings/matrix-sdk-ffi/src/client.rs | 49 ++++- bindings/matrix-sdk-ffi/src/lib.rs | 3 +- .../src/session_verification.rs | 193 ++++++++++++++++++ 6 files changed, 276 insertions(+), 6 deletions(-) create mode 100644 bindings/matrix-sdk-ffi/src/session_verification.rs diff --git a/bindings/apple/debug_build_xcframework.sh b/bindings/apple/debug_build_xcframework.sh index 9022c14dd..975e62cf7 100755 --- a/bindings/apple/debug_build_xcframework.sh +++ b/bindings/apple/debug_build_xcframework.sh @@ -64,7 +64,7 @@ if [ "$IS_CI" = false ] ; then echo "Preparing matrix-rust-components-swift" # Debug -> Copy generated files over to ../../../matrix-rust-components-swift - echo "$(echo "import MatrixSDKFFIWrapper\n"; cat "${SWIFT_DIR}/sdk.swift")" > "${SWIFT_DIR}/sdk.swift" + echo "$(printf "import MatrixSDKFFIWrapper\n\n"; cat "${SWIFT_DIR}/sdk.swift")" > "${SWIFT_DIR}/sdk.swift" rsync -a --delete "${GENERATED_DIR}/MatrixSDKFFI.xcframework" "${SRC_ROOT}/../matrix-rust-components-swift/" rsync -a --delete "${GENERATED_DIR}/swift/" "${SRC_ROOT}/../matrix-rust-components-swift/Sources/MatrixRustSDK" diff --git a/bindings/matrix-sdk-ffi/Cargo.toml b/bindings/matrix-sdk-ffi/Cargo.toml index fc6c3e0c6..bc2f81bb1 100644 --- a/bindings/matrix-sdk-ffi/Cargo.toml +++ b/bindings/matrix-sdk-ffi/Cargo.toml @@ -10,7 +10,7 @@ rust-version = "1.56" repository = "https://github.com/matrix-org/matrix-rust-sdk" [lib] -crate-type = ["cdylib", "staticlib"] +crate-type = ["staticlib"] [build-dependencies] diff --git a/bindings/matrix-sdk-ffi/src/api.udl b/bindings/matrix-sdk-ffi/src/api.udl index c0c42975f..5a903f5b6 100644 --- a/bindings/matrix-sdk-ffi/src/api.udl +++ b/bindings/matrix-sdk-ffi/src/api.udl @@ -65,6 +65,9 @@ interface Client { [Throws=ClientError] sequence get_media_content(MediaSource source); + + [Throws=ClientError] + SessionVerificationController get_session_verification_controller(); }; callback interface RoomDelegate { @@ -148,3 +151,33 @@ interface EmoteMessage { interface MediaSource { string url(); }; + +interface SessionVerificationEmoji { + string symbol(); + string description(); +}; + +callback interface SessionVerificationControllerDelegate { + void did_receive_verification_data(sequence data); + void did_fail(); + void did_cancel(); + void did_finish(); +}; + +interface SessionVerificationController { + void set_delegate(SessionVerificationControllerDelegate? delegate); + + boolean is_verified(); + + [Throws=ClientError] + void request_verification(); + + [Throws=ClientError] + void approve_verification(); + + [Throws=ClientError] + void decline_verification(); + + [Throws=ClientError] + void cancel_verification(); +}; diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index 132520e42..9c703416b 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -15,7 +15,10 @@ use matrix_sdk::{ }; use parking_lot::RwLock; -use super::{room::Room, ClientState, RestoreToken, RUNTIME}; +use super::{ + room::Room, session_verification::SessionVerificationController, ClientState, RestoreToken, + RUNTIME, +}; impl std::ops::Deref for Client { type Target = MatrixClient; @@ -33,6 +36,8 @@ pub struct Client { client: MatrixClient, state: Arc>, delegate: Arc>>>, + session_verification_controller: + Arc>>, } impl Client { @@ -41,6 +46,7 @@ impl Client { client, state: Arc::new(RwLock::new(state)), delegate: Arc::new(RwLock::new(None)), + session_verification_controller: Arc::new(matrix_sdk::locks::RwLock::new(None)), } } @@ -69,6 +75,7 @@ impl Client { let client = self.client.clone(); let state = self.state.clone(); let delegate = self.delegate.clone(); + let session_verification_controller = self.session_verification_controller.clone(); RUNTIME.spawn(async move { let mut filter = FilterDefinition::default(); let mut room_filter = RoomFilter::default(); @@ -84,7 +91,7 @@ impl Client { let sync_settings = SyncSettings::new().filter(Filter::FilterId(&filter_id)); client - .sync_with_callback(sync_settings, |_| async { + .sync_with_callback(sync_settings, |sync_response| async { if !state.read().has_first_synced { state.write().has_first_synced = true } @@ -96,9 +103,18 @@ impl Client { state.write().is_syncing = true; } - if let Some(ref delegate) = *delegate.read() { + if let Some(delegate) = &*delegate.read() { delegate.did_receive_sync_update() } + + if let Some(session_verification_controller) = + &*session_verification_controller.read().await + { + session_verification_controller + .process_to_device_messages(sync_response.to_device) + .await; + } + LoopCtrl::Continue }) .await; @@ -172,6 +188,33 @@ impl Client { .await?) }) } + + pub fn get_session_verification_controller( + &self, + ) -> anyhow::Result> { + RUNTIME.block_on(async move { + if let Some(session_verification_controller) = + &*self.session_verification_controller.read().await + { + return Ok(Arc::new(session_verification_controller.clone())); + } + + let user_id = self.client.user_id().expect("Failed retrieving current user_id"); + let user_identity = self + .client + .encryption() + .get_user_identity(user_id) + .await? + .expect("Failed retrieving user identity"); + + let session_verification_controller = SessionVerificationController::new(user_identity); + + *self.session_verification_controller.write().await = + Some(session_verification_controller.clone()); + + Ok(Arc::new(session_verification_controller)) + }) + } } pub fn gen_transaction_id() -> String { diff --git a/bindings/matrix-sdk-ffi/src/lib.rs b/bindings/matrix-sdk-ffi/src/lib.rs index 02b5250fb..b18dcef6e 100644 --- a/bindings/matrix-sdk-ffi/src/lib.rs +++ b/bindings/matrix-sdk-ffi/src/lib.rs @@ -7,6 +7,7 @@ pub mod client; pub mod client_builder; pub mod messages; pub mod room; +pub mod session_verification; mod uniffi_api; use client::Client; @@ -22,7 +23,7 @@ pub static RUNTIME: Lazy = pub use matrix_sdk::ruma::{api::client::account::register, UserId}; -pub use self::{backward_stream::*, client::*, messages::*, room::*}; +pub use self::{backward_stream::*, client::*, messages::*, room::*, session_verification::*}; #[derive(Default, Debug)] pub struct ClientState { diff --git a/bindings/matrix-sdk-ffi/src/session_verification.rs b/bindings/matrix-sdk-ffi/src/session_verification.rs new file mode 100644 index 000000000..ea3997567 --- /dev/null +++ b/bindings/matrix-sdk-ffi/src/session_verification.rs @@ -0,0 +1,193 @@ +use std::sync::Arc; + +use matrix_sdk::{ + encryption::{ + identities::UserIdentity, + verification::{SasVerification, VerificationRequest}, + }, + ruma::{ + api::client::sync::sync_events::v3::ToDevice, + events::{key::verification::VerificationMethod, AnyToDeviceEvent}, + }, +}; +use parking_lot::RwLock; + +use super::RUNTIME; + +pub struct SessionVerificationEmoji { + symbol: String, + description: String, +} + +impl SessionVerificationEmoji { + pub fn symbol(&self) -> String { + self.symbol.clone() + } + + pub fn description(&self) -> String { + self.description.clone() + } +} + +pub trait SessionVerificationControllerDelegate: Sync + Send { + fn did_receive_verification_data(&self, data: Vec>); + fn did_fail(&self); + fn did_cancel(&self); + fn did_finish(&self); +} + +#[derive(Clone)] +pub struct SessionVerificationController { + user_identity: UserIdentity, + delegate: Arc>>>, + verification_request: Arc>>, + sas_verification: Arc>>, +} + +impl SessionVerificationController { + pub fn new(user_identity: UserIdentity) -> Self { + SessionVerificationController { + user_identity, + delegate: Arc::new(RwLock::new(None)), + verification_request: Arc::new(RwLock::new(None)), + sas_verification: Arc::new(RwLock::new(None)), + } + } + + pub fn set_delegate(&self, delegate: Option>) { + *self.delegate.write() = delegate; + } + + pub fn is_verified(&self) -> bool { + self.user_identity.verified() + } + + pub fn request_verification(&self) -> anyhow::Result<()> { + RUNTIME.block_on(async move { + let methods = vec![VerificationMethod::SasV1]; + let verification_request = + self.user_identity.request_verification_with_methods(methods).await?; + *self.verification_request.write() = Some(verification_request); + + Ok(()) + }) + } + + pub fn approve_verification(&self) -> anyhow::Result<()> { + RUNTIME.block_on(async move { + let sas_verification = self.sas_verification.read().clone(); + if let Some(sas_verification) = sas_verification { + sas_verification.confirm().await?; + } + + Ok(()) + }) + } + + pub fn decline_verification(&self) -> anyhow::Result<()> { + RUNTIME.block_on(async move { + let sas_verification = self.sas_verification.read().clone(); + if let Some(sas_verification) = sas_verification { + sas_verification.mismatch().await?; + } + + Ok(()) + }) + } + + pub fn cancel_verification(&self) -> anyhow::Result<()> { + RUNTIME.block_on(async move { + let verification_request = self.verification_request.read().clone(); + if let Some(verification) = verification_request { + verification.cancel().await?; + } + + Ok(()) + }) + } + + pub async fn process_to_device_messages(&self, to_device: ToDevice) { + let sas_verification = self.sas_verification.clone(); + + for event in to_device.events.into_iter().filter_map(|e| e.deserialize().ok()) { + match event { + AnyToDeviceEvent::KeyVerificationReady(event) => { + if !self.is_transaction_id_valid(event.content.transaction_id.to_string()) { + return; + } + self.start_sas_verification().await; + } + AnyToDeviceEvent::KeyVerificationCancel(event) => { + if !self.is_transaction_id_valid(event.content.transaction_id.to_string()) { + return; + } + + if let Some(delegate) = &*self.delegate.read() { + delegate.did_cancel() + } + } + AnyToDeviceEvent::KeyVerificationKey(event) => { + if !self.is_transaction_id_valid(event.content.transaction_id.to_string()) { + return; + } + + if let Some(sas_verification) = &*sas_verification.read() { + if let Some(emojis) = sas_verification.emoji() { + if let Some(delegate) = &*self.delegate.read() { + let emojis = emojis + .iter() + .map(|e| { + Arc::new(SessionVerificationEmoji { + symbol: e.symbol.to_owned(), + description: e.description.to_owned(), + }) + }) + .collect::>(); + + delegate.did_receive_verification_data(emojis); + } + } else if let Some(delegate) = &*self.delegate.read() { + delegate.did_fail() + } + } else if let Some(delegate) = &*self.delegate.read() { + delegate.did_fail() + } + } + AnyToDeviceEvent::KeyVerificationDone(event) => { + if !self.is_transaction_id_valid(event.content.transaction_id.to_string()) { + return; + } + + if let Some(delegate) = &*self.delegate.read() { + delegate.did_finish() + } + } + _ => (), + } + } + } + + fn is_transaction_id_valid(&self, transaction_id: String) -> bool { + if let Some(verification) = &*self.verification_request.read() { + return verification.flow_id() == transaction_id; + } + + false + } + + async fn start_sas_verification(&self) { + let verification_request = self.verification_request.read().clone(); + if let Some(verification) = verification_request { + match verification.start_sas().await { + Ok(verification) => { + *self.sas_verification.write() = verification; + } + Err(_) => { + if let Some(delegate) = &*self.delegate.read() { + delegate.did_fail() + } + } + } + } + } +}