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