matrix_sdk_crypto/olm/group_sessions/
inbound.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::Ordering,
17    fmt,
18    ops::Deref,
19    sync::{
20        Arc,
21        atomic::{AtomicBool, Ordering::SeqCst},
22    },
23};
24
25use ruma::{
26    DeviceKeyAlgorithm, OwnedRoomId, RoomId, events::room::history_visibility::HistoryVisibility,
27    serde::JsonObject,
28};
29use serde::{Deserialize, Serialize};
30use tokio::sync::Mutex;
31use vodozemac::{
32    Curve25519PublicKey, Ed25519PublicKey, PickleError,
33    megolm::{
34        DecryptedMessage, DecryptionError, InboundGroupSession as InnerSession,
35        InboundGroupSessionPickle, MegolmMessage, SessionConfig, SessionOrdering,
36    },
37};
38
39use super::{
40    BackedUpRoomKey, ExportedRoomKey, OutboundGroupSession, SenderData, SenderDataType,
41    SessionCreationError, SessionKey,
42};
43#[cfg(doc)]
44use crate::types::events::room_key::RoomKeyContent;
45use crate::{
46    error::{EventError, MegolmResult},
47    types::{
48        EventEncryptionAlgorithm, SigningKeys, deserialize_curve_key,
49        events::{
50            forwarded_room_key::{
51                ForwardedMegolmV1AesSha2Content, ForwardedMegolmV2AesSha2Content,
52                ForwardedRoomKeyContent,
53            },
54            olm_v1::DecryptedForwardedRoomKeyEvent,
55            room::encrypted::{EncryptedEvent, RoomEventEncryptionScheme},
56            room_key,
57        },
58        room_history::HistoricRoomKey,
59        serialize_curve_key,
60    },
61};
62// TODO: add creation times to the inbound group sessions so we can export
63// sessions that were created between some time period, this should only be set
64// for non-imported sessions.
65
66/// Information about the creator of an inbound group session.
67#[derive(Clone)]
68pub(crate) struct SessionCreatorInfo {
69    /// The Curve25519 identity key of the session creator.
70    ///
71    /// If the session was received directly from its creator device through an
72    /// `m.room_key` event (and therefore, session sender == session creator),
73    /// this key equals the Curve25519 device identity key of that device. Since
74    /// this key is one of three keys used to establish the Olm session through
75    /// which encrypted to-device messages (including `m.room_key`) are sent,
76    /// this constitutes a proof that this inbound group session is owned by
77    /// that particular Curve25519 key.
78    ///
79    /// However, if the session was simply forwarded to us in an
80    /// `m.forwarded_room_key` event (in which case sender != creator), this key
81    /// is just a *claim* made by the session sender of what the actual creator
82    /// device is.
83    pub curve25519_key: Curve25519PublicKey,
84
85    /// A mapping of DeviceKeyAlgorithm to the public signing keys of the
86    /// [`Device`] that sent us the session.
87    ///
88    /// If the session was received directly from the creator via an
89    /// `m.room_key` event, this map is taken from the plaintext value of
90    /// the decrypted Olm event, and is a copy of the
91    /// [`DecryptedOlmV1Event::keys`] field as defined in the [spec].
92    ///
93    /// If the session was forwarded to us using an `m.forwarded_room_key`, this
94    /// map is a copy of the claimed Ed25519 key from the content of the
95    /// event.
96    ///
97    /// [spec]: https://spec.matrix.org/unstable/client-server-api/#molmv1curve25519-aes-sha2
98    pub signing_keys: Arc<SigningKeys<DeviceKeyAlgorithm>>,
99}
100
101/// A structure representing an inbound group session.
102///
103/// Inbound group sessions, also known as "room keys", are used to facilitate
104/// the exchange of room messages among a group of participants. The inbound
105/// variant of the group session is used to decrypt the room messages.
106///
107/// This struct wraps the [vodozemac] type of the same name, and adds additional
108/// Matrix-specific data to it. Additionally, the wrapper ensures thread-safe
109/// access of the vodozemac type.
110///
111/// [vodozemac]: https://matrix-org.github.io/vodozemac/vodozemac/index.html
112///
113/// ## Structures representing serialised versions of an `InboundGroupSession`
114///
115/// This crate contains a number of structures which are used for exporting or
116/// sharing `InboundGroupSession` between users or devices, in different
117/// circumstances. The following is an attempt to catalogue them.
118///
119/// 1. First, we have the contents of an `m.room_key` to-device message (i.e., a
120///    [`RoomKeyContent`]. `RoomKeyContent` is unusual in that it can be created
121///    only by the original creator of the session (i.e., someone in possession
122///    of the corresponding [`OutboundGroupSession`]), since the embedded
123///    `session_key` is self-signed.
124///
125///    `RoomKeyContent` does **not** include any information about the creator
126///    of the session (such as the creator's public device keys), since it is
127///    assumed that the original creator of the session is the same as the
128///    device sending the to-device message; it is therefore implied by the Olm
129///    channel used to send the message.
130///
131///    All the other structs in this list include a `sender_key` field which
132///    contains the Curve25519 key belonging to the device which created the
133///    Megolm session (at least, according to the creator of the struct); they
134///    also include the Ed25519 key, though the exact serialisation mechanism
135///    varies.
136///
137/// 2. Next, we have the contents of an `m.forwarded_room_key` message (i.e. a
138///    [`ForwardedRoomKeyContent`]). This modifies `RoomKeyContent` by (a) using
139///    a `session_key` which is not self-signed, (b) adding a `sender_key` field
140///    as mentioned above, (c) adding a `sender_claimed_ed25519_key` field
141///    containing the original sender's Ed25519 key; (d) adding a
142///    `forwarding_curve25519_key_chain` field, which is intended to be used
143///    when the key is re-forwarded, but in practice is of little use.
144///
145/// 3. [`ExportedRoomKey`] is very similar to `ForwardedRoomKeyContent`. The
146///    only difference is that the original sender's Ed25519 key is embedded in
147///    a `sender_claimed_keys` map rather than a top-level
148///    `sender_claimed_ed25519_key` field.
149///
150/// 4. [`BackedUpRoomKey`] is essentially the same as `ExportedRoomKey`, but
151///    lacks explicit `room_id` and `session_id` (since those are implied by
152///    other parts of the key backup structure).
153///
154/// 5. [`HistoricRoomKey`] is also similar to `ExportedRoomKey`, but omits
155///    `forwarding_curve25519_key_chain` (since it has not been useful in
156///    practice) and `shared_history` (because any key being shared via that
157///    mechanism is inherently suitable for sharing with other users).
158///
159/// | Type     | Self-signed room key | `room_id`, `session_id` | `sender_key` | Sender's Ed25519 key | `forwarding _curve25519 _key _chain` | `shared _history` |
160/// |----------|----------------------|-------------------------|--------------|----------------------|------------------------------------|------------------|
161/// | [`RoomKeyContent`]          | ✅ | ✅                       | ❌            | ❌                    | ❌                                  | ✅                |
162/// | [`ForwardedRoomKeyContent`] | ❌ | ✅                       | ✅            | `sender_claimed_ed25519_key` | ✅                          | ✅                |
163/// | [`ExportedRoomKey`]         | ❌ | ✅                       | ✅            | `sender_claimed_keys` | ✅                                 | ✅                |
164/// | [`BackedUpRoomKey`]         | ❌ | ❌                       | ✅            | `sender_claimed_keys` | ✅                                 | ✅                |
165/// | [`HistoricRoomKey`]         | ❌ | ✅                       | ✅            | `sender_claimed_keys` | ❌                                 | ❌                |
166#[derive(Clone)]
167pub struct InboundGroupSession {
168    inner: Arc<Mutex<InnerSession>>,
169
170    /// A copy of [`InnerSession::session_id`] to avoid having to acquire a lock
171    /// to get to the session ID.
172    session_id: Arc<str>,
173
174    /// A copy of [`InnerSession::first_known_index`] to avoid having to acquire
175    /// a lock to get to the first known index.
176    first_known_index: u32,
177
178    /// Information about the creator of the [`InboundGroupSession`] ("room
179    /// key"). The trustworthiness of the information in this field depends
180    /// on how the session was received.
181    pub(crate) creator_info: SessionCreatorInfo,
182
183    /// Information about the sender of this session and how much we trust that
184    /// information. Holds the information we have about the device that created
185    /// the session, or, if we can use that device information to find the
186    /// sender's cross-signing identity, holds the user ID and cross-signing
187    /// key.
188    pub sender_data: SenderData,
189
190    /// The Room this GroupSession belongs to
191    pub room_id: OwnedRoomId,
192
193    /// A flag recording whether the `InboundGroupSession` was received directly
194    /// as a `m.room_key` event or indirectly via a forward or file import.
195    ///
196    /// If the session is considered to be imported, the information contained
197    /// in the `InboundGroupSession::creator_info` field is not proven to be
198    /// correct.
199    imported: bool,
200
201    /// The messaging algorithm of this [`InboundGroupSession`] as defined by
202    /// the [spec]. Will be one of the `m.megolm.*` algorithms.
203    ///
204    /// [spec]: https://spec.matrix.org/unstable/client-server-api/#messaging-algorithms
205    algorithm: Arc<EventEncryptionAlgorithm>,
206
207    /// The history visibility of the room at the time when the room key was
208    /// created.
209    history_visibility: Arc<Option<HistoryVisibility>>,
210
211    /// Was this room key backed up to the server.
212    backed_up: Arc<AtomicBool>,
213
214    /// Whether this [`InboundGroupSession`] can be shared with users who are
215    /// invited to the room in the future, allowing access to history, as
216    /// defined in [MSC3061].
217    ///
218    /// [MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061
219    shared_history: bool,
220}
221
222impl InboundGroupSession {
223    /// Create a new inbound group session for the given room.
224    ///
225    /// These sessions are used to decrypt room messages.
226    ///
227    /// # Arguments
228    ///
229    /// * `sender_key` - The public Curve25519 key of the account that sent us
230    ///   the session.
231    ///
232    /// * `signing_key` - The public Ed25519 key of the account that sent us the
233    ///   session.
234    ///
235    /// * `room_id` - The id of the room that the session is used in.
236    ///
237    /// * `session_key` - The private session key that is used to decrypt
238    ///   messages.
239    ///
240    /// * `sender_data` - Information about the sender of the to-device message
241    ///   that established this session.
242    ///
243    /// * `encryption_algorithm` - The [`EventEncryptionAlgorithm`] that should
244    ///   be used when messages are being decrypted. The method will return an
245    ///   [`SessionCreationError::Algorithm`] error if an algorithm we do not
246    ///   support is given,
247    ///
248    /// * `history_visibility` - The history visibility of the room at the time
249    ///   the matching [`OutboundGroupSession`] was created. This is only set if
250    ///   we are the crator of this  [`InboundGroupSession`]. Sessinons that are
251    ///   received from other devices use the  `shared_history` flag instead.
252    ///
253    /// * `shared_history` - Whether this [`InboundGroupSession`] can be shared
254    ///   with users who are invited to the room in the future, allowing access
255    ///   to history, as defined in [MSC3061]. This flag is a surjection of the
256    ///   history visibility of the room.
257    ///
258    /// [MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061
259    #[allow(clippy::too_many_arguments)]
260    pub fn new(
261        sender_key: Curve25519PublicKey,
262        signing_key: Ed25519PublicKey,
263        room_id: &RoomId,
264        session_key: &SessionKey,
265        sender_data: SenderData,
266        encryption_algorithm: EventEncryptionAlgorithm,
267        history_visibility: Option<HistoryVisibility>,
268        shared_history: bool,
269    ) -> Result<Self, SessionCreationError> {
270        let config = OutboundGroupSession::session_config(&encryption_algorithm)?;
271
272        let session = InnerSession::new(session_key, config);
273        let session_id = session.session_id();
274        let first_known_index = session.first_known_index();
275
276        let mut keys = SigningKeys::new();
277        keys.insert(DeviceKeyAlgorithm::Ed25519, signing_key.into());
278
279        Ok(InboundGroupSession {
280            inner: Arc::new(Mutex::new(session)),
281            history_visibility: history_visibility.into(),
282            session_id: session_id.into(),
283            first_known_index,
284            creator_info: SessionCreatorInfo {
285                curve25519_key: sender_key,
286                signing_keys: keys.into(),
287            },
288            sender_data,
289            room_id: room_id.into(),
290            imported: false,
291            algorithm: encryption_algorithm.into(),
292            backed_up: AtomicBool::new(false).into(),
293            shared_history,
294        })
295    }
296
297    /// Create a new [`InboundGroupSession`] from a `m.room_key` event with an
298    /// `m.megolm.v1.aes-sha2` content.
299    ///
300    /// The `m.room_key` event **must** have been encrypted using the
301    /// `m.olm.v1.curve25519-aes-sha2` algorithm and the `sender_key` **must**
302    /// be the long-term [`Curve25519PublicKey`] that was used to establish
303    /// the 1-to-1 Olm session.
304    ///
305    /// The `signing_key` **must** be the [`Ed25519PublicKey`] contained in the
306    /// `keys` field of the [decrypted payload].
307    ///
308    /// [decrypted payload]: https://spec.matrix.org/unstable/client-server-api/#molmv1curve25519-aes-sha2
309    pub fn from_room_key_content(
310        sender_key: Curve25519PublicKey,
311        signing_key: Ed25519PublicKey,
312        content: &room_key::MegolmV1AesSha2Content,
313    ) -> Result<Self, SessionCreationError> {
314        let room_key::MegolmV1AesSha2Content {
315            room_id,
316            session_id: _,
317            session_key,
318            shared_history,
319            ..
320        } = content;
321
322        Self::new(
323            sender_key,
324            signing_key,
325            room_id,
326            session_key,
327            SenderData::unknown(),
328            EventEncryptionAlgorithm::MegolmV1AesSha2,
329            None,
330            *shared_history,
331        )
332    }
333
334    /// Create a new [`InboundGroupSession`] from an exported version of the
335    /// group session.
336    ///
337    /// Most notably this can be called with an [`ExportedRoomKey`] from a
338    /// previous [`InboundGroupSession::export()`] call.
339    pub fn from_export(exported_session: &ExportedRoomKey) -> Result<Self, SessionCreationError> {
340        Self::try_from(exported_session)
341    }
342
343    /// Create a new [`InboundGroupSession`] which is a copy of this one, except
344    /// that its Megolm ratchet is replaced with a copy of that from another
345    /// [`InboundGroupSession`].
346    ///
347    /// This can be useful, for example, when we receive a new copy of the room
348    /// key, but at an earlier ratchet index.
349    ///
350    /// # Panics
351    ///
352    /// If the two sessions are for different room IDs, or have different
353    /// session IDs, this function will panic. It is up to the caller to ensure
354    /// that it only attempts to merge related sessions.
355    pub(crate) fn with_ratchet(mut self, other: &InboundGroupSession) -> Self {
356        if self.session_id != other.session_id {
357            panic!(
358                "Attempt to merge Megolm sessions with different session IDs: {} vs {}",
359                self.session_id, other.session_id
360            );
361        }
362        if self.room_id != other.room_id {
363            panic!(
364                "Attempt to merge Megolm sessions with different room IDs: {} vs {}",
365                self.room_id, other.room_id,
366            );
367        }
368        self.inner = other.inner.clone();
369        self.first_known_index = other.first_known_index;
370        self
371    }
372
373    /// Convert the [`InboundGroupSession`] into a
374    /// [`PickledInboundGroupSession`] which can be serialized.
375    pub async fn pickle(&self) -> PickledInboundGroupSession {
376        let pickle = self.inner.lock().await.pickle();
377
378        PickledInboundGroupSession {
379            pickle,
380            sender_key: self.creator_info.curve25519_key,
381            signing_key: (*self.creator_info.signing_keys).clone(),
382            sender_data: self.sender_data.clone(),
383            room_id: self.room_id().to_owned(),
384            imported: self.imported,
385            backed_up: self.backed_up(),
386            history_visibility: self.history_visibility.as_ref().clone(),
387            algorithm: (*self.algorithm).to_owned(),
388            shared_history: self.shared_history,
389        }
390    }
391
392    /// Export this session at the first known message index.
393    ///
394    /// If only a limited part of this session should be exported use
395    /// [`InboundGroupSession::export_at_index()`].
396    pub async fn export(&self) -> ExportedRoomKey {
397        self.export_at_index(self.first_known_index()).await
398    }
399
400    /// Get the sender key that this session was received from.
401    pub fn sender_key(&self) -> Curve25519PublicKey {
402        self.creator_info.curve25519_key
403    }
404
405    /// Has the session been backed up to the server.
406    pub fn backed_up(&self) -> bool {
407        self.backed_up.load(SeqCst)
408    }
409
410    /// Reset the backup state of the inbound group session.
411    pub fn reset_backup_state(&self) {
412        self.backed_up.store(false, SeqCst)
413    }
414
415    /// For testing, allow to manually mark this GroupSession to have been
416    /// backed up
417    pub fn mark_as_backed_up(&self) {
418        self.backed_up.store(true, SeqCst)
419    }
420
421    /// Get the map of signing keys this session was received from.
422    pub fn signing_keys(&self) -> &SigningKeys<DeviceKeyAlgorithm> {
423        &self.creator_info.signing_keys
424    }
425
426    /// Export this session at the given message index.
427    pub async fn export_at_index(&self, message_index: u32) -> ExportedRoomKey {
428        let message_index = std::cmp::max(self.first_known_index(), message_index);
429
430        let session_key =
431            self.inner.lock().await.export_at(message_index).expect("Can't export session");
432
433        ExportedRoomKey {
434            algorithm: self.algorithm().to_owned(),
435            room_id: self.room_id().to_owned(),
436            sender_key: self.creator_info.curve25519_key,
437            session_id: self.session_id().to_owned(),
438            forwarding_curve25519_key_chain: vec![],
439            sender_claimed_keys: (*self.creator_info.signing_keys).clone(),
440            session_key,
441            shared_history: self.shared_history,
442        }
443    }
444
445    /// Restore a Session from a previously pickled string.
446    ///
447    /// Returns the restored group session or a `UnpicklingError` if there
448    /// was an error.
449    ///
450    /// # Arguments
451    ///
452    /// * `pickle` - The pickled version of the `InboundGroupSession`.
453    ///
454    /// * `pickle_mode` - The mode that was used to pickle the session, either
455    ///   an unencrypted mode or an encrypted using passphrase.
456    pub fn from_pickle(pickle: PickledInboundGroupSession) -> Result<Self, PickleError> {
457        let PickledInboundGroupSession {
458            pickle,
459            sender_key,
460            signing_key,
461            sender_data,
462            room_id,
463            imported,
464            backed_up,
465            history_visibility,
466            algorithm,
467            shared_history,
468        } = pickle;
469
470        let session: InnerSession = pickle.into();
471        let first_known_index = session.first_known_index();
472        let session_id = session.session_id();
473
474        Ok(InboundGroupSession {
475            inner: Mutex::new(session).into(),
476            session_id: session_id.into(),
477            creator_info: SessionCreatorInfo {
478                curve25519_key: sender_key,
479                signing_keys: signing_key.into(),
480            },
481            sender_data,
482            history_visibility: history_visibility.into(),
483            first_known_index,
484            room_id,
485            backed_up: AtomicBool::from(backed_up).into(),
486            algorithm: algorithm.into(),
487            imported,
488            shared_history,
489        })
490    }
491
492    /// The room where this session is used in.
493    pub fn room_id(&self) -> &RoomId {
494        &self.room_id
495    }
496
497    /// Returns the unique identifier for this session.
498    pub fn session_id(&self) -> &str {
499        &self.session_id
500    }
501
502    /// The algorithm that this inbound group session is using to decrypt
503    /// events.
504    pub fn algorithm(&self) -> &EventEncryptionAlgorithm {
505        &self.algorithm
506    }
507
508    /// Get the first message index we know how to decrypt.
509    pub fn first_known_index(&self) -> u32 {
510        self.first_known_index
511    }
512
513    /// Has the session been imported from a file or server-side backup? As
514    /// opposed to being directly received as an `m.room_key` event.
515    pub fn has_been_imported(&self) -> bool {
516        self.imported
517    }
518
519    /// Check if the [`InboundGroupSession`] is better than the given other
520    /// [`InboundGroupSession`]
521    #[deprecated(
522        note = "Sessions cannot be compared on a linear scale. Consider calling `compare_ratchet`, as well as comparing the `sender_data`."
523    )]
524    pub async fn compare(&self, other: &InboundGroupSession) -> SessionOrdering {
525        match self.compare_ratchet(other).await {
526            SessionOrdering::Equal => {
527                match self.sender_data.compare_trust_level(&other.sender_data) {
528                    Ordering::Less => SessionOrdering::Worse,
529                    Ordering::Equal => SessionOrdering::Equal,
530                    Ordering::Greater => SessionOrdering::Better,
531                }
532            }
533            result => result,
534        }
535    }
536
537    /// Check if the [`InboundGroupSession`]'s ratchet index is better than that
538    /// of the given other [`InboundGroupSession`].
539    ///
540    /// If the two sessions are not connected (i.e., they are from different
541    /// senders, or if advancing the ratchets to the same index does not
542    /// give the same ratchet value), returns [`SessionOrdering::Unconnected`].
543    ///
544    /// Otherwise, returns [`SessionOrdering::Equal`],
545    /// [`SessionOrdering::Better`], or [`SessionOrdering::Worse`] respectively
546    /// depending on whether this session's first known index is equal to,
547    /// lower than, or higher than, that of `other`.
548    pub async fn compare_ratchet(&self, other: &InboundGroupSession) -> SessionOrdering {
549        // If this is the same object the ordering is the same, we can't compare because
550        // we would deadlock while trying to acquire the same lock twice.
551        if Arc::ptr_eq(&self.inner, &other.inner) {
552            SessionOrdering::Equal
553        } else if self.sender_key() != other.sender_key()
554            || self.signing_keys() != other.signing_keys()
555            || self.algorithm() != other.algorithm()
556            || self.room_id() != other.room_id()
557        {
558            SessionOrdering::Unconnected
559        } else {
560            let mut other_inner = other.inner.lock().await;
561            self.inner.lock().await.compare(&mut other_inner)
562        }
563    }
564
565    /// Decrypt the given ciphertext.
566    ///
567    /// Returns the decrypted plaintext or an `DecryptionError` if
568    /// decryption failed.
569    ///
570    /// # Arguments
571    ///
572    /// * `message` - The message that should be decrypted.
573    pub(crate) async fn decrypt_helper(
574        &self,
575        message: &MegolmMessage,
576    ) -> Result<DecryptedMessage, DecryptionError> {
577        self.inner.lock().await.decrypt(message)
578    }
579
580    /// Export the inbound group session into a format that can be uploaded to
581    /// the server as a backup.
582    pub async fn to_backup(&self) -> BackedUpRoomKey {
583        self.export().await.into()
584    }
585
586    /// Decrypt an event from a room timeline.
587    ///
588    /// # Arguments
589    ///
590    /// * `event` - The event that should be decrypted.
591    pub async fn decrypt(&self, event: &EncryptedEvent) -> MegolmResult<(JsonObject, u32)> {
592        let decrypted = match &event.content.scheme {
593            RoomEventEncryptionScheme::MegolmV1AesSha2(c) => {
594                self.decrypt_helper(&c.ciphertext).await?
595            }
596            #[cfg(feature = "experimental-algorithms")]
597            RoomEventEncryptionScheme::MegolmV2AesSha2(c) => {
598                self.decrypt_helper(&c.ciphertext).await?
599            }
600            RoomEventEncryptionScheme::Unknown(_) => {
601                return Err(EventError::UnsupportedAlgorithm.into());
602            }
603        };
604
605        let plaintext = String::from_utf8_lossy(&decrypted.plaintext);
606
607        let mut decrypted_object = serde_json::from_str::<JsonObject>(&plaintext)?;
608
609        let server_ts: i64 = event.origin_server_ts.0.into();
610
611        decrypted_object.insert("sender".to_owned(), event.sender.to_string().into());
612        decrypted_object.insert("event_id".to_owned(), event.event_id.to_string().into());
613        decrypted_object.insert("origin_server_ts".to_owned(), server_ts.into());
614
615        let room_id = decrypted_object
616            .get("room_id")
617            .and_then(|r| r.as_str().and_then(|r| RoomId::parse(r).ok()));
618
619        // Check that we have a room id and that the event wasn't forwarded from
620        // another room.
621        if room_id.as_deref() != Some(self.room_id()) {
622            return Err(EventError::MismatchedRoom(self.room_id().to_owned(), room_id).into());
623        }
624
625        decrypted_object.insert(
626            "unsigned".to_owned(),
627            serde_json::to_value(&event.unsigned).unwrap_or_default(),
628        );
629
630        if let Some(decrypted_content) =
631            decrypted_object.get_mut("content").and_then(|c| c.as_object_mut())
632            && !decrypted_content.contains_key("m.relates_to")
633            && let Some(relation) = &event.content.relates_to
634        {
635            decrypted_content.insert("m.relates_to".to_owned(), relation.to_owned());
636        }
637
638        Ok((decrypted_object, decrypted.message_index))
639    }
640
641    /// For test only, mark this session as imported.
642    #[cfg(test)]
643    pub(crate) fn mark_as_imported(&mut self) {
644        self.imported = true;
645    }
646
647    /// Return the [`SenderDataType`] of our [`SenderData`]. This is used during
648    /// serialization, to allow us to store the type in a separate queryable
649    /// column/property.
650    pub fn sender_data_type(&self) -> SenderDataType {
651        self.sender_data.to_type()
652    }
653
654    /// Whether this [`InboundGroupSession`] can be shared with users who are
655    /// invited to the room in the future, allowing access to history, as
656    /// defined in [MSC3061].
657    ///
658    /// [MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061
659    pub fn shared_history(&self) -> bool {
660        self.shared_history
661    }
662}
663
664#[cfg(not(tarpaulin_include))]
665impl fmt::Debug for InboundGroupSession {
666    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
667        f.debug_struct("InboundGroupSession").field("session_id", &self.session_id()).finish()
668    }
669}
670
671impl PartialEq for InboundGroupSession {
672    fn eq(&self, other: &Self) -> bool {
673        self.session_id() == other.session_id()
674    }
675}
676
677/// A pickled version of an `InboundGroupSession`.
678///
679/// Holds all the information that needs to be stored in a database to restore
680/// an InboundGroupSession.
681#[derive(Serialize, Deserialize)]
682#[allow(missing_debug_implementations)]
683pub struct PickledInboundGroupSession {
684    /// The pickle string holding the InboundGroupSession.
685    pub pickle: InboundGroupSessionPickle,
686    /// The public Curve25519 key of the account that sent us the session
687    #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
688    pub sender_key: Curve25519PublicKey,
689    /// The public ed25519 key of the account that sent us the session.
690    pub signing_key: SigningKeys<DeviceKeyAlgorithm>,
691    /// Information on the device/sender who sent us this session
692    #[serde(default)]
693    pub sender_data: SenderData,
694    /// The id of the room that the session is used in.
695    pub room_id: OwnedRoomId,
696    /// Flag remembering if the session was directly sent to us by the sender
697    /// or if it was imported.
698    pub imported: bool,
699    /// Flag remembering if the session has been backed up.
700    #[serde(default)]
701    pub backed_up: bool,
702    /// History visibility of the room when the session was created.
703    pub history_visibility: Option<HistoryVisibility>,
704    /// The algorithm of this inbound group session.
705    #[serde(default = "default_algorithm")]
706    pub algorithm: EventEncryptionAlgorithm,
707    /// Whether this [`InboundGroupSession`] can be shared with users who are
708    /// invited to the room in the future, allowing access to history, as
709    /// defined in [MSC3061].
710    ///
711    /// [MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061
712    #[serde(default)]
713    pub shared_history: bool,
714}
715
716fn default_algorithm() -> EventEncryptionAlgorithm {
717    EventEncryptionAlgorithm::MegolmV1AesSha2
718}
719
720impl TryFrom<&HistoricRoomKey> for InboundGroupSession {
721    type Error = SessionCreationError;
722
723    fn try_from(key: &HistoricRoomKey) -> Result<Self, Self::Error> {
724        let HistoricRoomKey {
725            algorithm,
726            room_id,
727            sender_key,
728            session_id,
729            session_key,
730            sender_claimed_keys,
731        } = key;
732
733        let config = OutboundGroupSession::session_config(algorithm)?;
734        let session = InnerSession::import(session_key, config);
735        let first_known_index = session.first_known_index();
736
737        Ok(InboundGroupSession {
738            inner: Mutex::new(session).into(),
739            session_id: session_id.to_owned().into(),
740            creator_info: SessionCreatorInfo {
741                curve25519_key: *sender_key,
742                signing_keys: sender_claimed_keys.to_owned().into(),
743            },
744            // TODO: How do we remember that this is a historic room key and events decrypted using
745            // this room key should always show some form of warning.
746            sender_data: SenderData::default(),
747            history_visibility: None.into(),
748            first_known_index,
749            room_id: room_id.to_owned(),
750            imported: true,
751            algorithm: algorithm.to_owned().into(),
752            backed_up: AtomicBool::from(false).into(),
753            shared_history: true,
754        })
755    }
756}
757
758impl TryFrom<&ExportedRoomKey> for InboundGroupSession {
759    type Error = SessionCreationError;
760
761    fn try_from(key: &ExportedRoomKey) -> Result<Self, Self::Error> {
762        let ExportedRoomKey {
763            algorithm,
764            room_id,
765            sender_key,
766            session_id,
767            session_key,
768            sender_claimed_keys,
769            forwarding_curve25519_key_chain: _,
770            shared_history,
771        } = key;
772
773        let config = OutboundGroupSession::session_config(algorithm)?;
774        let session = InnerSession::import(session_key, config);
775        let first_known_index = session.first_known_index();
776
777        Ok(InboundGroupSession {
778            inner: Mutex::new(session).into(),
779            session_id: session_id.to_owned().into(),
780            creator_info: SessionCreatorInfo {
781                curve25519_key: *sender_key,
782                signing_keys: sender_claimed_keys.to_owned().into(),
783            },
784            // TODO: In future, exported keys should contain sender data that we can use here.
785            // See https://github.com/matrix-org/matrix-rust-sdk/issues/3548
786            sender_data: SenderData::default(),
787            history_visibility: None.into(),
788            first_known_index,
789            room_id: room_id.to_owned(),
790            imported: true,
791            algorithm: algorithm.to_owned().into(),
792            backed_up: AtomicBool::from(false).into(),
793            shared_history: *shared_history,
794        })
795    }
796}
797
798impl From<&ForwardedMegolmV1AesSha2Content> for InboundGroupSession {
799    fn from(value: &ForwardedMegolmV1AesSha2Content) -> Self {
800        let session = InnerSession::import(&value.session_key, SessionConfig::version_1());
801        let session_id = session.session_id().into();
802        let first_known_index = session.first_known_index();
803
804        InboundGroupSession {
805            inner: Mutex::new(session).into(),
806            session_id,
807            creator_info: SessionCreatorInfo {
808                curve25519_key: value.claimed_sender_key,
809                signing_keys: SigningKeys::from([(
810                    DeviceKeyAlgorithm::Ed25519,
811                    value.claimed_ed25519_key.into(),
812                )])
813                .into(),
814            },
815            // In future, exported keys should contain sender data that we can use here.
816            // See https://github.com/matrix-org/matrix-rust-sdk/issues/3548
817            sender_data: SenderData::default(),
818            history_visibility: None.into(),
819            first_known_index,
820            room_id: value.room_id.to_owned(),
821            imported: true,
822            algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2.into(),
823            backed_up: AtomicBool::from(false).into(),
824            shared_history: false,
825        }
826    }
827}
828
829impl From<&ForwardedMegolmV2AesSha2Content> for InboundGroupSession {
830    fn from(value: &ForwardedMegolmV2AesSha2Content) -> Self {
831        let session = InnerSession::import(&value.session_key, SessionConfig::version_2());
832        let session_id = session.session_id().into();
833        let first_known_index = session.first_known_index();
834
835        InboundGroupSession {
836            inner: Mutex::new(session).into(),
837            session_id,
838            creator_info: SessionCreatorInfo {
839                curve25519_key: value.claimed_sender_key,
840                signing_keys: value.claimed_signing_keys.to_owned().into(),
841            },
842            // In future, exported keys should contain sender data that we can use here.
843            // See https://github.com/matrix-org/matrix-rust-sdk/issues/3548
844            sender_data: SenderData::default(),
845            history_visibility: None.into(),
846            first_known_index,
847            room_id: value.room_id.to_owned(),
848            imported: true,
849            algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2.into(),
850            backed_up: AtomicBool::from(false).into(),
851            shared_history: false,
852        }
853    }
854}
855
856impl TryFrom<&DecryptedForwardedRoomKeyEvent> for InboundGroupSession {
857    type Error = SessionCreationError;
858
859    fn try_from(value: &DecryptedForwardedRoomKeyEvent) -> Result<Self, Self::Error> {
860        match &value.content {
861            ForwardedRoomKeyContent::MegolmV1AesSha2(c) => Ok(Self::from(c.deref())),
862            #[cfg(feature = "experimental-algorithms")]
863            ForwardedRoomKeyContent::MegolmV2AesSha2(c) => Ok(Self::from(c.deref())),
864            ForwardedRoomKeyContent::Unknown(c) => {
865                Err(SessionCreationError::Algorithm(c.algorithm.to_owned()))
866            }
867        }
868    }
869}
870
871#[cfg(test)]
872mod tests {
873    use assert_matches2::assert_let;
874    use insta::assert_json_snapshot;
875    use matrix_sdk_test::async_test;
876    use ruma::{
877        DeviceId, UserId, device_id, events::room::history_visibility::HistoryVisibility,
878        owned_room_id, room_id, user_id,
879    };
880    use serde_json::json;
881    use similar_asserts::assert_eq;
882    use vodozemac::{
883        Curve25519PublicKey, Ed25519PublicKey,
884        megolm::{SessionKey, SessionOrdering},
885    };
886
887    use crate::{
888        Account,
889        olm::{BackedUpRoomKey, ExportedRoomKey, InboundGroupSession, KnownSenderData, SenderData},
890        types::{EventEncryptionAlgorithm, events::room_key},
891    };
892
893    fn alice_id() -> &'static UserId {
894        user_id!("@alice:example.org")
895    }
896
897    fn alice_device_id() -> &'static DeviceId {
898        device_id!("ALICEDEVICE")
899    }
900
901    #[async_test]
902    async fn test_pickle_snapshot() {
903        let account = Account::new(alice_id());
904        let room_id = room_id!("!test:localhost");
905        let (_, session) = account.create_group_session_pair_with_defaults(room_id).await;
906
907        let pickle = session.pickle().await;
908
909        assert_json_snapshot!(pickle, {
910            ".pickle.initial_ratchet.inner" => "[ratchet]",
911            ".pickle.signing_key" => "[signing_key]",
912            ".sender_key" => "[sender_key]",
913            ".signing_key.ed25519" => "[ed25519_key]",
914        });
915    }
916
917    #[async_test]
918    async fn test_can_deserialise_pickled_session_without_sender_data() {
919        // Given the raw JSON for a picked inbound group session without any sender_data
920        let pickle = r#"
921        {
922            "pickle": {
923                "initial_ratchet": {
924                    "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
925                               220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
926                               229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
927                               202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
928                               138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
929                               245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
930                               2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
931                               205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
932                               52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
933                    "counter": 0
934                },
935                "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
936                                 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
937                                 149, 43, 38 ],
938                "signing_key_verified": true,
939                "config": {
940                  "version": "V1"
941                }
942            },
943            "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
944            "signing_key": {
945                "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
946            },
947            "room_id": "!test:localhost",
948            "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
949            "imported": false,
950            "backed_up": false,
951            "history_visibility": "shared",
952            "algorithm": "m.megolm.v1.aes-sha2"
953        }
954        "#;
955
956        // When we deserialise it to from JSON
957        let deserialized = serde_json::from_str(pickle).unwrap();
958
959        // And unpickle it
960        let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
961
962        // Then it was parsed correctly
963        assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
964
965        // And we populated the InboundGroupSession's sender_data with a default value,
966        // with legacy_session set to true.
967        assert_let!(
968            SenderData::UnknownDevice { legacy_session, owner_check_failed } =
969                unpickled.sender_data
970        );
971        assert!(legacy_session);
972        assert!(!owner_check_failed);
973    }
974
975    #[async_test]
976    async fn test_can_serialise_pickled_session_with_sender_data() {
977        // Given an InboundGroupSession
978        let igs = InboundGroupSession::new(
979            Curve25519PublicKey::from_base64("AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8")
980                .unwrap(),
981            Ed25519PublicKey::from_base64("wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww").unwrap(),
982            room_id!("!test:localhost"),
983            &create_session_key(),
984            SenderData::unknown(),
985            EventEncryptionAlgorithm::MegolmV1AesSha2,
986            Some(HistoryVisibility::Shared),
987            false,
988        )
989        .unwrap();
990
991        // When we pickle it
992        let pickled = igs.pickle().await;
993
994        // And serialise it
995        let serialised = serde_json::to_string(&pickled).unwrap();
996
997        // Then it looks as we expect
998
999        // (Break out this list of numbers as otherwise it bothers the json macro below)
1000        let expected_inner = vec![
1001            193, 203, 223, 152, 33, 132, 200, 168, 24, 197, 79, 174, 231, 202, 45, 245, 128, 131,
1002            178, 165, 148, 37, 241, 214, 178, 218, 25, 33, 68, 48, 153, 104, 122, 6, 249, 198, 97,
1003            226, 214, 75, 64, 128, 25, 138, 98, 90, 138, 93, 52, 206, 174, 3, 84, 149, 101, 140,
1004            238, 156, 103, 107, 124, 144, 139, 104, 253, 5, 100, 251, 186, 118, 208, 87, 31, 218,
1005            123, 234, 103, 34, 246, 100, 39, 90, 216, 72, 187, 86, 202, 150, 100, 116, 204, 254,
1006            10, 154, 216, 133, 61, 250, 75, 100, 195, 63, 138, 22, 17, 13, 156, 123, 195, 132, 111,
1007            95, 250, 24, 236, 0, 246, 93, 230, 100, 211, 165, 211, 190, 181, 87, 42, 181,
1008        ];
1009        assert_eq!(
1010            serde_json::from_str::<serde_json::Value>(&serialised).unwrap(),
1011            serde_json::json!({
1012                "pickle":{
1013                    "initial_ratchet":{
1014                        "inner": expected_inner,
1015                        "counter":0
1016                    },
1017                    "signing_key":[
1018                        213,161,95,135,114,153,162,127,217,74,64,2,59,143,93,5,190,157,120,
1019                        80,89,8,87,129,115,148,104,144,152,186,178,109
1020                    ],
1021                    "signing_key_verified":true,
1022                    "config":{"version":"V1"}
1023                },
1024                "sender_key":"AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
1025                "signing_key":{"ed25519":"wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"},
1026                "sender_data":{
1027                    "UnknownDevice":{
1028                        "legacy_session":false
1029                    }
1030                },
1031                "room_id":"!test:localhost",
1032                "imported":false,
1033                "backed_up":false,
1034                "shared_history":false,
1035                "history_visibility":"shared",
1036                "algorithm":"m.megolm.v1.aes-sha2"
1037            })
1038        );
1039    }
1040
1041    #[async_test]
1042    async fn test_can_deserialise_pickled_session_with_sender_data() {
1043        // Given the raw JSON for a picked inbound group session (including sender_data)
1044        let pickle = r#"
1045        {
1046            "pickle": {
1047                "initial_ratchet": {
1048                    "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
1049                               220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
1050                               229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
1051                               202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
1052                               138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
1053                               245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
1054                               2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
1055                               205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
1056                               52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
1057                    "counter": 0
1058                },
1059                "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
1060                                 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
1061                                 149, 43, 38 ],
1062                "signing_key_verified": true,
1063                "config": {
1064                  "version": "V1"
1065                }
1066            },
1067            "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
1068            "signing_key": {
1069                "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
1070            },
1071            "sender_data":{
1072                "UnknownDevice":{
1073                    "legacy_session":false
1074                }
1075            },
1076            "room_id": "!test:localhost",
1077            "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
1078            "imported": false,
1079            "backed_up": false,
1080            "history_visibility": "shared",
1081            "algorithm": "m.megolm.v1.aes-sha2"
1082        }
1083        "#;
1084
1085        // When we deserialise it to from JSON
1086        let deserialized = serde_json::from_str(pickle).unwrap();
1087
1088        // And unpickle it
1089        let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
1090
1091        // Then it was parsed correctly
1092        assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
1093
1094        // And we populated the InboundGroupSession's sender_data with the provided
1095        // values
1096        assert_let!(
1097            SenderData::UnknownDevice { legacy_session, owner_check_failed } =
1098                unpickled.sender_data
1099        );
1100        assert!(!legacy_session);
1101        assert!(!owner_check_failed);
1102    }
1103
1104    #[async_test]
1105    #[allow(deprecated)]
1106    async fn test_session_comparison() {
1107        let alice = Account::with_device_id(alice_id(), alice_device_id());
1108        let room_id = room_id!("!test:localhost");
1109
1110        let (_, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1111
1112        let worse = InboundGroupSession::from_export(&inbound.export_at_index(10).await).unwrap();
1113        let mut copy = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
1114
1115        assert_eq!(inbound.compare(&worse).await, SessionOrdering::Better);
1116        assert_eq!(inbound.compare_ratchet(&worse).await, SessionOrdering::Better);
1117        assert_eq!(worse.compare(&inbound).await, SessionOrdering::Worse);
1118        assert_eq!(worse.compare_ratchet(&inbound).await, SessionOrdering::Worse);
1119        assert_eq!(inbound.compare(&inbound).await, SessionOrdering::Equal);
1120        assert_eq!(inbound.compare_ratchet(&inbound).await, SessionOrdering::Equal);
1121        assert_eq!(inbound.compare(&copy).await, SessionOrdering::Equal);
1122        assert_eq!(inbound.compare_ratchet(&copy).await, SessionOrdering::Equal);
1123
1124        copy.creator_info.curve25519_key =
1125            Curve25519PublicKey::from_base64("XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY")
1126                .unwrap();
1127
1128        assert_eq!(inbound.compare(&copy).await, SessionOrdering::Unconnected);
1129        assert_eq!(inbound.compare_ratchet(&copy).await, SessionOrdering::Unconnected);
1130    }
1131
1132    #[async_test]
1133    #[allow(deprecated)]
1134    async fn test_session_comparison_sender_data() {
1135        let alice = Account::with_device_id(alice_id(), alice_device_id());
1136        let room_id = room_id!("!test:localhost");
1137
1138        let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1139
1140        let sender_data = SenderData::SenderVerified(KnownSenderData {
1141            user_id: alice.user_id().into(),
1142            device_id: Some(alice.device_id().into()),
1143            master_key: alice.identity_keys().ed25519.into(),
1144        });
1145
1146        let mut better = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
1147        better.sender_data = sender_data.clone();
1148
1149        assert_eq!(inbound.compare(&better).await, SessionOrdering::Worse);
1150        assert_eq!(better.compare(&inbound).await, SessionOrdering::Better);
1151
1152        inbound.sender_data = sender_data;
1153        assert_eq!(better.compare(&inbound).await, SessionOrdering::Equal);
1154    }
1155
1156    fn create_session_key() -> SessionKey {
1157        SessionKey::from_base64(
1158            "\
1159            AgAAAADBy9+YIYTIqBjFT67nyi31gIOypZQl8day2hkhRDCZaHoG+cZh4tZLQIAZimJail0\
1160            0zq4DVJVljO6cZ2t8kIto/QVk+7p20Fcf2nvqZyL2ZCda2Ei7VsqWZHTM/gqa2IU9+ktkwz\
1161            +KFhENnHvDhG9f+hjsAPZd5mTTpdO+tVcqtdWhX4dymaJ/2UpAAjuPXQW+nXhQWQhXgXOUa\
1162            JCYurJtvbCbqZGeDMmVIoqukBs2KugNJ6j5WlTPoeFnMl6Guy9uH2iWWxGg8ZgT2xspqVl5\
1163            CwujjC+m7Dh1toVkvu+bAw\
1164            ",
1165        )
1166        .unwrap()
1167    }
1168
1169    #[async_test]
1170    async fn test_shared_history_from_m_room_key_content() {
1171        let content = json!({
1172            "algorithm": "m.megolm.v1.aes-sha2",
1173            "room_id": "!Cuyf34gef24t:localhost",
1174            "org.matrix.msc3061.shared_history": true,
1175            "session_id": "ZFD6+OmV7fVCsJ7Gap8UnORH8EnmiAkes8FAvQuCw/I",
1176            "session_key": "AgAAAADNp1EbxXYOGmJtyX4AkD1bvJvAUyPkbIaKxtnGKjv\
1177                            SQ3E/4mnuqdM4vsmNzpO1EeWzz1rDkUpYhYE9kP7sJhgLXi\
1178                            jVv80fMPHfGc49hPdu8A+xnwD4SQiYdFmSWJOIqsxeo/fiH\
1179                            tino//CDQENtcKuEt0I9s0+Kk4YSH310Szse2RQ+vjple31\
1180                            QrCexmqfFJzkR/BJ5ogJHrPBQL0LgsPyglIbMTLg7qygIaY\
1181                            U5Fe2QdKMH7nTZPNIRHh1RaMfHVETAUJBax88EWZBoifk80\
1182                            gdHUwHSgMk77vCc2a5KHKLDA",
1183        });
1184
1185        let sender_key = Curve25519PublicKey::from_bytes([0; 32]);
1186        let signing_key = Ed25519PublicKey::from_slice(&[0; 32]).expect("");
1187        let mut content: room_key::MegolmV1AesSha2Content = serde_json::from_value(content)
1188            .expect("We should be able to deserialize the m.room_key content");
1189
1190        let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1191            .expect(
1192                "We should be able to create an inbound group session from the room key content",
1193            );
1194
1195        assert!(
1196            session.shared_history,
1197            "The shared history flag should be set as it was set in the m.room_key content"
1198        );
1199
1200        content.shared_history = false;
1201        let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1202            .expect(
1203                "We should be able to create an inbound group session from the room key content",
1204            );
1205
1206        assert!(
1207            !session.shared_history,
1208            "The shared history flag should not be set as it was not set in the m.room_key content"
1209        );
1210    }
1211
1212    #[async_test]
1213    async fn test_shared_history_from_exported_room_key() {
1214        let content = json!({
1215                "algorithm": "m.megolm.v1.aes-sha2",
1216                "room_id": "!room:id",
1217                "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
1218                "session_id": "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0",
1219                "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
1220                "sender_claimed_keys": {
1221                    "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
1222                },
1223                "forwarding_curve25519_key_chain": [],
1224                "org.matrix.msc3061.shared_history": true
1225
1226        });
1227
1228        let mut content: ExportedRoomKey = serde_json::from_value(content)
1229            .expect("We should be able to deserialize the m.room_key content");
1230
1231        let session = InboundGroupSession::from_export(&content).expect(
1232            "We should be able to create an inbound group session from the room key export",
1233        );
1234        assert!(
1235            session.shared_history,
1236            "The shared history flag should be set as it was set in the exported room key"
1237        );
1238
1239        content.shared_history = false;
1240
1241        let session = InboundGroupSession::from_export(&content).expect(
1242            "We should be able to create an inbound group session from the room key export",
1243        );
1244        assert!(
1245            !session.shared_history,
1246            "The shared history flag should not be set as it was not set in the exported room key"
1247        );
1248    }
1249
1250    #[async_test]
1251    async fn test_shared_history_from_backed_up_room_key() {
1252        let content = json!({
1253                "algorithm": "m.megolm.v1.aes-sha2",
1254                "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
1255                "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
1256                "sender_claimed_keys": {
1257                    "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
1258                },
1259                "forwarding_curve25519_key_chain": [],
1260                "org.matrix.msc3061.shared_history": true
1261
1262        });
1263
1264        let session_id = "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0";
1265        let room_id = owned_room_id!("!room:id");
1266        let room_key: BackedUpRoomKey = serde_json::from_value(content)
1267            .expect("We should be able to deserialize the backed up room key");
1268
1269        let room_key =
1270            ExportedRoomKey::from_backed_up_room_key(room_id, session_id.to_owned(), room_key);
1271
1272        let session = InboundGroupSession::from_export(&room_key).expect(
1273            "We should be able to create an inbound group session from the room key export",
1274        );
1275        assert!(
1276            session.shared_history,
1277            "The shared history flag should be set as it was set in the backed up room key"
1278        );
1279    }
1280
1281    #[async_test]
1282    async fn test_shared_history_in_pickle() {
1283        let alice = Account::with_device_id(alice_id(), alice_device_id());
1284        let room_id = room_id!("!test:localhost");
1285
1286        let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1287
1288        inbound.shared_history = true;
1289        let pickle = inbound.pickle().await;
1290
1291        assert!(
1292            pickle.shared_history,
1293            "The set shared history flag should have been copied to the pickle"
1294        );
1295
1296        inbound.shared_history = false;
1297        let pickle = inbound.pickle().await;
1298
1299        assert!(
1300            !pickle.shared_history,
1301            "The unset shared history flag should have been copied to the pickle"
1302        );
1303    }
1304
1305    #[async_test]
1306    async fn test_shared_history_in_export() {
1307        let alice = Account::with_device_id(alice_id(), alice_device_id());
1308        let room_id = room_id!("!test:localhost");
1309
1310        let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1311
1312        inbound.shared_history = true;
1313        let export = inbound.export().await;
1314        assert!(
1315            export.shared_history,
1316            "The set shared history flag should have been copied to the room key export"
1317        );
1318
1319        inbound.shared_history = false;
1320        let export = inbound.export().await;
1321        assert!(
1322            !export.shared_history,
1323            "The unset shared history flag should have been copied to the room key export"
1324        );
1325    }
1326}