Skip to main content

matrix_sdk_crypto/olm/group_sessions/
outbound.rs

1// Copyright 2020 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{
16    cmp::max,
17    collections::{BTreeMap, BTreeSet},
18    fmt,
19    ops::Bound,
20    sync::{
21        Arc, RwLockReadGuard,
22        atomic::{AtomicBool, AtomicU64, Ordering},
23    },
24    time::Duration,
25};
26
27use matrix_sdk_common::{deserialized_responses::WithheldCode, locks::RwLock as StdRwLock};
28#[cfg(feature = "experimental-encrypted-state-events")]
29use ruma::events::AnyStateEventContent;
30use ruma::{
31    DeviceId, OwnedDeviceId, OwnedRoomId, OwnedTransactionId, OwnedUserId, RoomId,
32    SecondsSinceUnixEpoch, TransactionId, UserId,
33    events::{
34        AnyMessageLikeEventContent,
35        room::{
36            encryption::{PossiblyRedactedRoomEncryptionEventContent, RoomEncryptionEventContent},
37            history_visibility::HistoryVisibility,
38        },
39    },
40    serde::Raw,
41};
42use serde::{Deserialize, Serialize};
43use tokio::sync::RwLock;
44use tracing::{debug, error, info};
45use vodozemac::{Curve25519PublicKey, megolm::SessionConfig};
46pub use vodozemac::{
47    PickleError,
48    megolm::{GroupSession, GroupSessionPickle, MegolmMessage, SessionKey},
49    olm::IdentityKeys,
50};
51
52use super::SessionCreationError;
53#[cfg(feature = "experimental-algorithms")]
54use crate::types::events::room::encrypted::MegolmV2AesSha2Content;
55use crate::{
56    DeviceData,
57    olm::account::shared_history_from_history_visibility,
58    session_manager::CollectStrategy,
59    store::caches::SequenceNumber,
60    types::{
61        EventEncryptionAlgorithm,
62        events::{
63            room::encrypted::{
64                MegolmV1AesSha2Content, RoomEncryptedEventContent, RoomEventEncryptionScheme,
65            },
66            room_key::{MegolmV1AesSha2Content as MegolmV1AesSha2RoomKeyContent, RoomKeyContent},
67            room_key_withheld::RoomKeyWithheldContent,
68        },
69        requests::ToDeviceRequest,
70    },
71};
72
73const ONE_HOUR: Duration = Duration::from_secs(60 * 60);
74const ONE_WEEK: Duration = Duration::from_secs(60 * 60 * 24 * 7);
75
76const ROTATION_PERIOD: Duration = ONE_WEEK;
77const ROTATION_MESSAGES: u64 = 100;
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
80/// Information about whether a session was shared with a device.
81pub(crate) enum ShareState {
82    /// The session was not shared with the device.
83    NotShared,
84    /// The session was shared with the device with the given device ID, but
85    /// with a different curve25519 key.
86    SharedButChangedSenderKey,
87    /// The session was shared with the device, at the given message index. The
88    /// `olm_wedging_index` is the value of the `olm_wedging_index` from the
89    /// [`DeviceData`] at the time that we last shared the session with the
90    /// device, and indicates whether we need to re-share the session with the
91    /// device.
92    Shared { message_index: u32, olm_wedging_index: SequenceNumber },
93}
94
95/// Settings for an encrypted room.
96///
97/// This determines the algorithm and rotation periods of a group session.
98#[derive(Clone, Debug, Deserialize, Serialize)]
99pub struct EncryptionSettings {
100    /// The encryption algorithm that should be used in the room.
101    pub algorithm: EventEncryptionAlgorithm,
102    /// Whether state event encryption is enabled.
103    #[cfg(feature = "experimental-encrypted-state-events")]
104    #[serde(default)]
105    pub encrypt_state_events: bool,
106    /// How long the session should be used before changing it.
107    pub rotation_period: Duration,
108    /// How many messages should be sent before changing the session.
109    pub rotation_period_msgs: u64,
110    /// The history visibility of the room when the session was created.
111    pub history_visibility: HistoryVisibility,
112    /// The strategy used to distribute the room keys to participant. Default
113    /// will send to all devices.
114    #[serde(default)]
115    pub sharing_strategy: CollectStrategy,
116}
117
118impl Default for EncryptionSettings {
119    fn default() -> Self {
120        Self {
121            algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
122            #[cfg(feature = "experimental-encrypted-state-events")]
123            encrypt_state_events: false,
124            rotation_period: ROTATION_PERIOD,
125            rotation_period_msgs: ROTATION_MESSAGES,
126            history_visibility: HistoryVisibility::Shared,
127            sharing_strategy: CollectStrategy::default(),
128        }
129    }
130}
131
132impl EncryptionSettings {
133    /// Create new encryption settings using an `RoomEncryptionEventContent`, a
134    /// history visibility, and key sharing strategy.
135    pub fn new(
136        content: RoomEncryptionEventContent,
137        history_visibility: HistoryVisibility,
138        sharing_strategy: CollectStrategy,
139    ) -> Self {
140        let rotation_period: Duration =
141            content.rotation_period_ms.map_or(ROTATION_PERIOD, |r| Duration::from_millis(r.into()));
142        let rotation_period_msgs: u64 =
143            content.rotation_period_msgs.map_or(ROTATION_MESSAGES, Into::into);
144
145        Self {
146            algorithm: EventEncryptionAlgorithm::from(content.algorithm.as_str()),
147            #[cfg(feature = "experimental-encrypted-state-events")]
148            encrypt_state_events: false,
149            rotation_period,
150            rotation_period_msgs,
151            history_visibility,
152            sharing_strategy,
153        }
154    }
155
156    /// Create new encryption settings using a
157    /// `PossiblyRedactedRoomEncryptionEventContent`, a history visibility, and
158    /// key sharing strategy.
159    ///
160    /// Returns `None` if the `content` was redacted.
161    pub fn from_possibly_redacted(
162        content: PossiblyRedactedRoomEncryptionEventContent,
163        history_visibility: HistoryVisibility,
164        sharing_strategy: CollectStrategy,
165    ) -> Option<Self> {
166        let rotation_period: Duration =
167            content.rotation_period_ms.map_or(ROTATION_PERIOD, |r| Duration::from_millis(r.into()));
168        let rotation_period_msgs: u64 =
169            content.rotation_period_msgs.map_or(ROTATION_MESSAGES, Into::into);
170
171        Some(Self {
172            algorithm: EventEncryptionAlgorithm::from(content.algorithm?.as_str()),
173            #[cfg(feature = "experimental-encrypted-state-events")]
174            encrypt_state_events: false,
175            rotation_period,
176            rotation_period_msgs,
177            history_visibility,
178            sharing_strategy,
179        })
180    }
181}
182
183/// The result of encrypting a message with an outbound group session.
184///
185/// Contains the encrypted content, the algorithm used, and the session ID.
186#[derive(Debug)]
187pub struct OutboundGroupSessionEncryptionResult {
188    /// The encrypted content of the message.
189    pub content: Raw<RoomEncryptedEventContent>,
190    /// The algorithm used to encrypt the message.
191    pub algorithm: EventEncryptionAlgorithm,
192    /// The session ID used to encrypt the message.
193    pub session_id: Arc<str>,
194}
195
196/// Outbound group session.
197///
198/// Outbound group sessions are used to exchange room messages between a group
199/// of participants. Outbound group sessions are used to encrypt the room
200/// messages.
201#[derive(Clone)]
202pub struct OutboundGroupSession {
203    inner: Arc<RwLock<GroupSession>>,
204    device_id: OwnedDeviceId,
205    account_identity_keys: Arc<IdentityKeys>,
206    session_id: Arc<str>,
207    room_id: OwnedRoomId,
208    pub(crate) creation_time: SecondsSinceUnixEpoch,
209    message_count: Arc<AtomicU64>,
210    shared: Arc<AtomicBool>,
211    invalidated: Arc<AtomicBool>,
212    settings: Arc<EncryptionSettings>,
213    shared_with_set: Arc<StdRwLock<ShareInfoSet>>,
214    to_share_with_set: Arc<StdRwLock<ToShareMap>>,
215}
216
217/// A a map of userid/device it to a `ShareInfo`.
218///
219/// Holds the `ShareInfo` for all the user/device pairs that will receive the
220/// room key.
221pub type ShareInfoSet = BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, ShareInfo>>;
222
223type ToShareMap = BTreeMap<OwnedTransactionId, (Arc<ToDeviceRequest>, ShareInfoSet)>;
224
225/// Struct holding info about the share state of a outbound group session.
226#[derive(Clone, Debug, Serialize, Deserialize)]
227pub enum ShareInfo {
228    /// When the key has been shared
229    Shared(SharedWith),
230    /// When the session has been withheld
231    Withheld(WithheldCode),
232}
233
234impl ShareInfo {
235    /// Helper to create a SharedWith info
236    pub fn new_shared(
237        sender_key: Curve25519PublicKey,
238        message_index: u32,
239        olm_wedging_index: SequenceNumber,
240    ) -> Self {
241        ShareInfo::Shared(SharedWith { sender_key, message_index, olm_wedging_index })
242    }
243
244    /// Helper to create a Withheld info
245    pub fn new_withheld(code: WithheldCode) -> Self {
246        ShareInfo::Withheld(code)
247    }
248}
249
250#[derive(Clone, Debug, Serialize, Deserialize)]
251pub struct SharedWith {
252    /// The sender key of the device that was used to encrypt the room key.
253    pub sender_key: Curve25519PublicKey,
254    /// The message index that the device received.
255    pub message_index: u32,
256    /// The Olm wedging index of the device at the time the session was shared.
257    #[serde(default)]
258    pub olm_wedging_index: SequenceNumber,
259}
260
261/// A read-only view into the device sharing state of an
262/// [`OutboundGroupSession`].
263pub(crate) struct SharingView<'a> {
264    shared_with_set: RwLockReadGuard<'a, ShareInfoSet>,
265    to_share_with_set: RwLockReadGuard<'a, ToShareMap>,
266}
267
268impl SharingView<'_> {
269    /// Has the session been shared with the given user/device pair (or if not,
270    /// is there such a request pending).
271    pub(crate) fn get_share_state(&self, device: &DeviceData) -> ShareState {
272        self.iter_shares(Some(device.user_id()), Some(device.device_id()))
273            .map(|(_, _, info)| match info {
274                ShareInfo::Shared(info) => {
275                    if device.curve25519_key() == Some(info.sender_key) {
276                        ShareState::Shared {
277                            message_index: info.message_index,
278                            olm_wedging_index: info.olm_wedging_index,
279                        }
280                    } else {
281                        ShareState::SharedButChangedSenderKey
282                    }
283                }
284                ShareInfo::Withheld(_) => ShareState::NotShared,
285            })
286            // Return the most "definitive" ShareState found (in case there are
287            // multiple entries for the same device).
288            .max()
289            .unwrap_or(ShareState::NotShared)
290    }
291
292    /// Has the session been withheld for the given user/device pair (or if not,
293    /// is there such a request pending).
294    pub(crate) fn is_withheld_to(&self, device: &DeviceData, code: &WithheldCode) -> bool {
295        self.iter_shares(Some(device.user_id()), Some(device.device_id()))
296            .any(|(_, _, info)| matches!(info, ShareInfo::Withheld(c) if c == code))
297    }
298
299    /// Enumerate all sent or pending sharing requests for the given device (or
300    /// for all devices if not specified). This can yield the same device
301    /// multiple times.
302    pub(crate) fn iter_shares<'b, 'c>(
303        &self,
304        user_id: Option<&'b UserId>,
305        device_id: Option<&'c DeviceId>,
306    ) -> impl Iterator<Item = (&UserId, &DeviceId, &ShareInfo)> + use<'_, 'b, 'c> {
307        fn iter_share_info_set<'a, 'b, 'c>(
308            set: &'a ShareInfoSet,
309            user_ids: (Bound<&'b UserId>, Bound<&'b UserId>),
310            device_ids: (Bound<&'c DeviceId>, Bound<&'c DeviceId>),
311        ) -> impl Iterator<Item = (&'a UserId, &'a DeviceId, &'a ShareInfo)> + use<'a, 'b, 'c>
312        {
313            set.range::<UserId, _>(user_ids).flat_map(move |(uid, d)| {
314                d.range::<DeviceId, _>(device_ids)
315                    .map(|(id, info)| (uid.as_ref(), id.as_ref(), info))
316            })
317        }
318
319        let user_ids = user_id
320            .map(|u| (Bound::Included(u), Bound::Included(u)))
321            .unwrap_or((Bound::Unbounded, Bound::Unbounded));
322        let device_ids = device_id
323            .map(|d| (Bound::Included(d), Bound::Included(d)))
324            .unwrap_or((Bound::Unbounded, Bound::Unbounded));
325
326        let already_shared = iter_share_info_set(&self.shared_with_set, user_ids, device_ids);
327        let pending = self
328            .to_share_with_set
329            .values()
330            .flat_map(move |(_, set)| iter_share_info_set(set, user_ids, device_ids));
331        already_shared.chain(pending)
332    }
333
334    /// Enumerate all users that have received the session, or have pending
335    /// requests to receive it. This can yield the same user multiple times, so
336    /// you may want to `collect()` the result into a `BTreeSet`.
337    pub(crate) fn shared_with_users(&self) -> impl Iterator<Item = &UserId> {
338        self.iter_shares(None, None).filter_map(|(u, _, info)| match info {
339            ShareInfo::Shared(_) => Some(u),
340            ShareInfo::Withheld(_) => None,
341        })
342    }
343}
344
345impl OutboundGroupSession {
346    pub(super) fn session_config(
347        algorithm: &EventEncryptionAlgorithm,
348    ) -> Result<SessionConfig, SessionCreationError> {
349        match algorithm {
350            EventEncryptionAlgorithm::MegolmV1AesSha2 => Ok(SessionConfig::version_1()),
351            #[cfg(feature = "experimental-algorithms")]
352            EventEncryptionAlgorithm::MegolmV2AesSha2 => Ok(SessionConfig::version_2()),
353            _ => Err(SessionCreationError::Algorithm(algorithm.to_owned())),
354        }
355    }
356
357    /// Create a new outbound group session for the given room.
358    ///
359    /// Outbound group sessions are used to encrypt room messages.
360    ///
361    /// # Arguments
362    ///
363    /// - `device_id` - The id of the device that created this session.
364    /// - `identity_keys` - The identity keys of the account that created this
365    ///   session.
366    ///
367    /// - `room_id` - The id of the room that the session is used in.
368    /// - `settings` - Settings determining the algorithm and rotation period of
369    ///   the outbound group session.
370    pub fn new(
371        device_id: OwnedDeviceId,
372        identity_keys: Arc<IdentityKeys>,
373        room_id: &RoomId,
374        settings: EncryptionSettings,
375    ) -> Result<Self, SessionCreationError> {
376        let config = Self::session_config(&settings.algorithm)?;
377
378        let session = GroupSession::new(config);
379        let session_id = session.session_id();
380
381        Ok(OutboundGroupSession {
382            inner: RwLock::new(session).into(),
383            room_id: room_id.into(),
384            device_id,
385            account_identity_keys: identity_keys,
386            session_id: session_id.into(),
387            creation_time: SecondsSinceUnixEpoch::now(),
388            message_count: Arc::new(AtomicU64::new(0)),
389            shared: Arc::new(AtomicBool::new(false)),
390            invalidated: Arc::new(AtomicBool::new(false)),
391            settings: Arc::new(settings),
392            shared_with_set: Default::default(),
393            to_share_with_set: Default::default(),
394        })
395    }
396
397    /// Add a to-device request that is sending the session key (or room key)
398    /// belonging to this [`OutboundGroupSession`] to other members of the
399    /// group.
400    ///
401    /// The request will get persisted with the session which allows seamless
402    /// session reuse across application restarts.
403    ///
404    /// **Warning** this method is only exposed to be used in integration tests
405    /// of crypto-store implementations. **Do not use this outside of tests**.
406    pub fn add_request(
407        &self,
408        request_id: OwnedTransactionId,
409        request: Arc<ToDeviceRequest>,
410        share_infos: ShareInfoSet,
411    ) {
412        self.to_share_with_set.write().insert(request_id, (request, share_infos));
413    }
414
415    /// Create a new `m.room_key.withheld` event content with the given code for
416    /// this outbound group session.
417    pub fn withheld_code(&self, code: WithheldCode) -> RoomKeyWithheldContent {
418        RoomKeyWithheldContent::new(
419            self.settings().algorithm.to_owned(),
420            code,
421            self.room_id().to_owned(),
422            self.session_id().to_owned(),
423            self.sender_key().to_owned(),
424            self.device_id.clone(),
425        )
426    }
427
428    /// This should be called if an the user wishes to rotate this session.
429    pub fn invalidate_session(&self) {
430        self.invalidated.store(true, Ordering::Relaxed)
431    }
432
433    /// Get the encryption settings of this outbound session.
434    pub fn settings(&self) -> &EncryptionSettings {
435        &self.settings
436    }
437
438    /// Mark the request with the given request id as sent.
439    ///
440    /// This removes the request from the queue and marks the set of
441    /// users/devices that received the session.
442    pub fn mark_request_as_sent(
443        &self,
444        request_id: &TransactionId,
445    ) -> BTreeMap<OwnedUserId, BTreeSet<OwnedDeviceId>> {
446        let mut no_olm_devices = BTreeMap::new();
447
448        let removed = self.to_share_with_set.write().remove(request_id);
449        if let Some((to_device, request)) = removed {
450            let recipients: BTreeMap<&UserId, BTreeSet<&DeviceId>> = request
451                .iter()
452                .map(|(u, d)| (u.as_ref(), d.keys().map(|d| d.as_ref()).collect()))
453                .collect();
454
455            info!(
456                ?request_id,
457                ?recipients,
458                ?to_device.event_type,
459                "Marking to-device request carrying a room key or a withheld as sent"
460            );
461
462            for (user_id, info) in request {
463                let no_olms: BTreeSet<OwnedDeviceId> = info
464                    .iter()
465                    .filter(|(_, info)| matches!(info, ShareInfo::Withheld(WithheldCode::NoOlm)))
466                    .map(|(d, _)| d.to_owned())
467                    .collect();
468                no_olm_devices.insert(user_id.to_owned(), no_olms);
469
470                self.shared_with_set.write().entry(user_id).or_default().extend(info);
471            }
472
473            if self.to_share_with_set.read().is_empty() {
474                debug!(
475                    session_id = self.session_id(),
476                    room_id = ?self.room_id,
477                    "All m.room_key and withheld to-device requests were sent out, marking \
478                     session as shared.",
479                );
480
481                self.mark_as_shared();
482            }
483        } else {
484            let request_ids: Vec<String> =
485                self.to_share_with_set.read().keys().map(|k| k.to_string()).collect();
486
487            error!(
488                all_request_ids = ?request_ids,
489                ?request_id,
490                "Marking to-device request carrying a room key as sent but no \
491                 request found with the given id"
492            );
493        }
494
495        no_olm_devices
496    }
497
498    /// Encrypt the given plaintext using this session.
499    ///
500    /// Returns the encrypted ciphertext.
501    ///
502    /// # Arguments
503    ///
504    /// * `plaintext` - The plaintext that should be encrypted.
505    pub(crate) async fn encrypt_helper(&self, plaintext: String) -> MegolmMessage {
506        let mut session = self.inner.write().await;
507        self.message_count.fetch_add(1, Ordering::SeqCst);
508        session.encrypt(&plaintext)
509    }
510
511    /// Encrypt an arbitrary event for the given room.
512    ///
513    /// Beware that a room key needs to be shared before this method can be
514    /// called using the `share_room_key()` method.
515    ///
516    /// # Arguments
517    ///
518    /// - `payload` - The plaintext content of the event that should be
519    ///   serialized to JSON and encrypted.
520    ///
521    /// # Panics
522    ///
523    /// Panics if the content can't be serialized.
524    async fn encrypt_inner<T: Serialize>(
525        &self,
526        payload: &T,
527        relates_to: Option<serde_json::Value>,
528    ) -> OutboundGroupSessionEncryptionResult {
529        let ciphertext = self
530            .encrypt_helper(
531                serde_json::to_string(payload).expect("payload serialization never fails"),
532            )
533            .await;
534        let scheme: RoomEventEncryptionScheme = match self.settings.algorithm {
535            EventEncryptionAlgorithm::MegolmV1AesSha2 => MegolmV1AesSha2Content {
536                ciphertext,
537                sender_key: Some(self.account_identity_keys.curve25519),
538                session_id: self.session_id().to_owned(),
539                device_id: Some(self.device_id.clone()),
540            }
541            .into(),
542            #[cfg(feature = "experimental-algorithms")]
543            EventEncryptionAlgorithm::MegolmV2AesSha2 => {
544                MegolmV2AesSha2Content { ciphertext, session_id: self.session_id().to_owned() }
545                    .into()
546            }
547            _ => unreachable!(
548                "An outbound group session is always using one of the supported algorithms"
549            ),
550        };
551        let content = RoomEncryptedEventContent { scheme, relates_to, other: Default::default() };
552
553        OutboundGroupSessionEncryptionResult {
554            content: Raw::new(&content)
555                .expect("m.room.encrypted event content can always be serialized"),
556            algorithm: self.settings.algorithm.to_owned(),
557            session_id: self.session_id.clone(),
558        }
559    }
560
561    /// Encrypt a room message for the given room.
562    ///
563    /// Beware that a room key needs to be shared before this method can be
564    /// called using the `share_room_key()` method.
565    ///
566    /// # Arguments
567    ///
568    /// - `event_type` - The plaintext type of the event, the outer type of the
569    ///   event will become `m.room.encrypted`.
570    ///
571    /// - `content` - The plaintext content of the message that should be
572    ///   encrypted in raw JSON form.
573    ///
574    /// # Panics
575    ///
576    /// Panics if the content can't be serialized.
577    pub async fn encrypt(
578        &self,
579        event_type: &str,
580        content: &Raw<AnyMessageLikeEventContent>,
581    ) -> OutboundGroupSessionEncryptionResult {
582        #[derive(Serialize)]
583        struct Payload<'a> {
584            #[serde(rename = "type")]
585            event_type: &'a str,
586            content: &'a Raw<AnyMessageLikeEventContent>,
587            room_id: &'a RoomId,
588        }
589
590        let payload = Payload { event_type, content, room_id: &self.room_id };
591
592        let relates_to = content
593            .get_field::<serde_json::Value>("m.relates_to")
594            .expect("serde_json::Value deserialization with valid JSON input never fails");
595
596        self.encrypt_inner(&payload, relates_to).await
597    }
598
599    /// Encrypt a room state event for the given room.
600    ///
601    /// Beware that a room key needs to be shared before this method can be
602    /// called using the `share_room_key()` method.
603    ///
604    /// # Arguments
605    ///
606    /// - `event_type` - The plaintext type of the event, the outer type of the
607    ///   event will become `m.room.encrypted`.
608    ///
609    /// - `state_key` - The plaintext state key of the event, the outer state
610    ///   key will be derived from this and the event type.
611    ///
612    /// - `content` - The plaintext content of the message that should be
613    ///   encrypted in raw JSON form.
614    ///
615    /// # Panics
616    ///
617    /// Panics if the content can't be serialized.
618    #[cfg(feature = "experimental-encrypted-state-events")]
619    pub async fn encrypt_state(
620        &self,
621        event_type: &str,
622        state_key: &str,
623        content: &Raw<AnyStateEventContent>,
624    ) -> Raw<RoomEncryptedEventContent> {
625        #[derive(Serialize)]
626        struct Payload<'a> {
627            #[serde(rename = "type")]
628            event_type: &'a str,
629            state_key: &'a str,
630            content: &'a Raw<AnyStateEventContent>,
631            room_id: &'a RoomId,
632        }
633
634        let payload = Payload { event_type, state_key, content, room_id: &self.room_id };
635        self.encrypt_inner(&payload, None).await.content
636    }
637
638    fn elapsed(&self) -> bool {
639        let creation_time = Duration::from_secs(self.creation_time.get().into());
640        let now = Duration::from_secs(SecondsSinceUnixEpoch::now().get().into());
641        now.checked_sub(creation_time)
642            .map(|elapsed| elapsed >= self.safe_rotation_period())
643            .unwrap_or(true)
644    }
645
646    /// Returns the rotation_period_ms that was set for this session, clamped to
647    /// be no less than one hour.
648    ///
649    /// This is to prevent a malicious or careless user causing sessions to be
650    /// rotated very frequently.
651    ///
652    /// The feature flag `_disable-minimum-rotation-period-ms` can be used to
653    /// prevent this behaviour (which can be useful for tests).
654    fn safe_rotation_period(&self) -> Duration {
655        if cfg!(feature = "_disable-minimum-rotation-period-ms") {
656            self.settings.rotation_period
657        } else {
658            max(self.settings.rotation_period, ONE_HOUR)
659        }
660    }
661
662    /// Check if the session has expired and if it should be rotated.
663    ///
664    /// A session will expire after some time or if enough messages have been
665    /// encrypted using it.
666    pub fn expired(&self) -> bool {
667        let count = self.message_count.load(Ordering::SeqCst);
668        // We clamp the rotation period for message counts to be between 1 and
669        // 10000. The Megolm session should be usable for at least 1 message,
670        // and at most 10000 messages. Realistically Megolm uses u32 for it's
671        // internal counter and one could use the Megolm session for up to
672        // u32::MAX messages, but we're staying on the safe side of things.
673        let rotation_period_msgs = self.settings.rotation_period_msgs.clamp(1, 10_000);
674
675        count >= rotation_period_msgs || self.elapsed()
676    }
677
678    /// Has the session been invalidated.
679    pub fn invalidated(&self) -> bool {
680        self.invalidated.load(Ordering::Relaxed)
681    }
682
683    /// Mark the session as shared.
684    ///
685    /// Messages shouldn't be encrypted with the session before it has been
686    /// shared.
687    pub fn mark_as_shared(&self) {
688        self.shared.store(true, Ordering::Relaxed);
689    }
690
691    /// Check if the session has been marked as shared.
692    pub fn shared(&self) -> bool {
693        self.shared.load(Ordering::Relaxed)
694    }
695
696    /// Get the session key of this session.
697    ///
698    /// A session key can be used to to create an `InboundGroupSession`.
699    pub async fn session_key(&self) -> SessionKey {
700        let session = self.inner.read().await;
701        session.session_key()
702    }
703
704    /// Gets the Sender Key
705    pub fn sender_key(&self) -> Curve25519PublicKey {
706        self.account_identity_keys.as_ref().curve25519.to_owned()
707    }
708
709    /// Get the room id of the room this session belongs to.
710    pub fn room_id(&self) -> &RoomId {
711        &self.room_id
712    }
713
714    /// Returns the unique identifier for this session.
715    pub fn session_id(&self) -> &str {
716        &self.session_id
717    }
718
719    /// Get the current message index for this session.
720    ///
721    /// Each message is sent with an increasing index. This returns the message
722    /// index that will be used for the next encrypted message.
723    pub async fn message_index(&self) -> u32 {
724        let session = self.inner.read().await;
725        session.message_index()
726    }
727
728    pub(crate) async fn as_content(&self) -> RoomKeyContent {
729        let session_key = self.session_key().await;
730        let shared_history =
731            shared_history_from_history_visibility(&self.settings.history_visibility);
732
733        RoomKeyContent::MegolmV1AesSha2(
734            MegolmV1AesSha2RoomKeyContent::new(
735                self.room_id().to_owned(),
736                self.session_id().to_owned(),
737                session_key,
738                shared_history,
739            )
740            .into(),
741        )
742    }
743
744    /// Create a read-only view into the device sharing state of this session.
745    /// This view includes pending requests, so it is not guaranteed that the
746    /// represented state has been fully propagated yet.
747    pub(crate) fn sharing_view(&self) -> SharingView<'_> {
748        SharingView {
749            shared_with_set: self.shared_with_set.read(),
750            to_share_with_set: self.to_share_with_set.read(),
751        }
752    }
753
754    /// Mark the session as shared with the given user/device pair, starting
755    /// from some message index.
756    #[cfg(test)]
757    pub fn mark_shared_with_from_index(
758        &self,
759        user_id: &UserId,
760        device_id: &DeviceId,
761        sender_key: Curve25519PublicKey,
762        index: u32,
763    ) {
764        self.shared_with_set.write().entry(user_id.to_owned()).or_default().insert(
765            device_id.to_owned(),
766            ShareInfo::new_shared(sender_key, index, Default::default()),
767        );
768    }
769
770    /// Mark the session as shared with the given user/device pair, starting
771    /// from the current index.
772    #[cfg(test)]
773    pub async fn mark_shared_with(
774        &self,
775        user_id: &UserId,
776        device_id: &DeviceId,
777        sender_key: Curve25519PublicKey,
778    ) {
779        let share_info =
780            ShareInfo::new_shared(sender_key, self.message_index().await, Default::default());
781        self.shared_with_set
782            .write()
783            .entry(user_id.to_owned())
784            .or_default()
785            .insert(device_id.to_owned(), share_info);
786    }
787
788    /// Get the list of requests that need to be sent out for this session to be
789    /// marked as shared.
790    pub(crate) fn pending_requests(&self) -> Vec<Arc<ToDeviceRequest>> {
791        self.to_share_with_set.read().values().map(|(req, _)| req.clone()).collect()
792    }
793
794    /// Get the list of request ids this session is waiting for to be sent out.
795    pub(crate) fn pending_request_ids(&self) -> Vec<OwnedTransactionId> {
796        self.to_share_with_set.read().keys().cloned().collect()
797    }
798
799    /// Restore a Session from a previously pickled string.
800    ///
801    /// Returns the restored group session or a `OlmGroupSessionError` if there
802    /// was an error.
803    ///
804    /// # Arguments
805    ///
806    /// - `device_id` - The device ID of the device that created this session.
807    ///   Put differently, our own device ID.
808    ///
809    /// - `identity_keys` - The identity keys of the device that created this
810    ///   session, our own identity keys.
811    ///
812    /// - `pickle` - The pickled version of the `OutboundGroupSession`.
813    /// - `pickle_mode` - The mode that was used to pickle the session, either
814    ///   an unencrypted mode or an encrypted using passphrase.
815    pub fn from_pickle(
816        device_id: OwnedDeviceId,
817        identity_keys: Arc<IdentityKeys>,
818        pickle: PickledOutboundGroupSession,
819    ) -> Result<Self, PickleError> {
820        let inner: GroupSession = pickle.pickle.into();
821        let session_id = inner.session_id();
822
823        Ok(Self {
824            inner: Arc::new(RwLock::new(inner)),
825            device_id,
826            account_identity_keys: identity_keys,
827            session_id: session_id.into(),
828            room_id: pickle.room_id,
829            creation_time: pickle.creation_time,
830            message_count: AtomicU64::from(pickle.message_count).into(),
831            shared: AtomicBool::from(pickle.shared).into(),
832            invalidated: AtomicBool::from(pickle.invalidated).into(),
833            settings: pickle.settings,
834            shared_with_set: Arc::new(StdRwLock::new(pickle.shared_with_set)),
835            to_share_with_set: Arc::new(StdRwLock::new(pickle.requests)),
836        })
837    }
838
839    /// Store the group session as a base64 encoded string and associated data
840    /// belonging to the session.
841    ///
842    /// # Arguments
843    ///
844    /// - `pickle_mode` - The mode that should be used to pickle the group
845    ///   session, either an unencrypted mode or an encrypted using passphrase.
846    pub async fn pickle(&self) -> PickledOutboundGroupSession {
847        let pickle = self.inner.read().await.pickle();
848
849        PickledOutboundGroupSession {
850            pickle,
851            room_id: self.room_id.clone(),
852            settings: self.settings.clone(),
853            creation_time: self.creation_time,
854            message_count: self.message_count.load(Ordering::SeqCst),
855            shared: self.shared(),
856            invalidated: self.invalidated(),
857            shared_with_set: self.shared_with_set.read().clone(),
858            requests: self.to_share_with_set.read().clone(),
859        }
860    }
861}
862
863#[cfg(not(tarpaulin_include))]
864impl fmt::Debug for OutboundGroupSession {
865    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
866        f.debug_struct("OutboundGroupSession")
867            .field("session_id", &self.session_id)
868            .field("room_id", &self.room_id)
869            .field("creation_time", &self.creation_time)
870            .field("message_count", &self.message_count)
871            .finish()
872    }
873}
874
875/// A pickled version of an `InboundGroupSession`.
876///
877/// Holds all the information that needs to be stored in a database to restore
878/// an InboundGroupSession.
879#[derive(Deserialize, Serialize)]
880#[allow(missing_debug_implementations)]
881pub struct PickledOutboundGroupSession {
882    /// The pickle string holding the OutboundGroupSession.
883    pub pickle: GroupSessionPickle,
884    /// The settings this session adheres to.
885    pub settings: Arc<EncryptionSettings>,
886    /// The room id this session is used for.
887    pub room_id: OwnedRoomId,
888    /// The timestamp when this session was created.
889    pub creation_time: SecondsSinceUnixEpoch,
890    /// The number of messages this session has already encrypted.
891    pub message_count: u64,
892    /// Is the session shared.
893    pub shared: bool,
894    /// Has the session been invalidated.
895    pub invalidated: bool,
896    /// The set of users the session has been already shared with.
897    pub shared_with_set: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, ShareInfo>>,
898    /// Requests that need to be sent out to share the session.
899    pub requests: BTreeMap<OwnedTransactionId, (Arc<ToDeviceRequest>, ShareInfoSet)>,
900}
901
902#[cfg(test)]
903mod tests {
904    use std::time::Duration;
905
906    use ruma::{
907        EventEncryptionAlgorithm,
908        events::room::{
909            encryption::RoomEncryptionEventContent, history_visibility::HistoryVisibility,
910        },
911        uint,
912    };
913
914    use super::{EncryptionSettings, ROTATION_MESSAGES, ROTATION_PERIOD, ShareState};
915    use crate::CollectStrategy;
916
917    #[test]
918    fn test_encryption_settings_conversion() {
919        let mut content =
920            RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
921        let settings = EncryptionSettings::new(
922            content.clone(),
923            HistoryVisibility::Joined,
924            CollectStrategy::AllDevices,
925        );
926
927        assert_eq!(settings.rotation_period, ROTATION_PERIOD);
928        assert_eq!(settings.rotation_period_msgs, ROTATION_MESSAGES);
929
930        content.rotation_period_ms = Some(uint!(3600));
931        content.rotation_period_msgs = Some(uint!(500));
932
933        let settings = EncryptionSettings::new(
934            content,
935            HistoryVisibility::Shared,
936            CollectStrategy::AllDevices,
937        );
938
939        assert_eq!(settings.rotation_period, Duration::from_millis(3600));
940        assert_eq!(settings.rotation_period_msgs, 500);
941    }
942
943    /// Ensure that the `ShareState` PartialOrd instance orders according to
944    /// specificity of the value.
945    #[test]
946    fn test_share_state_ordering() {
947        let values = [
948            ShareState::NotShared,
949            ShareState::SharedButChangedSenderKey,
950            ShareState::Shared { message_index: 1, olm_wedging_index: Default::default() },
951        ];
952        // Make sure our test case of possible variants is exhaustive
953        match values[0] {
954            ShareState::NotShared
955            | ShareState::SharedButChangedSenderKey
956            | ShareState::Shared { .. } => {}
957        }
958        assert!(values.is_sorted());
959    }
960
961    #[cfg(any(target_os = "linux", target_os = "macos", target_family = "wasm"))]
962    mod expiration {
963        use std::{sync::atomic::Ordering, time::Duration};
964
965        use matrix_sdk_test::async_test;
966        use ruma::{
967            SecondsSinceUnixEpoch, device_id, events::room::message::RoomMessageEventContent,
968            room_id, serde::Raw, uint, user_id,
969        };
970
971        use crate::{
972            Account, EncryptionSettings, MegolmError,
973            olm::{OutboundGroupSession, SenderData},
974        };
975
976        const TWO_HOURS: Duration = Duration::from_secs(60 * 60 * 2);
977
978        #[async_test]
979        async fn test_session_is_not_expired_if_no_messages_sent_and_no_time_passed() {
980            // Given a session that expires after one message
981            let session = create_session(EncryptionSettings {
982                rotation_period_msgs: 1,
983                ..Default::default()
984            })
985            .await;
986
987            // When we send no messages at all
988
989            // Then it is not expired
990            assert!(!session.expired());
991        }
992
993        #[async_test]
994        async fn test_session_is_expired_if_we_rotate_every_message_and_one_was_sent()
995        -> Result<(), MegolmError> {
996            // Given a session that expires after one message
997            let session = create_session(EncryptionSettings {
998                rotation_period_msgs: 1,
999                ..Default::default()
1000            })
1001            .await;
1002
1003            // When we send a message
1004            let _ = session
1005                .encrypt(
1006                    "m.room.message",
1007                    &Raw::new(&RoomMessageEventContent::text_plain("Test message"))?.cast(),
1008                )
1009                .await;
1010
1011            // Then the session is expired
1012            assert!(session.expired());
1013
1014            Ok(())
1015        }
1016
1017        #[async_test]
1018        async fn test_session_with_rotation_period_is_not_expired_after_no_time() {
1019            // Given a session with a 2h expiration
1020            let session = create_session(EncryptionSettings {
1021                rotation_period: TWO_HOURS,
1022                ..Default::default()
1023            })
1024            .await;
1025
1026            // When we don't allow any time to pass
1027
1028            // Then it is not expired
1029            assert!(!session.expired());
1030        }
1031
1032        #[async_test]
1033        async fn test_session_is_expired_after_rotation_period() {
1034            // Given a session with a 2h expiration
1035            let mut session = create_session(EncryptionSettings {
1036                rotation_period: TWO_HOURS,
1037                ..Default::default()
1038            })
1039            .await;
1040
1041            // When 3 hours have passed
1042            let now = SecondsSinceUnixEpoch::now();
1043            session.creation_time = SecondsSinceUnixEpoch(now.get() - uint!(10800));
1044
1045            // Then the session is expired
1046            assert!(session.expired());
1047        }
1048
1049        #[async_test]
1050        #[cfg(not(feature = "_disable-minimum-rotation-period-ms"))]
1051        async fn test_session_does_not_expire_under_one_hour_even_if_we_ask_for_shorter() {
1052            // Given a session with a 100ms expiration
1053            let mut session = create_session(EncryptionSettings {
1054                rotation_period: Duration::from_millis(100),
1055                ..Default::default()
1056            })
1057            .await;
1058
1059            // When less than an hour has passed
1060            let now = SecondsSinceUnixEpoch::now();
1061            session.creation_time = SecondsSinceUnixEpoch(now.get() - uint!(1800));
1062
1063            // Then the session is not expired: we enforce a minimum of 1 hour
1064            assert!(!session.expired());
1065
1066            // But when more than an hour has passed
1067            session.creation_time = SecondsSinceUnixEpoch(now.get() - uint!(3601));
1068
1069            // Then the session is expired
1070            assert!(session.expired());
1071        }
1072
1073        #[async_test]
1074        #[cfg(feature = "_disable-minimum-rotation-period-ms")]
1075        async fn test_with_disable_minrotperiod_feature_sessions_can_expire_quickly() {
1076            // Given a session with a 100ms expiration
1077            let mut session = create_session(EncryptionSettings {
1078                rotation_period: Duration::from_millis(100),
1079                ..Default::default()
1080            })
1081            .await;
1082
1083            // When less than an hour has passed
1084            let now = SecondsSinceUnixEpoch::now();
1085            session.creation_time = SecondsSinceUnixEpoch(now.get() - uint!(1800));
1086
1087            // Then the session is expired: the feature flag has prevented us
1088            // enforcing a minimum
1089            assert!(session.expired());
1090        }
1091
1092        #[async_test]
1093        async fn test_session_with_zero_msgs_rotation_is_not_expired_initially() {
1094            // Given a session that is supposed to expire after zero messages
1095            let session = create_session(EncryptionSettings {
1096                rotation_period_msgs: 0,
1097                ..Default::default()
1098            })
1099            .await;
1100
1101            // When we send no messages
1102
1103            // Then the session is not expired: we are protected against this
1104            // nonsensical setup
1105            assert!(!session.expired());
1106        }
1107
1108        #[async_test]
1109        async fn test_session_with_zero_msgs_rotation_expires_after_one_message()
1110        -> Result<(), MegolmError> {
1111            // Given a session that is supposed to expire after zero messages
1112            let session = create_session(EncryptionSettings {
1113                rotation_period_msgs: 0,
1114                ..Default::default()
1115            })
1116            .await;
1117
1118            // When we send a message
1119            let _ = session
1120                .encrypt(
1121                    "m.room.message",
1122                    &Raw::new(&RoomMessageEventContent::text_plain("Test message"))?.cast(),
1123                )
1124                .await;
1125
1126            // Then the session is expired: we treated rotation_period_msgs=0 as
1127            // if it were =1
1128            assert!(session.expired());
1129
1130            Ok(())
1131        }
1132
1133        #[async_test]
1134        async fn test_session_expires_after_10k_messages_even_if_we_ask_for_more() {
1135            // Given we asked to expire after 100K messages
1136            let session = create_session(EncryptionSettings {
1137                rotation_period_msgs: 100_000,
1138                ..Default::default()
1139            })
1140            .await;
1141
1142            // Sanity: it does not expire after <10K messages
1143            assert!(!session.expired());
1144            session.message_count.store(1000, Ordering::SeqCst);
1145            assert!(!session.expired());
1146            session.message_count.store(9999, Ordering::SeqCst);
1147            assert!(!session.expired());
1148
1149            // When we have sent >= 10K messages
1150            session.message_count.store(10_000, Ordering::SeqCst);
1151
1152            // Then it is considered expired: we enforce a maximum of 10K
1153            // messages before rotation.
1154            assert!(session.expired());
1155        }
1156
1157        async fn create_session(settings: EncryptionSettings) -> OutboundGroupSession {
1158            let account =
1159                Account::with_device_id(user_id!("@alice:example.org"), device_id!("DEVICEID"))
1160                    .static_data;
1161            let (session, _) = account
1162                .create_group_session_pair(
1163                    room_id!("!test_room:example.org"),
1164                    settings,
1165                    SenderData::unknown(),
1166                )
1167                .await
1168                .unwrap();
1169            session
1170        }
1171    }
1172}