1use std::{
16 cmp::Ordering,
17 fmt,
18 ops::Deref,
19 sync::{
20 atomic::{AtomicBool, Ordering::SeqCst},
21 Arc,
22 },
23};
24
25use ruma::{
26 events::room::history_visibility::HistoryVisibility, serde::JsonObject, DeviceKeyAlgorithm,
27 OwnedRoomId, RoomId,
28};
29use serde::{Deserialize, Serialize};
30use tokio::sync::Mutex;
31use vodozemac::{
32 megolm::{
33 DecryptedMessage, DecryptionError, InboundGroupSession as InnerSession,
34 InboundGroupSessionPickle, MegolmMessage, SessionConfig, SessionOrdering,
35 },
36 Curve25519PublicKey, Ed25519PublicKey, PickleError,
37};
38
39use super::{
40 BackedUpRoomKey, ExportedRoomKey, OutboundGroupSession, SenderData, SenderDataType,
41 SessionCreationError, SessionKey,
42};
43#[cfg(doc)]
44use crate::types::events::room_key::RoomKeyContent;
45use crate::{
46 error::{EventError, MegolmResult},
47 types::{
48 deserialize_curve_key,
49 events::{
50 forwarded_room_key::{
51 ForwardedMegolmV1AesSha2Content, ForwardedMegolmV2AesSha2Content,
52 ForwardedRoomKeyContent,
53 },
54 olm_v1::DecryptedForwardedRoomKeyEvent,
55 room::encrypted::{EncryptedEvent, RoomEventEncryptionScheme},
56 room_key,
57 },
58 room_history::HistoricRoomKey,
59 serialize_curve_key, EventEncryptionAlgorithm, SigningKeys,
60 },
61};
62#[derive(Clone)]
68pub(crate) struct SessionCreatorInfo {
69 pub curve25519_key: Curve25519PublicKey,
84
85 pub signing_keys: Arc<SigningKeys<DeviceKeyAlgorithm>>,
99}
100
101#[derive(Clone)]
167pub struct InboundGroupSession {
168 inner: Arc<Mutex<InnerSession>>,
169
170 session_id: Arc<str>,
173
174 first_known_index: u32,
177
178 pub(crate) creator_info: SessionCreatorInfo,
182
183 pub sender_data: SenderData,
189
190 pub room_id: OwnedRoomId,
192
193 imported: bool,
200
201 algorithm: Arc<EventEncryptionAlgorithm>,
206
207 history_visibility: Arc<Option<HistoryVisibility>>,
210
211 backed_up: Arc<AtomicBool>,
213
214 shared_history: bool,
220}
221
222impl InboundGroupSession {
223 #[allow(clippy::too_many_arguments)]
260 pub fn new(
261 sender_key: Curve25519PublicKey,
262 signing_key: Ed25519PublicKey,
263 room_id: &RoomId,
264 session_key: &SessionKey,
265 sender_data: SenderData,
266 encryption_algorithm: EventEncryptionAlgorithm,
267 history_visibility: Option<HistoryVisibility>,
268 shared_history: bool,
269 ) -> Result<Self, SessionCreationError> {
270 let config = OutboundGroupSession::session_config(&encryption_algorithm)?;
271
272 let session = InnerSession::new(session_key, config);
273 let session_id = session.session_id();
274 let first_known_index = session.first_known_index();
275
276 let mut keys = SigningKeys::new();
277 keys.insert(DeviceKeyAlgorithm::Ed25519, signing_key.into());
278
279 Ok(InboundGroupSession {
280 inner: Arc::new(Mutex::new(session)),
281 history_visibility: history_visibility.into(),
282 session_id: session_id.into(),
283 first_known_index,
284 creator_info: SessionCreatorInfo {
285 curve25519_key: sender_key,
286 signing_keys: keys.into(),
287 },
288 sender_data,
289 room_id: room_id.into(),
290 imported: false,
291 algorithm: encryption_algorithm.into(),
292 backed_up: AtomicBool::new(false).into(),
293 shared_history,
294 })
295 }
296
297 pub fn from_room_key_content(
310 sender_key: Curve25519PublicKey,
311 signing_key: Ed25519PublicKey,
312 content: &room_key::MegolmV1AesSha2Content,
313 ) -> Result<Self, SessionCreationError> {
314 let room_key::MegolmV1AesSha2Content {
315 room_id,
316 session_id: _,
317 session_key,
318 shared_history,
319 ..
320 } = content;
321
322 Self::new(
323 sender_key,
324 signing_key,
325 room_id,
326 session_key,
327 SenderData::unknown(),
328 EventEncryptionAlgorithm::MegolmV1AesSha2,
329 None,
330 *shared_history,
331 )
332 }
333
334 pub fn from_export(exported_session: &ExportedRoomKey) -> Result<Self, SessionCreationError> {
340 Self::try_from(exported_session)
341 }
342
343 pub(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 {
633 if !decrypted_content.contains_key("m.relates_to") {
634 if let Some(relation) = &event.content.relates_to {
635 decrypted_content.insert("m.relates_to".to_owned(), relation.to_owned());
636 }
637 }
638 }
639
640 Ok((decrypted_object, decrypted.message_index))
641 }
642
643 #[cfg(test)]
645 pub(crate) fn mark_as_imported(&mut self) {
646 self.imported = true;
647 }
648
649 pub fn sender_data_type(&self) -> SenderDataType {
653 self.sender_data.to_type()
654 }
655
656 pub fn shared_history(&self) -> bool {
662 self.shared_history
663 }
664}
665
666#[cfg(not(tarpaulin_include))]
667impl fmt::Debug for InboundGroupSession {
668 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
669 f.debug_struct("InboundGroupSession").field("session_id", &self.session_id()).finish()
670 }
671}
672
673impl PartialEq for InboundGroupSession {
674 fn eq(&self, other: &Self) -> bool {
675 self.session_id() == other.session_id()
676 }
677}
678
679#[derive(Serialize, Deserialize)]
684#[allow(missing_debug_implementations)]
685pub struct PickledInboundGroupSession {
686 pub pickle: InboundGroupSessionPickle,
688 #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
690 pub sender_key: Curve25519PublicKey,
691 pub signing_key: SigningKeys<DeviceKeyAlgorithm>,
693 #[serde(default)]
695 pub sender_data: SenderData,
696 pub room_id: OwnedRoomId,
698 pub imported: bool,
701 #[serde(default)]
703 pub backed_up: bool,
704 pub history_visibility: Option<HistoryVisibility>,
706 #[serde(default = "default_algorithm")]
708 pub algorithm: EventEncryptionAlgorithm,
709 #[serde(default)]
715 pub shared_history: bool,
716}
717
718fn default_algorithm() -> EventEncryptionAlgorithm {
719 EventEncryptionAlgorithm::MegolmV1AesSha2
720}
721
722impl TryFrom<&HistoricRoomKey> for InboundGroupSession {
723 type Error = SessionCreationError;
724
725 fn try_from(key: &HistoricRoomKey) -> Result<Self, Self::Error> {
726 let HistoricRoomKey {
727 algorithm,
728 room_id,
729 sender_key,
730 session_id,
731 session_key,
732 sender_claimed_keys,
733 } = key;
734
735 let config = OutboundGroupSession::session_config(algorithm)?;
736 let session = InnerSession::import(session_key, config);
737 let first_known_index = session.first_known_index();
738
739 Ok(InboundGroupSession {
740 inner: Mutex::new(session).into(),
741 session_id: session_id.to_owned().into(),
742 creator_info: SessionCreatorInfo {
743 curve25519_key: *sender_key,
744 signing_keys: sender_claimed_keys.to_owned().into(),
745 },
746 sender_data: SenderData::default(),
749 history_visibility: None.into(),
750 first_known_index,
751 room_id: room_id.to_owned(),
752 imported: true,
753 algorithm: algorithm.to_owned().into(),
754 backed_up: AtomicBool::from(false).into(),
755 shared_history: true,
756 })
757 }
758}
759
760impl TryFrom<&ExportedRoomKey> for InboundGroupSession {
761 type Error = SessionCreationError;
762
763 fn try_from(key: &ExportedRoomKey) -> Result<Self, Self::Error> {
764 let ExportedRoomKey {
765 algorithm,
766 room_id,
767 sender_key,
768 session_id,
769 session_key,
770 sender_claimed_keys,
771 forwarding_curve25519_key_chain: _,
772 shared_history,
773 } = key;
774
775 let config = OutboundGroupSession::session_config(algorithm)?;
776 let session = InnerSession::import(session_key, config);
777 let first_known_index = session.first_known_index();
778
779 Ok(InboundGroupSession {
780 inner: Mutex::new(session).into(),
781 session_id: session_id.to_owned().into(),
782 creator_info: SessionCreatorInfo {
783 curve25519_key: *sender_key,
784 signing_keys: sender_claimed_keys.to_owned().into(),
785 },
786 sender_data: SenderData::default(),
789 history_visibility: None.into(),
790 first_known_index,
791 room_id: room_id.to_owned(),
792 imported: true,
793 algorithm: algorithm.to_owned().into(),
794 backed_up: AtomicBool::from(false).into(),
795 shared_history: *shared_history,
796 })
797 }
798}
799
800impl From<&ForwardedMegolmV1AesSha2Content> for InboundGroupSession {
801 fn from(value: &ForwardedMegolmV1AesSha2Content) -> Self {
802 let session = InnerSession::import(&value.session_key, SessionConfig::version_1());
803 let session_id = session.session_id().into();
804 let first_known_index = session.first_known_index();
805
806 InboundGroupSession {
807 inner: Mutex::new(session).into(),
808 session_id,
809 creator_info: SessionCreatorInfo {
810 curve25519_key: value.claimed_sender_key,
811 signing_keys: SigningKeys::from([(
812 DeviceKeyAlgorithm::Ed25519,
813 value.claimed_ed25519_key.into(),
814 )])
815 .into(),
816 },
817 sender_data: SenderData::default(),
820 history_visibility: None.into(),
821 first_known_index,
822 room_id: value.room_id.to_owned(),
823 imported: true,
824 algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2.into(),
825 backed_up: AtomicBool::from(false).into(),
826 shared_history: false,
827 }
828 }
829}
830
831impl From<&ForwardedMegolmV2AesSha2Content> for InboundGroupSession {
832 fn from(value: &ForwardedMegolmV2AesSha2Content) -> Self {
833 let session = InnerSession::import(&value.session_key, SessionConfig::version_2());
834 let session_id = session.session_id().into();
835 let first_known_index = session.first_known_index();
836
837 InboundGroupSession {
838 inner: Mutex::new(session).into(),
839 session_id,
840 creator_info: SessionCreatorInfo {
841 curve25519_key: value.claimed_sender_key,
842 signing_keys: value.claimed_signing_keys.to_owned().into(),
843 },
844 sender_data: SenderData::default(),
847 history_visibility: None.into(),
848 first_known_index,
849 room_id: value.room_id.to_owned(),
850 imported: true,
851 algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2.into(),
852 backed_up: AtomicBool::from(false).into(),
853 shared_history: false,
854 }
855 }
856}
857
858impl TryFrom<&DecryptedForwardedRoomKeyEvent> for InboundGroupSession {
859 type Error = SessionCreationError;
860
861 fn try_from(value: &DecryptedForwardedRoomKeyEvent) -> Result<Self, Self::Error> {
862 match &value.content {
863 ForwardedRoomKeyContent::MegolmV1AesSha2(c) => Ok(Self::from(c.deref())),
864 #[cfg(feature = "experimental-algorithms")]
865 ForwardedRoomKeyContent::MegolmV2AesSha2(c) => Ok(Self::from(c.deref())),
866 ForwardedRoomKeyContent::Unknown(c) => {
867 Err(SessionCreationError::Algorithm(c.algorithm.to_owned()))
868 }
869 }
870 }
871}
872
873#[cfg(test)]
874mod tests {
875 use assert_matches2::assert_let;
876 use insta::assert_json_snapshot;
877 use matrix_sdk_test::async_test;
878 use ruma::{
879 device_id, events::room::history_visibility::HistoryVisibility, owned_room_id, room_id,
880 user_id, DeviceId, UserId,
881 };
882 use serde_json::json;
883 use similar_asserts::assert_eq;
884 use vodozemac::{
885 megolm::{SessionKey, SessionOrdering},
886 Curve25519PublicKey, Ed25519PublicKey,
887 };
888
889 use crate::{
890 olm::{BackedUpRoomKey, ExportedRoomKey, InboundGroupSession, KnownSenderData, SenderData},
891 types::{events::room_key, EventEncryptionAlgorithm},
892 Account,
893 };
894
895 fn alice_id() -> &'static UserId {
896 user_id!("@alice:example.org")
897 }
898
899 fn alice_device_id() -> &'static DeviceId {
900 device_id!("ALICEDEVICE")
901 }
902
903 #[async_test]
904 async fn test_pickle_snapshot() {
905 let account = Account::new(alice_id());
906 let room_id = room_id!("!test:localhost");
907 let (_, session) = account.create_group_session_pair_with_defaults(room_id).await;
908
909 let pickle = session.pickle().await;
910
911 assert_json_snapshot!(pickle, {
912 ".pickle.initial_ratchet.inner" => "[ratchet]",
913 ".pickle.signing_key" => "[signing_key]",
914 ".sender_key" => "[sender_key]",
915 ".signing_key.ed25519" => "[ed25519_key]",
916 });
917 }
918
919 #[async_test]
920 async fn test_can_deserialise_pickled_session_without_sender_data() {
921 let pickle = r#"
923 {
924 "pickle": {
925 "initial_ratchet": {
926 "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
927 220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
928 229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
929 202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
930 138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
931 245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
932 2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
933 205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
934 52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
935 "counter": 0
936 },
937 "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
938 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
939 149, 43, 38 ],
940 "signing_key_verified": true,
941 "config": {
942 "version": "V1"
943 }
944 },
945 "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
946 "signing_key": {
947 "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
948 },
949 "room_id": "!test:localhost",
950 "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
951 "imported": false,
952 "backed_up": false,
953 "history_visibility": "shared",
954 "algorithm": "m.megolm.v1.aes-sha2"
955 }
956 "#;
957
958 let deserialized = serde_json::from_str(pickle).unwrap();
960
961 let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
963
964 assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
966
967 assert_let!(
970 SenderData::UnknownDevice { legacy_session, owner_check_failed } =
971 unpickled.sender_data
972 );
973 assert!(legacy_session);
974 assert!(!owner_check_failed);
975 }
976
977 #[async_test]
978 async fn test_can_serialise_pickled_session_with_sender_data() {
979 let igs = InboundGroupSession::new(
981 Curve25519PublicKey::from_base64("AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8")
982 .unwrap(),
983 Ed25519PublicKey::from_base64("wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww").unwrap(),
984 room_id!("!test:localhost"),
985 &create_session_key(),
986 SenderData::unknown(),
987 EventEncryptionAlgorithm::MegolmV1AesSha2,
988 Some(HistoryVisibility::Shared),
989 false,
990 )
991 .unwrap();
992
993 let pickled = igs.pickle().await;
995
996 let serialised = serde_json::to_string(&pickled).unwrap();
998
999 let expected_inner = vec![
1003 193, 203, 223, 152, 33, 132, 200, 168, 24, 197, 79, 174, 231, 202, 45, 245, 128, 131,
1004 178, 165, 148, 37, 241, 214, 178, 218, 25, 33, 68, 48, 153, 104, 122, 6, 249, 198, 97,
1005 226, 214, 75, 64, 128, 25, 138, 98, 90, 138, 93, 52, 206, 174, 3, 84, 149, 101, 140,
1006 238, 156, 103, 107, 124, 144, 139, 104, 253, 5, 100, 251, 186, 118, 208, 87, 31, 218,
1007 123, 234, 103, 34, 246, 100, 39, 90, 216, 72, 187, 86, 202, 150, 100, 116, 204, 254,
1008 10, 154, 216, 133, 61, 250, 75, 100, 195, 63, 138, 22, 17, 13, 156, 123, 195, 132, 111,
1009 95, 250, 24, 236, 0, 246, 93, 230, 100, 211, 165, 211, 190, 181, 87, 42, 181,
1010 ];
1011 assert_eq!(
1012 serde_json::from_str::<serde_json::Value>(&serialised).unwrap(),
1013 serde_json::json!({
1014 "pickle":{
1015 "initial_ratchet":{
1016 "inner": expected_inner,
1017 "counter":0
1018 },
1019 "signing_key":[
1020 213,161,95,135,114,153,162,127,217,74,64,2,59,143,93,5,190,157,120,
1021 80,89,8,87,129,115,148,104,144,152,186,178,109
1022 ],
1023 "signing_key_verified":true,
1024 "config":{"version":"V1"}
1025 },
1026 "sender_key":"AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
1027 "signing_key":{"ed25519":"wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"},
1028 "sender_data":{
1029 "UnknownDevice":{
1030 "legacy_session":false
1031 }
1032 },
1033 "room_id":"!test:localhost",
1034 "imported":false,
1035 "backed_up":false,
1036 "shared_history":false,
1037 "history_visibility":"shared",
1038 "algorithm":"m.megolm.v1.aes-sha2"
1039 })
1040 );
1041 }
1042
1043 #[async_test]
1044 async fn test_can_deserialise_pickled_session_with_sender_data() {
1045 let pickle = r#"
1047 {
1048 "pickle": {
1049 "initial_ratchet": {
1050 "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
1051 220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
1052 229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
1053 202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
1054 138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
1055 245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
1056 2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
1057 205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
1058 52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
1059 "counter": 0
1060 },
1061 "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
1062 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
1063 149, 43, 38 ],
1064 "signing_key_verified": true,
1065 "config": {
1066 "version": "V1"
1067 }
1068 },
1069 "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
1070 "signing_key": {
1071 "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
1072 },
1073 "sender_data":{
1074 "UnknownDevice":{
1075 "legacy_session":false
1076 }
1077 },
1078 "room_id": "!test:localhost",
1079 "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
1080 "imported": false,
1081 "backed_up": false,
1082 "history_visibility": "shared",
1083 "algorithm": "m.megolm.v1.aes-sha2"
1084 }
1085 "#;
1086
1087 let deserialized = serde_json::from_str(pickle).unwrap();
1089
1090 let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
1092
1093 assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
1095
1096 assert_let!(
1099 SenderData::UnknownDevice { legacy_session, owner_check_failed } =
1100 unpickled.sender_data
1101 );
1102 assert!(!legacy_session);
1103 assert!(!owner_check_failed);
1104 }
1105
1106 #[async_test]
1107 #[allow(deprecated)]
1108 async fn test_session_comparison() {
1109 let alice = Account::with_device_id(alice_id(), alice_device_id());
1110 let room_id = room_id!("!test:localhost");
1111
1112 let (_, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1113
1114 let worse = InboundGroupSession::from_export(&inbound.export_at_index(10).await).unwrap();
1115 let mut copy = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
1116
1117 assert_eq!(inbound.compare(&worse).await, SessionOrdering::Better);
1118 assert_eq!(inbound.compare_ratchet(&worse).await, SessionOrdering::Better);
1119 assert_eq!(worse.compare(&inbound).await, SessionOrdering::Worse);
1120 assert_eq!(worse.compare_ratchet(&inbound).await, SessionOrdering::Worse);
1121 assert_eq!(inbound.compare(&inbound).await, SessionOrdering::Equal);
1122 assert_eq!(inbound.compare_ratchet(&inbound).await, SessionOrdering::Equal);
1123 assert_eq!(inbound.compare(©).await, SessionOrdering::Equal);
1124 assert_eq!(inbound.compare_ratchet(©).await, SessionOrdering::Equal);
1125
1126 copy.creator_info.curve25519_key =
1127 Curve25519PublicKey::from_base64("XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY")
1128 .unwrap();
1129
1130 assert_eq!(inbound.compare(©).await, SessionOrdering::Unconnected);
1131 assert_eq!(inbound.compare_ratchet(©).await, SessionOrdering::Unconnected);
1132 }
1133
1134 #[async_test]
1135 #[allow(deprecated)]
1136 async fn test_session_comparison_sender_data() {
1137 let alice = Account::with_device_id(alice_id(), alice_device_id());
1138 let room_id = room_id!("!test:localhost");
1139
1140 let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1141
1142 let sender_data = SenderData::SenderVerified(KnownSenderData {
1143 user_id: alice.user_id().into(),
1144 device_id: Some(alice.device_id().into()),
1145 master_key: alice.identity_keys().ed25519.into(),
1146 });
1147
1148 let mut better = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
1149 better.sender_data = sender_data.clone();
1150
1151 assert_eq!(inbound.compare(&better).await, SessionOrdering::Worse);
1152 assert_eq!(better.compare(&inbound).await, SessionOrdering::Better);
1153
1154 inbound.sender_data = sender_data;
1155 assert_eq!(better.compare(&inbound).await, SessionOrdering::Equal);
1156 }
1157
1158 fn create_session_key() -> SessionKey {
1159 SessionKey::from_base64(
1160 "\
1161 AgAAAADBy9+YIYTIqBjFT67nyi31gIOypZQl8day2hkhRDCZaHoG+cZh4tZLQIAZimJail0\
1162 0zq4DVJVljO6cZ2t8kIto/QVk+7p20Fcf2nvqZyL2ZCda2Ei7VsqWZHTM/gqa2IU9+ktkwz\
1163 +KFhENnHvDhG9f+hjsAPZd5mTTpdO+tVcqtdWhX4dymaJ/2UpAAjuPXQW+nXhQWQhXgXOUa\
1164 JCYurJtvbCbqZGeDMmVIoqukBs2KugNJ6j5WlTPoeFnMl6Guy9uH2iWWxGg8ZgT2xspqVl5\
1165 CwujjC+m7Dh1toVkvu+bAw\
1166 ",
1167 )
1168 .unwrap()
1169 }
1170
1171 #[async_test]
1172 async fn test_shared_history_from_m_room_key_content() {
1173 let content = json!({
1174 "algorithm": "m.megolm.v1.aes-sha2",
1175 "room_id": "!Cuyf34gef24t:localhost",
1176 "org.matrix.msc3061.shared_history": true,
1177 "session_id": "ZFD6+OmV7fVCsJ7Gap8UnORH8EnmiAkes8FAvQuCw/I",
1178 "session_key": "AgAAAADNp1EbxXYOGmJtyX4AkD1bvJvAUyPkbIaKxtnGKjv\
1179 SQ3E/4mnuqdM4vsmNzpO1EeWzz1rDkUpYhYE9kP7sJhgLXi\
1180 jVv80fMPHfGc49hPdu8A+xnwD4SQiYdFmSWJOIqsxeo/fiH\
1181 tino//CDQENtcKuEt0I9s0+Kk4YSH310Szse2RQ+vjple31\
1182 QrCexmqfFJzkR/BJ5ogJHrPBQL0LgsPyglIbMTLg7qygIaY\
1183 U5Fe2QdKMH7nTZPNIRHh1RaMfHVETAUJBax88EWZBoifk80\
1184 gdHUwHSgMk77vCc2a5KHKLDA",
1185 });
1186
1187 let sender_key = Curve25519PublicKey::from_bytes([0; 32]);
1188 let signing_key = Ed25519PublicKey::from_slice(&[0; 32]).expect("");
1189 let mut content: room_key::MegolmV1AesSha2Content = serde_json::from_value(content)
1190 .expect("We should be able to deserialize the m.room_key content");
1191
1192 let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1193 .expect(
1194 "We should be able to create an inbound group session from the room key content",
1195 );
1196
1197 assert!(
1198 session.shared_history,
1199 "The shared history flag should be set as it was set in the m.room_key content"
1200 );
1201
1202 content.shared_history = false;
1203 let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1204 .expect(
1205 "We should be able to create an inbound group session from the room key content",
1206 );
1207
1208 assert!(
1209 !session.shared_history,
1210 "The shared history flag should not be set as it was not set in the m.room_key content"
1211 );
1212 }
1213
1214 #[async_test]
1215 async fn test_shared_history_from_exported_room_key() {
1216 let content = json!({
1217 "algorithm": "m.megolm.v1.aes-sha2",
1218 "room_id": "!room:id",
1219 "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
1220 "session_id": "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0",
1221 "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
1222 "sender_claimed_keys": {
1223 "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
1224 },
1225 "forwarding_curve25519_key_chain": [],
1226 "org.matrix.msc3061.shared_history": true
1227
1228 });
1229
1230 let mut content: ExportedRoomKey = serde_json::from_value(content)
1231 .expect("We should be able to deserialize the m.room_key content");
1232
1233 let session = InboundGroupSession::from_export(&content).expect(
1234 "We should be able to create an inbound group session from the room key export",
1235 );
1236 assert!(
1237 session.shared_history,
1238 "The shared history flag should be set as it was set in the exported room key"
1239 );
1240
1241 content.shared_history = false;
1242
1243 let session = InboundGroupSession::from_export(&content).expect(
1244 "We should be able to create an inbound group session from the room key export",
1245 );
1246 assert!(
1247 !session.shared_history,
1248 "The shared history flag should not be set as it was not set in the exported room key"
1249 );
1250 }
1251
1252 #[async_test]
1253 async fn test_shared_history_from_backed_up_room_key() {
1254 let content = json!({
1255 "algorithm": "m.megolm.v1.aes-sha2",
1256 "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
1257 "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
1258 "sender_claimed_keys": {
1259 "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
1260 },
1261 "forwarding_curve25519_key_chain": [],
1262 "org.matrix.msc3061.shared_history": true
1263
1264 });
1265
1266 let session_id = "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0";
1267 let room_id = owned_room_id!("!room:id");
1268 let room_key: BackedUpRoomKey = serde_json::from_value(content)
1269 .expect("We should be able to deserialize the backed up room key");
1270
1271 let room_key =
1272 ExportedRoomKey::from_backed_up_room_key(room_id, session_id.to_owned(), room_key);
1273
1274 let session = InboundGroupSession::from_export(&room_key).expect(
1275 "We should be able to create an inbound group session from the room key export",
1276 );
1277 assert!(
1278 session.shared_history,
1279 "The shared history flag should be set as it was set in the backed up room key"
1280 );
1281 }
1282
1283 #[async_test]
1284 async fn test_shared_history_in_pickle() {
1285 let alice = Account::with_device_id(alice_id(), alice_device_id());
1286 let room_id = room_id!("!test:localhost");
1287
1288 let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1289
1290 inbound.shared_history = true;
1291 let pickle = inbound.pickle().await;
1292
1293 assert!(
1294 pickle.shared_history,
1295 "The set shared history flag should have been copied to the pickle"
1296 );
1297
1298 inbound.shared_history = false;
1299 let pickle = inbound.pickle().await;
1300
1301 assert!(
1302 !pickle.shared_history,
1303 "The unset shared history flag should have been copied to the pickle"
1304 );
1305 }
1306
1307 #[async_test]
1308 async fn test_shared_history_in_export() {
1309 let alice = Account::with_device_id(alice_id(), alice_device_id());
1310 let room_id = room_id!("!test:localhost");
1311
1312 let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1313
1314 inbound.shared_history = true;
1315 let export = inbound.export().await;
1316 assert!(
1317 export.shared_history,
1318 "The set shared history flag should have been copied to the room key export"
1319 );
1320
1321 inbound.shared_history = false;
1322 let export = inbound.export().await;
1323 assert!(
1324 !export.shared_history,
1325 "The unset shared history flag should have been copied to the room key export"
1326 );
1327 }
1328}