From 5d9fdd86d79f0ee5a846058987ec1a4a8bce01a2 Mon Sep 17 00:00:00 2001 From: Skye Elliot Date: Wed, 1 Oct 2025 11:25:50 +0100 Subject: [PATCH 01/18] feat: Add `RoomKeyWithheldEntry` to wrap to-device and bundle payloads. --- crates/matrix-sdk-crypto/src/machine/mod.rs | 8 ++--- .../src/machine/tests/mod.rs | 2 +- .../src/store/integration_tests.rs | 15 +++++---- .../src/store/memorystore.rs | 10 +++--- crates/matrix-sdk-crypto/src/store/traits.rs | 6 ++-- crates/matrix-sdk-crypto/src/store/types.rs | 6 ++-- .../src/types/events/room_key_withheld.rs | 33 +++++++++++++++++++ .../src/crypto_store/mod.rs | 4 +-- crates/matrix-sdk-sqlite/src/crypto_store.rs | 8 ++--- 9 files changed, 63 insertions(+), 29 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/machine/mod.rs b/crates/matrix-sdk-crypto/src/machine/mod.rs index f8ecb766fd7..0527b8ee2c0 100644 --- a/crates/matrix-sdk-crypto/src/machine/mod.rs +++ b/crates/matrix-sdk-crypto/src/machine/mod.rs @@ -998,7 +998,7 @@ impl OlmMachine { Ok(()) } - fn add_withheld_info(&self, changes: &mut Changes, event: &RoomKeyWithheldEvent) { + pub(crate) fn add_withheld_info(&self, changes: &mut Changes, event: &RoomKeyWithheldEvent) { debug!(?event.content, "Processing `m.room_key.withheld` event"); if let RoomKeyWithheldContent::MegolmV1AesSha2( @@ -1010,7 +1010,7 @@ impl OlmMachine { .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()); } } @@ -2029,7 +2029,7 @@ impl OlmMachine { .store .get_withheld_info(room_id, content.session_id()) .await? - .map(|e| e.content.withheld_code()); + .map(|e| e.content().withheld_code()); if withheld_code.is_some() { // Partially withheld, report with a withheld code if we have one. @@ -2145,7 +2145,7 @@ impl OlmMachine { .store .get_withheld_info(room_id, session_id) .await? - .map(|e| e.content.withheld_code()); + .map(|e| e.content().withheld_code()); Err(MegolmError::MissingRoomKey(withheld_code)) } } diff --git a/crates/matrix-sdk-crypto/src/machine/tests/mod.rs b/crates/matrix-sdk-crypto/src/machine/tests/mod.rs index a70f8bbcf7c..0d809bc0b3a 100644 --- a/crates/matrix-sdk-crypto/src/machine/tests/mod.rs +++ b/crates/matrix-sdk-crypto/src/machine/tests/mod.rs @@ -1065,7 +1065,7 @@ async fn test_withheld_unverified() { assert_eq!(&withheld_received[0].room_id, room_id); assert_matches!( - &withheld_received[0].withheld_event.content, + &withheld_received[0].withheld_event.content(), RoomKeyWithheldContent::MegolmV1AesSha2(MegolmV1AesSha2WithheldContent::Unverified( unverified_withheld_content )) diff --git a/crates/matrix-sdk-crypto/src/store/integration_tests.rs b/crates/matrix-sdk-crypto/src/store/integration_tests.rs index a5b9063c97d..407298579f3 100644 --- a/crates/matrix-sdk-crypto/src/store/integration_tests.rs +++ b/crates/matrix-sdk-crypto/src/store/integration_tests.rs @@ -66,6 +66,7 @@ macro_rules! cryptostore_integration_tests { room_key_withheld::{ CommonWithheldCodeContent, MegolmV1AesSha2WithheldContent, RoomKeyWithheldContent, + RoomKeyWithheldEntry, }, room_key_bundle::RoomKeyBundleContent, secret_send::SecretSendContent, @@ -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(); @@ -1192,16 +1193,16 @@ macro_rules! cryptostore_integration_tests { assert_matches!( is_withheld, Some(event) - if event.content.algorithm() == EventEncryptionAlgorithm::MegolmV1AesSha2 && - event.content.withheld_code() == WithheldCode::Unverified + if event.content().algorithm() == EventEncryptionAlgorithm::MegolmV1AesSha2 && + event.content().withheld_code() == WithheldCode::Unverified ); let is_withheld = store.get_withheld_info(room_id, session_id_2).await.unwrap(); assert_matches!( is_withheld, Some(event) - if event.content.algorithm() == EventEncryptionAlgorithm::MegolmV1AesSha2 && - event.content.withheld_code() == WithheldCode::Blacklisted + if event.content().algorithm() == EventEncryptionAlgorithm::MegolmV1AesSha2 && + event.content().withheld_code() == WithheldCode::Blacklisted ); let other_room_id = room_id!("!nQRyiRFuyUhXeaQfiR:example.com"); diff --git a/crates/matrix-sdk-crypto/src/store/memorystore.rs b/crates/matrix-sdk-crypto/src/store/memorystore.rs index de3c333950b..917c34f8b90 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, + types::events::room_key_withheld::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() @@ -1276,7 +1276,7 @@ mod integration_tests { }, CryptoStore, }, - types::events::room_key_withheld::RoomKeyWithheldEvent, + types::events::room_key_withheld::RoomKeyWithheldEntry, Account, DeviceData, GossipRequest, GossippedSecret, SecretInfo, Session, UserIdentityData, }; @@ -1372,7 +1372,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/traits.rs b/crates/matrix-sdk-crypto/src/store/traits.rs index 133b8458bf2..c32e6e59111 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, + types::events::room_key_withheld::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..7b439759da6 100644 --- a/crates/matrix-sdk-crypto/src/store/types.rs +++ b/crates/matrix-sdk-crypto/src/store/types.rs @@ -34,7 +34,7 @@ use crate::{ SenderData, }, types::{ - events::{room_key_bundle::RoomKeyBundleContent, room_key_withheld::RoomKeyWithheldEvent}, + events::{room_key_bundle::RoomKeyBundleContent, room_key_withheld::RoomKeyWithheldEntry}, EventEncryptionAlgorithm, }, Account, Device, DeviceData, GossippedSecret, Session, UserIdentity, UserIdentityData, @@ -75,7 +75,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, @@ -486,7 +486,7 @@ pub struct RoomKeyWithheldInfo { /// The `m.room_key.withheld` event that notified us that the key is being /// withheld. - pub withheld_event: RoomKeyWithheldEvent, + pub withheld_event: RoomKeyWithheldEntry, } /// 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..10f7133419f 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 @@ -25,6 +25,39 @@ use vodozemac::Curve25519PublicKey; use super::{EventType, ToDeviceEvent}; use crate::types::{deserialize_curve_key, serialize_curve_key, EventEncryptionAlgorithm}; +/// 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)] +#[serde(untagged)] +pub enum RoomKeyWithheldEntry { + /// A to-device event containing withheld room key information. + ToDevice(RoomKeyWithheldEvent), + /// Content held within a room key bundle. + Bundle(RoomKeyWithheldContent), +} + +impl RoomKeyWithheldEntry { + /// Returns a reference to the underlying `RoomKeyWithheldContent`. + pub fn content(&self) -> &RoomKeyWithheldContent { + match self { + RoomKeyWithheldEntry::ToDevice(ev) => &ev.content, + RoomKeyWithheldEntry::Bundle(content) => content, + } + } +} + +impl From for RoomKeyWithheldEntry { + fn from(value: RoomKeyWithheldEvent) -> Self { + Self::ToDevice(value) + } +} + +impl From for RoomKeyWithheldEntry { + fn from(value: RoomKeyWithheldContent) -> Self { + Self::Bundle(value) + } +} + /// The `m.room_key_request` to-device event. pub type RoomKeyWithheldEvent = ToDeviceEvent; diff --git a/crates/matrix-sdk-indexeddb/src/crypto_store/mod.rs b/crates/matrix-sdk-indexeddb/src/crypto_store/mod.rs index b79a7eba1a1..3fd72911175 100644 --- a/crates/matrix-sdk-indexeddb/src/crypto_store/mod.rs +++ b/crates/matrix-sdk-indexeddb/src/crypto_store/mod.rs @@ -35,7 +35,7 @@ use matrix_sdk_crypto::{ }, CryptoStore, CryptoStoreError, }, - types::events::room_key_withheld::RoomKeyWithheldEvent, + types::events::room_key_withheld::RoomKeyWithheldEntry, vodozemac::base64_encode, Account, DeviceData, GossipRequest, GossippedSecret, SecretInfo, TrackedUser, UserIdentityData, }; @@ -1386,7 +1386,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/src/crypto_store.rs b/crates/matrix-sdk-sqlite/src/crypto_store.rs index a2f2ec0ed26..3ba70e0cc52 100644 --- a/crates/matrix-sdk-sqlite/src/crypto_store.rs +++ b/crates/matrix-sdk-sqlite/src/crypto_store.rs @@ -33,7 +33,7 @@ use matrix_sdk_crypto::{ }, CryptoStore, }, - types::events::room_key_withheld::RoomKeyWithheldEvent, + types::events::room_key_withheld::RoomKeyWithheldEntry, Account, DeviceData, GossipRequest, GossippedSecret, SecretInfo, TrackedUser, UserIdentityData, }; use matrix_sdk_store_encryption::StoreCipher; @@ -1379,7 +1379,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 +1388,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() @@ -1894,7 +1894,7 @@ mod tests { .expect("This session should be withheld") .unwrap(); - assert_eq!(withheld_info.content.withheld_code(), WithheldCode::Unverified); + assert_eq!(withheld_info.content().withheld_code(), WithheldCode::Unverified); let backup_keys = database.load_backup_keys().await.expect("backup key should be cached"); assert_eq!(backup_keys.backup_version.unwrap(), "6"); From e635219589fc1325e95402012da298bae4adccf2 Mon Sep 17 00:00:00 2001 From: kaylendog Date: Wed, 1 Oct 2025 16:30:34 +0100 Subject: [PATCH 02/18] feat: Append withheld info from room key bundle to store. --- crates/matrix-sdk-crypto/src/store/mod.rs | 132 ++++++++++++++++++++-- 1 file changed, 124 insertions(+), 8 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index dea03f5239d..56cde217892 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -77,8 +77,8 @@ use crate::{ Session, StaticAccountData, }, types::{ - BackupSecrets, CrossSigningSecrets, MegolmBackupV1Curve25519AesSha2Secrets, RoomKeyExport, - SecretsBundle, + events::room_key_withheld::MegolmV1AesSha2WithheldContent, BackupSecrets, + CrossSigningSecrets, MegolmBackupV1Curve25519AesSha2Secrets, RoomKeyExport, SecretsBundle, }, verification::VerificationMachine, CrossSigningStatus, OwnUserIdentityData, RoomKeyImportResult, @@ -1632,12 +1632,11 @@ impl Store { tracing::Span::current().record("sender_data", tracing::field::debug(&sender_data)); - match sender_data { + match &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::SenderUnverified(_) | SenderData::SenderVerified(_) => { let (good, bad): (Vec<_>, Vec<_>) = bundle.room_keys.iter().partition_map(|key| { @@ -1681,10 +1680,28 @@ impl Store { self.import_sessions_impl(good, None, progress_listener).await?; } } + } + } - Ok(()) + let mut changes = Changes::default(); + for withheld in &bundle.withheld { + if let RoomKeyWithheldContent::MegolmV1AesSha2( + MegolmV1AesSha2WithheldContent::BlackListed(c) + | MegolmV1AesSha2WithheldContent::Unverified(c) + | MegolmV1AesSha2WithheldContent::Unauthorised(c) + | MegolmV1AesSha2WithheldContent::Unavailable(c), + ) = withheld + { + changes + .withheld_session_info + .entry(c.room_id.to_owned()) + .or_default() + .insert(c.session_id.to_owned(), withheld.to_owned().into()); } } + self.save_changes(changes).await?; + + Ok(()) } } @@ -1717,17 +1734,32 @@ 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_mxc_uri, room_id, + serde::Base64, + user_id, RoomId, + }; use vodozemac::megolm::SessionKey; use crate::{ machine::test_helpers::get_machine_pair, olm::{InboundGroupSession, SenderData}, - store::types::DehydratedDeviceKey, - types::EventEncryptionAlgorithm, + store::types::{DehydratedDeviceKey, StoredRoomKeyBundleData}, + types::{ + events::{ + room_key_bundle::RoomKeyBundleContent, + room_key_withheld::{ + MegolmV1AesSha2WithheldContent, RoomKeyWithheldContent, RoomKeyWithheldEntry, + }, + }, + EventEncryptionAlgorithm, + }, OlmMachine, }; @@ -1969,6 +2001,90 @@ 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.clone(), + 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_string(), + key_ops: vec!["encrypt".to_string(), "decrypt".to_string()], + alg: "A256CTR.".to_string(), + k: Base64::new(vec![0u8; 128]), + ext: true, + } + .into(), + iv: Base64::new(vec![0u8; 128]), + hashes: vec![("sha256".to_string(), Base64::new(vec![0u8; 128]))] + .into_iter() + .collect(), + v: "v2".to_string(), + } + .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(), + Some(RoomKeyWithheldEntry::Bundle(RoomKeyWithheldContent::MegolmV1AesSha2( + MegolmV1AesSha2WithheldContent::Unauthorised(_) + ))) + ); + } + /// Create an inbound Megolm session for the given room. /// /// `olm_machine` is used to set the `sender_key` and `signing_key` From 7ef47d26d30004b0fcd2db3f4f01a5c573302db7 Mon Sep 17 00:00:00 2001 From: kaylendog Date: Wed, 1 Oct 2025 17:24:12 +0100 Subject: [PATCH 03/18] fix: Clippy lints, copy over clone, `to_owned`. --- crates/matrix-sdk-crypto/src/store/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 56cde217892..50cfd3f75e6 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -2034,7 +2034,7 @@ mod tests { .receive_room_key_bundle( &StoredRoomKeyBundleData { sender_user: alice.user_id().to_owned(), - sender_key: alice_key.clone(), + sender_key: alice_key, sender_data: SenderData::sender_verified( alice.user_id(), device_id!("ALICE"), @@ -2048,18 +2048,18 @@ mod tests { file: EncryptedFileInit { url: owned_mxc_uri!("mxc://example.com/0"), key: JsonWebKeyInit { - kty: "oct".to_string(), - key_ops: vec!["encrypt".to_string(), "decrypt".to_string()], - alg: "A256CTR.".to_string(), + 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_string(), Base64::new(vec![0u8; 128]))] + hashes: vec![("sha256".to_owned(), Base64::new(vec![0u8; 128]))] .into_iter() .collect(), - v: "v2".to_string(), + v: "v2".to_owned(), } .into(), }, From a98f85213584217c5b6c7f399e1b5ffeee94da07 Mon Sep 17 00:00:00 2001 From: kaylendog Date: Wed, 1 Oct 2025 18:12:41 +0100 Subject: [PATCH 04/18] docs: Update CHANGELOGs. --- crates/matrix-sdk-crypto/CHANGELOG.md | 10 ++++++++++ crates/matrix-sdk-indexeddb/CHANGELOG.md | 5 +++++ crates/matrix-sdk-sqlite/CHANGELOG.md | 6 ++++-- 3 files changed, 19 insertions(+), 2 deletions(-) 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-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-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`. - - From 20246ba2af9baaaae114c5d670224ec373a0d2e7 Mon Sep 17 00:00:00 2001 From: Skye Elliot Date: Thu, 2 Oct 2025 11:55:45 +0100 Subject: [PATCH 05/18] feat(crypto): Store `sender` in `RoomKeyWithheldEntry::Bundle`. --- crates/matrix-sdk-crypto/src/store/mod.rs | 26 ++++++++++------- .../src/types/events/room_key_withheld.rs | 28 +++++++++++++------ 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 50cfd3f75e6..fcdcacce1ba 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -77,8 +77,9 @@ use crate::{ Session, StaticAccountData, }, types::{ - events::room_key_withheld::MegolmV1AesSha2WithheldContent, BackupSecrets, - CrossSigningSecrets, MegolmBackupV1Curve25519AesSha2Secrets, RoomKeyExport, SecretsBundle, + events::room_key_withheld::{MegolmV1AesSha2WithheldContent, RoomKeyWithheldEntry}, + BackupSecrets, CrossSigningSecrets, MegolmBackupV1Curve25519AesSha2Secrets, RoomKeyExport, + SecretsBundle, }, verification::VerificationMachine, CrossSigningStatus, OwnUserIdentityData, RoomKeyImportResult, @@ -1692,11 +1693,13 @@ impl Store { | MegolmV1AesSha2WithheldContent::Unavailable(c), ) = withheld { - changes - .withheld_session_info - .entry(c.room_id.to_owned()) - .or_default() - .insert(c.session_id.to_owned(), withheld.to_owned().into()); + changes.withheld_session_info.entry(c.room_id.to_owned()).or_default().insert( + c.session_id.to_owned(), + RoomKeyWithheldEntry::Bundle { + sender: bundle_info.sender_user.clone(), + content: withheld.to_owned(), + }, + ); } } self.save_changes(changes).await?; @@ -2079,9 +2082,12 @@ mod tests { assert_matches!( bob.store().get_withheld_info(room_id, sessions[1].session_id()).await.unwrap(), - Some(RoomKeyWithheldEntry::Bundle(RoomKeyWithheldContent::MegolmV1AesSha2( - MegolmV1AesSha2WithheldContent::Unauthorised(_) - ))) + Some(RoomKeyWithheldEntry::Bundle { + content: RoomKeyWithheldContent::MegolmV1AesSha2( + MegolmV1AesSha2WithheldContent::Unauthorised(_) + ), + .. + }) ); } 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 10f7133419f..704b3a82fd2 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,10 @@ 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, OwnedUserId, + UserId, +}; use serde::{Deserialize, Serialize}; use serde_json::Value; use vodozemac::Curve25519PublicKey; @@ -33,7 +36,12 @@ pub enum RoomKeyWithheldEntry { /// A to-device event containing withheld room key information. ToDevice(RoomKeyWithheldEvent), /// Content held within a room key bundle. - Bundle(RoomKeyWithheldContent), + Bundle { + /// The ID of the user who sent the bundle. + sender: OwnedUserId, + /// The contents of a single withheld entry in the bundle. + content: RoomKeyWithheldContent, + }, } impl RoomKeyWithheldEntry { @@ -41,7 +49,15 @@ impl RoomKeyWithheldEntry { pub fn content(&self) -> &RoomKeyWithheldContent { match self { RoomKeyWithheldEntry::ToDevice(ev) => &ev.content, - RoomKeyWithheldEntry::Bundle(content) => content, + RoomKeyWithheldEntry::Bundle { content, .. } => content, + } + } + + /// Returns the sender's user ID, if available. + pub fn sender(&self) -> &UserId { + match self { + RoomKeyWithheldEntry::ToDevice(ev) => &ev.sender, + RoomKeyWithheldEntry::Bundle { sender, .. } => sender, } } } @@ -52,12 +68,6 @@ impl From for RoomKeyWithheldEntry { } } -impl From for RoomKeyWithheldEntry { - fn from(value: RoomKeyWithheldContent) -> Self { - Self::Bundle(value) - } -} - /// The `m.room_key_request` to-device event. pub type RoomKeyWithheldEvent = ToDeviceEvent; From fd3943cc9441b8f36c0512a09a6660cf2cc75ec3 Mon Sep 17 00:00:00 2001 From: Skye Elliot Date: Thu, 2 Oct 2025 15:23:08 +0100 Subject: [PATCH 06/18] feat: Use struct for `RoomKeyWithheldEntry`, move to store. --- crates/matrix-sdk-crypto/src/machine/mod.rs | 4 +- .../src/machine/tests/mod.rs | 2 +- .../src/store/integration_tests.rs | 12 ++--- .../src/store/memorystore.rs | 5 +-- crates/matrix-sdk-crypto/src/store/mod.rs | 16 +++---- crates/matrix-sdk-crypto/src/store/traits.rs | 2 +- crates/matrix-sdk-crypto/src/store/types.rs | 21 ++++++++- .../src/types/events/room_key_withheld.rs | 45 +------------------ .../src/crypto_store/mod.rs | 5 +-- crates/matrix-sdk-sqlite/src/crypto_store.rs | 7 ++- 10 files changed, 45 insertions(+), 74 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/machine/mod.rs b/crates/matrix-sdk-crypto/src/machine/mod.rs index 0527b8ee2c0..4c75619e9b5 100644 --- a/crates/matrix-sdk-crypto/src/machine/mod.rs +++ b/crates/matrix-sdk-crypto/src/machine/mod.rs @@ -2029,7 +2029,7 @@ impl OlmMachine { .store .get_withheld_info(room_id, content.session_id()) .await? - .map(|e| e.content().withheld_code()); + .map(|e| e.content.withheld_code()); if withheld_code.is_some() { // Partially withheld, report with a withheld code if we have one. @@ -2145,7 +2145,7 @@ impl OlmMachine { .store .get_withheld_info(room_id, session_id) .await? - .map(|e| e.content().withheld_code()); + .map(|e| e.content.withheld_code()); Err(MegolmError::MissingRoomKey(withheld_code)) } } diff --git a/crates/matrix-sdk-crypto/src/machine/tests/mod.rs b/crates/matrix-sdk-crypto/src/machine/tests/mod.rs index 0d809bc0b3a..a70f8bbcf7c 100644 --- a/crates/matrix-sdk-crypto/src/machine/tests/mod.rs +++ b/crates/matrix-sdk-crypto/src/machine/tests/mod.rs @@ -1065,7 +1065,7 @@ async fn test_withheld_unverified() { assert_eq!(&withheld_received[0].room_id, room_id); assert_matches!( - &withheld_received[0].withheld_event.content(), + &withheld_received[0].withheld_event.content, RoomKeyWithheldContent::MegolmV1AesSha2(MegolmV1AesSha2WithheldContent::Unverified( unverified_withheld_content )) diff --git a/crates/matrix-sdk-crypto/src/store/integration_tests.rs b/crates/matrix-sdk-crypto/src/store/integration_tests.rs index 407298579f3..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, }, @@ -66,7 +67,6 @@ macro_rules! cryptostore_integration_tests { room_key_withheld::{ CommonWithheldCodeContent, MegolmV1AesSha2WithheldContent, RoomKeyWithheldContent, - RoomKeyWithheldEntry, }, room_key_bundle::RoomKeyBundleContent, secret_send::SecretSendContent, @@ -1193,16 +1193,16 @@ macro_rules! cryptostore_integration_tests { assert_matches!( is_withheld, Some(event) - if event.content().algorithm() == EventEncryptionAlgorithm::MegolmV1AesSha2 && - event.content().withheld_code() == WithheldCode::Unverified + if event.content.algorithm() == EventEncryptionAlgorithm::MegolmV1AesSha2 && + event.content.withheld_code() == WithheldCode::Unverified ); let is_withheld = store.get_withheld_info(room_id, session_id_2).await.unwrap(); assert_matches!( is_withheld, Some(event) - if event.content().algorithm() == EventEncryptionAlgorithm::MegolmV1AesSha2 && - event.content().withheld_code() == WithheldCode::Blacklisted + if event.content.algorithm() == EventEncryptionAlgorithm::MegolmV1AesSha2 && + event.content.withheld_code() == WithheldCode::Blacklisted ); let other_room_id = room_id!("!nQRyiRFuyUhXeaQfiR:example.com"); diff --git a/crates/matrix-sdk-crypto/src/store/memorystore.rs b/crates/matrix-sdk-crypto/src/store/memorystore.rs index 917c34f8b90..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::RoomKeyWithheldEntry, + store::types::RoomKeyWithheldEntry, }; fn encode_key_info(info: &SecretInfo) -> String { @@ -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::RoomKeyWithheldEntry, Account, DeviceData, GossipRequest, GossippedSecret, SecretInfo, Session, UserIdentityData, }; diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index fcdcacce1ba..348facff465 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -76,10 +76,10 @@ use crate::{ Account, ExportedRoomKey, InboundGroupSession, PrivateCrossSigningIdentity, SenderData, Session, StaticAccountData, }, + store::types::RoomKeyWithheldEntry, types::{ - events::room_key_withheld::{MegolmV1AesSha2WithheldContent, RoomKeyWithheldEntry}, - BackupSecrets, CrossSigningSecrets, MegolmBackupV1Curve25519AesSha2Secrets, RoomKeyExport, - SecretsBundle, + events::room_key_withheld::MegolmV1AesSha2WithheldContent, BackupSecrets, + CrossSigningSecrets, MegolmBackupV1Curve25519AesSha2Secrets, RoomKeyExport, SecretsBundle, }, verification::VerificationMachine, CrossSigningStatus, OwnUserIdentityData, RoomKeyImportResult, @@ -1695,7 +1695,7 @@ impl Store { { changes.withheld_session_info.entry(c.room_id.to_owned()).or_default().insert( c.session_id.to_owned(), - RoomKeyWithheldEntry::Bundle { + RoomKeyWithheldEntry { sender: bundle_info.sender_user.clone(), content: withheld.to_owned(), }, @@ -1753,13 +1753,11 @@ mod tests { use crate::{ machine::test_helpers::get_machine_pair, olm::{InboundGroupSession, SenderData}, - store::types::{DehydratedDeviceKey, StoredRoomKeyBundleData}, + store::types::{DehydratedDeviceKey, RoomKeyWithheldEntry, StoredRoomKeyBundleData}, types::{ events::{ room_key_bundle::RoomKeyBundleContent, - room_key_withheld::{ - MegolmV1AesSha2WithheldContent, RoomKeyWithheldContent, RoomKeyWithheldEntry, - }, + room_key_withheld::{MegolmV1AesSha2WithheldContent, RoomKeyWithheldContent}, }, EventEncryptionAlgorithm, }, @@ -2082,7 +2080,7 @@ mod tests { assert_matches!( bob.store().get_withheld_info(room_id, sessions[1].session_id()).await.unwrap(), - Some(RoomKeyWithheldEntry::Bundle { + Some(RoomKeyWithheldEntry { content: RoomKeyWithheldContent::MegolmV1AesSha2( MegolmV1AesSha2WithheldContent::Unauthorised(_) ), diff --git a/crates/matrix-sdk-crypto/src/store/traits.rs b/crates/matrix-sdk-crypto/src/store/traits.rs index c32e6e59111..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::RoomKeyWithheldEntry, + store::types::RoomKeyWithheldEntry, Account, DeviceData, GossipRequest, GossippedSecret, SecretInfo, UserIdentityData, }; diff --git a/crates/matrix-sdk-crypto/src/store/types.rs b/crates/matrix-sdk-crypto/src/store/types.rs index 7b439759da6..f7eb9b87a1a 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::RoomKeyWithheldEntry}, + events::{ + room_key_bundle::RoomKeyBundleContent, + room_key_withheld::{RoomKeyWithheldContent, RoomKeyWithheldEvent}, + }, EventEncryptionAlgorithm, }, Account, Device, DeviceData, GossippedSecret, Session, UserIdentity, UserIdentityData, @@ -489,6 +492,22 @@ pub struct RoomKeyWithheldInfo { 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 ID of the user who sent the bundle. + pub sender: OwnedUserId, + /// The contents of a single withheld entry in the bundle. + 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. /// /// This struct contains information needed to uniquely identify a room key 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 704b3a82fd2..bc410b681a0 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,10 +17,7 @@ use std::collections::BTreeMap; use matrix_sdk_common::deserialized_responses::WithheldCode; -use ruma::{ - events::AnyToDeviceEventContent, serde::JsonCastable, OwnedDeviceId, OwnedRoomId, OwnedUserId, - UserId, -}; +use ruma::{events::AnyToDeviceEventContent, serde::JsonCastable, OwnedDeviceId, OwnedRoomId}; use serde::{Deserialize, Serialize}; use serde_json::Value; use vodozemac::Curve25519PublicKey; @@ -28,46 +25,6 @@ use vodozemac::Curve25519PublicKey; use super::{EventType, ToDeviceEvent}; use crate::types::{deserialize_curve_key, serialize_curve_key, EventEncryptionAlgorithm}; -/// 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)] -#[serde(untagged)] -pub enum RoomKeyWithheldEntry { - /// A to-device event containing withheld room key information. - ToDevice(RoomKeyWithheldEvent), - /// Content held within a room key bundle. - Bundle { - /// The ID of the user who sent the bundle. - sender: OwnedUserId, - /// The contents of a single withheld entry in the bundle. - content: RoomKeyWithheldContent, - }, -} - -impl RoomKeyWithheldEntry { - /// Returns a reference to the underlying `RoomKeyWithheldContent`. - pub fn content(&self) -> &RoomKeyWithheldContent { - match self { - RoomKeyWithheldEntry::ToDevice(ev) => &ev.content, - RoomKeyWithheldEntry::Bundle { content, .. } => content, - } - } - - /// Returns the sender's user ID, if available. - pub fn sender(&self) -> &UserId { - match self { - RoomKeyWithheldEntry::ToDevice(ev) => &ev.sender, - RoomKeyWithheldEntry::Bundle { sender, .. } => sender, - } - } -} - -impl From for RoomKeyWithheldEntry { - fn from(value: RoomKeyWithheldEvent) -> Self { - Self::ToDevice(value) - } -} - /// The `m.room_key_request` to-device event. pub type RoomKeyWithheldEvent = ToDeviceEvent; diff --git a/crates/matrix-sdk-indexeddb/src/crypto_store/mod.rs b/crates/matrix-sdk-indexeddb/src/crypto_store/mod.rs index 3fd72911175..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::RoomKeyWithheldEntry, vodozemac::base64_encode, Account, DeviceData, GossipRequest, GossippedSecret, SecretInfo, TrackedUser, UserIdentityData, }; diff --git a/crates/matrix-sdk-sqlite/src/crypto_store.rs b/crates/matrix-sdk-sqlite/src/crypto_store.rs index 3ba70e0cc52..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::RoomKeyWithheldEntry, Account, DeviceData, GossipRequest, GossippedSecret, SecretInfo, TrackedUser, UserIdentityData, }; use matrix_sdk_store_encryption::StoreCipher; @@ -1894,7 +1893,7 @@ mod tests { .expect("This session should be withheld") .unwrap(); - assert_eq!(withheld_info.content().withheld_code(), WithheldCode::Unverified); + assert_eq!(withheld_info.content.withheld_code(), WithheldCode::Unverified); let backup_keys = database.load_backup_keys().await.expect("backup key should be cached"); assert_eq!(backup_keys.backup_version.unwrap(), "6"); From e10a1e636e8daa34467a3e4089bbc4536ebeb05c Mon Sep 17 00:00:00 2001 From: kaylendog Date: Thu, 2 Oct 2025 18:13:57 +0100 Subject: [PATCH 07/18] tests: Test deserializing `m.room_key.withheld` to withheld entry. Tests that a to-device `m.room_key.withheld` event can be serialized (using JSON), then deserialized as a RoomKeyWithheldEntry. Ensures compatibility with exisiting store data. --- crates/matrix-sdk-crypto/src/store/mod.rs | 41 +++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 348facff465..1cae17720df 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -1744,7 +1744,7 @@ mod tests { use ruma::{ device_id, events::room::{EncryptedFileInit, JsonWebKeyInit}, - owned_mxc_uri, room_id, + owned_device_id, owned_mxc_uri, owned_room_id, owned_user_id, room_id, serde::Base64, user_id, RoomId, }; @@ -1757,7 +1757,11 @@ mod tests { types::{ events::{ room_key_bundle::RoomKeyBundleContent, - room_key_withheld::{MegolmV1AesSha2WithheldContent, RoomKeyWithheldContent}, + room_key_withheld::{ + CommonWithheldCodeContent, MegolmV1AesSha2WithheldContent, + RoomKeyWithheldContent, + }, + ToDeviceEvent, }, EventEncryptionAlgorithm, }, @@ -2089,6 +2093,39 @@ mod tests { ); } + #[async_test] + async fn test_deserialize_room_key_withheld_entry_from_to_device_event() { + let alice = OlmMachine::new(user_id!("@alice:s.co"), device_id!("ALICE")).await; + + let content = RoomKeyWithheldContent::MegolmV1AesSha2( + MegolmV1AesSha2WithheldContent::Unauthorised(Box::new(CommonWithheldCodeContent::new( + owned_room_id!("!roomid:s.co"), + "session123".into(), + alice.identity_keys().curve25519, + owned_device_id!("ALICE"), + ))), + ); + + let event = ToDeviceEvent::new(owned_user_id!("@alice:s.co"), content); + let entry: RoomKeyWithheldEntry = + serde_json::from_value(serde_json::to_value(&event).unwrap()).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, alice.identity_keys().curve25519); + 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` From 55c005da8bbbf4ea367643d19b41d88337612b16 Mon Sep 17 00:00:00 2001 From: Skye Elliot Date: Fri, 3 Oct 2025 14:32:36 +0100 Subject: [PATCH 08/18] tests: Use `serde_json::json!()` in test over invoking `Serialize`. --- crates/matrix-sdk-crypto/src/store/mod.rs | 49 +++++++++++++---------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 1cae17720df..5196ac4ea71 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -1744,10 +1744,11 @@ mod tests { use ruma::{ device_id, events::room::{EncryptedFileInit, JsonWebKeyInit}, - owned_device_id, owned_mxc_uri, owned_room_id, owned_user_id, room_id, + owned_device_id, owned_mxc_uri, room_id, serde::Base64, user_id, RoomId, }; + use serde_json::json; use vodozemac::megolm::SessionKey; use crate::{ @@ -1757,11 +1758,7 @@ mod tests { types::{ events::{ room_key_bundle::RoomKeyBundleContent, - room_key_withheld::{ - CommonWithheldCodeContent, MegolmV1AesSha2WithheldContent, - RoomKeyWithheldContent, - }, - ToDeviceEvent, + room_key_withheld::{MegolmV1AesSha2WithheldContent, RoomKeyWithheldContent}, }, EventEncryptionAlgorithm, }, @@ -2093,22 +2090,29 @@ mod tests { ); } + /// 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 alice = OlmMachine::new(user_id!("@alice:s.co"), device_id!("ALICE")).await; - - let content = RoomKeyWithheldContent::MegolmV1AesSha2( - MegolmV1AesSha2WithheldContent::Unauthorised(Box::new(CommonWithheldCodeContent::new( - owned_room_id!("!roomid:s.co"), - "session123".into(), - alice.identity_keys().curve25519, - owned_device_id!("ALICE"), - ))), - ); - - let event = ToDeviceEvent::new(owned_user_id!("@alice:s.co"), content); - let entry: RoomKeyWithheldEntry = - serde_json::from_value(serde_json::to_value(&event).unwrap()).unwrap(); + 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, @@ -2123,7 +2127,10 @@ mod tests { 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, alice.identity_keys().curve25519); + 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. From cc74a92e26999954ea93cb35149eebe425b6241d Mon Sep 17 00:00:00 2001 From: kaylendog Date: Mon, 6 Oct 2025 14:57:32 +0100 Subject: [PATCH 09/18] fix(crypto): Re-restrict `OlmMachine::add_withheld_info` visibility. --- crates/matrix-sdk-crypto/src/machine/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-crypto/src/machine/mod.rs b/crates/matrix-sdk-crypto/src/machine/mod.rs index 4c75619e9b5..4768164d694 100644 --- a/crates/matrix-sdk-crypto/src/machine/mod.rs +++ b/crates/matrix-sdk-crypto/src/machine/mod.rs @@ -998,7 +998,7 @@ impl OlmMachine { Ok(()) } - pub(crate) fn add_withheld_info(&self, changes: &mut Changes, event: &RoomKeyWithheldEvent) { + fn add_withheld_info(&self, changes: &mut Changes, event: &RoomKeyWithheldEvent) { debug!(?event.content, "Processing `m.room_key.withheld` event"); if let RoomKeyWithheldContent::MegolmV1AesSha2( From c7cbca57b5773cb58c88561f663f65c54419be0c Mon Sep 17 00:00:00 2001 From: kaylendog Date: Mon, 6 Oct 2025 15:04:09 +0100 Subject: [PATCH 10/18] docs(crypto): Clarify doc comments for RoomKeyWithheldEntry. --- crates/matrix-sdk-crypto/src/store/types.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/store/types.rs b/crates/matrix-sdk-crypto/src/store/types.rs index f7eb9b87a1a..552a78d9baf 100644 --- a/crates/matrix-sdk-crypto/src/store/types.rs +++ b/crates/matrix-sdk-crypto/src/store/types.rs @@ -487,8 +487,10 @@ 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. + /// 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/4362 pub withheld_event: RoomKeyWithheldEntry, } @@ -496,9 +498,13 @@ pub struct RoomKeyWithheldInfo { /// to-device event or a bundle entry. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RoomKeyWithheldEntry { - /// The ID of the user who sent the bundle. + /// 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/4362 pub sender: OwnedUserId, - /// The contents of a single withheld entry in the bundle. + /// The content of the entry, which provides details about the reason the + /// key was withheld. pub content: RoomKeyWithheldContent, } From a581fdd76c6bf8c96b4e0782effd1d999d708b3b Mon Sep 17 00:00:00 2001 From: kaylendog Date: Mon, 6 Oct 2025 15:36:43 +0100 Subject: [PATCH 11/18] refactor(cryptor): Split `receive_room_key_bundle` to helper methods. - Adds helper methods on `MegolmV1AesSha2WithheldContent` to access room and session ID. - Splits session import logic to `import_room_key_bundle_sessions`. - Splits withheld info import logic to `import_room_key_bundle_withheld_info`. --- crates/matrix-sdk-crypto/src/store/mod.rs | 143 +++++++++++------- .../src/types/events/room_key_withheld.rs | 26 +++- 2 files changed, 114 insertions(+), 55 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 5196ac4ea71..8fd2bb554d4 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}; @@ -78,8 +78,8 @@ use crate::{ }, store::types::RoomKeyWithheldEntry, types::{ - events::room_key_withheld::MegolmV1AesSha2WithheldContent, BackupSecrets, - CrossSigningSecrets, MegolmBackupV1Curve25519AesSha2Secrets, RoomKeyExport, SecretsBundle, + BackupSecrets, CrossSigningSecrets, MegolmBackupV1Curve25519AesSha2Secrets, RoomKeyExport, + SecretsBundle, }, verification::VerificationMachine, CrossSigningStatus, OwnUserIdentityData, RoomKeyImportResult, @@ -1633,74 +1633,108 @@ 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"); + | 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?; - } + ); } + + 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 { - if let RoomKeyWithheldContent::MegolmV1AesSha2( - MegolmV1AesSha2WithheldContent::BlackListed(c) - | MegolmV1AesSha2WithheldContent::Unverified(c) - | MegolmV1AesSha2WithheldContent::Unauthorised(c) - | MegolmV1AesSha2WithheldContent::Unavailable(c), - ) = withheld - { - changes.withheld_session_info.entry(c.room_id.to_owned()).or_default().insert( - c.session_id.to_owned(), - RoomKeyWithheldEntry { - sender: bundle_info.sender_user.clone(), - content: withheld.to_owned(), - }, - ); + let (room_id, session_id) = match withheld { + #[cfg(not(feature = "experimental-algorithms"))] + 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, + }, + _ => 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?; @@ -2133,6 +2167,7 @@ mod tests { ); 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` 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 { From 948402f717220bb5953be9cdacd8b7003354f476 Mon Sep 17 00:00:00 2001 From: kaylendog Date: Mon, 6 Oct 2025 16:28:11 +0100 Subject: [PATCH 12/18] test(crypto): Add fix for `experimental-algorithms` feature. --- crates/matrix-sdk-crypto/src/store/mod.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 8fd2bb554d4..868eeec3d25 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -2114,13 +2114,22 @@ mod tests { assert_eq!(imported_sessions[0].room_id(), room_id); assert_matches!( - bob.store().get_withheld_info(room_id, sessions[1].session_id()).await.unwrap(), - Some(RoomKeyWithheldEntry { + 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(_) + ), .. - }) + } ); } @@ -2185,7 +2194,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, ) From 88e0dfb94b815c87dfc029eb966e709bd92af114 Mon Sep 17 00:00:00 2001 From: kaylendog Date: Mon, 6 Oct 2025 17:09:15 +0100 Subject: [PATCH 13/18] test(crypto): Redact algorithm field in room key bundle snapshot. --- crates/matrix-sdk-crypto/src/store/mod.rs | 13 +++++++++++++ ...crypto__store__tests__build_room_key_bundle.snap | 6 +++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 868eeec3d25..abaa7a6f945 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -2016,6 +2016,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<'_>| { @@ -2030,6 +2041,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), 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.", From 0d563459f49d8940b6ebdc8464642accf1c78aa4 Mon Sep 17 00:00:00 2001 From: Skye Elliot Date: Tue, 7 Oct 2025 16:41:48 +0100 Subject: [PATCH 14/18] feat(crypto): Add all withheld types (with room ID) to store. --- crates/matrix-sdk-crypto/src/machine/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk-crypto/src/machine/mod.rs b/crates/matrix-sdk-crypto/src/machine/mod.rs index 4768164d694..c9d2ba3edca 100644 --- a/crates/matrix-sdk-crypto/src/machine/mod.rs +++ b/crates/matrix-sdk-crypto/src/machine/mod.rs @@ -1003,7 +1003,9 @@ impl OlmMachine { if let RoomKeyWithheldContent::MegolmV1AesSha2( MegolmV1AesSha2WithheldContent::BlackListed(c) - | MegolmV1AesSha2WithheldContent::Unverified(c), + | MegolmV1AesSha2WithheldContent::Unverified(c) + | MegolmV1AesSha2WithheldContent::Unauthorised(c) + | MegolmV1AesSha2WithheldContent::Unavailable(c), ) = &event.content { changes From 869007e7e6d0c4b2f064da627844f9da916f270f Mon Sep 17 00:00:00 2001 From: Skye Elliot Date: Thu, 9 Oct 2025 11:40:19 +0100 Subject: [PATCH 15/18] docs(crypto): Point URLs to correct MSC. --- crates/matrix-sdk-crypto/src/store/types.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/store/types.rs b/crates/matrix-sdk-crypto/src/store/types.rs index 552a78d9baf..5b5414b8421 100644 --- a/crates/matrix-sdk-crypto/src/store/types.rs +++ b/crates/matrix-sdk-crypto/src/store/types.rs @@ -490,7 +490,7 @@ pub struct RoomKeyWithheldInfo { /// 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/4362 + /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268 pub withheld_event: RoomKeyWithheldEntry, } @@ -501,7 +501,7 @@ 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/4362 + /// [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. From 48f87a49a6d5fe198134d80c8b06ffd837439ee5 Mon Sep 17 00:00:00 2001 From: Skye Elliot Date: Thu, 9 Oct 2025 11:58:11 +0100 Subject: [PATCH 16/18] docs(crypto): Clarify session algorithm dependency on feature flag. --- crates/matrix-sdk-crypto/src/store/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index abaa7a6f945..414b016e9d5 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -2194,6 +2194,12 @@ mod tests { /// /// `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, From 1510108bf1ede639f211768921d80626a2395e1a Mon Sep 17 00:00:00 2001 From: Skye Elliot Date: Thu, 9 Oct 2025 11:59:56 +0100 Subject: [PATCH 17/18] feat(crypto): Explicit `RoomKeyWithheldContent::Unknown` match case. --- crates/matrix-sdk-crypto/src/store/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 414b016e9d5..86d6c8e89a1 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -1720,7 +1720,7 @@ impl Store { (Some(room_id), Some(session_id)) => (room_id, session_id), _ => continue, }, - _ => continue, + RoomKeyWithheldContent::Unknown(_) => continue, }; if room_id != bundle_info.bundle_data.room_id { From 741505d245bb641532f1917c8ef34a2d64947f8e Mon Sep 17 00:00:00 2001 From: Skye Elliot Date: Thu, 9 Oct 2025 12:48:46 +0100 Subject: [PATCH 18/18] fix(crypto): Remove unnecessary inverted feature flag. --- crates/matrix-sdk-crypto/src/store/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 86d6c8e89a1..2fdd884523c 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -1710,7 +1710,6 @@ impl Store { let mut changes = Changes::default(); for withheld in &bundle.withheld { let (room_id, session_id) = match withheld { - #[cfg(not(feature = "experimental-algorithms"))] RoomKeyWithheldContent::MegolmV1AesSha2(c) => match (c.room_id(), c.session_id()) { (Some(room_id), Some(session_id)) => (room_id, session_id), _ => continue,