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, room_history::HistoricRoomKey};
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 serialize_curve_key, EventEncryptionAlgorithm, SigningKeys,
59 },
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 async fn pickle(&self) -> PickledInboundGroupSession {
346 let pickle = self.inner.lock().await.pickle();
347
348 PickledInboundGroupSession {
349 pickle,
350 sender_key: self.creator_info.curve25519_key,
351 signing_key: (*self.creator_info.signing_keys).clone(),
352 sender_data: self.sender_data.clone(),
353 room_id: self.room_id().to_owned(),
354 imported: self.imported,
355 backed_up: self.backed_up(),
356 history_visibility: self.history_visibility.as_ref().clone(),
357 algorithm: (*self.algorithm).to_owned(),
358 shared_history: self.shared_history,
359 }
360 }
361
362 pub async fn export(&self) -> ExportedRoomKey {
367 self.export_at_index(self.first_known_index()).await
368 }
369
370 pub fn sender_key(&self) -> Curve25519PublicKey {
372 self.creator_info.curve25519_key
373 }
374
375 pub fn backed_up(&self) -> bool {
377 self.backed_up.load(SeqCst)
378 }
379
380 pub fn reset_backup_state(&self) {
382 self.backed_up.store(false, SeqCst)
383 }
384
385 pub fn mark_as_backed_up(&self) {
388 self.backed_up.store(true, SeqCst)
389 }
390
391 pub fn signing_keys(&self) -> &SigningKeys<DeviceKeyAlgorithm> {
393 &self.creator_info.signing_keys
394 }
395
396 pub async fn export_at_index(&self, message_index: u32) -> ExportedRoomKey {
398 let message_index = std::cmp::max(self.first_known_index(), message_index);
399
400 let session_key =
401 self.inner.lock().await.export_at(message_index).expect("Can't export session");
402
403 ExportedRoomKey {
404 algorithm: self.algorithm().to_owned(),
405 room_id: self.room_id().to_owned(),
406 sender_key: self.creator_info.curve25519_key,
407 session_id: self.session_id().to_owned(),
408 forwarding_curve25519_key_chain: vec![],
409 sender_claimed_keys: (*self.creator_info.signing_keys).clone(),
410 session_key,
411 shared_history: self.shared_history,
412 }
413 }
414
415 pub fn from_pickle(pickle: PickledInboundGroupSession) -> Result<Self, PickleError> {
427 let PickledInboundGroupSession {
428 pickle,
429 sender_key,
430 signing_key,
431 sender_data,
432 room_id,
433 imported,
434 backed_up,
435 history_visibility,
436 algorithm,
437 shared_history,
438 } = pickle;
439
440 let session: InnerSession = pickle.into();
441 let first_known_index = session.first_known_index();
442 let session_id = session.session_id();
443
444 Ok(InboundGroupSession {
445 inner: Mutex::new(session).into(),
446 session_id: session_id.into(),
447 creator_info: SessionCreatorInfo {
448 curve25519_key: sender_key,
449 signing_keys: signing_key.into(),
450 },
451 sender_data,
452 history_visibility: history_visibility.into(),
453 first_known_index,
454 room_id,
455 backed_up: AtomicBool::from(backed_up).into(),
456 algorithm: algorithm.into(),
457 imported,
458 shared_history,
459 })
460 }
461
462 pub fn room_id(&self) -> &RoomId {
464 &self.room_id
465 }
466
467 pub fn session_id(&self) -> &str {
469 &self.session_id
470 }
471
472 pub fn algorithm(&self) -> &EventEncryptionAlgorithm {
475 &self.algorithm
476 }
477
478 pub fn first_known_index(&self) -> u32 {
480 self.first_known_index
481 }
482
483 pub fn has_been_imported(&self) -> bool {
486 self.imported
487 }
488
489 pub async fn compare(&self, other: &InboundGroupSession) -> SessionOrdering {
492 if Arc::ptr_eq(&self.inner, &other.inner) {
495 SessionOrdering::Equal
496 } else if self.sender_key() != other.sender_key()
497 || self.signing_keys() != other.signing_keys()
498 || self.algorithm() != other.algorithm()
499 || self.room_id() != other.room_id()
500 {
501 SessionOrdering::Unconnected
502 } else {
503 let mut other_inner = other.inner.lock().await;
504
505 match self.inner.lock().await.compare(&mut other_inner) {
506 SessionOrdering::Equal => {
507 match self.sender_data.compare_trust_level(&other.sender_data) {
508 Ordering::Less => SessionOrdering::Worse,
509 Ordering::Equal => SessionOrdering::Equal,
510 Ordering::Greater => SessionOrdering::Better,
511 }
512 }
513 result => result,
514 }
515 }
516 }
517
518 pub(crate) async fn decrypt_helper(
527 &self,
528 message: &MegolmMessage,
529 ) -> Result<DecryptedMessage, DecryptionError> {
530 self.inner.lock().await.decrypt(message)
531 }
532
533 pub async fn to_backup(&self) -> BackedUpRoomKey {
536 self.export().await.into()
537 }
538
539 pub async fn decrypt(&self, event: &EncryptedEvent) -> MegolmResult<(JsonObject, u32)> {
545 let decrypted = match &event.content.scheme {
546 RoomEventEncryptionScheme::MegolmV1AesSha2(c) => {
547 self.decrypt_helper(&c.ciphertext).await?
548 }
549 #[cfg(feature = "experimental-algorithms")]
550 RoomEventEncryptionScheme::MegolmV2AesSha2(c) => {
551 self.decrypt_helper(&c.ciphertext).await?
552 }
553 RoomEventEncryptionScheme::Unknown(_) => {
554 return Err(EventError::UnsupportedAlgorithm.into());
555 }
556 };
557
558 let plaintext = String::from_utf8_lossy(&decrypted.plaintext);
559
560 let mut decrypted_object = serde_json::from_str::<JsonObject>(&plaintext)?;
561
562 let server_ts: i64 = event.origin_server_ts.0.into();
563
564 decrypted_object.insert("sender".to_owned(), event.sender.to_string().into());
565 decrypted_object.insert("event_id".to_owned(), event.event_id.to_string().into());
566 decrypted_object.insert("origin_server_ts".to_owned(), server_ts.into());
567
568 let room_id = decrypted_object
569 .get("room_id")
570 .and_then(|r| r.as_str().and_then(|r| RoomId::parse(r).ok()));
571
572 if room_id.as_deref() != Some(self.room_id()) {
575 return Err(EventError::MismatchedRoom(self.room_id().to_owned(), room_id).into());
576 }
577
578 decrypted_object.insert(
579 "unsigned".to_owned(),
580 serde_json::to_value(&event.unsigned).unwrap_or_default(),
581 );
582
583 if let Some(decrypted_content) =
584 decrypted_object.get_mut("content").and_then(|c| c.as_object_mut())
585 {
586 if !decrypted_content.contains_key("m.relates_to") {
587 if let Some(relation) = &event.content.relates_to {
588 decrypted_content.insert("m.relates_to".to_owned(), relation.to_owned());
589 }
590 }
591 }
592
593 Ok((decrypted_object, decrypted.message_index))
594 }
595
596 #[cfg(test)]
598 pub(crate) fn mark_as_imported(&mut self) {
599 self.imported = true;
600 }
601
602 pub fn sender_data_type(&self) -> SenderDataType {
606 self.sender_data.to_type()
607 }
608
609 pub fn shared_history(&self) -> bool {
615 self.shared_history
616 }
617}
618
619#[cfg(not(tarpaulin_include))]
620impl fmt::Debug for InboundGroupSession {
621 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
622 f.debug_struct("InboundGroupSession").field("session_id", &self.session_id()).finish()
623 }
624}
625
626impl PartialEq for InboundGroupSession {
627 fn eq(&self, other: &Self) -> bool {
628 self.session_id() == other.session_id()
629 }
630}
631
632#[derive(Serialize, Deserialize)]
637#[allow(missing_debug_implementations)]
638pub struct PickledInboundGroupSession {
639 pub pickle: InboundGroupSessionPickle,
641 #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
643 pub sender_key: Curve25519PublicKey,
644 pub signing_key: SigningKeys<DeviceKeyAlgorithm>,
646 #[serde(default)]
648 pub sender_data: SenderData,
649 pub room_id: OwnedRoomId,
651 pub imported: bool,
654 #[serde(default)]
656 pub backed_up: bool,
657 pub history_visibility: Option<HistoryVisibility>,
659 #[serde(default = "default_algorithm")]
661 pub algorithm: EventEncryptionAlgorithm,
662 #[serde(default)]
668 pub shared_history: bool,
669}
670
671fn default_algorithm() -> EventEncryptionAlgorithm {
672 EventEncryptionAlgorithm::MegolmV1AesSha2
673}
674
675impl TryFrom<&ExportedRoomKey> for InboundGroupSession {
676 type Error = SessionCreationError;
677
678 fn try_from(key: &ExportedRoomKey) -> Result<Self, Self::Error> {
679 let ExportedRoomKey {
680 algorithm,
681 room_id,
682 sender_key,
683 session_id,
684 session_key,
685 sender_claimed_keys,
686 forwarding_curve25519_key_chain: _,
687 shared_history,
688 } = key;
689
690 let config = OutboundGroupSession::session_config(algorithm)?;
691 let session = InnerSession::import(session_key, config);
692 let first_known_index = session.first_known_index();
693
694 Ok(InboundGroupSession {
695 inner: Mutex::new(session).into(),
696 session_id: session_id.to_owned().into(),
697 creator_info: SessionCreatorInfo {
698 curve25519_key: *sender_key,
699 signing_keys: sender_claimed_keys.to_owned().into(),
700 },
701 sender_data: SenderData::default(),
704 history_visibility: None.into(),
705 first_known_index,
706 room_id: room_id.to_owned(),
707 imported: true,
708 algorithm: algorithm.to_owned().into(),
709 backed_up: AtomicBool::from(false).into(),
710 shared_history: *shared_history,
711 })
712 }
713}
714
715impl From<&ForwardedMegolmV1AesSha2Content> for InboundGroupSession {
716 fn from(value: &ForwardedMegolmV1AesSha2Content) -> Self {
717 let session = InnerSession::import(&value.session_key, SessionConfig::version_1());
718 let session_id = session.session_id().into();
719 let first_known_index = session.first_known_index();
720
721 InboundGroupSession {
722 inner: Mutex::new(session).into(),
723 session_id,
724 creator_info: SessionCreatorInfo {
725 curve25519_key: value.claimed_sender_key,
726 signing_keys: SigningKeys::from([(
727 DeviceKeyAlgorithm::Ed25519,
728 value.claimed_ed25519_key.into(),
729 )])
730 .into(),
731 },
732 sender_data: SenderData::default(),
735 history_visibility: None.into(),
736 first_known_index,
737 room_id: value.room_id.to_owned(),
738 imported: true,
739 algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2.into(),
740 backed_up: AtomicBool::from(false).into(),
741 shared_history: false,
742 }
743 }
744}
745
746impl From<&ForwardedMegolmV2AesSha2Content> for InboundGroupSession {
747 fn from(value: &ForwardedMegolmV2AesSha2Content) -> Self {
748 let session = InnerSession::import(&value.session_key, SessionConfig::version_2());
749 let session_id = session.session_id().into();
750 let first_known_index = session.first_known_index();
751
752 InboundGroupSession {
753 inner: Mutex::new(session).into(),
754 session_id,
755 creator_info: SessionCreatorInfo {
756 curve25519_key: value.claimed_sender_key,
757 signing_keys: value.claimed_signing_keys.to_owned().into(),
758 },
759 sender_data: SenderData::default(),
762 history_visibility: None.into(),
763 first_known_index,
764 room_id: value.room_id.to_owned(),
765 imported: true,
766 algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2.into(),
767 backed_up: AtomicBool::from(false).into(),
768 shared_history: false,
769 }
770 }
771}
772
773impl TryFrom<&DecryptedForwardedRoomKeyEvent> for InboundGroupSession {
774 type Error = SessionCreationError;
775
776 fn try_from(value: &DecryptedForwardedRoomKeyEvent) -> Result<Self, Self::Error> {
777 match &value.content {
778 ForwardedRoomKeyContent::MegolmV1AesSha2(c) => Ok(Self::from(c.deref())),
779 #[cfg(feature = "experimental-algorithms")]
780 ForwardedRoomKeyContent::MegolmV2AesSha2(c) => Ok(Self::from(c.deref())),
781 ForwardedRoomKeyContent::Unknown(c) => {
782 Err(SessionCreationError::Algorithm(c.algorithm.to_owned()))
783 }
784 }
785 }
786}
787
788#[cfg(test)]
789mod tests {
790 use assert_matches2::assert_let;
791 use insta::assert_json_snapshot;
792 use matrix_sdk_test::async_test;
793 use ruma::{
794 device_id, events::room::history_visibility::HistoryVisibility, owned_room_id, room_id,
795 user_id, DeviceId, UserId,
796 };
797 use serde_json::json;
798 use similar_asserts::assert_eq;
799 use vodozemac::{
800 megolm::{SessionKey, SessionOrdering},
801 Curve25519PublicKey, Ed25519PublicKey,
802 };
803
804 use crate::{
805 olm::{BackedUpRoomKey, ExportedRoomKey, InboundGroupSession, KnownSenderData, SenderData},
806 types::{events::room_key, EventEncryptionAlgorithm},
807 Account,
808 };
809
810 fn alice_id() -> &'static UserId {
811 user_id!("@alice:example.org")
812 }
813
814 fn alice_device_id() -> &'static DeviceId {
815 device_id!("ALICEDEVICE")
816 }
817
818 #[async_test]
819 async fn test_pickle_snapshot() {
820 let account = Account::new(alice_id());
821 let room_id = room_id!("!test:localhost");
822 let (_, session) = account.create_group_session_pair_with_defaults(room_id).await;
823
824 let pickle = session.pickle().await;
825
826 assert_json_snapshot!(pickle, {
827 ".pickle.initial_ratchet.inner" => "[ratchet]",
828 ".pickle.signing_key" => "[signing_key]",
829 ".sender_key" => "[sender_key]",
830 ".signing_key.ed25519" => "[ed25519_key]",
831 });
832 }
833
834 #[async_test]
835 async fn test_can_deserialise_pickled_session_without_sender_data() {
836 let pickle = r#"
838 {
839 "pickle": {
840 "initial_ratchet": {
841 "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
842 220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
843 229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
844 202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
845 138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
846 245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
847 2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
848 205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
849 52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
850 "counter": 0
851 },
852 "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
853 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
854 149, 43, 38 ],
855 "signing_key_verified": true,
856 "config": {
857 "version": "V1"
858 }
859 },
860 "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
861 "signing_key": {
862 "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
863 },
864 "room_id": "!test:localhost",
865 "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
866 "imported": false,
867 "backed_up": false,
868 "history_visibility": "shared",
869 "algorithm": "m.megolm.v1.aes-sha2"
870 }
871 "#;
872
873 let deserialized = serde_json::from_str(pickle).unwrap();
875
876 let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
878
879 assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
881
882 assert_let!(
885 SenderData::UnknownDevice { legacy_session, owner_check_failed } =
886 unpickled.sender_data
887 );
888 assert!(legacy_session);
889 assert!(!owner_check_failed);
890 }
891
892 #[async_test]
893 async fn test_can_serialise_pickled_session_with_sender_data() {
894 let igs = InboundGroupSession::new(
896 Curve25519PublicKey::from_base64("AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8")
897 .unwrap(),
898 Ed25519PublicKey::from_base64("wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww").unwrap(),
899 room_id!("!test:localhost"),
900 &create_session_key(),
901 SenderData::unknown(),
902 EventEncryptionAlgorithm::MegolmV1AesSha2,
903 Some(HistoryVisibility::Shared),
904 false,
905 )
906 .unwrap();
907
908 let pickled = igs.pickle().await;
910
911 let serialised = serde_json::to_string(&pickled).unwrap();
913
914 let expected_inner = vec![
918 193, 203, 223, 152, 33, 132, 200, 168, 24, 197, 79, 174, 231, 202, 45, 245, 128, 131,
919 178, 165, 148, 37, 241, 214, 178, 218, 25, 33, 68, 48, 153, 104, 122, 6, 249, 198, 97,
920 226, 214, 75, 64, 128, 25, 138, 98, 90, 138, 93, 52, 206, 174, 3, 84, 149, 101, 140,
921 238, 156, 103, 107, 124, 144, 139, 104, 253, 5, 100, 251, 186, 118, 208, 87, 31, 218,
922 123, 234, 103, 34, 246, 100, 39, 90, 216, 72, 187, 86, 202, 150, 100, 116, 204, 254,
923 10, 154, 216, 133, 61, 250, 75, 100, 195, 63, 138, 22, 17, 13, 156, 123, 195, 132, 111,
924 95, 250, 24, 236, 0, 246, 93, 230, 100, 211, 165, 211, 190, 181, 87, 42, 181,
925 ];
926 assert_eq!(
927 serde_json::from_str::<serde_json::Value>(&serialised).unwrap(),
928 serde_json::json!({
929 "pickle":{
930 "initial_ratchet":{
931 "inner": expected_inner,
932 "counter":0
933 },
934 "signing_key":[
935 213,161,95,135,114,153,162,127,217,74,64,2,59,143,93,5,190,157,120,
936 80,89,8,87,129,115,148,104,144,152,186,178,109
937 ],
938 "signing_key_verified":true,
939 "config":{"version":"V1"}
940 },
941 "sender_key":"AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
942 "signing_key":{"ed25519":"wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"},
943 "sender_data":{
944 "UnknownDevice":{
945 "legacy_session":false
946 }
947 },
948 "room_id":"!test:localhost",
949 "imported":false,
950 "backed_up":false,
951 "shared_history":false,
952 "history_visibility":"shared",
953 "algorithm":"m.megolm.v1.aes-sha2"
954 })
955 );
956 }
957
958 #[async_test]
959 async fn test_can_deserialise_pickled_session_with_sender_data() {
960 let pickle = r#"
962 {
963 "pickle": {
964 "initial_ratchet": {
965 "inner": [ 124, 251, 213, 204, 108, 247, 54, 7, 179, 162, 15, 107, 154, 215,
966 220, 46, 123, 113, 120, 162, 225, 246, 237, 203, 125, 102, 190, 212,
967 229, 195, 136, 185, 26, 31, 77, 140, 144, 181, 152, 177, 46, 105,
968 202, 6, 53, 158, 157, 170, 31, 155, 130, 87, 214, 110, 143, 55, 68,
969 138, 41, 35, 242, 230, 194, 15, 16, 145, 116, 94, 89, 35, 79, 145,
970 245, 117, 204, 173, 166, 178, 49, 131, 143, 61, 61, 15, 211, 167, 17,
971 2, 79, 110, 149, 200, 223, 23, 185, 200, 29, 64, 55, 39, 147, 167,
972 205, 224, 159, 101, 218, 249, 203, 30, 175, 174, 48, 252, 40, 131,
973 52, 135, 91, 57, 211, 96, 105, 58, 55, 68, 250, 24 ],
974 "counter": 0
975 },
976 "signing_key": [ 93, 185, 171, 61, 173, 100, 51, 9, 157, 180, 214, 39, 131, 80, 118,
977 130, 199, 232, 163, 197, 45, 23, 227, 100, 151, 59, 19, 102, 38,
978 149, 43, 38 ],
979 "signing_key_verified": true,
980 "config": {
981 "version": "V1"
982 }
983 },
984 "sender_key": "AmM1DvVJarsNNXVuX7OarzfT481N37GtDwvDVF0RcR8",
985 "signing_key": {
986 "ed25519": "wTRTdz4rn4EY+68cKPzpMdQ6RAlg7T8cbTmEjaXuUww"
987 },
988 "sender_data":{
989 "UnknownDevice":{
990 "legacy_session":false
991 }
992 },
993 "room_id": "!test:localhost",
994 "forwarding_chains": ["tb6kQKjk+SJl2KnfQ0lKVOZl6gDFMcsb9HcUP9k/4hc"],
995 "imported": false,
996 "backed_up": false,
997 "history_visibility": "shared",
998 "algorithm": "m.megolm.v1.aes-sha2"
999 }
1000 "#;
1001
1002 let deserialized = serde_json::from_str(pickle).unwrap();
1004
1005 let unpickled = InboundGroupSession::from_pickle(deserialized).unwrap();
1007
1008 assert_eq!(unpickled.session_id(), "XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY");
1010
1011 assert_let!(
1014 SenderData::UnknownDevice { legacy_session, owner_check_failed } =
1015 unpickled.sender_data
1016 );
1017 assert!(!legacy_session);
1018 assert!(!owner_check_failed);
1019 }
1020
1021 #[async_test]
1022 async fn test_session_comparison() {
1023 let alice = Account::with_device_id(alice_id(), alice_device_id());
1024 let room_id = room_id!("!test:localhost");
1025
1026 let (_, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1027
1028 let worse = InboundGroupSession::from_export(&inbound.export_at_index(10).await).unwrap();
1029 let mut copy = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
1030
1031 assert_eq!(inbound.compare(&worse).await, SessionOrdering::Better);
1032 assert_eq!(worse.compare(&inbound).await, SessionOrdering::Worse);
1033 assert_eq!(inbound.compare(&inbound).await, SessionOrdering::Equal);
1034 assert_eq!(inbound.compare(©).await, SessionOrdering::Equal);
1035
1036 copy.creator_info.curve25519_key =
1037 Curve25519PublicKey::from_base64("XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY")
1038 .unwrap();
1039
1040 assert_eq!(inbound.compare(©).await, SessionOrdering::Unconnected);
1041 }
1042
1043 #[async_test]
1044 async fn test_session_comparison_sender_data() {
1045 let alice = Account::with_device_id(alice_id(), alice_device_id());
1046 let room_id = room_id!("!test:localhost");
1047
1048 let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1049
1050 let sender_data = SenderData::SenderVerified(KnownSenderData {
1051 user_id: alice.user_id().into(),
1052 device_id: Some(alice.device_id().into()),
1053 master_key: alice.identity_keys().ed25519.into(),
1054 });
1055
1056 let mut better = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
1057 better.sender_data = sender_data.clone();
1058
1059 assert_eq!(inbound.compare(&better).await, SessionOrdering::Worse);
1060 assert_eq!(better.compare(&inbound).await, SessionOrdering::Better);
1061
1062 inbound.sender_data = sender_data;
1063 assert_eq!(better.compare(&inbound).await, SessionOrdering::Equal);
1064 }
1065
1066 fn create_session_key() -> SessionKey {
1067 SessionKey::from_base64(
1068 "\
1069 AgAAAADBy9+YIYTIqBjFT67nyi31gIOypZQl8day2hkhRDCZaHoG+cZh4tZLQIAZimJail0\
1070 0zq4DVJVljO6cZ2t8kIto/QVk+7p20Fcf2nvqZyL2ZCda2Ei7VsqWZHTM/gqa2IU9+ktkwz\
1071 +KFhENnHvDhG9f+hjsAPZd5mTTpdO+tVcqtdWhX4dymaJ/2UpAAjuPXQW+nXhQWQhXgXOUa\
1072 JCYurJtvbCbqZGeDMmVIoqukBs2KugNJ6j5WlTPoeFnMl6Guy9uH2iWWxGg8ZgT2xspqVl5\
1073 CwujjC+m7Dh1toVkvu+bAw\
1074 ",
1075 )
1076 .unwrap()
1077 }
1078
1079 #[async_test]
1080 async fn test_shared_history_from_m_room_key_content() {
1081 let content = json!({
1082 "algorithm": "m.megolm.v1.aes-sha2",
1083 "room_id": "!Cuyf34gef24t:localhost",
1084 "org.matrix.msc3061.shared_history": true,
1085 "session_id": "ZFD6+OmV7fVCsJ7Gap8UnORH8EnmiAkes8FAvQuCw/I",
1086 "session_key": "AgAAAADNp1EbxXYOGmJtyX4AkD1bvJvAUyPkbIaKxtnGKjv\
1087 SQ3E/4mnuqdM4vsmNzpO1EeWzz1rDkUpYhYE9kP7sJhgLXi\
1088 jVv80fMPHfGc49hPdu8A+xnwD4SQiYdFmSWJOIqsxeo/fiH\
1089 tino//CDQENtcKuEt0I9s0+Kk4YSH310Szse2RQ+vjple31\
1090 QrCexmqfFJzkR/BJ5ogJHrPBQL0LgsPyglIbMTLg7qygIaY\
1091 U5Fe2QdKMH7nTZPNIRHh1RaMfHVETAUJBax88EWZBoifk80\
1092 gdHUwHSgMk77vCc2a5KHKLDA",
1093 });
1094
1095 let sender_key = Curve25519PublicKey::from_bytes([0; 32]);
1096 let signing_key = Ed25519PublicKey::from_slice(&[0; 32]).expect("");
1097 let mut content: room_key::MegolmV1AesSha2Content = serde_json::from_value(content)
1098 .expect("We should be able to deserialize the m.room_key content");
1099
1100 let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1101 .expect(
1102 "We should be able to create an inbound group session from the room key content",
1103 );
1104
1105 assert!(
1106 session.shared_history,
1107 "The shared history flag should be set as it was set in the m.room_key content"
1108 );
1109
1110 content.shared_history = false;
1111 let session = InboundGroupSession::from_room_key_content(sender_key, signing_key, &content)
1112 .expect(
1113 "We should be able to create an inbound group session from the room key content",
1114 );
1115
1116 assert!(
1117 !session.shared_history,
1118 "The shared history flag should not be set as it was not set in the m.room_key content"
1119 );
1120 }
1121
1122 #[async_test]
1123 async fn test_shared_history_from_exported_room_key() {
1124 let content = json!({
1125 "algorithm": "m.megolm.v1.aes-sha2",
1126 "room_id": "!room:id",
1127 "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
1128 "session_id": "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0",
1129 "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
1130 "sender_claimed_keys": {
1131 "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
1132 },
1133 "forwarding_curve25519_key_chain": [],
1134 "org.matrix.msc3061.shared_history": true
1135
1136 });
1137
1138 let mut content: ExportedRoomKey = serde_json::from_value(content)
1139 .expect("We should be able to deserialize the m.room_key content");
1140
1141 let session = InboundGroupSession::from_export(&content).expect(
1142 "We should be able to create an inbound group session from the room key export",
1143 );
1144 assert!(
1145 session.shared_history,
1146 "The shared history flag should be set as it was set in the exported room key"
1147 );
1148
1149 content.shared_history = false;
1150
1151 let session = InboundGroupSession::from_export(&content).expect(
1152 "We should be able to create an inbound group session from the room key export",
1153 );
1154 assert!(
1155 !session.shared_history,
1156 "The shared history flag should not be set as it was not set in the exported room key"
1157 );
1158 }
1159
1160 #[async_test]
1161 async fn test_shared_history_from_backed_up_room_key() {
1162 let content = json!({
1163 "algorithm": "m.megolm.v1.aes-sha2",
1164 "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
1165 "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
1166 "sender_claimed_keys": {
1167 "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
1168 },
1169 "forwarding_curve25519_key_chain": [],
1170 "org.matrix.msc3061.shared_history": true
1171
1172 });
1173
1174 let session_id = "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0";
1175 let room_id = owned_room_id!("!room:id");
1176 let room_key: BackedUpRoomKey = serde_json::from_value(content)
1177 .expect("We should be able to deserialize the backed up room key");
1178
1179 let room_key =
1180 ExportedRoomKey::from_backed_up_room_key(room_id, session_id.to_owned(), room_key);
1181
1182 let session = InboundGroupSession::from_export(&room_key).expect(
1183 "We should be able to create an inbound group session from the room key export",
1184 );
1185 assert!(
1186 session.shared_history,
1187 "The shared history flag should be set as it was set in the backed up room key"
1188 );
1189 }
1190
1191 #[async_test]
1192 async fn test_shared_history_in_pickle() {
1193 let alice = Account::with_device_id(alice_id(), alice_device_id());
1194 let room_id = room_id!("!test:localhost");
1195
1196 let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1197
1198 inbound.shared_history = true;
1199 let pickle = inbound.pickle().await;
1200
1201 assert!(
1202 pickle.shared_history,
1203 "The set shared history flag should have been copied to the pickle"
1204 );
1205
1206 inbound.shared_history = false;
1207 let pickle = inbound.pickle().await;
1208
1209 assert!(
1210 !pickle.shared_history,
1211 "The unset shared history flag should have been copied to the pickle"
1212 );
1213 }
1214
1215 #[async_test]
1216 async fn test_shared_history_in_export() {
1217 let alice = Account::with_device_id(alice_id(), alice_device_id());
1218 let room_id = room_id!("!test:localhost");
1219
1220 let (_, mut inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
1221
1222 inbound.shared_history = true;
1223 let export = inbound.export().await;
1224 assert!(
1225 export.shared_history,
1226 "The set shared history flag should have been copied to the room key export"
1227 );
1228
1229 inbound.shared_history = false;
1230 let export = inbound.export().await;
1231 assert!(
1232 !export.shared_history,
1233 "The unset shared history flag should have been copied to the room key export"
1234 );
1235 }
1236}