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, room_history::HistoricRoomKey};
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        serialize_curve_key, EventEncryptionAlgorithm, SigningKeys,
59    },
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    /// Convert the [`InboundGroupSession`] into a
344    /// [`PickledInboundGroupSession`] which can be serialized.
345    pub async fn pickle(&self) -> PickledInboundGroupSession {
346        let pickle = self.inner.lock().await.pickle();
347
348        PickledInboundGroupSession {
349            pickle,
350            sender_key: self.creator_info.curve25519_key,
351            signing_key: (*self.creator_info.signing_keys).clone(),
352            sender_data: self.sender_data.clone(),
353            room_id: self.room_id().to_owned(),
354            imported: self.imported,
355            backed_up: self.backed_up(),
356            history_visibility: self.history_visibility.as_ref().clone(),
357            algorithm: (*self.algorithm).to_owned(),
358            shared_history: self.shared_history,
359        }
360    }
361
362    /// Export this session at the first known message index.
363    ///
364    /// If only a limited part of this session should be exported use
365    /// [`InboundGroupSession::export_at_index()`].
366    pub async fn export(&self) -> ExportedRoomKey {
367        self.export_at_index(self.first_known_index()).await
368    }
369
370    /// Get the sender key that this session was received from.
371    pub fn sender_key(&self) -> Curve25519PublicKey {
372        self.creator_info.curve25519_key
373    }
374
375    /// Has the session been backed up to the server.
376    pub fn backed_up(&self) -> bool {
377        self.backed_up.load(SeqCst)
378    }
379
380    /// Reset the backup state of the inbound group session.
381    pub fn reset_backup_state(&self) {
382        self.backed_up.store(false, SeqCst)
383    }
384
385    /// For testing, allow to manually mark this GroupSession to have been
386    /// backed up
387    pub fn mark_as_backed_up(&self) {
388        self.backed_up.store(true, SeqCst)
389    }
390
391    /// Get the map of signing keys this session was received from.
392    pub fn signing_keys(&self) -> &SigningKeys<DeviceKeyAlgorithm> {
393        &self.creator_info.signing_keys
394    }
395
396    /// Export this session at the given message index.
397    pub async fn export_at_index(&self, message_index: u32) -> ExportedRoomKey {
398        let message_index = std::cmp::max(self.first_known_index(), message_index);
399
400        let session_key =
401            self.inner.lock().await.export_at(message_index).expect("Can't export session");
402
403        ExportedRoomKey {
404            algorithm: self.algorithm().to_owned(),
405            room_id: self.room_id().to_owned(),
406            sender_key: self.creator_info.curve25519_key,
407            session_id: self.session_id().to_owned(),
408            forwarding_curve25519_key_chain: vec![],
409            sender_claimed_keys: (*self.creator_info.signing_keys).clone(),
410            session_key,
411            shared_history: self.shared_history,
412        }
413    }
414
415    /// Restore a Session from a previously pickled string.
416    ///
417    /// Returns the restored group session or a `UnpicklingError` if there
418    /// was an error.
419    ///
420    /// # Arguments
421    ///
422    /// * `pickle` - The pickled version of the `InboundGroupSession`.
423    ///
424    /// * `pickle_mode` - The mode that was used to pickle the session, either
425    ///   an unencrypted mode or an encrypted using passphrase.
426    pub fn from_pickle(pickle: PickledInboundGroupSession) -> Result<Self, PickleError> {
427        let PickledInboundGroupSession {
428            pickle,
429            sender_key,
430            signing_key,
431            sender_data,
432            room_id,
433            imported,
434            backed_up,
435            history_visibility,
436            algorithm,
437            shared_history,
438        } = pickle;
439
440        let session: InnerSession = pickle.into();
441        let first_known_index = session.first_known_index();
442        let session_id = session.session_id();
443
444        Ok(InboundGroupSession {
445            inner: Mutex::new(session).into(),
446            session_id: session_id.into(),
447            creator_info: SessionCreatorInfo {
448                curve25519_key: sender_key,
449                signing_keys: signing_key.into(),
450            },
451            sender_data,
452            history_visibility: history_visibility.into(),
453            first_known_index,
454            room_id,
455            backed_up: AtomicBool::from(backed_up).into(),
456            algorithm: algorithm.into(),
457            imported,
458            shared_history,
459        })
460    }
461
462    /// The room where this session is used in.
463    pub fn room_id(&self) -> &RoomId {
464        &self.room_id
465    }
466
467    /// Returns the unique identifier for this session.
468    pub fn session_id(&self) -> &str {
469        &self.session_id
470    }
471
472    /// The algorithm that this inbound group session is using to decrypt
473    /// events.
474    pub fn algorithm(&self) -> &EventEncryptionAlgorithm {
475        &self.algorithm
476    }
477
478    /// Get the first message index we know how to decrypt.
479    pub fn first_known_index(&self) -> u32 {
480        self.first_known_index
481    }
482
483    /// Has the session been imported from a file or server-side backup? As
484    /// opposed to being directly received as an `m.room_key` event.
485    pub fn has_been_imported(&self) -> bool {
486        self.imported
487    }
488
489    /// Check if the [`InboundGroupSession`] is better than the given other
490    /// [`InboundGroupSession`]
491    pub async fn compare(&self, other: &InboundGroupSession) -> SessionOrdering {
492        // If this is the same object the ordering is the same, we can't compare because
493        // we would deadlock while trying to acquire the same lock twice.
494        if Arc::ptr_eq(&self.inner, &other.inner) {
495            SessionOrdering::Equal
496        } else if self.sender_key() != other.sender_key()
497            || self.signing_keys() != other.signing_keys()
498            || self.algorithm() != other.algorithm()
499            || self.room_id() != other.room_id()
500        {
501            SessionOrdering::Unconnected
502        } else {
503            let mut other_inner = other.inner.lock().await;
504
505            match self.inner.lock().await.compare(&mut other_inner) {
506                SessionOrdering::Equal => {
507                    match self.sender_data.compare_trust_level(&other.sender_data) {
508                        Ordering::Less => SessionOrdering::Worse,
509                        Ordering::Equal => SessionOrdering::Equal,
510                        Ordering::Greater => SessionOrdering::Better,
511                    }
512                }
513                result => result,
514            }
515        }
516    }
517
518    /// Decrypt the given ciphertext.
519    ///
520    /// Returns the decrypted plaintext or an `DecryptionError` if
521    /// decryption failed.
522    ///
523    /// # Arguments
524    ///
525    /// * `message` - The message that should be decrypted.
526    pub(crate) async fn decrypt_helper(
527        &self,
528        message: &MegolmMessage,
529    ) -> Result<DecryptedMessage, DecryptionError> {
530        self.inner.lock().await.decrypt(message)
531    }
532
533    /// Export the inbound group session into a format that can be uploaded to
534    /// the server as a backup.
535    pub async fn to_backup(&self) -> BackedUpRoomKey {
536        self.export().await.into()
537    }
538
539    /// Decrypt an event from a room timeline.
540    ///
541    /// # Arguments
542    ///
543    /// * `event` - The event that should be decrypted.
544    pub async fn decrypt(&self, event: &EncryptedEvent) -> MegolmResult<(JsonObject, u32)> {
545        let decrypted = match &event.content.scheme {
546            RoomEventEncryptionScheme::MegolmV1AesSha2(c) => {
547                self.decrypt_helper(&c.ciphertext).await?
548            }
549            #[cfg(feature = "experimental-algorithms")]
550            RoomEventEncryptionScheme::MegolmV2AesSha2(c) => {
551                self.decrypt_helper(&c.ciphertext).await?
552            }
553            RoomEventEncryptionScheme::Unknown(_) => {
554                return Err(EventError::UnsupportedAlgorithm.into());
555            }
556        };
557
558        let plaintext = String::from_utf8_lossy(&decrypted.plaintext);
559
560        let mut decrypted_object = serde_json::from_str::<JsonObject>(&plaintext)?;
561
562        let server_ts: i64 = event.origin_server_ts.0.into();
563
564        decrypted_object.insert("sender".to_owned(), event.sender.to_string().into());
565        decrypted_object.insert("event_id".to_owned(), event.event_id.to_string().into());
566        decrypted_object.insert("origin_server_ts".to_owned(), server_ts.into());
567
568        let room_id = decrypted_object
569            .get("room_id")
570            .and_then(|r| r.as_str().and_then(|r| RoomId::parse(r).ok()));
571
572        // Check that we have a room id and that the event wasn't forwarded from
573        // another room.
574        if room_id.as_deref() != Some(self.room_id()) {
575            return Err(EventError::MismatchedRoom(self.room_id().to_owned(), room_id).into());
576        }
577
578        decrypted_object.insert(
579            "unsigned".to_owned(),
580            serde_json::to_value(&event.unsigned).unwrap_or_default(),
581        );
582
583        if let Some(decrypted_content) =
584            decrypted_object.get_mut("content").and_then(|c| c.as_object_mut())
585        {
586            if !decrypted_content.contains_key("m.relates_to") {
587                if let Some(relation) = &event.content.relates_to {
588                    decrypted_content.insert("m.relates_to".to_owned(), relation.to_owned());
589                }
590            }
591        }
592
593        Ok((decrypted_object, decrypted.message_index))
594    }
595
596    /// For test only, mark this session as imported.
597    #[cfg(test)]
598    pub(crate) fn mark_as_imported(&mut self) {
599        self.imported = true;
600    }
601
602    /// Return the [`SenderDataType`] of our [`SenderData`]. This is used during
603    /// serialization, to allow us to store the type in a separate queryable
604    /// column/property.
605    pub fn sender_data_type(&self) -> SenderDataType {
606        self.sender_data.to_type()
607    }
608
609    /// Whether this [`InboundGroupSession`] can be shared with users who are
610    /// invited to the room in the future, allowing access to history, as
611    /// defined in [MSC3061].
612    ///
613    /// [MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061
614    pub fn shared_history(&self) -> bool {
615        self.shared_history
616    }
617}
618
619#[cfg(not(tarpaulin_include))]
620impl fmt::Debug for InboundGroupSession {
621    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
622        f.debug_struct("InboundGroupSession").field("session_id", &self.session_id()).finish()
623    }
624}
625
626impl PartialEq for InboundGroupSession {
627    fn eq(&self, other: &Self) -> bool {
628        self.session_id() == other.session_id()
629    }
630}
631
632/// A pickled version of an `InboundGroupSession`.
633///
634/// Holds all the information that needs to be stored in a database to restore
635/// an InboundGroupSession.
636#[derive(Serialize, Deserialize)]
637#[allow(missing_debug_implementations)]
638pub struct PickledInboundGroupSession {
639    /// The pickle string holding the InboundGroupSession.
640    pub pickle: InboundGroupSessionPickle,
641    /// The public Curve25519 key of the account that sent us the session
642    #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
643    pub sender_key: Curve25519PublicKey,
644    /// The public ed25519 key of the account that sent us the session.
645    pub signing_key: SigningKeys<DeviceKeyAlgorithm>,
646    /// Information on the device/sender who sent us this session
647    #[serde(default)]
648    pub sender_data: SenderData,
649    /// The id of the room that the session is used in.
650    pub room_id: OwnedRoomId,
651    /// Flag remembering if the session was directly sent to us by the sender
652    /// or if it was imported.
653    pub imported: bool,
654    /// Flag remembering if the session has been backed up.
655    #[serde(default)]
656    pub backed_up: bool,
657    /// History visibility of the room when the session was created.
658    pub history_visibility: Option<HistoryVisibility>,
659    /// The algorithm of this inbound group session.
660    #[serde(default = "default_algorithm")]
661    pub algorithm: EventEncryptionAlgorithm,
662    /// Whether this [`InboundGroupSession`] can be shared with users who are
663    /// invited to the room in the future, allowing access to history, as
664    /// defined in [MSC3061].
665    ///
666    /// [MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061
667    #[serde(default)]
668    pub shared_history: bool,
669}
670
671fn default_algorithm() -> EventEncryptionAlgorithm {
672    EventEncryptionAlgorithm::MegolmV1AesSha2
673}
674
675impl TryFrom<&ExportedRoomKey> for InboundGroupSession {
676    type Error = SessionCreationError;
677
678    fn try_from(key: &ExportedRoomKey) -> Result<Self, Self::Error> {
679        let ExportedRoomKey {
680            algorithm,
681            room_id,
682            sender_key,
683            session_id,
684            session_key,
685            sender_claimed_keys,
686            forwarding_curve25519_key_chain: _,
687            shared_history,
688        } = key;
689
690        let config = OutboundGroupSession::session_config(algorithm)?;
691        let session = InnerSession::import(session_key, config);
692        let first_known_index = session.first_known_index();
693
694        Ok(InboundGroupSession {
695            inner: Mutex::new(session).into(),
696            session_id: session_id.to_owned().into(),
697            creator_info: SessionCreatorInfo {
698                curve25519_key: *sender_key,
699                signing_keys: sender_claimed_keys.to_owned().into(),
700            },
701            // TODO: In future, exported keys should contain sender data that we can use here.
702            // See https://github.com/matrix-org/matrix-rust-sdk/issues/3548
703            sender_data: SenderData::default(),
704            history_visibility: None.into(),
705            first_known_index,
706            room_id: room_id.to_owned(),
707            imported: true,
708            algorithm: algorithm.to_owned().into(),
709            backed_up: AtomicBool::from(false).into(),
710            shared_history: *shared_history,
711        })
712    }
713}
714
715impl From<&ForwardedMegolmV1AesSha2Content> for InboundGroupSession {
716    fn from(value: &ForwardedMegolmV1AesSha2Content) -> Self {
717        let session = InnerSession::import(&value.session_key, SessionConfig::version_1());
718        let session_id = session.session_id().into();
719        let first_known_index = session.first_known_index();
720
721        InboundGroupSession {
722            inner: Mutex::new(session).into(),
723            session_id,
724            creator_info: SessionCreatorInfo {
725                curve25519_key: value.claimed_sender_key,
726                signing_keys: SigningKeys::from([(
727                    DeviceKeyAlgorithm::Ed25519,
728                    value.claimed_ed25519_key.into(),
729                )])
730                .into(),
731            },
732            // In future, exported keys should contain sender data that we can use here.
733            // See https://github.com/matrix-org/matrix-rust-sdk/issues/3548
734            sender_data: SenderData::default(),
735            history_visibility: None.into(),
736            first_known_index,
737            room_id: value.room_id.to_owned(),
738            imported: true,
739            algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2.into(),
740            backed_up: AtomicBool::from(false).into(),
741            shared_history: false,
742        }
743    }
744}
745
746impl From<&ForwardedMegolmV2AesSha2Content> for InboundGroupSession {
747    fn from(value: &ForwardedMegolmV2AesSha2Content) -> Self {
748        let session = InnerSession::import(&value.session_key, SessionConfig::version_2());
749        let session_id = session.session_id().into();
750        let first_known_index = session.first_known_index();
751
752        InboundGroupSession {
753            inner: Mutex::new(session).into(),
754            session_id,
755            creator_info: SessionCreatorInfo {
756                curve25519_key: value.claimed_sender_key,
757                signing_keys: value.claimed_signing_keys.to_owned().into(),
758            },
759            // In future, exported keys should contain sender data that we can use here.
760            // See https://github.com/matrix-org/matrix-rust-sdk/issues/3548
761            sender_data: SenderData::default(),
762            history_visibility: None.into(),
763            first_known_index,
764            room_id: value.room_id.to_owned(),
765            imported: true,
766            algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2.into(),
767            backed_up: AtomicBool::from(false).into(),
768            shared_history: false,
769        }
770    }
771}
772
773impl TryFrom<&DecryptedForwardedRoomKeyEvent> for InboundGroupSession {
774    type Error = SessionCreationError;
775
776    fn try_from(value: &DecryptedForwardedRoomKeyEvent) -> Result<Self, Self::Error> {
777        match &value.content {
778            ForwardedRoomKeyContent::MegolmV1AesSha2(c) => Ok(Self::from(c.deref())),
779            #[cfg(feature = "experimental-algorithms")]
780            ForwardedRoomKeyContent::MegolmV2AesSha2(c) => Ok(Self::from(c.deref())),
781            ForwardedRoomKeyContent::Unknown(c) => {
782                Err(SessionCreationError::Algorithm(c.algorithm.to_owned()))
783            }
784        }
785    }
786}
787
788#[cfg(test)]
789mod tests {
790    use assert_matches2::assert_let;
791    use insta::assert_json_snapshot;
792    use matrix_sdk_test::async_test;
793    use ruma::{
794        device_id, events::room::history_visibility::HistoryVisibility, owned_room_id, room_id,
795        user_id, DeviceId, UserId,
796    };
797    use serde_json::json;
798    use similar_asserts::assert_eq;
799    use vodozemac::{
800        megolm::{SessionKey, SessionOrdering},
801        Curve25519PublicKey, Ed25519PublicKey,
802    };
803
804    use crate::{
805        olm::{BackedUpRoomKey, ExportedRoomKey, InboundGroupSession, KnownSenderData, SenderData},
806        types::{events::room_key, EventEncryptionAlgorithm},
807        Account,
808    };
809
810    fn alice_id() -> &'static UserId {
811        user_id!("@alice:example.org")
812    }
813
814    fn alice_device_id() -> &'static DeviceId {
815        device_id!("ALICEDEVICE")
816    }
817
818    #[async_test]
819    async fn test_pickle_snapshot() {
820        let account = Account::new(alice_id());
821        let room_id = room_id!("!test:localhost");
822        let (_, session) = account.create_group_session_pair_with_defaults(room_id).await;
823
824        let pickle = session.pickle().await;
825
826        assert_json_snapshot!(pickle, {
827            ".pickle.initial_ratchet.inner" => "[ratchet]",
828            ".pickle.signing_key" => "[signing_key]",
829            ".sender_key" => "[sender_key]",
830            ".signing_key.ed25519" => "[ed25519_key]",
831        });
832    }
833
834    #[async_test]
835    async fn test_can_deserialise_pickled_session_without_sender_data() {
836        // Given the raw JSON for a picked inbound group session without any sender_data
837        let pickle = r#"
838        {
839            "pickle": {
840                "initial_ratchet": {
841                    "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
842                               220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
843                               229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
844                               202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
845                               138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
846                               245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
847                               2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
848                               205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
849                               52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
850                    "counter": 0
851                },
852                "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
853                                 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
854                                 149, 43, 38 ],
855                "signing_key_verified": true,
856                "config": {
857                  "version": "V1"
858                }
859            },
860            "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
861            "signing_key": {
862                "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
863            },
864            "room_id": "!test:localhost",
865            "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
866            "imported": false,
867            "backed_up": false,
868            "history_visibility": "shared",
869            "algorithm": "m.megolm.v1.aes-sha2"
870        }
871        "#;
872
873        // When we deserialise it to from JSON
874        let deserialized = serde_json::from_str(pickle).unwrap();
875
876        // And unpickle it
877        let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
878
879        // Then it was parsed correctly
880        assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
881
882        // And we populated the InboundGroupSession's sender_data with a default value,
883        // with legacy_session set to true.
884        assert_let!(
885            SenderData::UnknownDevice { legacy_session, owner_check_failed } =
886                unpickled.sender_data
887        );
888        assert!(legacy_session);
889        assert!(!owner_check_failed);
890    }
891
892    #[async_test]
893    async fn test_can_serialise_pickled_session_with_sender_data() {
894        // Given an InboundGroupSession
895        let igs = InboundGroupSession::new(
896            Curve25519PublicKey::from_base64("AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8")
897                .unwrap(),
898            Ed25519PublicKey::from_base64("wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww").unwrap(),
899            room_id!("!test:localhost"),
900            &create_session_key(),
901            SenderData::unknown(),
902            EventEncryptionAlgorithm::MegolmV1AesSha2,
903            Some(HistoryVisibility::Shared),
904            false,
905        )
906        .unwrap();
907
908        // When we pickle it
909        let pickled = igs.pickle().await;
910
911        // And serialise it
912        let serialised = serde_json::to_string(&pickled).unwrap();
913
914        // Then it looks as we expect
915
916        // (Break out this list of numbers as otherwise it bothers the json macro below)
917        let expected_inner = vec![
918            193, 203, 223, 152, 33, 132, 200, 168, 24, 197, 79, 174, 231, 202, 45, 245, 128, 131,
919            178, 165, 148, 37, 241, 214, 178, 218, 25, 33, 68, 48, 153, 104, 122, 6, 249, 198, 97,
920            226, 214, 75, 64, 128, 25, 138, 98, 90, 138, 93, 52, 206, 174, 3, 84, 149, 101, 140,
921            238, 156, 103, 107, 124, 144, 139, 104, 253, 5, 100, 251, 186, 118, 208, 87, 31, 218,
922            123, 234, 103, 34, 246, 100, 39, 90, 216, 72, 187, 86, 202, 150, 100, 116, 204, 254,
923            10, 154, 216, 133, 61, 250, 75, 100, 195, 63, 138, 22, 17, 13, 156, 123, 195, 132, 111,
924            95, 250, 24, 236, 0, 246, 93, 230, 100, 211, 165, 211, 190, 181, 87, 42, 181,
925        ];
926        assert_eq!(
927            serde_json::from_str::<serde_json::Value>(&serialised).unwrap(),
928            serde_json::json!({
929                "pickle":{
930                    "initial_ratchet":{
931                        "inner": expected_inner,
932                        "counter":0
933                    },
934                    "signing_key":[
935                        213,161,95,135,114,153,162,127,217,74,64,2,59,143,93,5,190,157,120,
936                        80,89,8,87,129,115,148,104,144,152,186,178,109
937                    ],
938                    "signing_key_verified":true,
939                    "config":{"version":"V1"}
940                },
941                "sender_key":"AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
942                "signing_key":{"ed25519":"wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"},
943                "sender_data":{
944                    "UnknownDevice":{
945                        "legacy_session":false
946                    }
947                },
948                "room_id":"!test:localhost",
949                "imported":false,
950                "backed_up":false,
951                "shared_history":false,
952                "history_visibility":"shared",
953                "algorithm":"m.megolm.v1.aes-sha2"
954            })
955        );
956    }
957
958    #[async_test]
959    async fn test_can_deserialise_pickled_session_with_sender_data() {
960        // Given the raw JSON for a picked inbound group session (including sender_data)
961        let pickle = r#"
962        {
963            "pickle": {
964                "initial_ratchet": {
965                    "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
966                               220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
967                               229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
968                               202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
969                               138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
970                               245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
971                               2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
972                               205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
973                               52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
974                    "counter": 0
975                },
976                "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
977                                 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
978                                 149, 43, 38 ],
979                "signing_key_verified": true,
980                "config": {
981                  "version": "V1"
982                }
983            },
984            "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
985            "signing_key": {
986                "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
987            },
988            "sender_data":{
989                "UnknownDevice":{
990                    "legacy_session":false
991                }
992            },
993            "room_id": "!test:localhost",
994            "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
995            "imported": false,
996            "backed_up": false,
997            "history_visibility": "shared",
998            "algorithm": "m.megolm.v1.aes-sha2"
999        }
1000        "#;
1001
1002        // When we deserialise it to from JSON
1003        let deserialized = serde_json::from_str(pickle).unwrap();
1004
1005        // And unpickle it
1006        let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
1007
1008        // Then it was parsed correctly
1009        assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
1010
1011        // And we populated the InboundGroupSession's sender_data with the provided
1012        // values
1013        assert_let!(
1014            SenderData::UnknownDevice { legacy_session, owner_check_failed } =
1015                unpickled.sender_data
1016        );
1017        assert!(!legacy_session);
1018        assert!(!owner_check_failed);
1019    }
1020
1021    #[async_test]
1022    async fn test_session_comparison() {
1023        let alice = Account::with_device_id(alice_id(), alice_device_id());
1024        let room_id = room_id!("!test:localhost");
1025
1026        let (_, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1027
1028        let worse = InboundGroupSession::from_export(&inbound.export_at_index(10).await).unwrap();
1029        let mut copy = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
1030
1031        assert_eq!(inbound.compare(&worse).await, SessionOrdering::Better);
1032        assert_eq!(worse.compare(&inbound).await, SessionOrdering::Worse);
1033        assert_eq!(inbound.compare(&inbound).await, SessionOrdering::Equal);
1034        assert_eq!(inbound.compare(&copy).await, SessionOrdering::Equal);
1035
1036        copy.creator_info.curve25519_key =
1037            Curve25519PublicKey::from_base64("XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY")
1038                .unwrap();
1039
1040        assert_eq!(inbound.compare(&copy).await, SessionOrdering::Unconnected);
1041    }
1042
1043    #[async_test]
1044    async fn test_session_comparison_sender_data() {
1045        let alice = Account::with_device_id(alice_id(), alice_device_id());
1046        let room_id = room_id!("!test:localhost");
1047
1048        let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1049
1050        let sender_data = SenderData::SenderVerified(KnownSenderData {
1051            user_id: alice.user_id().into(),
1052            device_id: Some(alice.device_id().into()),
1053            master_key: alice.identity_keys().ed25519.into(),
1054        });
1055
1056        let mut better = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
1057        better.sender_data = sender_data.clone();
1058
1059        assert_eq!(inbound.compare(&better).await, SessionOrdering::Worse);
1060        assert_eq!(better.compare(&inbound).await, SessionOrdering::Better);
1061
1062        inbound.sender_data = sender_data;
1063        assert_eq!(better.compare(&inbound).await, SessionOrdering::Equal);
1064    }
1065
1066    fn create_session_key() -> SessionKey {
1067        SessionKey::from_base64(
1068            "\
1069            AgAAAADBy9+YIYTIqBjFT67nyi31gIOypZQl8day2hkhRDCZaHoG+cZh4tZLQIAZimJail0\
1070            0zq4DVJVljO6cZ2t8kIto/QVk+7p20Fcf2nvqZyL2ZCda2Ei7VsqWZHTM/gqa2IU9+ktkwz\
1071            +KFhENnHvDhG9f+hjsAPZd5mTTpdO+tVcqtdWhX4dymaJ/2UpAAjuPXQW+nXhQWQhXgXOUa\
1072            JCYurJtvbCbqZGeDMmVIoqukBs2KugNJ6j5WlTPoeFnMl6Guy9uH2iWWxGg8ZgT2xspqVl5\
1073            CwujjC+m7Dh1toVkvu+bAw\
1074            ",
1075        )
1076        .unwrap()
1077    }
1078
1079    #[async_test]
1080    async fn test_shared_history_from_m_room_key_content() {
1081        let content = json!({
1082            "algorithm": "m.megolm.v1.aes-sha2",
1083            "room_id": "!Cuyf34gef24t:localhost",
1084            "org.matrix.msc3061.shared_history": true,
1085            "session_id": "ZFD6+OmV7fVCsJ7Gap8UnORH8EnmiAkes8FAvQuCw/I",
1086            "session_key": "AgAAAADNp1EbxXYOGmJtyX4AkD1bvJvAUyPkbIaKxtnGKjv\
1087                            SQ3E/4mnuqdM4vsmNzpO1EeWzz1rDkUpYhYE9kP7sJhgLXi\
1088                            jVv80fMPHfGc49hPdu8A+xnwD4SQiYdFmSWJOIqsxeo/fiH\
1089                            tino//CDQENtcKuEt0I9s0+Kk4YSH310Szse2RQ+vjple31\
1090                            QrCexmqfFJzkR/BJ5ogJHrPBQL0LgsPyglIbMTLg7qygIaY\
1091                            U5Fe2QdKMH7nTZPNIRHh1RaMfHVETAUJBax88EWZBoifk80\
1092                            gdHUwHSgMk77vCc2a5KHKLDA",
1093        });
1094
1095        let sender_key = Curve25519PublicKey::from_bytes([0; 32]);
1096        let signing_key = Ed25519PublicKey::from_slice(&[0; 32]).expect("");
1097        let mut content: room_key::MegolmV1AesSha2Content = serde_json::from_value(content)
1098            .expect("We should be able to deserialize the m.room_key content");
1099
1100        let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1101            .expect(
1102                "We should be able to create an inbound group session from the room key content",
1103            );
1104
1105        assert!(
1106            session.shared_history,
1107            "The shared history flag should be set as it was set in the m.room_key content"
1108        );
1109
1110        content.shared_history = false;
1111        let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1112            .expect(
1113                "We should be able to create an inbound group session from the room key content",
1114            );
1115
1116        assert!(
1117            !session.shared_history,
1118            "The shared history flag should not be set as it was not set in the m.room_key content"
1119        );
1120    }
1121
1122    #[async_test]
1123    async fn test_shared_history_from_exported_room_key() {
1124        let content = json!({
1125                "algorithm": "m.megolm.v1.aes-sha2",
1126                "room_id": "!room:id",
1127                "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
1128                "session_id": "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0",
1129                "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
1130                "sender_claimed_keys": {
1131                    "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
1132                },
1133                "forwarding_curve25519_key_chain": [],
1134                "org.matrix.msc3061.shared_history": true
1135
1136        });
1137
1138        let mut content: ExportedRoomKey = serde_json::from_value(content)
1139            .expect("We should be able to deserialize the m.room_key content");
1140
1141        let session = InboundGroupSession::from_export(&content).expect(
1142            "We should be able to create an inbound group session from the room key export",
1143        );
1144        assert!(
1145            session.shared_history,
1146            "The shared history flag should be set as it was set in the exported room key"
1147        );
1148
1149        content.shared_history = false;
1150
1151        let session = InboundGroupSession::from_export(&content).expect(
1152            "We should be able to create an inbound group session from the room key export",
1153        );
1154        assert!(
1155            !session.shared_history,
1156            "The shared history flag should not be set as it was not set in the exported room key"
1157        );
1158    }
1159
1160    #[async_test]
1161    async fn test_shared_history_from_backed_up_room_key() {
1162        let content = json!({
1163                "algorithm": "m.megolm.v1.aes-sha2",
1164                "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
1165                "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
1166                "sender_claimed_keys": {
1167                    "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
1168                },
1169                "forwarding_curve25519_key_chain": [],
1170                "org.matrix.msc3061.shared_history": true
1171
1172        });
1173
1174        let session_id = "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0";
1175        let room_id = owned_room_id!("!room:id");
1176        let room_key: BackedUpRoomKey = serde_json::from_value(content)
1177            .expect("We should be able to deserialize the backed up room key");
1178
1179        let room_key =
1180            ExportedRoomKey::from_backed_up_room_key(room_id, session_id.to_owned(), room_key);
1181
1182        let session = InboundGroupSession::from_export(&room_key).expect(
1183            "We should be able to create an inbound group session from the room key export",
1184        );
1185        assert!(
1186            session.shared_history,
1187            "The shared history flag should be set as it was set in the backed up room key"
1188        );
1189    }
1190
1191    #[async_test]
1192    async fn test_shared_history_in_pickle() {
1193        let alice = Account::with_device_id(alice_id(), alice_device_id());
1194        let room_id = room_id!("!test:localhost");
1195
1196        let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1197
1198        inbound.shared_history = true;
1199        let pickle = inbound.pickle().await;
1200
1201        assert!(
1202            pickle.shared_history,
1203            "The set shared history flag should have been copied to the pickle"
1204        );
1205
1206        inbound.shared_history = false;
1207        let pickle = inbound.pickle().await;
1208
1209        assert!(
1210            !pickle.shared_history,
1211            "The unset shared history flag should have been copied to the pickle"
1212        );
1213    }
1214
1215    #[async_test]
1216    async fn test_shared_history_in_export() {
1217        let alice = Account::with_device_id(alice_id(), alice_device_id());
1218        let room_id = room_id!("!test:localhost");
1219
1220        let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1221
1222        inbound.shared_history = true;
1223        let export = inbound.export().await;
1224        assert!(
1225            export.shared_history,
1226            "The set shared history flag should have been copied to the room key export"
1227        );
1228
1229        inbound.shared_history = false;
1230        let export = inbound.export().await;
1231        assert!(
1232            !export.shared_history,
1233            "The unset shared history flag should have been copied to the room key export"
1234        );
1235    }
1236}