mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-18 13:40:55 -04:00
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
This commit is contained in:
@@ -507,6 +507,7 @@ fn collect_sessions(
|
||||
imported: session.imported,
|
||||
backed_up: session.backed_up,
|
||||
history_visibility: None,
|
||||
shared_history: false,
|
||||
algorithm: RustEventEncryptionAlgorithm::MegolmV1AesSha2,
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -154,6 +154,13 @@ pub struct InboundGroupSession {
|
||||
|
||||
/// Was this room key backed up to the server.
|
||||
backed_up: Arc<AtomicBool>,
|
||||
|
||||
/// 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<HistoryVisibility>,
|
||||
shared_history: bool,
|
||||
) -> Result<Self, SessionCreationError> {
|
||||
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<Self, SessionCreationError> {
|
||||
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"
|
||||
})
|
||||
|
||||
@@ -98,6 +98,14 @@ pub struct ExportedRoomKey {
|
||||
serialize_with = "serialize_curve_key_vec"
|
||||
)]
|
||||
pub forwarding_curve25519_key_chain: Vec<Curve25519PublicKey>,
|
||||
|
||||
/// 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<DeviceKeyAlgorithm>,
|
||||
|
||||
/// 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<Curve25519PublicKey>,
|
||||
|
||||
/// 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<ExportedRoomKey> for ForwardedRoomKeyContent {
|
||||
@@ -228,6 +246,7 @@ impl From<ExportedRoomKey> for BackedUpRoomKey {
|
||||
session_key,
|
||||
sender_claimed_keys,
|
||||
forwarding_curve25519_key_chain,
|
||||
shared_history,
|
||||
} = value;
|
||||
|
||||
Self {
|
||||
@@ -236,6 +255,7 @@ impl From<ExportedRoomKey> for BackedUpRoomKey {
|
||||
session_key,
|
||||
sender_claimed_keys,
|
||||
forwarding_curve25519_key_chain,
|
||||
shared_history,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,6 +281,7 @@ impl TryFrom<ForwardedRoomKeyContent> 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<ForwardedRoomKeyContent> 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)),
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
))),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1308,6 +1308,7 @@ macro_rules! cryptostore_integration_tests {
|
||||
sender_data,
|
||||
EventEncryptionAlgorithm::MegolmV1AesSha2,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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<String, Value>,
|
||||
@@ -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\
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -1837,6 +1837,7 @@ mod unit_tests {
|
||||
sender_data,
|
||||
EventEncryptionAlgorithm::MegolmV1AesSha2,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -1586,6 +1586,7 @@ async fn inbound_session_from_outbound_session(
|
||||
SenderData::unknown(),
|
||||
EventEncryptionAlgorithm::MegolmV1AesSha2,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user