diff --git a/crates/matrix-sdk-crypto/CHANGELOG.md b/crates/matrix-sdk-crypto/CHANGELOG.md index 4f70217866e..b478e386596 100644 --- a/crates/matrix-sdk-crypto/CHANGELOG.md +++ b/crates/matrix-sdk-crypto/CHANGELOG.md @@ -6,6 +6,16 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - ReleaseDate +### Features + +- Improves feedback support for shared history when downloading room key bundles. + ([#5737](https://github.com/matrix-org/matrix-rust-sdk/pull/5737)) + - Add `RoomKeyWithheldEntry` enum, wrapping either a received to-device `m.room_key.withheld` event or + its content, if derived from a downloaded room key bundle. + - `OlmMachine::receive_room_key_bundle` now appends withheld key information to the store. + - [**breaking**] `Changes::withheld_session_info` now stores a `RookKeyWithheldEntry` in each `room-id`-`session-id` entry. + - [**breaking**] `CryptoStore::get_withheld_info` now returns `Result>`. This change also affects `MemoryStore`. + ### Bug Fixes - Fix a bug introduced in 0.14.0 which meant that the serialization of the value returned by `OtherUserIdentity::verification_request_content` did not include a `msgtype` field. diff --git a/crates/matrix-sdk-crypto/src/machine/mod.rs b/crates/matrix-sdk-crypto/src/machine/mod.rs index f8ecb766fd7..c9d2ba3edca 100644 --- a/crates/matrix-sdk-crypto/src/machine/mod.rs +++ b/crates/matrix-sdk-crypto/src/machine/mod.rs @@ -1003,14 +1003,16 @@ impl OlmMachine { if let RoomKeyWithheldContent::MegolmV1AesSha2( MegolmV1AesSha2WithheldContent::BlackListed(c) - | MegolmV1AesSha2WithheldContent::Unverified(c), + | MegolmV1AesSha2WithheldContent::Unverified(c) + | MegolmV1AesSha2WithheldContent::Unauthorised(c) + | MegolmV1AesSha2WithheldContent::Unavailable(c), ) = &event.content { changes .withheld_session_info .entry(c.room_id.to_owned()) .or_default() - .insert(c.session_id.to_owned(), event.to_owned()); + .insert(c.session_id.to_owned(), event.to_owned().into()); } } diff --git a/crates/matrix-sdk-crypto/src/store/integration_tests.rs b/crates/matrix-sdk-crypto/src/store/integration_tests.rs index a5b9063c97d..5e4dcc932cd 100644 --- a/crates/matrix-sdk-crypto/src/store/integration_tests.rs +++ b/crates/matrix-sdk-crypto/src/store/integration_tests.rs @@ -53,7 +53,8 @@ macro_rules! cryptostore_integration_tests { store::{ types::{ BackupDecryptionKey, Changes, DehydratedDeviceKey, DeviceChanges, - IdentityChanges, PendingChanges, StoredRoomKeyBundleData, RoomSettings, + IdentityChanges, PendingChanges, StoredRoomKeyBundleData, RoomKeyWithheldEntry, + RoomSettings }, CryptoStore, GossipRequest, }, @@ -1138,7 +1139,7 @@ macro_rules! cryptostore_integration_tests { async fn test_withheld_info_storage() { let (account, store) = get_loaded_store("withheld_info_storage").await; - let mut info_list: BTreeMap<_, BTreeMap<_, _>> = BTreeMap::new(); + let mut info_list: BTreeMap<_, BTreeMap<_, RoomKeyWithheldEntry>> = BTreeMap::new(); let user_id = account.user_id().to_owned(); let room_id = room_id!("!DwLygpkclUAfQNnfva:example.com"); @@ -1163,7 +1164,7 @@ macro_rules! cryptostore_integration_tests { info_list .entry(room_id.to_owned()) .or_default() - .insert(session_id_1.to_owned(), event); + .insert(session_id_1.to_owned(), event.into()); let content = RoomKeyWithheldContent::MegolmV1AesSha2( MegolmV1AesSha2WithheldContent::BlackListed( @@ -1183,7 +1184,7 @@ macro_rules! cryptostore_integration_tests { info_list .entry(room_id.to_owned()) .or_default() - .insert(session_id_2.to_owned(), event); + .insert(session_id_2.to_owned(), event.into()); let changes = Changes { withheld_session_info: info_list, ..Default::default() }; store.save_changes(changes).await.unwrap(); diff --git a/crates/matrix-sdk-crypto/src/store/memorystore.rs b/crates/matrix-sdk-crypto/src/store/memorystore.rs index de3c333950b..7aab3cde7fb 100644 --- a/crates/matrix-sdk-crypto/src/store/memorystore.rs +++ b/crates/matrix-sdk-crypto/src/store/memorystore.rs @@ -45,7 +45,7 @@ use crate::{ OutboundGroupSession, PickledAccount, PickledInboundGroupSession, PickledSession, PrivateCrossSigningIdentity, SenderDataType, StaticAccountData, }, - types::events::room_key_withheld::RoomKeyWithheldEvent, + store::types::RoomKeyWithheldEntry, }; fn encode_key_info(info: &SecretInfo) -> String { @@ -97,7 +97,7 @@ pub struct MemoryStore { identities: StdRwLock>, outgoing_key_requests: StdRwLock>, key_requests_by_info: StdRwLock>, - direct_withheld_info: StdRwLock>>, + direct_withheld_info: StdRwLock>>, custom_values: StdRwLock>>, leases: StdRwLock>, secret_inbox: StdRwLock>>, @@ -420,7 +420,7 @@ impl CryptoStore for MemoryStore { &self, room_id: &RoomId, session_id: &str, - ) -> Result> { + ) -> Result> { Ok(self .direct_withheld_info .read() @@ -1272,11 +1272,10 @@ mod integration_tests { store::{ types::{ BackupKeys, Changes, DehydratedDeviceKey, PendingChanges, RoomKeyCounts, - RoomSettings, StoredRoomKeyBundleData, TrackedUser, + RoomKeyWithheldEntry, RoomSettings, StoredRoomKeyBundleData, TrackedUser, }, CryptoStore, }, - types::events::room_key_withheld::RoomKeyWithheldEvent, Account, DeviceData, GossipRequest, GossippedSecret, SecretInfo, Session, UserIdentityData, }; @@ -1372,7 +1371,7 @@ mod integration_tests { &self, room_id: &RoomId, session_id: &str, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { self.0.get_withheld_info(room_id, session_id).await } diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index dea03f5239d..2fdd884523c 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -59,7 +59,7 @@ use serde::{de::DeserializeOwned, Serialize}; use thiserror::Error; use tokio::sync::{Mutex, Notify, OwnedRwLockWriteGuard, RwLock}; use tokio_stream::wrappers::errors::BroadcastStreamRecvError; -use tracing::{error, info, instrument, trace, warn}; +use tracing::{info, instrument, trace, warn}; use types::{RoomKeyBundleInfo, StoredRoomKeyBundleData}; use vodozemac::{megolm::SessionOrdering, Curve25519PublicKey}; @@ -76,6 +76,7 @@ use crate::{ Account, ExportedRoomKey, InboundGroupSession, PrivateCrossSigningIdentity, SenderData, Session, StaticAccountData, }, + store::types::RoomKeyWithheldEntry, types::{ BackupSecrets, CrossSigningSecrets, MegolmBackupV1Curve25519AesSha2Secrets, RoomKeyExport, SecretsBundle, @@ -1632,59 +1633,111 @@ impl Store { tracing::Span::current().record("sender_data", tracing::field::debug(&sender_data)); - match sender_data { + if matches!( + &sender_data, SenderData::UnknownDevice { .. } - | SenderData::VerificationViolation(_) - | SenderData::DeviceInfo { .. } => { - warn!("Not accepting a historic room key bundle due to insufficient trust in the sender"); - Ok(()) + | SenderData::VerificationViolation(_) + | SenderData::DeviceInfo { .. } + ) { + warn!( + "Not accepting a historic room key bundle due to insufficient trust in the sender" + ); + return Ok(()); + } + + self.import_room_key_bundle_sessions(bundle_info, &bundle, progress_listener).await?; + self.import_room_key_bundle_withheld_info(bundle_info, &bundle).await?; + + Ok(()) + } + + async fn import_room_key_bundle_sessions( + &self, + bundle_info: &StoredRoomKeyBundleData, + bundle: &RoomKeyBundle, + progress_listener: impl Fn(usize, usize), + ) -> Result<(), CryptoStoreError> { + let (good, bad): (Vec<_>, Vec<_>) = bundle.room_keys.iter().partition_map(|key| { + if key.room_id != bundle_info.bundle_data.room_id { + trace!("Ignoring key for incorrect room {} in bundle", key.room_id); + Either::Right(key) + } else { + Either::Left(key) } - SenderData::SenderUnverified(_) | SenderData::SenderVerified(_) => { - let (good, bad): (Vec<_>, Vec<_>) = bundle.room_keys.iter().partition_map(|key| { - if key.room_id != bundle_info.bundle_data.room_id { - trace!("Ignoring key for incorrect room {} in bundle", key.room_id); - Either::Right(key) - } else { - Either::Left(key) - } - }); + }); - match (bad.is_empty(), good.is_empty()) { - // Case 1: Completely empty bundle. - (true, true) => { - warn!("Received a completely empty room key bundle"); - } + match (bad.is_empty(), good.is_empty()) { + // Case 1: Completely empty bundle. + (true, true) => { + warn!("Received a completely empty room key bundle"); + } - // Case 2: A bundle for the wrong room. - (false, true) => { - let bad_keys: Vec<_> = - bad.iter().map(|&key| (&key.room_id, &key.session_id)).collect(); + // Case 2: A bundle for the wrong room. + (false, true) => { + let bad_keys: Vec<_> = + bad.iter().map(|&key| (&key.room_id, &key.session_id)).collect(); - warn!( + warn!( ?bad_keys, "Received a room key bundle for the wrong room, ignoring all room keys from the bundle" ); - } + } - // Case 3: A bundle containing useful room keys. - (_, false) => { - // We have at least some good keys, if we also have some bad ones let's - // mention that here. - if !bad.is_empty() { - warn!( - bad_key_count = bad.len(), - "The room key bundle contained some room keys \ + // Case 3: A bundle containing useful room keys. + (_, false) => { + // We have at least some good keys, if we also have some bad ones let's + // mention that here. + if !bad.is_empty() { + warn!( + bad_key_count = bad.len(), + "The room key bundle contained some room keys \ that were meant for a different room" - ); - } - - self.import_sessions_impl(good, None, progress_listener).await?; - } + ); } - Ok(()) + self.import_sessions_impl(good, None, progress_listener).await?; + } + } + + Ok(()) + } + + async fn import_room_key_bundle_withheld_info( + &self, + bundle_info: &StoredRoomKeyBundleData, + bundle: &RoomKeyBundle, + ) -> Result<(), CryptoStoreError> { + let mut changes = Changes::default(); + for withheld in &bundle.withheld { + let (room_id, session_id) = match withheld { + RoomKeyWithheldContent::MegolmV1AesSha2(c) => match (c.room_id(), c.session_id()) { + (Some(room_id), Some(session_id)) => (room_id, session_id), + _ => continue, + }, + #[cfg(feature = "experimental-algorithms")] + RoomKeyWithheldContent::MegolmV2AesSha2(c) => match (c.room_id(), c.session_id()) { + (Some(room_id), Some(session_id)) => (room_id, session_id), + _ => continue, + }, + RoomKeyWithheldContent::Unknown(_) => continue, + }; + + if room_id != bundle_info.bundle_data.room_id { + trace!("Ignoring withheld info for incorrect room {} in bundle", room_id); + continue; } + + changes.withheld_session_info.entry(room_id.to_owned()).or_default().insert( + session_id.to_owned(), + RoomKeyWithheldEntry { + sender: bundle_info.sender_user.clone(), + content: withheld.to_owned(), + }, + ); } + self.save_changes(changes).await?; + + Ok(()) } } @@ -1717,17 +1770,31 @@ impl matrix_sdk_common::cross_process_lock::TryLock for LockableCryptoStore { mod tests { use std::pin::pin; + use assert_matches2::assert_matches; use futures_util::StreamExt; use insta::{_macro_support::Content, assert_json_snapshot, internals::ContentPath}; use matrix_sdk_test::async_test; - use ruma::{device_id, room_id, user_id, RoomId}; + use ruma::{ + device_id, + events::room::{EncryptedFileInit, JsonWebKeyInit}, + owned_device_id, owned_mxc_uri, room_id, + serde::Base64, + user_id, RoomId, + }; + use serde_json::json; use vodozemac::megolm::SessionKey; use crate::{ machine::test_helpers::get_machine_pair, olm::{InboundGroupSession, SenderData}, - store::types::DehydratedDeviceKey, - types::EventEncryptionAlgorithm, + store::types::{DehydratedDeviceKey, RoomKeyWithheldEntry, StoredRoomKeyBundleData}, + types::{ + events::{ + room_key_bundle::RoomKeyBundleContent, + room_key_withheld::{MegolmV1AesSha2WithheldContent, RoomKeyWithheldContent}, + }, + EventEncryptionAlgorithm, + }, OlmMachine, }; @@ -1948,6 +2015,17 @@ mod tests { // We sort the sessions in the bundle, so that the snapshot is stable. bundle.room_keys.sort_by_key(|session| session.session_id.clone()); + // We substitute the algorithm, since this changes based on feature flags. + let algorithm = if cfg!(feature = "experimental-algorithms") { + "m.megolm.v2.aes-sha2" + } else { + "m.megolm.v1.aes-sha2" + }; + let map_algorithm = move |value: Content, _path: ContentPath<'_>| { + assert_eq!(value.as_str().unwrap(), algorithm); + "[algorithm]" + }; + // We also substitute alice's keys in the snapshot with placeholders let alice_curve_key = alice.identity_keys().curve25519.to_base64(); let map_alice_curve_key = move |value: Content, _path: ContentPath<'_>| { @@ -1962,6 +2040,8 @@ mod tests { insta::with_settings!({ sort_maps => true }, { assert_json_snapshot!(bundle, { + ".withheld[].algorithm" => insta::dynamic_redaction(map_algorithm), + ".room_keys[].algorithm" => insta::dynamic_redaction(map_algorithm), ".room_keys[].sender_key" => insta::dynamic_redaction(map_alice_curve_key.clone()), ".withheld[].sender_key" => insta::dynamic_redaction(map_alice_curve_key), ".room_keys[].sender_claimed_keys.ed25519" => insta::dynamic_redaction(map_alice_ed25519_key), @@ -1969,10 +2049,156 @@ mod tests { }); } + #[async_test] + async fn test_receive_room_key_bundle() { + let alice = OlmMachine::new(user_id!("@a:s.co"), device_id!("ALICE")).await; + let alice_key = alice.identity_keys().curve25519; + let bob = OlmMachine::new(user_id!("@b:s.co"), device_id!("BOB")).await; + + let room_id = room_id!("!room1:localhost"); + + let session_key1 = "AgAAAAC2XHVzsMBKs4QCRElJ92CJKyGtknCSC8HY7cQ7UYwndMKLQAejXLh5UA0l6s736mgctcUMNvELScUWrObdflrHo+vth/gWreXOaCnaSxmyjjKErQwyIYTkUfqbHy40RJfEesLwnN23on9XAkch/iy8R2+Jz7B8zfG01f2Ow2SxPQFnAndcO1ZSD2GmXgedy6n4B20MWI1jGP2wiexOWbFSya8DO/VxC9m5+/mF+WwYqdpKn9g4Y05Yw4uz7cdjTc3rXm7xK+8E7hI//5QD1nHPvuKYbjjM9u2JSL+Bzp61Cw"; + let session_key2 = "AgAAAAC1BXreFTUQQSBGekTEuYxhdytRKyv4JgDGcG+VOBYdPNGgs807SdibCGJky4lJ3I+7ZDGHoUzZPZP/4ogGu4kxni0PWdtWuN7+5zsuamgoFF/BkaGeUUGv6kgIkx8pyPpM5SASTUEP9bN2loDSpUPYwfiIqz74DgC4WQ4435sTBctYvKz8n+TDJwdLXpyT6zKljuqADAioud+s/iqx9LYn9HpbBfezZcvbg67GtE113pLrvde3IcPI5s6dNHK2onGO2B2eoaobcen18bbEDnlUGPeIivArLya7Da6us14jBQ"; + + let sessions = [ + create_inbound_group_session_with_visibility( + &alice, + room_id, + &SessionKey::from_base64(session_key1).unwrap(), + true, + ), + create_inbound_group_session_with_visibility( + &alice, + room_id, + &SessionKey::from_base64(session_key2).unwrap(), + false, + ), + ]; + + alice.store().save_inbound_group_sessions(&sessions).await.unwrap(); + let bundle = alice.store().build_room_key_bundle(room_id).await.unwrap(); + + bob.store() + .receive_room_key_bundle( + &StoredRoomKeyBundleData { + sender_user: alice.user_id().to_owned(), + sender_key: alice_key, + sender_data: SenderData::sender_verified( + alice.user_id(), + device_id!("ALICE"), + alice.identity_keys().ed25519, + ), + + bundle_data: RoomKeyBundleContent { + room_id: room_id.to_owned(), + // This isn't used at all in the method call, so we can fill it with + // garbage. + file: EncryptedFileInit { + url: owned_mxc_uri!("mxc://example.com/0"), + key: JsonWebKeyInit { + kty: "oct".to_owned(), + key_ops: vec!["encrypt".to_owned(), "decrypt".to_owned()], + alg: "A256CTR.".to_owned(), + k: Base64::new(vec![0u8; 128]), + ext: true, + } + .into(), + iv: Base64::new(vec![0u8; 128]), + hashes: vec![("sha256".to_owned(), Base64::new(vec![0u8; 128]))] + .into_iter() + .collect(), + v: "v2".to_owned(), + } + .into(), + }, + }, + bundle, + |_, _| {}, + ) + .await + .unwrap(); + + // The room key should be imported successfully + let imported_sessions = + bob.store().get_inbound_group_sessions_by_room_id(room_id).await.unwrap(); + + assert_eq!(imported_sessions.len(), 1); + assert_eq!(imported_sessions[0].room_id(), room_id); + + assert_matches!( + bob.store() + .get_withheld_info(room_id, sessions[1].session_id()) + .await + .unwrap() + .expect("Withheld info should be present in the store."), + RoomKeyWithheldEntry { + #[cfg(not(feature = "experimental-algorithms"))] + content: RoomKeyWithheldContent::MegolmV1AesSha2( + MegolmV1AesSha2WithheldContent::Unauthorised(_) + ), + #[cfg(feature = "experimental-algorithms")] + content: RoomKeyWithheldContent::MegolmV2AesSha2( + MegolmV1AesSha2WithheldContent::Unauthorised(_) + ), + .. + } + ); + } + + /// Tests that the new store format introduced in [#5737][#5737] does not + /// conflict with items already in the store that were serialised with the + /// older format. + /// + /// [#5737]: https://github.com/matrix-org/matrix-rust-sdk/pull/5737 + #[async_test] + async fn test_deserialize_room_key_withheld_entry_from_to_device_event() { + let entry: RoomKeyWithheldEntry = serde_json::from_value(json!( + { + "content": { + "algorithm": "m.megolm.v1.aes-sha2", + "code": "m.unauthorised", + "from_device": "ALICE", + "reason": "You are not authorised to read the message.", + "room_id": "!roomid:s.co", + "sender_key": "7hIcOrEroXYdzjtCBvBjUiqvT0Me7g+ymeXqoc65RS0", + "session_id": "session123" + }, + "sender": "@alice:s.co", + "type": "m.room_key.withheld" + } + )) + .unwrap(); + + assert_matches!( + entry, + RoomKeyWithheldEntry { + sender, + content: RoomKeyWithheldContent::MegolmV1AesSha2( + MegolmV1AesSha2WithheldContent::Unauthorised(withheld_content,) + ), + } + ); + + assert_eq!(sender, "@alice:s.co"); + assert_eq!(withheld_content.room_id, "!roomid:s.co"); + assert_eq!(withheld_content.session_id, "session123"); + assert_eq!( + withheld_content.sender_key.to_base64(), + "7hIcOrEroXYdzjtCBvBjUiqvT0Me7g+ymeXqoc65RS0" + ); + assert_eq!(withheld_content.from_device, Some(owned_device_id!("ALICE"))); + } + /// Create an inbound Megolm session for the given room. /// /// `olm_machine` is used to set the `sender_key` and `signing_key` /// fields of the resultant session. + /// + /// The encryption algorithm used for the session depends on the + /// `experimental-algorithms` feature flag: + /// + /// - When not set, the session uses `m.megolm.v1.aes-sha2`. + /// - When set, the session uses `m.megolm.v2.aes-sha2`. fn create_inbound_group_session_with_visibility( olm_machine: &OlmMachine, room_id: &RoomId, @@ -1986,7 +2212,10 @@ mod tests { room_id, session_key, SenderData::unknown(), + #[cfg(not(feature = "experimental-algorithms"))] EventEncryptionAlgorithm::MegolmV1AesSha2, + #[cfg(feature = "experimental-algorithms")] + EventEncryptionAlgorithm::MegolmV2AesSha2, None, shared_history, ) diff --git a/crates/matrix-sdk-crypto/src/store/snapshots/matrix_sdk_crypto__store__tests__build_room_key_bundle.snap b/crates/matrix-sdk-crypto/src/store/snapshots/matrix_sdk_crypto__store__tests__build_room_key_bundle.snap index 47b615d1936..cdc15086a1b 100644 --- a/crates/matrix-sdk-crypto/src/store/snapshots/matrix_sdk_crypto__store__tests__build_room_key_bundle.snap +++ b/crates/matrix-sdk-crypto/src/store/snapshots/matrix_sdk_crypto__store__tests__build_room_key_bundle.snap @@ -5,7 +5,7 @@ expression: bundle { "room_keys": [ { - "algorithm": "m.megolm.v1.aes-sha2", + "algorithm": "[algorithm]", "room_id": "!room1:localhost", "sender_key": "[alice curve key]", "session_id": "AWcCd1w7VlIPYaZeB53LqfgHbQxYjWMY/bCJ7E5ZsVI", @@ -15,7 +15,7 @@ expression: bundle } }, { - "algorithm": "m.megolm.v1.aes-sha2", + "algorithm": "[algorithm]", "room_id": "!room1:localhost", "sender_key": "[alice curve key]", "session_id": "y1i8rPyf5MMnB0tenJPrMqWO6oAMCKi536z+KrH0tic", @@ -27,7 +27,7 @@ expression: bundle ], "withheld": [ { - "algorithm": "m.megolm.v1.aes-sha2", + "algorithm": "[algorithm]", "code": "m.unauthorised", "from_device": "BOB", "reason": "You are not authorised to read the message.", diff --git a/crates/matrix-sdk-crypto/src/store/traits.rs b/crates/matrix-sdk-crypto/src/store/traits.rs index 133b8458bf2..2d5e360cb96 100644 --- a/crates/matrix-sdk-crypto/src/store/traits.rs +++ b/crates/matrix-sdk-crypto/src/store/traits.rs @@ -35,7 +35,7 @@ use crate::{ InboundGroupSession, OlmMessageHash, OutboundGroupSession, PrivateCrossSigningIdentity, SenderDataType, Session, }, - types::events::room_key_withheld::RoomKeyWithheldEvent, + store::types::RoomKeyWithheldEntry, Account, DeviceData, GossipRequest, GossippedSecret, SecretInfo, UserIdentityData, }; @@ -116,7 +116,7 @@ pub trait CryptoStore: AsyncTraitDeps { &self, room_id: &RoomId, session_id: &str, - ) -> Result, Self::Error>; + ) -> Result, Self::Error>; /// Get all the inbound group sessions we have stored. async fn get_inbound_group_sessions(&self) -> Result, Self::Error>; @@ -588,7 +588,7 @@ impl CryptoStore for EraseCryptoStoreError { &self, room_id: &RoomId, session_id: &str, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { self.0.get_withheld_info(room_id, session_id).await.map_err(Into::into) } diff --git a/crates/matrix-sdk-crypto/src/store/types.rs b/crates/matrix-sdk-crypto/src/store/types.rs index d6cecfa2920..5b5414b8421 100644 --- a/crates/matrix-sdk-crypto/src/store/types.rs +++ b/crates/matrix-sdk-crypto/src/store/types.rs @@ -34,7 +34,10 @@ use crate::{ SenderData, }, types::{ - events::{room_key_bundle::RoomKeyBundleContent, room_key_withheld::RoomKeyWithheldEvent}, + events::{ + room_key_bundle::RoomKeyBundleContent, + room_key_withheld::{RoomKeyWithheldContent, RoomKeyWithheldEvent}, + }, EventEncryptionAlgorithm, }, Account, Device, DeviceData, GossippedSecret, Session, UserIdentity, UserIdentityData, @@ -75,7 +78,7 @@ pub struct Changes { pub identities: IdentityChanges, pub devices: DeviceChanges, /// Stores when a `m.room_key.withheld` is received - pub withheld_session_info: BTreeMap>, + pub withheld_session_info: BTreeMap>, pub room_settings: HashMap, pub secrets: Vec, pub next_batch_token: Option, @@ -484,9 +487,31 @@ pub struct RoomKeyWithheldInfo { /// The ID of the session that the key is for. pub session_id: String, - /// The `m.room_key.withheld` event that notified us that the key is being - /// withheld. - pub withheld_event: RoomKeyWithheldEvent, + /// The withheld entry from a `m.room_key.withheld` event or [MSC4268] room + /// key bundle. + /// + /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268 + pub withheld_event: RoomKeyWithheldEntry, +} + +/// Represents an entry for a withheld room key event, which can be either a +/// to-device event or a bundle entry. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RoomKeyWithheldEntry { + /// The user ID responsible for this entry, either from a + /// `m.room_key.withheld` to-device event or an [MSC4268] room key bundle. + /// + /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268 + pub sender: OwnedUserId, + /// The content of the entry, which provides details about the reason the + /// key was withheld. + pub content: RoomKeyWithheldContent, +} + +impl From for RoomKeyWithheldEntry { + fn from(value: RoomKeyWithheldEvent) -> Self { + Self { sender: value.sender, content: value.content } + } } /// Information about a received historic room key bundle. diff --git a/crates/matrix-sdk-crypto/src/types/events/room_key_withheld.rs b/crates/matrix-sdk-crypto/src/types/events/room_key_withheld.rs index bc410b681a0..d63e74ad837 100644 --- a/crates/matrix-sdk-crypto/src/types/events/room_key_withheld.rs +++ b/crates/matrix-sdk-crypto/src/types/events/room_key_withheld.rs @@ -17,7 +17,9 @@ use std::collections::BTreeMap; use matrix_sdk_common::deserialized_responses::WithheldCode; -use ruma::{events::AnyToDeviceEventContent, serde::JsonCastable, OwnedDeviceId, OwnedRoomId}; +use ruma::{ + events::AnyToDeviceEventContent, serde::JsonCastable, OwnedDeviceId, OwnedRoomId, RoomId, +}; use serde::{Deserialize, Serialize}; use serde_json::Value; use vodozemac::Curve25519PublicKey; @@ -223,6 +225,28 @@ impl CommonWithheldCodeContent { } impl MegolmV1AesSha2WithheldContent { + /// Get the session ID for this content, if available. + pub fn session_id(&self) -> Option<&str> { + match self { + MegolmV1AesSha2WithheldContent::BlackListed(content) + | MegolmV1AesSha2WithheldContent::Unverified(content) + | MegolmV1AesSha2WithheldContent::Unauthorised(content) + | MegolmV1AesSha2WithheldContent::Unavailable(content) => Some(&content.session_id), + MegolmV1AesSha2WithheldContent::NoOlm(_) => None, + } + } + + /// Get the room ID for this content, if available. + pub fn room_id(&self) -> Option<&RoomId> { + match self { + MegolmV1AesSha2WithheldContent::BlackListed(content) + | MegolmV1AesSha2WithheldContent::Unverified(content) + | MegolmV1AesSha2WithheldContent::Unauthorised(content) + | MegolmV1AesSha2WithheldContent::Unavailable(content) => Some(&content.room_id), + MegolmV1AesSha2WithheldContent::NoOlm(_) => None, + } + } + /// Get the withheld code for this content pub fn withheld_code(&self) -> WithheldCode { match self { diff --git a/crates/matrix-sdk-indexeddb/CHANGELOG.md b/crates/matrix-sdk-indexeddb/CHANGELOG.md index 71b00d3c725..b80c839e6e7 100644 --- a/crates/matrix-sdk-indexeddb/CHANGELOG.md +++ b/crates/matrix-sdk-indexeddb/CHANGELOG.md @@ -6,6 +6,11 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - ReleaseDate +### Features + +- [**breaking**] `IndexeddbCryptoStore::get_withheld_info` now returns `Result, ...>` + ([#5737](https://github.com/matrix-org/matrix-rust-sdk/pull/5737)) + ## [0.14.0] - 2025-09-04 No notable changes in this release. diff --git a/crates/matrix-sdk-indexeddb/src/crypto_store/mod.rs b/crates/matrix-sdk-indexeddb/src/crypto_store/mod.rs index b79a7eba1a1..df21e174583 100644 --- a/crates/matrix-sdk-indexeddb/src/crypto_store/mod.rs +++ b/crates/matrix-sdk-indexeddb/src/crypto_store/mod.rs @@ -30,12 +30,11 @@ use matrix_sdk_crypto::{ }, store::{ types::{ - BackupKeys, Changes, DehydratedDeviceKey, PendingChanges, RoomKeyCounts, RoomSettings, - StoredRoomKeyBundleData, + BackupKeys, Changes, DehydratedDeviceKey, PendingChanges, RoomKeyCounts, + RoomKeyWithheldEntry, RoomSettings, StoredRoomKeyBundleData, }, CryptoStore, CryptoStoreError, }, - types::events::room_key_withheld::RoomKeyWithheldEvent, vodozemac::base64_encode, Account, DeviceData, GossipRequest, GossippedSecret, SecretInfo, TrackedUser, UserIdentityData, }; @@ -1386,7 +1385,7 @@ impl_crypto_store! { &self, room_id: &RoomId, session_id: &str, - ) -> Result> { + ) -> Result> { let key = self.serializer.encode_key(keys::DIRECT_WITHHELD_INFO, (session_id, room_id)); if let Some(pickle) = self .inner diff --git a/crates/matrix-sdk-sqlite/CHANGELOG.md b/crates/matrix-sdk-sqlite/CHANGELOG.md index 3e40f102be4..3ede8420a71 100644 --- a/crates/matrix-sdk-sqlite/CHANGELOG.md +++ b/crates/matrix-sdk-sqlite/CHANGELOG.md @@ -7,9 +7,13 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - ReleaseDate ### Features + - Implement a new constructor that allows to open `SqliteCryptoStore` with a cryptographic key ([#5472](https://github.com/matrix-org/matrix-rust-sdk/pull/5472)) +- [**breaking**] `SqliteCryptoStore::get_withheld_info` now returns `Result>`. + ([#5737](https://github.com/matrix-org/matrix-rust-sdk/pull/5737)) + ### Refactor - [breaking] Change the logic for opening a store so as to use a `Secret` enum in the function `open_with_pool` instead of a `passphrase` ([#5472](https://github.com/matrix-org/matrix-rust-sdk/pull/5472)) @@ -85,5 +89,3 @@ No notable changes in this release. ### Refactor - Move `event_cache_store/` to `event_cache/store/` in `matrix-sdk-base`. - - diff --git a/crates/matrix-sdk-sqlite/src/crypto_store.rs b/crates/matrix-sdk-sqlite/src/crypto_store.rs index a2f2ec0ed26..c0588f7de64 100644 --- a/crates/matrix-sdk-sqlite/src/crypto_store.rs +++ b/crates/matrix-sdk-sqlite/src/crypto_store.rs @@ -28,12 +28,11 @@ use matrix_sdk_crypto::{ }, store::{ types::{ - BackupKeys, Changes, DehydratedDeviceKey, PendingChanges, RoomKeyCounts, RoomSettings, - StoredRoomKeyBundleData, + BackupKeys, Changes, DehydratedDeviceKey, PendingChanges, RoomKeyCounts, + RoomKeyWithheldEntry, RoomSettings, StoredRoomKeyBundleData, }, CryptoStore, }, - types::events::room_key_withheld::RoomKeyWithheldEvent, Account, DeviceData, GossipRequest, GossippedSecret, SecretInfo, TrackedUser, UserIdentityData, }; use matrix_sdk_store_encryption::StoreCipher; @@ -1379,7 +1378,7 @@ impl CryptoStore for SqliteCryptoStore { &self, room_id: &RoomId, session_id: &str, - ) -> Result> { + ) -> Result> { let room_id = self.encode_key("direct_withheld_info", room_id); let session_id = self.encode_key("direct_withheld_info", session_id); @@ -1388,7 +1387,7 @@ impl CryptoStore for SqliteCryptoStore { .get_direct_withheld_info(session_id, room_id) .await? .map(|value| { - let info = self.deserialize_json::(&value)?; + let info = self.deserialize_json::(&value)?; Ok(info) }) .transpose()