diff --git a/bindings/matrix-sdk-crypto-ffi/src/verification.rs b/bindings/matrix-sdk-crypto-ffi/src/verification.rs index e8c31c4a3a8..c522a1ffa16 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/verification.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/verification.rs @@ -752,7 +752,7 @@ impl VerificationRequest { RustVerificationRequestState::Ready { their_methods, our_methods, - other_device_id: _, + other_device_data: _, } => VerificationRequestState::Ready { their_methods: their_methods.iter().map(|m| m.to_string()).collect(), our_methods: our_methods.iter().map(|m| m.to_string()).collect(), diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index f5f693bae21..6cf78b28ae5 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -213,10 +213,10 @@ impl Client { let session_verification_controller: Arc< tokio::sync::RwLock>, > = Default::default(); - let ctrl = session_verification_controller.clone(); + let controller = session_verification_controller.clone(); sdk_client.add_event_handler(move |ev: AnyToDeviceEvent| async move { - if let Some(session_verification_controller) = &*ctrl.clone().read().await { + if let Some(session_verification_controller) = &*controller.clone().read().await { session_verification_controller.process_to_device_message(ev).await; } else { debug!("received to-device message, but verification controller isn't ready"); diff --git a/bindings/matrix-sdk-ffi/src/session_verification.rs b/bindings/matrix-sdk-ffi/src/session_verification.rs index 655e92d7e5f..6cc4dea8b76 100644 --- a/bindings/matrix-sdk-ffi/src/session_verification.rs +++ b/bindings/matrix-sdk-ffi/src/session_verification.rs @@ -1,15 +1,16 @@ use std::sync::{Arc, RwLock}; -use anyhow::Context as _; use futures_util::StreamExt; use matrix_sdk::{ encryption::{ identities::UserIdentity, - verification::{SasState, SasVerification, VerificationRequest}, + verification::{SasState, SasVerification, VerificationRequest, VerificationRequestState}, Encryption, }, ruma::events::{key::verification::VerificationMethod, AnyToDeviceEvent}, }; +use ruma::UserId; +use tracing::{error, info}; use super::RUNTIME; use crate::error::ClientError; @@ -37,8 +38,20 @@ pub enum SessionVerificationData { Decimals { values: Vec }, } +/// Details about the incoming verification request +#[derive(Debug, uniffi::Record)] +pub struct SessionVerificationRequestDetails { + sender_id: String, + flow_id: String, + device_id: String, + display_name: Option, + /// First time this device was seen in milliseconds since epoch. + first_seen_timestamp: u64, +} + #[matrix_sdk_ffi_macros::export(callback_interface)] pub trait SessionVerificationControllerDelegate: Sync + Send { + fn did_receive_verification_request(&self, details: SessionVerificationRequestDetails); fn did_accept_verification_request(&self); fn did_start_sas_verification(&self); fn did_receive_verification_data(&self, data: SessionVerificationData); @@ -60,17 +73,51 @@ pub struct SessionVerificationController { #[matrix_sdk_ffi_macros::export] impl SessionVerificationController { - pub async fn is_verified(&self) -> Result { - let device = - self.encryption.get_own_device().await?.context("Our own device is missing")?; + pub fn set_delegate(&self, delegate: Option>) { + *self.delegate.write().unwrap() = delegate; + } + + /// Set this particular request as the currently active one and register for + /// events pertaining it. + /// * `sender_id` - The user requesting verification. + /// * `flow_id` - - The ID that uniquely identifies the verification flow. + pub async fn acknowledge_verification_request( + &self, + sender_id: String, + flow_id: String, + ) -> Result<(), ClientError> { + let sender_id = UserId::parse(sender_id.clone())?; + + let verification_request = self + .encryption + .get_verification_request(&sender_id, flow_id) + .await + .ok_or(ClientError::new("Unknown session verification request"))?; + + *self.verification_request.write().unwrap() = Some(verification_request.clone()); - Ok(device.is_cross_signed_by_owner()) + RUNTIME.spawn(Self::listen_to_verification_request_changes( + verification_request, + self.sas_verification.clone(), + self.delegate.clone(), + )); + + Ok(()) } - pub fn set_delegate(&self, delegate: Option>) { - *self.delegate.write().unwrap() = delegate; + /// Accept the previously acknowledged verification request + pub async fn accept_verification_request(&self) -> Result<(), ClientError> { + let verification_request = self.verification_request.read().unwrap().clone(); + + if let Some(verification_request) = verification_request { + let methods = vec![VerificationMethod::SasV1]; + verification_request.accept_with_methods(methods).await?; + } + + Ok(()) } + /// Request verification for the current device pub async fn request_verification(&self) -> Result<(), ClientError> { let methods = vec![VerificationMethod::SasV1]; let verification_request = self @@ -78,30 +125,41 @@ impl SessionVerificationController { .request_verification_with_methods(methods) .await .map_err(anyhow::Error::from)?; - *self.verification_request.write().unwrap() = Some(verification_request); + + *self.verification_request.write().unwrap() = Some(verification_request.clone()); + + RUNTIME.spawn(Self::listen_to_verification_request_changes( + verification_request, + self.sas_verification.clone(), + self.delegate.clone(), + )); Ok(()) } + /// Transition the current verification request into a SAS verification + /// flow. pub async fn start_sas_verification(&self) -> Result<(), ClientError> { let verification_request = self.verification_request.read().unwrap().clone(); - if let Some(verification) = verification_request { - match verification.start_sas().await { - Ok(Some(verification)) => { - *self.sas_verification.write().unwrap() = Some(verification.clone()); + let Some(verification_request) = verification_request else { + return Err(ClientError::new("Verification request missing.")); + }; - if let Some(delegate) = &*self.delegate.read().unwrap() { - delegate.did_start_sas_verification() - } + match verification_request.start_sas().await { + Ok(Some(verification)) => { + *self.sas_verification.write().unwrap() = Some(verification.clone()); - let delegate = self.delegate.clone(); - RUNTIME.spawn(Self::listen_to_changes(delegate, verification)); + if let Some(delegate) = &*self.delegate.read().unwrap() { + delegate.did_start_sas_verification() } - _ => { - if let Some(delegate) = &*self.delegate.read().unwrap() { - delegate.did_fail() - } + + let delegate = self.delegate.clone(); + RUNTIME.spawn(Self::listen_to_sas_verification_changes(verification, delegate)); + } + _ => { + if let Some(delegate) = &*self.delegate.read().unwrap() { + delegate.did_fail() } } } @@ -109,31 +167,37 @@ impl SessionVerificationController { Ok(()) } + /// Confirm that the short auth strings match on both sides. pub async fn approve_verification(&self) -> Result<(), ClientError> { let sas_verification = self.sas_verification.read().unwrap().clone(); - if let Some(sas_verification) = sas_verification { - sas_verification.confirm().await?; - } - Ok(()) + let Some(sas_verification) = sas_verification else { + return Err(ClientError::new("SAS verification missing")); + }; + + Ok(sas_verification.confirm().await?) } + /// Reject the short auth string pub async fn decline_verification(&self) -> Result<(), ClientError> { let sas_verification = self.sas_verification.read().unwrap().clone(); - if let Some(sas_verification) = sas_verification { - sas_verification.mismatch().await?; - } - Ok(()) + let Some(sas_verification) = sas_verification else { + return Err(ClientError::new("SAS verification missing")); + }; + + Ok(sas_verification.mismatch().await?) } + /// Cancel the current verification request pub async fn cancel_verification(&self) -> Result<(), ClientError> { let verification_request = self.verification_request.read().unwrap().clone(); - if let Some(verification) = verification_request { - verification.cancel().await?; - } - Ok(()) + let Some(verification_request) = verification_request else { + return Err(ClientError::new("Verification request missing.")); + }; + + Ok(verification_request.cancel().await?) } } @@ -149,58 +213,88 @@ impl SessionVerificationController { } pub(crate) async fn process_to_device_message(&self, event: AnyToDeviceEvent) { - match event { - // TODO: Use the changes stream for this as well once we expose - // VerificationRequest::changes() in the main crate. - AnyToDeviceEvent::KeyVerificationStart(event) => { - if !self.is_transaction_id_valid(event.content.transaction_id.to_string()) { - return; - } - if let Some(verification) = self - .encryption - .get_verification( - self.user_identity.user_id(), - event.content.transaction_id.as_str(), - ) - .await - { - if let Some(sas_verification) = verification.sas() { - *self.sas_verification.write().unwrap() = Some(sas_verification.clone()); - - if sas_verification.accept().await.is_ok() { - if let Some(delegate) = &*self.delegate.read().unwrap() { - delegate.did_start_sas_verification() - } - - let delegate = self.delegate.clone(); - RUNTIME.spawn(Self::listen_to_changes(delegate, sas_verification)); - } else if let Some(delegate) = &*self.delegate.read().unwrap() { - delegate.did_fail() - } - } - } + if let AnyToDeviceEvent::KeyVerificationRequest(event) = event { + info!("Received verification request: {:}", event.sender); + + let Some(request) = self + .encryption + .get_verification_request(&event.sender, &event.content.transaction_id) + .await + else { + error!("Failed retrieving verification request"); + return; + }; + + if !request.is_self_verification() { + info!("Received non-self verification request. Ignoring."); + return; } - AnyToDeviceEvent::KeyVerificationReady(event) => { - if !self.is_transaction_id_valid(event.content.transaction_id.to_string()) { - return; - } - if let Some(delegate) = &*self.delegate.read().unwrap() { - delegate.did_accept_verification_request() - } + let VerificationRequestState::Requested { other_device_data, .. } = request.state() + else { + error!("Received key verification event but the request is in the wrong state."); + return; + }; + + if let Some(delegate) = &*self.delegate.read().unwrap() { + delegate.did_receive_verification_request(SessionVerificationRequestDetails { + sender_id: request.other_user_id().into(), + flow_id: request.flow_id().into(), + device_id: other_device_data.device_id().into(), + display_name: other_device_data.display_name().map(str::to_string), + first_seen_timestamp: other_device_data.first_time_seen_ts().get().into(), + }); } - _ => (), } } - fn is_transaction_id_valid(&self, transaction_id: String) -> bool { - match &*self.verification_request.read().unwrap() { - Some(verification) => verification.flow_id() == transaction_id, - None => false, + async fn listen_to_verification_request_changes( + verification_request: VerificationRequest, + sas_verification: Arc>>, + delegate: Delegate, + ) { + let mut stream = verification_request.changes(); + + while let Some(state) = stream.next().await { + match state { + VerificationRequestState::Transitioned { verification } => { + let Some(verification) = verification.sas() else { + error!("Invalid, non-sas verification flow. Returning."); + return; + }; + + *sas_verification.write().unwrap() = Some(verification.clone()); + + if verification.accept().await.is_ok() { + if let Some(delegate) = &*delegate.read().unwrap() { + delegate.did_start_sas_verification() + } + + let delegate = delegate.clone(); + RUNTIME.spawn(Self::listen_to_sas_verification_changes( + verification, + delegate, + )); + } else if let Some(delegate) = &*delegate.read().unwrap() { + delegate.did_fail() + } + } + VerificationRequestState::Ready { .. } => { + if let Some(delegate) = &*delegate.read().unwrap() { + delegate.did_accept_verification_request() + } + } + VerificationRequestState::Cancelled(..) => { + if let Some(delegate) = &*delegate.read().unwrap() { + delegate.did_cancel(); + } + } + _ => {} + } } } - async fn listen_to_changes(delegate: Delegate, sas: SasVerification) { + async fn listen_to_sas_verification_changes(sas: SasVerification, delegate: Delegate) { let mut stream = sas.changes(); while let Some(state) = stream.next().await { diff --git a/crates/matrix-sdk-crypto/src/verification/machine.rs b/crates/matrix-sdk-crypto/src/verification/machine.rs index c82d3b7867f..892fa255183 100644 --- a/crates/matrix-sdk-crypto/src/verification/machine.rs +++ b/crates/matrix-sdk-crypto/src/verification/machine.rs @@ -341,6 +341,7 @@ impl VerificationMachine { match &content { AnyVerificationContent::Request(r) => { info!( + sender = ?event.sender(), from_device = r.from_device().as_str(), "Received a new verification request", ); @@ -370,12 +371,20 @@ impl VerificationMachine { return Ok(()); } + let Some(device_data) = + self.store.get_device(event.sender(), r.from_device()).await? + else { + warn!("Could not retrieve the device data for the incoming verification request, ignoring it"); + return Ok(()); + }; + let request = VerificationRequest::from_request( self.verifications.clone(), self.store.clone(), event.sender(), flow_id, r, + device_data, ); self.insert_request(request); @@ -403,7 +412,13 @@ impl VerificationMachine { }; if request.flow_id() == &flow_id { - request.receive_ready(event.sender(), c); + if let Some(device_data) = + self.store.get_device(event.sender(), c.from_device()).await? + { + request.receive_ready(event.sender(), c, device_data); + } else { + warn!("Could not retrieve the data for the accepting device, ignoring it"); + } } else { flow_id_mismatch(); } @@ -725,12 +740,12 @@ mod tests { let content: OutgoingContent = request.try_into().unwrap(); machine - .receive_any_event(&wrap_any_to_device_content(bob_request.other_user(), content)) + .receive_any_event(&wrap_any_to_device_content(bob_request.own_user_id(), content)) .await .unwrap(); let alice_request = - machine.get_request(bob_request.other_user(), bob_request.flow_id().as_str()).unwrap(); + machine.get_request(bob_request.own_user_id(), bob_request.flow_id().as_str()).unwrap(); // We're not yet cancelled. assert!(!alice_request.is_cancelled()); @@ -749,12 +764,12 @@ mod tests { let content: OutgoingContent = request.try_into().unwrap(); machine - .receive_any_event(&wrap_any_to_device_content(bob_request.other_user(), content)) + .receive_any_event(&wrap_any_to_device_content(bob_request.own_user_id(), content)) .await .unwrap(); let second_request = - machine.get_request(bob_request.other_user(), bob_request.flow_id().as_str()).unwrap(); + machine.get_request(bob_request.own_user_id(), bob_request.flow_id().as_str()).unwrap(); // Make sure we fetched the new one. assert_eq!(second_request.flow_id().as_str(), second_transaction_id); @@ -787,12 +802,12 @@ mod tests { let content: OutgoingContent = request.try_into().unwrap(); machine - .receive_any_event(&wrap_any_to_device_content(bob_request.other_user(), content)) + .receive_any_event(&wrap_any_to_device_content(bob_request.own_user_id(), content)) .await .unwrap(); let first_request = - machine.get_request(bob_request.other_user(), bob_request.flow_id().as_str()).unwrap(); + machine.get_request(bob_request.own_user_id(), bob_request.flow_id().as_str()).unwrap(); // We're not yet cancelled. assert!(!first_request.is_cancelled()); @@ -811,12 +826,12 @@ mod tests { let content: OutgoingContent = request.try_into().unwrap(); machine - .receive_any_event(&wrap_any_to_device_content(bob_request.other_user(), content)) + .receive_any_event(&wrap_any_to_device_content(bob_request.own_user_id(), content)) .await .unwrap(); let second_request = - machine.get_request(bob_request.other_user(), bob_request.flow_id().as_str()).unwrap(); + machine.get_request(bob_request.own_user_id(), bob_request.flow_id().as_str()).unwrap(); // None of the requests are cancelled assert!(!first_request.is_cancelled()); diff --git a/crates/matrix-sdk-crypto/src/verification/mod.rs b/crates/matrix-sdk-crypto/src/verification/mod.rs index 6d48d38e684..1598114fb06 100644 --- a/crates/matrix-sdk-crypto/src/verification/mod.rs +++ b/crates/matrix-sdk-crypto/src/verification/mod.rs @@ -819,7 +819,7 @@ pub(crate) mod tests { } pub fn bob_device_id() -> &'static DeviceId { - device_id!("BOBDEVCIE") + device_id!("BOBDEVICE") } pub(crate) async fn setup_stores() -> (Account, VerificationStore, Account, VerificationStore) { diff --git a/crates/matrix-sdk-crypto/src/verification/requests.rs b/crates/matrix-sdk-crypto/src/verification/requests.rs index 94c2b599d59..3815ff49e5c 100644 --- a/crates/matrix-sdk-crypto/src/verification/requests.rs +++ b/crates/matrix-sdk-crypto/src/verification/requests.rs @@ -52,8 +52,8 @@ use super::{ CancelInfo, Cancelled, FlowId, Verification, VerificationStore, }; use crate::{ - olm::StaticAccountData, CryptoStoreError, OutgoingVerificationRequest, RoomMessageRequest, Sas, - ToDeviceRequest, + olm::StaticAccountData, CryptoStoreError, DeviceData, OutgoingVerificationRequest, + RoomMessageRequest, Sas, ToDeviceRequest, }; const SUPPORTED_METHODS: &[VerificationMethod] = &[ @@ -78,9 +78,9 @@ pub enum VerificationRequestState { /// The verification methods supported by the sender. their_methods: Vec, - /// The device ID of the device that responded to the verification + /// The device data of the device that responded to the verification /// request. - other_device_id: OwnedDeviceId, + other_device_data: DeviceData, }, /// The verification request is ready to start a verification flow. Ready { @@ -90,9 +90,9 @@ pub enum VerificationRequestState { /// The verification methods supported by the us. our_methods: Vec, - /// The device ID of the device that responded to the verification + /// The device data of the device that responded to the verification /// request. - other_device_id: OwnedDeviceId, + other_device_data: DeviceData, }, /// The verification request has transitioned into a concrete verification /// flow. For example it transitioned into the emoji based SAS @@ -101,6 +101,10 @@ pub enum VerificationRequestState { /// The concrete [`Verification`] object the verification request /// transitioned into. verification: Verification, + + /// The device data of the device that responded to the verification + /// request. + other_device_data: DeviceData, }, /// The verification flow that was started with this request has finished. Done, @@ -116,16 +120,17 @@ impl From<&InnerRequest> for VerificationRequestState { } InnerRequest::Requested(s) => Self::Requested { their_methods: s.state.their_methods.to_owned(), - other_device_id: s.state.other_device_id.to_owned(), + other_device_data: s.state.other_device_data.to_owned(), }, InnerRequest::Ready(s) => Self::Ready { their_methods: s.state.their_methods.to_owned(), our_methods: s.state.our_methods.to_owned(), - other_device_id: s.state.other_device_id.to_owned(), + other_device_data: s.state.other_device_data.to_owned(), + }, + InnerRequest::Transitioned(s) => Self::Transitioned { + verification: s.state.verification.to_owned(), + other_device_data: s.state.other_device_data.to_owned(), }, - InnerRequest::Transitioned(s) => { - Self::Transitioned { verification: s.state.verification.to_owned() } - } InnerRequest::Passive(_) => { Self::Cancelled(Cancelled::new(true, CancelCode::Accepted).into()) } @@ -281,9 +286,11 @@ impl VerificationRequest { /// The id of the other device that is participating in this verification. pub fn other_device_id(&self) -> Option { match &*self.inner.read() { - InnerRequest::Requested(r) => Some(r.state.other_device_id.to_owned()), - InnerRequest::Ready(r) => Some(r.state.other_device_id.to_owned()), - InnerRequest::Transitioned(r) => Some(r.state.ready.other_device_id.to_owned()), + InnerRequest::Requested(r) => Some(r.state.other_device_data.device_id().to_owned()), + InnerRequest::Ready(r) => Some(r.state.other_device_data.device_id().to_owned()), + InnerRequest::Transitioned(r) => { + Some(r.state.ready.other_device_data.device_id().to_owned()) + } InnerRequest::Created(_) | InnerRequest::Passive(_) | InnerRequest::Done(_) @@ -466,13 +473,21 @@ impl VerificationRequest { sender: &UserId, flow_id: FlowId, content: &RequestContent<'_>, + device_data: DeviceData, ) -> Self { let account = store.account.clone(); Self { verification_cache: cache.clone(), inner: SharedObservable::new(InnerRequest::Requested( - RequestState::from_request_event(cache, store, sender, &flow_id, content), + RequestState::from_request_event( + cache, + store, + sender, + &flow_id, + content, + device_data, + ), )), account, other_user_id: sender.into(), @@ -658,12 +673,18 @@ impl VerificationRequest { Some(ToDeviceRequest::for_recipients(recipient, recip_devices, &c, TransactionId::new())) } - pub(crate) fn receive_ready(&self, sender: &UserId, content: &ReadyContent<'_>) { + pub(crate) fn receive_ready( + &self, + sender: &UserId, + content: &ReadyContent<'_>, + from_device_data: DeviceData, + ) { let mut guard = self.inner.write(); match &*guard { InnerRequest::Created(s) => { - let new_value = InnerRequest::Ready(s.clone().into_ready(sender, content)); + let new_value = + InnerRequest::Ready(s.clone().into_ready(sender, content, from_device_data)); ObservableWriteGuard::set(&mut guard, new_value); if let Some(request) = @@ -889,14 +910,14 @@ impl InnerRequest { match self { InnerRequest::Created(_) => DeviceIdOrAllDevices::AllDevices, InnerRequest::Requested(r) => { - DeviceIdOrAllDevices::DeviceId(r.state.other_device_id.to_owned()) + DeviceIdOrAllDevices::DeviceId(r.state.other_device_data.device_id().to_owned()) } InnerRequest::Ready(r) => { - DeviceIdOrAllDevices::DeviceId(r.state.other_device_id.to_owned()) - } - InnerRequest::Transitioned(r) => { - DeviceIdOrAllDevices::DeviceId(r.state.ready.other_device_id.to_owned()) + DeviceIdOrAllDevices::DeviceId(r.state.other_device_data.device_id().to_owned()) } + InnerRequest::Transitioned(r) => DeviceIdOrAllDevices::DeviceId( + r.state.ready.other_device_data.device_id().to_owned(), + ), InnerRequest::Passive(_) => DeviceIdOrAllDevices::AllDevices, InnerRequest::Done(_) => DeviceIdOrAllDevices::AllDevices, InnerRequest::Cancelled(_) => DeviceIdOrAllDevices::AllDevices, @@ -1034,7 +1055,12 @@ impl RequestState { } } - fn into_ready(self, _sender: &UserId, content: &ReadyContent<'_>) -> RequestState { + fn into_ready( + self, + _sender: &UserId, + content: &ReadyContent<'_>, + from_device_data: DeviceData, + ) -> RequestState { // TODO check the flow id, and that the methods match what we suggested. RequestState { flow_id: self.flow_id, @@ -1044,7 +1070,7 @@ impl RequestState { state: Ready { their_methods: content.methods().to_owned(), our_methods: self.state.our_methods, - other_device_id: content.from_device().into(), + other_device_data: from_device_data, }, } } @@ -1061,8 +1087,9 @@ struct Requested { /// The verification methods supported by the sender. pub their_methods: Vec, - /// The device ID of the device that responded to the verification request. - pub other_device_id: OwnedDeviceId, + /// The device data of the device that responded to the verification + /// request. + pub other_device_data: DeviceData, } impl RequestState { @@ -1072,6 +1099,7 @@ impl RequestState { sender: &UserId, flow_id: &FlowId, content: &RequestContent<'_>, + device_data: DeviceData, ) -> RequestState { // TODO only create this if we support the methods RequestState { @@ -1081,7 +1109,7 @@ impl RequestState { other_user_id: sender.to_owned(), state: Requested { their_methods: content.methods().to_owned(), - other_device_id: content.from_device().into(), + other_device_data: device_data, }, } } @@ -1105,7 +1133,7 @@ impl RequestState { state: Ready { their_methods: self.state.their_methods, our_methods: methods.clone(), - other_device_id: self.state.other_device_id.clone(), + other_device_data: self.state.other_device_data, }, }; @@ -1143,8 +1171,9 @@ struct Ready { /// The verification methods supported by the us. pub our_methods: Vec, - /// The device ID of the device that responded to the verification request. - pub other_device_id: OwnedDeviceId, + /// The device data of the device that responded to the verification + /// request. + pub other_device_data: DeviceData, } #[cfg(feature = "qrcode")] @@ -1158,7 +1187,7 @@ async fn scan_qr_code( let verification = QrVerification::from_scan( request_state.store.to_owned(), request_state.other_user_id.to_owned(), - state.other_device_id.to_owned(), + state.other_device_data.device_id().to_owned(), request_state.flow_id.as_ref().to_owned(), data, we_started, @@ -1174,6 +1203,7 @@ async fn scan_qr_code( state: Transitioned { ready: state.to_owned(), verification: verification.to_owned().into(), + other_device_data: state.other_device_data.to_owned(), }, }; @@ -1197,21 +1227,7 @@ async fn generate_qr_code( return Ok(None); } - let Some(device) = request_state - .store - .get_device(&request_state.other_user_id, &state.other_device_id) - .await? - else { - warn!( - user_id = ?request_state.other_user_id, - device_id = ?state.other_device_id, - "Can't create a QR code, the device that accepted the \ - verification doesn't exist" - ); - return Ok(None); - }; - - let identities = request_state.store.get_identities(device).await?; + let identities = request_state.store.get_identities(state.other_device_data.clone()).await?; let verification = if let Some(identity) = &identities.identity_being_verified { match &identity { @@ -1230,7 +1246,7 @@ async fn generate_qr_code( } else { warn!( user_id = ?request_state.other_user_id, - device_id = ?state.other_device_id, + device_id = ?state.other_device_data.device_id(), "Can't create a QR code, the other device \ doesn't have a valid device key" ); @@ -1249,7 +1265,7 @@ async fn generate_qr_code( } else { warn!( user_id = ?request_state.other_user_id, - device_id = ?state.other_device_id, + device_id = ?state.other_device_data.device_id(), "Can't create a QR code, our cross signing identity \ doesn't contain a valid master key" ); @@ -1278,7 +1294,7 @@ async fn generate_qr_code( } else { warn!( user_id = ?request_state.other_user_id, - device_id = ?state.other_device_id, + device_id = ?state.other_device_data.device_id(), "Can't create a QR code, we don't trust our own \ master key" ); @@ -1287,7 +1303,7 @@ async fn generate_qr_code( } else { warn!( user_id = ?request_state.other_user_id, - device_id = ?state.other_device_id, + device_id = ?state.other_device_data.device_id(), "Can't create a QR code, the user's identity \ doesn't have a valid master key" ); @@ -1298,7 +1314,7 @@ async fn generate_qr_code( } else { warn!( user_id = ?request_state.other_user_id, - device_id = ?state.other_device_id, + device_id = ?state.other_device_data.device_id(), "Can't create a QR code, the user doesn't have a valid cross \ signing identity." ); @@ -1315,6 +1331,7 @@ async fn generate_qr_code( state: Transitioned { ready: state.to_owned(), verification: verification.to_owned().into(), + other_device_data: state.other_device_data.to_owned(), }, }; @@ -1341,17 +1358,8 @@ async fn receive_start( "Received a new verification start event", ); - let Some(device) = request_state.store.get_device(sender, content.from_device()).await? else { - warn!( - ?sender, - device = ?content.from_device(), - "Received a key verification start event from an unknown device", - ); - - return Ok(None); - }; - - let identities = request_state.store.get_identities(device.clone()).await?; + let other_device_data = state.other_device_data.clone(); + let identities = request_state.store.get_identities(other_device_data.clone()).await?; let own_user_id = &request_state.store.account.user_id; let own_device_id = &request_state.store.account.device_id; @@ -1375,7 +1383,10 @@ async fn receive_start( // we're the lexicographically smaller user ID (or device ID if equal). use std::cmp::Ordering; if !matches!( - (sender.cmp(own_user_id), device.device_id().cmp(own_device_id)), + ( + sender.cmp(own_user_id), + other_device_data.device_id().cmp(own_device_id) + ), (Ordering::Greater, _) | (Ordering::Equal, Ordering::Greater) ) { info!("Started a new SAS verification, replacing an already started one."); @@ -1414,14 +1425,14 @@ async fn receive_start( } Err(c) => { warn!( - user_id = ?device.user_id(), - device_id = ?device.device_id(), + user_id = ?other_device_data.user_id(), + device_id = ?other_device_data.device_id(), content = ?c, "Can't start key verification, canceling.", ); request_state.verification_cache.queue_up_content( - device.user_id(), - device.device_id(), + other_device_data.user_id(), + other_device_data.device_id(), c, None, ); @@ -1468,30 +1479,18 @@ async fn start_sas( return Ok(None); } - // TODO signal why starting the sas flow doesn't work? - let Some(device) = request_state - .store - .get_device(&request_state.other_user_id, &state.other_device_id) - .await? - else { - warn!( - user_id = ?request_state.other_user_id, - device_id = ?state.other_device_id, - "Can't start the SAS verification flow, the device that \ - accepted the verification doesn't exist" - ); - return Ok(None); - }; - - let identities = request_state.store.get_identities(device).await?; + let identities = request_state.store.get_identities(state.other_device_data.clone()).await?; let (state, sas, content) = match request_state.flow_id.as_ref() { FlowId::ToDevice(t) => { let (sas, content) = Sas::start(identities, t.to_owned(), we_started, Some(request_handle), None); - let state = - Transitioned { ready: state.to_owned(), verification: sas.to_owned().into() }; + let state = Transitioned { + ready: state.to_owned(), + verification: sas.to_owned().into(), + other_device_data: state.other_device_data.to_owned(), + }; (state, sas, content) } @@ -1503,8 +1502,11 @@ async fn start_sas( we_started, request_handle, ); - let state = - Transitioned { ready: state.to_owned(), verification: sas.to_owned().into() }; + let state = Transitioned { + ready: state.to_owned(), + verification: sas.to_owned().into(), + other_device_data: state.other_device_data.to_owned(), + }; (state, sas, content) } }; @@ -1560,7 +1562,11 @@ impl Ready { store: request_state.store.to_owned(), flow_id: request_state.flow_id.to_owned(), other_user_id: request_state.other_user_id.to_owned(), - state: Transitioned { ready: self.clone(), verification }, + state: Transitioned { + ready: self.clone(), + verification, + other_device_data: self.other_device_data.clone(), + }, } } } @@ -1569,6 +1575,7 @@ impl Ready { struct Transitioned { ready: Ready, verification: Verification, + other_device_data: DeviceData, } impl RequestState { @@ -1643,7 +1650,10 @@ mod tests { let event_id = event_id!("$1234localhost").to_owned(); let room_id = room_id!("!test:localhost").to_owned(); - let (_alice, alice_store, _bob, bob_store) = setup_stores().await; + let (alice, alice_store, bob, bob_store) = setup_stores().await; + + let alice_device_data = DeviceData::from_account(&alice); + let bob_device_data = DeviceData::from_account(&bob); let content = VerificationRequest::request( &bob_store.account.user_id, @@ -1674,6 +1684,7 @@ mod tests { bob_id(), flow_id, &(&content).into(), + bob_device_data, ); assert_matches!(alice_request.state(), VerificationRequestState::Requested { .. }); @@ -1681,7 +1692,7 @@ mod tests { let content: OutgoingContent = alice_request.accept().unwrap().try_into().unwrap(); let content = ReadyContent::try_from(&content).unwrap(); - bob_request.receive_ready(alice_id(), &content); + bob_request.receive_ready(alice_id(), &content, alice_device_data); assert_matches!(bob_request.state(), VerificationRequestState::Ready { .. }); assert_matches!(alice_request.state(), VerificationRequestState::Ready { .. }); @@ -1698,7 +1709,7 @@ mod tests { // Set up the pair of verification requests let bob_request = build_test_request(&bob_store, alice_id(), None); - let alice_request = build_incoming_verification_request(&alice_store, &bob_request); + let alice_request = build_incoming_verification_request(&alice_store, &bob_request).await; let outgoing_request = alice_request.cancel().unwrap(); @@ -1731,8 +1742,10 @@ mod tests { let event_id = event_id!("$1234localhost"); let room_id = room_id!("!test:localhost"); - let (_alice, alice_store, bob, bob_store) = setup_stores().await; - let bob_device = DeviceData::from_account(&bob); + let (alice, alice_store, bob, bob_store) = setup_stores().await; + + let alice_device_data = DeviceData::from_account(&alice); + let bob_device_data = DeviceData::from_account(&bob); let content = VerificationRequest::request( &bob_store.account.user_id, @@ -1740,6 +1753,7 @@ mod tests { alice_id(), None, ); + let flow_id = FlowId::from((room_id, event_id)); let bob_request = VerificationRequest::new( @@ -1758,60 +1772,81 @@ mod tests { bob_id(), flow_id, &(&content).into(), + bob_device_data.clone(), ); - do_accept_request(&alice_request, &bob_request, None); + do_accept_request(&alice_request, alice_device_data.clone(), &bob_request, None); let (bob_sas, request) = bob_request.start_sas().await.unwrap().unwrap(); let content: OutgoingContent = request.try_into().unwrap(); let content = StartContent::try_from(&content).unwrap(); let flow_id = content.flow_id().to_owned(); - alice_request.receive_start(bob_device.user_id(), &content).await.unwrap(); + alice_request.receive_start(bob_device_data.user_id(), &content).await.unwrap(); let alice_sas = - alice_request.verification_cache.get_sas(bob_device.user_id(), &flow_id).unwrap(); + alice_request.verification_cache.get_sas(bob_device_data.user_id(), &flow_id).unwrap(); - assert_matches!( - alice_request.state(), - VerificationRequestState::Transitioned { verification: Verification::SasV1(_) } + assert_let!( + VerificationRequestState::Transitioned { + verification: Verification::SasV1(_), + other_device_data + } = alice_request.state() ); - assert_matches!( - bob_request.state(), - VerificationRequestState::Transitioned { verification: Verification::SasV1(_) } + + assert_eq!(bob_device_data, other_device_data); + + assert_let!( + VerificationRequestState::Transitioned { + verification: Verification::SasV1(_), + other_device_data + } = bob_request.state() ); + assert_eq!(alice_device_data, other_device_data); + assert!(!bob_sas.is_cancelled()); assert!(!alice_sas.is_cancelled()); } #[async_test] async fn test_requesting_until_sas_to_device() { - let (_alice, alice_store, bob, bob_store) = setup_stores().await; - let bob_device = DeviceData::from_account(&bob); + let (alice, alice_store, bob, bob_store) = setup_stores().await; + + let alice_device_data = DeviceData::from_account(&alice); + let bob_device_data = DeviceData::from_account(&bob); // Set up the pair of verification requests let bob_request = build_test_request(&bob_store, alice_id(), None); - let alice_request = build_incoming_verification_request(&alice_store, &bob_request); - do_accept_request(&alice_request, &bob_request, None); + let alice_request = build_incoming_verification_request(&alice_store, &bob_request).await; + do_accept_request(&alice_request, alice_device_data.clone(), &bob_request, None); let (bob_sas, request) = bob_request.start_sas().await.unwrap().unwrap(); let content: OutgoingContent = request.try_into().unwrap(); let content = StartContent::try_from(&content).unwrap(); let flow_id = content.flow_id().to_owned(); - alice_request.receive_start(bob_device.user_id(), &content).await.unwrap(); + alice_request.receive_start(bob_device_data.user_id(), &content).await.unwrap(); let alice_sas = - alice_request.verification_cache.get_sas(bob_device.user_id(), &flow_id).unwrap(); + alice_request.verification_cache.get_sas(bob_device_data.user_id(), &flow_id).unwrap(); - assert_matches!( - alice_request.state(), - VerificationRequestState::Transitioned { verification: Verification::SasV1(_) } + assert_let!( + VerificationRequestState::Transitioned { + verification: Verification::SasV1(_), + other_device_data + } = alice_request.state() ); - assert_matches!( - bob_request.state(), - VerificationRequestState::Transitioned { verification: Verification::SasV1(_) } + + assert_eq!(bob_device_data, other_device_data); + + assert_let!( + VerificationRequestState::Transitioned { + verification: Verification::SasV1(_), + other_device_data + } = bob_request.state() ); + assert_eq!(alice_device_data, other_device_data); + assert!(!bob_sas.is_cancelled()); assert!(!alice_sas.is_cancelled()); assert!(alice_sas.started_from_request()); @@ -1821,7 +1856,10 @@ mod tests { #[async_test] #[cfg(feature = "qrcode")] async fn test_can_scan_another_qr_after_creating_mine() { - let (_alice, alice_store, _bob, bob_store) = setup_stores().await; + let (alice, alice_store, bob, bob_store) = setup_stores().await; + + let alice_device_data = DeviceData::from_account(&alice); + let bob_device_data = DeviceData::from_account(&bob); // Set up the pair of verification requests let bob_request = build_test_request( @@ -1829,9 +1867,10 @@ mod tests { alice_id(), Some(vec![VerificationMethod::QrCodeScanV1, VerificationMethod::QrCodeShowV1]), ); - let alice_request = build_incoming_verification_request(&alice_store, &bob_request); + let alice_request = build_incoming_verification_request(&alice_store, &bob_request).await; do_accept_request( &alice_request, + alice_device_data.clone(), &bob_request, Some(vec![VerificationMethod::QrCodeScanV1, VerificationMethod::QrCodeShowV1]), ); @@ -1840,15 +1879,24 @@ mod tests { let alice_verification = alice_request.generate_qr_code().await.unwrap(); let bob_verification = bob_request.generate_qr_code().await.unwrap(); - assert_matches!( - alice_request.state(), - VerificationRequestState::Transitioned { verification: Verification::QrV1(_) } + assert_let!( + VerificationRequestState::Transitioned { + verification: Verification::QrV1(_), + other_device_data + } = alice_request.state() ); - assert_matches!( - bob_request.state(), - VerificationRequestState::Transitioned { verification: Verification::QrV1(_) } + + assert_eq!(bob_device_data, other_device_data); + + assert_let!( + VerificationRequestState::Transitioned { + verification: Verification::QrV1(_), + other_device_data + } = bob_request.state() ); + assert_eq!(alice_device_data, other_device_data); + assert!(alice_verification.is_some()); assert!(bob_verification.is_some()); @@ -1859,10 +1907,13 @@ mod tests { assert_let!( VerificationRequestState::Transitioned { - verification: Verification::QrV1(alice_verification) + verification: Verification::QrV1(alice_verification), + other_device_data } = alice_request.state() ); + assert_eq!(bob_device_data, other_device_data); + // Finally we assert that the verification has been reciprocated rather than // cancelled due to a duplicate verification flow assert!(!alice_verification.is_cancelled()); @@ -1872,32 +1923,48 @@ mod tests { #[async_test] #[cfg(feature = "qrcode")] async fn test_can_start_sas_after_generating_qr_code() { - let (_alice, alice_store, _bob, bob_store) = setup_stores().await; + let (alice, alice_store, bob, bob_store) = setup_stores().await; + + let alice_device_data = DeviceData::from_account(&alice); + let bob_device_data = DeviceData::from_account(&bob); // Set up the pair of verification requests let bob_request = build_test_request(&bob_store, alice_id(), Some(all_methods())); - let alice_request = build_incoming_verification_request(&alice_store, &bob_request); - do_accept_request(&alice_request, &bob_request, Some(all_methods())); + let alice_request = build_incoming_verification_request(&alice_store, &bob_request).await; + do_accept_request( + &alice_request, + alice_device_data.clone(), + &bob_request, + Some(all_methods()), + ); // Each side can start its own QR verification flow by generating QR code let alice_verification = alice_request.generate_qr_code().await.unwrap(); let bob_verification = bob_request.generate_qr_code().await.unwrap(); - assert_matches!( - alice_request.state(), - VerificationRequestState::Transitioned { verification: Verification::QrV1(_) } + assert_let!( + VerificationRequestState::Transitioned { + verification: Verification::QrV1(_), + other_device_data + } = alice_request.state() ); + assert_eq!(bob_device_data, other_device_data); + assert!(alice_verification.is_some()); assert!(bob_verification.is_some()); // Alice can now start SAS verification flow instead of QR without cancelling // the request let (sas, request) = alice_request.start_sas().await.unwrap().unwrap(); - assert_matches!( - alice_request.state(), - VerificationRequestState::Transitioned { verification: Verification::SasV1(_) } + assert_let!( + VerificationRequestState::Transitioned { + verification: Verification::SasV1(_), + other_device_data + } = alice_request.state() ); + + assert_eq!(bob_device_data, other_device_data); assert!(!sas.is_cancelled()); // Bob receives the SAS start @@ -1907,9 +1974,14 @@ mod tests { // Bob should now have transitioned to SAS... assert_let!( - VerificationRequestState::Transitioned { verification: Verification::SasV1(bob_sas) } = - bob_request.state() + VerificationRequestState::Transitioned { + verification: Verification::SasV1(bob_sas), + other_device_data + } = bob_request.state() ); + + assert_eq!(alice_device_data, other_device_data); + // ... and, more to the point, it should not be cancelled. assert!(!bob_sas.is_cancelled()); } @@ -1917,38 +1989,58 @@ mod tests { #[async_test] #[cfg(feature = "qrcode")] async fn test_start_sas_after_scan_cancels_request() { - let (_alice, alice_store, _bob, bob_store) = setup_stores().await; + let (alice, alice_store, bob, bob_store) = setup_stores().await; + + let alice_device_data = DeviceData::from_account(&alice); + let bob_device_data = DeviceData::from_account(&bob); // Set up the pair of verification requests let bob_request = build_test_request(&bob_store, alice_id(), Some(all_methods())); - let alice_request = build_incoming_verification_request(&alice_store, &bob_request); - do_accept_request(&alice_request, &bob_request, Some(all_methods())); + let alice_request = build_incoming_verification_request(&alice_store, &bob_request).await; + do_accept_request( + &alice_request, + alice_device_data.clone(), + &bob_request, + Some(all_methods()), + ); // Bob generates a QR code let bob_verification = bob_request.generate_qr_code().await.unwrap().unwrap(); - assert_matches!( - bob_request.state(), - VerificationRequestState::Transitioned { verification: Verification::QrV1(_) } + assert_let!( + VerificationRequestState::Transitioned { + verification: Verification::QrV1(_), + other_device_data + } = bob_request.state() ); + assert_eq!(alice_device_data, other_device_data); + // Now Alice scans Bob's code let bob_qr_code = bob_verification.to_bytes().unwrap(); let bob_qr_code = QrVerificationData::from_bytes(bob_qr_code).unwrap(); let _ = alice_request.scan_qr_code(bob_qr_code).await.unwrap().unwrap(); assert_let!( - VerificationRequestState::Transitioned { verification: Verification::QrV1(alice_qr) } = - alice_request.state() + VerificationRequestState::Transitioned { + verification: Verification::QrV1(alice_qr), + other_device_data + } = alice_request.state() ); + + assert_eq!(bob_device_data, other_device_data); assert!(alice_qr.reciprocated()); // But Bob wants to do an SAS verification! let (_, request) = bob_request.start_sas().await.unwrap().unwrap(); - assert_matches!( - bob_request.state(), - VerificationRequestState::Transitioned { verification: Verification::SasV1(_) } + assert_let!( + VerificationRequestState::Transitioned { + verification: Verification::SasV1(_), + other_device_data + } = bob_request.state() ); + assert_eq!(alice_device_data, other_device_data); + // Alice receives the SAS start let content: OutgoingContent = request.try_into().unwrap(); let content = StartContent::try_from(&content).unwrap(); @@ -1960,9 +2052,12 @@ mod tests { // and she should now have a *cancelled* SAS verification assert_let!( VerificationRequestState::Transitioned { - verification: Verification::SasV1(alice_sas) + verification: Verification::SasV1(alice_sas), + other_device_data } = alice_request.state() ); + + assert_eq!(bob_device_data, other_device_data); assert!(alice_sas.is_cancelled()); } @@ -1996,7 +2091,7 @@ mod tests { /// Tells the outgoing request to generate an `m.key.verification.request` /// to-device message, and uses it to build a new request for the incoming /// side. - fn build_incoming_verification_request( + async fn build_incoming_verification_request( verification_store: &VerificationStore, outgoing_request: &VerificationRequest, ) -> VerificationRequest { @@ -2004,12 +2099,19 @@ mod tests { let content: OutgoingContent = request.try_into().unwrap(); let content = RequestContent::try_from(&content).unwrap(); + let device_data = verification_store + .get_device(outgoing_request.own_user_id(), content.from_device()) + .await + .unwrap() + .expect("Missing device data"); + VerificationRequest::from_request( VerificationCache::new(), verification_store.clone(), outgoing_request.own_user_id(), outgoing_request.flow_id().clone(), &content, + device_data, ) } @@ -2025,6 +2127,7 @@ mod tests { /// default list of methods will be used. fn do_accept_request( accepting_request: &VerificationRequest, + accepting_device_data: DeviceData, initiating_request: &VerificationRequest, methods: Option>, ) { @@ -2034,7 +2137,11 @@ mod tests { }; let content: OutgoingContent = request.unwrap().try_into().unwrap(); let content = ReadyContent::try_from(&content).unwrap(); - initiating_request.receive_ready(accepting_request.own_user_id(), &content); + initiating_request.receive_ready( + accepting_request.own_user_id(), + &content, + accepting_device_data, + ); assert!(initiating_request.is_ready()); assert!(accepting_request.is_ready()); diff --git a/crates/matrix-sdk-crypto/src/verification/sas/mod.rs b/crates/matrix-sdk-crypto/src/verification/sas/mod.rs index c9a3acf961f..caf43a249ab 100644 --- a/crates/matrix-sdk-crypto/src/verification/sas/mod.rs +++ b/crates/matrix-sdk-crypto/src/verification/sas/mod.rs @@ -899,7 +899,7 @@ mod tests { } fn bob_device_id() -> &'static DeviceId { - device_id!("BOBDEVCIE") + device_id!("BOBDEVICE") } fn machine_pair_test_helper() -> (VerificationStore, DeviceData, VerificationStore, DeviceData) diff --git a/crates/matrix-sdk/src/encryption/verification/requests.rs b/crates/matrix-sdk/src/encryption/verification/requests.rs index 30e56f1f177..49b8a51b438 100644 --- a/crates/matrix-sdk/src/encryption/verification/requests.rs +++ b/crates/matrix-sdk/src/encryption/verification/requests.rs @@ -13,8 +13,10 @@ // limitations under the License. use futures_util::{Stream, StreamExt}; -use matrix_sdk_base::crypto::{CancelInfo, VerificationRequest as BaseVerificationRequest}; -use ruma::{events::key::verification::VerificationMethod, OwnedDeviceId, RoomId}; +use matrix_sdk_base::crypto::{ + CancelInfo, DeviceData, VerificationRequest as BaseVerificationRequest, +}; +use ruma::{events::key::verification::VerificationMethod, RoomId}; #[cfg(feature = "qrcode")] use super::{QrVerification, QrVerificationData}; @@ -41,9 +43,9 @@ pub enum VerificationRequestState { /// The verification methods supported by the sender. their_methods: Vec, - /// The device ID of the device that responded to the verification + /// The device data of the device that responded to the verification /// request. - other_device_id: OwnedDeviceId, + other_device_data: DeviceData, }, /// The verification request is ready to start a verification flow. Ready { @@ -53,9 +55,9 @@ pub enum VerificationRequestState { /// The verification methods supported by the us. our_methods: Vec, - /// The device ID of the device that responded to the verification + /// The device data of the device that responded to the verification /// request. - other_device_id: OwnedDeviceId, + other_device_data: DeviceData, }, /// The verification request has transitioned into a concrete verification /// flow. For example it transitioned into the emoji based SAS @@ -218,13 +220,13 @@ impl VerificationRequest { match state { Created { our_methods } => VerificationRequestState::Created { our_methods }, - Requested { their_methods, other_device_id } => { - VerificationRequestState::Requested { their_methods, other_device_id } + Requested { their_methods, other_device_data } => { + VerificationRequestState::Requested { their_methods, other_device_data } } - Ready { their_methods, our_methods, other_device_id } => { - VerificationRequestState::Ready { their_methods, our_methods, other_device_id } + Ready { their_methods, our_methods, other_device_data } => { + VerificationRequestState::Ready { their_methods, our_methods, other_device_data } } - Transitioned { verification } => VerificationRequestState::Transitioned { + Transitioned { verification, .. } => VerificationRequestState::Transitioned { verification: match verification { matrix_sdk_base::crypto::Verification::SasV1(s) => { Verification::SasV1(SasVerification { inner: s, client }) diff --git a/testing/matrix-sdk-integration-testing/src/tests/e2ee.rs b/testing/matrix-sdk-integration-testing/src/tests/e2ee.rs index 233dbc49e7d..1c72ae8c17a 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/e2ee.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/e2ee.rs @@ -70,6 +70,7 @@ async fn test_mutual_sas_verification() -> Result<()> { bob.get_room(room_id).unwrap().join().await?; alice.sync_once().await?; + bob.sync_once().await?; warn!("alice and bob are both aware of each other in the e2ee room"); @@ -331,6 +332,7 @@ async fn test_mutual_qrcode_verification() -> Result<()> { bob.get_room(room_id).unwrap().join().await?; alice.sync_once().await?; + bob.sync_once().await?; warn!("alice and bob are both aware of each other in the e2ee room"); @@ -841,6 +843,9 @@ async fn test_secret_gossip_after_interactive_verification() -> Result<()> { // The first client is not verified from the point of view of the second client. assert!(!seconds_first_device.is_verified()); + // Make the first client aware of the device we're requesting verification for + first_client.sync_once().await?; + // Let's send out a request to verify with each other. let seconds_verification_request = seconds_first_device.request_verification().await?; let flow_id = seconds_verification_request.flow_id();