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