Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new API to request missing secrets #2641

Merged
merged 3 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions bindings/matrix-sdk-crypto-ffi/src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1295,6 +1295,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<bool, CryptoStoreError> {
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
Expand Down
138 changes: 135 additions & 3 deletions crates/matrix-sdk-crypto/src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use std::{
};

use dashmap::DashMap;
use itertools::Itertools;
use matrix_sdk_common::deserialized_responses::{
AlgorithmInfo, DeviceLinkProblem, EncryptionInfo, TimelineEvent, VerificationLevel,
VerificationState,
Expand Down Expand Up @@ -1290,6 +1291,67 @@ 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<bool> {
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?.decryption_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 changes = Changes { key_requests: not_yet_requested, ..Default::default() };

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
Expand Down Expand Up @@ -2013,6 +2075,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,
};
Expand Down Expand Up @@ -2063,9 +2126,9 @@ pub(crate) mod tests {
CrossSigningKey, DeviceKeys, EventEncryptionAlgorithm, SignedKey, SigningKeys,
},
utilities::json_convert,
verification::tests::{outgoing_request_to_event, request_to_event},
EncryptionSettings, LocalTrust, MegolmError, OlmError, ReadOnlyDevice, ToDeviceRequest,
UserIdentities,
verification::tests::{bob_id, outgoing_request_to_event, request_to_event},
EncryptionSettings, LocalTrust, MegolmError, OlmError, OutgoingRequests, ReadOnlyDevice,
ToDeviceRequest, UserIdentities,
};

/// These keys need to be periodically uploaded to the server.
Expand Down Expand Up @@ -2625,6 +2688,75 @@ pub(crate) mod tests {
assert_eq!(room_key_updates[0].session_id, alice_session.session_id());
}

#[async_test]
async fn test_request_missing_secrets() {
let (alice, _) = get_machine_pair_with_session(alice_id(), bob_id(), 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(alice_id(), bob_id(), 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(alice_id(), user_id(), false).await;
Expand Down