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::{EventEncryptionAlgorithm, events::room_key},
942    };
943
944    fn alice_id() -> &'static UserId {
945        user_id!("@alice:example.org")
946    }
947
948    fn alice_device_id() -> &'static DeviceId {
949        device_id!("ALICEDEVICE")
950    }
951
952    #[async_test]
953    async fn test_pickle_snapshot() {
954        let account = Account::new(alice_id());
955        let room_id = room_id!("!test:localhost");
956        let (_, session) = account.create_group_session_pair_with_defaults(room_id).await;
957
958        let pickle = session.pickle().await;
959
960        with_settings!({prepend_module_to_snapshot => false}, {
961            assert_json_snapshot!(
962                "InboundGroupSession__test_pickle_snapshot__regression",
963                pickle,
964                {
965                    ".pickle.initial_ratchet.inner" => "[ratchet]",
966                    ".pickle.signing_key" => "[signing_key]",
967                    ".sender_key" => "[sender_key]",
968                    ".signing_key.ed25519" => "[ed25519_key]",
969                }
970            );
971        });
972    }
973
974    #[async_test]
975    async fn test_can_deserialise_pickled_session_without_sender_data() {
976        // Given the raw JSON for a picked inbound group session without any
977        // sender_data
978        let pickle = r#"
979        {
980            "pickle": {
981                "initial_ratchet": {
982                    "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
983                               220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
984                               229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
985                               202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
986                               138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
987                               245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
988                               2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
989                               205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
990                               52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
991                    "counter": 0
992                },
993                "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
994                                 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
995                                 149, 43, 38 ],
996                "signing_key_verified": true,
997                "config": {
998                  "version": "V1"
999                }
1000            },
1001            "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
1002            "signing_key": {
1003                "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
1004            },
1005            "room_id": "!test:localhost",
1006            "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
1007            "imported": false,
1008            "backed_up": false,
1009            "history_visibility": "shared",
1010            "algorithm": "m.megolm.v1.aes-sha2"
1011        }
1012        "#;
1013
1014        // When we deserialise it to from JSON
1015        let deserialized = serde_json::from_str(pickle).unwrap();
1016
1017        // And unpickle it
1018        let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
1019
1020        // Then it was parsed correctly
1021        assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
1022
1023        // And we populated the InboundGroupSession's sender_data with a default
1024        // value, with legacy_session set to true.
1025        assert_let!(
1026            SenderData::UnknownDevice { legacy_session, owner_check_failed } =
1027                unpickled.sender_data
1028        );
1029        assert!(legacy_session);
1030        assert!(!owner_check_failed);
1031    }
1032
1033    #[async_test]
1034    async fn test_can_serialise_pickled_session_with_sender_data() {
1035        // Given an InboundGroupSession
1036        let igs = InboundGroupSession::new(
1037            Curve25519PublicKey::from_base64("AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8")
1038                .unwrap(),
1039            Ed25519PublicKey::from_base64("wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww").unwrap(),
1040            room_id!("!test:localhost"),
1041            &create_session_key(),
1042            SenderData::unknown(),
1043            None,
1044            EventEncryptionAlgorithm::MegolmV1AesSha2,
1045            Some(HistoryVisibility::Shared),
1046            false,
1047        )
1048        .unwrap();
1049
1050        // When we pickle it
1051        let pickled = igs.pickle().await;
1052
1053        // And serialise it
1054        let serialised = serde_json::to_string(&pickled).unwrap();
1055
1056        // Then it looks as we expect
1057
1058        // (Break out this list of numbers as otherwise it bothers the json
1059        // macro below)
1060        let expected_inner = vec![
1061            193, 203, 223, 152, 33, 132, 200, 168, 24, 197, 79, 174, 231, 202, 45, 245, 128, 131,
1062            178, 165, 148, 37, 241, 214, 178, 218, 25, 33, 68, 48, 153, 104, 122, 6, 249, 198, 97,
1063            226, 214, 75, 64, 128, 25, 138, 98, 90, 138, 93, 52, 206, 174, 3, 84, 149, 101, 140,
1064            238, 156, 103, 107, 124, 144, 139, 104, 253, 5, 100, 251, 186, 118, 208, 87, 31, 218,
1065            123, 234, 103, 34, 246, 100, 39, 90, 216, 72, 187, 86, 202, 150, 100, 116, 204, 254,
1066            10, 154, 216, 133, 61, 250, 75, 100, 195, 63, 138, 22, 17, 13, 156, 123, 195, 132, 111,
1067            95, 250, 24, 236, 0, 246, 93, 230, 100, 211, 165, 211, 190, 181, 87, 42, 181,
1068        ];
1069        assert_eq!(
1070            serde_json::from_str::<serde_json::Value>(&serialised).unwrap(),
1071            serde_json::json!({
1072                "pickle":{
1073                    "initial_ratchet":{
1074                        "inner": expected_inner,
1075                        "counter":0
1076                    },
1077                    "signing_key":[
1078                        213,161,95,135,114,153,162,127,217,74,64,2,59,143,93,5,190,157,120,
1079                        80,89,8,87,129,115,148,104,144,152,186,178,109
1080                    ],
1081                    "signing_key_verified":true,
1082                    "config":{"version":"V1"}
1083                },
1084                "sender_key":"AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
1085                "signing_key":{"ed25519":"wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"},
1086                "sender_data":{
1087                    "UnknownDevice":{
1088                        "legacy_session":false
1089                    }
1090                },
1091                "forwarder_data":null,
1092                "room_id":"!test:localhost",
1093                "imported":false,
1094                "backed_up":false,
1095                "shared_history":false,
1096                "history_visibility":"shared",
1097                "algorithm":"m.megolm.v1.aes-sha2"
1098            })
1099        );
1100    }
1101
1102    #[async_test]
1103    async fn test_can_deserialise_pickled_session_with_sender_data() {
1104        // Given the raw JSON for a picked inbound group session (including
1105        // sender_data)
1106        let pickle = r#"
1107        {
1108            "pickle": {
1109                "initial_ratchet": {
1110                    "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
1111                               220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
1112                               229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
1113                               202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
1114                               138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
1115                               245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
1116                               2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
1117                               205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
1118                               52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
1119                    "counter": 0
1120                },
1121                "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
1122                                 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
1123                                 149, 43, 38 ],
1124                "signing_key_verified": true,
1125                "config": {
1126                  "version": "V1"
1127                }
1128            },
1129            "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
1130            "signing_key": {
1131                "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
1132            },
1133            "sender_data":{
1134                "UnknownDevice":{
1135                    "legacy_session":false
1136                }
1137            },
1138            "room_id": "!test:localhost",
1139            "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
1140            "imported": false,
1141            "backed_up": false,
1142            "history_visibility": "shared",
1143            "algorithm": "m.megolm.v1.aes-sha2"
1144        }
1145        "#;
1146
1147        // When we deserialise it to from JSON
1148        let deserialized = serde_json::from_str(pickle).unwrap();
1149
1150        // And unpickle it
1151        let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
1152
1153        // Then it was parsed correctly
1154        assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
1155
1156        // And we populated the InboundGroupSession's sender_data with the
1157        // provided values
1158        assert_let!(
1159            SenderData::UnknownDevice { legacy_session, owner_check_failed } =
1160                unpickled.sender_data
1161        );
1162        assert!(!legacy_session);
1163        assert!(!owner_check_failed);
1164    }
1165
1166    #[async_test]
1167    #[allow(deprecated)]
1168    async fn test_session_comparison() {
1169        let alice = Account::with_device_id(alice_id(), alice_device_id());
1170        let room_id = room_id!("!test:localhost");
1171
1172        let (_, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1173
1174        let worse = InboundGroupSession::from_export(&inbound.export_at_index(10).await).unwrap();
1175        let mut copy = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
1176
1177        assert_eq!(inbound.compare(&worse).await, SessionOrdering::Better);
1178        assert_eq!(inbound.compare_ratchet(&worse).await, SessionOrdering::Better);
1179        assert_eq!(worse.compare(&inbound).await, SessionOrdering::Worse);
1180        assert_eq!(worse.compare_ratchet(&inbound).await, SessionOrdering::Worse);
1181        assert_eq!(inbound.compare(&inbound).await, SessionOrdering::Equal);
1182        assert_eq!(inbound.compare_ratchet(&inbound).await, SessionOrdering::Equal);
1183        assert_eq!(inbound.compare(&copy).await, SessionOrdering::Equal);
1184        assert_eq!(inbound.compare_ratchet(&copy).await, SessionOrdering::Equal);
1185
1186        copy.creator_info.curve25519_key =
1187            Curve25519PublicKey::from_base64("XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY")
1188                .unwrap();
1189
1190        assert_eq!(inbound.compare(&copy).await, SessionOrdering::Unconnected);
1191        assert_eq!(inbound.compare_ratchet(&copy).await, SessionOrdering::Unconnected);
1192    }
1193
1194    #[async_test]
1195    #[allow(deprecated)]
1196    async fn test_session_comparison_sender_data() {
1197        let alice = Account::with_device_id(alice_id(), alice_device_id());
1198        let room_id = room_id!("!test:localhost");
1199
1200        let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1201
1202        let sender_data = SenderData::SenderVerified(KnownSenderData {
1203            user_id: alice.user_id().into(),
1204            device_id: Some(alice.device_id().into()),
1205            master_key: alice.identity_keys().ed25519.into(),
1206        });
1207
1208        let mut better = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
1209        better.sender_data = sender_data.clone();
1210
1211        assert_eq!(inbound.compare(&better).await, SessionOrdering::Worse);
1212        assert_eq!(better.compare(&inbound).await, SessionOrdering::Better);
1213
1214        inbound.sender_data = sender_data;
1215        assert_eq!(better.compare(&inbound).await, SessionOrdering::Equal);
1216    }
1217
1218    fn create_session_key() -> SessionKey {
1219        SessionKey::from_base64(
1220            "\
1221            AgAAAADBy9+YIYTIqBjFT67nyi31gIOypZQl8day2hkhRDCZaHoG+cZh4tZLQIAZimJail0\
1222            0zq4DVJVljO6cZ2t8kIto/QVk+7p20Fcf2nvqZyL2ZCda2Ei7VsqWZHTM/gqa2IU9+ktkwz\
1223            +KFhENnHvDhG9f+hjsAPZd5mTTpdO+tVcqtdWhX4dymaJ/2UpAAjuPXQW+nXhQWQhXgXOUa\
1224            JCYurJtvbCbqZGeDMmVIoqukBs2KugNJ6j5WlTPoeFnMl6Guy9uH2iWWxGg8ZgT2xspqVl5\
1225            CwujjC+m7Dh1toVkvu+bAw\
1226            ",
1227        )
1228        .unwrap()
1229    }
1230
1231    fn key_json(stable: bool) -> serde_json::Value {
1232        let shared_history =
1233            if stable { "m.shared_history" } else { "org.matrix.msc3061.shared_history" };
1234
1235        json!({
1236            "algorithm": "m.megolm.v1.aes-sha2",
1237            "room_id": "!Cuyf34gef24t:localhost",
1238            shared_history: true,
1239            "session_id": "ZFD6+OmV7fVCsJ7Gap8UnORH8EnmiAkes8FAvQuCw/I",
1240            "session_key": "AgAAAADNp1EbxXYOGmJtyX4AkD1bvJvAUyPkbIaKxtnGKjv\
1241                            SQ3E/4mnuqdM4vsmNzpO1EeWzz1rDkUpYhYE9kP7sJhgLXi\
1242                            jVv80fMPHfGc49hPdu8A+xnwD4SQiYdFmSWJOIqsxeo/fiH\
1243                            tino//CDQENtcKuEt0I9s0+Kk4YSH310Szse2RQ+vjple31\
1244                            QrCexmqfFJzkR/BJ5ogJHrPBQL0LgsPyglIbMTLg7qygIaY\
1245                            U5Fe2QdKMH7nTZPNIRHh1RaMfHVETAUJBax88EWZBoifk80\
1246                            gdHUwHSgMk77vCc2a5KHKLDA",
1247        })
1248    }
1249
1250    #[async_test]
1251    async fn test_shared_history_from_m_room_key_content_stable() {
1252        let content = key_json(true);
1253
1254        let sender_key = Curve25519PublicKey::from_bytes([0; 32]);
1255        let signing_key = Ed25519PublicKey::from_slice(&[0; 32]).expect("");
1256        let mut content: room_key::MegolmV1AesSha2Content = serde_json::from_value(content)
1257            .expect("We should be able to deserialize the m.room_key content");
1258
1259        let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1260            .expect(
1261                "We should be able to create an inbound group session from the room key content",
1262            );
1263
1264        assert!(
1265            session.shared_history,
1266            "The shared history flag should be set as it was set in the m.room_key content"
1267        );
1268
1269        content.shared_history = false;
1270        let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1271            .expect(
1272                "We should be able to create an inbound group session from the room key content",
1273            );
1274
1275        assert!(
1276            !session.shared_history,
1277            "The shared history flag should not be set as it was not set in the m.room_key content"
1278        );
1279    }
1280
1281    #[async_test]
1282    async fn test_shared_history_from_m_room_key_content_unstable() {
1283        let content = key_json(false);
1284
1285        let sender_key = Curve25519PublicKey::from_bytes([0; 32]);
1286        let signing_key = Ed25519PublicKey::from_slice(&[0; 32]).expect("");
1287        let mut content: room_key::MegolmV1AesSha2Content = serde_json::from_value(content)
1288            .expect("We should be able to deserialize the m.room_key content");
1289
1290        let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1291            .expect(
1292                "We should be able to create an inbound group session from the room key content",
1293            );
1294
1295        assert!(
1296            session.shared_history,
1297            "The shared history flag should be set as it was set in the m.room_key content"
1298        );
1299
1300        content.shared_history = false;
1301        let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1302            .expect(
1303                "We should be able to create an inbound group session from the room key content",
1304            );
1305
1306        assert!(
1307            !session.shared_history,
1308            "The shared history flag should not be set as it was not set in the m.room_key content"
1309        );
1310    }
1311
1312    fn exported_key_json(stable: bool) -> serde_json::Value {
1313        let shared_history =
1314            if stable { "m.shared_history" } else { "org.matrix.msc3061.shared_history" };
1315
1316        json!({
1317            "algorithm": "m.megolm.v1.aes-sha2",
1318            "room_id": "!room:id",
1319            "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
1320            "session_id": "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0",
1321            "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
1322            "sender_claimed_keys": {
1323                "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
1324            },
1325            "forwarding_curve25519_key_chain": [],
1326            shared_history: true
1327        })
1328    }
1329
1330    #[async_test]
1331    async fn test_shared_history_from_exported_room_key_stable() {
1332        let content = exported_key_json(true);
1333
1334        let mut content: ExportedRoomKey = serde_json::from_value(content)
1335            .expect("We should be able to deserialize the m.room_key content");
1336
1337        let session = InboundGroupSession::from_export(&content).expect(
1338            "We should be able to create an inbound group session from the room key export",
1339        );
1340        assert!(
1341            session.shared_history,
1342            "The shared history flag should be set as it was set in the exported room key"
1343        );
1344
1345        content.shared_history = false;
1346
1347        let session = InboundGroupSession::from_export(&content).expect(
1348            "We should be able to create an inbound group session from the room key export",
1349        );
1350        assert!(
1351            !session.shared_history,
1352            "The shared history flag should not be set as it was not set in the exported room key"
1353        );
1354    }
1355
1356    #[async_test]
1357    async fn test_shared_history_from_exported_room_key_unstable() {
1358        let content = exported_key_json(false);
1359
1360        let mut content: ExportedRoomKey = serde_json::from_value(content)
1361            .expect("We should be able to deserialize the m.room_key content");
1362
1363        let session = InboundGroupSession::from_export(&content).expect(
1364            "We should be able to create an inbound group session from the room key export",
1365        );
1366        assert!(
1367            session.shared_history,
1368            "The shared history flag should be set as it was set in the exported room key"
1369        );
1370
1371        content.shared_history = false;
1372
1373        let session = InboundGroupSession::from_export(&content).expect(
1374            "We should be able to create an inbound group session from the room key export",
1375        );
1376        assert!(
1377            !session.shared_history,
1378            "The shared history flag should not be set as it was not set in the exported room key"
1379        );
1380    }
1381
1382    fn backed_up_room_key(stable: bool) -> serde_json::Value {
1383        let shared_history =
1384            if stable { "m.shared_history" } else { "org.matrix.msc3061.shared_history" };
1385
1386        json!({
1387                "algorithm": "m.megolm.v1.aes-sha2",
1388                "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
1389                "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
1390                "sender_claimed_keys": {
1391                    "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
1392                },
1393                "forwarding_curve25519_key_chain": [],
1394                shared_history: true
1395        })
1396    }
1397
1398    #[async_test]
1399    async fn test_shared_history_from_backed_up_room_key_stable() {
1400        let content = backed_up_room_key(true);
1401
1402        let session_id = "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0";
1403        let room_id = owned_room_id!("!room:id");
1404        let room_key: BackedUpRoomKey = serde_json::from_value(content)
1405            .expect("We should be able to deserialize the backed up room key");
1406
1407        let room_key =
1408            ExportedRoomKey::from_backed_up_room_key(room_id, session_id.to_owned(), room_key);
1409
1410        let session = InboundGroupSession::from_export(&room_key).expect(
1411            "We should be able to create an inbound group session from the room key export",
1412        );
1413        assert!(
1414            session.shared_history,
1415            "The shared history flag should be set as it was set in the backed up room key"
1416        );
1417    }
1418
1419    #[async_test]
1420    async fn test_shared_history_from_backed_up_room_key_unstable() {
1421        let content = backed_up_room_key(false);
1422
1423        let session_id = "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0";
1424        let room_id = owned_room_id!("!room:id");
1425        let room_key: BackedUpRoomKey = serde_json::from_value(content)
1426            .expect("We should be able to deserialize the backed up room key");
1427
1428        let room_key =
1429            ExportedRoomKey::from_backed_up_room_key(room_id, session_id.to_owned(), room_key);
1430
1431        let session = InboundGroupSession::from_export(&room_key).expect(
1432            "We should be able to create an inbound group session from the room key export",
1433        );
1434        assert!(
1435            session.shared_history,
1436            "The shared history flag should be set as it was set in the backed up room key"
1437        );
1438    }
1439
1440    #[async_test]
1441    async fn test_shared_history_in_pickle() {
1442        let alice = Account::with_device_id(alice_id(), alice_device_id());
1443        let room_id = room_id!("!test:localhost");
1444
1445        let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1446
1447        inbound.shared_history = true;
1448        let pickle = inbound.pickle().await;
1449
1450        assert!(
1451            pickle.shared_history,
1452            "The set shared history flag should have been copied to the pickle"
1453        );
1454
1455        inbound.shared_history = false;
1456        let pickle = inbound.pickle().await;
1457
1458        assert!(
1459            !pickle.shared_history,
1460            "The unset shared history flag should have been copied to the pickle"
1461        );
1462    }
1463
1464    #[async_test]
1465    async fn test_shared_history_in_export() {
1466        let alice = Account::with_device_id(alice_id(), alice_device_id());
1467        let room_id = room_id!("!test:localhost");
1468
1469        let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1470
1471        inbound.shared_history = true;
1472        let export = inbound.export().await;
1473        assert!(
1474            export.shared_history,
1475            "The set shared history flag should have been copied to the room key export"
1476        );
1477
1478        inbound.shared_history = false;
1479        let export = inbound.export().await;
1480        assert!(
1481            !export.shared_history,
1482            "The unset shared history flag should have been copied to the room key export"
1483        );
1484    }
1485}