1mod 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#[derive(Error, Debug)]
57pub enum DecryptionError {
58 #[error("Failed decrypting Olm message, invalid MAC: {0}")]
60 InvalidMAC(#[from] MacError),
61 #[error("Failed decrypting Olm message, invalid MAC length: expected {0}, got {1}")]
64 InvalidMACLength(usize, usize),
65 #[error("Failed decrypting Olm message, invalid padding")]
67 InvalidPadding(#[from] UnpadError),
68 #[error("The message key with the given key can't be created, message index: {0}")]
72 MissingMessageKey(u64),
73 #[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
121pub 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 pub fn session_id(&self) -> String {
215 self.session_keys.session_id()
216 }
217
218 pub const fn has_received_message(&self) -> bool {
223 !self.receiving_chains.is_empty()
224 }
225
226 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 pub const fn session_keys(&self) -> SessionKeys {
249 self.session_keys
250 }
251
252 pub const fn session_config(&self) -> SessionConfig {
254 self.config
255 }
256
257 #[cfg(feature = "low-level-api")]
267 pub fn next_message_key(&mut self) -> MessageKey {
268 self.sending_ratchet.next_message_key()
269 }
270
271 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 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 pub fn from_pickle(pickle: SessionPickle) -> Self {
315 pickle.into()
316 }
317
318 #[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 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#[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 pub fn encrypt(self, pickle_key: &[u8; 32]) -> String {
498 pickle(&self, pickle_key)
499 }
500
501 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 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 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 assert_matches!(
677 alice_session.decrypt(&messages[0]),
678 Err(DecryptionError::MissingMessageKey(_))
679 );
680
681 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}