From bfa89bc73fbd93495c909e2b5b92605e02a5ff51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 20 Feb 2025 12:14:49 +0100 Subject: [PATCH] feat(crypto): Add support for the shared_history flag defined in MSC3061 This patch adds support for the `shared_history` flag from MSC3061 to the `m.room_key` content, exported room keys, and backed-up room keys. The flag is now persisted in our `InboundGroupSession`. Additionally, when creating a new `InboundGroupSession`, we ensure the `shared_history` flag is set appropriately. MSC3061: https://github.com/matrix-org/matrix-spec-proposals/pull/3061 --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 1 + crates/matrix-sdk-crypto/CHANGELOG.md | 11 +++++ .../tests/decryption_verification_state.rs | 2 + crates/matrix-sdk-crypto/src/olm/account.rs | 32 ++++++++++++- .../src/olm/group_sessions/inbound.rs | 45 ++++++++++++++++++- .../src/olm/group_sessions/mod.rs | 24 +++++++++- .../src/olm/group_sessions/outbound.rs | 4 ++ .../olm/group_sessions/sender_data_finder.rs | 2 + .../src/store/integration_tests.rs | 1 + .../src/store/memorystore.rs | 2 + .../src/types/events/room_key.rs | 18 +++++++- .../src/types/events/to_device.rs | 1 + .../src/crypto_store/migrations/mod.rs | 3 ++ .../src/crypto_store/mod.rs | 1 + .../tests/integration/encryption/backups.rs | 1 + 15 files changed, 143 insertions(+), 5 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 50e99cb50..9ebd94c27 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -507,6 +507,7 @@ fn collect_sessions( imported: session.imported, backed_up: session.backed_up, history_visibility: None, + shared_history: false, algorithm: RustEventEncryptionAlgorithm::MegolmV1AesSha2, }; diff --git a/crates/matrix-sdk-crypto/CHANGELOG.md b/crates/matrix-sdk-crypto/CHANGELOG.md index b9c9b34d8..c86e561de 100644 --- a/crates/matrix-sdk-crypto/CHANGELOG.md +++ b/crates/matrix-sdk-crypto/CHANGELOG.md @@ -6,6 +6,17 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - ReleaseDate +### Features + +- [**breaking**] Add support for the shared history flag defined in [MSC3061](https://github.com/matrix-org/matrix-spec-proposals/pull/3061). + The shared history flag is now respected when room keys are received as an + `m.room_key` event as well as when they are imported from a backup or a file + export. We also ensure to set the flag when we send out room keys. Due to + this, a new argument to the constructor for `room_key::MegolmV1AesSha2Content` + has been added and `PickledInboundGroupSession` has received a new + `shared_history` field that defaults to `false.` + ([#4700](https://github.com/matrix-org/matrix-rust-sdk/pull/4700)) + ## [0.10.0] - 2025-02-04 ### Features diff --git a/crates/matrix-sdk-crypto/src/machine/tests/decryption_verification_state.rs b/crates/matrix-sdk-crypto/src/machine/tests/decryption_verification_state.rs index 82cb3d707..2ec499cf9 100644 --- a/crates/matrix-sdk-crypto/src/machine/tests/decryption_verification_state.rs +++ b/crates/matrix-sdk-crypto/src/machine/tests/decryption_verification_state.rs @@ -353,6 +353,7 @@ async fn test_verification_states_multiple_device() { SenderData::unknown(), EventEncryptionAlgorithm::MegolmV1AesSha2, None, + false, ) .unwrap(); @@ -370,6 +371,7 @@ async fn test_verification_states_multiple_device() { SenderData::unknown(), EventEncryptionAlgorithm::MegolmV1AesSha2, None, + false, ) .unwrap(); diff --git a/crates/matrix-sdk-crypto/src/olm/account.rs b/crates/matrix-sdk-crypto/src/olm/account.rs index bb08b9fc5..fc5f34fdb 100644 --- a/crates/matrix-sdk-crypto/src/olm/account.rs +++ b/crates/matrix-sdk-crypto/src/olm/account.rs @@ -32,7 +32,7 @@ use ruma::{ upload_signatures::v3::{Request as SignatureUploadRequest, SignedKeys}, }, }, - events::AnyToDeviceEvent, + events::{room::history_visibility::HistoryVisibility, AnyToDeviceEvent}, serde::Raw, DeviceId, DeviceKeyAlgorithm, DeviceKeyId, MilliSecondsSinceUnixEpoch, OneTimeKeyAlgorithm, OneTimeKeyId, OwnedDeviceId, OwnedDeviceKeyId, OwnedOneTimeKeyId, OwnedUserId, RoomId, @@ -220,6 +220,7 @@ impl StaticAccountData { let sender_key = identity_keys.curve25519; let signing_key = identity_keys.ed25519; + let shared_history = shared_history_from_history_visibility(&visibility); let inbound = InboundGroupSession::new( sender_key, @@ -229,6 +230,7 @@ impl StaticAccountData { own_sender_data, algorithm, Some(visibility), + shared_history, )?; Ok((outbound, inbound)) @@ -1511,6 +1513,34 @@ impl PartialEq for Account { } } +/// Calculate the shared history flag from the history visibility as defined in +/// [MSC3061] +/// +/// The MSC defines that the shared history flag should be set to true when the +/// history visibility setting is set to `shared` or `world_readable`: +/// +/// > A room key is flagged as having been used for shared history when it was +/// > used to encrypt a message while the room's history visibility setting +/// > was set to world_readable or shared. +/// +/// In all other cases, even if we encounter a custom history visibility, we +/// should return false: +/// +/// > If the client does not have an m.room.history_visibility state event for +/// > the room, or its value is not understood, the client should treat it as if +/// > its value is joined for the purposes of determining whether the key is +/// > used for shared history. +/// +/// [MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061 +pub(crate) fn shared_history_from_history_visibility( + history_visibility: &HistoryVisibility, +) -> bool { + match history_visibility { + HistoryVisibility::Shared | HistoryVisibility::WorldReadable => true, + HistoryVisibility::Invited | HistoryVisibility::Joined | _ => false, + } +} + /// Expand the pickle key for an older version of dehydrated devices /// /// The `org.matrix.msc3814.v1.olm` variant of dehydrated devices used the diff --git a/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs b/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs index 778ed47a1..763a463b2 100644 --- a/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs @@ -154,6 +154,13 @@ pub struct InboundGroupSession { /// Was this room key backed up to the server. backed_up: Arc, + + /// Whether this [`InboundGroupSession`] can be shared with users who are + /// invited to the room in the future, allowing access to history, as + /// defined in [MSC3061]. + /// + /// [MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061 + shared_history: bool, } impl InboundGroupSession { @@ -176,6 +183,7 @@ impl InboundGroupSession { /// /// * `sender_data` - Information about the sender of the to-device message /// that established this session. + #[allow(clippy::too_many_arguments)] pub fn new( sender_key: Curve25519PublicKey, signing_key: Ed25519PublicKey, @@ -184,6 +192,7 @@ impl InboundGroupSession { sender_data: SenderData, encryption_algorithm: EventEncryptionAlgorithm, history_visibility: Option, + shared_history: bool, ) -> Result { let config = OutboundGroupSession::session_config(&encryption_algorithm)?; @@ -208,6 +217,7 @@ impl InboundGroupSession { imported: false, algorithm: encryption_algorithm.into(), backed_up: AtomicBool::new(false).into(), + shared_history, }) } @@ -228,7 +238,13 @@ impl InboundGroupSession { signing_key: Ed25519PublicKey, content: &room_key::MegolmV1AesSha2Content, ) -> Result { - let room_key::MegolmV1AesSha2Content { room_id, session_id: _, session_key, .. } = content; + let room_key::MegolmV1AesSha2Content { + room_id, + session_id: _, + session_key, + shared_history, + .. + } = content; Self::new( sender_key, @@ -238,6 +254,7 @@ impl InboundGroupSession { SenderData::unknown(), EventEncryptionAlgorithm::MegolmV1AesSha2, None, + *shared_history, ) } @@ -265,6 +282,7 @@ impl InboundGroupSession { backed_up: self.backed_up(), history_visibility: self.history_visibility.as_ref().clone(), algorithm: (*self.algorithm).to_owned(), + shared_history: self.shared_history, } } @@ -317,6 +335,7 @@ impl InboundGroupSession { forwarding_curve25519_key_chain: vec![], sender_claimed_keys: (*self.creator_info.signing_keys).clone(), session_key, + shared_history: self.shared_history, } } @@ -342,6 +361,7 @@ impl InboundGroupSession { backed_up, history_visibility, algorithm, + shared_history, } = pickle; let session: InnerSession = pickle.into(); @@ -362,6 +382,7 @@ impl InboundGroupSession { backed_up: AtomicBool::from(backed_up).into(), algorithm: algorithm.into(), imported, + shared_history, }) } @@ -511,6 +532,15 @@ impl InboundGroupSession { pub fn sender_data_type(&self) -> SenderDataType { self.sender_data.to_type() } + + /// Whether this [`InboundGroupSession`] can be shared with users who are + /// invited to the room in the future, allowing access to history, as + /// defined in [MSC3061]. + /// + /// [MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061 + pub fn shared_history(&self) -> bool { + self.shared_history + } } #[cfg(not(tarpaulin_include))] @@ -556,6 +586,13 @@ pub struct PickledInboundGroupSession { /// The algorithm of this inbound group session. #[serde(default = "default_algorithm")] pub algorithm: EventEncryptionAlgorithm, + /// Whether this [`InboundGroupSession`] can be shared with users who are + /// invited to the room in the future, allowing access to history, as + /// defined in [MSC3061]. + /// + /// [MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061 + #[serde(default)] + pub shared_history: bool, } fn default_algorithm() -> EventEncryptionAlgorithm { @@ -574,6 +611,7 @@ impl TryFrom<&ExportedRoomKey> for InboundGroupSession { session_key, sender_claimed_keys, forwarding_curve25519_key_chain: _, + shared_history, } = key; let config = OutboundGroupSession::session_config(algorithm)?; @@ -596,6 +634,7 @@ impl TryFrom<&ExportedRoomKey> for InboundGroupSession { imported: true, algorithm: algorithm.to_owned().into(), backed_up: AtomicBool::from(false).into(), + shared_history: *shared_history, }) } } @@ -626,6 +665,7 @@ impl From<&ForwardedMegolmV1AesSha2Content> for InboundGroupSession { imported: true, algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2.into(), backed_up: AtomicBool::from(false).into(), + shared_history: false, } } } @@ -652,6 +692,7 @@ impl From<&ForwardedMegolmV2AesSha2Content> for InboundGroupSession { imported: true, algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2.into(), backed_up: AtomicBool::from(false).into(), + shared_history: false, } } } @@ -768,6 +809,7 @@ mod tests { SenderData::unknown(), EventEncryptionAlgorithm::MegolmV1AesSha2, Some(HistoryVisibility::Shared), + false, ) .unwrap(); @@ -814,6 +856,7 @@ mod tests { "room_id":"!test:localhost", "imported":false, "backed_up":false, + "shared_history":false, "history_visibility":"shared", "algorithm":"m.megolm.v1.aes-sha2" }) diff --git a/crates/matrix-sdk-crypto/src/olm/group_sessions/mod.rs b/crates/matrix-sdk-crypto/src/olm/group_sessions/mod.rs index 8f2d974d6..3b67809f0 100644 --- a/crates/matrix-sdk-crypto/src/olm/group_sessions/mod.rs +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/mod.rs @@ -98,6 +98,14 @@ pub struct ExportedRoomKey { serialize_with = "serialize_curve_key_vec" )] pub forwarding_curve25519_key_chain: Vec, + + /// Whether this [`ExportedRoomKey`] can be shared with users who are + /// invited to the room in the future, allowing access to history, as + /// defined in [MSC3061]. + /// + /// [MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061 + #[serde(default, rename = "org.matrix.msc3061.shared_history")] + pub shared_history: bool, } impl ExportedRoomKey { @@ -115,6 +123,7 @@ impl ExportedRoomKey { session_key, sender_claimed_keys, forwarding_curve25519_key_chain, + shared_history, } = room_key; Self { @@ -125,6 +134,7 @@ impl ExportedRoomKey { session_key, sender_claimed_keys, forwarding_curve25519_key_chain, + shared_history, } } } @@ -152,13 +162,21 @@ pub struct BackedUpRoomKey { pub sender_claimed_keys: SigningKeys, /// Chain of Curve25519 keys through which this session was forwarded, via - /// m.forwarded_room_key events. + /// `m.forwarded_room_key` events. #[serde( default, deserialize_with = "deserialize_curve_key_vec", serialize_with = "serialize_curve_key_vec" )] pub forwarding_curve25519_key_chain: Vec, + + /// Whether this [`BackedUpRoomKey`] can be shared with users who are + /// invited to the room in the future, allowing access to history, as + /// defined in [MSC3061]. + /// + /// [MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061 + #[serde(default, rename = "org.matrix.msc3061.shared_history")] + pub shared_history: bool, } impl TryFrom for ForwardedRoomKeyContent { @@ -228,6 +246,7 @@ impl From for BackedUpRoomKey { session_key, sender_claimed_keys, forwarding_curve25519_key_chain, + shared_history, } = value; Self { @@ -236,6 +255,7 @@ impl From for BackedUpRoomKey { session_key, sender_claimed_keys, forwarding_curve25519_key_chain, + shared_history, } } } @@ -261,6 +281,7 @@ impl TryFrom for ExportedRoomKey { sender_claimed_keys, sender_key: content.claimed_sender_key, session_key: content.session_key, + shared_history: false, }) } #[cfg(feature = "experimental-algorithms")] @@ -272,6 +293,7 @@ impl TryFrom for ExportedRoomKey { sender_claimed_keys: content.claimed_signing_keys, sender_key: content.claimed_sender_key, session_key: content.session_key, + shared_history: false, }), ForwardedRoomKeyContent::Unknown(c) => Err(SessionExportError::Algorithm(c.algorithm)), } diff --git a/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs b/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs index 9e079eb79..21e4eeb8e 100644 --- a/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs @@ -47,6 +47,7 @@ use super::SessionCreationError; #[cfg(feature = "experimental-algorithms")] use crate::types::events::room::encrypted::MegolmV2AesSha2Content; use crate::{ + olm::account::shared_history_from_history_visibility, session_manager::CollectStrategy, store::caches::SequenceNumber, types::{ @@ -526,12 +527,15 @@ impl OutboundGroupSession { pub(crate) async fn as_content(&self) -> RoomKeyContent { let session_key = self.session_key().await; + let shared_history = + shared_history_from_history_visibility(&self.settings.history_visibility); RoomKeyContent::MegolmV1AesSha2( MegolmV1AesSha2RoomKeyContent::new( self.room_id().to_owned(), self.session_id().to_owned(), session_key, + shared_history, ) .into(), ) diff --git a/crates/matrix-sdk-crypto/src/olm/group_sessions/sender_data_finder.rs b/crates/matrix-sdk-crypto/src/olm/group_sessions/sender_data_finder.rs index 0f23f013f..2a9ee9b96 100644 --- a/crates/matrix-sdk-crypto/src/olm/group_sessions/sender_data_finder.rs +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/sender_data_finder.rs @@ -861,6 +861,7 @@ mod tests { SenderData::unknown(), EventEncryptionAlgorithm::MegolmV1AesSha2, None, + false, ) .unwrap(); if options.session_is_imported { @@ -1083,6 +1084,7 @@ mod tests { room_id.to_owned(), "mysession".to_owned(), clone_session_key(session_key), + false, ))), ) } diff --git a/crates/matrix-sdk-crypto/src/store/integration_tests.rs b/crates/matrix-sdk-crypto/src/store/integration_tests.rs index 7d368e7a0..2c0f9baf5 100644 --- a/crates/matrix-sdk-crypto/src/store/integration_tests.rs +++ b/crates/matrix-sdk-crypto/src/store/integration_tests.rs @@ -1308,6 +1308,7 @@ macro_rules! cryptostore_integration_tests { sender_data, EventEncryptionAlgorithm::MegolmV1AesSha2, None, + false, ) .unwrap() } diff --git a/crates/matrix-sdk-crypto/src/store/memorystore.rs b/crates/matrix-sdk-crypto/src/store/memorystore.rs index 6b9e81e5a..89ade3e64 100644 --- a/crates/matrix-sdk-crypto/src/store/memorystore.rs +++ b/crates/matrix-sdk-crypto/src/store/memorystore.rs @@ -806,6 +806,7 @@ mod tests { SenderData::unknown(), outbound.settings().algorithm.to_owned(), None, + false, ) .unwrap(); @@ -1203,6 +1204,7 @@ mod tests { SenderData::unknown(), outbound.settings().algorithm.to_owned(), None, + false, ) .unwrap() } diff --git a/crates/matrix-sdk-crypto/src/types/events/room_key.rs b/crates/matrix-sdk-crypto/src/types/events/room_key.rs index c3c3d0aec..7199db27e 100644 --- a/crates/matrix-sdk-crypto/src/types/events/room_key.rs +++ b/crates/matrix-sdk-crypto/src/types/events/room_key.rs @@ -113,6 +113,13 @@ pub struct MegolmV1AesSha2Content { /// /// [`InboundGroupSession`]: vodozemac::megolm::InboundGroupSession pub session_key: SessionKey, + /// Whether this room key can be shared with users who are invited to the + /// room in the future, allowing access to history, as defined in + /// [MSC3061]. + /// + /// [MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061 + #[serde(default, rename = "org.matrix.msc3061.shared_history")] + pub shared_history: bool, /// Any other, custom and non-specced fields of the content. #[serde(flatten)] other: BTreeMap, @@ -120,8 +127,13 @@ pub struct MegolmV1AesSha2Content { impl MegolmV1AesSha2Content { /// Create a new `m.megolm.v1.aes-sha2` `m.room_key` content. - pub fn new(room_id: OwnedRoomId, session_id: String, session_key: SessionKey) -> Self { - Self { room_id, session_id, session_key, other: Default::default() } + pub fn new( + room_id: OwnedRoomId, + session_id: String, + session_key: SessionKey, + shared_history: bool, + ) -> Self { + Self { room_id, session_id, session_key, other: Default::default(), shared_history } } } @@ -206,6 +218,7 @@ impl Serialize for RoomKeyContent { pub(super) mod tests { use assert_matches::assert_matches; use serde_json::{json, Value}; + use similar_asserts::assert_eq; use super::RoomKeyEvent; use crate::types::events::room_key::RoomKeyContent; @@ -217,6 +230,7 @@ pub(super) mod tests { "m.custom": "something custom", "algorithm": "m.megolm.v1.aes-sha2", "room_id": "!Cuyf34gef24t:localhost", + "org.matrix.msc3061.shared_history": false, "session_id": "ZFD6+OmV7fVCsJ7Gap8UnORH8EnmiAkes8FAvQuCw/I", "session_key": "AgAAAADNp1EbxXYOGmJtyX4AkD1bvJvAUyPkbIaKxtnGKjv\ SQ3E/4mnuqdM4vsmNzpO1EeWzz1rDkUpYhYE9kP7sJhgLXi\ diff --git a/crates/matrix-sdk-crypto/src/types/events/to_device.rs b/crates/matrix-sdk-crypto/src/types/events/to_device.rs index ee6cdc739..fb6cf780f 100644 --- a/crates/matrix-sdk-crypto/src/types/events/to_device.rs +++ b/crates/matrix-sdk-crypto/src/types/events/to_device.rs @@ -408,6 +408,7 @@ impl Serialize for ToDeviceEvents { mod tests { use assert_matches::assert_matches; use serde_json::{json, Value}; + use similar_asserts::assert_eq; use super::ToDeviceEvents; diff --git a/crates/matrix-sdk-indexeddb/src/crypto_store/migrations/mod.rs b/crates/matrix-sdk-indexeddb/src/crypto_store/migrations/mod.rs index b12e5055e..2ba66c426 100644 --- a/crates/matrix-sdk-indexeddb/src/crypto_store/migrations/mod.rs +++ b/crates/matrix-sdk-indexeddb/src/crypto_store/migrations/mod.rs @@ -478,6 +478,7 @@ mod tests { SenderData::unknown(), encryption_algorithm, history_visibility, + false, ) .unwrap() } @@ -636,6 +637,7 @@ mod tests { SenderData::legacy(), EventEncryptionAlgorithm::MegolmV1AesSha2, None, + false, ) .unwrap(); backed_up_session.mark_as_backed_up(); @@ -655,6 +657,7 @@ mod tests { SenderData::legacy(), EventEncryptionAlgorithm::MegolmV1AesSha2, None, + false, ) .unwrap(); diff --git a/crates/matrix-sdk-indexeddb/src/crypto_store/mod.rs b/crates/matrix-sdk-indexeddb/src/crypto_store/mod.rs index 94136c6b6..7f0658956 100644 --- a/crates/matrix-sdk-indexeddb/src/crypto_store/mod.rs +++ b/crates/matrix-sdk-indexeddb/src/crypto_store/mod.rs @@ -1837,6 +1837,7 @@ mod unit_tests { sender_data, EventEncryptionAlgorithm::MegolmV1AesSha2, None, + false, ) .unwrap(); diff --git a/crates/matrix-sdk/tests/integration/encryption/backups.rs b/crates/matrix-sdk/tests/integration/encryption/backups.rs index fe56cf0f0..2ca8fc74e 100644 --- a/crates/matrix-sdk/tests/integration/encryption/backups.rs +++ b/crates/matrix-sdk/tests/integration/encryption/backups.rs @@ -1586,6 +1586,7 @@ async fn inbound_session_from_outbound_session( SenderData::unknown(), EventEncryptionAlgorithm::MegolmV1AesSha2, None, + false, ) }