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        atomic::{AtomicBool, Ordering::SeqCst},
21        Arc,
22    },
23};
24
25use ruma::{
26    events::room::history_visibility::HistoryVisibility, serde::JsonObject, DeviceKeyAlgorithm,
27    OwnedRoomId, RoomId,
28};
29use serde::{Deserialize, Serialize};
30use tokio::sync::Mutex;
31use vodozemac::{
32    megolm::{
33        DecryptedMessage, DecryptionError, InboundGroupSession as InnerSession,
34        InboundGroupSessionPickle, MegolmMessage, SessionConfig, SessionOrdering,
35    },
36    Curve25519PublicKey, Ed25519PublicKey, PickleError,
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        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, EventEncryptionAlgorithm, SigningKeys,
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        {
633            if !decrypted_content.contains_key("m.relates_to") {
634                if let Some(relation) = &event.content.relates_to {
635                    decrypted_content.insert("m.relates_to".to_owned(), relation.to_owned());
636                }
637            }
638        }
639
640        Ok((decrypted_object, decrypted.message_index))
641    }
642
643    /// For test only, mark this session as imported.
644    #[cfg(test)]
645    pub(crate) fn mark_as_imported(&mut self) {
646        self.imported = true;
647    }
648
649    /// Return the [`SenderDataType`] of our [`SenderData`]. This is used during
650    /// serialization, to allow us to store the type in a separate queryable
651    /// column/property.
652    pub fn sender_data_type(&self) -> SenderDataType {
653        self.sender_data.to_type()
654    }
655
656    /// Whether this [`InboundGroupSession`] can be shared with users who are
657    /// invited to the room in the future, allowing access to history, as
658    /// defined in [MSC3061].
659    ///
660    /// [MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061
661    pub fn shared_history(&self) -> bool {
662        self.shared_history
663    }
664}
665
666#[cfg(not(tarpaulin_include))]
667impl fmt::Debug for InboundGroupSession {
668    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
669        f.debug_struct("InboundGroupSession").field("session_id", &self.session_id()).finish()
670    }
671}
672
673impl PartialEq for InboundGroupSession {
674    fn eq(&self, other: &Self) -> bool {
675        self.session_id() == other.session_id()
676    }
677}
678
679/// A pickled version of an `InboundGroupSession`.
680///
681/// Holds all the information that needs to be stored in a database to restore
682/// an InboundGroupSession.
683#[derive(Serialize, Deserialize)]
684#[allow(missing_debug_implementations)]
685pub struct PickledInboundGroupSession {
686    /// The pickle string holding the InboundGroupSession.
687    pub pickle: InboundGroupSessionPickle,
688    /// The public Curve25519 key of the account that sent us the session
689    #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
690    pub sender_key: Curve25519PublicKey,
691    /// The public ed25519 key of the account that sent us the session.
692    pub signing_key: SigningKeys<DeviceKeyAlgorithm>,
693    /// Information on the device/sender who sent us this session
694    #[serde(default)]
695    pub sender_data: SenderData,
696    /// The id of the room that the session is used in.
697    pub room_id: OwnedRoomId,
698    /// Flag remembering if the session was directly sent to us by the sender
699    /// or if it was imported.
700    pub imported: bool,
701    /// Flag remembering if the session has been backed up.
702    #[serde(default)]
703    pub backed_up: bool,
704    /// History visibility of the room when the session was created.
705    pub history_visibility: Option<HistoryVisibility>,
706    /// The algorithm of this inbound group session.
707    #[serde(default = "default_algorithm")]
708    pub algorithm: EventEncryptionAlgorithm,
709    /// Whether this [`InboundGroupSession`] can be shared with users who are
710    /// invited to the room in the future, allowing access to history, as
711    /// defined in [MSC3061].
712    ///
713    /// [MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061
714    #[serde(default)]
715    pub shared_history: bool,
716}
717
718fn default_algorithm() -> EventEncryptionAlgorithm {
719    EventEncryptionAlgorithm::MegolmV1AesSha2
720}
721
722impl TryFrom<&HistoricRoomKey> for InboundGroupSession {
723    type Error = SessionCreationError;
724
725    fn try_from(key: &HistoricRoomKey) -> Result<Self, Self::Error> {
726        let HistoricRoomKey {
727            algorithm,
728            room_id,
729            sender_key,
730            session_id,
731            session_key,
732            sender_claimed_keys,
733        } = key;
734
735        let config = OutboundGroupSession::session_config(algorithm)?;
736        let session = InnerSession::import(session_key, config);
737        let first_known_index = session.first_known_index();
738
739        Ok(InboundGroupSession {
740            inner: Mutex::new(session).into(),
741            session_id: session_id.to_owned().into(),
742            creator_info: SessionCreatorInfo {
743                curve25519_key: *sender_key,
744                signing_keys: sender_claimed_keys.to_owned().into(),
745            },
746            // TODO: How do we remember that this is a historic room key and events decrypted using
747            // this room key should always show some form of warning.
748            sender_data: SenderData::default(),
749            history_visibility: None.into(),
750            first_known_index,
751            room_id: room_id.to_owned(),
752            imported: true,
753            algorithm: algorithm.to_owned().into(),
754            backed_up: AtomicBool::from(false).into(),
755            shared_history: true,
756        })
757    }
758}
759
760impl TryFrom<&ExportedRoomKey> for InboundGroupSession {
761    type Error = SessionCreationError;
762
763    fn try_from(key: &ExportedRoomKey) -> Result<Self, Self::Error> {
764        let ExportedRoomKey {
765            algorithm,
766            room_id,
767            sender_key,
768            session_id,
769            session_key,
770            sender_claimed_keys,
771            forwarding_curve25519_key_chain: _,
772            shared_history,
773        } = key;
774
775        let config = OutboundGroupSession::session_config(algorithm)?;
776        let session = InnerSession::import(session_key, config);
777        let first_known_index = session.first_known_index();
778
779        Ok(InboundGroupSession {
780            inner: Mutex::new(session).into(),
781            session_id: session_id.to_owned().into(),
782            creator_info: SessionCreatorInfo {
783                curve25519_key: *sender_key,
784                signing_keys: sender_claimed_keys.to_owned().into(),
785            },
786            // TODO: In future, exported keys should contain sender data that we can use here.
787            // See https://github.com/matrix-org/matrix-rust-sdk/issues/3548
788            sender_data: SenderData::default(),
789            history_visibility: None.into(),
790            first_known_index,
791            room_id: room_id.to_owned(),
792            imported: true,
793            algorithm: algorithm.to_owned().into(),
794            backed_up: AtomicBool::from(false).into(),
795            shared_history: *shared_history,
796        })
797    }
798}
799
800impl From<&ForwardedMegolmV1AesSha2Content> for InboundGroupSession {
801    fn from(value: &ForwardedMegolmV1AesSha2Content) -> Self {
802        let session = InnerSession::import(&value.session_key, SessionConfig::version_1());
803        let session_id = session.session_id().into();
804        let first_known_index = session.first_known_index();
805
806        InboundGroupSession {
807            inner: Mutex::new(session).into(),
808            session_id,
809            creator_info: SessionCreatorInfo {
810                curve25519_key: value.claimed_sender_key,
811                signing_keys: SigningKeys::from([(
812                    DeviceKeyAlgorithm::Ed25519,
813                    value.claimed_ed25519_key.into(),
814                )])
815                .into(),
816            },
817            // In future, exported keys should contain sender data that we can use here.
818            // See https://github.com/matrix-org/matrix-rust-sdk/issues/3548
819            sender_data: SenderData::default(),
820            history_visibility: None.into(),
821            first_known_index,
822            room_id: value.room_id.to_owned(),
823            imported: true,
824            algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2.into(),
825            backed_up: AtomicBool::from(false).into(),
826            shared_history: false,
827        }
828    }
829}
830
831impl From<&ForwardedMegolmV2AesSha2Content> for InboundGroupSession {
832    fn from(value: &ForwardedMegolmV2AesSha2Content) -> Self {
833        let session = InnerSession::import(&value.session_key, SessionConfig::version_2());
834        let session_id = session.session_id().into();
835        let first_known_index = session.first_known_index();
836
837        InboundGroupSession {
838            inner: Mutex::new(session).into(),
839            session_id,
840            creator_info: SessionCreatorInfo {
841                curve25519_key: value.claimed_sender_key,
842                signing_keys: value.claimed_signing_keys.to_owned().into(),
843            },
844            // In future, exported keys should contain sender data that we can use here.
845            // See https://github.com/matrix-org/matrix-rust-sdk/issues/3548
846            sender_data: SenderData::default(),
847            history_visibility: None.into(),
848            first_known_index,
849            room_id: value.room_id.to_owned(),
850            imported: true,
851            algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2.into(),
852            backed_up: AtomicBool::from(false).into(),
853            shared_history: false,
854        }
855    }
856}
857
858impl TryFrom<&DecryptedForwardedRoomKeyEvent> for InboundGroupSession {
859    type Error = SessionCreationError;
860
861    fn try_from(value: &DecryptedForwardedRoomKeyEvent) -> Result<Self, Self::Error> {
862        match &value.content {
863            ForwardedRoomKeyContent::MegolmV1AesSha2(c) => Ok(Self::from(c.deref())),
864            #[cfg(feature = "experimental-algorithms")]
865            ForwardedRoomKeyContent::MegolmV2AesSha2(c) => Ok(Self::from(c.deref())),
866            ForwardedRoomKeyContent::Unknown(c) => {
867                Err(SessionCreationError::Algorithm(c.algorithm.to_owned()))
868            }
869        }
870    }
871}
872
873#[cfg(test)]
874mod tests {
875    use assert_matches2::assert_let;
876    use insta::assert_json_snapshot;
877    use matrix_sdk_test::async_test;
878    use ruma::{
879        device_id, events::room::history_visibility::HistoryVisibility, owned_room_id, room_id,
880        user_id, DeviceId, UserId,
881    };
882    use serde_json::json;
883    use similar_asserts::assert_eq;
884    use vodozemac::{
885        megolm::{SessionKey, SessionOrdering},
886        Curve25519PublicKey, Ed25519PublicKey,
887    };
888
889    use crate::{
890        olm::{BackedUpRoomKey, ExportedRoomKey, InboundGroupSession, KnownSenderData, SenderData},
891        types::{events::room_key, EventEncryptionAlgorithm},
892        Account,
893    };
894
895    fn alice_id() -> &'static UserId {
896        user_id!("@alice:example.org")
897    }
898
899    fn alice_device_id() -> &'static DeviceId {
900        device_id!("ALICEDEVICE")
901    }
902
903    #[async_test]
904    async fn test_pickle_snapshot() {
905        let account = Account::new(alice_id());
906        let room_id = room_id!("!test:localhost");
907        let (_, session) = account.create_group_session_pair_with_defaults(room_id).await;
908
909        let pickle = session.pickle().await;
910
911        assert_json_snapshot!(pickle, {
912            ".pickle.initial_ratchet.inner" => "[ratchet]",
913            ".pickle.signing_key" => "[signing_key]",
914            ".sender_key" => "[sender_key]",
915            ".signing_key.ed25519" => "[ed25519_key]",
916        });
917    }
918
919    #[async_test]
920    async fn test_can_deserialise_pickled_session_without_sender_data() {
921        // Given the raw JSON for a picked inbound group session without any sender_data
922        let pickle = r#"
923        {
924            "pickle": {
925                "initial_ratchet": {
926                    "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
927                               220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
928                               229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
929                               202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
930                               138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
931                               245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
932                               2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
933                               205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
934                               52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
935                    "counter": 0
936                },
937                "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
938                                 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
939                                 149, 43, 38 ],
940                "signing_key_verified": true,
941                "config": {
942                  "version": "V1"
943                }
944            },
945            "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
946            "signing_key": {
947                "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
948            },
949            "room_id": "!test:localhost",
950            "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
951            "imported": false,
952            "backed_up": false,
953            "history_visibility": "shared",
954            "algorithm": "m.megolm.v1.aes-sha2"
955        }
956        "#;
957
958        // When we deserialise it to from JSON
959        let deserialized = serde_json::from_str(pickle).unwrap();
960
961        // And unpickle it
962        let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
963
964        // Then it was parsed correctly
965        assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
966
967        // And we populated the InboundGroupSession's sender_data with a default value,
968        // with legacy_session set to true.
969        assert_let!(
970            SenderData::UnknownDevice { legacy_session, owner_check_failed } =
971                unpickled.sender_data
972        );
973        assert!(legacy_session);
974        assert!(!owner_check_failed);
975    }
976
977    #[async_test]
978    async fn test_can_serialise_pickled_session_with_sender_data() {
979        // Given an InboundGroupSession
980        let igs = InboundGroupSession::new(
981            Curve25519PublicKey::from_base64("AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8")
982                .unwrap(),
983            Ed25519PublicKey::from_base64("wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww").unwrap(),
984            room_id!("!test:localhost"),
985            &create_session_key(),
986            SenderData::unknown(),
987            EventEncryptionAlgorithm::MegolmV1AesSha2,
988            Some(HistoryVisibility::Shared),
989            false,
990        )
991        .unwrap();
992
993        // When we pickle it
994        let pickled = igs.pickle().await;
995
996        // And serialise it
997        let serialised = serde_json::to_string(&pickled).unwrap();
998
999        // Then it looks as we expect
1000
1001        // (Break out this list of numbers as otherwise it bothers the json macro below)
1002        let expected_inner = vec![
1003            193, 203, 223, 152, 33, 132, 200, 168, 24, 197, 79, 174, 231, 202, 45, 245, 128, 131,
1004            178, 165, 148, 37, 241, 214, 178, 218, 25, 33, 68, 48, 153, 104, 122, 6, 249, 198, 97,
1005            226, 214, 75, 64, 128, 25, 138, 98, 90, 138, 93, 52, 206, 174, 3, 84, 149, 101, 140,
1006            238, 156, 103, 107, 124, 144, 139, 104, 253, 5, 100, 251, 186, 118, 208, 87, 31, 218,
1007            123, 234, 103, 34, 246, 100, 39, 90, 216, 72, 187, 86, 202, 150, 100, 116, 204, 254,
1008            10, 154, 216, 133, 61, 250, 75, 100, 195, 63, 138, 22, 17, 13, 156, 123, 195, 132, 111,
1009            95, 250, 24, 236, 0, 246, 93, 230, 100, 211, 165, 211, 190, 181, 87, 42, 181,
1010        ];
1011        assert_eq!(
1012            serde_json::from_str::<serde_json::Value>(&serialised).unwrap(),
1013            serde_json::json!({
1014                "pickle":{
1015                    "initial_ratchet":{
1016                        "inner": expected_inner,
1017                        "counter":0
1018                    },
1019                    "signing_key":[
1020                        213,161,95,135,114,153,162,127,217,74,64,2,59,143,93,5,190,157,120,
1021                        80,89,8,87,129,115,148,104,144,152,186,178,109
1022                    ],
1023                    "signing_key_verified":true,
1024                    "config":{"version":"V1"}
1025                },
1026                "sender_key":"AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
1027                "signing_key":{"ed25519":"wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"},
1028                "sender_data":{
1029                    "UnknownDevice":{
1030                        "legacy_session":false
1031                    }
1032                },
1033                "room_id":"!test:localhost",
1034                "imported":false,
1035                "backed_up":false,
1036                "shared_history":false,
1037                "history_visibility":"shared",
1038                "algorithm":"m.megolm.v1.aes-sha2"
1039            })
1040        );
1041    }
1042
1043    #[async_test]
1044    async fn test_can_deserialise_pickled_session_with_sender_data() {
1045        // Given the raw JSON for a picked inbound group session (including sender_data)
1046        let pickle = r#"
1047        {
1048            "pickle": {
1049                "initial_ratchet": {
1050                    "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
1051                               220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
1052                               229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
1053                               202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
1054                               138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
1055                               245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
1056                               2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
1057                               205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
1058                               52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
1059                    "counter": 0
1060                },
1061                "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
1062                                 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
1063                                 149, 43, 38 ],
1064                "signing_key_verified": true,
1065                "config": {
1066                  "version": "V1"
1067                }
1068            },
1069            "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
1070            "signing_key": {
1071                "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
1072            },
1073            "sender_data":{
1074                "UnknownDevice":{
1075                    "legacy_session":false
1076                }
1077            },
1078            "room_id": "!test:localhost",
1079            "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
1080            "imported": false,
1081            "backed_up": false,
1082            "history_visibility": "shared",
1083            "algorithm": "m.megolm.v1.aes-sha2"
1084        }
1085        "#;
1086
1087        // When we deserialise it to from JSON
1088        let deserialized = serde_json::from_str(pickle).unwrap();
1089
1090        // And unpickle it
1091        let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
1092
1093        // Then it was parsed correctly
1094        assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
1095
1096        // And we populated the InboundGroupSession's sender_data with the provided
1097        // values
1098        assert_let!(
1099            SenderData::UnknownDevice { legacy_session, owner_check_failed } =
1100                unpickled.sender_data
1101        );
1102        assert!(!legacy_session);
1103        assert!(!owner_check_failed);
1104    }
1105
1106    #[async_test]
1107    #[allow(deprecated)]
1108    async fn test_session_comparison() {
1109        let alice = Account::with_device_id(alice_id(), alice_device_id());
1110        let room_id = room_id!("!test:localhost");
1111
1112        let (_, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1113
1114        let worse = InboundGroupSession::from_export(&inbound.export_at_index(10).await).unwrap();
1115        let mut copy = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
1116
1117        assert_eq!(inbound.compare(&worse).await, SessionOrdering::Better);
1118        assert_eq!(inbound.compare_ratchet(&worse).await, SessionOrdering::Better);
1119        assert_eq!(worse.compare(&inbound).await, SessionOrdering::Worse);
1120        assert_eq!(worse.compare_ratchet(&inbound).await, SessionOrdering::Worse);
1121        assert_eq!(inbound.compare(&inbound).await, SessionOrdering::Equal);
1122        assert_eq!(inbound.compare_ratchet(&inbound).await, SessionOrdering::Equal);
1123        assert_eq!(inbound.compare(&copy).await, SessionOrdering::Equal);
1124        assert_eq!(inbound.compare_ratchet(&copy).await, SessionOrdering::Equal);
1125
1126        copy.creator_info.curve25519_key =
1127            Curve25519PublicKey::from_base64("XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY")
1128                .unwrap();
1129
1130        assert_eq!(inbound.compare(&copy).await, SessionOrdering::Unconnected);
1131        assert_eq!(inbound.compare_ratchet(&copy).await, SessionOrdering::Unconnected);
1132    }
1133
1134    #[async_test]
1135    #[allow(deprecated)]
1136    async fn test_session_comparison_sender_data() {
1137        let alice = Account::with_device_id(alice_id(), alice_device_id());
1138        let room_id = room_id!("!test:localhost");
1139
1140        let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1141
1142        let sender_data = SenderData::SenderVerified(KnownSenderData {
1143            user_id: alice.user_id().into(),
1144            device_id: Some(alice.device_id().into()),
1145            master_key: alice.identity_keys().ed25519.into(),
1146        });
1147
1148        let mut better = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
1149        better.sender_data = sender_data.clone();
1150
1151        assert_eq!(inbound.compare(&better).await, SessionOrdering::Worse);
1152        assert_eq!(better.compare(&inbound).await, SessionOrdering::Better);
1153
1154        inbound.sender_data = sender_data;
1155        assert_eq!(better.compare(&inbound).await, SessionOrdering::Equal);
1156    }
1157
1158    fn create_session_key() -> SessionKey {
1159        SessionKey::from_base64(
1160            "\
1161            AgAAAADBy9+YIYTIqBjFT67nyi31gIOypZQl8day2hkhRDCZaHoG+cZh4tZLQIAZimJail0\
1162            0zq4DVJVljO6cZ2t8kIto/QVk+7p20Fcf2nvqZyL2ZCda2Ei7VsqWZHTM/gqa2IU9+ktkwz\
1163            +KFhENnHvDhG9f+hjsAPZd5mTTpdO+tVcqtdWhX4dymaJ/2UpAAjuPXQW+nXhQWQhXgXOUa\
1164            JCYurJtvbCbqZGeDMmVIoqukBs2KugNJ6j5WlTPoeFnMl6Guy9uH2iWWxGg8ZgT2xspqVl5\
1165            CwujjC+m7Dh1toVkvu+bAw\
1166            ",
1167        )
1168        .unwrap()
1169    }
1170
1171    #[async_test]
1172    async fn test_shared_history_from_m_room_key_content() {
1173        let content = json!({
1174            "algorithm": "m.megolm.v1.aes-sha2",
1175            "room_id": "!Cuyf34gef24t:localhost",
1176            "org.matrix.msc3061.shared_history": true,
1177            "session_id": "ZFD6+OmV7fVCsJ7Gap8UnORH8EnmiAkes8FAvQuCw/I",
1178            "session_key": "AgAAAADNp1EbxXYOGmJtyX4AkD1bvJvAUyPkbIaKxtnGKjv\
1179                            SQ3E/4mnuqdM4vsmNzpO1EeWzz1rDkUpYhYE9kP7sJhgLXi\
1180                            jVv80fMPHfGc49hPdu8A+xnwD4SQiYdFmSWJOIqsxeo/fiH\
1181                            tino//CDQENtcKuEt0I9s0+Kk4YSH310Szse2RQ+vjple31\
1182                            QrCexmqfFJzkR/BJ5ogJHrPBQL0LgsPyglIbMTLg7qygIaY\
1183                            U5Fe2QdKMH7nTZPNIRHh1RaMfHVETAUJBax88EWZBoifk80\
1184                            gdHUwHSgMk77vCc2a5KHKLDA",
1185        });
1186
1187        let sender_key = Curve25519PublicKey::from_bytes([0; 32]);
1188        let signing_key = Ed25519PublicKey::from_slice(&[0; 32]).expect("");
1189        let mut content: room_key::MegolmV1AesSha2Content = serde_json::from_value(content)
1190            .expect("We should be able to deserialize the m.room_key content");
1191
1192        let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1193            .expect(
1194                "We should be able to create an inbound group session from the room key content",
1195            );
1196
1197        assert!(
1198            session.shared_history,
1199            "The shared history flag should be set as it was set in the m.room_key content"
1200        );
1201
1202        content.shared_history = false;
1203        let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1204            .expect(
1205                "We should be able to create an inbound group session from the room key content",
1206            );
1207
1208        assert!(
1209            !session.shared_history,
1210            "The shared history flag should not be set as it was not set in the m.room_key content"
1211        );
1212    }
1213
1214    #[async_test]
1215    async fn test_shared_history_from_exported_room_key() {
1216        let content = json!({
1217                "algorithm": "m.megolm.v1.aes-sha2",
1218                "room_id": "!room:id",
1219                "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
1220                "session_id": "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0",
1221                "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
1222                "sender_claimed_keys": {
1223                    "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
1224                },
1225                "forwarding_curve25519_key_chain": [],
1226                "org.matrix.msc3061.shared_history": true
1227
1228        });
1229
1230        let mut content: ExportedRoomKey = serde_json::from_value(content)
1231            .expect("We should be able to deserialize the m.room_key content");
1232
1233        let session = InboundGroupSession::from_export(&content).expect(
1234            "We should be able to create an inbound group session from the room key export",
1235        );
1236        assert!(
1237            session.shared_history,
1238            "The shared history flag should be set as it was set in the exported room key"
1239        );
1240
1241        content.shared_history = false;
1242
1243        let session = InboundGroupSession::from_export(&content).expect(
1244            "We should be able to create an inbound group session from the room key export",
1245        );
1246        assert!(
1247            !session.shared_history,
1248            "The shared history flag should not be set as it was not set in the exported room key"
1249        );
1250    }
1251
1252    #[async_test]
1253    async fn test_shared_history_from_backed_up_room_key() {
1254        let content = json!({
1255                "algorithm": "m.megolm.v1.aes-sha2",
1256                "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
1257                "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
1258                "sender_claimed_keys": {
1259                    "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
1260                },
1261                "forwarding_curve25519_key_chain": [],
1262                "org.matrix.msc3061.shared_history": true
1263
1264        });
1265
1266        let session_id = "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0";
1267        let room_id = owned_room_id!("!room:id");
1268        let room_key: BackedUpRoomKey = serde_json::from_value(content)
1269            .expect("We should be able to deserialize the backed up room key");
1270
1271        let room_key =
1272            ExportedRoomKey::from_backed_up_room_key(room_id, session_id.to_owned(), room_key);
1273
1274        let session = InboundGroupSession::from_export(&room_key).expect(
1275            "We should be able to create an inbound group session from the room key export",
1276        );
1277        assert!(
1278            session.shared_history,
1279            "The shared history flag should be set as it was set in the backed up room key"
1280        );
1281    }
1282
1283    #[async_test]
1284    async fn test_shared_history_in_pickle() {
1285        let alice = Account::with_device_id(alice_id(), alice_device_id());
1286        let room_id = room_id!("!test:localhost");
1287
1288        let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1289
1290        inbound.shared_history = true;
1291        let pickle = inbound.pickle().await;
1292
1293        assert!(
1294            pickle.shared_history,
1295            "The set shared history flag should have been copied to the pickle"
1296        );
1297
1298        inbound.shared_history = false;
1299        let pickle = inbound.pickle().await;
1300
1301        assert!(
1302            !pickle.shared_history,
1303            "The unset shared history flag should have been copied to the pickle"
1304        );
1305    }
1306
1307    #[async_test]
1308    async fn test_shared_history_in_export() {
1309        let alice = Account::with_device_id(alice_id(), alice_device_id());
1310        let room_id = room_id!("!test:localhost");
1311
1312        let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1313
1314        inbound.shared_history = true;
1315        let export = inbound.export().await;
1316        assert!(
1317            export.shared_history,
1318            "The set shared history flag should have been copied to the room key export"
1319        );
1320
1321        inbound.shared_history = false;
1322        let export = inbound.export().await;
1323        assert!(
1324            !export.shared_history,
1325            "The unset shared history flag should have been copied to the room key export"
1326        );
1327    }
1328}