1use std::{
16 cmp::Ordering,
17 fmt,
18 ops::Deref,
19 sync::{
20 Arc,
21 atomic::{AtomicBool, Ordering::SeqCst},
22 },
23};
24
25use ruma::{
26 DeviceKeyAlgorithm, OwnedRoomId, RoomId, events::room::history_visibility::HistoryVisibility,
27 serde::JsonObject,
28};
29use serde::{Deserialize, Serialize};
30use tokio::sync::Mutex;
31use vodozemac::{
32 Curve25519PublicKey, Ed25519PublicKey, PickleError,
33 megolm::{
34 DecryptedMessage, DecryptionError, InboundGroupSession as InnerSession,
35 InboundGroupSessionPickle, MegolmMessage, SessionConfig, SessionOrdering,
36 },
37};
38
39use super::{
40 BackedUpRoomKey, ExportedRoomKey, OutboundGroupSession, SenderData, SenderDataType,
41 SessionCreationError, SessionKey,
42};
43#[cfg(doc)]
44use crate::types::events::room_key::RoomKeyContent;
45use crate::{
46 error::{EventError, MegolmResult},
47 olm::group_sessions::forwarder_data::ForwarderData,
48 types::{
49 EventEncryptionAlgorithm, SigningKeys, deserialize_curve_key,
50 events::{
51 forwarded_room_key::{
52 ForwardedMegolmV1AesSha2Content, ForwardedMegolmV2AesSha2Content,
53 ForwardedRoomKeyContent,
54 },
55 olm_v1::DecryptedForwardedRoomKeyEvent,
56 room::encrypted::{EncryptedEvent, RoomEventEncryptionScheme},
57 room_key,
58 },
59 room_history::HistoricRoomKey,
60 serialize_curve_key,
61 },
62};
63#[derive(Clone)]
69pub(crate) struct SessionCreatorInfo {
70 pub curve25519_key: Curve25519PublicKey,
85
86 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 forwarder_data: Option<ForwarderData>,
196
197 pub room_id: OwnedRoomId,
199
200 imported: bool,
207
208 algorithm: Arc<EventEncryptionAlgorithm>,
213
214 history_visibility: Arc<Option<HistoryVisibility>>,
217
218 backed_up: Arc<AtomicBool>,
220
221 shared_history: bool,
227}
228
229impl InboundGroupSession {
230 #[allow(clippy::too_many_arguments)]
271 pub fn new(
272 sender_key: Curve25519PublicKey,
273 signing_key: Ed25519PublicKey,
274 room_id: &RoomId,
275 session_key: &SessionKey,
276 sender_data: SenderData,
277 forwarder_data: Option<ForwarderData>,
278 encryption_algorithm: EventEncryptionAlgorithm,
279 history_visibility: Option<HistoryVisibility>,
280 shared_history: bool,
281 ) -> Result<Self, SessionCreationError> {
282 let config = OutboundGroupSession::session_config(&encryption_algorithm)?;
283
284 let session = InnerSession::new(session_key, config);
285 let session_id = session.session_id();
286 let first_known_index = session.first_known_index();
287
288 let mut keys = SigningKeys::new();
289 keys.insert(DeviceKeyAlgorithm::Ed25519, signing_key.into());
290
291 Ok(InboundGroupSession {
292 inner: Arc::new(Mutex::new(session)),
293 history_visibility: history_visibility.into(),
294 session_id: session_id.into(),
295 first_known_index,
296 creator_info: SessionCreatorInfo {
297 curve25519_key: sender_key,
298 signing_keys: keys.into(),
299 },
300 sender_data,
301 forwarder_data,
302 room_id: room_id.into(),
303 imported: false,
304 algorithm: encryption_algorithm.into(),
305 backed_up: AtomicBool::new(false).into(),
306 shared_history,
307 })
308 }
309
310 pub fn from_room_key_content(
323 sender_key: Curve25519PublicKey,
324 signing_key: Ed25519PublicKey,
325 content: &room_key::MegolmV1AesSha2Content,
326 ) -> Result<Self, SessionCreationError> {
327 let room_key::MegolmV1AesSha2Content {
328 room_id,
329 session_id: _,
330 session_key,
331 shared_history,
332 ..
333 } = content;
334
335 Self::new(
336 sender_key,
337 signing_key,
338 room_id,
339 session_key,
340 SenderData::unknown(),
341 None,
342 EventEncryptionAlgorithm::MegolmV1AesSha2,
343 None,
344 *shared_history,
345 )
346 }
347
348 pub fn from_export(exported_session: &ExportedRoomKey) -> Result<Self, SessionCreationError> {
354 Self::try_from(exported_session)
355 }
356
357 pub(crate) fn with_ratchet(mut self, other: &InboundGroupSession) -> Self {
370 if self.session_id != other.session_id {
371 panic!(
372 "Attempt to merge Megolm sessions with different session IDs: {} vs {}",
373 self.session_id, other.session_id
374 );
375 }
376 if self.room_id != other.room_id {
377 panic!(
378 "Attempt to merge Megolm sessions with different room IDs: {} vs {}",
379 self.room_id, other.room_id,
380 );
381 }
382 self.inner = other.inner.clone();
383 self.first_known_index = other.first_known_index;
384 self
385 }
386
387 pub async fn pickle(&self) -> PickledInboundGroupSession {
390 let pickle = self.inner.lock().await.pickle();
391
392 PickledInboundGroupSession {
393 pickle,
394 sender_key: self.creator_info.curve25519_key,
395 signing_key: (*self.creator_info.signing_keys).clone(),
396 sender_data: self.sender_data.clone(),
397 forwarder_data: self.forwarder_data.clone(),
398 room_id: self.room_id().to_owned(),
399 imported: self.imported,
400 backed_up: self.backed_up(),
401 history_visibility: self.history_visibility.as_ref().clone(),
402 algorithm: (*self.algorithm).to_owned(),
403 shared_history: self.shared_history,
404 }
405 }
406
407 pub async fn export(&self) -> ExportedRoomKey {
412 self.export_at_index(self.first_known_index()).await
413 }
414
415 pub fn sender_key(&self) -> Curve25519PublicKey {
417 self.creator_info.curve25519_key
418 }
419
420 pub fn backed_up(&self) -> bool {
422 self.backed_up.load(SeqCst)
423 }
424
425 pub fn reset_backup_state(&self) {
427 self.backed_up.store(false, SeqCst)
428 }
429
430 pub fn mark_as_backed_up(&self) {
433 self.backed_up.store(true, SeqCst)
434 }
435
436 pub fn signing_keys(&self) -> &SigningKeys<DeviceKeyAlgorithm> {
438 &self.creator_info.signing_keys
439 }
440
441 pub async fn export_at_index(&self, message_index: u32) -> ExportedRoomKey {
443 let message_index = std::cmp::max(self.first_known_index(), message_index);
444
445 let session_key =
446 self.inner.lock().await.export_at(message_index).expect("Can't export session");
447
448 ExportedRoomKey {
449 algorithm: self.algorithm().to_owned(),
450 room_id: self.room_id().to_owned(),
451 sender_key: self.creator_info.curve25519_key,
452 session_id: self.session_id().to_owned(),
453 forwarding_curve25519_key_chain: vec![],
454 sender_claimed_keys: (*self.creator_info.signing_keys).clone(),
455 session_key,
456 shared_history: self.shared_history,
457 }
458 }
459
460 pub fn from_pickle(pickle: PickledInboundGroupSession) -> Result<Self, PickleError> {
471 let PickledInboundGroupSession {
472 pickle,
473 sender_key,
474 signing_key,
475 sender_data,
476 forwarder_data,
477 room_id,
478 imported,
479 backed_up,
480 history_visibility,
481 algorithm,
482 shared_history,
483 } = pickle;
484
485 let session: InnerSession = pickle.into();
486 let first_known_index = session.first_known_index();
487 let session_id = session.session_id();
488
489 Ok(InboundGroupSession {
490 inner: Mutex::new(session).into(),
491 session_id: session_id.into(),
492 creator_info: SessionCreatorInfo {
493 curve25519_key: sender_key,
494 signing_keys: signing_key.into(),
495 },
496 sender_data,
497 forwarder_data,
498 history_visibility: history_visibility.into(),
499 first_known_index,
500 room_id,
501 backed_up: AtomicBool::from(backed_up).into(),
502 algorithm: algorithm.into(),
503 imported,
504 shared_history,
505 })
506 }
507
508 pub fn room_id(&self) -> &RoomId {
510 &self.room_id
511 }
512
513 pub fn session_id(&self) -> &str {
515 &self.session_id
516 }
517
518 pub fn algorithm(&self) -> &EventEncryptionAlgorithm {
521 &self.algorithm
522 }
523
524 pub fn first_known_index(&self) -> u32 {
526 self.first_known_index
527 }
528
529 pub fn has_been_imported(&self) -> bool {
532 self.imported
533 }
534
535 #[deprecated(
538 note = "Sessions cannot be compared on a linear scale. Consider calling `compare_ratchet`, as well as comparing the `sender_data`."
539 )]
540 pub async fn compare(&self, other: &InboundGroupSession) -> SessionOrdering {
541 match self.compare_ratchet(other).await {
542 SessionOrdering::Equal => {
543 match self.sender_data.compare_trust_level(&other.sender_data) {
544 Ordering::Less => SessionOrdering::Worse,
545 Ordering::Equal => SessionOrdering::Equal,
546 Ordering::Greater => SessionOrdering::Better,
547 }
548 }
549 result => result,
550 }
551 }
552
553 pub async fn compare_ratchet(&self, other: &InboundGroupSession) -> SessionOrdering {
565 if Arc::ptr_eq(&self.inner, &other.inner) {
569 SessionOrdering::Equal
570 } else if self.sender_key() != other.sender_key()
571 || self.signing_keys() != other.signing_keys()
572 || self.algorithm() != other.algorithm()
573 || self.room_id() != other.room_id()
574 {
575 SessionOrdering::Unconnected
576 } else {
577 let mut other_inner = other.inner.lock().await;
578 self.inner.lock().await.compare(&mut other_inner)
579 }
580 }
581
582 pub(crate) async fn decrypt_helper(
591 &self,
592 message: &MegolmMessage,
593 ) -> Result<DecryptedMessage, DecryptionError> {
594 self.inner.lock().await.decrypt(message)
595 }
596
597 pub async fn to_backup(&self) -> BackedUpRoomKey {
600 self.export().await.into()
601 }
602
603 pub async fn decrypt(&self, event: &EncryptedEvent) -> MegolmResult<(JsonObject, u32)> {
609 let decrypted = match &event.content.scheme {
610 RoomEventEncryptionScheme::MegolmV1AesSha2(c) => {
611 self.decrypt_helper(&c.ciphertext).await?
612 }
613 #[cfg(feature = "experimental-algorithms")]
614 RoomEventEncryptionScheme::MegolmV2AesSha2(c) => {
615 self.decrypt_helper(&c.ciphertext).await?
616 }
617 RoomEventEncryptionScheme::Unknown(_) => {
618 return Err(EventError::UnsupportedAlgorithm.into());
619 }
620 };
621
622 let plaintext = String::from_utf8_lossy(&decrypted.plaintext);
623
624 let mut decrypted_object = serde_json::from_str::<JsonObject>(&plaintext)?;
625
626 let server_ts: i64 = event.origin_server_ts.0.into();
627
628 decrypted_object.insert("sender".to_owned(), event.sender.to_string().into());
629 decrypted_object.insert("event_id".to_owned(), event.event_id.to_string().into());
630 decrypted_object.insert("origin_server_ts".to_owned(), server_ts.into());
631
632 let room_id = decrypted_object
633 .get("room_id")
634 .and_then(|r| r.as_str().and_then(|r| RoomId::parse(r).ok()));
635
636 if room_id.as_deref() != Some(self.room_id()) {
639 return Err(EventError::MismatchedRoom(self.room_id().to_owned(), room_id).into());
640 }
641
642 decrypted_object.insert(
643 "unsigned".to_owned(),
644 serde_json::to_value(&event.unsigned).unwrap_or_default(),
645 );
646
647 if let Some(decrypted_content) =
648 decrypted_object.get_mut("content").and_then(|c| c.as_object_mut())
649 && !decrypted_content.contains_key("m.relates_to")
650 && let Some(relation) = &event.content.relates_to
651 {
652 decrypted_content.insert("m.relates_to".to_owned(), relation.to_owned());
653 }
654
655 Ok((decrypted_object, decrypted.message_index))
656 }
657
658 #[cfg(test)]
660 pub(crate) fn mark_as_imported(&mut self) {
661 self.imported = true;
662 }
663
664 pub fn sender_data_type(&self) -> SenderDataType {
668 self.sender_data.to_type()
669 }
670
671 pub fn shared_history(&self) -> bool {
677 self.shared_history
678 }
679}
680
681#[cfg(not(tarpaulin_include))]
682impl fmt::Debug for InboundGroupSession {
683 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
684 f.debug_struct("InboundGroupSession").field("session_id", &self.session_id()).finish()
685 }
686}
687
688impl PartialEq for InboundGroupSession {
689 fn eq(&self, other: &Self) -> bool {
690 self.session_id() == other.session_id()
691 }
692}
693
694#[derive(Serialize, Deserialize)]
699#[allow(missing_debug_implementations)]
700pub struct PickledInboundGroupSession {
701 pub pickle: InboundGroupSessionPickle,
703 #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
705 pub sender_key: Curve25519PublicKey,
706 pub signing_key: SigningKeys<DeviceKeyAlgorithm>,
708 #[serde(default)]
710 pub sender_data: SenderData,
711 #[serde(default)]
713 pub forwarder_data: Option<ForwarderData>,
714 pub room_id: OwnedRoomId,
716 pub imported: bool,
719 #[serde(default)]
721 pub backed_up: bool,
722 pub history_visibility: Option<HistoryVisibility>,
724 #[serde(default = "default_algorithm")]
726 pub algorithm: EventEncryptionAlgorithm,
727 #[serde(default)]
733 pub shared_history: bool,
734}
735
736fn default_algorithm() -> EventEncryptionAlgorithm {
737 EventEncryptionAlgorithm::MegolmV1AesSha2
738}
739
740impl HistoricRoomKey {
741 pub fn try_into_inbound_group_session(
764 &self,
765 forwarder_data: &ForwarderData,
766 ) -> Result<InboundGroupSession, SessionCreationError> {
767 let HistoricRoomKey {
768 algorithm,
769 room_id,
770 sender_key,
771 session_id,
772 session_key,
773 sender_claimed_keys,
774 } = self;
775
776 let config = OutboundGroupSession::session_config(algorithm)?;
777 let session = InnerSession::import(session_key, config);
778 let first_known_index = session.first_known_index();
779
780 Ok(InboundGroupSession {
781 inner: Mutex::new(session).into(),
782 session_id: session_id.to_owned().into(),
783 creator_info: SessionCreatorInfo {
784 curve25519_key: *sender_key,
785 signing_keys: sender_claimed_keys.to_owned().into(),
786 },
787 sender_data: SenderData::default(),
791 forwarder_data: Some(forwarder_data.clone()),
792 history_visibility: None.into(),
793 first_known_index,
794 room_id: room_id.to_owned(),
795 imported: true,
796 algorithm: algorithm.to_owned().into(),
797 backed_up: AtomicBool::from(false).into(),
798 shared_history: true,
799 })
800 }
801}
802
803impl TryFrom<&ExportedRoomKey> for InboundGroupSession {
804 type Error = SessionCreationError;
805
806 fn try_from(key: &ExportedRoomKey) -> Result<Self, Self::Error> {
807 let ExportedRoomKey {
808 algorithm,
809 room_id,
810 sender_key,
811 session_id,
812 session_key,
813 sender_claimed_keys,
814 forwarding_curve25519_key_chain: _,
815 shared_history,
816 } = key;
817
818 let config = OutboundGroupSession::session_config(algorithm)?;
819 let session = InnerSession::import(session_key, config);
820 let first_known_index = session.first_known_index();
821
822 Ok(InboundGroupSession {
823 inner: Mutex::new(session).into(),
824 session_id: session_id.to_owned().into(),
825 creator_info: SessionCreatorInfo {
826 curve25519_key: *sender_key,
827 signing_keys: sender_claimed_keys.to_owned().into(),
828 },
829 sender_data: SenderData::default(),
833 forwarder_data: None,
834 history_visibility: None.into(),
835 first_known_index,
836 room_id: room_id.to_owned(),
837 imported: true,
838 algorithm: algorithm.to_owned().into(),
839 backed_up: AtomicBool::from(false).into(),
840 shared_history: *shared_history,
841 })
842 }
843}
844
845impl From<&ForwardedMegolmV1AesSha2Content> for InboundGroupSession {
846 fn from(value: &ForwardedMegolmV1AesSha2Content) -> Self {
847 let session = InnerSession::import(&value.session_key, SessionConfig::version_1());
848 let session_id = session.session_id().into();
849 let first_known_index = session.first_known_index();
850
851 InboundGroupSession {
852 inner: Mutex::new(session).into(),
853 session_id,
854 creator_info: SessionCreatorInfo {
855 curve25519_key: value.claimed_sender_key,
856 signing_keys: SigningKeys::from([(
857 DeviceKeyAlgorithm::Ed25519,
858 value.claimed_ed25519_key.into(),
859 )])
860 .into(),
861 },
862 sender_data: SenderData::default(),
866 forwarder_data: None,
867 history_visibility: None.into(),
868 first_known_index,
869 room_id: value.room_id.to_owned(),
870 imported: true,
871 algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2.into(),
872 backed_up: AtomicBool::from(false).into(),
873 shared_history: false,
874 }
875 }
876}
877
878impl From<&ForwardedMegolmV2AesSha2Content> for InboundGroupSession {
879 fn from(value: &ForwardedMegolmV2AesSha2Content) -> Self {
880 let session = InnerSession::import(&value.session_key, SessionConfig::version_2());
881 let session_id = session.session_id().into();
882 let first_known_index = session.first_known_index();
883
884 InboundGroupSession {
885 inner: Mutex::new(session).into(),
886 session_id,
887 creator_info: SessionCreatorInfo {
888 curve25519_key: value.claimed_sender_key,
889 signing_keys: value.claimed_signing_keys.to_owned().into(),
890 },
891 sender_data: SenderData::default(),
895 forwarder_data: None,
896 history_visibility: None.into(),
897 first_known_index,
898 room_id: value.room_id.to_owned(),
899 imported: true,
900 algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2.into(),
901 backed_up: AtomicBool::from(false).into(),
902 shared_history: false,
903 }
904 }
905}
906
907impl TryFrom<&DecryptedForwardedRoomKeyEvent> for InboundGroupSession {
908 type Error = SessionCreationError;
909
910 fn try_from(value: &DecryptedForwardedRoomKeyEvent) -> Result<Self, Self::Error> {
911 match &value.content {
912 ForwardedRoomKeyContent::MegolmV1AesSha2(c) => Ok(Self::from(c.deref())),
913 #[cfg(feature = "experimental-algorithms")]
914 ForwardedRoomKeyContent::MegolmV2AesSha2(c) => Ok(Self::from(c.deref())),
915 ForwardedRoomKeyContent::Unknown(c) => {
916 Err(SessionCreationError::Algorithm(c.algorithm.to_owned()))
917 }
918 }
919 }
920}
921
922#[cfg(test)]
923mod tests {
924 use insta::{assert_json_snapshot, with_settings};
925 use matrix_sdk_test::async_test;
926 use ruma::{
927 DeviceId, UserId, device_id, events::room::history_visibility::HistoryVisibility,
928 owned_room_id, room_id, user_id,
929 };
930 use serde_json::json;
931 use similar_asserts::assert_eq;
932 use strass::assert_let;
933 use vodozemac::{
934 Curve25519PublicKey, Ed25519PublicKey,
935 megolm::{SessionKey, SessionOrdering},
936 };
937
938 use crate::{
939 Account,
940 olm::{BackedUpRoomKey, ExportedRoomKey, InboundGroupSession, KnownSenderData, SenderData},
941 types::{EventEncryptionAlgorithm, events::room_key},
942 };
943
944 fn alice_id() -> &'static UserId {
945 user_id!("@alice:example.org")
946 }
947
948 fn alice_device_id() -> &'static DeviceId {
949 device_id!("ALICEDEVICE")
950 }
951
952 #[async_test]
953 async fn test_pickle_snapshot() {
954 let account = Account::new(alice_id());
955 let room_id = room_id!("!test:localhost");
956 let (_, session) = account.create_group_session_pair_with_defaults(room_id).await;
957
958 let pickle = session.pickle().await;
959
960 with_settings!({prepend_module_to_snapshot => false}, {
961 assert_json_snapshot!(
962 "InboundGroupSession__test_pickle_snapshot__regression",
963 pickle,
964 {
965 ".pickle.initial_ratchet.inner" => "[ratchet]",
966 ".pickle.signing_key" => "[signing_key]",
967 ".sender_key" => "[sender_key]",
968 ".signing_key.ed25519" => "[ed25519_key]",
969 }
970 );
971 });
972 }
973
974 #[async_test]
975 async fn test_can_deserialise_pickled_session_without_sender_data() {
976 let pickle = r#"
979 {
980 "pickle": {
981 "initial_ratchet": {
982 "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
983 220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
984 229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
985 202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
986 138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
987 245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
988 2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
989 205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
990 52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
991 "counter": 0
992 },
993 "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
994 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
995 149, 43, 38 ],
996 "signing_key_verified": true,
997 "config": {
998 "version": "V1"
999 }
1000 },
1001 "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
1002 "signing_key": {
1003 "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
1004 },
1005 "room_id": "!test:localhost",
1006 "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
1007 "imported": false,
1008 "backed_up": false,
1009 "history_visibility": "shared",
1010 "algorithm": "m.megolm.v1.aes-sha2"
1011 }
1012 "#;
1013
1014 let deserialized = serde_json::from_str(pickle).unwrap();
1016
1017 let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
1019
1020 assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
1022
1023 assert_let!(
1026 SenderData::UnknownDevice { legacy_session, owner_check_failed } =
1027 unpickled.sender_data
1028 );
1029 assert!(legacy_session);
1030 assert!(!owner_check_failed);
1031 }
1032
1033 #[async_test]
1034 async fn test_can_serialise_pickled_session_with_sender_data() {
1035 let igs = InboundGroupSession::new(
1037 Curve25519PublicKey::from_base64("AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8")
1038 .unwrap(),
1039 Ed25519PublicKey::from_base64("wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww").unwrap(),
1040 room_id!("!test:localhost"),
1041 &create_session_key(),
1042 SenderData::unknown(),
1043 None,
1044 EventEncryptionAlgorithm::MegolmV1AesSha2,
1045 Some(HistoryVisibility::Shared),
1046 false,
1047 )
1048 .unwrap();
1049
1050 let pickled = igs.pickle().await;
1052
1053 let serialised = serde_json::to_string(&pickled).unwrap();
1055
1056 let expected_inner = vec![
1061 193, 203, 223, 152, 33, 132, 200, 168, 24, 197, 79, 174, 231, 202, 45, 245, 128, 131,
1062 178, 165, 148, 37, 241, 214, 178, 218, 25, 33, 68, 48, 153, 104, 122, 6, 249, 198, 97,
1063 226, 214, 75, 64, 128, 25, 138, 98, 90, 138, 93, 52, 206, 174, 3, 84, 149, 101, 140,
1064 238, 156, 103, 107, 124, 144, 139, 104, 253, 5, 100, 251, 186, 118, 208, 87, 31, 218,
1065 123, 234, 103, 34, 246, 100, 39, 90, 216, 72, 187, 86, 202, 150, 100, 116, 204, 254,
1066 10, 154, 216, 133, 61, 250, 75, 100, 195, 63, 138, 22, 17, 13, 156, 123, 195, 132, 111,
1067 95, 250, 24, 236, 0, 246, 93, 230, 100, 211, 165, 211, 190, 181, 87, 42, 181,
1068 ];
1069 assert_eq!(
1070 serde_json::from_str::<serde_json::Value>(&serialised).unwrap(),
1071 serde_json::json!({
1072 "pickle":{
1073 "initial_ratchet":{
1074 "inner": expected_inner,
1075 "counter":0
1076 },
1077 "signing_key":[
1078 213,161,95,135,114,153,162,127,217,74,64,2,59,143,93,5,190,157,120,
1079 80,89,8,87,129,115,148,104,144,152,186,178,109
1080 ],
1081 "signing_key_verified":true,
1082 "config":{"version":"V1"}
1083 },
1084 "sender_key":"AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
1085 "signing_key":{"ed25519":"wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"},
1086 "sender_data":{
1087 "UnknownDevice":{
1088 "legacy_session":false
1089 }
1090 },
1091 "forwarder_data":null,
1092 "room_id":"!test:localhost",
1093 "imported":false,
1094 "backed_up":false,
1095 "shared_history":false,
1096 "history_visibility":"shared",
1097 "algorithm":"m.megolm.v1.aes-sha2"
1098 })
1099 );
1100 }
1101
1102 #[async_test]
1103 async fn test_can_deserialise_pickled_session_with_sender_data() {
1104 let pickle = r#"
1107 {
1108 "pickle": {
1109 "initial_ratchet": {
1110 "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
1111 220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
1112 229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
1113 202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
1114 138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
1115 245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
1116 2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
1117 205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
1118 52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
1119 "counter": 0
1120 },
1121 "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
1122 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
1123 149, 43, 38 ],
1124 "signing_key_verified": true,
1125 "config": {
1126 "version": "V1"
1127 }
1128 },
1129 "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
1130 "signing_key": {
1131 "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
1132 },
1133 "sender_data":{
1134 "UnknownDevice":{
1135 "legacy_session":false
1136 }
1137 },
1138 "room_id": "!test:localhost",
1139 "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
1140 "imported": false,
1141 "backed_up": false,
1142 "history_visibility": "shared",
1143 "algorithm": "m.megolm.v1.aes-sha2"
1144 }
1145 "#;
1146
1147 let deserialized = serde_json::from_str(pickle).unwrap();
1149
1150 let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
1152
1153 assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
1155
1156 assert_let!(
1159 SenderData::UnknownDevice { legacy_session, owner_check_failed } =
1160 unpickled.sender_data
1161 );
1162 assert!(!legacy_session);
1163 assert!(!owner_check_failed);
1164 }
1165
1166 #[async_test]
1167 #[allow(deprecated)]
1168 async fn test_session_comparison() {
1169 let alice = Account::with_device_id(alice_id(), alice_device_id());
1170 let room_id = room_id!("!test:localhost");
1171
1172 let (_, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1173
1174 let worse = InboundGroupSession::from_export(&inbound.export_at_index(10).await).unwrap();
1175 let mut copy = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
1176
1177 assert_eq!(inbound.compare(&worse).await, SessionOrdering::Better);
1178 assert_eq!(inbound.compare_ratchet(&worse).await, SessionOrdering::Better);
1179 assert_eq!(worse.compare(&inbound).await, SessionOrdering::Worse);
1180 assert_eq!(worse.compare_ratchet(&inbound).await, SessionOrdering::Worse);
1181 assert_eq!(inbound.compare(&inbound).await, SessionOrdering::Equal);
1182 assert_eq!(inbound.compare_ratchet(&inbound).await, SessionOrdering::Equal);
1183 assert_eq!(inbound.compare(©).await, SessionOrdering::Equal);
1184 assert_eq!(inbound.compare_ratchet(©).await, SessionOrdering::Equal);
1185
1186 copy.creator_info.curve25519_key =
1187 Curve25519PublicKey::from_base64("XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY")
1188 .unwrap();
1189
1190 assert_eq!(inbound.compare(©).await, SessionOrdering::Unconnected);
1191 assert_eq!(inbound.compare_ratchet(©).await, SessionOrdering::Unconnected);
1192 }
1193
1194 #[async_test]
1195 #[allow(deprecated)]
1196 async fn test_session_comparison_sender_data() {
1197 let alice = Account::with_device_id(alice_id(), alice_device_id());
1198 let room_id = room_id!("!test:localhost");
1199
1200 let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1201
1202 let sender_data = SenderData::SenderVerified(KnownSenderData {
1203 user_id: alice.user_id().into(),
1204 device_id: Some(alice.device_id().into()),
1205 master_key: alice.identity_keys().ed25519.into(),
1206 });
1207
1208 let mut better = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
1209 better.sender_data = sender_data.clone();
1210
1211 assert_eq!(inbound.compare(&better).await, SessionOrdering::Worse);
1212 assert_eq!(better.compare(&inbound).await, SessionOrdering::Better);
1213
1214 inbound.sender_data = sender_data;
1215 assert_eq!(better.compare(&inbound).await, SessionOrdering::Equal);
1216 }
1217
1218 fn create_session_key() -> SessionKey {
1219 SessionKey::from_base64(
1220 "\
1221 AgAAAADBy9+YIYTIqBjFT67nyi31gIOypZQl8day2hkhRDCZaHoG+cZh4tZLQIAZimJail0\
1222 0zq4DVJVljO6cZ2t8kIto/QVk+7p20Fcf2nvqZyL2ZCda2Ei7VsqWZHTM/gqa2IU9+ktkwz\
1223 +KFhENnHvDhG9f+hjsAPZd5mTTpdO+tVcqtdWhX4dymaJ/2UpAAjuPXQW+nXhQWQhXgXOUa\
1224 JCYurJtvbCbqZGeDMmVIoqukBs2KugNJ6j5WlTPoeFnMl6Guy9uH2iWWxGg8ZgT2xspqVl5\
1225 CwujjC+m7Dh1toVkvu+bAw\
1226 ",
1227 )
1228 .unwrap()
1229 }
1230
1231 fn key_json(stable: bool) -> serde_json::Value {
1232 let shared_history =
1233 if stable { "m.shared_history" } else { "org.matrix.msc3061.shared_history" };
1234
1235 json!({
1236 "algorithm": "m.megolm.v1.aes-sha2",
1237 "room_id": "!Cuyf34gef24t:localhost",
1238 shared_history: true,
1239 "session_id": "ZFD6+OmV7fVCsJ7Gap8UnORH8EnmiAkes8FAvQuCw/I",
1240 "session_key": "AgAAAADNp1EbxXYOGmJtyX4AkD1bvJvAUyPkbIaKxtnGKjv\
1241 SQ3E/4mnuqdM4vsmNzpO1EeWzz1rDkUpYhYE9kP7sJhgLXi\
1242 jVv80fMPHfGc49hPdu8A+xnwD4SQiYdFmSWJOIqsxeo/fiH\
1243 tino//CDQENtcKuEt0I9s0+Kk4YSH310Szse2RQ+vjple31\
1244 QrCexmqfFJzkR/BJ5ogJHrPBQL0LgsPyglIbMTLg7qygIaY\
1245 U5Fe2QdKMH7nTZPNIRHh1RaMfHVETAUJBax88EWZBoifk80\
1246 gdHUwHSgMk77vCc2a5KHKLDA",
1247 })
1248 }
1249
1250 #[async_test]
1251 async fn test_shared_history_from_m_room_key_content_stable() {
1252 let content = key_json(true);
1253
1254 let sender_key = Curve25519PublicKey::from_bytes([0; 32]);
1255 let signing_key = Ed25519PublicKey::from_slice(&[0; 32]).expect("");
1256 let mut content: room_key::MegolmV1AesSha2Content = serde_json::from_value(content)
1257 .expect("We should be able to deserialize the m.room_key content");
1258
1259 let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1260 .expect(
1261 "We should be able to create an inbound group session from the room key content",
1262 );
1263
1264 assert!(
1265 session.shared_history,
1266 "The shared history flag should be set as it was set in the m.room_key content"
1267 );
1268
1269 content.shared_history = false;
1270 let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1271 .expect(
1272 "We should be able to create an inbound group session from the room key content",
1273 );
1274
1275 assert!(
1276 !session.shared_history,
1277 "The shared history flag should not be set as it was not set in the m.room_key content"
1278 );
1279 }
1280
1281 #[async_test]
1282 async fn test_shared_history_from_m_room_key_content_unstable() {
1283 let content = key_json(false);
1284
1285 let sender_key = Curve25519PublicKey::from_bytes([0; 32]);
1286 let signing_key = Ed25519PublicKey::from_slice(&[0; 32]).expect("");
1287 let mut content: room_key::MegolmV1AesSha2Content = serde_json::from_value(content)
1288 .expect("We should be able to deserialize the m.room_key content");
1289
1290 let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1291 .expect(
1292 "We should be able to create an inbound group session from the room key content",
1293 );
1294
1295 assert!(
1296 session.shared_history,
1297 "The shared history flag should be set as it was set in the m.room_key content"
1298 );
1299
1300 content.shared_history = false;
1301 let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1302 .expect(
1303 "We should be able to create an inbound group session from the room key content",
1304 );
1305
1306 assert!(
1307 !session.shared_history,
1308 "The shared history flag should not be set as it was not set in the m.room_key content"
1309 );
1310 }
1311
1312 fn exported_key_json(stable: bool) -> serde_json::Value {
1313 let shared_history =
1314 if stable { "m.shared_history" } else { "org.matrix.msc3061.shared_history" };
1315
1316 json!({
1317 "algorithm": "m.megolm.v1.aes-sha2",
1318 "room_id": "!room:id",
1319 "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
1320 "session_id": "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0",
1321 "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
1322 "sender_claimed_keys": {
1323 "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
1324 },
1325 "forwarding_curve25519_key_chain": [],
1326 shared_history: true
1327 })
1328 }
1329
1330 #[async_test]
1331 async fn test_shared_history_from_exported_room_key_stable() {
1332 let content = exported_key_json(true);
1333
1334 let mut content: ExportedRoomKey = serde_json::from_value(content)
1335 .expect("We should be able to deserialize the m.room_key content");
1336
1337 let session = InboundGroupSession::from_export(&content).expect(
1338 "We should be able to create an inbound group session from the room key export",
1339 );
1340 assert!(
1341 session.shared_history,
1342 "The shared history flag should be set as it was set in the exported room key"
1343 );
1344
1345 content.shared_history = false;
1346
1347 let session = InboundGroupSession::from_export(&content).expect(
1348 "We should be able to create an inbound group session from the room key export",
1349 );
1350 assert!(
1351 !session.shared_history,
1352 "The shared history flag should not be set as it was not set in the exported room key"
1353 );
1354 }
1355
1356 #[async_test]
1357 async fn test_shared_history_from_exported_room_key_unstable() {
1358 let content = exported_key_json(false);
1359
1360 let mut content: ExportedRoomKey = serde_json::from_value(content)
1361 .expect("We should be able to deserialize the m.room_key content");
1362
1363 let session = InboundGroupSession::from_export(&content).expect(
1364 "We should be able to create an inbound group session from the room key export",
1365 );
1366 assert!(
1367 session.shared_history,
1368 "The shared history flag should be set as it was set in the exported room key"
1369 );
1370
1371 content.shared_history = false;
1372
1373 let session = InboundGroupSession::from_export(&content).expect(
1374 "We should be able to create an inbound group session from the room key export",
1375 );
1376 assert!(
1377 !session.shared_history,
1378 "The shared history flag should not be set as it was not set in the exported room key"
1379 );
1380 }
1381
1382 fn backed_up_room_key(stable: bool) -> serde_json::Value {
1383 let shared_history =
1384 if stable { "m.shared_history" } else { "org.matrix.msc3061.shared_history" };
1385
1386 json!({
1387 "algorithm": "m.megolm.v1.aes-sha2",
1388 "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
1389 "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
1390 "sender_claimed_keys": {
1391 "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
1392 },
1393 "forwarding_curve25519_key_chain": [],
1394 shared_history: true
1395 })
1396 }
1397
1398 #[async_test]
1399 async fn test_shared_history_from_backed_up_room_key_stable() {
1400 let content = backed_up_room_key(true);
1401
1402 let session_id = "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0";
1403 let room_id = owned_room_id!("!room:id");
1404 let room_key: BackedUpRoomKey = serde_json::from_value(content)
1405 .expect("We should be able to deserialize the backed up room key");
1406
1407 let room_key =
1408 ExportedRoomKey::from_backed_up_room_key(room_id, session_id.to_owned(), room_key);
1409
1410 let session = InboundGroupSession::from_export(&room_key).expect(
1411 "We should be able to create an inbound group session from the room key export",
1412 );
1413 assert!(
1414 session.shared_history,
1415 "The shared history flag should be set as it was set in the backed up room key"
1416 );
1417 }
1418
1419 #[async_test]
1420 async fn test_shared_history_from_backed_up_room_key_unstable() {
1421 let content = backed_up_room_key(false);
1422
1423 let session_id = "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0";
1424 let room_id = owned_room_id!("!room:id");
1425 let room_key: BackedUpRoomKey = serde_json::from_value(content)
1426 .expect("We should be able to deserialize the backed up room key");
1427
1428 let room_key =
1429 ExportedRoomKey::from_backed_up_room_key(room_id, session_id.to_owned(), room_key);
1430
1431 let session = InboundGroupSession::from_export(&room_key).expect(
1432 "We should be able to create an inbound group session from the room key export",
1433 );
1434 assert!(
1435 session.shared_history,
1436 "The shared history flag should be set as it was set in the backed up room key"
1437 );
1438 }
1439
1440 #[async_test]
1441 async fn test_shared_history_in_pickle() {
1442 let alice = Account::with_device_id(alice_id(), alice_device_id());
1443 let room_id = room_id!("!test:localhost");
1444
1445 let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1446
1447 inbound.shared_history = true;
1448 let pickle = inbound.pickle().await;
1449
1450 assert!(
1451 pickle.shared_history,
1452 "The set shared history flag should have been copied to the pickle"
1453 );
1454
1455 inbound.shared_history = false;
1456 let pickle = inbound.pickle().await;
1457
1458 assert!(
1459 !pickle.shared_history,
1460 "The unset shared history flag should have been copied to the pickle"
1461 );
1462 }
1463
1464 #[async_test]
1465 async fn test_shared_history_in_export() {
1466 let alice = Account::with_device_id(alice_id(), alice_device_id());
1467 let room_id = room_id!("!test:localhost");
1468
1469 let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1470
1471 inbound.shared_history = true;
1472 let export = inbound.export().await;
1473 assert!(
1474 export.shared_history,
1475 "The set shared history flag should have been copied to the room key export"
1476 );
1477
1478 inbound.shared_history = false;
1479 let export = inbound.export().await;
1480 assert!(
1481 !export.shared_history,
1482 "The unset shared history flag should have been copied to the room key export"
1483 );
1484 }
1485}