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::{
942 EventEncryptionAlgorithm,
943 events::{room_key, room_key::tests::SharedHistoryField},
944 },
945 };
946
947 fn alice_id() -> &'static UserId {
948 user_id!("@alice:example.org")
949 }
950
951 fn alice_device_id() -> &'static DeviceId {
952 device_id!("ALICEDEVICE")
953 }
954
955 #[async_test]
956 async fn test_pickle_snapshot() {
957 let account = Account::new(alice_id());
958 let room_id = room_id!("!test:localhost");
959 let (_, session) = account.create_group_session_pair_with_defaults(room_id).await;
960
961 let pickle = session.pickle().await;
962
963 with_settings!({prepend_module_to_snapshot => false}, {
964 assert_json_snapshot!(
965 "InboundGroupSession__test_pickle_snapshot__regression",
966 pickle,
967 {
968 ".pickle.initial_ratchet.inner" => "[ratchet]",
969 ".pickle.signing_key" => "[signing_key]",
970 ".sender_key" => "[sender_key]",
971 ".signing_key.ed25519" => "[ed25519_key]",
972 }
973 );
974 });
975 }
976
977 #[async_test]
978 async fn test_can_deserialise_pickled_session_without_sender_data() {
979 let pickle = r#"
982 {
983 "pickle": {
984 "initial_ratchet": {
985 "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
986 220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
987 229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
988 202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
989 138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
990 245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
991 2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
992 205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
993 52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
994 "counter": 0
995 },
996 "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
997 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
998 149, 43, 38 ],
999 "signing_key_verified": true,
1000 "config": {
1001 "version": "V1"
1002 }
1003 },
1004 "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
1005 "signing_key": {
1006 "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
1007 },
1008 "room_id": "!test:localhost",
1009 "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
1010 "imported": false,
1011 "backed_up": false,
1012 "history_visibility": "shared",
1013 "algorithm": "m.megolm.v1.aes-sha2"
1014 }
1015 "#;
1016
1017 let deserialized = serde_json::from_str(pickle).unwrap();
1019
1020 let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
1022
1023 assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
1025
1026 assert_let!(
1029 SenderData::UnknownDevice { legacy_session, owner_check_failed } =
1030 unpickled.sender_data
1031 );
1032 assert!(legacy_session);
1033 assert!(!owner_check_failed);
1034 }
1035
1036 #[async_test]
1037 async fn test_can_serialise_pickled_session_with_sender_data() {
1038 let igs = InboundGroupSession::new(
1040 Curve25519PublicKey::from_base64("AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8")
1041 .unwrap(),
1042 Ed25519PublicKey::from_base64("wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww").unwrap(),
1043 room_id!("!test:localhost"),
1044 &create_session_key(),
1045 SenderData::unknown(),
1046 None,
1047 EventEncryptionAlgorithm::MegolmV1AesSha2,
1048 Some(HistoryVisibility::Shared),
1049 false,
1050 )
1051 .unwrap();
1052
1053 let pickled = igs.pickle().await;
1055
1056 let serialised = serde_json::to_string(&pickled).unwrap();
1058
1059 let expected_inner = vec![
1064 193, 203, 223, 152, 33, 132, 200, 168, 24, 197, 79, 174, 231, 202, 45, 245, 128, 131,
1065 178, 165, 148, 37, 241, 214, 178, 218, 25, 33, 68, 48, 153, 104, 122, 6, 249, 198, 97,
1066 226, 214, 75, 64, 128, 25, 138, 98, 90, 138, 93, 52, 206, 174, 3, 84, 149, 101, 140,
1067 238, 156, 103, 107, 124, 144, 139, 104, 253, 5, 100, 251, 186, 118, 208, 87, 31, 218,
1068 123, 234, 103, 34, 246, 100, 39, 90, 216, 72, 187, 86, 202, 150, 100, 116, 204, 254,
1069 10, 154, 216, 133, 61, 250, 75, 100, 195, 63, 138, 22, 17, 13, 156, 123, 195, 132, 111,
1070 95, 250, 24, 236, 0, 246, 93, 230, 100, 211, 165, 211, 190, 181, 87, 42, 181,
1071 ];
1072 assert_eq!(
1073 serde_json::from_str::<serde_json::Value>(&serialised).unwrap(),
1074 serde_json::json!({
1075 "pickle":{
1076 "initial_ratchet":{
1077 "inner": expected_inner,
1078 "counter":0
1079 },
1080 "signing_key":[
1081 213,161,95,135,114,153,162,127,217,74,64,2,59,143,93,5,190,157,120,
1082 80,89,8,87,129,115,148,104,144,152,186,178,109
1083 ],
1084 "signing_key_verified":true,
1085 "config":{"version":"V1"}
1086 },
1087 "sender_key":"AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
1088 "signing_key":{"ed25519":"wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"},
1089 "sender_data":{
1090 "UnknownDevice":{
1091 "legacy_session":false
1092 }
1093 },
1094 "forwarder_data":null,
1095 "room_id":"!test:localhost",
1096 "imported":false,
1097 "backed_up":false,
1098 "shared_history":false,
1099 "history_visibility":"shared",
1100 "algorithm":"m.megolm.v1.aes-sha2"
1101 })
1102 );
1103 }
1104
1105 #[async_test]
1106 async fn test_can_deserialise_pickled_session_with_sender_data() {
1107 let pickle = r#"
1110 {
1111 "pickle": {
1112 "initial_ratchet": {
1113 "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
1114 220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
1115 229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
1116 202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
1117 138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
1118 245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
1119 2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
1120 205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
1121 52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
1122 "counter": 0
1123 },
1124 "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
1125 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
1126 149, 43, 38 ],
1127 "signing_key_verified": true,
1128 "config": {
1129 "version": "V1"
1130 }
1131 },
1132 "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
1133 "signing_key": {
1134 "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
1135 },
1136 "sender_data":{
1137 "UnknownDevice":{
1138 "legacy_session":false
1139 }
1140 },
1141 "room_id": "!test:localhost",
1142 "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
1143 "imported": false,
1144 "backed_up": false,
1145 "history_visibility": "shared",
1146 "algorithm": "m.megolm.v1.aes-sha2"
1147 }
1148 "#;
1149
1150 let deserialized = serde_json::from_str(pickle).unwrap();
1152
1153 let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
1155
1156 assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
1158
1159 assert_let!(
1162 SenderData::UnknownDevice { legacy_session, owner_check_failed } =
1163 unpickled.sender_data
1164 );
1165 assert!(!legacy_session);
1166 assert!(!owner_check_failed);
1167 }
1168
1169 #[async_test]
1170 #[allow(deprecated)]
1171 async fn test_session_comparison() {
1172 let alice = Account::with_device_id(alice_id(), alice_device_id());
1173 let room_id = room_id!("!test:localhost");
1174
1175 let (_, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1176
1177 let worse = InboundGroupSession::from_export(&inbound.export_at_index(10).await).unwrap();
1178 let mut copy = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
1179
1180 assert_eq!(inbound.compare(&worse).await, SessionOrdering::Better);
1181 assert_eq!(inbound.compare_ratchet(&worse).await, SessionOrdering::Better);
1182 assert_eq!(worse.compare(&inbound).await, SessionOrdering::Worse);
1183 assert_eq!(worse.compare_ratchet(&inbound).await, SessionOrdering::Worse);
1184 assert_eq!(inbound.compare(&inbound).await, SessionOrdering::Equal);
1185 assert_eq!(inbound.compare_ratchet(&inbound).await, SessionOrdering::Equal);
1186 assert_eq!(inbound.compare(©).await, SessionOrdering::Equal);
1187 assert_eq!(inbound.compare_ratchet(©).await, SessionOrdering::Equal);
1188
1189 copy.creator_info.curve25519_key =
1190 Curve25519PublicKey::from_base64("XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY")
1191 .unwrap();
1192
1193 assert_eq!(inbound.compare(©).await, SessionOrdering::Unconnected);
1194 assert_eq!(inbound.compare_ratchet(©).await, SessionOrdering::Unconnected);
1195 }
1196
1197 #[async_test]
1198 #[allow(deprecated)]
1199 async fn test_session_comparison_sender_data() {
1200 let alice = Account::with_device_id(alice_id(), alice_device_id());
1201 let room_id = room_id!("!test:localhost");
1202
1203 let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1204
1205 let sender_data = SenderData::SenderVerified(KnownSenderData {
1206 user_id: alice.user_id().into(),
1207 device_id: Some(alice.device_id().into()),
1208 master_key: alice.identity_keys().ed25519.into(),
1209 });
1210
1211 let mut better = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
1212 better.sender_data = sender_data.clone();
1213
1214 assert_eq!(inbound.compare(&better).await, SessionOrdering::Worse);
1215 assert_eq!(better.compare(&inbound).await, SessionOrdering::Better);
1216
1217 inbound.sender_data = sender_data;
1218 assert_eq!(better.compare(&inbound).await, SessionOrdering::Equal);
1219 }
1220
1221 fn create_session_key() -> SessionKey {
1222 SessionKey::from_base64(
1223 "\
1224 AgAAAADBy9+YIYTIqBjFT67nyi31gIOypZQl8day2hkhRDCZaHoG+cZh4tZLQIAZimJail0\
1225 0zq4DVJVljO6cZ2t8kIto/QVk+7p20Fcf2nvqZyL2ZCda2Ei7VsqWZHTM/gqa2IU9+ktkwz\
1226 +KFhENnHvDhG9f+hjsAPZd5mTTpdO+tVcqtdWhX4dymaJ/2UpAAjuPXQW+nXhQWQhXgXOUa\
1227 JCYurJtvbCbqZGeDMmVIoqukBs2KugNJ6j5WlTPoeFnMl6Guy9uH2iWWxGg8ZgT2xspqVl5\
1228 CwujjC+m7Dh1toVkvu+bAw\
1229 ",
1230 )
1231 .unwrap()
1232 }
1233
1234 fn key_json(shared_history: SharedHistoryField) -> serde_json::Value {
1235 let shared_history = shared_history.name();
1236
1237 json!({
1238 "algorithm": "m.megolm.v1.aes-sha2",
1239 "room_id": "!Cuyf34gef24t:localhost",
1240 shared_history: true,
1241 "session_id": "ZFD6+OmV7fVCsJ7Gap8UnORH8EnmiAkes8FAvQuCw/I",
1242 "session_key": "AgAAAADNp1EbxXYOGmJtyX4AkD1bvJvAUyPkbIaKxtnGKjv\
1243 SQ3E/4mnuqdM4vsmNzpO1EeWzz1rDkUpYhYE9kP7sJhgLXi\
1244 jVv80fMPHfGc49hPdu8A+xnwD4SQiYdFmSWJOIqsxeo/fiH\
1245 tino//CDQENtcKuEt0I9s0+Kk4YSH310Szse2RQ+vjple31\
1246 QrCexmqfFJzkR/BJ5ogJHrPBQL0LgsPyglIbMTLg7qygIaY\
1247 U5Fe2QdKMH7nTZPNIRHh1RaMfHVETAUJBax88EWZBoifk80\
1248 gdHUwHSgMk77vCc2a5KHKLDA",
1249 })
1250 }
1251
1252 fn check_shared_history_from_m_room_key_content(shared_history: SharedHistoryField) {
1253 let content = key_json(shared_history);
1254
1255 let sender_key = Curve25519PublicKey::from_bytes([0; 32]);
1256 let signing_key = Ed25519PublicKey::from_slice(&[0; 32]).expect("");
1257 let mut content: room_key::MegolmV1AesSha2Content = serde_json::from_value(content)
1258 .expect("We should be able to deserialize the m.room_key content");
1259
1260 let serialized = serde_json::to_value(&content)
1261 .expect("We should be able to serialize the m.room_key content");
1262 assert_eq!(
1263 serialized,
1264 key_json(SharedHistoryField::Spec),
1265 "The m.room_key content should be serialized with the name from the spec"
1266 );
1267
1268 let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1269 .expect(
1270 "We should be able to create an inbound group session from the room key content",
1271 );
1272
1273 assert!(
1274 session.shared_history,
1275 "The shared history flag should be set as it was set in the m.room_key content"
1276 );
1277
1278 content.shared_history = false;
1279 let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1280 .expect(
1281 "We should be able to create an inbound group session from the room key content",
1282 );
1283
1284 assert!(
1285 !session.shared_history,
1286 "The shared history flag should not be set as it was not set in the m.room_key content"
1287 );
1288 }
1289
1290 #[test]
1291 fn test_shared_history_from_m_room_key_content_stable() {
1292 check_shared_history_from_m_room_key_content(SharedHistoryField::Spec);
1293 }
1294
1295 #[test]
1296 fn test_shared_history_from_m_room_key_content_legacy() {
1297 check_shared_history_from_m_room_key_content(SharedHistoryField::Legacy);
1298 }
1299
1300 #[test]
1301 fn test_shared_history_from_m_room_key_content_unstable() {
1302 check_shared_history_from_m_room_key_content(SharedHistoryField::Unstable);
1303 }
1304
1305 fn exported_key_json(shared_history: SharedHistoryField) -> serde_json::Value {
1306 let shared_history = shared_history.name();
1307
1308 json!({
1309 "algorithm": "m.megolm.v1.aes-sha2",
1310 "room_id": "!room:id",
1311 "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
1312 "session_id": "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0",
1313 "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
1314 "sender_claimed_keys": {
1315 "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
1316 },
1317 "forwarding_curve25519_key_chain": [],
1318 shared_history: true
1319 })
1320 }
1321
1322 fn check_shared_history_from_exported_room_key(shared_history: SharedHistoryField) {
1323 let content = exported_key_json(shared_history);
1324
1325 let mut content: ExportedRoomKey = serde_json::from_value(content)
1326 .expect("We should be able to deserialize the m.room_key content");
1327
1328 let serialized = serde_json::to_value(&content)
1329 .expect("We should be able to serialize the exported room key");
1330 assert_eq!(
1331 serialized,
1332 exported_key_json(SharedHistoryField::Spec),
1333 "The exported room key should be serialized with the name from the spec"
1334 );
1335
1336 let session = InboundGroupSession::from_export(&content).expect(
1337 "We should be able to create an inbound group session from the room key export",
1338 );
1339 assert!(
1340 session.shared_history,
1341 "The shared history flag should be set as it was set in the exported room key"
1342 );
1343
1344 content.shared_history = false;
1345
1346 let session = InboundGroupSession::from_export(&content).expect(
1347 "We should be able to create an inbound group session from the room key export",
1348 );
1349 assert!(
1350 !session.shared_history,
1351 "The shared history flag should not be set as it was not set in the exported room key"
1352 );
1353 }
1354
1355 #[test]
1356 fn test_shared_history_from_exported_room_key_stable() {
1357 check_shared_history_from_exported_room_key(SharedHistoryField::Spec);
1358 }
1359
1360 #[test]
1361 fn test_shared_history_from_exported_room_key_legacy() {
1362 check_shared_history_from_exported_room_key(SharedHistoryField::Legacy);
1363 }
1364
1365 #[test]
1366 fn test_shared_history_from_exported_room_key_unstable() {
1367 check_shared_history_from_exported_room_key(SharedHistoryField::Unstable);
1368 }
1369
1370 fn backed_up_room_key(shared_history: SharedHistoryField) -> serde_json::Value {
1371 let shared_history = shared_history.name();
1372
1373 json!({
1374 "algorithm": "m.megolm.v1.aes-sha2",
1375 "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
1376 "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
1377 "sender_claimed_keys": {
1378 "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
1379 },
1380 "forwarding_curve25519_key_chain": [],
1381 shared_history: true
1382 })
1383 }
1384
1385 fn check_shared_history_from_backed_up_room_key(shared_history: SharedHistoryField) {
1386 let content = backed_up_room_key(shared_history);
1387
1388 let session_id = "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0";
1389 let room_id = owned_room_id!("!room:id");
1390 let room_key: BackedUpRoomKey = serde_json::from_value(content)
1391 .expect("We should be able to deserialize the backed up room key");
1392
1393 let serialized = serde_json::to_value(&room_key)
1394 .expect("We should be able to serialize the backed up room key");
1395 assert_eq!(
1396 serialized,
1397 backed_up_room_key(SharedHistoryField::Spec),
1398 "The backed up room key should be serialized with the name from the spec"
1399 );
1400
1401 let room_key =
1402 ExportedRoomKey::from_backed_up_room_key(room_id, session_id.to_owned(), room_key);
1403
1404 let session = InboundGroupSession::from_export(&room_key).expect(
1405 "We should be able to create an inbound group session from the room key export",
1406 );
1407 assert!(
1408 session.shared_history,
1409 "The shared history flag should be set as it was set in the backed up room key"
1410 );
1411 }
1412
1413 #[test]
1414 fn test_shared_history_from_backed_up_room_key_stable() {
1415 check_shared_history_from_backed_up_room_key(SharedHistoryField::Spec);
1416 }
1417
1418 #[test]
1419 fn test_shared_history_from_backed_up_room_key_legacy() {
1420 check_shared_history_from_backed_up_room_key(SharedHistoryField::Legacy);
1421 }
1422
1423 #[test]
1424 fn test_shared_history_from_backed_up_room_key_unstable() {
1425 check_shared_history_from_backed_up_room_key(SharedHistoryField::Unstable);
1426 }
1427
1428 #[async_test]
1429 async fn test_shared_history_in_pickle() {
1430 let alice = Account::with_device_id(alice_id(), alice_device_id());
1431 let room_id = room_id!("!test:localhost");
1432
1433 let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1434
1435 inbound.shared_history = true;
1436 let pickle = inbound.pickle().await;
1437
1438 assert!(
1439 pickle.shared_history,
1440 "The set shared history flag should have been copied to the pickle"
1441 );
1442
1443 inbound.shared_history = false;
1444 let pickle = inbound.pickle().await;
1445
1446 assert!(
1447 !pickle.shared_history,
1448 "The unset shared history flag should have been copied to the pickle"
1449 );
1450 }
1451
1452 #[async_test]
1453 async fn test_shared_history_in_export() {
1454 let alice = Account::with_device_id(alice_id(), alice_device_id());
1455 let room_id = room_id!("!test:localhost");
1456
1457 let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1458
1459 inbound.shared_history = true;
1460 let export = inbound.export().await;
1461 assert!(
1462 export.shared_history,
1463 "The set shared history flag should have been copied to the room key export"
1464 );
1465
1466 inbound.shared_history = false;
1467 let export = inbound.export().await;
1468 assert!(
1469 !export.shared_history,
1470 "The unset shared history flag should have been copied to the room key export"
1471 );
1472 }
1473}