matrix_sdk_crypto/olm/group_sessions/
inbound.rs

1// Copyright 2020 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{
16    cmp::Ordering,
17    fmt,
18    ops::Deref,
19    sync::{
20        atomic::{AtomicBool, Ordering::SeqCst},
21        Arc,
22    },
23};
24
25use ruma::{
26    events::room::history_visibility::HistoryVisibility, serde::JsonObject, DeviceKeyAlgorithm,
27    OwnedRoomId, RoomId,
28};
29use serde::{Deserialize, Serialize};
30use tokio::sync::Mutex;
31use vodozemac::{
32    megolm::{
33        DecryptedMessage, DecryptionError, InboundGroupSession as InnerSession,
34        InboundGroupSessionPickle, MegolmMessage, SessionConfig, SessionOrdering,
35    },
36    Curve25519PublicKey, Ed25519PublicKey, PickleError,
37};
38
39use super::{
40    BackedUpRoomKey, ExportedRoomKey, OutboundGroupSession, SenderData, SenderDataType,
41    SessionCreationError, SessionKey,
42};
43use crate::{
44    error::{EventError, MegolmResult},
45    types::{
46        deserialize_curve_key,
47        events::{
48            forwarded_room_key::{
49                ForwardedMegolmV1AesSha2Content, ForwardedMegolmV2AesSha2Content,
50                ForwardedRoomKeyContent,
51            },
52            olm_v1::DecryptedForwardedRoomKeyEvent,
53            room::encrypted::{EncryptedEvent, RoomEventEncryptionScheme},
54            room_key,
55        },
56        serialize_curve_key, EventEncryptionAlgorithm, SigningKeys,
57    },
58};
59
60// TODO: add creation times to the inbound group sessions so we can export
61// sessions that were created between some time period, this should only be set
62// for non-imported sessions.
63
64/// Information about the creator of an inbound group session.
65#[derive(Clone)]
66pub(crate) struct SessionCreatorInfo {
67    /// The Curve25519 identity key of the session creator.
68    ///
69    /// If the session was received directly from its creator device through an
70    /// `m.room_key` event (and therefore, session sender == session creator),
71    /// this key equals the Curve25519 device identity key of that device. Since
72    /// this key is one of three keys used to establish the Olm session through
73    /// which encrypted to-device messages (including `m.room_key`) are sent,
74    /// this constitutes a proof that this inbound group session is owned by
75    /// that particular Curve25519 key.
76    ///
77    /// However, if the session was simply forwarded to us in an
78    /// `m.forwarded_room_key` event (in which case sender != creator), this key
79    /// is just a *claim* made by the session sender of what the actual creator
80    /// device is.
81    pub curve25519_key: Curve25519PublicKey,
82
83    /// A mapping of DeviceKeyAlgorithm to the public signing keys of the
84    /// [`Device`] that sent us the session.
85    ///
86    /// If the session was received directly from the creator via an
87    /// `m.room_key` event, this map is taken from the plaintext value of
88    /// the decrypted Olm event, and is a copy of the
89    /// [`DecryptedOlmV1Event::keys`] field as defined in the [spec].
90    ///
91    /// If the session was forwarded to us using an `m.forwarded_room_key`, this
92    /// map is a copy of the claimed Ed25519 key from the content of the
93    /// event.
94    ///
95    /// [spec]: https://spec.matrix.org/unstable/client-server-api/#molmv1curve25519-aes-sha2
96    pub signing_keys: Arc<SigningKeys<DeviceKeyAlgorithm>>,
97}
98
99/// A structure representing an inbound group session.
100///
101/// Inbound group sessions, also known as "room keys", are used to facilitate
102/// the exchange of room messages among a group of participants. The inbound
103/// variant of the group session is used to decrypt the room messages.
104///
105/// This struct wraps the [vodozemac] type of the same name, and adds additional
106/// Matrix-specific data to it. Additionally, the wrapper ensures thread-safe
107/// access of the vodozemac type.
108///
109/// [vodozemac]: https://matrix-org.github.io/vodozemac/vodozemac/index.html
110#[derive(Clone)]
111pub struct InboundGroupSession {
112    inner: Arc<Mutex<InnerSession>>,
113
114    /// A copy of [`InnerSession::session_id`] to avoid having to acquire a lock
115    /// to get to the session ID.
116    session_id: Arc<str>,
117
118    /// A copy of [`InnerSession::first_known_index`] to avoid having to acquire
119    /// a lock to get to the first known index.
120    first_known_index: u32,
121
122    /// Information about the creator of the [`InboundGroupSession`] ("room
123    /// key"). The trustworthiness of the information in this field depends
124    /// on how the session was received.
125    pub(crate) creator_info: SessionCreatorInfo,
126
127    /// Information about the sender of this session and how much we trust that
128    /// information. Holds the information we have about the device that created
129    /// the session, or, if we can use that device information to find the
130    /// sender's cross-signing identity, holds the user ID and cross-signing
131    /// key.
132    pub sender_data: SenderData,
133
134    /// The Room this GroupSession belongs to
135    pub room_id: OwnedRoomId,
136
137    /// A flag recording whether the `InboundGroupSession` was received directly
138    /// as a `m.room_key` event or indirectly via a forward or file import.
139    ///
140    /// If the session is considered to be imported, the information contained
141    /// in the `InboundGroupSession::creator_info` field is not proven to be
142    /// correct.
143    imported: bool,
144
145    /// The messaging algorithm of this [`InboundGroupSession`] as defined by
146    /// the [spec]. Will be one of the `m.megolm.*` algorithms.
147    ///
148    /// [spec]: https://spec.matrix.org/unstable/client-server-api/#messaging-algorithms
149    algorithm: Arc<EventEncryptionAlgorithm>,
150
151    /// The history visibility of the room at the time when the room key was
152    /// created.
153    history_visibility: Arc<Option<HistoryVisibility>>,
154
155    /// Was this room key backed up to the server.
156    backed_up: Arc<AtomicBool>,
157}
158
159impl InboundGroupSession {
160    /// Create a new inbound group session for the given room.
161    ///
162    /// These sessions are used to decrypt room messages.
163    ///
164    /// # Arguments
165    ///
166    /// * `sender_key` - The public Curve25519 key of the account that sent us
167    ///   the session.
168    ///
169    /// * `signing_key` - The public Ed25519 key of the account that sent us the
170    ///   session.
171    ///
172    /// * `room_id` - The id of the room that the session is used in.
173    ///
174    /// * `session_key` - The private session key that is used to decrypt
175    ///   messages.
176    ///
177    /// * `sender_data` - Information about the sender of the to-device message
178    ///   that established this session.
179    pub fn new(
180        sender_key: Curve25519PublicKey,
181        signing_key: Ed25519PublicKey,
182        room_id: &RoomId,
183        session_key: &SessionKey,
184        sender_data: SenderData,
185        encryption_algorithm: EventEncryptionAlgorithm,
186        history_visibility: Option<HistoryVisibility>,
187    ) -> Result<Self, SessionCreationError> {
188        let config = OutboundGroupSession::session_config(&encryption_algorithm)?;
189
190        let session = InnerSession::new(session_key, config);
191        let session_id = session.session_id();
192        let first_known_index = session.first_known_index();
193
194        let mut keys = SigningKeys::new();
195        keys.insert(DeviceKeyAlgorithm::Ed25519, signing_key.into());
196
197        Ok(InboundGroupSession {
198            inner: Arc::new(Mutex::new(session)),
199            history_visibility: history_visibility.into(),
200            session_id: session_id.into(),
201            first_known_index,
202            creator_info: SessionCreatorInfo {
203                curve25519_key: sender_key,
204                signing_keys: keys.into(),
205            },
206            sender_data,
207            room_id: room_id.into(),
208            imported: false,
209            algorithm: encryption_algorithm.into(),
210            backed_up: AtomicBool::new(false).into(),
211        })
212    }
213
214    /// Create a new [`InboundGroupSession`] from a `m.room_key` event with an
215    /// `m.megolm.v1.aes-sha2` content.
216    ///
217    /// The `m.room_key` event **must** have been encrypted using the
218    /// `m.olm.v1.curve25519-aes-sha2` algorithm and the `sender_key` **must**
219    /// be the long-term [`Curve25519PublicKey`] that was used to establish
220    /// the 1-to-1 Olm session.
221    ///
222    /// The `signing_key` **must** be the [`Ed25519PublicKey`] contained in the
223    /// `keys` field of the [decrypted payload].
224    ///
225    /// [decrypted payload]: https://spec.matrix.org/unstable/client-server-api/#molmv1curve25519-aes-sha2
226    pub fn from_room_key_content(
227        sender_key: Curve25519PublicKey,
228        signing_key: Ed25519PublicKey,
229        content: &room_key::MegolmV1AesSha2Content,
230    ) -> Result<Self, SessionCreationError> {
231        let room_key::MegolmV1AesSha2Content { room_id, session_id: _, session_key, .. } = content;
232
233        Self::new(
234            sender_key,
235            signing_key,
236            room_id,
237            session_key,
238            SenderData::unknown(),
239            EventEncryptionAlgorithm::MegolmV1AesSha2,
240            None,
241        )
242    }
243
244    /// Create a new [`InboundGroupSession`] from an exported version of the
245    /// group session.
246    ///
247    /// Most notably this can be called with an [`ExportedRoomKey`] from a
248    /// previous [`InboundGroupSession::export()`] call.
249    pub fn from_export(exported_session: &ExportedRoomKey) -> Result<Self, SessionCreationError> {
250        Self::try_from(exported_session)
251    }
252
253    /// Convert the [`InboundGroupSession`] into a
254    /// [`PickledInboundGroupSession`] which can be serialized.
255    pub async fn pickle(&self) -> PickledInboundGroupSession {
256        let pickle = self.inner.lock().await.pickle();
257
258        PickledInboundGroupSession {
259            pickle,
260            sender_key: self.creator_info.curve25519_key,
261            signing_key: (*self.creator_info.signing_keys).clone(),
262            sender_data: self.sender_data.clone(),
263            room_id: self.room_id().to_owned(),
264            imported: self.imported,
265            backed_up: self.backed_up(),
266            history_visibility: self.history_visibility.as_ref().clone(),
267            algorithm: (*self.algorithm).to_owned(),
268        }
269    }
270
271    /// Export this session at the first known message index.
272    ///
273    /// If only a limited part of this session should be exported use
274    /// [`InboundGroupSession::export_at_index()`].
275    pub async fn export(&self) -> ExportedRoomKey {
276        self.export_at_index(self.first_known_index()).await
277    }
278
279    /// Get the sender key that this session was received from.
280    pub fn sender_key(&self) -> Curve25519PublicKey {
281        self.creator_info.curve25519_key
282    }
283
284    /// Has the session been backed up to the server.
285    pub fn backed_up(&self) -> bool {
286        self.backed_up.load(SeqCst)
287    }
288
289    /// Reset the backup state of the inbound group session.
290    pub fn reset_backup_state(&self) {
291        self.backed_up.store(false, SeqCst)
292    }
293
294    /// For testing, allow to manually mark this GroupSession to have been
295    /// backed up
296    pub fn mark_as_backed_up(&self) {
297        self.backed_up.store(true, SeqCst)
298    }
299
300    /// Get the map of signing keys this session was received from.
301    pub fn signing_keys(&self) -> &SigningKeys<DeviceKeyAlgorithm> {
302        &self.creator_info.signing_keys
303    }
304
305    /// Export this session at the given message index.
306    pub async fn export_at_index(&self, message_index: u32) -> ExportedRoomKey {
307        let message_index = std::cmp::max(self.first_known_index(), message_index);
308
309        let session_key =
310            self.inner.lock().await.export_at(message_index).expect("Can't export session");
311
312        ExportedRoomKey {
313            algorithm: self.algorithm().to_owned(),
314            room_id: self.room_id().to_owned(),
315            sender_key: self.creator_info.curve25519_key,
316            session_id: self.session_id().to_owned(),
317            forwarding_curve25519_key_chain: vec![],
318            sender_claimed_keys: (*self.creator_info.signing_keys).clone(),
319            session_key,
320        }
321    }
322
323    /// Restore a Session from a previously pickled string.
324    ///
325    /// Returns the restored group session or a `UnpicklingError` if there
326    /// was an error.
327    ///
328    /// # Arguments
329    ///
330    /// * `pickle` - The pickled version of the `InboundGroupSession`.
331    ///
332    /// * `pickle_mode` - The mode that was used to pickle the session, either
333    ///   an unencrypted mode or an encrypted using passphrase.
334    pub fn from_pickle(pickle: PickledInboundGroupSession) -> Result<Self, PickleError> {
335        let session: InnerSession = pickle.pickle.into();
336        let first_known_index = session.first_known_index();
337        let session_id = session.session_id();
338
339        Ok(InboundGroupSession {
340            inner: Mutex::new(session).into(),
341            session_id: session_id.into(),
342            creator_info: SessionCreatorInfo {
343                curve25519_key: pickle.sender_key,
344                signing_keys: pickle.signing_key.into(),
345            },
346            sender_data: pickle.sender_data,
347            history_visibility: pickle.history_visibility.into(),
348            first_known_index,
349            room_id: (*pickle.room_id).into(),
350            backed_up: AtomicBool::from(pickle.backed_up).into(),
351            algorithm: pickle.algorithm.into(),
352            imported: pickle.imported,
353        })
354    }
355
356    /// The room where this session is used in.
357    pub fn room_id(&self) -> &RoomId {
358        &self.room_id
359    }
360
361    /// Returns the unique identifier for this session.
362    pub fn session_id(&self) -> &str {
363        &self.session_id
364    }
365
366    /// The algorithm that this inbound group session is using to decrypt
367    /// events.
368    pub fn algorithm(&self) -> &EventEncryptionAlgorithm {
369        &self.algorithm
370    }
371
372    /// Get the first message index we know how to decrypt.
373    pub fn first_known_index(&self) -> u32 {
374        self.first_known_index
375    }
376
377    /// Has the session been imported from a file or server-side backup? As
378    /// opposed to being directly received as an `m.room_key` event.
379    pub fn has_been_imported(&self) -> bool {
380        self.imported
381    }
382
383    /// Check if the [`InboundGroupSession`] is better than the given other
384    /// [`InboundGroupSession`]
385    pub async fn compare(&self, other: &InboundGroupSession) -> SessionOrdering {
386        // If this is the same object the ordering is the same, we can't compare because
387        // we would deadlock while trying to acquire the same lock twice.
388        if Arc::ptr_eq(&self.inner, &other.inner) {
389            SessionOrdering::Equal
390        } else if self.sender_key() != other.sender_key()
391            || self.signing_keys() != other.signing_keys()
392            || self.algorithm() != other.algorithm()
393            || self.room_id() != other.room_id()
394        {
395            SessionOrdering::Unconnected
396        } else {
397            let mut other_inner = other.inner.lock().await;
398
399            match self.inner.lock().await.compare(&mut other_inner) {
400                SessionOrdering::Equal => {
401                    match self.sender_data.compare_trust_level(&other.sender_data) {
402                        Ordering::Less => SessionOrdering::Worse,
403                        Ordering::Equal => SessionOrdering::Equal,
404                        Ordering::Greater => SessionOrdering::Better,
405                    }
406                }
407                result => result,
408            }
409        }
410    }
411
412    /// Decrypt the given ciphertext.
413    ///
414    /// Returns the decrypted plaintext or an `DecryptionError` if
415    /// decryption failed.
416    ///
417    /// # Arguments
418    ///
419    /// * `message` - The message that should be decrypted.
420    pub(crate) async fn decrypt_helper(
421        &self,
422        message: &MegolmMessage,
423    ) -> Result<DecryptedMessage, DecryptionError> {
424        self.inner.lock().await.decrypt(message)
425    }
426
427    /// Export the inbound group session into a format that can be uploaded to
428    /// the server as a backup.
429    pub async fn to_backup(&self) -> BackedUpRoomKey {
430        self.export().await.into()
431    }
432
433    /// Decrypt an event from a room timeline.
434    ///
435    /// # Arguments
436    ///
437    /// * `event` - The event that should be decrypted.
438    pub async fn decrypt(&self, event: &EncryptedEvent) -> MegolmResult<(JsonObject, u32)> {
439        let decrypted = match &event.content.scheme {
440            RoomEventEncryptionScheme::MegolmV1AesSha2(c) => {
441                self.decrypt_helper(&c.ciphertext).await?
442            }
443            #[cfg(feature = "experimental-algorithms")]
444            RoomEventEncryptionScheme::MegolmV2AesSha2(c) => {
445                self.decrypt_helper(&c.ciphertext).await?
446            }
447            RoomEventEncryptionScheme::Unknown(_) => {
448                return Err(EventError::UnsupportedAlgorithm.into());
449            }
450        };
451
452        let plaintext = String::from_utf8_lossy(&decrypted.plaintext);
453
454        let mut decrypted_object = serde_json::from_str::<JsonObject>(&plaintext)?;
455
456        let server_ts: i64 = event.origin_server_ts.0.into();
457
458        decrypted_object.insert("sender".to_owned(), event.sender.to_string().into());
459        decrypted_object.insert("event_id".to_owned(), event.event_id.to_string().into());
460        decrypted_object.insert("origin_server_ts".to_owned(), server_ts.into());
461
462        let room_id = decrypted_object
463            .get("room_id")
464            .and_then(|r| r.as_str().and_then(|r| RoomId::parse(r).ok()));
465
466        // Check that we have a room id and that the event wasn't forwarded from
467        // another room.
468        if room_id.as_deref() != Some(self.room_id()) {
469            return Err(EventError::MismatchedRoom(self.room_id().to_owned(), room_id).into());
470        }
471
472        decrypted_object.insert(
473            "unsigned".to_owned(),
474            serde_json::to_value(&event.unsigned).unwrap_or_default(),
475        );
476
477        if let Some(decrypted_content) =
478            decrypted_object.get_mut("content").and_then(|c| c.as_object_mut())
479        {
480            if !decrypted_content.contains_key("m.relates_to") {
481                if let Some(relation) = &event.content.relates_to {
482                    decrypted_content.insert("m.relates_to".to_owned(), relation.to_owned());
483                }
484            }
485        }
486
487        Ok((decrypted_object, decrypted.message_index))
488    }
489
490    /// For test only, mark this session as imported.
491    #[cfg(test)]
492    pub(crate) fn mark_as_imported(&mut self) {
493        self.imported = true;
494    }
495
496    /// Return the [`SenderDataType`] of our [`SenderData`]. This is used during
497    /// serialization, to allow us to store the type in a separate queryable
498    /// column/property.
499    pub fn sender_data_type(&self) -> SenderDataType {
500        self.sender_data.to_type()
501    }
502}
503
504#[cfg(not(tarpaulin_include))]
505impl fmt::Debug for InboundGroupSession {
506    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
507        f.debug_struct("InboundGroupSession").field("session_id", &self.session_id()).finish()
508    }
509}
510
511impl PartialEq for InboundGroupSession {
512    fn eq(&self, other: &Self) -> bool {
513        self.session_id() == other.session_id()
514    }
515}
516
517/// A pickled version of an `InboundGroupSession`.
518///
519/// Holds all the information that needs to be stored in a database to restore
520/// an InboundGroupSession.
521#[derive(Serialize, Deserialize)]
522#[allow(missing_debug_implementations)]
523pub struct PickledInboundGroupSession {
524    /// The pickle string holding the InboundGroupSession.
525    pub pickle: InboundGroupSessionPickle,
526    /// The public Curve25519 key of the account that sent us the session
527    #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
528    pub sender_key: Curve25519PublicKey,
529    /// The public ed25519 key of the account that sent us the session.
530    pub signing_key: SigningKeys<DeviceKeyAlgorithm>,
531    /// Information on the device/sender who sent us this session
532    #[serde(default)]
533    pub sender_data: SenderData,
534    /// The id of the room that the session is used in.
535    pub room_id: OwnedRoomId,
536    /// Flag remembering if the session was directly sent to us by the sender
537    /// or if it was imported.
538    pub imported: bool,
539    /// Flag remembering if the session has been backed up.
540    #[serde(default)]
541    pub backed_up: bool,
542    /// History visibility of the room when the session was created.
543    pub history_visibility: Option<HistoryVisibility>,
544    /// The algorithm of this inbound group session.
545    #[serde(default = "default_algorithm")]
546    pub algorithm: EventEncryptionAlgorithm,
547}
548
549fn default_algorithm() -> EventEncryptionAlgorithm {
550    EventEncryptionAlgorithm::MegolmV1AesSha2
551}
552
553impl TryFrom<&ExportedRoomKey> for InboundGroupSession {
554    type Error = SessionCreationError;
555
556    fn try_from(key: &ExportedRoomKey) -> Result<Self, Self::Error> {
557        let config = OutboundGroupSession::session_config(&key.algorithm)?;
558        let session = InnerSession::import(&key.session_key, config);
559        let first_known_index = session.first_known_index();
560
561        Ok(InboundGroupSession {
562            inner: Mutex::new(session).into(),
563            session_id: key.session_id.to_owned().into(),
564            creator_info: SessionCreatorInfo {
565                curve25519_key: key.sender_key,
566                signing_keys: key.sender_claimed_keys.to_owned().into(),
567            },
568            // TODO: In future, exported keys should contain sender data that we can use here.
569            // See https://github.com/matrix-org/matrix-rust-sdk/issues/3548
570            sender_data: SenderData::default(),
571            history_visibility: None.into(),
572            first_known_index,
573            room_id: key.room_id.to_owned(),
574            imported: true,
575            algorithm: key.algorithm.to_owned().into(),
576            backed_up: AtomicBool::from(false).into(),
577        })
578    }
579}
580
581impl From<&ForwardedMegolmV1AesSha2Content> for InboundGroupSession {
582    fn from(value: &ForwardedMegolmV1AesSha2Content) -> Self {
583        let session = InnerSession::import(&value.session_key, SessionConfig::version_1());
584        let session_id = session.session_id().into();
585        let first_known_index = session.first_known_index();
586
587        InboundGroupSession {
588            inner: Mutex::new(session).into(),
589            session_id,
590            creator_info: SessionCreatorInfo {
591                curve25519_key: value.claimed_sender_key,
592                signing_keys: SigningKeys::from([(
593                    DeviceKeyAlgorithm::Ed25519,
594                    value.claimed_ed25519_key.into(),
595                )])
596                .into(),
597            },
598            // In future, exported keys should contain sender data that we can use here.
599            // See https://github.com/matrix-org/matrix-rust-sdk/issues/3548
600            sender_data: SenderData::default(),
601            history_visibility: None.into(),
602            first_known_index,
603            room_id: value.room_id.to_owned(),
604            imported: true,
605            algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2.into(),
606            backed_up: AtomicBool::from(false).into(),
607        }
608    }
609}
610
611impl From<&ForwardedMegolmV2AesSha2Content> for InboundGroupSession {
612    fn from(value: &ForwardedMegolmV2AesSha2Content) -> Self {
613        let session = InnerSession::import(&value.session_key, SessionConfig::version_2());
614        let session_id = session.session_id().into();
615        let first_known_index = session.first_known_index();
616
617        InboundGroupSession {
618            inner: Mutex::new(session).into(),
619            session_id,
620            creator_info: SessionCreatorInfo {
621                curve25519_key: value.claimed_sender_key,
622                signing_keys: value.claimed_signing_keys.to_owned().into(),
623            },
624            // In future, exported keys should contain sender data that we can use here.
625            // See https://github.com/matrix-org/matrix-rust-sdk/issues/3548
626            sender_data: SenderData::default(),
627            history_visibility: None.into(),
628            first_known_index,
629            room_id: value.room_id.to_owned(),
630            imported: true,
631            algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2.into(),
632            backed_up: AtomicBool::from(false).into(),
633        }
634    }
635}
636
637impl TryFrom<&DecryptedForwardedRoomKeyEvent> for InboundGroupSession {
638    type Error = SessionCreationError;
639
640    fn try_from(value: &DecryptedForwardedRoomKeyEvent) -> Result<Self, Self::Error> {
641        match &value.content {
642            ForwardedRoomKeyContent::MegolmV1AesSha2(c) => Ok(Self::from(c.deref())),
643            #[cfg(feature = "experimental-algorithms")]
644            ForwardedRoomKeyContent::MegolmV2AesSha2(c) => Ok(Self::from(c.deref())),
645            ForwardedRoomKeyContent::Unknown(c) => {
646                Err(SessionCreationError::Algorithm(c.algorithm.to_owned()))
647            }
648        }
649    }
650}
651
652#[cfg(test)]
653mod tests {
654    use assert_matches2::assert_let;
655    use matrix_sdk_test::async_test;
656    use ruma::{
657        device_id, events::room::history_visibility::HistoryVisibility, room_id, user_id, DeviceId,
658        UserId,
659    };
660    use vodozemac::{
661        megolm::{SessionKey, SessionOrdering},
662        Curve25519PublicKey, Ed25519PublicKey,
663    };
664
665    use crate::{
666        olm::{InboundGroupSession, KnownSenderData, SenderData},
667        types::EventEncryptionAlgorithm,
668        Account,
669    };
670
671    fn alice_id() -> &'static UserId {
672        user_id!("@alice:example.org")
673    }
674
675    fn alice_device_id() -> &'static DeviceId {
676        device_id!("ALICEDEVICE")
677    }
678
679    #[async_test]
680    async fn test_can_deserialise_pickled_session_without_sender_data() {
681        // Given the raw JSON for a picked inbound group session without any sender_data
682        let pickle = r#"
683        {
684            "pickle": {
685                "initial_ratchet": {
686                    "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
687                               220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
688                               229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
689                               202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
690                               138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
691                               245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
692                               2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
693                               205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
694                               52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
695                    "counter": 0
696                },
697                "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
698                                 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
699                                 149, 43, 38 ],
700                "signing_key_verified": true,
701                "config": {
702                  "version": "V1"
703                }
704            },
705            "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
706            "signing_key": {
707                "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
708            },
709            "room_id": "!test:localhost",
710            "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
711            "imported": false,
712            "backed_up": false,
713            "history_visibility": "shared",
714            "algorithm": "m.megolm.v1.aes-sha2"
715        }
716        "#;
717
718        // When we deserialise it to from JSON
719        let deserialized = serde_json::from_str(pickle).unwrap();
720
721        // And unpickle it
722        let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
723
724        // Then it was parsed correctly
725        assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
726
727        // And we populated the InboundGroupSession's sender_data with a default value,
728        // with legacy_session set to true.
729        assert_let!(
730            SenderData::UnknownDevice { legacy_session, owner_check_failed } =
731                unpickled.sender_data
732        );
733        assert!(legacy_session);
734        assert!(!owner_check_failed);
735    }
736
737    #[async_test]
738    async fn test_can_serialise_pickled_session_with_sender_data() {
739        // Given an InboundGroupSession
740        let igs = InboundGroupSession::new(
741            Curve25519PublicKey::from_base64("AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8")
742                .unwrap(),
743            Ed25519PublicKey::from_base64("wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww").unwrap(),
744            room_id!("!test:localhost"),
745            &create_session_key(),
746            SenderData::unknown(),
747            EventEncryptionAlgorithm::MegolmV1AesSha2,
748            Some(HistoryVisibility::Shared),
749        )
750        .unwrap();
751
752        // When we pickle it
753        let pickled = igs.pickle().await;
754
755        // And serialise it
756        let serialised = serde_json::to_string(&pickled).unwrap();
757
758        // Then it looks as we expect
759
760        // (Break out this list of numbers as otherwise it bothers the json macro below)
761        let expected_inner = vec![
762            193, 203, 223, 152, 33, 132, 200, 168, 24, 197, 79, 174, 231, 202, 45, 245, 128, 131,
763            178, 165, 148, 37, 241, 214, 178, 218, 25, 33, 68, 48, 153, 104, 122, 6, 249, 198, 97,
764            226, 214, 75, 64, 128, 25, 138, 98, 90, 138, 93, 52, 206, 174, 3, 84, 149, 101, 140,
765            238, 156, 103, 107, 124, 144, 139, 104, 253, 5, 100, 251, 186, 118, 208, 87, 31, 218,
766            123, 234, 103, 34, 246, 100, 39, 90, 216, 72, 187, 86, 202, 150, 100, 116, 204, 254,
767            10, 154, 216, 133, 61, 250, 75, 100, 195, 63, 138, 22, 17, 13, 156, 123, 195, 132, 111,
768            95, 250, 24, 236, 0, 246, 93, 230, 100, 211, 165, 211, 190, 181, 87, 42, 181,
769        ];
770        assert_eq!(
771            serde_json::from_str::<serde_json::Value>(&serialised).unwrap(),
772            serde_json::json!({
773                "pickle":{
774                    "initial_ratchet":{
775                        "inner": expected_inner,
776                        "counter":0
777                    },
778                    "signing_key":[
779                        213,161,95,135,114,153,162,127,217,74,64,2,59,143,93,5,190,157,120,
780                        80,89,8,87,129,115,148,104,144,152,186,178,109
781                    ],
782                    "signing_key_verified":true,
783                    "config":{"version":"V1"}
784                },
785                "sender_key":"AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
786                "signing_key":{"ed25519":"wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"},
787                "sender_data":{
788                    "UnknownDevice":{
789                        "legacy_session":false
790                    }
791                },
792                "room_id":"!test:localhost",
793                "imported":false,
794                "backed_up":false,
795                "history_visibility":"shared",
796                "algorithm":"m.megolm.v1.aes-sha2"
797            })
798        );
799    }
800
801    #[async_test]
802    async fn test_can_deserialise_pickled_session_with_sender_data() {
803        // Given the raw JSON for a picked inbound group session (including sender_data)
804        let pickle = r#"
805        {
806            "pickle": {
807                "initial_ratchet": {
808                    "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
809                               220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
810                               229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
811                               202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
812                               138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
813                               245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
814                               2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
815                               205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
816                               52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
817                    "counter": 0
818                },
819                "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
820                                 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
821                                 149, 43, 38 ],
822                "signing_key_verified": true,
823                "config": {
824                  "version": "V1"
825                }
826            },
827            "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
828            "signing_key": {
829                "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
830            },
831            "sender_data":{
832                "UnknownDevice":{
833                    "legacy_session":false
834                }
835            },
836            "room_id": "!test:localhost",
837            "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
838            "imported": false,
839            "backed_up": false,
840            "history_visibility": "shared",
841            "algorithm": "m.megolm.v1.aes-sha2"
842        }
843        "#;
844
845        // When we deserialise it to from JSON
846        let deserialized = serde_json::from_str(pickle).unwrap();
847
848        // And unpickle it
849        let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
850
851        // Then it was parsed correctly
852        assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
853
854        // And we populated the InboundGroupSession's sender_data with the provided
855        // values
856        assert_let!(
857            SenderData::UnknownDevice { legacy_session, owner_check_failed } =
858                unpickled.sender_data
859        );
860        assert!(!legacy_session);
861        assert!(!owner_check_failed);
862    }
863
864    #[async_test]
865    async fn test_session_comparison() {
866        let alice = Account::with_device_id(alice_id(), alice_device_id());
867        let room_id = room_id!("!test:localhost");
868
869        let (_, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
870
871        let worse = InboundGroupSession::from_export(&inbound.export_at_index(10).await).unwrap();
872        let mut copy = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
873
874        assert_eq!(inbound.compare(&worse).await, SessionOrdering::Better);
875        assert_eq!(worse.compare(&inbound).await, SessionOrdering::Worse);
876        assert_eq!(inbound.compare(&inbound).await, SessionOrdering::Equal);
877        assert_eq!(inbound.compare(&copy).await, SessionOrdering::Equal);
878
879        copy.creator_info.curve25519_key =
880            Curve25519PublicKey::from_base64("XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY")
881                .unwrap();
882
883        assert_eq!(inbound.compare(&copy).await, SessionOrdering::Unconnected);
884    }
885
886    #[async_test]
887    async fn test_session_comparison_sender_data() {
888        let alice = Account::with_device_id(alice_id(), alice_device_id());
889        let room_id = room_id!("!test:localhost");
890
891        let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
892
893        let sender_data = SenderData::SenderVerified(KnownSenderData {
894            user_id: alice.user_id().into(),
895            device_id: Some(alice.device_id().into()),
896            master_key: alice.identity_keys().ed25519.into(),
897        });
898
899        let mut better = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
900        better.sender_data = sender_data.clone();
901
902        assert_eq!(inbound.compare(&better).await, SessionOrdering::Worse);
903        assert_eq!(better.compare(&inbound).await, SessionOrdering::Better);
904
905        inbound.sender_data = sender_data;
906        assert_eq!(better.compare(&inbound).await, SessionOrdering::Equal);
907    }
908
909    fn create_session_key() -> SessionKey {
910        SessionKey::from_base64(
911            "\
912            AgAAAADBy9+YIYTIqBjFT67nyi31gIOypZQl8day2hkhRDCZaHoG+cZh4tZLQIAZimJail0\
913            0zq4DVJVljO6cZ2t8kIto/QVk+7p20Fcf2nvqZyL2ZCda2Ei7VsqWZHTM/gqa2IU9+ktkwz\
914            +KFhENnHvDhG9f+hjsAPZd5mTTpdO+tVcqtdWhX4dymaJ/2UpAAjuPXQW+nXhQWQhXgXOUa\
915            JCYurJtvbCbqZGeDMmVIoqukBs2KugNJ6j5WlTPoeFnMl6Guy9uH2iWWxGg8ZgT2xspqVl5\
916            CwujjC+m7Dh1toVkvu+bAw\
917            ",
918        )
919        .unwrap()
920    }
921}