Skip to main content

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