diff --git a/.typos.toml b/.typos.toml index 3a2312d4f82..84c55dc4c26 100644 --- a/.typos.toml +++ b/.typos.toml @@ -36,4 +36,5 @@ extend-exclude = [ "crates/matrix-sdk-ui/tests/integration/room_list_service.rs", # Hand-crafted base64 session keys that are understood as typos. "crates/matrix-sdk-indexeddb/src/crypto_store/migrations/mod.rs", + "crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs", ] diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 63c5936a1d4..1810e127aff 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -35,7 +35,7 @@ pub use logger::{set_logger, Logger}; pub use machine::{KeyRequestPair, OlmMachine, SignatureVerification}; use matrix_sdk_common::deserialized_responses::ShieldState as RustShieldState; use matrix_sdk_crypto::{ - olm::{IdentityKeys, InboundGroupSession, Session}, + olm::{IdentityKeys, InboundGroupSession, SenderData, Session}, store::{Changes, CryptoStore, PendingChanges, RoomSettings as RustRoomSettings}, types::{ DeviceKey, DeviceKeys, EventEncryptionAlgorithm as RustEventEncryptionAlgorithm, SigningKey, @@ -501,6 +501,7 @@ fn collect_sessions( Ok((algorithm, key)) }) .collect::>()?, + sender_data: SenderData::legacy(), room_id: RoomId::parse(session.room_id)?, imported: session.imported, backed_up: session.backed_up, diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index dfa1ea3f134..95822e77454 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -66,7 +66,7 @@ use crate::{ identities::{user::UserIdentities, Device, IdentityManager, UserDevices}, olm::{ Account, CrossSigningStatus, EncryptionSettings, IdentityKeys, InboundGroupSession, - OlmDecryptionInfo, PrivateCrossSigningIdentity, SessionType, StaticAccountData, + OlmDecryptionInfo, PrivateCrossSigningIdentity, SenderData, SessionType, StaticAccountData, }, requests::{IncomingResponse, OutgoingRequest, UploadSigningKeysRequest}, session_manager::{GroupSessionManager, SessionManager}, @@ -816,11 +816,14 @@ impl OlmMachine { event: &DecryptedRoomKeyEvent, content: &MegolmV1AesSha2Content, ) -> OlmResult> { + let sender_data = SenderData::unknown(); + let session = InboundGroupSession::new( sender_key, event.keys.ed25519, &content.room_id, &content.session_key, + sender_data, event.content.algorithm(), None, ); @@ -2417,7 +2420,8 @@ pub(crate) mod tests { error::{EventError, SetRoomSettingsError}, machine::{EncryptionSyncChanges, OlmMachine}, olm::{ - BackedUpRoomKey, ExportedRoomKey, InboundGroupSession, OutboundGroupSession, VerifyJson, + BackedUpRoomKey, ExportedRoomKey, InboundGroupSession, OutboundGroupSession, + SenderData, VerifyJson, }, session_manager::CollectStrategy, store::{BackupDecryptionKey, Changes, CryptoStore, MemoryStore, RoomSettings}, @@ -3713,6 +3717,7 @@ pub(crate) mod tests { Ed25519PublicKey::from_base64("loz5i40dP+azDtWvsD0L/xpnCjNkmrcvtXVXzCHX8Vw").unwrap(), fake_room_id, &olm, + SenderData::unknown(), EventEncryptionAlgorithm::MegolmV1AesSha2, None, ) @@ -3730,6 +3735,7 @@ pub(crate) mod tests { Ed25519PublicKey::from_base64("48f3WQAMGwYLBg5M5qUhqnEVA8yeibjZpPsShoWMFT8").unwrap(), fake_room_id, &olm, + SenderData::unknown(), EventEncryptionAlgorithm::MegolmV1AesSha2, None, ) diff --git a/crates/matrix-sdk-crypto/src/olm/account.rs b/crates/matrix-sdk-crypto/src/olm/account.rs index e16ea78e332..eae5bcea90f 100644 --- a/crates/matrix-sdk-crypto/src/olm/account.rs +++ b/crates/matrix-sdk-crypto/src/olm/account.rs @@ -61,6 +61,7 @@ use crate::{ dehydrated_devices::DehydrationError, error::{EventError, OlmResult, SessionCreationError}, identities::ReadOnlyDevice, + olm::SenderData, requests::UploadSigningKeysRequest, store::{Changes, DeviceChanges, Store}, types::{ @@ -220,6 +221,7 @@ impl StaticAccountData { signing_key, room_id, &outbound.session_key().await, + SenderData::unknown(), algorithm, Some(visibility), )?; 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 ab00d7a05ba..dac2c21794f 100644 --- a/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs @@ -36,7 +36,8 @@ use vodozemac::{ }; use super::{ - BackedUpRoomKey, ExportedRoomKey, OutboundGroupSession, SessionCreationError, SessionKey, + BackedUpRoomKey, ExportedRoomKey, OutboundGroupSession, SenderData, SessionCreationError, + SessionKey, }; use crate::{ error::{EventError, MegolmResult}, @@ -121,6 +122,13 @@ pub struct InboundGroupSession { /// on how the session was received. pub(crate) creator_info: SessionCreatorInfo, + /// Information about the sender of this session and how much we trust that + /// information. Holds the information we have about the device that created + /// the session, or, if we can use that device information to find the + /// sender's cross-signing identity, holds the user ID and cross-signing + /// key. + pub(crate) sender_data: SenderData, + /// The Room this GroupSession belongs to pub room_id: OwnedRoomId, @@ -163,11 +171,15 @@ impl InboundGroupSession { /// /// * `session_key` - The private session key that is used to decrypt /// messages. + /// + /// * `sender_data` - Information about the sender of the to-device message + /// that established this session. pub fn new( sender_key: Curve25519PublicKey, signing_key: Ed25519PublicKey, room_id: &RoomId, session_key: &SessionKey, + sender_data: SenderData, encryption_algorithm: EventEncryptionAlgorithm, history_visibility: Option, ) -> Result { @@ -189,6 +201,7 @@ impl InboundGroupSession { curve25519_key: sender_key, signing_keys: keys.into(), }, + sender_data, room_id: room_id.into(), imported: false, algorithm: encryption_algorithm.into(), @@ -241,6 +254,7 @@ impl InboundGroupSession { pickle, sender_key: self.creator_info.curve25519_key, signing_key: (*self.creator_info.signing_keys).clone(), + sender_data: self.sender_data.clone(), room_id: self.room_id().to_owned(), imported: self.imported, backed_up: self.backed_up(), @@ -324,6 +338,7 @@ impl InboundGroupSession { curve25519_key: pickle.sender_key, signing_keys: pickle.signing_key.into(), }, + sender_data: pickle.sender_data, history_visibility: pickle.history_visibility.into(), first_known_index, room_id: (*pickle.room_id).into(), @@ -485,6 +500,9 @@ pub struct PickledInboundGroupSession { pub sender_key: Curve25519PublicKey, /// The public ed25519 key of the account that sent us the session. pub signing_key: SigningKeys, + /// Information on the device/sender who sent us this session + #[serde(default)] + pub sender_data: SenderData, /// The id of the room that the session is used in. pub room_id: OwnedRoomId, /// Flag remembering if the session was directly sent to us by the sender @@ -519,6 +537,9 @@ impl TryFrom<&ExportedRoomKey> for InboundGroupSession { curve25519_key: key.sender_key, signing_keys: key.sender_claimed_keys.to_owned().into(), }, + // TODO: In future, exported keys should contain sender data that we can use here. + // See https://github.com/matrix-org/matrix-rust-sdk/issues/3548 + sender_data: SenderData::default(), history_visibility: None.into(), first_known_index, room_id: key.room_id.to_owned(), @@ -546,6 +567,9 @@ impl From<&ForwardedMegolmV1AesSha2Content> for InboundGroupSession { )]) .into(), }, + // In future, exported keys should contain sender data that we can use here. + // See https://github.com/matrix-org/matrix-rust-sdk/issues/3548 + sender_data: SenderData::default(), history_visibility: None.into(), first_known_index, room_id: value.room_id.to_owned(), @@ -569,6 +593,9 @@ impl From<&ForwardedMegolmV2AesSha2Content> for InboundGroupSession { curve25519_key: value.claimed_sender_key, signing_keys: value.claimed_signing_keys.to_owned().into(), }, + // In future, exported keys should contain sender data that we can use here. + // See https://github.com/matrix-org/matrix-rust-sdk/issues/3548 + sender_data: SenderData::default(), history_visibility: None.into(), first_known_index, room_id: value.room_id.to_owned(), @@ -596,11 +623,22 @@ impl TryFrom<&DecryptedForwardedRoomKeyEvent> for InboundGroupSession { #[cfg(test)] mod tests { + use assert_matches2::assert_let; use matrix_sdk_test::async_test; - use ruma::{device_id, room_id, user_id, DeviceId, UserId}; - use vodozemac::{megolm::SessionOrdering, Curve25519PublicKey}; - - use crate::{olm::InboundGroupSession, Account}; + use ruma::{ + device_id, events::room::history_visibility::HistoryVisibility, room_id, user_id, DeviceId, + UInt, UserId, + }; + use vodozemac::{ + megolm::{SessionKey, SessionOrdering}, + Curve25519PublicKey, Ed25519PublicKey, + }; + + use crate::{ + olm::{InboundGroupSession, SenderData, SenderDataRetryDetails}, + types::EventEncryptionAlgorithm, + Account, + }; fn alice_id() -> &'static UserId { user_id!("@alice:example.org") @@ -611,7 +649,133 @@ mod tests { } #[async_test] - async fn inbound_group_session_serialization() { + async fn test_can_deserialise_pickled_session_without_sender_data() { + // Given the raw JSON for a picked inbound group session without any sender_data + let pickle = r#" + { + "pickle": { + "initial_ratchet": { + "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215, + 220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212, + 229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105, + 202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68, + 138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145, + 245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17, + 2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167, + 205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131, + 52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ], + "counter": 0 + }, + "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118, + 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38, + 149, 43, 38 ], + "signing_key_verified": true, + "config": { + "version": "V1" + } + }, + "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8", + "signing_key": { + "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww" + }, + "room_id": "!test:localhost", + "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"], + "imported": false, + "backed_up": false, + "history_visibility": "shared", + "algorithm": "m.megolm.v1.aes-sha2" + } + "#; + + // When we deserialise it to from JSON + let deserialized = serde_json::from_str(pickle).unwrap(); + + // And unpickle it + let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap(); + + // Then it was parsed correctly + assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY"); + + // And we populated the InboundGroupSession's sender_data with a default value, + // with legacy_session set to true. + assert_let!( + SenderData::UnknownDevice { retry_details, legacy_session } = unpickled.sender_data + ); + assert_eq!(retry_details.retry_count, 0); + assert!(legacy_session); + } + + #[async_test] + async fn test_can_serialise_pickled_session_with_sender_data() { + // Given an InboundGroupSession + let igs = InboundGroupSession::new( + Curve25519PublicKey::from_base64("AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8") + .unwrap(), + Ed25519PublicKey::from_base64("wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww").unwrap(), + room_id!("!test:localhost"), + &create_session_key(), + SenderData::unknown_retry_at(SenderDataRetryDetails::new(5, 1234)), + EventEncryptionAlgorithm::MegolmV1AesSha2, + Some(HistoryVisibility::Shared), + ) + .unwrap(); + + // When we pickle it + let pickled = igs.pickle().await; + + // And serialise it + let serialised = serde_json::to_string(&pickled).unwrap(); + + // Then it looks as we expect + + // (Break out this list of numbers as otherwise it bothers the json macro below) + let expected_inner = vec![ + 193, 203, 223, 152, 33, 132, 200, 168, 24, 197, 79, 174, 231, 202, 45, 245, 128, 131, + 178, 165, 148, 37, 241, 214, 178, 218, 25, 33, 68, 48, 153, 104, 122, 6, 249, 198, 97, + 226, 214, 75, 64, 128, 25, 138, 98, 90, 138, 93, 52, 206, 174, 3, 84, 149, 101, 140, + 238, 156, 103, 107, 124, 144, 139, 104, 253, 5, 100, 251, 186, 118, 208, 87, 31, 218, + 123, 234, 103, 34, 246, 100, 39, 90, 216, 72, 187, 86, 202, 150, 100, 116, 204, 254, + 10, 154, 216, 133, 61, 250, 75, 100, 195, 63, 138, 22, 17, 13, 156, 123, 195, 132, 111, + 95, 250, 24, 236, 0, 246, 93, 230, 100, 211, 165, 211, 190, 181, 87, 42, 181, + ]; + assert_eq!( + serde_json::from_str::(&serialised).unwrap(), + serde_json::json!({ + "pickle":{ + "initial_ratchet":{ + "inner": expected_inner, + "counter":0 + }, + "signing_key":[ + 213,161,95,135,114,153,162,127,217,74,64,2,59,143,93,5,190,157,120, + 80,89,8,87,129,115,148,104,144,152,186,178,109 + ], + "signing_key_verified":true, + "config":{"version":"V1"} + }, + "sender_key":"AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8", + "signing_key":{"ed25519":"wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"}, + "sender_data":{ + "UnknownDevice":{ + "retry_details":{ + "retry_count":5, + "next_retry_time_ms":1234 + }, + "legacy_session":false + } + }, + "room_id":"!test:localhost", + "imported":false, + "backed_up":false, + "history_visibility":"shared", + "algorithm":"m.megolm.v1.aes-sha2" + }) + ); + } + + #[async_test] + async fn test_can_deserialise_pickled_session_with_sender_data() { + // Given the raw JSON for a picked inbound group session (including sender_data) let pickle = r#" { "pickle": { @@ -639,6 +803,15 @@ mod tests { "signing_key": { "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww" }, + "sender_data":{ + "UnknownDevice":{ + "retry_details":{ + "retry_count":0, + "next_retry_time_ms":98765 + }, + "legacy_session":false + } + }, "room_id": "!test:localhost", "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"], "imported": false, @@ -648,11 +821,24 @@ mod tests { } "#; + // When we deserialise it to from JSON let deserialized = serde_json::from_str(pickle).unwrap(); + // And unpickle it let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap(); + // Then it was parsed correctly assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY"); + + // And we populated the InboundGroupSession's sender_data with the provided + // values + let SenderData::UnknownDevice { retry_details, legacy_session } = unpickled.sender_data + else { + panic!("Expected sender_data to be UnknownDevice!"); + }; + assert_eq!(retry_details.retry_count, 0); + assert_eq!(retry_details.next_retry_time_ms.0, UInt::new(98765).unwrap()); + assert!(!legacy_session); } #[async_test] @@ -676,4 +862,17 @@ mod tests { assert_eq!(inbound.compare(©).await, SessionOrdering::Unconnected); } + + fn create_session_key() -> SessionKey { + SessionKey::from_base64( + "\ + AgAAAADBy9+YIYTIqBjFT67nyi31gIOypZQl8day2hkhRDCZaHoG+cZh4tZLQIAZimJail0\ + 0zq4DVJVljO6cZ2t8kIto/QVk+7p20Fcf2nvqZyL2ZCda2Ei7VsqWZHTM/gqa2IU9+ktkwz\ + +KFhENnHvDhG9f+hjsAPZd5mTTpdO+tVcqtdWhX4dymaJ/2UpAAjuPXQW+nXhQWQhXgXOUa\ + JCYurJtvbCbqZGeDMmVIoqukBs2KugNJ6j5WlTPoeFnMl6Guy9uH2iWWxGg8ZgT2xspqVl5\ + CwujjC+m7Dh1toVkvu+bAw\ + ", + ) + .unwrap() + } } 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 f832723e0d8..cf1c5255357 100644 --- a/crates/matrix-sdk-crypto/src/olm/group_sessions/mod.rs +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/mod.rs @@ -17,12 +17,14 @@ use serde::{Deserialize, Serialize}; mod inbound; mod outbound; +mod sender_data; pub use inbound::{InboundGroupSession, PickledInboundGroupSession}; pub(crate) use outbound::ShareState; pub use outbound::{ EncryptionSettings, OutboundGroupSession, PickledOutboundGroupSession, ShareInfo, }; +pub use sender_data::{SenderData, SenderDataRetryDetails}; use thiserror::Error; pub use vodozemac::megolm::{ExportedSessionKey, SessionKey}; use vodozemac::{megolm::SessionKeyDecodeError, Curve25519PublicKey}; 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 6e8fe3a81aa..5f2e05d0a70 100644 --- a/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs @@ -531,6 +531,7 @@ impl OutboundGroupSession { self.room_id().to_owned(), self.session_id().to_owned(), session_key, + None, ) .into(), ) diff --git a/crates/matrix-sdk-crypto/src/olm/group_sessions/sender_data.rs b/crates/matrix-sdk-crypto/src/olm/group_sessions/sender_data.rs new file mode 100644 index 00000000000..6984ac5be05 --- /dev/null +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/sender_data.rs @@ -0,0 +1,147 @@ +// Copyright 2024 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. + +use ruma::{MilliSecondsSinceUnixEpoch, OwnedUserId}; +use serde::{Deserialize, Serialize}; +use vodozemac::Ed25519PublicKey; + +use crate::types::DeviceKeys; + +/// Information on the device and user that sent the megolm session data to us +/// +/// Sessions start off in `UnknownDevice` state, and progress into `DeviceInfo` +/// state when we get the device info. Finally, if we can look up the sender +/// using the device info, the session can be moved into `SenderKnown` state. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub enum SenderData { + /// We have not yet found the (signed) device info for the sending device + UnknownDevice { + /// When we will next try again to find device info for this session, + /// and how many times we have tried + retry_details: SenderDataRetryDetails, + + /// Was this session created before we started collecting trust + /// information about sessions? If so, we may choose to display its + /// messages even though trust info is missing. + legacy_session: bool, + }, + + /// We have the signed device info for the sending device, but not yet the + /// cross-signing key that it was signed with. + DeviceInfo { + /// Information about the device that sent the to-device message + /// creating this session. + device_keys: DeviceKeys, + /// When we will next try again to find a cross-signing key that signed + /// the device information, and how many times we have tried. + retry_details: SenderDataRetryDetails, + + /// Was this session created before we started collecting trust + /// information about sessions? If so, we may choose to display its + /// messages even though trust info is missing. + legacy_session: bool, + }, + + /// We have found proof that this user, with this cross-signing key, sent + /// the to-device message that established this session. + SenderKnown { + /// The user ID of the user who established this session. + user_id: OwnedUserId, + + /// The cross-signing key of the user who established this session. + msk: Ed25519PublicKey, + + /// Whether, at the time we checked the signature on the device, + /// we had actively verified that `msk` belongs to the user. + /// If false, we had simply accepted the key as this user's latest + /// key. + msk_verified: bool, + }, +} + +impl SenderData { + /// Create a [`SenderData`] which contains no device info and will be + /// retried soon. + pub fn unknown() -> Self { + Self::UnknownDevice { + retry_details: SenderDataRetryDetails::retry_soon(), + // TODO: when we have implemented all of SenderDataFinder, + // legacy_session should be set to false, but for now we leave + // it as true because we might lose device info while + // this code is still in transition. + legacy_session: true, + } + } + + /// Create a [`SenderData`] which has the legacy flag set. Caution: messages + /// within sessions with this flag will be displayed in some contexts, + /// even when we are unable to verify the sender. + /// + /// The returned struct contains no device info, and will be retried soon. + pub fn legacy() -> Self { + Self::UnknownDevice { + retry_details: SenderDataRetryDetails::retry_soon(), + legacy_session: true, + } + } + + #[cfg(test)] + pub(crate) fn unknown_retry_at(retry_details: SenderDataRetryDetails) -> Self { + Self::UnknownDevice { retry_details, legacy_session: false } + } +} + +/// Used when deserialising and the sender_data property is missing. +/// If we are deserialising an InboundGroupSession session with missing +/// sender_data, this must be a legacy session (i.e. it was created before we +/// started tracking sender data). We set its legacy flag to true, and set it up +/// to be retried soon, so we can populate it with trust information if it is +/// available. +impl Default for SenderData { + fn default() -> Self { + Self::legacy() + } +} + +/// Tracking information about when we need to try again fetching device or +/// user information, and how many times we have already tried. +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct SenderDataRetryDetails { + /// How many times we have already tried to find the currently-needed + /// information for this session. + pub retry_count: u8, + + /// What time to try again to find the currently-needed information. + pub next_retry_time_ms: MilliSecondsSinceUnixEpoch, +} + +impl SenderDataRetryDetails { + /// Create a new RetryDetails with a retry count of zero, and retry time of + /// now. + pub(crate) fn retry_soon() -> Self { + Self { retry_count: 0, next_retry_time_ms: MilliSecondsSinceUnixEpoch::now() } + } + + #[cfg(test)] + pub(crate) fn new(retry_count: u8, next_retry_time_ms: u64) -> Self { + use ruma::UInt; + + Self { + retry_count, + next_retry_time_ms: MilliSecondsSinceUnixEpoch( + UInt::try_from(next_retry_time_ms).unwrap_or(UInt::from(0u8)), + ), + } + } +} diff --git a/crates/matrix-sdk-crypto/src/olm/mod.rs b/crates/matrix-sdk-crypto/src/olm/mod.rs index 8bf6d6e75e5..51396e67c89 100644 --- a/crates/matrix-sdk-crypto/src/olm/mod.rs +++ b/crates/matrix-sdk-crypto/src/olm/mod.rs @@ -28,8 +28,8 @@ pub(crate) use account::{OlmDecryptionInfo, SessionType}; pub(crate) use group_sessions::ShareState; pub use group_sessions::{ BackedUpRoomKey, EncryptionSettings, ExportedRoomKey, InboundGroupSession, - OutboundGroupSession, PickledInboundGroupSession, PickledOutboundGroupSession, - SessionCreationError, SessionExportError, SessionKey, ShareInfo, + OutboundGroupSession, PickledInboundGroupSession, PickledOutboundGroupSession, SenderData, + SenderDataRetryDetails, SessionCreationError, SessionExportError, SessionKey, ShareInfo, }; pub use session::{PickledSession, Session}; pub use signing::{CrossSigningStatus, PickledCrossSigningIdentity, PrivateCrossSigningIdentity}; @@ -58,7 +58,7 @@ pub(crate) mod tests { }; use crate::{ - olm::{Account, ExportedRoomKey, InboundGroupSession, Session}, + olm::{Account, ExportedRoomKey, InboundGroupSession, SenderData, Session}, types::events::{ forwarded_room_key::ForwardedRoomKeyContent, room::encrypted::EncryptedEvent, }, @@ -185,6 +185,7 @@ pub(crate) mod tests { Ed25519PublicKey::from_base64("ee3Ek+J2LkkPmjGPGLhMxiKnhiX//xcqaVL4RP6EypE").unwrap(), room_id, &outbound.session_key().await, + SenderData::unknown(), outbound.settings().algorithm.to_owned(), None, ) @@ -228,6 +229,7 @@ pub(crate) mod tests { Ed25519PublicKey::from_base64("ee3Ek+J2LkkPmjGPGLhMxiKnhiX//xcqaVL4RP6EypE").unwrap(), room_id, &outbound.session_key().await, + SenderData::unknown(), outbound.settings().algorithm.to_owned(), None, ) diff --git a/crates/matrix-sdk-crypto/src/store/caches.rs b/crates/matrix-sdk-crypto/src/store/caches.rs index 59397529b50..dbd95ffa61b 100644 --- a/crates/matrix-sdk-crypto/src/store/caches.rs +++ b/crates/matrix-sdk-crypto/src/store/caches.rs @@ -394,7 +394,7 @@ mod tests { use super::{DeviceStore, GroupSessionStore, SequenceNumber, SessionStore}; use crate::{ identities::device::testing::get_device, - olm::{tests::get_account_and_session_test_helper, InboundGroupSession}, + olm::{tests::get_account_and_session_test_helper, InboundGroupSession, SenderData}, }; #[async_test] @@ -447,6 +447,7 @@ mod tests { Ed25519PublicKey::from_base64("ee3Ek+J2LkkPmjGPGLhMxiKnhiX//xcqaVL4RP6EypE").unwrap(), room_id, &outbound.session_key().await, + SenderData::unknown(), outbound.settings().algorithm.to_owned(), None, ) diff --git a/crates/matrix-sdk-crypto/src/store/memorystore.rs b/crates/matrix-sdk-crypto/src/store/memorystore.rs index 0b465dcbae8..f27c5129a87 100644 --- a/crates/matrix-sdk-crypto/src/store/memorystore.rs +++ b/crates/matrix-sdk-crypto/src/store/memorystore.rs @@ -625,7 +625,7 @@ mod tests { identities::device::testing::get_device, olm::{ tests::get_account_and_session_test_helper, Account, InboundGroupSession, - OlmMessageHash, PrivateCrossSigningIdentity, + OlmMessageHash, PrivateCrossSigningIdentity, SenderData, }, store::{memorystore::MemoryStore, Changes, CryptoStore, PendingChanges}, }; @@ -660,6 +660,7 @@ mod tests { Ed25519PublicKey::from_base64("ee3Ek+J2LkkPmjGPGLhMxiKnhiX//xcqaVL4RP6EypE").unwrap(), room_id, &outbound.session_key().await, + SenderData::unknown(), outbound.settings().algorithm.to_owned(), None, ) @@ -1056,6 +1057,7 @@ mod tests { Ed25519PublicKey::from_base64("ee3Ek+J2LkkPmjGPGLhMxiKnhiX//xcqaVL4RP6EypE").unwrap(), room_id, &outbound.session_key().await, + SenderData::unknown(), outbound.settings().algorithm.to_owned(), None, ) diff --git a/crates/matrix-sdk-crypto/src/types/events/room/encrypted.rs b/crates/matrix-sdk-crypto/src/types/events/room/encrypted.rs index debb082f324..399f8f044da 100644 --- a/crates/matrix-sdk-crypto/src/types/events/room/encrypted.rs +++ b/crates/matrix-sdk-crypto/src/types/events/room/encrypted.rs @@ -431,6 +431,7 @@ pub(crate) mod tests { "device_id": "DEWRCMENGS", "session_id": "ZFD6+OmV7fVCsJ7Gap8UnORH8EnmiAkes8FAvQuCw/I", "sender_key": "WJ6Ce7U67a6jqkHYHd8o0+5H4bqdi9hInZdk0+swuXs", + "device_keys": null, "ciphertext": "AwgAEiBQs2LgBD2CcB+RLH2bsgp9VadFUJhBXOtCmcJuttBDOeDNjL21d9\ z0AcVSfQFAh9huh4or7sWuNrHcvu9/sMbweTgc0UtdA5xFLheubHouXy4a\ 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 be1ae640633..a294abb9167 100644 --- a/crates/matrix-sdk-crypto/src/types/events/room_key.rs +++ b/crates/matrix-sdk-crypto/src/types/events/room_key.rs @@ -22,7 +22,7 @@ use serde_json::{value::to_raw_value, Value}; use vodozemac::megolm::SessionKey; use super::{EventType, ToDeviceEvent}; -use crate::types::EventEncryptionAlgorithm; +use crate::types::{DeviceKeys, EventEncryptionAlgorithm}; /// The `m.room_key` to-device event. pub type RoomKeyEvent = ToDeviceEvent; @@ -113,6 +113,8 @@ pub struct MegolmV1AesSha2Content { /// /// [`InboundGroupSession`]: vodozemac::megolm::InboundGroupSession pub session_key: SessionKey, + /// The device keys if supplied as per MSC4147 + pub device_keys: Option, /// Any other, custom and non-specced fields of the content. #[serde(flatten)] other: BTreeMap, @@ -120,8 +122,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, + device_keys: Option, + ) -> Self { + Self { room_id, session_id, session_key, device_keys, other: Default::default() } } } @@ -224,7 +231,8 @@ pub(super) mod tests { tino//CDQENtcKuEt0I9s0+Kk4YSH310Szse2RQ+vjple31\ QrCexmqfFJzkR/BJ5ogJHrPBQL0LgsPyglIbMTLg7qygIaY\ U5Fe2QdKMH7nTZPNIRHh1RaMfHVETAUJBax88EWZBoifk80\ - gdHUwHSgMk77vCc2a5KHKLDA" + gdHUwHSgMk77vCc2a5KHKLDA", + "device_keys": null }, "type": "m.room_key", "m.custom.top": "something custom in the top", 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 e274455f404..285c37c50c2 100644 --- a/crates/matrix-sdk-indexeddb/src/crypto_store/migrations/mod.rs +++ b/crates/matrix-sdk-indexeddb/src/crypto_store/migrations/mod.rs @@ -169,7 +169,7 @@ mod tests { use indexed_db_futures::prelude::*; use matrix_sdk_common::js_tracing::make_tracing_subscriber; use matrix_sdk_crypto::{ - olm::{InboundGroupSession, SessionKey}, + olm::{InboundGroupSession, SenderData, SessionKey}, store::CryptoStore, types::EventEncryptionAlgorithm, vodozemac::{Curve25519PublicKey, Curve25519SecretKey, Ed25519PublicKey, Ed25519SecretKey}, @@ -400,6 +400,7 @@ mod tests { signing_key, &room_id, session_key, + SenderData::unknown(), encryption_algorithm, history_visibility, ) @@ -510,6 +511,7 @@ mod tests { 33ii9J8RGPYOp7QWl0kTEc8mAlqZL7mKppo9AwgtmYweAg", ) .unwrap(), + SenderData::legacy(), EventEncryptionAlgorithm::MegolmV1AesSha2, None, ) @@ -528,6 +530,7 @@ mod tests { 1NWjZD9f1vvXnSKKDdHj1927WFMFZ/yYc24607zEVUaODQ", ) .unwrap(), + SenderData::legacy(), EventEncryptionAlgorithm::MegolmV1AesSha2, None, ) diff --git a/crates/matrix-sdk/tests/integration/encryption/backups.rs b/crates/matrix-sdk/tests/integration/encryption/backups.rs index eaf1df2bb87..8908ff14924 100644 --- a/crates/matrix-sdk/tests/integration/encryption/backups.rs +++ b/crates/matrix-sdk/tests/integration/encryption/backups.rs @@ -20,7 +20,7 @@ use futures_util::{pin_mut, FutureExt, StreamExt}; use matrix_sdk::{ config::RequestConfig, crypto::{ - olm::{InboundGroupSession, SessionCreationError}, + olm::{InboundGroupSession, SenderData, SessionCreationError}, store::BackupDecryptionKey, types::EventEncryptionAlgorithm, }, @@ -1533,6 +1533,7 @@ async fn inbound_session_from_outbound_session( sender_signing_key, room_id, &outbound_group_session.session_key().await, + SenderData::unknown(), EventEncryptionAlgorithm::MegolmV1AesSha2, None, )