diff --git a/bindings/matrix-sdk-crypto-ffi/src/machine.rs b/bindings/matrix-sdk-crypto-ffi/src/machine.rs index 5ab3af7b1e6..af50a0a06f9 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/machine.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/machine.rs @@ -1239,6 +1239,16 @@ impl OlmMachine { Ok(()) } + /// Request missing local secrets from our devices (cross signing private + /// keys, megolm backup). This will ask the sdk to create outgoing + /// request to get the missing secrets. + /// + /// The requests will be processed as soon as `outgoing_requests()` is + /// called to process them. + pub fn query_missing_secrets_from_other_sessions(&self) -> Result { + Ok(self.runtime.block_on(self.inner.query_missing_secrets_from_other_sessions())?) + } + /// Activate the given backup key to be used with the given backup version. /// /// **Warning**: The caller needs to make sure that the given `BackupKey` is diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index ee5a1a9c9b4..02ab02b6d71 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -19,6 +19,7 @@ use std::{ }; use dashmap::DashMap; +use itertools::Itertools; use matrix_sdk_common::deserialized_responses::{ AlgorithmInfo, DeviceLinkProblem, EncryptionInfo, TimelineEvent, VerificationLevel, VerificationState, @@ -1220,6 +1221,68 @@ impl OlmMachine { }) } + /// Request missing local secrets from our devices (cross signing private + /// keys, megolm backup). This will ask the sdk to create outgoing + /// request to get the missing secrets. + /// + /// The requests will be processed as soon as `outgoing_requests()` is + /// called to process them. + /// + /// # Returns + /// + /// A bool result saying if actual secrets were missing and have been + /// requested + /// + /// # Examples + // + /// ``` + /// # async { + /// # use matrix_sdk_crypto::OlmMachine; + /// # let machine: OlmMachine = unimplemented!(); + /// if machine.query_missing_secrets_from_other_sessions().await.unwrap() { + /// let to_send = machine.outgoing_requests().await.unwrap(); + /// // send the to device requests + /// }; + /// # anyhow::Ok(()) }; + /// ``` + pub async fn query_missing_secrets_from_other_sessions(&self) -> StoreResult { + let identity = self.inner.user_identity.lock().await; + #[allow(unused_mut)] + let mut secrets = identity.get_missing_secrets().await; + + #[cfg(feature = "backups_v1")] + if self.store().load_backup_keys().await?.recovery_key.is_none() { + secrets.push(SecretName::RecoveryKey); + } + + if secrets.is_empty() { + debug!("No missing requests to query"); + return Ok(false); + } + + let secret_requests = GossipMachine::request_missing_secrets(self.user_id(), secrets); + + // Check if there are already inflight requests for these secrets? + let unsent_request = self.store().get_unsent_secret_requests().await?; + let not_yet_requested = secret_requests + .into_iter() + .filter(|request| !unsent_request.iter().any(|unsent| unsent.info == request.info)) + .collect_vec(); + + if not_yet_requested.is_empty() { + debug!("The missing secrets have already been requested"); + Ok(false) + } else { + debug!("Requesting missing secrets"); + + let mut changes = Changes::default(); + changes.key_requests = not_yet_requested; + + self.store().save_changes(changes).await?; + Ok(true) + } + } + /// Get some metadata pertaining to a given group session. /// /// This includes the session owner's Matrix user ID, their device ID, info @@ -1751,6 +1814,7 @@ pub(crate) mod tests { use assert_matches::assert_matches; use futures_util::{FutureExt, StreamExt}; + use itertools::Itertools; use matrix_sdk_common::deserialized_responses::{ DeviceLinkProblem, ShieldState, VerificationLevel, VerificationState, }; @@ -1801,8 +1865,8 @@ pub(crate) mod tests { }, utilities::json_convert, verification::tests::{outgoing_request_to_event, request_to_event}, - EncryptionSettings, LocalTrust, MegolmError, OlmError, ReadOnlyDevice, ToDeviceRequest, - UserIdentities, + EncryptionSettings, LocalTrust, MegolmError, OlmError, OutgoingRequests, ReadOnlyDevice, + ToDeviceRequest, UserIdentities, }; /// These keys need to be periodically uploaded to the server. @@ -2316,6 +2380,75 @@ pub(crate) mod tests { assert!(session.unwrap().is_some()); } + #[async_test] + async fn test_request_missing_secrets() { + let (alice, _) = get_machine_pair_with_session(false).await; + + let should_query_secrets = alice.query_missing_secrets_from_other_sessions().await.unwrap(); + + assert!(should_query_secrets); + + let outgoing_to_device = alice + .outgoing_requests() + .await + .unwrap() + .into_iter() + .filter(|outgoing| match outgoing.request.as_ref() { + OutgoingRequests::ToDeviceRequest(request) => { + request.event_type.to_string() == "m.secret.request" + } + _ => false, + }) + .collect_vec(); + + if cfg!(feature = "backups_v1") { + assert_eq!(outgoing_to_device.len(), 4); + } else { + assert_eq!(outgoing_to_device.len(), 3); + } + + // The second time, as there are already in-flight requests, it should have no + // effect. + let should_query_secrets_now = + alice.query_missing_secrets_from_other_sessions().await.unwrap(); + assert!(!should_query_secrets_now); + } + + #[async_test] + async fn test_request_missing_secrets_cross_signed() { + let (alice, bob) = get_machine_pair_with_session(false).await; + + setup_cross_signing_for_machine(&alice, &bob).await; + + let should_query_secrets = alice.query_missing_secrets_from_other_sessions().await.unwrap(); + + if cfg!(feature = "backups_v1") { + assert!(should_query_secrets); + + let outgoing_to_device = alice + .outgoing_requests() + .await + .unwrap() + .into_iter() + .filter(|outgoing| match outgoing.request.as_ref() { + OutgoingRequests::ToDeviceRequest(request) => { + request.event_type.to_string() == "m.secret.request" + } + _ => false, + }) + .collect_vec(); + assert_eq!(outgoing_to_device.len(), 1); + } else { + assert!(!should_query_secrets); + } + + // The second time, as there are already in-flight requests, it should have no + // effect. + let should_query_secrets_now = + alice.query_missing_secrets_from_other_sessions().await.unwrap(); + assert!(!should_query_secrets_now); + } + #[async_test] async fn test_megolm_encryption() { let (alice, bob) = get_machine_pair_with_setup_sessions().await;