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