matrix_sdk_crypto/olm/group_sessions/
inbound.rs

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