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