1use std::{
27 collections::{BTreeMap, BTreeSet},
28 sync::Arc,
29};
30
31use ruma::{
32 api::client::backup::RoomKeyBackup, serde::Raw, DeviceId, DeviceKeyAlgorithm, OwnedDeviceId,
33 OwnedRoomId, OwnedTransactionId, RoomId, TransactionId,
34};
35use tokio::sync::RwLock;
36use tracing::{debug, info, instrument, trace, warn};
37
38use crate::{
39 olm::{BackedUpRoomKey, ExportedRoomKey, InboundGroupSession, SignedJsonObject},
40 store::{BackupDecryptionKey, BackupKeys, Changes, RoomKeyCounts, Store},
41 types::{requests::KeysBackupRequest, MegolmV1AuthData, RoomKeyBackupInfo, Signatures},
42 CryptoStoreError, Device, RoomKeyImportResult, SignatureError,
43};
44
45mod keys;
46
47pub use keys::{DecodeError, DecryptionError, MegolmV1BackupKey};
48
49#[derive(Debug, Clone)]
56pub struct BackupMachine {
57 store: Store,
58 backup_key: Arc<RwLock<Option<MegolmV1BackupKey>>>,
59 pending_backup: Arc<RwLock<Option<PendingBackup>>>,
60}
61
62type SenderKey = String;
63type SessionId = String;
64
65#[derive(Debug, Clone)]
66struct PendingBackup {
67 request_id: OwnedTransactionId,
68 request: KeysBackupRequest,
69 sessions: BTreeMap<OwnedRoomId, BTreeMap<SenderKey, BTreeSet<SessionId>>>,
70}
71
72#[derive(Clone, Debug, Default, PartialEq, Eq)]
74pub struct SignatureVerification {
75 pub device_signature: SignatureState,
78 pub user_identity_signature: SignatureState,
81 pub other_signatures: BTreeMap<OwnedDeviceId, SignatureState>,
84}
85
86impl SignatureVerification {
87 pub fn trusted(&self) -> bool {
96 self.device_signature.trusted()
97 || self.user_identity_signature.trusted()
98 || self.other_signatures.values().any(|s| s.trusted())
99 }
100}
101
102#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
104#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
105pub enum SignatureState {
106 #[default]
108 Missing,
109 Invalid,
111 ValidButNotTrusted,
114 ValidAndTrusted,
117}
118
119impl SignatureState {
120 pub fn trusted(self) -> bool {
122 self == SignatureState::ValidAndTrusted
123 }
124
125 pub fn signed(self) -> bool {
127 self == SignatureState::ValidButNotTrusted && self == SignatureState::ValidAndTrusted
128 }
129}
130
131impl BackupMachine {
132 const BACKUP_BATCH_SIZE: usize = 100;
133
134 pub(crate) fn new(store: Store, backup_key: Option<MegolmV1BackupKey>) -> Self {
135 Self {
136 store,
137 backup_key: RwLock::new(backup_key).into(),
138 pending_backup: RwLock::new(None).into(),
139 }
140 }
141
142 pub async fn enabled(&self) -> bool {
144 self.backup_key.read().await.as_ref().is_some_and(|b| b.backup_version().is_some())
145 }
146
147 fn check_own_device_signature(
149 &self,
150 signatures: &Signatures,
151 auth_data: &str,
152 ) -> SignatureState {
153 match self.store.static_account().has_signed_raw(signatures, auth_data) {
154 Ok(_) => SignatureState::ValidAndTrusted,
155 Err(e) => match e {
156 SignatureError::NoSignatureFound => SignatureState::Missing,
157 _ => SignatureState::Invalid,
158 },
159 }
160 }
161
162 async fn check_own_identity_signature(
165 &self,
166 signatures: &Signatures,
167 auth_data: &str,
168 ) -> Result<SignatureState, CryptoStoreError> {
169 let user_id = &self.store.static_account().user_id;
170 let identity = self.store.get_identity(user_id).await?;
171
172 let ret = if let Some(identity) = identity.and_then(|i| i.own()) {
173 match identity.master_key().has_signed_raw(signatures, auth_data) {
174 Ok(_) => {
175 if identity.is_verified() {
176 SignatureState::ValidAndTrusted
177 } else {
178 SignatureState::ValidButNotTrusted
179 }
180 }
181 Err(e) => match e {
182 SignatureError::NoSignatureFound => SignatureState::Missing,
183 _ => SignatureState::Invalid,
184 },
185 }
186 } else {
187 SignatureState::Missing
188 };
189
190 Ok(ret)
191 }
192
193 fn backup_signed_by_device(
196 &self,
197 device: Device,
198 signatures: &Signatures,
199 auth_data: &str,
200 ) -> SignatureState {
201 if device.has_signed_raw(signatures, auth_data).is_ok() {
202 if device.is_verified() {
203 SignatureState::ValidAndTrusted
204 } else {
205 SignatureState::ValidButNotTrusted
206 }
207 } else {
208 SignatureState::Invalid
209 }
210 }
211
212 async fn test_device_signatures(
215 &self,
216 signatures: &Signatures,
217 auth_data: &str,
218 compute_all_signatures: bool,
219 ) -> Result<BTreeMap<OwnedDeviceId, SignatureState>, CryptoStoreError> {
220 let mut result = BTreeMap::new();
221
222 if let Some(user_signatures) = signatures.get(&self.store.static_account().user_id) {
223 for device_key_id in user_signatures.keys() {
224 if device_key_id.algorithm() == DeviceKeyAlgorithm::Ed25519 {
225 if device_key_id.key_name() == self.store.static_account().device_id {
228 continue;
229 }
230
231 let state = self
232 .test_ed25519_device_signature(
233 device_key_id.key_name(),
234 signatures,
235 auth_data,
236 )
237 .await?;
238
239 result.insert(device_key_id.key_name().to_owned(), state);
240
241 if state.trusted() && !compute_all_signatures {
244 break;
245 }
246 }
247 }
248 }
249
250 Ok(result)
251 }
252
253 async fn test_ed25519_device_signature(
254 &self,
255 device_id: &DeviceId,
256 signatures: &Signatures,
257 auth_data: &str,
258 ) -> Result<SignatureState, CryptoStoreError> {
259 let device = self.store.get_device(self.store.user_id(), device_id).await?;
263 trace!(?device_id, "Checking backup auth data for device");
264
265 if let Some(device) = device {
266 Ok(self.backup_signed_by_device(device, signatures, auth_data))
267 } else {
268 trace!(?device_id, "Device not found, can't check signature");
269 Ok(SignatureState::Missing)
270 }
271 }
272
273 async fn verify_auth_data_v1(
274 &self,
275 auth_data: MegolmV1AuthData,
276 compute_all_signatures: bool,
277 ) -> Result<SignatureVerification, CryptoStoreError> {
278 let serialized_auth_data = match auth_data.to_canonical_json() {
279 Ok(s) => s,
280 Err(e) => {
281 warn!(error =? e, "Error while verifying backup, can't canonicalize auth data");
282 return Ok(Default::default());
283 }
284 };
285
286 let device_signature =
288 self.check_own_device_signature(&auth_data.signatures, &serialized_auth_data);
289 let user_identity_signature =
291 self.check_own_identity_signature(&auth_data.signatures, &serialized_auth_data).await?;
292
293 let other_signatures = if !(device_signature.trusted() || user_identity_signature.trusted())
296 || compute_all_signatures
297 {
298 self.test_device_signatures(
299 &auth_data.signatures,
300 &serialized_auth_data,
301 compute_all_signatures,
302 )
303 .await?
304 } else {
305 Default::default()
306 };
307
308 Ok(SignatureVerification { device_signature, user_identity_signature, other_signatures })
309 }
310
311 pub async fn verify_backup(
326 &self,
327 backup_info: RoomKeyBackupInfo,
328 compute_all_signatures: bool,
329 ) -> Result<SignatureVerification, CryptoStoreError> {
330 trace!(?backup_info, "Verifying backup auth data");
331
332 if let RoomKeyBackupInfo::MegolmBackupV1Curve25519AesSha2(data) = backup_info {
333 self.verify_auth_data_v1(data, compute_all_signatures).await
334 } else {
335 Ok(Default::default())
336 }
337 }
338
339 pub async fn sign_backup(
348 &self,
349 backup_info: &mut RoomKeyBackupInfo,
350 ) -> Result<(), SignatureError> {
351 if let RoomKeyBackupInfo::MegolmBackupV1Curve25519AesSha2(data) = backup_info {
352 let canonical_json = data.to_canonical_json()?;
353
354 let private_identity = self.store.private_identity();
355 let identity = private_identity.lock().await;
356
357 if let Some(key_id) = identity.master_key_id().await {
358 if let Ok(signature) = identity.sign(&canonical_json).await {
359 data.signatures.add_signature(
360 self.store.user_id().to_owned(),
361 key_id,
362 signature,
363 );
364 }
365 }
366
367 let cache = self.store.cache().await?;
368 let account = cache.account().await?;
369 let key_id = account.signing_key_id();
370 let signature = account.sign(&canonical_json);
371 data.signatures.add_signature(self.store.user_id().to_owned(), key_id, signature);
372
373 Ok(())
374 } else {
375 Err(SignatureError::UnsupportedAlgorithm)
376 }
377 }
378
379 pub async fn enable_backup_v1(&self, key: MegolmV1BackupKey) -> Result<(), CryptoStoreError> {
388 if key.backup_version().is_some() {
389 *self.backup_key.write().await = Some(key.clone());
390 info!(backup_key = ?key, "Activated a backup");
391 } else {
392 warn!(backup_key = ?key, "Tried to activate a backup without having the backup key uploaded");
393 }
394
395 Ok(())
396 }
397
398 pub async fn room_key_counts(&self) -> Result<RoomKeyCounts, CryptoStoreError> {
400 let backup_version = self.backup_key.read().await.as_ref().and_then(|k| k.backup_version());
401 self.store.inbound_group_session_counts(backup_version.as_deref()).await
402 }
403
404 #[instrument(skip(self))]
409 pub async fn disable_backup(&self) -> Result<(), CryptoStoreError> {
410 debug!("Disabling key backup and resetting backup state for room keys");
411
412 self.backup_key.write().await.take();
413 self.pending_backup.write().await.take();
414
415 self.store.reset_backup_state().await?;
416
417 debug!("Done disabling backup");
418
419 Ok(())
420 }
421
422 pub async fn backup_version(&self) -> Option<String> {
426 self.backup_key.read().await.as_ref().and_then(|k| k.backup_version())
427 }
428
429 pub async fn save_decryption_key(
434 &self,
435 backup_decryption_key: Option<BackupDecryptionKey>,
436 version: Option<String>,
437 ) -> Result<(), CryptoStoreError> {
438 let changes =
439 Changes { backup_decryption_key, backup_version: version, ..Default::default() };
440 self.store.save_changes(changes).await
441 }
442
443 pub async fn get_backup_keys(&self) -> Result<BackupKeys, CryptoStoreError> {
445 self.store.load_backup_keys().await
446 }
447
448 pub async fn backup(
451 &self,
452 ) -> Result<Option<(OwnedTransactionId, KeysBackupRequest)>, CryptoStoreError> {
453 let mut request = self.pending_backup.write().await;
454
455 if let Some(request) = &*request {
456 trace!("Backing up, returning an existing request");
457
458 Ok(Some((request.request_id.clone(), request.request.clone())))
459 } else {
460 trace!("Backing up, creating a new request");
461
462 let new_request = self.backup_helper().await?;
463 *request = new_request.clone();
464
465 Ok(new_request.map(|r| (r.request_id, r.request)))
466 }
467 }
468
469 pub(crate) async fn mark_request_as_sent(
470 &self,
471 request_id: &TransactionId,
472 ) -> Result<(), CryptoStoreError> {
473 let mut request = self.pending_backup.write().await;
474 if let Some(r) = &*request {
475 if r.request_id == request_id {
476 let room_and_session_ids: Vec<(&RoomId, &str)> = r
477 .sessions
478 .iter()
479 .flat_map(|(room_id, sender_key_to_session_ids)| {
480 std::iter::repeat(room_id).zip(sender_key_to_session_ids.values().flatten())
481 })
482 .map(|(room_id, session_id)| (room_id.as_ref(), session_id.as_str()))
483 .collect();
484
485 trace!(request_id = ?r.request_id, keys = ?r.sessions, "Marking room keys as backed up");
486
487 self.store
488 .mark_inbound_group_sessions_as_backed_up(
489 &r.request.version,
490 &room_and_session_ids,
491 )
492 .await?;
493
494 trace!(
495 request_id = ?r.request_id,
496 keys = ?r.sessions,
497 "Marked room keys as backed up"
498 );
499
500 *request = None;
501 } else {
502 warn!(
503 expected = ?r.request_id,
504 got = ?request_id,
505 "Tried to mark a pending backup as sent but the request id didn't match"
506 );
507 }
508 } else {
509 warn!(
510 ?request_id,
511 "Tried to mark a pending backup as sent but there isn't a backup pending"
512 );
513 };
514
515 Ok(())
516 }
517
518 async fn backup_helper(&self) -> Result<Option<PendingBackup>, CryptoStoreError> {
519 let Some(backup_key) = &*self.backup_key.read().await else {
520 warn!("Trying to backup room keys but no backup key was found");
521 return Ok(None);
522 };
523
524 let Some(version) = backup_key.backup_version() else {
525 warn!("Trying to backup room keys but the backup key wasn't uploaded");
526 return Ok(None);
527 };
528
529 let sessions =
530 self.store.inbound_group_sessions_for_backup(&version, Self::BACKUP_BATCH_SIZE).await?;
531
532 if sessions.is_empty() {
533 trace!(?backup_key, "No room keys need to be backed up");
534 return Ok(None);
535 }
536
537 let key_count = sessions.len();
538 let (backup, session_record) = Self::backup_keys(sessions, backup_key).await;
539
540 info!(
541 key_count = key_count,
542 keys = ?session_record,
543 ?backup_key,
544 "Successfully created a room keys backup request"
545 );
546
547 let request = PendingBackup {
548 request_id: TransactionId::new(),
549 request: KeysBackupRequest { version, rooms: backup },
550 sessions: session_record,
551 };
552
553 Ok(Some(request))
554 }
555
556 async fn backup_keys(
558 sessions: Vec<InboundGroupSession>,
559 backup_key: &MegolmV1BackupKey,
560 ) -> (
561 BTreeMap<OwnedRoomId, RoomKeyBackup>,
562 BTreeMap<OwnedRoomId, BTreeMap<SenderKey, BTreeSet<SessionId>>>,
563 ) {
564 let mut backup: BTreeMap<OwnedRoomId, RoomKeyBackup> = BTreeMap::new();
565 let mut session_record: BTreeMap<OwnedRoomId, BTreeMap<SenderKey, BTreeSet<SessionId>>> =
566 BTreeMap::new();
567
568 for session in sessions {
569 let room_id = session.room_id().to_owned();
570 let session_id = session.session_id().to_owned();
571 let sender_key = session.sender_key().to_owned();
572 let session = backup_key.encrypt(session).await;
573
574 session_record
575 .entry(room_id.to_owned())
576 .or_default()
577 .entry(sender_key.to_base64())
578 .or_default()
579 .insert(session_id.clone());
580
581 let session = Raw::new(&session).expect("Can't serialize a backed up room key");
582
583 backup
584 .entry(room_id)
585 .or_insert_with(|| RoomKeyBackup::new(BTreeMap::new()))
586 .sessions
587 .insert(session_id, session);
588 }
589
590 (backup, session_record)
591 }
592
593 #[deprecated(note = "Use the OlmMachine::store::import_room_keys method instead")]
604 pub async fn import_backed_up_room_keys(
605 &self,
606 room_keys: BTreeMap<OwnedRoomId, BTreeMap<String, BackedUpRoomKey>>,
607 progress_listener: impl Fn(usize, usize),
608 ) -> Result<RoomKeyImportResult, CryptoStoreError> {
609 let mut decrypted_room_keys = vec![];
610
611 for (room_id, room_keys) in room_keys {
612 for (session_id, room_key) in room_keys {
613 let room_key = ExportedRoomKey::from_backed_up_room_key(
614 room_id.to_owned(),
615 session_id,
616 room_key,
617 );
618
619 decrypted_room_keys.push(room_key);
620 }
621 }
622
623 let backup_version = self.backup_version().await;
628
629 self.store
630 .import_room_keys(decrypted_room_keys, backup_version.as_deref(), progress_listener)
631 .await
632 }
633}
634
635#[cfg(test)]
636mod tests {
637 use std::collections::BTreeMap;
638
639 use assert_matches2::assert_let;
640 use matrix_sdk_test::async_test;
641 use ruma::{device_id, room_id, user_id, CanonicalJsonValue, DeviceId, RoomId, UserId};
642 use serde_json::json;
643
644 use super::BackupMachine;
645 use crate::{
646 olm::BackedUpRoomKey,
647 store::{BackupDecryptionKey, Changes, CryptoStore, MemoryStore},
648 types::RoomKeyBackupInfo,
649 OlmError, OlmMachine,
650 };
651
652 fn room_key() -> BackedUpRoomKey {
653 let json = json!({
654 "algorithm": "m.megolm.v1.aes-sha2",
655 "sender_key": "DeHIg4gwhClxzFYcmNntPNF9YtsdZbmMy8+3kzCMXHA",
656 "session_key": "AQAAAABvWMNZjKFtebYIePKieQguozuoLgzeY6wKcyJjLJcJtQgy1dPqTBD12U+XrYLrRHn\
657 lKmxoozlhFqJl456+9hlHCL+yq+6ScFuBHtJepnY1l2bdLb4T0JMDkNsNErkiLiLnD6yp3J\
658 DSjIhkdHxmup/huygrmroq6/L5TaThEoqvW4DPIuO14btKudsS34FF82pwjKS4p6Mlch+0e\
659 fHAblQV",
660 "sender_claimed_keys":{},
661 "forwarding_curve25519_key_chain":[]
662 });
663
664 serde_json::from_value(json)
665 .expect("We should be able to deserialize our backed up room key")
666 }
667
668 fn alice_id() -> &'static UserId {
669 user_id!("@alice:example.org")
670 }
671
672 fn alice_device_id() -> &'static DeviceId {
673 device_id!("JLAFKJWSCS")
674 }
675
676 fn room_id() -> &'static RoomId {
677 room_id!("!test:localhost")
678 }
679
680 fn room_id2() -> &'static RoomId {
681 room_id!("!test2:localhost")
682 }
683
684 async fn backup_flow(machine: OlmMachine) -> Result<(), OlmError> {
685 let backup_machine = machine.backup_machine();
686 let backup_version = current_backup_version(backup_machine).await;
687
688 let counts =
689 backup_machine.store.inbound_group_session_counts(backup_version.as_deref()).await?;
690
691 assert_eq!(counts.total, 0, "Initially no keys exist");
692 assert_eq!(counts.backed_up, 0, "Initially no backed up keys exist");
693
694 machine.create_outbound_group_session_with_defaults_test_helper(room_id()).await?;
695 machine.create_outbound_group_session_with_defaults_test_helper(room_id2()).await?;
696
697 let counts =
698 backup_machine.store.inbound_group_session_counts(backup_version.as_deref()).await?;
699 assert_eq!(counts.total, 2, "Two room keys need to exist in the store");
700 assert_eq!(counts.backed_up, 0, "No room keys have been backed up yet");
701
702 let decryption_key = BackupDecryptionKey::new().expect("Can't create new recovery key");
703 let backup_key = decryption_key.megolm_v1_public_key();
704 backup_key.set_version("1".to_owned());
705
706 backup_machine.enable_backup_v1(backup_key).await?;
707
708 let (request_id, _) =
709 backup_machine.backup().await?.expect("Created a backup request successfully");
710 assert_eq!(
711 Some(&request_id),
712 backup_machine.backup().await?.as_ref().map(|(request_id, _)| request_id),
713 "Calling backup again without uploading creates the same backup request"
714 );
715
716 backup_machine.mark_request_as_sent(&request_id).await?;
717 let backup_version = current_backup_version(backup_machine).await;
718
719 let counts =
720 backup_machine.store.inbound_group_session_counts(backup_version.as_deref()).await?;
721 assert_eq!(counts.total, 2);
722 assert_eq!(counts.backed_up, 2, "All room keys have been backed up");
723
724 assert!(
725 backup_machine.backup().await?.is_none(),
726 "No room keys need to be backed up, no request needs to be created"
727 );
728
729 backup_machine.disable_backup().await?;
730 let backup_version = current_backup_version(backup_machine).await;
731
732 let counts =
733 backup_machine.store.inbound_group_session_counts(backup_version.as_deref()).await?;
734 assert_eq!(counts.total, 2);
735 assert_eq!(
736 counts.backed_up, 0,
737 "Disabling the backup resets the backup flag on the room keys"
738 );
739
740 Ok(())
741 }
742
743 async fn current_backup_version(backup_machine: &BackupMachine) -> Option<String> {
744 backup_machine.backup_key.read().await.as_ref().and_then(|k| k.backup_version())
745 }
746
747 #[async_test]
748 async fn test_memory_store_backups() -> Result<(), OlmError> {
749 let machine = OlmMachine::new(alice_id(), alice_device_id()).await;
750
751 backup_flow(machine).await
752 }
753
754 #[async_test]
755 async fn test_verify_auth_data() -> Result<(), OlmError> {
756 let machine = OlmMachine::new(alice_id(), alice_device_id()).await;
757 let backup_machine = machine.backup_machine();
758
759 let auth_data = json!({
760 "public_key":"XjhWTCjW7l59pbfx9tlCBQolfnIQWARoKOzjTOPSlWM",
761 });
762
763 let backup_version = json!({
764 "algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
765 "auth_data": auth_data,
766 });
767
768 let canonical_json: CanonicalJsonValue =
769 auth_data.clone().try_into().expect("Canonicalizing should always work");
770 let serialized = canonical_json.to_string();
771
772 let backup_version: RoomKeyBackupInfo = serde_json::from_value(backup_version).unwrap();
773
774 let state = backup_machine
775 .verify_backup(backup_version, false)
776 .await
777 .expect("Verifying should work");
778 assert!(!state.trusted());
779 assert!(!state.device_signature.trusted());
780 assert!(!state.user_identity_signature.trusted());
781 assert!(!state.other_signatures.values().any(|s| s.trusted()));
782
783 let signatures = machine.sign(&serialized).await?;
784
785 let backup_version = json!({
786 "algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
787 "auth_data": {
788 "public_key":"XjhWTCjW7l59pbfx9tlCBQolfnIQWARoKOzjTOPSlWM",
789 "signatures": signatures,
790 }
791 });
792 let backup_version: RoomKeyBackupInfo = serde_json::from_value(backup_version).unwrap();
793
794 let state = backup_machine
795 .verify_backup(backup_version, false)
796 .await
797 .expect("Verifying should work");
798
799 assert!(state.trusted());
800 assert!(state.device_signature.trusted());
801 assert!(!state.user_identity_signature.trusted());
802 assert!(!state.other_signatures.values().any(|s| s.trusted()));
803
804 machine
805 .bootstrap_cross_signing(true)
806 .await
807 .expect("Bootstrapping a new identity always works");
808
809 let signatures = machine.sign(&serialized).await?;
810
811 let backup_version = json!({
812 "algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
813 "auth_data": {
814 "public_key":"XjhWTCjW7l59pbfx9tlCBQolfnIQWARoKOzjTOPSlWM",
815 "signatures": signatures,
816 }
817 });
818 let backup_version: RoomKeyBackupInfo = serde_json::from_value(backup_version).unwrap();
819
820 let state = backup_machine
821 .verify_backup(backup_version, false)
822 .await
823 .expect("Verifying should work");
824
825 assert!(state.trusted());
826 assert!(state.device_signature.trusted());
827 assert!(state.user_identity_signature.trusted());
828 assert!(!state.other_signatures.values().any(|s| s.trusted()));
829
830 Ok(())
831 }
832
833 #[async_test]
834 async fn test_import_backed_up_room_keys() {
835 let machine = OlmMachine::new(alice_id(), alice_device_id()).await;
836 let backup_machine = machine.backup_machine();
837
838 let decryption_key = BackupDecryptionKey::new().expect("Couldn't create new recovery key");
840 let backup_key = decryption_key.megolm_v1_public_key();
841 backup_key.set_version("1".to_owned());
842 backup_machine.enable_backup_v1(backup_key).await.expect("Couldn't enable backup");
843
844 let room_id = room_id!("!DovneieKSTkdHKpIXy:morpheus.localhost");
845 let session_id = "gM8i47Xhu0q52xLfgUXzanCMpLinoyVyH7R58cBuVBU";
846 let room_key = room_key();
847
848 let room_keys: BTreeMap<_, BTreeMap<_, _>> = BTreeMap::from([(
849 room_id.to_owned(),
850 BTreeMap::from([(session_id.to_owned(), room_key)]),
851 )]);
852
853 let session = machine.store().get_inbound_group_session(room_id, session_id).await.unwrap();
854
855 assert!(session.is_none(), "Initially we should not have the session in the store");
856
857 #[allow(deprecated)]
858 backup_machine
859 .import_backed_up_room_keys(room_keys, |_, _| {})
860 .await
861 .expect("We should be able to import a room key");
862
863 let session = machine.store().get_inbound_group_session(room_id, session_id).await.unwrap();
866 assert_let!(Some(session) = session);
867 assert!(
868 session.backed_up(),
869 "If a session was imported from a backup, it should be considered to be backed up"
870 );
871 assert!(session.has_been_imported());
872
873 let backup_request =
875 backup_machine.backup().await.expect("We should be able to create a backup request");
876 assert!(
877 backup_request.is_none(),
878 "If a session was imported from backup, it should not be backed up again."
879 );
880 }
881
882 #[async_test]
883 async fn test_sign_backup_info() {
884 let machine = OlmMachine::new(alice_id(), alice_device_id()).await;
885 let backup_machine = machine.backup_machine();
886
887 let decryption_key = BackupDecryptionKey::new().unwrap();
888 let mut backup_info = decryption_key.to_backup_info();
889
890 let result = backup_machine.verify_backup(backup_info.to_owned(), false).await.unwrap();
891
892 assert!(!result.trusted());
893
894 backup_machine.sign_backup(&mut backup_info).await.unwrap();
895
896 let result = backup_machine.verify_backup(backup_info, false).await.unwrap();
897
898 assert!(result.trusted());
899 }
900
901 #[async_test]
902 async fn test_fix_backup_key_mismatch() {
903 let store = MemoryStore::new();
904
905 let backup_decryption_key = BackupDecryptionKey::new().unwrap();
906
907 store
908 .save_changes(Changes {
909 backup_decryption_key: Some(backup_decryption_key.clone()),
910 backup_version: Some("1".to_owned()),
911 ..Default::default()
912 })
913 .await
914 .unwrap();
915
916 let alice =
919 OlmMachine::with_store(alice_id(), alice_device_id(), store, None).await.unwrap();
920
921 let binding = alice.backup_machine().backup_key.read().await;
922 let machine_backup_key = binding.as_ref().unwrap();
923
924 assert_eq!(
925 machine_backup_key.to_base64(),
926 backup_decryption_key.megolm_v1_public_key().to_base64(),
927 "The OlmMachine loaded the wrong backup key."
928 );
929 }
930}