vodozemac/olm/session/
mod.rs

1// Copyright 2021 Damir Jelić
2// Copyright 2021 The Matrix.org Foundation C.I.C.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16mod chain_key;
17mod double_ratchet;
18pub mod message_key;
19pub mod ratchet;
20mod receiver_chain;
21mod root_key;
22
23use std::fmt::Debug;
24
25use aes::cipher::block_padding::UnpadError;
26use arrayvec::ArrayVec;
27use chain_key::RemoteChainKey;
28use double_ratchet::DoubleRatchet;
29use hmac::digest::MacError;
30use ratchet::RemoteRatchetKey;
31use receiver_chain::ReceiverChain;
32use root_key::RemoteRootKey;
33use serde::{Deserialize, Serialize};
34use thiserror::Error;
35
36use super::{
37    SessionConfig,
38    session_config::Version,
39    session_keys::SessionKeys,
40    shared_secret::{RemoteShared3DHSecret, Shared3DHSecret},
41};
42#[cfg(feature = "low-level-api")]
43use crate::hazmat::olm::MessageKey;
44use crate::{
45    Curve25519PublicKey, PickleError,
46    olm::{
47        messages::{Message, OlmMessage, PreKeyMessage},
48        session::double_ratchet::RatchetCount,
49    },
50    utilities::{pickle, unpickle},
51};
52
53const MAX_RECEIVING_CHAINS: usize = 5;
54
55/// Error type for Olm-based decryption failures.
56#[derive(Error, Debug)]
57pub enum DecryptionError {
58    /// The message authentication code of the message was invalid.
59    #[error("Failed decrypting Olm message, invalid MAC: {0}")]
60    InvalidMAC(#[from] MacError),
61    /// The length of the message authentication code of the message did not
62    /// match our expected length.
63    #[error("Failed decrypting Olm message, invalid MAC length: expected {0}, got {1}")]
64    InvalidMACLength(usize, usize),
65    /// The ciphertext of the message isn't padded correctly.
66    #[error("Failed decrypting Olm message, invalid padding")]
67    InvalidPadding(#[from] UnpadError),
68    /// The session is missing the correct message key to decrypt the message,
69    /// either because it was already used up, or because the Session has been
70    /// ratcheted forwards and the message key has been discarded.
71    #[error("The message key with the given key can't be created, message index: {0}")]
72    MissingMessageKey(u64),
73    /// Too many messages have been skipped to attempt decrypting this message.
74    #[error("The message gap was too big, got {0}, max allowed {1}")]
75    TooBigMessageGap(u64, u64),
76}
77
78#[derive(Serialize, Deserialize, Clone)]
79struct ChainStore {
80    inner: ArrayVec<ReceiverChain, MAX_RECEIVING_CHAINS>,
81}
82
83impl ChainStore {
84    fn new() -> Self {
85        Self { inner: ArrayVec::new() }
86    }
87
88    fn push(&mut self, ratchet: ReceiverChain) {
89        if self.inner.is_full() {
90            self.inner.pop_at(0);
91        }
92
93        self.inner.push(ratchet)
94    }
95
96    const fn is_empty(&self) -> bool {
97        self.inner.is_empty()
98    }
99
100    #[cfg(test)]
101    pub const fn len(&self) -> usize {
102        self.inner.len()
103    }
104
105    #[cfg(feature = "libolm-compat")]
106    pub fn get(&self, index: usize) -> Option<&ReceiverChain> {
107        self.inner.get(index)
108    }
109
110    fn find_ratchet(&mut self, ratchet_key: &RemoteRatchetKey) -> Option<&mut ReceiverChain> {
111        self.inner.iter_mut().find(|r| r.belongs_to(ratchet_key))
112    }
113}
114
115impl Default for ChainStore {
116    fn default() -> Self {
117        Self::new()
118    }
119}
120
121/// An Olm session represents one end of an encrypted communication channel
122/// between two participants.
123///
124/// A session enables enables the session owner to encrypt messages intended
125/// for, and decrypt messages sent by, the other participant of the channel.
126///
127/// Olm sessions have two important properties:
128///
129/// 1. They are based on a double ratchet algorithm which continuously
130///    introduces new entropy into the channel as messages are sent and
131///    received. This imbues the channel with *self-healing* properties,
132///    allowing it to recover from a momentary loss of confidentiality in the
133///    event of a key compromise.
134/// 2. They are *asynchronous*, allowing the participant to start sending
135///    messages to the other side even if the other participant is not online at
136///    the moment.
137///
138/// An Olm [`Session`] is acquired from an [`Account`], by calling either
139///
140/// - [`Account::create_outbound_session`], if you are the first participant to
141///   send a message in this channel, or
142/// - [`Account::create_inbound_session`], if the other participant initiated
143///   the channel by sending you a message.
144///
145/// [`Account`]: crate::olm::Account
146/// [`Account::create_outbound_session`]: crate::olm::Account::create_outbound_session
147/// [`Account::create_inbound_session`]: crate::olm::Account::create_inbound_session
148pub struct Session {
149    session_keys: SessionKeys,
150    sending_ratchet: DoubleRatchet,
151    receiving_chains: ChainStore,
152    config: SessionConfig,
153}
154
155impl Debug for Session {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        let Self { session_keys: _, sending_ratchet, receiving_chains, config } = self;
158
159        f.debug_struct("Session")
160            .field("session_id", &self.session_id())
161            .field("sending_ratchet", &sending_ratchet)
162            .field("receiving_chains", &receiving_chains.inner)
163            .field("config", config)
164            .finish_non_exhaustive()
165    }
166}
167
168impl Session {
169    pub(super) fn new(
170        config: SessionConfig,
171        shared_secret: Shared3DHSecret,
172        session_keys: SessionKeys,
173    ) -> Self {
174        let local_ratchet = DoubleRatchet::active(shared_secret);
175
176        Self {
177            session_keys,
178            sending_ratchet: local_ratchet,
179            receiving_chains: Default::default(),
180            config,
181        }
182    }
183
184    pub(super) fn new_remote(
185        config: SessionConfig,
186        shared_secret: RemoteShared3DHSecret,
187        remote_ratchet_key: Curve25519PublicKey,
188        session_keys: SessionKeys,
189    ) -> Self {
190        let (root_key, remote_chain_key) = shared_secret.expand();
191
192        let remote_ratchet_key = RemoteRatchetKey::from(remote_ratchet_key);
193        let root_key = RemoteRootKey::new(root_key);
194        let remote_chain_key = RemoteChainKey::new(remote_chain_key);
195
196        let local_ratchet = DoubleRatchet::inactive_from_prekey_data(root_key, remote_ratchet_key);
197        let remote_ratchet =
198            ReceiverChain::new(remote_ratchet_key, remote_chain_key, RatchetCount::new());
199
200        let mut ratchet_store = ChainStore::new();
201        ratchet_store.push(remote_ratchet);
202
203        Self {
204            session_keys,
205            sending_ratchet: local_ratchet,
206            receiving_chains: ratchet_store,
207            config,
208        }
209    }
210
211    /// Returns the globally unique session ID, in base64-encoded form.
212    ///
213    /// This is a shorthand helper of the [`SessionKeys::session_id()`] method.
214    pub fn session_id(&self) -> String {
215        self.session_keys.session_id()
216    }
217
218    /// Have we ever received and decrypted a message from the other side?
219    ///
220    /// Used to decide if outgoing messages should be sent as normal or pre-key
221    /// messages.
222    pub const fn has_received_message(&self) -> bool {
223        !self.receiving_chains.is_empty()
224    }
225
226    /// Encrypt the `plaintext` and construct an [`OlmMessage`].
227    ///
228    /// The message will either be a pre-key message or a normal message,
229    /// depending on whether the session is fully established. A [`Session`] is
230    /// fully established once you receive (and decrypt) at least one
231    /// message from the other side.
232    pub fn encrypt(&mut self, plaintext: impl AsRef<[u8]>) -> OlmMessage {
233        let message = match self.config.version {
234            Version::V1 => self.sending_ratchet.encrypt_truncated_mac(plaintext.as_ref()),
235            Version::V2 => self.sending_ratchet.encrypt(plaintext.as_ref()),
236        };
237
238        if self.has_received_message() {
239            OlmMessage::Normal(message)
240        } else {
241            let message = PreKeyMessage::new(self.session_keys, message);
242
243            OlmMessage::PreKey(message)
244        }
245    }
246
247    /// Get the keys associated with this session.
248    pub const fn session_keys(&self) -> SessionKeys {
249        self.session_keys
250    }
251
252    /// Get the [`SessionConfig`] that this [`Session`] is configured to use.
253    pub const fn session_config(&self) -> SessionConfig {
254        self.config
255    }
256
257    /// Get the [`MessageKey`] to encrypt the next message.
258    ///
259    /// **Note**: Each key obtained in this way should be used to encrypt
260    /// a message and the message must then be sent to the recipient.
261    ///
262    /// Failing to do so will increase the number of out-of-order messages on
263    /// the recipient side. Given that a `Session` can only support a limited
264    /// number of out-of-order messages, this will eventually lead to
265    /// undecryptable messages.
266    #[cfg(feature = "low-level-api")]
267    pub fn next_message_key(&mut self) -> MessageKey {
268        self.sending_ratchet.next_message_key()
269    }
270
271    /// Try to decrypt an Olm message, which will either return the plaintext or
272    /// result in a [`DecryptionError`].
273    pub fn decrypt(&mut self, message: &OlmMessage) -> Result<Vec<u8>, DecryptionError> {
274        let decrypted = match message {
275            OlmMessage::Normal(m) => self.decrypt_decoded(m)?,
276            OlmMessage::PreKey(m) => self.decrypt_decoded(&m.message)?,
277        };
278
279        Ok(decrypted)
280    }
281
282    pub(super) fn decrypt_decoded(
283        &mut self,
284        message: &Message,
285    ) -> Result<Vec<u8>, DecryptionError> {
286        let ratchet_key = RemoteRatchetKey::from(message.ratchet_key);
287
288        if let Some(ratchet) = self.receiving_chains.find_ratchet(&ratchet_key) {
289            ratchet.decrypt(message, &self.config)
290        } else {
291            let (sending_ratchet, mut remote_ratchet) = self.sending_ratchet.advance(ratchet_key);
292
293            let plaintext = remote_ratchet.decrypt(message, &self.config)?;
294
295            self.sending_ratchet = sending_ratchet;
296            self.receiving_chains.push(remote_ratchet);
297
298            Ok(plaintext)
299        }
300    }
301
302    /// Convert the session into a struct which implements [`serde::Serialize`]
303    /// and [`serde::Deserialize`].
304    pub fn pickle(&self) -> SessionPickle {
305        SessionPickle {
306            session_keys: self.session_keys,
307            sending_ratchet: self.sending_ratchet.clone(),
308            receiving_chains: self.receiving_chains.clone(),
309            config: self.config,
310        }
311    }
312
313    /// Restore a [`Session`] from a previously saved [`SessionPickle`].
314    pub fn from_pickle(pickle: SessionPickle) -> Self {
315        pickle.into()
316    }
317
318    /// Create a [`Session`] object by unpickling a session pickle in libolm
319    /// legacy pickle format.
320    ///
321    /// Such pickles are encrypted and need to first be decrypted using
322    /// `pickle_key`.
323    #[cfg(feature = "libolm-compat")]
324    pub fn from_libolm_pickle(
325        pickle: &str,
326        pickle_key: &[u8],
327    ) -> Result<Self, crate::LibolmPickleError> {
328        use crate::{olm::session::libolm_compat::Pickle, utilities::unpickle_libolm};
329
330        const PICKLE_VERSION: u32 = 1;
331        unpickle_libolm::<Pickle, _>(pickle, pickle_key, PICKLE_VERSION)
332    }
333}
334
335#[cfg(feature = "libolm-compat")]
336mod libolm_compat {
337    use matrix_pickle::Decode;
338    use zeroize::{Zeroize, ZeroizeOnDrop};
339
340    use super::{
341        ChainStore, Session,
342        chain_key::{ChainKey, RemoteChainKey},
343        double_ratchet::{DoubleRatchet, RatchetCount},
344        message_key::RemoteMessageKey,
345        ratchet::{Ratchet, RatchetKey, RemoteRatchetKey},
346        receiver_chain::ReceiverChain,
347        root_key::{RemoteRootKey, RootKey},
348    };
349    use crate::{
350        Curve25519PublicKey,
351        olm::{SessionConfig, SessionKeys},
352        types::Curve25519SecretKey,
353    };
354
355    #[derive(Decode, Zeroize, ZeroizeOnDrop)]
356    struct SenderChain {
357        public_ratchet_key: [u8; 32],
358        #[secret]
359        secret_ratchet_key: Box<[u8; 32]>,
360        chain_key: Box<[u8; 32]>,
361        chain_key_index: u32,
362    }
363
364    #[derive(Decode, Zeroize, ZeroizeOnDrop)]
365    struct ReceivingChain {
366        public_ratchet_key: [u8; 32],
367        #[secret]
368        chain_key: Box<[u8; 32]>,
369        chain_key_index: u32,
370    }
371
372    impl From<&ReceivingChain> for ReceiverChain {
373        fn from(chain: &ReceivingChain) -> Self {
374            let ratchet_key = RemoteRatchetKey::from(chain.public_ratchet_key);
375            let chain_key = RemoteChainKey::from_bytes_and_index(
376                chain.chain_key.clone(),
377                chain.chain_key_index,
378            );
379
380            ReceiverChain::new(ratchet_key, chain_key, RatchetCount::unknown())
381        }
382    }
383
384    #[derive(Decode, Zeroize, ZeroizeOnDrop)]
385    struct MessageKey {
386        ratchet_key: [u8; 32],
387        #[secret]
388        message_key: Box<[u8; 32]>,
389        index: u32,
390    }
391
392    impl From<&MessageKey> for RemoteMessageKey {
393        fn from(key: &MessageKey) -> Self {
394            RemoteMessageKey { key: key.message_key.clone(), index: key.index.into() }
395        }
396    }
397
398    #[derive(Decode)]
399    pub(super) struct Pickle {
400        #[allow(dead_code)]
401        version: u32,
402        #[allow(dead_code)]
403        received_message: bool,
404        session_keys: SessionKeys,
405        #[secret]
406        root_key: Box<[u8; 32]>,
407        sender_chains: Vec<SenderChain>,
408        receiver_chains: Vec<ReceivingChain>,
409        message_keys: Vec<MessageKey>,
410    }
411
412    impl Drop for Pickle {
413        fn drop(&mut self) {
414            self.root_key.zeroize();
415            self.sender_chains.zeroize();
416            self.receiver_chains.zeroize();
417            self.message_keys.zeroize();
418        }
419    }
420
421    impl TryFrom<Pickle> for Session {
422        type Error = crate::LibolmPickleError;
423
424        fn try_from(pickle: Pickle) -> Result<Self, Self::Error> {
425            let mut receiving_chains = ChainStore::new();
426
427            for chain in &pickle.receiver_chains {
428                receiving_chains.push(chain.into())
429            }
430
431            for key in &pickle.message_keys {
432                let ratchet_key =
433                    RemoteRatchetKey::from(Curve25519PublicKey::from(key.ratchet_key));
434
435                if let Some(receiving_chain) = receiving_chains.find_ratchet(&ratchet_key) {
436                    receiving_chain.insert_message_key(key.into())
437                }
438            }
439
440            if let Some(chain) = pickle.sender_chains.first() {
441                // XXX: Passing in secret array as value.
442                let ratchet_key = RatchetKey::from(Curve25519SecretKey::from_slice(
443                    chain.secret_ratchet_key.as_ref(),
444                ));
445                let chain_key =
446                    ChainKey::from_bytes_and_index(chain.chain_key.clone(), chain.chain_key_index);
447
448                let root_key = RootKey::new(pickle.root_key.clone());
449
450                let ratchet = Ratchet::new_with_ratchet_key(root_key, ratchet_key);
451                let sending_ratchet = DoubleRatchet::from_ratchet_and_chain_key(ratchet, chain_key);
452
453                Ok(Self {
454                    session_keys: pickle.session_keys,
455                    sending_ratchet,
456                    receiving_chains,
457                    config: SessionConfig::version_1(),
458                })
459            } else if let Some(chain) = receiving_chains.get(0) {
460                let sending_ratchet = DoubleRatchet::inactive_from_libolm_pickle(
461                    RemoteRootKey::new(pickle.root_key.clone()),
462                    chain.ratchet_key(),
463                );
464
465                Ok(Self {
466                    session_keys: pickle.session_keys,
467                    sending_ratchet,
468                    receiving_chains,
469                    config: SessionConfig::version_1(),
470                })
471            } else {
472                Err(crate::LibolmPickleError::InvalidSession)
473            }
474        }
475    }
476}
477
478/// A format suitable for serialization which implements [`serde::Serialize`]
479/// and [`serde::Deserialize`]. Obtainable by calling [`Session::pickle`].
480#[derive(Deserialize, Serialize)]
481pub struct SessionPickle {
482    session_keys: SessionKeys,
483    sending_ratchet: DoubleRatchet,
484    receiving_chains: ChainStore,
485    #[serde(default = "default_config")]
486    config: SessionConfig,
487}
488
489const fn default_config() -> SessionConfig {
490    SessionConfig::version_1()
491}
492
493impl SessionPickle {
494    /// Serialize and encrypt the pickle using the given key.
495    ///
496    /// This is the inverse of [`SessionPickle::from_encrypted`].
497    pub fn encrypt(self, pickle_key: &[u8; 32]) -> String {
498        pickle(&self, pickle_key)
499    }
500
501    /// Obtain a pickle from a ciphertext by decrypting and deserializing using
502    /// the given key.
503    ///
504    /// This is the inverse of [`SessionPickle::encrypt`].
505    pub fn from_encrypted(ciphertext: &str, pickle_key: &[u8; 32]) -> Result<Self, PickleError> {
506        unpickle(ciphertext, pickle_key)
507    }
508}
509
510impl From<SessionPickle> for Session {
511    fn from(pickle: SessionPickle) -> Self {
512        Self {
513            session_keys: pickle.session_keys,
514            sending_ratchet: pickle.sending_ratchet,
515            receiving_chains: pickle.receiving_chains,
516            config: pickle.config,
517        }
518    }
519}
520
521#[cfg(test)]
522mod test {
523    use anyhow::{Result, bail};
524    use assert_matches2::assert_matches;
525    use olm_rs::{
526        account::OlmAccount,
527        session::{OlmMessage, OlmSession},
528    };
529
530    use super::{DecryptionError, Session};
531    use crate::{
532        Curve25519PublicKey,
533        olm::{
534            Account, SessionConfig, SessionPickle, messages,
535            session::receiver_chain::{MAX_MESSAGE_GAP, MAX_MESSAGE_KEYS},
536        },
537    };
538
539    const PICKLE_KEY: [u8; 32] = [0u8; 32];
540
541    /// Create a pair of accounts, one using vodozemac and one libolm.
542    ///
543    /// Then, create a pair of sessions between the two.
544    pub fn session_and_libolm_pair() -> Result<(Account, OlmAccount, Session, OlmSession)> {
545        let alice = Account::new();
546        let bob = OlmAccount::new();
547        bob.generate_one_time_keys(1);
548
549        let one_time_key = bob
550            .parsed_one_time_keys()
551            .curve25519()
552            .values()
553            .next()
554            .cloned()
555            .expect("Couldn't find a one-time key");
556
557        let identity_keys = bob.parsed_identity_keys();
558        let curve25519_key = Curve25519PublicKey::from_base64(identity_keys.curve25519())?;
559        let one_time_key = Curve25519PublicKey::from_base64(&one_time_key)?;
560        let mut alice_session =
561            alice.create_outbound_session(SessionConfig::version_1(), curve25519_key, one_time_key);
562
563        let message = "It's a secret to everybody";
564
565        let olm_message = alice_session.encrypt(message);
566        bob.mark_keys_as_published();
567
568        if let OlmMessage::PreKey(m) = olm_message.into() {
569            let session =
570                bob.create_inbound_session_from(&alice.curve25519_key().to_base64(), m)?;
571
572            Ok((alice, bob, alice_session, session))
573        } else {
574            bail!("Invalid message type");
575        }
576    }
577
578    #[test]
579    fn session_config() {
580        let (_, _, alice_session, _) = session_and_libolm_pair().unwrap();
581        assert_eq!(alice_session.session_config(), SessionConfig::version_1());
582    }
583
584    #[test]
585    fn has_received_message() {
586        let (_, _, mut alice_session, bob_session) = session_and_libolm_pair().unwrap();
587        assert!(!alice_session.has_received_message());
588        assert!(!bob_session.has_received_message());
589        let message = bob_session.encrypt("Message").into();
590        assert_eq!(
591            "Message".as_bytes(),
592            alice_session.decrypt(&message).expect("Should be able to decrypt message")
593        );
594        assert!(alice_session.has_received_message());
595        assert!(!bob_session.has_received_message());
596    }
597
598    #[test]
599    fn out_of_order_decryption() {
600        let (_, _, mut alice_session, bob_session) = session_and_libolm_pair().unwrap();
601
602        let message_1 = bob_session.encrypt("Message 1").into();
603        let message_2 = bob_session.encrypt("Message 2").into();
604        let message_3 = bob_session.encrypt("Message 3").into();
605
606        assert_eq!(
607            "Message 3".as_bytes(),
608            alice_session.decrypt(&message_3).expect("Should be able to decrypt message 3")
609        );
610        assert_eq!(
611            "Message 2".as_bytes(),
612            alice_session.decrypt(&message_2).expect("Should be able to decrypt message 2")
613        );
614        assert_eq!(
615            "Message 1".as_bytes(),
616            alice_session.decrypt(&message_1).expect("Should be able to decrypt message 1")
617        );
618    }
619
620    #[test]
621    fn more_out_of_order_decryption() {
622        let (_, _, mut alice_session, bob_session) = session_and_libolm_pair().unwrap();
623
624        let message_1 = bob_session.encrypt("Message 1").into();
625        let message_2 = bob_session.encrypt("Message 2").into();
626        let message_3 = bob_session.encrypt("Message 3").into();
627
628        assert_eq!(
629            "Message 1".as_bytes(),
630            alice_session.decrypt(&message_1).expect("Should be able to decrypt message 1")
631        );
632
633        assert_eq!(alice_session.receiving_chains.len(), 1);
634
635        let message_4 = alice_session.encrypt("Message 4").into();
636        assert_eq!(
637            "Message 4",
638            bob_session.decrypt(message_4).expect("Should be able to decrypt message 4")
639        );
640
641        let message_5 = bob_session.encrypt("Message 5").into();
642        assert_eq!(
643            "Message 5".as_bytes(),
644            alice_session.decrypt(&message_5).expect("Should be able to decrypt message 5")
645        );
646        assert_eq!(
647            "Message 3".as_bytes(),
648            alice_session.decrypt(&message_3).expect("Should be able to decrypt message 3")
649        );
650        assert_eq!(
651            "Message 2".as_bytes(),
652            alice_session.decrypt(&message_2).expect("Should be able to decrypt message 2")
653        );
654
655        assert_eq!(alice_session.receiving_chains.len(), 2);
656    }
657
658    #[test]
659    fn max_keys_out_of_order_decryption() {
660        let (_, _, mut alice_session, bob_session) = session_and_libolm_pair().unwrap();
661
662        let mut messages: Vec<messages::OlmMessage> = Vec::new();
663        for i in 0..(MAX_MESSAGE_KEYS + 2) {
664            messages.push(bob_session.encrypt(format!("Message {i}").as_str()).into());
665        }
666
667        // Decrypt last message
668        assert_eq!(
669            format!("Message {}", MAX_MESSAGE_KEYS + 1).as_bytes(),
670            alice_session
671                .decrypt(&messages[MAX_MESSAGE_KEYS + 1])
672                .expect("Should be able to decrypt last message")
673        );
674
675        // Cannot decrypt first message because it is more than MAX_MESSAGE_KEYS ago
676        assert_matches!(
677            alice_session.decrypt(&messages[0]),
678            Err(DecryptionError::MissingMessageKey(_))
679        );
680
681        // Can decrypt all other messages
682        for (i, message) in messages.iter().enumerate().skip(1).take(MAX_MESSAGE_KEYS) {
683            assert_eq!(
684                format!("Message {i}").as_bytes(),
685                alice_session
686                    .decrypt(message)
687                    .expect("Should be able to decrypt remaining messages")
688            );
689        }
690    }
691
692    #[test]
693    fn max_gap_out_of_order_decryption() {
694        let (_, _, mut alice_session, bob_session) = session_and_libolm_pair().unwrap();
695
696        for i in 0..(MAX_MESSAGE_GAP + 1) {
697            bob_session.encrypt(format!("Message {i}").as_str());
698        }
699
700        let message = bob_session.encrypt("Message").into();
701        assert_matches!(
702            alice_session.decrypt(&message),
703            Err(DecryptionError::TooBigMessageGap(_, _))
704        );
705    }
706
707    #[test]
708    fn pickle_default_config() {
709        let json = r#"
710            {
711                "receiving_chains": {
712                    "inner": []
713                },
714                "sending_ratchet": {
715                    "active_ratchet": {
716                        "ratchet_key": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
717                        "root_key": [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
718                    },
719                    "parent_ratchet_key": null,
720                    "ratchet_count": {
721                        "Known": 1
722                    },
723                    "symmetric_key_ratchet": {
724                        "index": 1,
725                        "key": [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
726                    },
727                    "type": "active"
728                },
729                "session_keys": {
730                    "base_key": [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
731                    "identity_key": [5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
732                    "one_time_key": [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6]
733                }
734            }
735        "#;
736        let pickle: SessionPickle =
737            serde_json::from_str(json).expect("Should be able to deserialize JSON");
738        assert_eq!(pickle.config, SessionConfig::version_1());
739    }
740
741    #[test]
742    #[cfg(feature = "libolm-compat")]
743    fn libolm_unpickling() {
744        let (_, _, mut session, olm) = session_and_libolm_pair().unwrap();
745
746        let plaintext = "It's a secret to everybody";
747        let old_message = session.encrypt(plaintext);
748
749        for _ in 0..9 {
750            session.encrypt("Hello");
751        }
752
753        let message = session.encrypt("Hello");
754        olm.decrypt(message.into()).expect("Should be able to decrypt message");
755
756        let key = b"DEFAULT_PICKLE_KEY";
757        let pickle = olm.pickle(olm_rs::PicklingMode::Encrypted { key: key.to_vec() });
758
759        let mut unpickled =
760            Session::from_libolm_pickle(&pickle, key).expect("Should be able to unpickle session");
761
762        assert_eq!(olm.session_id(), unpickled.session_id());
763
764        assert_eq!(
765            unpickled
766                .decrypt(&old_message)
767                .expect("Should be able to decrypt old message with unpickled session"),
768            plaintext.as_bytes()
769        );
770
771        let message = unpickled.encrypt(plaintext);
772
773        assert_eq!(
774            session.decrypt(&message).expect("Should be able to decrypt re-encrypted message"),
775            plaintext.as_bytes()
776        );
777    }
778
779    #[test]
780    fn session_pickling_roundtrip_is_identity() {
781        let (_, _, session, _) = session_and_libolm_pair().unwrap();
782
783        let pickle = session.pickle().encrypt(&PICKLE_KEY);
784
785        let decrypted_pickle = SessionPickle::from_encrypted(&pickle, &PICKLE_KEY)
786            .expect("Should be able to decrypt encrypted pickle");
787        let unpickled_group_session = Session::from_pickle(decrypted_pickle);
788        let repickle = unpickled_group_session.pickle();
789
790        assert_eq!(session.session_id(), unpickled_group_session.session_id());
791
792        let decrypted_pickle = SessionPickle::from_encrypted(&pickle, &PICKLE_KEY)
793            .expect("Should be able to decrypt encrypted pickle");
794        let pickle = serde_json::to_value(decrypted_pickle).unwrap();
795        let repickle = serde_json::to_value(repickle).unwrap();
796
797        assert_eq!(pickle, repickle);
798    }
799}