1use 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};
43#[cfg(doc)]
44use crate::types::events::room_key::RoomKeyContent;
45use crate::{
46 error::{EventError, MegolmResult},
47 types::{
48 deserialize_curve_key,
49 events::{
50 forwarded_room_key::{
51 ForwardedMegolmV1AesSha2Content, ForwardedMegolmV2AesSha2Content,
52 ForwardedRoomKeyContent,
53 },
54 olm_v1::DecryptedForwardedRoomKeyEvent,
55 room::encrypted::{EncryptedEvent, RoomEventEncryptionScheme},
56 room_key,
57 },
58 room_history::HistoricRoomKey,
59 serialize_curve_key, EventEncryptionAlgorithm, SigningKeys,
60 },
61};
62#[derive(Clone)]
68pub(crate) struct SessionCreatorInfo {
69 pub curve25519_key: Curve25519PublicKey,
84
85 pub signing_keys: Arc<SigningKeys<DeviceKeyAlgorithm>>,
99}
100
101#[derive(Clone)]
167pub struct InboundGroupSession {
168 inner: Arc<Mutex<InnerSession>>,
169
170 session_id: Arc<str>,
173
174 first_known_index: u32,
177
178 pub(crate) creator_info: SessionCreatorInfo,
182
183 pub sender_data: SenderData,
189
190 pub room_id: OwnedRoomId,
192
193 imported: bool,
200
201 algorithm: Arc<EventEncryptionAlgorithm>,
206
207 history_visibility: Arc<Option<HistoryVisibility>>,
210
211 backed_up: Arc<AtomicBool>,
213
214 shared_history: bool,
220}
221
222impl InboundGroupSession {
223 #[allow(clippy::too_many_arguments)]
260 pub fn new(
261 sender_key: Curve25519PublicKey,
262 signing_key: Ed25519PublicKey,
263 room_id: &RoomId,
264 session_key: &SessionKey,
265 sender_data: SenderData,
266 encryption_algorithm: EventEncryptionAlgorithm,
267 history_visibility: Option<HistoryVisibility>,
268 shared_history: bool,
269 ) -> Result<Self, SessionCreationError> {
270 let config = OutboundGroupSession::session_config(&encryption_algorithm)?;
271
272 let session = InnerSession::new(session_key, config);
273 let session_id = session.session_id();
274 let first_known_index = session.first_known_index();
275
276 let mut keys = SigningKeys::new();
277 keys.insert(DeviceKeyAlgorithm::Ed25519, signing_key.into());
278
279 Ok(InboundGroupSession {
280 inner: Arc::new(Mutex::new(session)),
281 history_visibility: history_visibility.into(),
282 session_id: session_id.into(),
283 first_known_index,
284 creator_info: SessionCreatorInfo {
285 curve25519_key: sender_key,
286 signing_keys: keys.into(),
287 },
288 sender_data,
289 room_id: room_id.into(),
290 imported: false,
291 algorithm: encryption_algorithm.into(),
292 backed_up: AtomicBool::new(false).into(),
293 shared_history,
294 })
295 }
296
297 pub fn from_room_key_content(
310 sender_key: Curve25519PublicKey,
311 signing_key: Ed25519PublicKey,
312 content: &room_key::MegolmV1AesSha2Content,
313 ) -> Result<Self, SessionCreationError> {
314 let room_key::MegolmV1AesSha2Content {
315 room_id,
316 session_id: _,
317 session_key,
318 shared_history,
319 ..
320 } = content;
321
322 Self::new(
323 sender_key,
324 signing_key,
325 room_id,
326 session_key,
327 SenderData::unknown(),
328 EventEncryptionAlgorithm::MegolmV1AesSha2,
329 None,
330 *shared_history,
331 )
332 }
333
334 pub fn from_export(exported_session: &ExportedRoomKey) -> Result<Self, SessionCreationError> {
340 Self::try_from(exported_session)
341 }
342
343 pub async fn pickle(&self) -> PickledInboundGroupSession {
346 let pickle = self.inner.lock().await.pickle();
347
348 PickledInboundGroupSession {
349 pickle,
350 sender_key: self.creator_info.curve25519_key,
351 signing_key: (*self.creator_info.signing_keys).clone(),
352 sender_data: self.sender_data.clone(),
353 room_id: self.room_id().to_owned(),
354 imported: self.imported,
355 backed_up: self.backed_up(),
356 history_visibility: self.history_visibility.as_ref().clone(),
357 algorithm: (*self.algorithm).to_owned(),
358 shared_history: self.shared_history,
359 }
360 }
361
362 pub async fn export(&self) -> ExportedRoomKey {
367 self.export_at_index(self.first_known_index()).await
368 }
369
370 pub fn sender_key(&self) -> Curve25519PublicKey {
372 self.creator_info.curve25519_key
373 }
374
375 pub fn backed_up(&self) -> bool {
377 self.backed_up.load(SeqCst)
378 }
379
380 pub fn reset_backup_state(&self) {
382 self.backed_up.store(false, SeqCst)
383 }
384
385 pub fn mark_as_backed_up(&self) {
388 self.backed_up.store(true, SeqCst)
389 }
390
391 pub fn signing_keys(&self) -> &SigningKeys<DeviceKeyAlgorithm> {
393 &self.creator_info.signing_keys
394 }
395
396 pub async fn export_at_index(&self, message_index: u32) -> ExportedRoomKey {
398 let message_index = std::cmp::max(self.first_known_index(), message_index);
399
400 let session_key =
401 self.inner.lock().await.export_at(message_index).expect("Can't export session");
402
403 ExportedRoomKey {
404 algorithm: self.algorithm().to_owned(),
405 room_id: self.room_id().to_owned(),
406 sender_key: self.creator_info.curve25519_key,
407 session_id: self.session_id().to_owned(),
408 forwarding_curve25519_key_chain: vec![],
409 sender_claimed_keys: (*self.creator_info.signing_keys).clone(),
410 session_key,
411 shared_history: self.shared_history,
412 }
413 }
414
415 pub fn from_pickle(pickle: PickledInboundGroupSession) -> Result<Self, PickleError> {
427 let PickledInboundGroupSession {
428 pickle,
429 sender_key,
430 signing_key,
431 sender_data,
432 room_id,
433 imported,
434 backed_up,
435 history_visibility,
436 algorithm,
437 shared_history,
438 } = pickle;
439
440 let session: InnerSession = pickle.into();
441 let first_known_index = session.first_known_index();
442 let session_id = session.session_id();
443
444 Ok(InboundGroupSession {
445 inner: Mutex::new(session).into(),
446 session_id: session_id.into(),
447 creator_info: SessionCreatorInfo {
448 curve25519_key: sender_key,
449 signing_keys: signing_key.into(),
450 },
451 sender_data,
452 history_visibility: history_visibility.into(),
453 first_known_index,
454 room_id,
455 backed_up: AtomicBool::from(backed_up).into(),
456 algorithm: algorithm.into(),
457 imported,
458 shared_history,
459 })
460 }
461
462 pub fn room_id(&self) -> &RoomId {
464 &self.room_id
465 }
466
467 pub fn session_id(&self) -> &str {
469 &self.session_id
470 }
471
472 pub fn algorithm(&self) -> &EventEncryptionAlgorithm {
475 &self.algorithm
476 }
477
478 pub fn first_known_index(&self) -> u32 {
480 self.first_known_index
481 }
482
483 pub fn has_been_imported(&self) -> bool {
486 self.imported
487 }
488
489 pub async fn compare(&self, other: &InboundGroupSession) -> SessionOrdering {
492 if Arc::ptr_eq(&self.inner, &other.inner) {
495 SessionOrdering::Equal
496 } else if self.sender_key() != other.sender_key()
497 || self.signing_keys() != other.signing_keys()
498 || self.algorithm() != other.algorithm()
499 || self.room_id() != other.room_id()
500 {
501 SessionOrdering::Unconnected
502 } else {
503 let mut other_inner = other.inner.lock().await;
504
505 match self.inner.lock().await.compare(&mut other_inner) {
506 SessionOrdering::Equal => {
507 match self.sender_data.compare_trust_level(&other.sender_data) {
508 Ordering::Less => SessionOrdering::Worse,
509 Ordering::Equal => SessionOrdering::Equal,
510 Ordering::Greater => SessionOrdering::Better,
511 }
512 }
513 result => result,
514 }
515 }
516 }
517
518 pub(crate) async fn decrypt_helper(
527 &self,
528 message: &MegolmMessage,
529 ) -> Result<DecryptedMessage, DecryptionError> {
530 self.inner.lock().await.decrypt(message)
531 }
532
533 pub async fn to_backup(&self) -> BackedUpRoomKey {
536 self.export().await.into()
537 }
538
539 pub async fn decrypt(&self, event: &EncryptedEvent) -> MegolmResult<(JsonObject, u32)> {
545 let decrypted = match &event.content.scheme {
546 RoomEventEncryptionScheme::MegolmV1AesSha2(c) => {
547 self.decrypt_helper(&c.ciphertext).await?
548 }
549 #[cfg(feature = "experimental-algorithms")]
550 RoomEventEncryptionScheme::MegolmV2AesSha2(c) => {
551 self.decrypt_helper(&c.ciphertext).await?
552 }
553 RoomEventEncryptionScheme::Unknown(_) => {
554 return Err(EventError::UnsupportedAlgorithm.into());
555 }
556 };
557
558 let plaintext = String::from_utf8_lossy(&decrypted.plaintext);
559
560 let mut decrypted_object = serde_json::from_str::<JsonObject>(&plaintext)?;
561
562 let server_ts: i64 = event.origin_server_ts.0.into();
563
564 decrypted_object.insert("sender".to_owned(), event.sender.to_string().into());
565 decrypted_object.insert("event_id".to_owned(), event.event_id.to_string().into());
566 decrypted_object.insert("origin_server_ts".to_owned(), server_ts.into());
567
568 let room_id = decrypted_object
569 .get("room_id")
570 .and_then(|r| r.as_str().and_then(|r| RoomId::parse(r).ok()));
571
572 if room_id.as_deref() != Some(self.room_id()) {
575 return Err(EventError::MismatchedRoom(self.room_id().to_owned(), room_id).into());
576 }
577
578 decrypted_object.insert(
579 "unsigned".to_owned(),
580 serde_json::to_value(&event.unsigned).unwrap_or_default(),
581 );
582
583 if let Some(decrypted_content) =
584 decrypted_object.get_mut("content").and_then(|c| c.as_object_mut())
585 {
586 if !decrypted_content.contains_key("m.relates_to") {
587 if let Some(relation) = &event.content.relates_to {
588 decrypted_content.insert("m.relates_to".to_owned(), relation.to_owned());
589 }
590 }
591 }
592
593 Ok((decrypted_object, decrypted.message_index))
594 }
595
596 #[cfg(test)]
598 pub(crate) fn mark_as_imported(&mut self) {
599 self.imported = true;
600 }
601
602 pub fn sender_data_type(&self) -> SenderDataType {
606 self.sender_data.to_type()
607 }
608
609 pub fn shared_history(&self) -> bool {
615 self.shared_history
616 }
617}
618
619#[cfg(not(tarpaulin_include))]
620impl fmt::Debug for InboundGroupSession {
621 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
622 f.debug_struct("InboundGroupSession").field("session_id", &self.session_id()).finish()
623 }
624}
625
626impl PartialEq for InboundGroupSession {
627 fn eq(&self, other: &Self) -> bool {
628 self.session_id() == other.session_id()
629 }
630}
631
632#[derive(Serialize, Deserialize)]
637#[allow(missing_debug_implementations)]
638pub struct PickledInboundGroupSession {
639 pub pickle: InboundGroupSessionPickle,
641 #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
643 pub sender_key: Curve25519PublicKey,
644 pub signing_key: SigningKeys<DeviceKeyAlgorithm>,
646 #[serde(default)]
648 pub sender_data: SenderData,
649 pub room_id: OwnedRoomId,
651 pub imported: bool,
654 #[serde(default)]
656 pub backed_up: bool,
657 pub history_visibility: Option<HistoryVisibility>,
659 #[serde(default = "default_algorithm")]
661 pub algorithm: EventEncryptionAlgorithm,
662 #[serde(default)]
668 pub shared_history: bool,
669}
670
671fn default_algorithm() -> EventEncryptionAlgorithm {
672 EventEncryptionAlgorithm::MegolmV1AesSha2
673}
674
675impl TryFrom<&HistoricRoomKey> for InboundGroupSession {
676 type Error = SessionCreationError;
677
678 fn try_from(key: &HistoricRoomKey) -> Result<Self, Self::Error> {
679 let HistoricRoomKey {
680 algorithm,
681 room_id,
682 sender_key,
683 session_id,
684 session_key,
685 sender_claimed_keys,
686 } = key;
687
688 let config = OutboundGroupSession::session_config(algorithm)?;
689 let session = InnerSession::import(session_key, config);
690 let first_known_index = session.first_known_index();
691
692 Ok(InboundGroupSession {
693 inner: Mutex::new(session).into(),
694 session_id: session_id.to_owned().into(),
695 creator_info: SessionCreatorInfo {
696 curve25519_key: *sender_key,
697 signing_keys: sender_claimed_keys.to_owned().into(),
698 },
699 sender_data: SenderData::default(),
702 history_visibility: None.into(),
703 first_known_index,
704 room_id: room_id.to_owned(),
705 imported: true,
706 algorithm: algorithm.to_owned().into(),
707 backed_up: AtomicBool::from(false).into(),
708 shared_history: true,
709 })
710 }
711}
712
713impl TryFrom<&ExportedRoomKey> for InboundGroupSession {
714 type Error = SessionCreationError;
715
716 fn try_from(key: &ExportedRoomKey) -> Result<Self, Self::Error> {
717 let ExportedRoomKey {
718 algorithm,
719 room_id,
720 sender_key,
721 session_id,
722 session_key,
723 sender_claimed_keys,
724 forwarding_curve25519_key_chain: _,
725 shared_history,
726 } = key;
727
728 let config = OutboundGroupSession::session_config(algorithm)?;
729 let session = InnerSession::import(session_key, config);
730 let first_known_index = session.first_known_index();
731
732 Ok(InboundGroupSession {
733 inner: Mutex::new(session).into(),
734 session_id: session_id.to_owned().into(),
735 creator_info: SessionCreatorInfo {
736 curve25519_key: *sender_key,
737 signing_keys: sender_claimed_keys.to_owned().into(),
738 },
739 sender_data: SenderData::default(),
742 history_visibility: None.into(),
743 first_known_index,
744 room_id: room_id.to_owned(),
745 imported: true,
746 algorithm: algorithm.to_owned().into(),
747 backed_up: AtomicBool::from(false).into(),
748 shared_history: *shared_history,
749 })
750 }
751}
752
753impl From<&ForwardedMegolmV1AesSha2Content> for InboundGroupSession {
754 fn from(value: &ForwardedMegolmV1AesSha2Content) -> Self {
755 let session = InnerSession::import(&value.session_key, SessionConfig::version_1());
756 let session_id = session.session_id().into();
757 let first_known_index = session.first_known_index();
758
759 InboundGroupSession {
760 inner: Mutex::new(session).into(),
761 session_id,
762 creator_info: SessionCreatorInfo {
763 curve25519_key: value.claimed_sender_key,
764 signing_keys: SigningKeys::from([(
765 DeviceKeyAlgorithm::Ed25519,
766 value.claimed_ed25519_key.into(),
767 )])
768 .into(),
769 },
770 sender_data: SenderData::default(),
773 history_visibility: None.into(),
774 first_known_index,
775 room_id: value.room_id.to_owned(),
776 imported: true,
777 algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2.into(),
778 backed_up: AtomicBool::from(false).into(),
779 shared_history: false,
780 }
781 }
782}
783
784impl From<&ForwardedMegolmV2AesSha2Content> for InboundGroupSession {
785 fn from(value: &ForwardedMegolmV2AesSha2Content) -> Self {
786 let session = InnerSession::import(&value.session_key, SessionConfig::version_2());
787 let session_id = session.session_id().into();
788 let first_known_index = session.first_known_index();
789
790 InboundGroupSession {
791 inner: Mutex::new(session).into(),
792 session_id,
793 creator_info: SessionCreatorInfo {
794 curve25519_key: value.claimed_sender_key,
795 signing_keys: value.claimed_signing_keys.to_owned().into(),
796 },
797 sender_data: SenderData::default(),
800 history_visibility: None.into(),
801 first_known_index,
802 room_id: value.room_id.to_owned(),
803 imported: true,
804 algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2.into(),
805 backed_up: AtomicBool::from(false).into(),
806 shared_history: false,
807 }
808 }
809}
810
811impl TryFrom<&DecryptedForwardedRoomKeyEvent> for InboundGroupSession {
812 type Error = SessionCreationError;
813
814 fn try_from(value: &DecryptedForwardedRoomKeyEvent) -> Result<Self, Self::Error> {
815 match &value.content {
816 ForwardedRoomKeyContent::MegolmV1AesSha2(c) => Ok(Self::from(c.deref())),
817 #[cfg(feature = "experimental-algorithms")]
818 ForwardedRoomKeyContent::MegolmV2AesSha2(c) => Ok(Self::from(c.deref())),
819 ForwardedRoomKeyContent::Unknown(c) => {
820 Err(SessionCreationError::Algorithm(c.algorithm.to_owned()))
821 }
822 }
823 }
824}
825
826#[cfg(test)]
827mod tests {
828 use assert_matches2::assert_let;
829 use insta::assert_json_snapshot;
830 use matrix_sdk_test::async_test;
831 use ruma::{
832 device_id, events::room::history_visibility::HistoryVisibility, owned_room_id, room_id,
833 user_id, DeviceId, UserId,
834 };
835 use serde_json::json;
836 use similar_asserts::assert_eq;
837 use vodozemac::{
838 megolm::{SessionKey, SessionOrdering},
839 Curve25519PublicKey, Ed25519PublicKey,
840 };
841
842 use crate::{
843 olm::{BackedUpRoomKey, ExportedRoomKey, InboundGroupSession, KnownSenderData, SenderData},
844 types::{events::room_key, EventEncryptionAlgorithm},
845 Account,
846 };
847
848 fn alice_id() -> &'static UserId {
849 user_id!("@alice:example.org")
850 }
851
852 fn alice_device_id() -> &'static DeviceId {
853 device_id!("ALICEDEVICE")
854 }
855
856 #[async_test]
857 async fn test_pickle_snapshot() {
858 let account = Account::new(alice_id());
859 let room_id = room_id!("!test:localhost");
860 let (_, session) = account.create_group_session_pair_with_defaults(room_id).await;
861
862 let pickle = session.pickle().await;
863
864 assert_json_snapshot!(pickle, {
865 ".pickle.initial_ratchet.inner" => "[ratchet]",
866 ".pickle.signing_key" => "[signing_key]",
867 ".sender_key" => "[sender_key]",
868 ".signing_key.ed25519" => "[ed25519_key]",
869 });
870 }
871
872 #[async_test]
873 async fn test_can_deserialise_pickled_session_without_sender_data() {
874 let pickle = r#"
876 {
877 "pickle": {
878 "initial_ratchet": {
879 "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
880 220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
881 229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
882 202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
883 138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
884 245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
885 2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
886 205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
887 52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
888 "counter": 0
889 },
890 "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
891 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
892 149, 43, 38 ],
893 "signing_key_verified": true,
894 "config": {
895 "version": "V1"
896 }
897 },
898 "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
899 "signing_key": {
900 "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
901 },
902 "room_id": "!test:localhost",
903 "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
904 "imported": false,
905 "backed_up": false,
906 "history_visibility": "shared",
907 "algorithm": "m.megolm.v1.aes-sha2"
908 }
909 "#;
910
911 let deserialized = serde_json::from_str(pickle).unwrap();
913
914 let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
916
917 assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
919
920 assert_let!(
923 SenderData::UnknownDevice { legacy_session, owner_check_failed } =
924 unpickled.sender_data
925 );
926 assert!(legacy_session);
927 assert!(!owner_check_failed);
928 }
929
930 #[async_test]
931 async fn test_can_serialise_pickled_session_with_sender_data() {
932 let igs = InboundGroupSession::new(
934 Curve25519PublicKey::from_base64("AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8")
935 .unwrap(),
936 Ed25519PublicKey::from_base64("wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww").unwrap(),
937 room_id!("!test:localhost"),
938 &create_session_key(),
939 SenderData::unknown(),
940 EventEncryptionAlgorithm::MegolmV1AesSha2,
941 Some(HistoryVisibility::Shared),
942 false,
943 )
944 .unwrap();
945
946 let pickled = igs.pickle().await;
948
949 let serialised = serde_json::to_string(&pickled).unwrap();
951
952 let expected_inner = vec![
956 193, 203, 223, 152, 33, 132, 200, 168, 24, 197, 79, 174, 231, 202, 45, 245, 128, 131,
957 178, 165, 148, 37, 241, 214, 178, 218, 25, 33, 68, 48, 153, 104, 122, 6, 249, 198, 97,
958 226, 214, 75, 64, 128, 25, 138, 98, 90, 138, 93, 52, 206, 174, 3, 84, 149, 101, 140,
959 238, 156, 103, 107, 124, 144, 139, 104, 253, 5, 100, 251, 186, 118, 208, 87, 31, 218,
960 123, 234, 103, 34, 246, 100, 39, 90, 216, 72, 187, 86, 202, 150, 100, 116, 204, 254,
961 10, 154, 216, 133, 61, 250, 75, 100, 195, 63, 138, 22, 17, 13, 156, 123, 195, 132, 111,
962 95, 250, 24, 236, 0, 246, 93, 230, 100, 211, 165, 211, 190, 181, 87, 42, 181,
963 ];
964 assert_eq!(
965 serde_json::from_str::<serde_json::Value>(&serialised).unwrap(),
966 serde_json::json!({
967 "pickle":{
968 "initial_ratchet":{
969 "inner": expected_inner,
970 "counter":0
971 },
972 "signing_key":[
973 213,161,95,135,114,153,162,127,217,74,64,2,59,143,93,5,190,157,120,
974 80,89,8,87,129,115,148,104,144,152,186,178,109
975 ],
976 "signing_key_verified":true,
977 "config":{"version":"V1"}
978 },
979 "sender_key":"AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
980 "signing_key":{"ed25519":"wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"},
981 "sender_data":{
982 "UnknownDevice":{
983 "legacy_session":false
984 }
985 },
986 "room_id":"!test:localhost",
987 "imported":false,
988 "backed_up":false,
989 "shared_history":false,
990 "history_visibility":"shared",
991 "algorithm":"m.megolm.v1.aes-sha2"
992 })
993 );
994 }
995
996 #[async_test]
997 async fn test_can_deserialise_pickled_session_with_sender_data() {
998 let pickle = r#"
1000 {
1001 "pickle": {
1002 "initial_ratchet": {
1003 "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
1004 220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
1005 229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
1006 202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
1007 138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
1008 245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
1009 2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
1010 205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
1011 52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
1012 "counter": 0
1013 },
1014 "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
1015 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
1016 149, 43, 38 ],
1017 "signing_key_verified": true,
1018 "config": {
1019 "version": "V1"
1020 }
1021 },
1022 "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
1023 "signing_key": {
1024 "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
1025 },
1026 "sender_data":{
1027 "UnknownDevice":{
1028 "legacy_session":false
1029 }
1030 },
1031 "room_id": "!test:localhost",
1032 "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
1033 "imported": false,
1034 "backed_up": false,
1035 "history_visibility": "shared",
1036 "algorithm": "m.megolm.v1.aes-sha2"
1037 }
1038 "#;
1039
1040 let deserialized = serde_json::from_str(pickle).unwrap();
1042
1043 let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
1045
1046 assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
1048
1049 assert_let!(
1052 SenderData::UnknownDevice { legacy_session, owner_check_failed } =
1053 unpickled.sender_data
1054 );
1055 assert!(!legacy_session);
1056 assert!(!owner_check_failed);
1057 }
1058
1059 #[async_test]
1060 async fn test_session_comparison() {
1061 let alice = Account::with_device_id(alice_id(), alice_device_id());
1062 let room_id = room_id!("!test:localhost");
1063
1064 let (_, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1065
1066 let worse = InboundGroupSession::from_export(&inbound.export_at_index(10).await).unwrap();
1067 let mut copy = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
1068
1069 assert_eq!(inbound.compare(&worse).await, SessionOrdering::Better);
1070 assert_eq!(worse.compare(&inbound).await, SessionOrdering::Worse);
1071 assert_eq!(inbound.compare(&inbound).await, SessionOrdering::Equal);
1072 assert_eq!(inbound.compare(©).await, SessionOrdering::Equal);
1073
1074 copy.creator_info.curve25519_key =
1075 Curve25519PublicKey::from_base64("XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY")
1076 .unwrap();
1077
1078 assert_eq!(inbound.compare(©).await, SessionOrdering::Unconnected);
1079 }
1080
1081 #[async_test]
1082 async fn test_session_comparison_sender_data() {
1083 let alice = Account::with_device_id(alice_id(), alice_device_id());
1084 let room_id = room_id!("!test:localhost");
1085
1086 let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1087
1088 let sender_data = SenderData::SenderVerified(KnownSenderData {
1089 user_id: alice.user_id().into(),
1090 device_id: Some(alice.device_id().into()),
1091 master_key: alice.identity_keys().ed25519.into(),
1092 });
1093
1094 let mut better = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
1095 better.sender_data = sender_data.clone();
1096
1097 assert_eq!(inbound.compare(&better).await, SessionOrdering::Worse);
1098 assert_eq!(better.compare(&inbound).await, SessionOrdering::Better);
1099
1100 inbound.sender_data = sender_data;
1101 assert_eq!(better.compare(&inbound).await, SessionOrdering::Equal);
1102 }
1103
1104 fn create_session_key() -> SessionKey {
1105 SessionKey::from_base64(
1106 "\
1107 AgAAAADBy9+YIYTIqBjFT67nyi31gIOypZQl8day2hkhRDCZaHoG+cZh4tZLQIAZimJail0\
1108 0zq4DVJVljO6cZ2t8kIto/QVk+7p20Fcf2nvqZyL2ZCda2Ei7VsqWZHTM/gqa2IU9+ktkwz\
1109 +KFhENnHvDhG9f+hjsAPZd5mTTpdO+tVcqtdWhX4dymaJ/2UpAAjuPXQW+nXhQWQhXgXOUa\
1110 JCYurJtvbCbqZGeDMmVIoqukBs2KugNJ6j5WlTPoeFnMl6Guy9uH2iWWxGg8ZgT2xspqVl5\
1111 CwujjC+m7Dh1toVkvu+bAw\
1112 ",
1113 )
1114 .unwrap()
1115 }
1116
1117 #[async_test]
1118 async fn test_shared_history_from_m_room_key_content() {
1119 let content = json!({
1120 "algorithm": "m.megolm.v1.aes-sha2",
1121 "room_id": "!Cuyf34gef24t:localhost",
1122 "org.matrix.msc3061.shared_history": true,
1123 "session_id": "ZFD6+OmV7fVCsJ7Gap8UnORH8EnmiAkes8FAvQuCw/I",
1124 "session_key": "AgAAAADNp1EbxXYOGmJtyX4AkD1bvJvAUyPkbIaKxtnGKjv\
1125 SQ3E/4mnuqdM4vsmNzpO1EeWzz1rDkUpYhYE9kP7sJhgLXi\
1126 jVv80fMPHfGc49hPdu8A+xnwD4SQiYdFmSWJOIqsxeo/fiH\
1127 tino//CDQENtcKuEt0I9s0+Kk4YSH310Szse2RQ+vjple31\
1128 QrCexmqfFJzkR/BJ5ogJHrPBQL0LgsPyglIbMTLg7qygIaY\
1129 U5Fe2QdKMH7nTZPNIRHh1RaMfHVETAUJBax88EWZBoifk80\
1130 gdHUwHSgMk77vCc2a5KHKLDA",
1131 });
1132
1133 let sender_key = Curve25519PublicKey::from_bytes([0; 32]);
1134 let signing_key = Ed25519PublicKey::from_slice(&[0; 32]).expect("");
1135 let mut content: room_key::MegolmV1AesSha2Content = serde_json::from_value(content)
1136 .expect("We should be able to deserialize the m.room_key content");
1137
1138 let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1139 .expect(
1140 "We should be able to create an inbound group session from the room key content",
1141 );
1142
1143 assert!(
1144 session.shared_history,
1145 "The shared history flag should be set as it was set in the m.room_key content"
1146 );
1147
1148 content.shared_history = false;
1149 let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1150 .expect(
1151 "We should be able to create an inbound group session from the room key content",
1152 );
1153
1154 assert!(
1155 !session.shared_history,
1156 "The shared history flag should not be set as it was not set in the m.room_key content"
1157 );
1158 }
1159
1160 #[async_test]
1161 async fn test_shared_history_from_exported_room_key() {
1162 let content = json!({
1163 "algorithm": "m.megolm.v1.aes-sha2",
1164 "room_id": "!room:id",
1165 "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
1166 "session_id": "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0",
1167 "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
1168 "sender_claimed_keys": {
1169 "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
1170 },
1171 "forwarding_curve25519_key_chain": [],
1172 "org.matrix.msc3061.shared_history": true
1173
1174 });
1175
1176 let mut content: ExportedRoomKey = serde_json::from_value(content)
1177 .expect("We should be able to deserialize the m.room_key content");
1178
1179 let session = InboundGroupSession::from_export(&content).expect(
1180 "We should be able to create an inbound group session from the room key export",
1181 );
1182 assert!(
1183 session.shared_history,
1184 "The shared history flag should be set as it was set in the exported room key"
1185 );
1186
1187 content.shared_history = false;
1188
1189 let session = InboundGroupSession::from_export(&content).expect(
1190 "We should be able to create an inbound group session from the room key export",
1191 );
1192 assert!(
1193 !session.shared_history,
1194 "The shared history flag should not be set as it was not set in the exported room key"
1195 );
1196 }
1197
1198 #[async_test]
1199 async fn test_shared_history_from_backed_up_room_key() {
1200 let content = json!({
1201 "algorithm": "m.megolm.v1.aes-sha2",
1202 "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
1203 "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
1204 "sender_claimed_keys": {
1205 "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
1206 },
1207 "forwarding_curve25519_key_chain": [],
1208 "org.matrix.msc3061.shared_history": true
1209
1210 });
1211
1212 let session_id = "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0";
1213 let room_id = owned_room_id!("!room:id");
1214 let room_key: BackedUpRoomKey = serde_json::from_value(content)
1215 .expect("We should be able to deserialize the backed up room key");
1216
1217 let room_key =
1218 ExportedRoomKey::from_backed_up_room_key(room_id, session_id.to_owned(), room_key);
1219
1220 let session = InboundGroupSession::from_export(&room_key).expect(
1221 "We should be able to create an inbound group session from the room key export",
1222 );
1223 assert!(
1224 session.shared_history,
1225 "The shared history flag should be set as it was set in the backed up room key"
1226 );
1227 }
1228
1229 #[async_test]
1230 async fn test_shared_history_in_pickle() {
1231 let alice = Account::with_device_id(alice_id(), alice_device_id());
1232 let room_id = room_id!("!test:localhost");
1233
1234 let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1235
1236 inbound.shared_history = true;
1237 let pickle = inbound.pickle().await;
1238
1239 assert!(
1240 pickle.shared_history,
1241 "The set shared history flag should have been copied to the pickle"
1242 );
1243
1244 inbound.shared_history = false;
1245 let pickle = inbound.pickle().await;
1246
1247 assert!(
1248 !pickle.shared_history,
1249 "The unset shared history flag should have been copied to the pickle"
1250 );
1251 }
1252
1253 #[async_test]
1254 async fn test_shared_history_in_export() {
1255 let alice = Account::with_device_id(alice_id(), alice_device_id());
1256 let room_id = room_id!("!test:localhost");
1257
1258 let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1259
1260 inbound.shared_history = true;
1261 let export = inbound.export().await;
1262 assert!(
1263 export.shared_history,
1264 "The set shared history flag should have been copied to the room key export"
1265 );
1266
1267 inbound.shared_history = false;
1268 let export = inbound.export().await;
1269 assert!(
1270 !export.shared_history,
1271 "The unset shared history flag should have been copied to the room key export"
1272 );
1273 }
1274}