From cf17a05eccdf288f37eaa0dff9be2002bb91c062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 29 Mar 2023 10:45:26 +0200 Subject: [PATCH] Restructure the withheld code types --- crates/matrix-sdk-crypto/src/machine.rs | 15 +- .../src/olm/group_sessions/outbound.rs | 15 +- .../src/session_manager/group_sessions.rs | 10 +- .../src/store/integration_tests.rs | 43 +- .../src/store/memorystore.rs | 20 +- .../matrix-sdk-crypto/src/store/withheld.rs | 142 +++++- .../src/types/events/room_key_withheld.rs | 468 +++++++++--------- .../matrix-sdk-indexeddb/src/crypto_store.rs | 5 +- crates/matrix-sdk-sled/src/crypto_store.rs | 11 +- 9 files changed, 399 insertions(+), 330 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index 3cb1bad0c..0ee514e62 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -1289,15 +1289,15 @@ impl OlmMachine { if let MegolmError::Decryption(DecryptionError::UnknownMessageIndex(_, _)) = error { - let withheld_info = self + let withheld_code = self .store .get_withheld_info(room_id, content.session_id()) .await? - .map(|i| i.withheld_code); + .map(|i| i.withheld_code()); - if withheld_info.is_some() { + if withheld_code.is_some() { // Partially withheld, report with a withheld code if we have one. - MegolmError::MissingRoomKey(withheld_info) + MegolmError::MissingRoomKey(withheld_code) } else { error } @@ -1307,12 +1307,13 @@ impl OlmMachine { ), } } else { - let withheld_info = self + let withheld_code = self .store .get_withheld_info(room_id, content.session_id()) .await? - .map(|i| i.withheld_code); - Err(MegolmError::MissingRoomKey(withheld_info)) + .map(|i| i.withheld_code()); + + Err(MegolmError::MissingRoomKey(withheld_code)) } } 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 e6f7f23c3..8a64d8b9f 100644 --- a/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs @@ -51,7 +51,7 @@ use crate::{ MegolmV1AesSha2Content, RoomEncryptedEventContent, RoomEventEncryptionScheme, }, room_key::{MegolmV1AesSha2Content as MegolmV1AesSha2RoomKeyContent, RoomKeyContent}, - room_key_withheld::WithheldCode, + room_key_withheld::{RoomKeyWithheldContent, WithheldCode}, }, EventEncryptionAlgorithm, }, @@ -242,6 +242,19 @@ impl OutboundGroupSession { self.to_share_with_set.insert(request_id, (request, share_infos)); } + /// Create a new `m.room_key.withheld` event content with the given code for + /// this outbound group session. + pub fn withheld_code(&self, code: WithheldCode) -> RoomKeyWithheldContent { + RoomKeyWithheldContent::new( + self.settings().algorithm.to_owned(), + code, + self.room_id().to_owned(), + self.session_id().to_owned(), + self.sender_key().to_owned(), + (*self.device_id).to_owned(), + ) + } + /// This should be called if an the user wishes to rotate this session. pub fn invalidate_session(&self) { self.invalidated.store(true, Ordering::Relaxed) diff --git a/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs b/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs index 49fa1a6e6..55e726595 100644 --- a/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs +++ b/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs @@ -586,15 +586,7 @@ impl GroupSessionManager { ) -> OlmResult<()> { // Convert a withheld code for the group session into a to-device event content. let to_content = |code| { - let content = RoomKeyWithheldContent::create( - group_session.settings().algorithm.to_owned(), - code, - group_session.room_id().to_owned(), - group_session.session_id().to_owned(), - group_session.sender_key(), - Some(self.account.device_id().to_owned()), - ); - + let content = group_session.withheld_code(code); Raw::new(&content).expect("We can always serialize a withheld content info").cast() }; diff --git a/crates/matrix-sdk-crypto/src/store/integration_tests.rs b/crates/matrix-sdk-crypto/src/store/integration_tests.rs index d983a70ba..2dd3a9770 100644 --- a/crates/matrix-sdk-crypto/src/store/integration_tests.rs +++ b/crates/matrix-sdk-crypto/src/store/integration_tests.rs @@ -8,7 +8,7 @@ macro_rules! cryptostore_integration_tests { use matrix_sdk_test::async_test; use ruma::{ device_id, encryption::SignedKey, room_id, serde::Base64, user_id, DeviceId, - OwnedDeviceId, OwnedUserId, TransactionId, UserId, + JsOption, OwnedDeviceId, OwnedUserId, TransactionId, UserId, }; use $crate::{ olm::{ @@ -22,7 +22,8 @@ macro_rules! cryptostore_integration_tests { testing::{get_device, get_other_identity, get_own_identity}, types::{ events::{ - room_key_request::MegolmV1AesSha2Content, room_key_withheld::WithheldCode, + room_key_request::MegolmV1AesSha2Content, + room_key_withheld::{CommonWithheldCodeContent, WithheldCode}, }, EventEncryptionAlgorithm, }, @@ -629,30 +630,28 @@ macro_rules! cryptostore_integration_tests { let session_id_1 = "GBnDxGP9i3IkPsz3/ihNr6P7qjIXxSRVWZ1MYmSn09w"; let session_id_2 = "IDLtnNCH2kIr3xIf1B7JFkGpQmTjyMca2jww+X6zeOE"; - let info = DirectWithheldInfo { + let content = CommonWithheldCodeContent { room_id: room_id.to_owned(), - algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, session_id: session_id_1.into(), - claimed_sender_key: Curve25519PublicKey::from_base64( + from_device: JsOption::Undefined, + other: Default::default(), + sender_key: Curve25519PublicKey::from_base64( "9n7mdWKOjr9c4NTlG6zV8dbFtNK79q9vZADoh7nMUwA", ) .unwrap(), - withheld_code: WithheldCode::Unverified, }; + let info = DirectWithheldInfo::new( + EventEncryptionAlgorithm::MegolmV1AesSha2, + WithheldCode::Unverified, + content.to_owned(), + ); info_list.push(info); - - let info = DirectWithheldInfo { - room_id: room_id.to_owned(), - algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, - session_id: session_id_2.into(), - claimed_sender_key: Curve25519PublicKey::from_base64( - "9n7mdWKOjr9c4NTlG6zV8dbFtNK79q9vZADoh7nMUwA", - ) - .unwrap(), - withheld_code: WithheldCode::Blacklisted, - }; - + let info = DirectWithheldInfo::new( + EventEncryptionAlgorithm::MegolmV1AesSha2, + WithheldCode::Blacklisted, + content, + ); info_list.push(info); let changes = Changes { withheld_session_info: info_list, ..Default::default() }; @@ -662,9 +661,9 @@ macro_rules! cryptostore_integration_tests { let is_withheld = store.get_withheld_info(room_id, session_id_1).await.unwrap(); if let Some(info) = is_withheld { - let actual_code = info.withheld_code; - assert_eq!(EventEncryptionAlgorithm::MegolmV1AesSha2, info.algorithm); - assert_eq!(room_id, info.room_id); + let actual_code = info.withheld_code(); + assert_eq!(EventEncryptionAlgorithm::MegolmV1AesSha2, info.algorithm()); + assert_eq!(room_id, info.room_id()); assert_eq!(WithheldCode::Unverified, actual_code); } else { panic!(); @@ -673,7 +672,7 @@ macro_rules! cryptostore_integration_tests { let is_withheld = store.get_withheld_info(room_id, session_id_2).await.unwrap(); if let Some(info) = is_withheld { - let actual_code = info.withheld_code; + let actual_code = info.withheld_code(); assert_eq!(WithheldCode::Blacklisted, actual_code); } else { panic!(); diff --git a/crates/matrix-sdk-crypto/src/store/memorystore.rs b/crates/matrix-sdk-crypto/src/store/memorystore.rs index 40f07b0a3..b4b56dd3d 100644 --- a/crates/matrix-sdk-crypto/src/store/memorystore.rs +++ b/crates/matrix-sdk-crypto/src/store/memorystore.rs @@ -150,7 +150,7 @@ impl CryptoStore for MemoryStore { } for info in changes.withheld_session_info { - self.direct_withheld_info.insert(info.session_id.to_owned(), info.to_owned()); + self.direct_withheld_info.insert(info.session_id().to_owned(), info); } Ok(()) @@ -285,19 +285,11 @@ impl CryptoStore for MemoryStore { room_id: &RoomId, session_id: &str, ) -> Result> { - let entry = self.direct_withheld_info.entry(session_id.to_owned()); - - match entry { - Entry::Occupied(e) => { - let found = e.get(); - if found.room_id == room_id { - Ok(Some(found.to_owned())) - } else { - Ok(None) - } - } - Entry::Vacant(_) => Ok(None), - } + Ok(self + .direct_withheld_info + .get(session_id) + .filter(|e| e.value().room_id() == room_id) + .map(|e| e.value().to_owned())) } async fn get_room_settings(&self, _room_id: &RoomId) -> Result> { diff --git a/crates/matrix-sdk-crypto/src/store/withheld.rs b/crates/matrix-sdk-crypto/src/store/withheld.rs index 29b4fa587..34d10be8b 100644 --- a/crates/matrix-sdk-crypto/src/store/withheld.rs +++ b/crates/matrix-sdk-crypto/src/store/withheld.rs @@ -16,33 +16,126 @@ //! to all devices present in the room. Sometimes this may be inadvertent (for //! example, if the sending device is not aware of some devices that have //! joined), but some times, this may be purposeful. -use ruma::OwnedRoomId; + +use ruma::RoomId; use serde::{Deserialize, Serialize}; -use vodozemac::Curve25519PublicKey; use crate::types::{ - events::room_key_withheld::{MegolmV1AesSha2WithheldContent, WithheldCode}, + events::room_key_withheld::{ + CommonWithheldCodeContent, MegolmV1AesSha2WithheldContent, WithheldCode, + }, EventEncryptionAlgorithm, }; -/// /// We want to store when the owner of the group session sent us a withheld /// code. It's not storing withheld code that can be sent in a /// `m.forwarded_room_key`, it's another sort of relation as the same key can be /// withheld for several reasons by different devices. #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] -pub struct DirectWithheldInfo { - /// The room of the group key. - pub room_id: OwnedRoomId, - /// The key algorithm - pub algorithm: EventEncryptionAlgorithm, - /// The group session id. - pub session_id: String, - /// The session creator device identity. - /// Claimed because withheld codes message are sent in clear - pub claimed_sender_key: Curve25519PublicKey, - /// The reason why the key was not distributed - pub withheld_code: WithheldCode, +pub enum DirectWithheldInfo { + MegolmV1(MegolmV1WithheldInfo), + #[cfg(feature = "experimental-algorithms")] + MegolmV2(MegolmV2WithheldInfo), +} + +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +pub enum MegolmV1WithheldInfo { + BlackListed(Box), + Unverified(Box), +} + +#[cfg(feature = "experimental-algorithms")] +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +pub enum MegolmV2WithheldInfo { + BlackListed(Box), + Unverified(Box), +} + +impl DirectWithheldInfo { + pub fn new( + algorithm: EventEncryptionAlgorithm, + code: WithheldCode, + content: CommonWithheldCodeContent, + ) -> Self { + let content = content.into(); + + match algorithm { + EventEncryptionAlgorithm::MegolmV1AesSha2 => match code { + WithheldCode::Blacklisted => { + Self::MegolmV1(MegolmV1WithheldInfo::BlackListed(content)) + } + WithheldCode::Unverified => { + Self::MegolmV1(MegolmV1WithheldInfo::Unverified(content)) + } + _ => panic!("Unsupported direct withheld code"), + }, + #[cfg(feature = "experimental-algorithms")] + EventEncryptionAlgorithm::MegolmV1AesSha2 => match code { + WithheldCode::Blacklisted => { + Self::MegolmV2(MegolmV2WithheldInfo::BlackListed(content)) + } + WithheldCode::Unverified => { + Self::MegolmV2(MegolmV2WithheldInfo::Unverified(content)) + } + _ => panic!("Unsupported direct withheld code"), + }, + _ => todo!(), + } + } + + pub fn algorithm(&self) -> EventEncryptionAlgorithm { + match self { + DirectWithheldInfo::MegolmV1(_) => EventEncryptionAlgorithm::MegolmV1AesSha2, + #[cfg(feature = "experimental-algorithms")] + DirectWithheldInfo::MegolmV2(_) => EventEncryptionAlgorithm::MegolmV2AesSha2, + } + } + + pub fn withheld_code(&self) -> WithheldCode { + match self { + DirectWithheldInfo::MegolmV1(c) => match c { + MegolmV1WithheldInfo::BlackListed(_) => WithheldCode::Blacklisted, + MegolmV1WithheldInfo::Unverified(_) => WithheldCode::Unverified, + }, + #[cfg(feature = "experimental-algorithms")] + DirectWithheldInfo::MegolmV2(c) => match c { + MegolmV2WithheldInfo::BlackListed(_) => WithheldCode::Blacklisted, + MegolmV2WithheldInfo::Unverified(_) => WithheldCode::Unverified, + }, + } + } + + pub fn room_id(&self) -> &RoomId { + match self { + DirectWithheldInfo::MegolmV1(c) => match c { + MegolmV1WithheldInfo::BlackListed(c) | MegolmV1WithheldInfo::Unverified(c) => { + &c.room_id + } + }, + #[cfg(feature = "experimental-algorithms")] + DirectWithheldInfo::MegolmV2(c) => match c { + MegolmV2WithheldInfo::BlackListed(c) | MegolmV2WithheldInfo::Unverified(c) => { + &c.room_id + } + }, + } + } + + pub fn session_id(&self) -> &str { + match self { + DirectWithheldInfo::MegolmV1(c) => match c { + MegolmV1WithheldInfo::BlackListed(c) | MegolmV1WithheldInfo::Unverified(c) => { + &c.session_id + } + }, + #[cfg(feature = "experimental-algorithms")] + DirectWithheldInfo::MegolmV2(c) => match c { + MegolmV2WithheldInfo::BlackListed(c) | MegolmV2WithheldInfo::Unverified(c) => { + &c.session_id + } + }, + } + } } impl TryFrom<&MegolmV1AesSha2WithheldContent> for DirectWithheldInfo { @@ -50,16 +143,15 @@ impl TryFrom<&MegolmV1AesSha2WithheldContent> for DirectWithheldInfo { fn try_from(value: &MegolmV1AesSha2WithheldContent) -> Result { match value { - MegolmV1AesSha2WithheldContent::AnyContent((code, content)) => Ok(DirectWithheldInfo { - room_id: content.room_id.to_owned(), - algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, - session_id: content.session_id.to_owned(), - claimed_sender_key: content.sender_key, - withheld_code: code.clone(), - }), - MegolmV1AesSha2WithheldContent::NoOlmContent(_) => { - Err("No Olm can't be converted to direct info") + MegolmV1AesSha2WithheldContent::BlackListed(c) => { + Ok(Self::MegolmV1(MegolmV1WithheldInfo::BlackListed(c.to_owned()))) } + MegolmV1AesSha2WithheldContent::Unverified(c) => { + Ok(Self::MegolmV1(MegolmV1WithheldInfo::Unverified(c.to_owned()))) + } + MegolmV1AesSha2WithheldContent::Unauthorised(_) => todo!(), + MegolmV1AesSha2WithheldContent::Unavailable(_) => todo!(), + MegolmV1AesSha2WithheldContent::NoOlm(_) => todo!(), } } } 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 33341cac3..20ed4bdd4 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 @@ -13,6 +13,7 @@ // limitations under the License. //! Types for the `m.room_key.withheld` events. + use std::collections::BTreeMap; use ruma::{ @@ -44,97 +45,78 @@ pub type RoomKeyWithheldEvent = ToDeviceEvent; pub enum RoomKeyWithheldContent { /// The `m.megolm.v1.aes-sha2` variant of the `m.room_key.withheld` content. MegolmV1AesSha2(MegolmV1AesSha2WithheldContent), - /// The `m.megolm.v2.aes-sha2` variant of the `m.room_key.withheld` content. + // The `m.megolm.v2.aes-sha2` variant of the `m.room_key.withheld` content. #[cfg(feature = "experimental-algorithms")] MegolmV2AesSha2(MegolmV2AesSha2WithheldContent), /// An unknown and unsupported variant of the `m.room_key.withheld` content. Unknown(UnknownRoomKeyWithHeld), } +macro_rules! construct_withheld_content { + ($algorithm:ident, $code:ident, $room_id:ident, $session_id:ident, $sender_key:ident, $from_device:ident) => { + match $code { + WithheldCode::Blacklisted + | WithheldCode::Unverified + | WithheldCode::Unauthorised + | WithheldCode::Unavailable => { + let content = CommonWithheldCodeContent { + $room_id, + $session_id, + $sender_key, + $from_device, + other: Default::default(), + }; + + RoomKeyWithheldContent::$algorithm( + MegolmV1AesSha2WithheldContent::from_code_and_content($code, content), + ) + } + WithheldCode::NoOlm => { + RoomKeyWithheldContent::$algorithm(MegolmV1AesSha2WithheldContent::NoOlm( + NoOlmWithheldContent { $sender_key, $from_device, other: Default::default() } + .into(), + )) + } + _ => unreachable!("Can't create an unknown withheld code content"), + } + }; +} + impl RoomKeyWithheldContent { /// Creates a withheld content from the given info - pub fn create( + pub fn new( algorithm: EventEncryptionAlgorithm, code: WithheldCode, room_id: OwnedRoomId, session_id: String, sender_key: Curve25519PublicKey, - from_device: Option, + from_device: OwnedDeviceId, ) -> Self { + let from_device = JsOption::Some(from_device); + match algorithm { EventEncryptionAlgorithm::MegolmV1AesSha2 => { - let other = match code { - WithheldCode::NoOlm => { - let content = NoOlmWithheldContent { - sender_key, - reason: JsOption::Some(code.to_string()), - from_device: JsOption::from_implicit_option(from_device), - other: Default::default(), - }; - serde_json::to_value(content) - .expect("We can always serialize this construction") - } - _ => { - let content = AnyWithheldContent { - room_id, - session_id, - sender_key, - // code: code.clone(), - reason: JsOption::Some(code.to_string()), - from_device: JsOption::from_implicit_option(from_device), - other: Default::default(), - }; - serde_json::to_value(content) - .expect("We can always serialize this construction") - } - }; - let helper = WithheldHelper { - algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, + construct_withheld_content!( + MegolmV1AesSha2, code, - other, - }; - RoomKeyWithheldContent::try_from(helper) - .expect("We can always serialize this construction") + room_id, + session_id, + sender_key, + from_device + ) } #[cfg(feature = "experimental-algorithms")] EventEncryptionAlgorithm::MegolmV2AesSha2 => { - let other = match code { - WithheldCode::NoOlm => { - let content = NoOlmWithheldContent { - sender_key, - reason: JsOption::Some(code.to_string()), - from_device: JsOption::from_implicit_option(from_device), - other: Default::default(), - }; - serde_json::to_value(content) - .expect("We can always serialize this construction") - } - _ => { - let content = AnyWithheldContent { - room_id, - session_id, - sender_key, - reason: JsOption::Some(code.to_string()), - from_device: JsOption::from_implicit_option(from_device), - other: Default::default(), - }; - serde_json::to_value(content) - .expect("We can always serialize this construction") - } - }; - let helper = WithheldHelper { - algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, + construct_withheld_content!( + MegolmV2AesSha2, code, - other, - }; - RoomKeyWithheldContent::try_from(helper) - .expect("We can always serialize this construction") + room_id, + session_id, + sender_key, + from_device + ) } - _ => RoomKeyWithheldContent::Unknown(UnknownRoomKeyWithHeld { - algorithm, - code, - other: Default::default(), - }), + _ => todo!(), } } @@ -215,70 +197,74 @@ impl std::fmt::Display for WithheldCode { #[derive(Deserialize, Serialize, Debug)] struct WithheldHelper { pub algorithm: EventEncryptionAlgorithm, + pub reason: JsOption, pub code: WithheldCode, #[serde(flatten)] other: Value, } -/// The content of a withheld event depends on the -/// value of the code. This enum reflect that for better type safety #[derive(Clone, Debug)] pub enum MegolmV1AesSha2WithheldContent { - /// Content for regular withheld content - AnyContent((WithheldCode, Box)), - /// Content for no_olm withheld content - NoOlmContent(Box), + BlackListed(Box), + Unverified(Box), + Unauthorised(Box), + Unavailable(Box), + NoOlm(Box), +} + +#[derive(Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct CommonWithheldCodeContent { + /// The room where the key is used. + pub room_id: OwnedRoomId, + + /// The ID of the session. + pub session_id: String, + + /// The device curve25519 key of the session creator. + #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")] + pub sender_key: Curve25519PublicKey, + + /// The device ID of the device sending the m.room_key.withheld message + /// MSC3735. + #[serde(default, skip_serializing_if = "JsOption::is_undefined")] + pub from_device: JsOption, + + #[serde(flatten)] + pub other: BTreeMap, } impl MegolmV1AesSha2WithheldContent { /// Get the withheld code for this content pub fn withheld_code(&self) -> WithheldCode { match self { - MegolmV1AesSha2WithheldContent::AnyContent((code, _)) => code.clone(), - MegolmV1AesSha2WithheldContent::NoOlmContent(_) => WithheldCode::NoOlm, + MegolmV1AesSha2WithheldContent::BlackListed(_) => WithheldCode::Blacklisted, + MegolmV1AesSha2WithheldContent::Unverified(_) => WithheldCode::Unverified, + MegolmV1AesSha2WithheldContent::Unauthorised(_) => WithheldCode::Unauthorised, + MegolmV1AesSha2WithheldContent::Unavailable(_) => WithheldCode::Unavailable, + MegolmV1AesSha2WithheldContent::NoOlm(_) => WithheldCode::NoOlm, + } + } + + fn from_code_and_content(code: WithheldCode, content: CommonWithheldCodeContent) -> Self { + let content = content.into(); + + match code { + WithheldCode::Blacklisted => Self::BlackListed(content), + WithheldCode::Unverified => Self::Unverified(content), + WithheldCode::Unauthorised => Self::Unauthorised(content), + WithheldCode::Unavailable => Self::Unavailable(content), + _ => unreachable!("This constructor requires one of the common withheld codes"), } } } -/// Devices that purposely do not send megolm keys to a device may instead send -/// an m.room_key.withheld event as a to-device message to the device to -/// indicate that it should not expect to receive keys for the message. -#[derive(Clone, Deserialize, Serialize)] -pub struct AnyWithheldContent { - /// The room where the key is used. - pub room_id: OwnedRoomId, - /// The ID of the session. - pub session_id: String, - #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")] - /// The device curve25519 key of the session creator. - pub sender_key: Curve25519PublicKey, - - /// A human-readable reason for why the key was not sent. The receiving - /// client should only use this string if it does not understand the code - #[serde(default, skip_serializing_if = "JsOption::is_undefined")] - pub reason: JsOption, - - /// The device ID of the device sending the m.room_key.withheld message - /// MSC3735. - #[serde(default, skip_serializing_if = "JsOption::is_undefined")] - pub from_device: JsOption, - - #[serde(flatten)] - other: BTreeMap, -} - -#[derive(Clone, Deserialize, Serialize)] /// No olm content (no room_id nor session_id) +#[derive(Clone, Deserialize, Serialize)] pub struct NoOlmWithheldContent { #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")] /// The device curve25519 key of the session creator. pub sender_key: Curve25519PublicKey, - /// A human-readable reason for why the key was not sent. The receiving - /// client should only use this string if it does not understand the code - #[serde(default, skip_serializing_if = "JsOption::is_undefined")] - pub reason: JsOption, - /// The device ID of the device sending the m.room_key.withheld message /// MSC3735. #[serde(default, skip_serializing_if = "JsOption::is_undefined")] @@ -288,7 +274,7 @@ pub struct NoOlmWithheldContent { other: BTreeMap, } -impl std::fmt::Debug for AnyWithheldContent { +impl std::fmt::Debug for CommonWithheldCodeContent { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("AnyWithheldContent") .field("room_id", &self.room_id) @@ -318,6 +304,8 @@ pub struct UnknownRoomKeyWithHeld { pub algorithm: EventEncryptionAlgorithm, /// The withheld code pub code: WithheldCode, + + pub reason: JsOption, /// The other data of the unknown room key. #[serde(flatten)] other: BTreeMap, @@ -327,39 +315,52 @@ impl TryFrom for RoomKeyWithheldContent { type Error = serde_json::Error; fn try_from(value: WithheldHelper) -> Result { - Ok(match value.algorithm { - EventEncryptionAlgorithm::MegolmV1AesSha2 => { - let content = match value.code { - WithheldCode::NoOlm => { - let content: NoOlmWithheldContent = serde_json::from_value(value.other)?; - MegolmV1AesSha2WithheldContent::NoOlmContent(content.into()) - } - _ => { - let content: AnyWithheldContent = serde_json::from_value(value.other)?; - MegolmV1AesSha2WithheldContent::AnyContent((value.code, content.into())) - } - }; - Self::MegolmV1AesSha2(content) - } - #[cfg(feature = "experimental-algorithms")] - EventEncryptionAlgorithm::MegolmV2AesSha2 => { - let content = match value.code { - WithheldCode::NoOlm => { - let content: NoOlmWithheldContent = serde_json::from_value(value.other)?; - MegolmV1AesSha2WithheldContent::NoOlmContent(content.into()) - } - _ => { - let content: AnyWithheldContent = serde_json::from_value(value.other)?; - MegolmV1AesSha2WithheldContent::AnyContent((value.code, content.into())) - } - }; - Self::MegolmV2AesSha2(content) - } - _ => Self::Unknown(UnknownRoomKeyWithHeld { + let unknown = |value: WithheldHelper| -> Result { + Ok(Self::Unknown(UnknownRoomKeyWithHeld { algorithm: value.algorithm, code: value.code, + reason: value.reason, other: serde_json::from_value(value.other)?, - }), + })) + }; + + Ok(match value.algorithm { + EventEncryptionAlgorithm::MegolmV1AesSha2 => match value.code { + WithheldCode::NoOlm => { + let content: NoOlmWithheldContent = serde_json::from_value(value.other)?; + Self::MegolmV1AesSha2(MegolmV1AesSha2WithheldContent::NoOlm(content.into())) + } + WithheldCode::Blacklisted + | WithheldCode::Unverified + | WithheldCode::Unauthorised + | WithheldCode::Unavailable => { + let content: CommonWithheldCodeContent = serde_json::from_value(value.other)?; + + Self::MegolmV1AesSha2(MegolmV1AesSha2WithheldContent::from_code_and_content( + value.code, content, + )) + } + _ => unknown(value)?, + }, + #[cfg(feature = "experimental-algorithms")] + EventEncryptionAlgorithm::MegolmV2AesSha2 => match value.code { + WithheldCode::NoOlm => { + let content: NoOlmWithheldContent = serde_json::from_value(value.other)?; + Self::MegolmV1AesSha2(MegolmV1AesSha2WithheldContent::NoOlm(content.into())) + } + WithheldCode::Blacklisted + | WithheldCode::Unverified + | WithheldCode::Unauthorised + | WithheldCode::Unavailable => { + let content: CommonWithheldCodeContent = serde_json::from_value(value.other)?; + + Self::MegolmV1AesSha2(MegolmV1AesSha2WithheldContent::from_code_and_content( + value.code, content, + )) + } + _ => unknown(value)?, + }, + _ => unknown(value)?, }) } } @@ -369,35 +370,58 @@ impl Serialize for RoomKeyWithheldContent { where S: serde::Serializer, { + let algorithm = self.algorithm(); + let helper = match self { - Self::MegolmV1AesSha2(r) => match r { - MegolmV2AesSha2WithheldContent::AnyContent((code, content)) => WithheldHelper { - algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, - code: code.clone(), - other: serde_json::to_value(content).map_err(serde::ser::Error::custom)?, - }, - MegolmV2AesSha2WithheldContent::NoOlmContent(content) => WithheldHelper { - algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, - code: WithheldCode::NoOlm, - other: serde_json::to_value(content).map_err(serde::ser::Error::custom)?, - }, - }, + Self::MegolmV1AesSha2(r) => { + let code = r.withheld_code(); + let reason = JsOption::Some(code.to_string()); + + match r { + MegolmV1AesSha2WithheldContent::BlackListed(content) + | MegolmV1AesSha2WithheldContent::Unverified(content) + | MegolmV1AesSha2WithheldContent::Unauthorised(content) + | MegolmV1AesSha2WithheldContent::Unavailable(content) => WithheldHelper { + algorithm, + code, + reason, + other: serde_json::to_value(content).map_err(serde::ser::Error::custom)?, + }, + MegolmV1AesSha2WithheldContent::NoOlm(content) => WithheldHelper { + algorithm, + code, + reason, + other: serde_json::to_value(content).map_err(serde::ser::Error::custom)?, + }, + } + } #[cfg(feature = "experimental-algorithms")] - Self::MegolmV2AesSha2(r) => match r { - MegolmV2AesSha2WithheldContent::AnyContent((code, content)) => WithheldHelper { - algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, - code: code.clone(), - other: serde_json::to_value(content).map_err(serde::ser::Error::custom)?, - }, - MegolmV2AesSha2WithheldContent::NoOlmContent(content) => WithheldHelper { - algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, - code: WithheldCode::NoOlm, - other: serde_json::to_value(content).map_err(serde::ser::Error::custom)?, - }, - }, + Self::MegolmV2AesSha2(r) => { + let code = r.withheld_code(); + let reason = JsOption::Some(code.to_string()); + + match r { + MegolmV1AesSha2WithheldContent::BlackListed(content) + | MegolmV1AesSha2WithheldContent::Unverified(content) + | MegolmV1AesSha2WithheldContent::Unauthorised(content) + | MegolmV1AesSha2WithheldContent::Unavailable(content) => WithheldHelper { + algorithm, + code, + reason, + other: serde_json::to_value(content).map_err(serde::ser::Error::custom)?, + }, + MegolmV1AesSha2WithheldContent::NoOlm(content) => WithheldHelper { + algorithm, + code, + reason, + other: serde_json::to_value(content).map_err(serde::ser::Error::custom)?, + }, + } + } Self::Unknown(r) => WithheldHelper { - algorithm: r.algorithm.clone(), - code: r.code.clone(), + algorithm: r.algorithm.to_owned(), + code: r.code.to_owned(), + reason: r.reason.to_owned(), other: serde_json::to_value(r.other.clone()).map_err(serde::ser::Error::custom)?, }, }; @@ -498,32 +522,10 @@ pub(super) mod test { for code in codes { let json = json(&code); let event: RoomKeyWithheldEvent = serde_json::from_value(json.clone())?; - assert_matches!( - event.content, - RoomKeyWithheldContent::MegolmV1AesSha2( - MegolmV1AesSha2WithheldContent::AnyContent(_) - ) - ); - if let RoomKeyWithheldContent::MegolmV1AesSha2(content) = &event.content { - assert_eq!(content.withheld_code().to_owned(), code) - } else { - panic!() - } + assert_matches!(&event.content, RoomKeyWithheldContent::MegolmV1AesSha2(content) if code == content.withheld_code()); - if let RoomKeyWithheldContent::MegolmV1AesSha2( - MegolmV1AesSha2WithheldContent::AnyContent((_, content)), - ) = &event.content - { - assert_eq!(content.reason, ruma::JsOption::Some(code.to_string())) - } else { - panic!() - } - - assert_eq!( - event.content.algorithm().to_owned(), - EventEncryptionAlgorithm::MegolmV1AesSha2 - ); + assert_eq!(event.content.algorithm(), EventEncryptionAlgorithm::MegolmV1AesSha2); let serialized = serde_json::to_value(event)?; assert_eq!(json, serialized); } @@ -536,9 +538,7 @@ pub(super) mod test { let event: RoomKeyWithheldEvent = serde_json::from_value(json.clone())?; assert_matches!( event.content, - RoomKeyWithheldContent::MegolmV1AesSha2(MegolmV1AesSha2WithheldContent::NoOlmContent( - _ - )) + RoomKeyWithheldContent::MegolmV1AesSha2(MegolmV1AesSha2WithheldContent::NoOlm(_)) ); let serialized = serde_json::to_value(event)?; assert_eq!(json, serialized); @@ -550,19 +550,14 @@ pub(super) mod test { fn deserialization_unknown_code() -> Result<(), serde_json::Error> { let json = unknown_code_json(); let event: RoomKeyWithheldEvent = serde_json::from_value(json.clone())?; + assert_matches!(event.content, RoomKeyWithheldContent::Unknown(_)); + assert_matches!( - event.content, - RoomKeyWithheldContent::MegolmV1AesSha2(MegolmV1AesSha2WithheldContent::AnyContent(( - _, - _ - ))) + &event.content, + RoomKeyWithheldContent::Unknown(content) + if content.code.as_str() == "org.mscXXX.new_code" ); - if let RoomKeyWithheldContent::MegolmV1AesSha2(content) = &event.content { - assert_matches!(content.withheld_code(), WithheldCode::_Custom(_)); - } else { - panic!() - } let serialized = serde_json::to_value(event)?; assert_eq!(json, serialized); @@ -597,13 +592,13 @@ pub(super) mod test { Curve25519PublicKey::from_base64("9n7mdWKOjr9c4NTlG6zV8dbFtNK79q9vZADoh7nMUwA") .unwrap(); - let content = RoomKeyWithheldContent::create( + let content = RoomKeyWithheldContent::new( EventEncryptionAlgorithm::MegolmV1AesSha2, WithheldCode::Unverified, room_id.to_owned(), "0ZcULv8j1nqVWx6orFjD6OW9JQHydDPXfaanA+uRyfs".to_owned(), sender_key, - Some(device_id.to_owned()), + device_id.to_owned(), ); let content: Raw = Raw::new(&content).expect("We can always serialize a withheld content info").cast(); @@ -617,15 +612,15 @@ pub(super) mod test { let expected: Value = json!({ "@alice:example.org":{ - "DEV001":{ - "algorithm":"m.megolm.v1.aes-sha2", - "code":"m.unverified", - "from_device":"DEV001", - "reason":"The sender has disabled encrypting to unverified devices.", - "room_id":"!DwLygpkclUAfQNnfva:localhost:8481", - "sender_key":"9n7mdWKOjr9c4NTlG6zV8dbFtNK79q9vZADoh7nMUwA", - "session_id":"0ZcULv8j1nqVWx6orFjD6OW9JQHydDPXfaanA+uRyfs" - } + "DEV001":{ + "algorithm":"m.megolm.v1.aes-sha2", + "code":"m.unverified", + "from_device":"DEV001", + "reason":"The sender has disabled encrypting to unverified devices.", + "room_id":"!DwLygpkclUAfQNnfva:localhost:8481", + "sender_key":"9n7mdWKOjr9c4NTlG6zV8dbFtNK79q9vZADoh7nMUwA", + "session_id":"0ZcULv8j1nqVWx6orFjD6OW9JQHydDPXfaanA+uRyfs" + } } }); assert_eq!(serialized, expected); @@ -639,49 +634,34 @@ pub(super) mod test { Curve25519PublicKey::from_base64("9n7mdWKOjr9c4NTlG6zV8dbFtNK79q9vZADoh7nMUwA") .unwrap(); - let content = RoomKeyWithheldContent::create( + let content = RoomKeyWithheldContent::new( EventEncryptionAlgorithm::MegolmV1AesSha2, WithheldCode::NoOlm, room_id.to_owned(), "0ZcULv8j1nqVWx6orFjD6OW9JQHydDPXfaanA+uRyfs".to_owned(), sender_key, - Some(device_id.to_owned()), + device_id.to_owned(), ); - assert_matches!(content, RoomKeyWithheldContent::MegolmV1AesSha2(_)); + assert_matches!( + content, + RoomKeyWithheldContent::MegolmV1AesSha2(MegolmV1AesSha2WithheldContent::NoOlm(content)) + if content.sender_key == sender_key + ); - match content { - RoomKeyWithheldContent::MegolmV1AesSha2(content) => match content { - MegolmV1AesSha2WithheldContent::AnyContent(_) => panic!(), - MegolmV1AesSha2WithheldContent::NoOlmContent(no_olm_content) => { - assert_eq!(sender_key, no_olm_content.sender_key); - } - }, - _ => panic!(), - } - - let content = RoomKeyWithheldContent::create( + let content = RoomKeyWithheldContent::new( EventEncryptionAlgorithm::MegolmV1AesSha2, WithheldCode::Unverified, room_id.to_owned(), "0ZcULv8j1nqVWx6orFjD6OW9JQHydDPXfaanA+uRyfs".to_owned(), sender_key, - Some(device_id.to_owned()), + device_id.to_owned(), ); - assert_matches!(content, RoomKeyWithheldContent::MegolmV1AesSha2(_)); - - match content { - RoomKeyWithheldContent::MegolmV1AesSha2(content) => match content { - MegolmV1AesSha2WithheldContent::NoOlmContent(_) => panic!(), - MegolmV1AesSha2WithheldContent::AnyContent((code, content)) => { - assert_eq!(sender_key, content.sender_key); - assert_eq!(code, WithheldCode::Unverified); - assert_eq!("0ZcULv8j1nqVWx6orFjD6OW9JQHydDPXfaanA+uRyfs", content.session_id); - assert_eq!(room_id, content.room_id); - } - }, - _ => panic!(), - } + assert_matches!( + content, + RoomKeyWithheldContent::MegolmV1AesSha2(MegolmV1AesSha2WithheldContent::Unverified(content)) + if content.session_id == "0ZcULv8j1nqVWx6orFjD6OW9JQHydDPXfaanA+uRyfs" + ); } } diff --git a/crates/matrix-sdk-indexeddb/src/crypto_store.rs b/crates/matrix-sdk-indexeddb/src/crypto_store.rs index cf21d7c24..e85fec54d 100644 --- a/crates/matrix-sdk-indexeddb/src/crypto_store.rs +++ b/crates/matrix-sdk-indexeddb/src/crypto_store.rs @@ -570,8 +570,9 @@ impl_crypto_store! { let withhelds = tx.object_store(keys::DIRECT_WITHHELD_INFO)?; for info in withheld_session_info { - let room_id = info.room_id.to_owned(); - let session_id = info.session_id.to_owned(); + let room_id = info.room_id(); + let session_id = info.session_id(); + let key = self.encode_key(keys::DIRECT_WITHHELD_INFO, (session_id, room_id)); withhelds.put_key_val(&key, &self.serialize_value(&info)?)?; } diff --git a/crates/matrix-sdk-sled/src/crypto_store.rs b/crates/matrix-sdk-sled/src/crypto_store.rs index 89bb22819..6a1320e70 100644 --- a/crates/matrix-sdk-sled/src/crypto_store.rs +++ b/crates/matrix-sdk-sled/src/crypto_store.rs @@ -671,12 +671,11 @@ impl SledCryptoStore { } for info in &changes.withheld_session_info { - let session_id = info.session_id.to_owned(); - let room_id = info.room_id.to_owned(); - let key = self.encode_key( - DIRECT_WITHHELD_INFO_TABLE, - (session_id.as_str(), room_id.as_str()), - ); + let session_id = info.session_id(); + let room_id = info.room_id(); + + let key = + self.encode_key(DIRECT_WITHHELD_INFO_TABLE, (session_id, room_id)); direct_withheld_info.insert( key, self.serialize_value(info)