From 329d461a2f095a402a6dcdf34e1d600305bfd7ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 3 Aug 2022 13:00:17 +0200 Subject: [PATCH] feat(crypto): Add customized event type for the forwarded room key --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 4 +- .../src/gossiping/machine.rs | 98 +++++---- .../src/identities/device.rs | 21 +- crates/matrix-sdk-crypto/src/machine.rs | 6 +- .../src/olm/group_sessions/inbound.rs | 31 ++- .../src/olm/group_sessions/mod.rs | 86 ++++---- crates/matrix-sdk-crypto/src/olm/mod.rs | 9 +- crates/matrix-sdk-crypto/src/olm/session.rs | 2 + .../src/store/integration_tests.rs | 7 +- .../src/types/events/forwarded_room_key.rs | 190 ++++++++++++++++++ .../matrix-sdk-crypto/src/types/events/mod.rs | 1 + .../src/types/events/to_device.rs | 22 +- crates/matrix-sdk-crypto/src/types/mod.rs | 40 +++- 13 files changed, 394 insertions(+), 123 deletions(-) create mode 100644 crates/matrix-sdk-crypto/src/types/events/forwarded_room_key.rs diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index bbaf381b5..614d9d88a 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -250,6 +250,8 @@ pub fn migrate( InboundGroupSession::from_libolm_pickle(&session.pickle, &data.pickle_key)?.pickle(); let sender_key = Curve25519PublicKey::from_base64(&session.sender_key)?; + let forwarding_chains: Result, _> = + session.forwarding_chains.iter().map(|k| Curve25519PublicKey::from_base64(k)).collect(); let pickle = matrix_sdk_crypto::olm::PickledInboundGroupSession { pickle, @@ -260,7 +262,7 @@ pub fn migrate( .map(|(k, v)| Ok((DeviceKeyAlgorithm::try_from(k)?, v))) .collect::>()?, room_id: RoomId::parse(session.room_id)?, - forwarding_chains: session.forwarding_chains, + forwarding_chains: forwarding_chains?, imported: session.imported, backed_up: session.backed_up, history_visibility: None, diff --git a/crates/matrix-sdk-crypto/src/gossiping/machine.rs b/crates/matrix-sdk-crypto/src/gossiping/machine.rs index 2aedf1a40..6a629de86 100644 --- a/crates/matrix-sdk-crypto/src/gossiping/machine.rs +++ b/crates/matrix-sdk-crypto/src/gossiping/machine.rs @@ -26,7 +26,6 @@ use dashmap::{mapref::entry::Entry, DashMap, DashSet}; use ruma::{ api::client::keys::claim_keys::v3::Request as KeysClaimRequest, events::{ - forwarded_room_key::{ToDeviceForwardedRoomKeyEvent, ToDeviceForwardedRoomKeyEventContent}, room_key_request::{Action, RequestedKeyInfo, ToDeviceRoomKeyRequestEvent}, secret::request::{ RequestAction, SecretName, ToDeviceSecretRequestEvent as SecretRequestEvent, @@ -46,6 +45,9 @@ use crate::{ session_manager::GroupSessionCache, store::{Changes, CryptoStoreError, SecretImportError, Store}, types::events::{ + forwarded_room_key::{ + ForwardedMegolmV1AesSha2Content, ForwardedRoomKeyContent, ForwardedRoomKeyEvent, + }, secret_send::{SecretSendContent, SecretSendEvent}, EventType, }, @@ -700,12 +702,12 @@ impl GossipMachine { /// Get an outgoing key info that matches the forwarded room key content. async fn get_key_info( &self, - content: &ToDeviceForwardedRoomKeyEventContent, + content: &ForwardedMegolmV1AesSha2Content, ) -> Result, CryptoStoreError> { let info = RequestedKeyInfo::new( - content.algorithm.clone(), + EventEncryptionAlgorithm::MegolmV1AesSha2, content.room_id.clone(), - content.sender_key.clone(), + content.claimed_sender_key.to_base64(), content.session_id.clone(), ) .into(); @@ -880,10 +882,11 @@ impl GossipMachine { async fn accept_forwarded_room_key( &self, info: &GossipRequest, + sender: &UserId, sender_key: Curve25519PublicKey, - event: &ToDeviceForwardedRoomKeyEvent, + content: &ForwardedMegolmV1AesSha2Content, ) -> Result, CryptoStoreError> { - match InboundGroupSession::from_forwarded_key(sender_key, &event.content) { + match InboundGroupSession::from_forwarded_key(sender_key, content) { Ok(session) => { let old_session = self .store @@ -913,19 +916,19 @@ impl GossipMachine { if let Some(s) = &session { info!( - sender = event.sender.as_str(), + %sender, sender_key = sender_key.to_base64(), - claimed_sender_key = event.content.sender_key.as_str(), + claimed_sender_key = content.claimed_sender_key.to_base64(), room_id = s.room_id().as_str(), session_id = session_id.as_str(), "Received a forwarded room key", ); } else { info!( - sender = event.sender.as_str(), + %sender, sender_key = sender_key.to_base64(), - claimed_sender_key = event.content.sender_key.as_str(), - room_id = event.content.room_id.as_str(), + claimed_sender_key = content.claimed_sender_key.to_base64(), + room_id = %content.room_id, session_id = session_id.as_str(), "Received a forwarded room key but we already have a better version of it", ); @@ -935,10 +938,10 @@ impl GossipMachine { } Err(e) => { warn!( - sender = event.sender.as_str(), + %sender, sender_key = sender_key.to_base64(), - claimed_sender_key = event.content.sender_key.as_str(), - room_id = event.content.room_id.as_str(), + claimed_sender_key = content.claimed_sender_key.to_base64(), + room_id = content.room_id.as_str(), "Couldn't create a group session from a received room key" ); Err(e.into()) @@ -950,20 +953,36 @@ impl GossipMachine { pub async fn receive_forwarded_room_key( &self, sender_key: Curve25519PublicKey, - event: &ToDeviceForwardedRoomKeyEvent, + event: &ForwardedRoomKeyEvent, ) -> Result, CryptoStoreError> { - if let Some(info) = self.get_key_info(&event.content).await? { - self.accept_forwarded_room_key(&info, sender_key, event).await - } else { - warn!( - sender = event.sender.as_str(), - sender_key = sender_key.to_base64(), - room_id = event.content.room_id.as_str(), - session_id = event.content.session_id.as_str(), - claimed_sender_key = event.content.sender_key.as_str(), - "Received a forwarded room key that we didn't request", - ); - Ok(None) + match &event.content { + ForwardedRoomKeyContent::MegolmV1AesSha2(content) => { + if let Some(info) = self.get_key_info(content).await? { + self.accept_forwarded_room_key(&info, &event.sender, sender_key, content).await + } else { + warn!( + sender = event.sender.as_str(), + sender_key = sender_key.to_base64(), + room_id = content.room_id.as_str(), + session_id = content.session_id.as_str(), + claimed_sender_key = content.claimed_sender_key.to_base64(), + algorithm = %event.algorithm(), + "Received a forwarded room key that we didn't request", + ); + + Ok(None) + } + } + ForwardedRoomKeyContent::Unknown(_) => { + warn!( + sender = event.sender.as_str(), + sender_key = sender_key.to_base64(), + algorithm = %event.algorithm(), + "Received a forwarded room key with an unsupported algorithm", + ); + + Ok(None) + } } } } @@ -979,10 +998,9 @@ mod tests { use ruma::{ device_id, events::{ - forwarded_room_key::ToDeviceForwardedRoomKeyEventContent, room::encrypted::ToDeviceRoomEncryptedEventContent, secret::request::{RequestAction, SecretName, ToDeviceSecretRequestEventContent}, - AnyToDeviceEvent, ToDeviceEvent as RumaToDeviceEvent, ToDeviceEventContent, + ToDeviceEvent as RumaToDeviceEvent, ToDeviceEventContent, }, room_id, user_id, DeviceId, RoomId, UserId, }; @@ -994,6 +1012,9 @@ mod tests { olm::{Account, OutboundGroupSession, PrivateCrossSigningIdentity, ReadOnlyAccount}, session_manager::GroupSessionCache, store::{Changes, CryptoStore, MemoryStore, Store}, + types::events::{ + forwarded_room_key::ForwardedRoomKeyContent, ToDeviceEvent, ToDeviceEvents, + }, utilities::json_convert, verification::VerificationMachine, OutgoingRequest, OutgoingRequests, @@ -1284,9 +1305,10 @@ mod tests { let export = session.export_at_index(10).await; - let content: ToDeviceForwardedRoomKeyEventContent = export.try_into().unwrap(); + let content: ForwardedRoomKeyContent = export.try_into().unwrap(); - let event = RumaToDeviceEvent { sender: alice_id().to_owned(), content }; + let event = + ToDeviceEvent { sender: alice_id().to_owned(), content, other: Default::default() }; assert!(machine .store @@ -1332,9 +1354,10 @@ mod tests { let export = session.export_at_index(15).await; - let content: ToDeviceForwardedRoomKeyEventContent = export.try_into().unwrap(); + let content: ForwardedRoomKeyContent = export.try_into().unwrap(); - let event = RumaToDeviceEvent { sender: alice_id().to_owned(), content }; + let event = + ToDeviceEvent { sender: alice_id().to_owned(), content, other: Default::default() }; let second_session = machine .receive_forwarded_room_key(alice_device.curve25519_key().unwrap(), &event) @@ -1345,9 +1368,10 @@ mod tests { let export = session.export_at_index(0).await; - let content: ToDeviceForwardedRoomKeyEventContent = export.try_into().unwrap(); + let content: ForwardedRoomKeyContent = export.try_into().unwrap(); - let event = RumaToDeviceEvent { sender: alice_id().to_owned(), content }; + let event = + ToDeviceEvent { sender: alice_id().to_owned(), content, other: Default::default() }; let second_session = machine .receive_forwarded_room_key(alice_device.curve25519_key().unwrap(), &event) @@ -1516,7 +1540,7 @@ mod tests { let decrypted = alice_account.decrypt_to_device_event(&event).await.unwrap(); - if let AnyToDeviceEvent::ForwardedRoomKey(e) = decrypted.event.deserialize().unwrap() { + if let ToDeviceEvents::ForwardedRoomKey(e) = decrypted.event.deserialize_as().unwrap() { let session = alice_machine.receive_forwarded_room_key(decrypted.sender_key, &e).await.unwrap(); alice_machine.store.save_inbound_group_sessions(&[session.unwrap()]).await.unwrap(); @@ -1674,7 +1698,7 @@ mod tests { let decrypted = alice_account.decrypt_to_device_event(&event).await.unwrap(); - if let AnyToDeviceEvent::ForwardedRoomKey(e) = decrypted.event.deserialize().unwrap() { + if let ToDeviceEvents::ForwardedRoomKey(e) = decrypted.event.deserialize_as().unwrap() { let session = alice_machine.receive_forwarded_room_key(decrypted.sender_key, &e).await.unwrap(); alice_machine.store.save_inbound_group_sessions(&[session.unwrap()]).await.unwrap(); diff --git a/crates/matrix-sdk-crypto/src/identities/device.rs b/crates/matrix-sdk-crypto/src/identities/device.rs index aae701d3d..732541a9c 100644 --- a/crates/matrix-sdk-crypto/src/identities/device.rs +++ b/crates/matrix-sdk-crypto/src/identities/device.rs @@ -26,13 +26,8 @@ use atomic::Atomic; use matrix_sdk_common::locks::Mutex; use ruma::{ api::client::keys::upload_signatures::v3::Request as SignatureUploadRequest, - events::{ - forwarded_room_key::ToDeviceForwardedRoomKeyEventContent, - key::verification::VerificationMethod, - }, - serde::Raw, - DeviceId, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, OwnedDeviceId, - OwnedDeviceKeyId, UserId, + events::key::verification::VerificationMethod, serde::Raw, DeviceId, DeviceKeyAlgorithm, + DeviceKeyId, EventEncryptionAlgorithm, OwnedDeviceId, OwnedDeviceKeyId, UserId, }; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; @@ -48,8 +43,11 @@ use crate::{ olm::{InboundGroupSession, Session, SignedJsonObject, VerifyJson}, store::{Changes, CryptoStore, DeviceChanges, Result as StoreResult}, types::{ - events::room::encrypted::ToDeviceEncryptedEventContent, DeviceKey, DeviceKeys, Signatures, - SignedKey, + events::{ + forwarded_room_key::ForwardedRoomKeyContent, + room::encrypted::ToDeviceEncryptedEventContent, EventType, + }, + DeviceKey, DeviceKeys, Signatures, SignedKey, }, verification::VerificationMachine, OutgoingVerificationRequest, ReadOnlyAccount, Sas, ToDeviceRequest, VerificationRequest, @@ -277,7 +275,7 @@ impl Device { session.export().await }; - let content: ToDeviceForwardedRoomKeyEventContent = if let Ok(c) = export.try_into() { + let content: ForwardedRoomKeyContent = if let Ok(c) = export.try_into() { c } else { // TODO remove this panic. @@ -290,9 +288,10 @@ impl Device { ); }; + let event_type = content.event_type(); let content = serde_json::to_value(content)?; - self.encrypt("m.forwarded_room_key", content).await + self.encrypt(event_type, content).await } } diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index 6870c589f..adc7e8ae1 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -1037,7 +1037,11 @@ impl OlmMachine { algorithm_info: AlgorithmInfo::MegolmV1AesSha2 { curve25519_key: session.sender_key().to_base64(), sender_claimed_keys: session.signing_keys().to_owned(), - forwarding_curve25519_key_chain: session.forwarding_key_chain().to_vec(), + forwarding_curve25519_key_chain: session + .forwarding_key_chain() + .iter() + .map(|k| k.to_base64()) + .collect(), }, verification_state, }) 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 d1a5df49b..0b8aaecab 100644 --- a/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs @@ -23,10 +23,7 @@ use std::{ use matrix_sdk_common::locks::Mutex; use ruma::{ - events::{ - forwarded_room_key::ToDeviceForwardedRoomKeyEventContent, - room::history_visibility::HistoryVisibility, AnyRoomEvent, - }, + events::{room::history_visibility::HistoryVisibility, AnyRoomEvent}, serde::Raw, DeviceKeyAlgorithm, EventEncryptionAlgorithm, OwnedRoomId, RoomId, }; @@ -34,7 +31,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use vodozemac::{ megolm::{ - DecryptedMessage, DecryptionError, ExportedSessionKey, InboundGroupSession as InnerSession, + DecryptedMessage, DecryptionError, InboundGroupSession as InnerSession, InboundGroupSessionPickle, MegolmMessage, SessionConfig, SessionOrdering, }, Curve25519PublicKey, PickleError, @@ -45,7 +42,10 @@ use crate::{ error::{EventError, MegolmResult}, types::{ deserialize_curve_key, - events::room::encrypted::{EncryptedEvent, RoomEventEncryptionScheme}, + events::{ + forwarded_room_key::ForwardedMegolmV1AesSha2Content, + room::encrypted::{EncryptedEvent, RoomEventEncryptionScheme}, + }, serialize_curve_key, }, }; @@ -71,7 +71,7 @@ pub struct InboundGroupSession { pub signing_keys: Arc>, /// The Room this GroupSession belongs to pub room_id: Arc, - forwarding_chains: Arc>, + forwarding_chains: Arc>, imported: bool, algorithm: Arc, backed_up: Arc, @@ -165,25 +165,24 @@ impl InboundGroupSession { /// to create the `InboundGroupSession`. pub fn from_forwarded_key( sender_key: Curve25519PublicKey, - content: &ToDeviceForwardedRoomKeyEventContent, + content: &ForwardedMegolmV1AesSha2Content, ) -> Result { - let key = ExportedSessionKey::from_base64(&content.session_key)?; - let algorithm = EventEncryptionAlgorithm::from(content.algorithm.as_str()); + let algorithm = EventEncryptionAlgorithm::MegolmV1AesSha2; - let session = InnerSession::import(&key, SessionConfig::version_1()); + let session = InnerSession::import(&content.session_key, SessionConfig::version_1()); let first_known_index = session.first_known_index(); let mut forwarding_chains = content.forwarding_curve25519_key_chain.clone(); - forwarding_chains.push(sender_key.to_base64()); + forwarding_chains.push(sender_key); let mut sender_claimed_key = BTreeMap::new(); sender_claimed_key - .insert(DeviceKeyAlgorithm::Ed25519, content.sender_claimed_ed25519_key.to_owned()); + .insert(DeviceKeyAlgorithm::Ed25519, content.claimed_ed25519_key.to_base64()); Ok(InboundGroupSession { inner: Mutex::new(session).into(), session_id: content.session_id.as_str().into(), - sender_key, + sender_key: content.claimed_sender_key, first_known_index, history_visibility: None.into(), signing_keys: sender_claimed_key.into(), @@ -256,7 +255,7 @@ impl InboundGroupSession { /// Each ed25519 key represents a single device. If device A forwards the /// session to device B and device B to C this list will contain the ed25519 /// keys of A and B. - pub fn forwarding_key_chain(&self) -> &[String] { + pub fn forwarding_key_chain(&self) -> &[Curve25519PublicKey] { &self.forwarding_chains } @@ -446,7 +445,7 @@ pub struct PickledInboundGroupSession { /// The list of claimed ed25519 that forwarded us this key. Will be None if /// we directly received this session. #[serde(default)] - pub forwarding_chains: Vec, + pub forwarding_chains: Vec, /// Flag remembering if the session was directly sent to us by the sender /// or if it was imported. pub imported: bool, 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 46f7cb0d2..4e0762159 100644 --- a/crates/matrix-sdk-crypto/src/olm/group_sessions/mod.rs +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/mod.rs @@ -14,12 +14,7 @@ use std::collections::BTreeMap; -use ruma::{ - events::forwarded_room_key::{ - ToDeviceForwardedRoomKeyEventContent, ToDeviceForwardedRoomKeyEventContentInit, - }, - DeviceKeyAlgorithm, EventEncryptionAlgorithm, OwnedRoomId, -}; +use ruma::{DeviceKeyAlgorithm, EventEncryptionAlgorithm, OwnedRoomId}; use serde::{Deserialize, Serialize}; mod inbound; @@ -32,10 +27,13 @@ pub use outbound::{ }; use thiserror::Error; pub use vodozemac::megolm::{ExportedSessionKey, SessionKey}; -use vodozemac::{megolm::SessionKeyDecodeError, Curve25519PublicKey}; -use zeroize::Zeroize; +use vodozemac::{megolm::SessionKeyDecodeError, Curve25519PublicKey, Ed25519PublicKey}; -use crate::types::{deserialize_curve_key, serialize_curve_key}; +use crate::types::{ + deserialize_curve_key, + events::forwarded_room_key::{ForwardedMegolmV1AesSha2Content, ForwardedRoomKeyContent}, + serialize_curve_key, +}; /// An error type for the creation of group sessions. #[derive(Debug, Error)] @@ -77,7 +75,7 @@ pub struct ExportedRoomKey { /// Chain of Curve25519 keys through which this session was forwarded, via /// m.forwarded_room_key events. #[serde(default)] - pub forwarding_curve25519_key_chain: Vec, + pub forwarding_curve25519_key_chain: Vec, } /// A backed up version of an `InboundGroupSession` @@ -101,10 +99,10 @@ pub struct BackedUpRoomKey { /// Chain of Curve25519 keys through which this session was forwarded, via /// m.forwarded_room_key events. - pub forwarding_curve25519_key_chain: Vec, + pub forwarding_curve25519_key_chain: Vec, } -impl TryInto for ExportedRoomKey { +impl TryInto for ExportedRoomKey { type Error = (); /// Convert an exported room key into a content for a forwarded room key @@ -113,7 +111,7 @@ impl TryInto for ExportedRoomKey { /// This will fail if the exported room key has multiple sender claimed keys /// or if the algorithm of the claimed sender key isn't /// `DeviceKeyAlgorithm::Ed25519`. - fn try_into(self) -> Result { + fn try_into(self) -> Result { if self.sender_claimed_keys.len() != 1 { Err(()) } else { @@ -123,16 +121,19 @@ impl TryInto for ExportedRoomKey { return Err(()); } - Ok(ToDeviceForwardedRoomKeyEventContentInit { - algorithm: self.algorithm, - room_id: self.room_id, - sender_key: self.sender_key.to_base64(), - session_id: self.session_id, - session_key: self.session_key.to_base64(), - sender_claimed_ed25519_key: claimed_key.to_owned(), - forwarding_curve25519_key_chain: self.forwarding_curve25519_key_chain, - } - .into()) + Ok(ForwardedRoomKeyContent::MegolmV1AesSha2( + ForwardedMegolmV1AesSha2Content { + room_id: self.room_id, + session_id: self.session_id, + session_key: self.session_key, + claimed_sender_key: self.sender_key, + claimed_ed25519_key: Ed25519PublicKey::from_base64(claimed_key) + .map_err(|_| ())?, + forwarding_curve25519_key_chain: self.forwarding_curve25519_key_chain.clone(), + other: Default::default(), + } + .into(), + )) } } } @@ -149,29 +150,28 @@ impl From for BackedUpRoomKey { } } -impl TryFrom for ExportedRoomKey { +impl TryFrom for ExportedRoomKey { type Error = SessionKeyDecodeError; /// Convert the content of a forwarded room key into a exported room key. - fn try_from( - mut forwarded_key: ToDeviceForwardedRoomKeyEventContent, - ) -> Result { - let mut sender_claimed_keys: BTreeMap = BTreeMap::new(); - sender_claimed_keys - .insert(DeviceKeyAlgorithm::Ed25519, forwarded_key.sender_claimed_ed25519_key); + fn try_from(forwarded_key: ForwardedRoomKeyContent) -> Result { + match forwarded_key { + ForwardedRoomKeyContent::MegolmV1AesSha2(content) => { + let mut sender_claimed_keys: BTreeMap = BTreeMap::new(); + sender_claimed_keys + .insert(DeviceKeyAlgorithm::Ed25519, content.claimed_ed25519_key.to_base64()); - let session_key = ExportedSessionKey::from_base64(&forwarded_key.session_key)?; - forwarded_key.session_key.zeroize(); - let sender_key = Curve25519PublicKey::from_base64(&forwarded_key.sender_key)?; - - Ok(Self { - algorithm: forwarded_key.algorithm, - room_id: forwarded_key.room_id, - session_id: forwarded_key.session_id, - forwarding_curve25519_key_chain: forwarded_key.forwarding_curve25519_key_chain, - sender_claimed_keys, - sender_key, - session_key, - }) + Ok(Self { + algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, + room_id: content.room_id, + session_id: content.session_id, + forwarding_curve25519_key_chain: content.forwarding_curve25519_key_chain, + sender_claimed_keys, + sender_key: content.claimed_sender_key, + session_key: content.session_key, + }) + } + ForwardedRoomKeyContent::Unknown(_) => Err(SessionKeyDecodeError::Version(1, 2)), + } } } diff --git a/crates/matrix-sdk-crypto/src/olm/mod.rs b/crates/matrix-sdk-crypto/src/olm/mod.rs index 7ab8e652b..680ba8bca 100644 --- a/crates/matrix-sdk-crypto/src/olm/mod.rs +++ b/crates/matrix-sdk-crypto/src/olm/mod.rs @@ -34,7 +34,7 @@ pub use group_sessions::{ pub use session::{PickledSession, Session}; pub use signing::{CrossSigningStatus, PickledCrossSigningIdentity, PrivateCrossSigningIdentity}; pub(crate) use utility::{SignedJsonObject, VerifyJson}; -pub use vodozemac::olm::IdentityKeys; +pub use vodozemac::{olm::IdentityKeys, Curve25519PublicKey}; #[cfg(test)] pub(crate) mod tests { @@ -43,7 +43,6 @@ pub(crate) mod tests { use ruma::{ device_id, event_id, events::{ - forwarded_room_key::ToDeviceForwardedRoomKeyEventContent, room::message::{Relation, Replacement, RoomMessageEventContent}, AnyMessageLikeEvent, AnyRoomEvent, MessageLikeEvent, }, @@ -54,7 +53,9 @@ pub(crate) mod tests { use crate::{ olm::{ExportedRoomKey, InboundGroupSession, ReadOnlyAccount, Session}, - types::events::room::encrypted::EncryptedEvent, + types::events::{ + forwarded_room_key::ForwardedRoomKeyContent, room::encrypted::EncryptedEvent, + }, utilities::json_convert, }; @@ -321,7 +322,7 @@ pub(crate) mod tests { let (_, inbound) = alice.create_group_session_pair_with_defaults(room_id).await; let export = inbound.export().await; - let export: ToDeviceForwardedRoomKeyEventContent = export.try_into().unwrap(); + let export: ForwardedRoomKeyContent = export.try_into().unwrap(); let export = ExportedRoomKey::try_from(export).unwrap(); let imported = InboundGroupSession::from_export(&export) diff --git a/crates/matrix-sdk-crypto/src/olm/session.rs b/crates/matrix-sdk-crypto/src/olm/session.rs index c887beb58..bdceda2b2 100644 --- a/crates/matrix-sdk-crypto/src/olm/session.rs +++ b/crates/matrix-sdk-crypto/src/olm/session.rs @@ -109,6 +109,8 @@ impl Session { /// encrypted, this needs to be the device that was used to create this /// session with. /// + /// * `event_type` - The type of the event content. + /// /// * `content` - The content of the event. pub async fn encrypt( &mut self, diff --git a/crates/matrix-sdk-crypto/src/store/integration_tests.rs b/crates/matrix-sdk-crypto/src/store/integration_tests.rs index 27d2c6a5f..cd5dde5f6 100644 --- a/crates/matrix-sdk-crypto/src/store/integration_tests.rs +++ b/crates/matrix-sdk-crypto/src/store/integration_tests.rs @@ -18,8 +18,8 @@ macro_rules! cryptostore_integration_tests { testing::{get_device, get_other_identity, get_own_identity}, ReadOnlyDevice, olm::{ - InboundGroupSession, OlmMessageHash, PrivateCrossSigningIdentity, - ReadOnlyAccount, Session, + Curve25519PublicKey, InboundGroupSession, OlmMessageHash, + PrivateCrossSigningIdentity, ReadOnlyAccount, Session, }, store::{CryptoStore, GossipRequest, Changes, DeviceChanges, IdentityChanges, RecoveryKey}, }; @@ -286,7 +286,8 @@ macro_rules! cryptostore_integration_tests { let mut export = session.export().await; - export.forwarding_curve25519_key_chain = vec!["some_chain".to_owned()]; + let curve_key = Curve25519PublicKey::from_base64("Nn0L2hkcCMFKqynTjyGsJbth7QrVmX3lbrksMkrGOAw").unwrap(); + export.forwarding_curve25519_key_chain = vec![curve_key]; let session = InboundGroupSession::from_export(&export).unwrap(); diff --git a/crates/matrix-sdk-crypto/src/types/events/forwarded_room_key.rs b/crates/matrix-sdk-crypto/src/types/events/forwarded_room_key.rs new file mode 100644 index 000000000..d9429edcc --- /dev/null +++ b/crates/matrix-sdk-crypto/src/types/events/forwarded_room_key.rs @@ -0,0 +1,190 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Types for `m.forwarded_room_key` to-device events. + +use std::collections::BTreeMap; + +use ruma::{EventEncryptionAlgorithm, OwnedRoomId}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use vodozemac::{megolm::ExportedSessionKey, Curve25519PublicKey, Ed25519PublicKey}; + +use super::{EventType, ToDeviceEvent}; +use crate::types::{ + deserialize_curve_key, deserialize_curve_key_vec, deserialize_ed25519_key, serialize_curve_key, + serialize_curve_key_vec, serialize_ed25519_key, +}; + +/// The `m.forwarded_room_key` to-device event. +pub type ForwardedRoomKeyEvent = ToDeviceEvent; + +impl ForwardedRoomKeyEvent { + /// Get the algorithm of the forwarded room key. + pub fn algorithm(&self) -> EventEncryptionAlgorithm { + self.content.algorithm() + } +} + +/// The `m.forwarded_room_key` event content. +/// +/// This is an enum over the different room key algorithms we support. +/// +/// This event type is used to forward keys for end-to-end encryption. +/// Typically it is encrypted as an m.room.encrypted event, then sent as a +/// to-device event. +#[derive(Debug, Deserialize)] +#[serde(try_from = "RoomKeyHelper")] +pub enum ForwardedRoomKeyContent { + /// The `m.megolm.v1.aes-sha2` variant of the `m.forwarded_room_key` + /// content. + MegolmV1AesSha2(Box), + /// An unknown and unsupported variant of the `m.forwarded_room_key` + /// content. + Unknown(UnknownRoomKeyContent), +} + +impl ForwardedRoomKeyContent { + /// Get the algorithm of the forwarded room key content. + pub fn algorithm(&self) -> EventEncryptionAlgorithm { + match self { + ForwardedRoomKeyContent::MegolmV1AesSha2(_) => { + EventEncryptionAlgorithm::MegolmV1AesSha2 + } + ForwardedRoomKeyContent::Unknown(c) => c.algorithm.to_owned(), + } + } +} + +impl EventType for ForwardedRoomKeyContent { + const EVENT_TYPE: &'static str = "m.forwarded_room_key"; +} + +/// The `m.megolm.v1.aes-sha2` variant of the `m.room_key` content. +#[derive(Deserialize, Serialize)] +pub struct ForwardedMegolmV1AesSha2Content { + /// The room where the key is used. + pub room_id: OwnedRoomId, + + /// The ID of the session that the key is for. + pub session_id: String, + + /// The key to be exchanged. Can be used to create a [`InboundGroupSession`] + /// that can be used to decrypt room events. + /// + /// [`InboundGroupSession`]: vodozemac::megolm::InboundGroupSession + pub session_key: ExportedSessionKey, + + /// Chain of Curve25519 keys. It starts out empty, but each time the key is + /// forwarded to another device, the previous sender in the chain is added + /// to the end of the list. + #[serde( + deserialize_with = "deserialize_curve_key_vec", + serialize_with = "serialize_curve_key_vec" + )] + pub forwarding_curve25519_key_chain: Vec, + + /// The Curve25519 key of the device which initiated the session originally. + /// + /// It is ‘claimed’ because the receiving device has no way to tell that + /// the original room_key actually came from a device which owns the private + /// part of this key. + #[serde( + rename = "sender_key", + deserialize_with = "deserialize_curve_key", + serialize_with = "serialize_curve_key" + )] + pub claimed_sender_key: Curve25519PublicKey, + + /// The Ed25519 key of the device which initiated the session originally. + /// + /// It is ‘claimed’ because the receiving device has no way to tell that + /// the original room_key actually came from a device which owns the private + /// part of this key. + #[serde( + rename = "sender_claimed_ed25519_key", + deserialize_with = "deserialize_ed25519_key", + serialize_with = "serialize_ed25519_key" + )] + pub claimed_ed25519_key: Ed25519PublicKey, + + #[serde(flatten)] + pub(crate) other: BTreeMap, +} + +/// An unknown and unsupported `m.room_key` algorithm. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct UnknownRoomKeyContent { + /// The algorithm of the unknown room key. + algorithm: EventEncryptionAlgorithm, + /// The other data of the unknown room key. + #[serde(flatten)] + other: BTreeMap, +} + +impl std::fmt::Debug for ForwardedMegolmV1AesSha2Content { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ForwardedMegolmV1AesSha2Content") + .field("room_id", &self.room_id) + .field("session_id", &self.session_id) + .field("forwarding_curve25519_key_chain", &self.forwarding_curve25519_key_chain) + .field("claimed_sender_key", &self.claimed_sender_key) + .field("claimed_ed25519_key", &self.claimed_ed25519_key) + .finish_non_exhaustive() + } +} + +#[derive(Deserialize, Serialize)] +struct RoomKeyHelper { + algorithm: EventEncryptionAlgorithm, + #[serde(flatten)] + other: Value, +} + +impl TryFrom for ForwardedRoomKeyContent { + type Error = serde_json::Error; + + fn try_from(value: RoomKeyHelper) -> Result { + Ok(match value.algorithm { + EventEncryptionAlgorithm::MegolmV1AesSha2 => { + let content: ForwardedMegolmV1AesSha2Content = serde_json::from_value(value.other)?; + Self::MegolmV1AesSha2(content.into()) + } + _ => Self::Unknown(UnknownRoomKeyContent { + algorithm: value.algorithm, + other: serde_json::from_value(value.other)?, + }), + }) + } +} + +impl Serialize for ForwardedRoomKeyContent { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let helper = match self { + Self::MegolmV1AesSha2(r) => RoomKeyHelper { + algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, + other: serde_json::to_value(r).map_err(serde::ser::Error::custom)?, + }, + Self::Unknown(r) => RoomKeyHelper { + algorithm: r.algorithm.clone(), + other: serde_json::to_value(r.other.clone()).map_err(serde::ser::Error::custom)?, + }, + }; + + helper.serialize(serializer) + } +} diff --git a/crates/matrix-sdk-crypto/src/types/events/mod.rs b/crates/matrix-sdk-crypto/src/types/events/mod.rs index fed94e3e3..f90d8655e 100644 --- a/crates/matrix-sdk-crypto/src/types/events/mod.rs +++ b/crates/matrix-sdk-crypto/src/types/events/mod.rs @@ -18,6 +18,7 @@ //! types. Once deserialized they aim to zeroize all the secret material once //! the type is dropped. +pub mod forwarded_room_key; pub mod room; pub mod room_key; pub mod secret_send; 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 e4c097097..4b84e8019 100644 --- a/crates/matrix-sdk-crypto/src/types/events/to_device.rs +++ b/crates/matrix-sdk-crypto/src/types/events/to_device.rs @@ -17,7 +17,6 @@ use std::{collections::BTreeMap, fmt::Debug}; use ruma::{ events::{ dummy::ToDeviceDummyEvent, - forwarded_room_key::ToDeviceForwardedRoomKeyEvent, key::verification::{ accept::ToDeviceKeyVerificationAcceptEvent, cancel::ToDeviceKeyVerificationCancelEvent, done::ToDeviceKeyVerificationDoneEvent, key::ToDeviceKeyVerificationKeyEvent, @@ -39,7 +38,10 @@ use serde_json::{ use zeroize::Zeroize; use super::{ - room::encrypted::EncryptedToDeviceEvent, room_key::RoomKeyEvent, secret_send::SecretSendEvent, + forwarded_room_key::{ForwardedRoomKeyContent, ForwardedRoomKeyEvent}, + room::encrypted::EncryptedToDeviceEvent, + room_key::RoomKeyEvent, + secret_send::SecretSendEvent, EventType, }; use crate::types::events::from_str; @@ -76,7 +78,7 @@ pub enum ToDeviceEvents { /// The `m.room_key_request` to-device event. RoomKeyRequest(ToDeviceRoomKeyRequestEvent), /// The `m.forwarded_room_key` to-device event. - ForwardedRoomKey(ToDeviceForwardedRoomKeyEvent), + ForwardedRoomKey(Box), /// The `m.secret.send` to-device event. SecretSend(SecretSendEvent), /// The `m.secret.request` to-device event. @@ -127,7 +129,7 @@ impl ToDeviceEvents { ToDeviceEvents::RoomEncrypted(_) => ToDeviceEventType::RoomEncrypted, ToDeviceEvents::RoomKey(_) => ToDeviceEventType::RoomKey, ToDeviceEvents::RoomKeyRequest(e) => e.content.event_type(), - ToDeviceEvents::ForwardedRoomKey(e) => e.content.event_type(), + ToDeviceEvents::ForwardedRoomKey(_) => ToDeviceEventType::ForwardedRoomKey, ToDeviceEvents::SecretSend(_) => ToDeviceEventType::SecretSend, ToDeviceEvents::SecretRequest(e) => e.content.event_type(), @@ -197,7 +199,11 @@ impl ToDeviceEvents { Raw::from_json(raw_value) } ToDeviceEvents::ForwardedRoomKey(mut e) => { - e.content.session_key.zeroize(); + match &mut e.content { + ForwardedRoomKeyContent::MegolmV1AesSha2(c) => c.session_key.zeroize(), + ForwardedRoomKeyContent::Unknown(_) => (), + } + Raw::from_json(to_raw_value(&e)?) } ToDeviceEvents::SecretSend(mut e) => { @@ -410,7 +416,11 @@ mod test { "sender_claimed_ed25519_key": "aj40p+aw64yPIdsxoog8jhPu9i7l7NcFRecuOQblE3Y", "sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU", "session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ", - "session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf..." + "session_key": "AQAAAAq2JpkMceK5f6JrZPJWwzQTn59zliuIv0F7apVLXDcZCCT\ + 3LqBjD21sULYEO5YTKdpMVhi9i6ZSZhdvZvp//tzRpDT7wpWVWI\ + 00Y3EPEjmpm/HfZ4MMAKpk+tzJVuuvfAcHBZgpnxBGzYOc/DAqa\ + pK7Tk3t3QJ1UMSD94HfAqlb1JF5QBPwoh0fOvD8pJdanB8zxz05\ + tKFdR73/vo2Q/zE3" }, "type": "m.forwarded_room_key" }) diff --git a/crates/matrix-sdk-crypto/src/types/mod.rs b/crates/matrix-sdk-crypto/src/types/mod.rs index 0d4cd6e9d..700d6e733 100644 --- a/crates/matrix-sdk-crypto/src/types/mod.rs +++ b/crates/matrix-sdk-crypto/src/types/mod.rs @@ -37,7 +37,7 @@ pub use device_keys::*; pub use one_time_keys::*; use ruma::{DeviceKeyAlgorithm, DeviceKeyId, OwnedDeviceKeyId, OwnedUserId, UserId}; use serde::{Deserialize, Serialize, Serializer}; -use vodozemac::{Curve25519PublicKey, Ed25519Signature}; +use vodozemac::{Curve25519PublicKey, Ed25519PublicKey, Ed25519Signature, KeyError}; /// Represents a potentially decoded signature (but *not* a validated one). /// @@ -248,3 +248,41 @@ where let key = key.to_base64(); s.serialize_str(&key) } + +pub(crate) fn deserialize_ed25519_key<'de, D>(de: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let key: String = Deserialize::deserialize(de)?; + Ed25519PublicKey::from_base64(&key).map_err(serde::de::Error::custom) +} + +pub(crate) fn serialize_ed25519_key(key: &Ed25519PublicKey, s: S) -> Result +where + S: Serializer, +{ + let key = key.to_base64(); + s.serialize_str(&key) +} + +pub(crate) fn deserialize_curve_key_vec<'de, D>(de: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + let keys: Vec = Deserialize::deserialize(de)?; + let keys: Result, KeyError> = + keys.iter().map(|k| Curve25519PublicKey::from_base64(k)).collect(); + + keys.map_err(serde::de::Error::custom) +} + +pub(crate) fn serialize_curve_key_vec( + keys: &[Curve25519PublicKey], + s: S, +) -> Result +where + S: Serializer, +{ + let keys: Vec = keys.iter().map(|k| k.to_base64()).collect(); + keys.serialize(s) +}