1use std::collections::{BTreeMap, BTreeSet};
24
25use futures_core::Stream;
26use futures_util::StreamExt;
27#[cfg(feature = "experimental-encrypted-state-events")]
28use matrix_sdk_base::crypto::types::events::room::encrypted::EncryptedEvent;
29use matrix_sdk_base::crypto::{
30 OlmMachine, RoomKeyImportResult,
31 backups::MegolmV1BackupKey,
32 store::types::BackupDecryptionKey,
33 types::{RoomKeyBackupInfo, requests::KeysBackupRequest},
34};
35#[cfg(feature = "experimental-push-secrets")]
36use ruma::events::secret::push::ToDeviceSecretPushEvent;
37#[cfg(feature = "experimental-encrypted-state-events")]
38use ruma::serde::JsonCastable;
39use ruma::{
40 OwnedRoomId, RoomId, TransactionId,
41 api::{
42 client::backup::{
43 RoomKeyBackup, add_backup_keys, create_backup_version, get_backup_keys,
44 get_backup_keys_for_room, get_backup_keys_for_session, get_latest_backup_info,
45 },
46 error::ErrorKind,
47 },
48 events::{
49 room::encrypted::OriginalSyncRoomEncryptedEvent,
50 secret::{request::SecretName, send::ToDeviceSecretSendEvent},
51 },
52 serde::Raw,
53};
54use tokio_stream::wrappers::{BroadcastStream, errors::BroadcastStreamRecvError};
55use tracing::{Span, error, info, instrument, trace, warn};
56
57pub mod futures;
58pub(crate) mod types;
59
60use matrix_sdk_base::crypto::olm::ExportedRoomKey;
61pub use types::{BackupState, UploadState};
62
63use self::futures::WaitForSteadyState;
64use crate::{Client, Error, Room, encryption::BackupDownloadStrategy};
65
66#[derive(Debug, Clone)]
68pub struct Backups {
69 pub(super) client: Client,
70}
71
72impl Backups {
73 pub async fn create(&self) -> Result<(), Error> {
98 self.client.inner.e2ee.backup_state.clear_backup_exists_on_server();
99 let _guard = self.client.locks().backup_modify_lock.lock().await;
100
101 self.set_state(BackupState::Creating);
102
103 let future = async {
106 let olm_machine = self.client.olm_machine().await;
107 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
108
109 let decryption_key = BackupDecryptionKey::new();
111
112 let mut backup_info = decryption_key.to_backup_info();
134
135 if let Err(e) = olm_machine.backup_machine().sign_backup(&mut backup_info).await {
136 warn!("Unable to sign the newly created backup version: {e:?}");
137 }
138
139 let algorithm = Raw::new(&backup_info)?.cast();
140 let request = create_backup_version::v3::Request::new(algorithm);
141 let response = self.client.send(request).await?;
142 let version = response.version;
143
144 olm_machine.backup_machine().disable_backup().await?;
147
148 let backup_key = decryption_key.megolm_v1_public_key();
149
150 olm_machine
152 .backup_machine()
153 .save_decryption_key(Some(decryption_key), Some(version.to_owned()))
154 .await?;
155
156 self.enable(olm_machine, backup_key, version).await?;
158
159 #[cfg(feature = "experimental-push-secrets")]
160 {
161 if let Some((txn_id, keys_claim_request)) = olm_machine
167 .get_missing_sessions(vec![olm_machine.user_id()].into_iter())
168 .await?
169 {
170 let keys_claim_response = self.client.send(keys_claim_request).await?;
171 olm_machine.mark_request_as_sent(&txn_id, &keys_claim_response).await?;
172 }
173
174 let _ = olm_machine.push_secret_to_verified_devices(SecretName::RecoveryKey).await;
178 }
179
180 Ok(())
181 };
182
183 let result = future.await;
184
185 if result.is_err() {
186 self.set_state(BackupState::Unknown);
187 }
188
189 result
190 }
191
192 #[instrument(skip_all, fields(version))]
213 pub async fn disable(&self) -> Result<(), Error> {
214 let _guard = self.client.locks().backup_modify_lock.lock().await;
215
216 self.set_state(BackupState::Disabling);
217
218 let future = async {
220 let olm_machine = self.client.olm_machine().await;
221 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
222
223 let backup_keys = olm_machine.backup_machine().get_backup_keys().await?;
224
225 if let Some(version) = backup_keys.backup_version {
226 Span::current().record("version", &version);
227 info!("Deleting and disabling backup");
228
229 self.delete_backup_from_server(version).await?;
230 info!("Backup successfully deleted");
231
232 olm_machine.backup_machine().disable_backup().await?;
233
234 info!("Backup successfully disabled and deleted");
235
236 Ok(())
237 } else {
238 info!("Backup is not enabled, can't disable it");
239 Err(Error::BackupNotEnabled)
240 }
241 };
242
243 let result = future.await;
244
245 self.set_state(BackupState::Unknown);
246
247 result
248 }
249
250 pub async fn disable_and_delete(&self) -> Result<(), Error> {
277 let _guard = self.client.locks().backup_modify_lock.lock().await;
278
279 self.set_state(BackupState::Disabling);
280
281 let future = async {
283 while let Some(response) = self.get_current_version().await? {
284 self.delete_backup_from_server(response.version).await?;
285 }
286
287 let olm_machine = self.client.olm_machine().await;
288 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
289
290 olm_machine.backup_machine().disable_backup().await?;
291
292 Ok(())
293 };
294
295 let result = future.await;
296
297 self.set_state(BackupState::Unknown);
298
299 result
300 }
301
302 pub fn wait_for_steady_state(&self) -> WaitForSteadyState<'_> {
346 WaitForSteadyState {
347 backups: self,
348 progress: self.client.inner.e2ee.backup_state.upload_progress.clone(),
349 timeout: None,
350 }
351 }
352
353 pub fn state_stream(
384 &self,
385 ) -> impl Stream<Item = Result<BackupState, BroadcastStreamRecvError>> + use<> {
386 self.client.inner.e2ee.backup_state.global_state.subscribe()
387 }
388
389 pub fn state(&self) -> BackupState {
391 self.client.inner.e2ee.backup_state.global_state.get()
392 }
393
394 pub async fn are_enabled(&self) -> bool {
399 let olm_machine = self.client.olm_machine().await;
400
401 if let Some(machine) = olm_machine.as_ref() {
402 machine.backup_machine().enabled().await
403 } else {
404 false
405 }
406 }
407
408 pub async fn fetch_exists_on_server(&self) -> Result<bool, Error> {
413 let exists_on_server = self.get_current_version().await?.is_some();
414 self.client.inner.e2ee.backup_state.set_backup_exists_on_server(exists_on_server);
415 Ok(exists_on_server)
416 }
417
418 pub async fn exists_on_server(&self) -> Result<bool, Error> {
429 if let Some(cached_value) = self.client.inner.e2ee.backup_state.backup_exists_on_server() {
431 return Ok(cached_value);
432 }
433
434 self.fetch_exists_on_server().await
437 }
438
439 pub fn room_keys_for_room_stream(
442 &self,
443 room_id: &RoomId,
444 ) -> impl Stream<Item = Result<BTreeMap<String, BTreeSet<String>>, BroadcastStreamRecvError>> + use<>
445 {
446 let room_id = room_id.to_owned();
447
448 self.room_keys_stream().filter_map(move |import_result| {
456 let room_id = room_id.to_owned();
457
458 async move {
459 match import_result {
460 Ok(mut import_result) => import_result.keys.remove(&room_id).map(Ok),
461 Err(e) => Some(Err(e)),
462 }
463 }
464 })
465 }
466
467 pub async fn download_room_keys_for_room(&self, room_id: &RoomId) -> Result<(), Error> {
470 let olm_machine = self.client.olm_machine().await;
471 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
472
473 let backup_keys = olm_machine.store().load_backup_keys().await?;
474
475 if let Some(decryption_key) = backup_keys.decryption_key
476 && let Some(version) = backup_keys.backup_version
477 {
478 let request =
479 get_backup_keys_for_room::v3::Request::new(version.clone(), room_id.to_owned());
480 let response = self.client.send(request).await?;
481
482 let response = get_backup_keys::v3::Response::new(BTreeMap::from([(
484 room_id.to_owned(),
485 RoomKeyBackup::new(response.sessions),
486 )]));
487
488 self.handle_downloaded_room_keys(response, decryption_key, &version, olm_machine)
489 .await?;
490 }
491
492 Ok(())
493 }
494
495 pub async fn download_room_key(
502 &self,
503 room_id: &RoomId,
504 session_id: &str,
505 ) -> Result<bool, Error> {
506 let olm_machine = self.client.olm_machine().await;
507 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
508
509 let backup_keys = olm_machine.store().load_backup_keys().await?;
510
511 if let Some(decryption_key) = backup_keys.decryption_key {
512 if let Some(version) = backup_keys.backup_version {
513 let request = get_backup_keys_for_session::v3::Request::new(
514 version.clone(),
515 room_id.to_owned(),
516 session_id.to_owned(),
517 );
518 let response = self.client.send(request).await?;
519
520 let response = get_backup_keys::v3::Response::new(BTreeMap::from([(
522 room_id.to_owned(),
523 RoomKeyBackup::new(BTreeMap::from([(
524 session_id.to_owned(),
525 response.key_data,
526 )])),
527 )]));
528
529 self.handle_downloaded_room_keys(response, decryption_key, &version, olm_machine)
530 .await?;
531
532 Ok(true)
533 } else {
534 Ok(false)
535 }
536 } else {
537 Ok(false)
538 }
539 }
540
541 fn set_state(&self, new_state: BackupState) {
543 let old_state = self.client.inner.e2ee.backup_state.global_state.set(new_state);
544
545 if old_state != new_state {
546 info!("Backup state changed from {old_state:?} to {new_state:?}");
547 }
548 }
549
550 async fn enable(
553 &self,
554 olm_machine: &OlmMachine,
555 backup_key: MegolmV1BackupKey,
556 version: String,
557 ) -> Result<(), Error> {
558 backup_key.set_version(version);
559 olm_machine.backup_machine().enable_backup_v1(backup_key).await?;
560
561 self.set_state(BackupState::Enabled);
562
563 Ok(())
564 }
565
566 async fn handle_downloaded_room_keys(
569 &self,
570 backed_up_keys: get_backup_keys::v3::Response,
571 backup_decryption_key: BackupDecryptionKey,
572 backup_version: &str,
573 olm_machine: &OlmMachine,
574 ) -> Result<(), Error> {
575 let mut decrypted_room_keys: Vec<_> = Vec::new();
576
577 for (room_id, room_keys) in backed_up_keys.rooms {
578 for (session_id, room_key) in room_keys.sessions {
579 let room_key = match room_key.deserialize() {
580 Ok(k) => k,
581 Err(e) => {
582 warn!(
583 "Couldn't deserialize a room key we downloaded from backups, session \
584 ID: {session_id}, error: {e:?}"
585 );
586 continue;
587 }
588 };
589
590 let room_key =
591 match backup_decryption_key.decrypt_session_data(room_key.session_data) {
592 Ok(k) => k,
593 Err(e) => {
594 warn!(
595 "Couldn't decrypt a room key we downloaded from backups, session \
596 ID: {session_id}, error: {e:?}"
597 );
598 continue;
599 }
600 };
601
602 decrypted_room_keys.push(ExportedRoomKey::from_backed_up_room_key(
603 room_id.to_owned(),
604 session_id,
605 room_key,
606 ));
607 }
608 }
609
610 let result = olm_machine
611 .store()
612 .import_room_keys(decrypted_room_keys, Some(backup_version), |_, _| {})
613 .await?;
614
615 let _ = self.client.inner.e2ee.backup_state.room_keys_broadcaster.send(result);
618
619 Ok(())
620 }
621
622 async fn download_all_room_keys(
624 &self,
625 decryption_key: BackupDecryptionKey,
626 version: String,
627 ) -> Result<(), Error> {
628 let request = get_backup_keys::v3::Request::new(version.clone());
629 let response = self.client.send(request).await?;
630
631 let olm_machine = self.client.olm_machine().await;
632 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
633
634 self.handle_downloaded_room_keys(response, decryption_key, &version, olm_machine).await?;
635
636 Ok(())
637 }
638
639 fn room_keys_stream(
640 &self,
641 ) -> impl Stream<Item = Result<RoomKeyImportResult, BroadcastStreamRecvError>> + use<> {
642 BroadcastStream::new(self.client.inner.e2ee.backup_state.room_keys_broadcaster.subscribe())
643 }
644
645 async fn get_current_version(
647 &self,
648 ) -> Result<Option<get_latest_backup_info::v3::Response>, Error> {
649 let request = get_latest_backup_info::v3::Request::new();
650
651 match self.client.send(request).await {
652 Ok(r) => Ok(Some(r)),
653 Err(e) => {
654 if let Some(kind) = e.client_api_error_kind() {
655 if kind == &ErrorKind::NotFound { Ok(None) } else { Err(e.into()) }
656 } else {
657 Err(e.into())
658 }
659 }
660 }
661 }
662
663 async fn delete_backup_from_server(&self, version: String) -> Result<(), Error> {
664 let request = ruma::api::client::backup::delete_backup_version::v3::Request::new(version);
665
666 let ret = match self.client.send(request).await {
667 Ok(_) => Ok(()),
668 Err(e) => {
669 if let Some(kind) = e.client_api_error_kind() {
670 if kind == &ErrorKind::NotFound { Ok(()) } else { Err(e.into()) }
671 } else {
672 Err(e.into())
673 }
674 }
675 };
676
677 self.client.inner.e2ee.backup_state.clear_backup_exists_on_server();
681
682 ret
683 }
684
685 #[instrument(skip(self, olm_machine, request))]
686 async fn send_backup_request(
687 &self,
688 olm_machine: &OlmMachine,
689 request_id: &TransactionId,
690 request: KeysBackupRequest,
691 ) -> Result<(), Error> {
692 trace!("Uploading some room keys");
693
694 let add_backup_keys = add_backup_keys::v3::Request::new(request.version, request.rooms);
695
696 match self.client.send(add_backup_keys).await {
697 Ok(response) => {
698 olm_machine.mark_request_as_sent(request_id, &response).await?;
699
700 let new_counts = olm_machine.backup_machine().room_key_counts().await?;
701
702 self.client
703 .inner
704 .e2ee
705 .backup_state
706 .upload_progress
707 .set(UploadState::Uploading(new_counts));
708
709 let delay =
710 self.client.inner.e2ee.backup_state.upload_delay.read().unwrap().to_owned();
711 crate::sleep::sleep(delay).await;
712
713 Ok(())
714 }
715 Err(error) => {
716 if let Some(kind) = error.client_api_error_kind() {
717 match kind {
718 ErrorKind::NotFound => {
719 warn!(
720 "No backup found on the server, the backup likely got deleted, \
721 disabling backups."
722 );
723
724 self.handle_deleted_backup_version(olm_machine).await?;
725 }
726 ErrorKind::WrongRoomKeysVersion(wrong_version) => {
727 warn!(
728 new_version = wrong_version.current_version,
729 "A new backup version was found on the server, disabling backups."
730 );
731
732 self.handle_deleted_backup_version(olm_machine).await?;
736 }
737
738 _ => (),
739 }
740 }
741
742 Err(error.into())
743 }
744 }
745 }
746
747 pub(crate) async fn backup_room_keys(&self) -> Result<(), Error> {
754 let _guard = self.client.locks().backup_upload_lock.lock().await;
755
756 let olm_machine = self.client.olm_machine().await;
757 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
758
759 while let Some((request_id, request)) = olm_machine.backup_machine().backup().await? {
760 self.send_backup_request(olm_machine, &request_id, request).await?;
761 }
762
763 self.client.inner.e2ee.backup_state.upload_progress.set(UploadState::Done);
764
765 Ok(())
766 }
767
768 pub(crate) async fn setup_and_resume(&self) -> Result<(), Error> {
771 info!("Setting up secret listeners and trying to resume backups");
772
773 self.client.add_event_handler(Self::secret_send_event_handler);
774 #[cfg(feature = "experimental-push-secrets")]
775 self.client.add_event_handler(Self::secret_push_event_handler);
776
777 if self.client.inner.e2ee.encryption_settings.backup_download_strategy
778 == BackupDownloadStrategy::AfterDecryptionFailure
779 {
780 self.client.add_event_handler(Self::utd_event_handler);
781 }
782
783 self.maybe_resume_backups().await?;
784
785 Ok(())
786 }
787
788 #[instrument(skip_all)]
804 pub(crate) async fn maybe_enable_backups(
805 &self,
806 maybe_recovery_key: &str,
807 ) -> Result<bool, EnableBackupError> {
808 let _guard = self.client.locks().backup_modify_lock.lock().await;
809
810 let future = async {
813 self.set_state(BackupState::Enabling);
814
815 let olm_machine = self.client.olm_machine().await;
816 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
817 let backup_machine = olm_machine.backup_machine();
818
819 let decryption_key =
820 BackupDecryptionKey::from_base64(maybe_recovery_key).map_err(|e| {
821 <serde_json::Error as serde::de::Error>::custom(format!(
822 "Couldn't deserialize the backup recovery key: {e:?}"
823 ))
824 })?;
825
826 let current_version = self.get_current_version().await?;
828
829 let Some(current_version) = current_version else {
830 warn!("Tried to enable backups, but no backup version was found on the server.");
831 return Ok(false);
832 };
833
834 Span::current().record("backup_version", ¤t_version.version);
835
836 let backup_info: RoomKeyBackupInfo = current_version.algorithm.deserialize_as()?;
837 let stored_keys = backup_machine.get_backup_keys().await?;
838
839 if stored_keys.backup_version.as_ref() == Some(¤t_version.version)
840 && self.are_enabled().await
841 {
842 Ok(true)
846 } else if decryption_key.backup_key_matches(&backup_info) {
847 info!(
848 "We have found the correct backup recovery key. Storing the backup recovery \
849 key and enabling backups."
850 );
851
852 backup_machine.disable_backup().await?;
855
856 let backup_key = decryption_key.megolm_v1_public_key();
857 backup_key.set_version(current_version.version.to_owned());
858
859 backup_machine
861 .save_decryption_key(
862 Some(decryption_key.to_owned()),
863 Some(current_version.version.to_owned()),
864 )
865 .await?;
866 backup_machine.enable_backup_v1(backup_key).await?;
867
868 if self.client.inner.e2ee.encryption_settings.backup_download_strategy
877 == BackupDownloadStrategy::OneShot
878 {
879 self.set_state(BackupState::Downloading);
880
881 if let Err(e) =
882 self.download_all_room_keys(decryption_key, current_version.version).await
883 {
884 warn!("Couldn't automatically download all room keys from backup: {e:?}");
885 }
886 }
887
888 self.maybe_trigger_backup();
890
891 Ok(true)
892 } else {
893 let derived_key = decryption_key.megolm_v1_public_key();
894 let downloaded_key = current_version.algorithm;
895
896 warn!(
897 ?derived_key,
898 ?downloaded_key,
899 "Found an active backup but the recovery key we received isn't the one used for \
900 this backup version"
901 );
902
903 Err(EnableBackupError::InconsistentBackupDecryptionKey)
904 }
905 };
906
907 match future.await {
908 Ok(enabled) => {
909 if enabled {
910 self.set_state(BackupState::Enabled);
911 } else {
912 self.set_state(BackupState::Unknown);
913 }
914
915 Ok(enabled)
916 }
917 Err(e) => {
918 self.set_state(BackupState::Unknown);
919
920 Err(e)
921 }
922 }
923 }
924
925 async fn resume_backup_from_stored_backup_key(
930 &self,
931 olm_machine: &OlmMachine,
932 ) -> Result<bool, Error> {
933 let backup_keys = olm_machine.store().load_backup_keys().await?;
934
935 if let Some(decryption_key) = backup_keys.decryption_key {
936 if let Some(version) = backup_keys.backup_version {
937 let backup_key = decryption_key.megolm_v1_public_key();
938
939 self.enable(olm_machine, backup_key, version).await?;
940
941 Ok(true)
942 } else {
943 Ok(false)
944 }
945 } else {
946 Ok(false)
947 }
948 }
949
950 async fn maybe_resume_from_secret_inbox(&self, olm_machine: &OlmMachine) -> Result<(), Error> {
954 let secrets = olm_machine.store().get_secrets_from_inbox(&SecretName::RecoveryKey).await?;
955
956 for secret in secrets {
957 match self.maybe_enable_backups(&secret).await {
958 Ok(enabled) => {
959 if enabled {
960 break;
961 }
962 }
963 Err(EnableBackupError::InconsistentBackupDecryptionKey) => {
964 }
967 Err(EnableBackupError::Error(e)) => return Err(e),
968 }
969 }
970
971 olm_machine.store().delete_secrets_from_inbox(&SecretName::RecoveryKey).await?;
972
973 Ok(())
974 }
975
976 pub(super) async fn maybe_resume_backups(&self) -> Result<(), Error> {
978 let olm_machine = self.client.olm_machine().await;
979 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
980
981 if !self.resume_backup_from_stored_backup_key(olm_machine).await? {
984 self.maybe_resume_from_secret_inbox(olm_machine).await?;
987 }
988
989 Ok(())
990 }
991
992 #[instrument(skip_all)]
995 pub(crate) async fn secret_send_event_handler(_: ToDeviceSecretSendEvent, client: Client) {
996 let olm_machine = client.olm_machine().await;
997
998 if let Some(olm_machine) = olm_machine.as_ref() {
1003 if let Err(e) =
1004 client.encryption().backups().maybe_resume_from_secret_inbox(olm_machine).await
1005 {
1006 error!("Could not handle `m.secret.send` event: {e:?}");
1007 }
1008 } else {
1009 error!("Tried to handle a `m.secret.send` event but no OlmMachine was initialized");
1010 }
1011 }
1012
1013 #[cfg(feature = "experimental-push-secrets")]
1016 #[instrument(skip_all)]
1017 pub(crate) async fn secret_push_event_handler(_: ToDeviceSecretPushEvent, client: Client) {
1018 let olm_machine = client.olm_machine().await;
1019
1020 if let Some(olm_machine) = olm_machine.as_ref() {
1026 if let Err(e) =
1027 client.encryption().backups().maybe_resume_from_secret_inbox(olm_machine).await
1028 {
1029 error!("Could not handle `io.element.msc4385.secret.push` event: {e:?}");
1030 }
1031 } else {
1032 error!(
1033 "Tried to handle a `io.element.msc4385.secret.push` event but no OlmMachine was initialized"
1034 );
1035 }
1036 }
1037
1038 #[allow(clippy::unused_async)] pub(crate) async fn utd_event_handler(
1047 event: Raw<OriginalSyncRoomEncryptedEvent>,
1048 room: Room,
1049 client: Client,
1050 ) {
1051 client.encryption().backups().maybe_download_room_key(room.room_id().to_owned(), event);
1052 }
1053
1054 #[cfg(not(feature = "experimental-encrypted-state-events"))]
1057 pub(crate) fn maybe_download_room_key(
1058 &self,
1059 room_id: OwnedRoomId,
1060 event: Raw<OriginalSyncRoomEncryptedEvent>,
1061 ) {
1062 let tasks = self.client.inner.e2ee.tasks.lock();
1063 if let Some(task) = tasks.download_room_keys.as_ref() {
1064 task.trigger_download_for_utd_event(room_id, event);
1065 }
1066 }
1067
1068 #[cfg(feature = "experimental-encrypted-state-events")]
1071 pub(crate) fn maybe_download_room_key<T: JsonCastable<EncryptedEvent>>(
1072 &self,
1073 room_id: OwnedRoomId,
1074 event: Raw<T>,
1075 ) {
1076 let tasks = self.client.inner.e2ee.tasks.lock();
1077 if let Some(task) = tasks.download_room_keys.as_ref() {
1078 task.trigger_download_for_utd_event(room_id, event);
1079 }
1080 }
1081
1082 pub(crate) fn maybe_trigger_backup(&self) {
1085 let tasks = self.client.inner.e2ee.tasks.lock();
1086
1087 if let Some(tasks) = tasks.upload_room_keys.as_ref() {
1088 tasks.trigger_upload();
1089 }
1090 }
1091
1092 async fn handle_deleted_backup_version(&self, olm_machine: &OlmMachine) -> Result<(), Error> {
1095 olm_machine.backup_machine().disable_backup().await?;
1096 self.set_state(BackupState::Unknown);
1097
1098 Ok(())
1099 }
1100}
1101
1102#[derive(Debug, thiserror::Error)]
1104pub enum EnableBackupError {
1105 #[error("The backup decryption key does not match the latest backup version")]
1108 InconsistentBackupDecryptionKey,
1109
1110 #[error(transparent)]
1112 Error(Error),
1113}
1114
1115impl<T: Into<Error>> From<T> for EnableBackupError {
1116 fn from(value: T) -> Self {
1117 Self::Error(value.into())
1118 }
1119}
1120
1121#[cfg(all(test, not(target_family = "wasm")))]
1122mod test {
1123 use std::time::Duration;
1124
1125 use assert_matches2::assert_matches;
1126 use matrix_sdk_base::crypto::{
1127 GossipRequest, GossippedSecret, SecretInfo,
1128 store::types::Changes,
1129 types::events::{
1130 olm_v1::{DecryptedSecretSendEvent, OlmV1Keys},
1131 secret_send::SecretSendContent,
1132 },
1133 };
1134 use matrix_sdk_test::async_test;
1135 #[cfg(feature = "experimental-push-secrets")]
1136 use ruma::{device_id, user_id};
1137 use serde_json::json;
1138 use vodozemac::Curve25519PublicKey;
1139 use wiremock::{
1140 Mock, MockServer, ResponseTemplate,
1141 matchers::{header, method, path},
1142 };
1143
1144 use super::*;
1145 use crate::test_utils::{logged_in_client, mocks::MatrixMockServer};
1146
1147 fn room_key() -> ExportedRoomKey {
1148 let json = json!({
1149 "algorithm": "m.megolm.v1.aes-sha2",
1150 "room_id": "!DovneieKSTkdHKpIXy:morpheus.localhost",
1151 "sender_key": "DeHIg4gwhClxzFYcmNntPNF9YtsdZbmMy8+3kzCMXHA",
1152 "session_id": "gM8i47Xhu0q52xLfgUXzanCMpLinoyVyH7R58cBuVBU",
1153 "session_key": "AQAAAABvWMNZjKFtebYIePKieQguozuoLgzeY6wKcyJjLJcJtQgy1dPqTBD12U+XrYLrRHn\
1154 lKmxoozlhFqJl456+9hlHCL+yq+6ScFuBHtJepnY1l2bdLb4T0JMDkNsNErkiLiLnD6yp3J\
1155 DSjIhkdHxmup/huygrmroq6/L5TaThEoqvW4DPIuO14btKudsS34FF82pwjKS4p6Mlch+0e\
1156 fHAblQV",
1157 "sender_claimed_keys":{},
1158 "forwarding_curve25519_key_chain":[]
1159 });
1160
1161 serde_json::from_value(json)
1162 .expect("We should be able to deserialize our exported room key")
1163 }
1164
1165 async fn backup_disabling_test_body(
1166 client: &Client,
1167 server: &MockServer,
1168 put_response: ResponseTemplate,
1169 ) {
1170 let _post_scope = Mock::given(method("POST"))
1171 .and(path("_matrix/client/unstable/room_keys/version"))
1172 .and(header("authorization", "Bearer 1234"))
1173 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1174 "version": "1"
1175 })))
1176 .expect(1)
1177 .named("POST for the backup creation")
1178 .mount_as_scoped(server)
1179 .await;
1180
1181 let _put_scope = Mock::given(method("PUT"))
1182 .and(path("_matrix/client/unstable/room_keys/keys"))
1183 .and(header("authorization", "Bearer 1234"))
1184 .respond_with(put_response)
1185 .expect(1)
1186 .named("POST for the backup creation")
1187 .mount_as_scoped(server)
1188 .await;
1189
1190 client
1191 .encryption()
1192 .backups()
1193 .create()
1194 .await
1195 .expect("We should be able to create a new backup");
1196
1197 assert_eq!(client.encryption().backups().state(), BackupState::Enabled);
1198
1199 client
1200 .encryption()
1201 .backups()
1202 .backup_room_keys()
1203 .await
1204 .expect_err("Backups should be disabled");
1205
1206 assert_eq!(client.encryption().backups().state(), BackupState::Unknown);
1207 }
1208
1209 #[async_test]
1210 async fn test_resuming_backups_when_keys_are_consistent_makes_backups_enabled() {
1211 let server = MatrixMockServer::new().await;
1212 let client = server.client_builder().build().await;
1213 let backups = client.encryption().backups();
1214 let backup_decryption_key = BackupDecryptionKey::new();
1215
1216 let matching_public_key = derive_public_key_from(&backup_decryption_key);
1217
1218 server
1219 .mock_room_keys_version()
1220 .exists_with_key(&matching_public_key.to_base64())
1221 .expect(1)
1222 .mount()
1223 .await;
1224
1225 queue_backup_decryption_key_secret(client, &backup_decryption_key.to_base64()).await;
1228
1229 let res = backups.maybe_resume_backups().await;
1231
1232 assert_matches!(res, Ok(_));
1234
1235 assert_eq!(backups.state(), BackupState::Enabled);
1237 }
1238
1239 #[async_test]
1240 async fn test_resuming_backups_when_keys_are_inconsistent_has_no_effect() {
1241 let server = MatrixMockServer::new().await;
1249 let client = server.client_builder().build().await;
1250 let backups = client.encryption().backups();
1251 let backup_decryption_key = BackupDecryptionKey::new();
1252
1253 let non_matching_public_key = derive_public_key_from(&BackupDecryptionKey::new());
1254
1255 server
1256 .mock_room_keys_version()
1257 .exists_with_key(&non_matching_public_key.to_base64())
1258 .expect(1)
1259 .mount()
1260 .await;
1261
1262 queue_backup_decryption_key_secret(client, &backup_decryption_key.to_base64()).await;
1265
1266 let res = backups.maybe_resume_backups().await;
1268
1269 assert_matches!(res, Ok(_));
1271
1272 assert_eq!(backups.state(), BackupState::Unknown);
1275 }
1276
1277 #[async_test]
1278 async fn test_errors_when_resuming_backups_are_propagated() {
1279 let server = MatrixMockServer::new().await;
1280 let client = server.client_builder().build().await;
1281 let backups = client.encryption().backups();
1282
1283 queue_backup_decryption_key_secret(client, "not valid base64").await;
1285
1286 let res = backups.maybe_resume_backups().await;
1288
1289 assert_matches!(res, Err(Error::SerdeJson(_)));
1291
1292 assert_eq!(backups.state(), BackupState::Unknown);
1294 }
1295
1296 #[async_test]
1297 async fn test_backup_disabling_after_remote_deletion() {
1298 let server = MockServer::start().await;
1299 let client = logged_in_client(Some(server.uri())).await;
1300
1301 {
1302 let machine = client.olm_machine().await;
1303 machine
1304 .as_ref()
1305 .unwrap()
1306 .store()
1307 .import_exported_room_keys(vec![room_key()], |_, _| {})
1308 .await
1309 .expect("We should be able to import a room key");
1310 }
1311
1312 backup_disabling_test_body(
1313 &client,
1314 &server,
1315 ResponseTemplate::new(404).set_body_json(json!({
1316 "errcode": "M_NOT_FOUND",
1317 "error": "Unknown backup version"
1318 })),
1319 )
1320 .await;
1321
1322 backup_disabling_test_body(
1323 &client,
1324 &server,
1325 ResponseTemplate::new(403).set_body_json(json!({
1326 "current_version": "42",
1327 "errcode": "M_WRONG_ROOM_KEYS_VERSION",
1328 "error": "Wrong backup version."
1329 })),
1330 )
1331 .await;
1332
1333 server.verify().await;
1334 }
1335
1336 #[async_test]
1337 async fn test_when_a_backup_exists_then_fetch_exists_on_server_returns_true() {
1338 let server = MatrixMockServer::new().await;
1339 let client = server.client_builder().build().await;
1340
1341 server.mock_room_keys_version().exists().expect(1).mount().await;
1342
1343 let exists = client
1344 .encryption()
1345 .backups()
1346 .fetch_exists_on_server()
1347 .await
1348 .expect("We should be able to check if backups exist on the server");
1349
1350 assert!(exists, "We should deduce that a backup exists on the server");
1351 }
1352
1353 #[async_test]
1354 async fn test_repeated_calls_to_fetch_exists_on_server_makes_repeated_requests() {
1355 let server = MatrixMockServer::new().await;
1356 let client = server.client_builder().build().await;
1357
1358 server.mock_room_keys_version().exists().expect(2).mount().await;
1360
1361 let backups = client.encryption().backups();
1362
1363 backups.fetch_exists_on_server().await.unwrap();
1365 let exists = backups.fetch_exists_on_server().await.unwrap();
1366
1367 assert!(exists, "We should deduce that a backup exists on the server");
1368 }
1369
1370 #[async_test]
1371 async fn test_when_no_backup_exists_then_fetch_exists_on_server_returns_false() {
1372 let server = MatrixMockServer::new().await;
1373 let client = server.client_builder().build().await;
1374
1375 server.mock_room_keys_version().none().expect(1).mount().await;
1376
1377 let exists = client
1378 .encryption()
1379 .backups()
1380 .fetch_exists_on_server()
1381 .await
1382 .expect("We should be able to check if backups exist on the server");
1383
1384 assert!(!exists, "We should deduce that no backup exists on the server");
1385 }
1386
1387 #[async_test]
1388 async fn test_when_server_returns_an_error_then_fetch_exists_on_server_returns_an_error() {
1389 let server = MatrixMockServer::new().await;
1390 let client = server.client_builder().build().await;
1391
1392 {
1393 let _scope =
1394 server.mock_room_keys_version().error429().expect(1).mount_as_scoped().await;
1395
1396 client.encryption().backups().fetch_exists_on_server().await.expect_err(
1397 "If the /version endpoint returns a non 404 error we should throw an error",
1398 );
1399 }
1400
1401 {
1402 let _scope =
1403 server.mock_room_keys_version().error404().expect(1).mount_as_scoped().await;
1404
1405 client.encryption().backups().fetch_exists_on_server().await.expect_err(
1406 "If the /version endpoint returns a non-Matrix 404 error we should throw an error",
1407 );
1408 }
1409 }
1410
1411 #[async_test]
1412 async fn test_when_a_backup_exists_then_exists_on_server_returns_true() {
1413 let server = MatrixMockServer::new().await;
1414 let client = server.client_builder().build().await;
1415
1416 server.mock_room_keys_version().exists().expect(1).mount().await;
1417
1418 let exists = client
1419 .encryption()
1420 .backups()
1421 .exists_on_server()
1422 .await
1423 .expect("We should be able to check if backups exist on the server");
1424
1425 assert!(exists, "We should deduce that a backup exists on the server");
1426 }
1427
1428 #[async_test]
1429 async fn test_when_no_backup_exists_then_exists_on_server_returns_false() {
1430 let server = MatrixMockServer::new().await;
1431 let client = server.client_builder().build().await;
1432
1433 server.mock_room_keys_version().none().expect(1).mount().await;
1434
1435 let exists = client
1436 .encryption()
1437 .backups()
1438 .exists_on_server()
1439 .await
1440 .expect("We should be able to check if backups exist on the server");
1441
1442 assert!(!exists, "We should deduce that no backup exists on the server");
1443 }
1444
1445 #[async_test]
1446 async fn test_when_server_returns_an_error_then_exists_on_server_returns_an_error() {
1447 let server = MatrixMockServer::new().await;
1448 let client = server.client_builder().build().await;
1449
1450 {
1451 let _scope =
1452 server.mock_room_keys_version().error429().expect(1).mount_as_scoped().await;
1453
1454 client.encryption().backups().exists_on_server().await.expect_err(
1455 "If the /version endpoint returns a non 404 error we should throw an error",
1456 );
1457 }
1458
1459 {
1460 let _scope =
1461 server.mock_room_keys_version().error404().expect(1).mount_as_scoped().await;
1462
1463 client.encryption().backups().exists_on_server().await.expect_err(
1464 "If the /version endpoint returns a non-Matrix 404 error we should throw an error",
1465 );
1466 }
1467 }
1468
1469 #[async_test]
1470 async fn test_repeated_calls_to_exists_on_server_do_not_make_additional_requests() {
1471 let server = MatrixMockServer::new().await;
1472 let client = server.client_builder().build().await;
1473
1474 server.mock_room_keys_version().exists().expect(1).mount().await;
1476
1477 let backups = client.encryption().backups();
1478
1479 backups.exists_on_server().await.unwrap();
1481 backups.exists_on_server().await.unwrap();
1482 backups.exists_on_server().await.unwrap();
1483
1484 let exists = backups
1485 .exists_on_server()
1486 .await
1487 .expect("We should be able to check if backups exist on the server");
1488
1489 assert!(exists, "We should deduce that a backup exists on the server");
1490
1491 }
1493
1494 #[async_test]
1495 async fn test_adding_a_backup_invalidates_exists_on_server_cache() {
1496 let server = MatrixMockServer::new().await;
1497 let client = server.client_builder().build().await;
1498 let backups = client.encryption().backups();
1499
1500 {
1501 let _scope = server.mock_room_keys_version().none().expect(1).mount_as_scoped().await;
1502
1503 let exists = backups.exists_on_server().await.unwrap();
1505 assert!(!exists, "No backup exists at this point");
1506 }
1507
1508 server.mock_add_room_keys_version().ok().expect(1).mount().await;
1510 backups.create().await.expect("Failed to create a backup");
1511
1512 server.mock_room_keys_version().exists().expect(1).mount().await;
1513 let exists = backups
1514 .exists_on_server()
1515 .await
1516 .expect("We should be able to check if backups exist on the server");
1517
1518 assert!(exists, "But now a backup does exist");
1519 }
1520
1521 #[async_test]
1522 async fn test_removing_a_backup_invalidates_exists_on_server_cache() {
1523 let server = MatrixMockServer::new().await;
1524 let client = server.client_builder().build().await;
1525 let backups = client.encryption().backups();
1526
1527 {
1528 let _scope = server.mock_room_keys_version().exists().expect(1).mount_as_scoped().await;
1529
1530 let exists = backups.exists_on_server().await.unwrap();
1532 assert!(exists, "A backup exists at this point");
1533 }
1534
1535 server.mock_delete_room_keys_version().ok().expect(1).mount().await;
1537 backups.delete_backup_from_server("1".to_owned()).await.expect("Failed to delete a backup");
1538
1539 server.mock_room_keys_version().none().expect(1).mount().await;
1540 let exists = backups
1541 .exists_on_server()
1542 .await
1543 .expect("We should be able to check if backups exist on the server");
1544
1545 assert!(!exists, "But now there is no backup");
1546 }
1547
1548 #[async_test]
1549 async fn test_waiting_for_steady_state_resets_the_delay() {
1550 let server = MatrixMockServer::new().await;
1551 let client = server.client_builder().build().await;
1552
1553 server.mock_add_room_keys_version().ok().expect(1).mount().await;
1554
1555 client
1556 .encryption()
1557 .backups()
1558 .create()
1559 .await
1560 .expect("We should be able to create a new backup");
1561
1562 let backups = client.encryption().backups();
1563
1564 let old_duration =
1565 { client.inner.e2ee.backup_state.upload_delay.read().unwrap().to_owned() };
1566
1567 let wait_for_steady_state =
1568 backups.wait_for_steady_state().with_delay(Duration::from_nanos(100));
1569
1570 let mut progress_stream = wait_for_steady_state.subscribe_to_progress();
1571
1572 let task = matrix_sdk_common::executor::spawn({
1573 let client = client.to_owned();
1574 async move {
1575 while let Some(state) = progress_stream.next().await {
1576 let Ok(state) = state else {
1577 panic!("Error while waiting for the upload state")
1578 };
1579
1580 match state {
1581 UploadState::Idle => (),
1582 UploadState::Done => {
1583 let current_delay = {
1584 client
1585 .inner
1586 .e2ee
1587 .backup_state
1588 .upload_delay
1589 .read()
1590 .unwrap()
1591 .to_owned()
1592 };
1593
1594 assert_ne!(current_delay, old_duration);
1595 break;
1596 }
1597 _ => panic!("We should not have entered any other state"),
1598 }
1599 }
1600 }
1601 });
1602
1603 wait_for_steady_state.await.expect("We should be able to wait for the steady state");
1604 task.await.unwrap();
1605
1606 let current_duration =
1607 { client.inner.e2ee.backup_state.upload_delay.read().unwrap().to_owned() };
1608
1609 assert_eq!(old_duration, current_duration);
1610 }
1611
1612 fn derive_public_key_from(backup_decryption_key: &BackupDecryptionKey) -> Curve25519PublicKey {
1615 let backup_info = backup_decryption_key.to_backup_info();
1616 match backup_info {
1617 RoomKeyBackupInfo::MegolmBackupV1Curve25519AesSha2(megolm_v1_auth_data) => {
1618 megolm_v1_auth_data.public_key
1619 }
1620 RoomKeyBackupInfo::Other { .. } => {
1621 panic!("Unexpected backup info type")
1622 }
1623 }
1624 }
1625
1626 async fn queue_backup_decryption_key_secret(
1629 client: Client,
1630 secret_backup_decryption_key: &str,
1631 ) {
1632 let _guard = client.olm_machine().await;
1633 let machine = _guard.as_ref().unwrap();
1634 let transaction_id = TransactionId::new();
1635 let secret_info = SecretInfo::SecretRequest(SecretName::RecoveryKey);
1636 let user_id = machine.user_id().to_owned();
1637
1638 let gossip_request = GossipRequest {
1639 request_recipient: machine.user_id().to_owned(),
1640 request_id: transaction_id.clone(),
1641 info: secret_info.clone(),
1642 sent_out: true,
1643 };
1644
1645 let event = DecryptedSecretSendEvent {
1646 sender: user_id.clone(),
1647 recipient: user_id.clone(),
1648 keys: OlmV1Keys { ed25519: machine.identity_keys().ed25519 },
1649 recipient_keys: OlmV1Keys { ed25519: machine.identity_keys().ed25519 },
1650 sender_device_keys: None,
1651 content: SecretSendContent::new(
1652 transaction_id.to_owned(),
1653 secret_backup_decryption_key.to_owned(),
1654 ),
1655 };
1656
1657 let gossipped_secret =
1658 GossippedSecret { secret_name: SecretName::RecoveryKey, gossip_request, event };
1659
1660 let changes = Changes { secrets: vec![gossipped_secret.into()], ..Default::default() };
1661
1662 machine
1663 .store()
1664 .save_changes(changes)
1665 .await
1666 .expect("We should be able to import a room key");
1667 }
1668
1669 #[async_test]
1670 #[cfg(feature = "experimental-push-secrets")]
1671 async fn test_push_secret_on_create() {
1672 let server = MatrixMockServer::new().await;
1673 server.mock_add_room_keys_version().ok().mount().await;
1674 server.mock_crypto_endpoints_preset().await;
1675
1676 let client = server
1678 .client_builder_for_crypto_end_to_end(
1679 user_id!("@example:localhost"),
1680 device_id!("DEVICEID"),
1681 )
1682 .build()
1683 .await;
1684 let _other_client = server
1685 .set_up_new_device_for_encryption(&client, device_id!("OTHERDEVICEID"), vec![])
1686 .await;
1687
1688 client.encryption().bootstrap_cross_signing(None).await.unwrap();
1690 let other_device = client
1691 .encryption()
1692 .get_device(user_id!("@example:localhost"), device_id!("OTHERDEVICEID"))
1693 .await
1694 .unwrap()
1695 .unwrap();
1696 other_device.verify().await.unwrap();
1697 client.encryption().request_user_identity(user_id!("@example:localhost")).await.unwrap();
1698
1699 client
1701 .encryption()
1702 .backups()
1703 .create()
1704 .await
1705 .expect("We should be able to create a new backup");
1706
1707 let (_guard, to_device) =
1710 server.mock_capture_put_to_device(client.user_id().unwrap()).await;
1711 client.send_outgoing_requests().await.unwrap();
1712 to_device.await;
1713 }
1714}