matrix_sdk/encryption/recovery/mod.rs
1// Copyright 2023 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! The recovery module
16//!
17//! The recovery module attempts to provide a unified and simplified view over
18//! the secret storage and backup subsystems.
19//!
20//! **Note**: If you are using this module, do not use the [`SecretStorage`] and
21//! [`Backups`] subsystems directly. This module makes assumptions that might be
22//! broken by the direct usage of the respective lower level modules.
23//!
24//! **Note**: The term Recovery used in this submodule is not the same as the
25//! [`Recovery key`] mentioned in the spec. The recovery key from the spec is
26//! solely about backups, while the term recovery in this file includes both the
27//! backups and the secret storage subsystems. The recovery key mentioned in
28//! this file is the secret storage key.
29//!
30//! You should configure your client to bootstrap cross-signing automatically
31//! and may choose to let your client automatically create a backup, if it
32//! doesn't exist, as well:
33//!
34//! ```no_run
35//! use matrix_sdk::{Client, encryption::EncryptionSettings};
36//!
37//! # async {
38//! # let homeserver = "http://example.org";
39//! let client = Client::builder()
40//! .homeserver_url(homeserver)
41//! .with_encryption_settings(EncryptionSettings {
42//! auto_enable_cross_signing: true,
43//! auto_enable_backups: true,
44//! ..Default::default()
45//! })
46//! .build()
47//! .await?;
48//! # anyhow::Ok(()) };
49//! ```
50//!
51//! # Examples
52//!
53//! For a newly registered user you will want to enable recovery, either
54//! immediately or before the user logs out.
55//!
56//! ```no_run
57//! # use matrix_sdk::{Client, encryption::recovery::EnableProgress};
58//! # use url::Url;
59//! # async {
60//! # let homeserver = Url::parse("http://example.com")?;
61//! # let client = Client::new(homeserver).await?;
62//! let recovery = client.encryption().recovery();
63//!
64//! // Create a new recovery key, you can use the provided passphrase, or the returned recovery key
65//! // to recover.
66//! let recovery_key = recovery
67//! .enable()
68//! .wait_for_backups_to_upload()
69//! .with_passphrase("my passphrase")
70//! .await;
71//! # anyhow::Ok(()) };
72//! ```
73//!
74//! If the user logs in with another device, you'll want to let the user recover
75//! its secrets by entering the recovery key or recovery passphrase.
76//!
77//! ```no_run
78//! # use matrix_sdk::{Client, encryption::recovery::EnableProgress};
79//! # use url::Url;
80//! # async {
81//! # let homeserver = Url::parse("http://example.com")?;
82//! # let client = Client::new(homeserver).await?;
83//! let recovery = client.encryption().recovery();
84//!
85//! // Create a new recovery key, you can use the provided passphrase, or the returned recovery key
86//! // to recover.
87//! recovery.recover("my recovery key or passphrase").await;
88//! # anyhow::Ok(()) };
89//! ```
90//!
91//! [`Recovery key`]: https://spec.matrix.org/v1.8/client-server-api/#recovery-key
92
93use futures_core::{Future, Stream};
94use futures_util::StreamExt as _;
95use ruma::{
96 api::client::keys::get_keys,
97 events::{
98 GlobalAccountDataEventType,
99 secret::{request::SecretName, send::ToDeviceSecretSendEvent},
100 secret_storage::{default_key::SecretStorageDefaultKeyEvent, secret::SecretEventContent},
101 },
102 serde::Raw,
103};
104use serde_json::{json, value::to_raw_value};
105use tracing::{error, info, instrument, warn};
106
107#[cfg(doc)]
108use crate::encryption::{
109 backups::Backups,
110 secret_storage::{SecretStorage, SecretStore},
111};
112use crate::{
113 Client,
114 encryption::{backups::BackupState, secret_storage::SecretStorageError},
115};
116
117pub mod futures;
118mod types;
119pub use self::types::{EnableProgress, RecoveryError, RecoveryState, Result};
120use self::{
121 futures::{Enable, RecoverAndReset, Reset},
122 types::{BackupDisabledContent, KeyBackupContent, SecretStorageDisabledContent},
123};
124use crate::encryption::{AuthData, CrossSigningResetAuthType, CrossSigningResetHandle};
125
126/// The recovery manager for the [`Client`].
127#[derive(Debug)]
128pub struct Recovery {
129 pub(super) client: Client,
130}
131
132impl Recovery {
133 /// The list of known secrets that are contained in secret storage once
134 /// recover is enabled.
135 pub const KNOWN_SECRETS: &[SecretName] = &[
136 SecretName::CrossSigningMasterKey,
137 SecretName::CrossSigningUserSigningKey,
138 SecretName::CrossSigningSelfSigningKey,
139 SecretName::RecoveryKey,
140 ];
141
142 /// Get the current [`RecoveryState`] for this [`Client`].
143 pub fn state(&self) -> RecoveryState {
144 self.client.inner.e2ee.recovery_state.get()
145 }
146
147 /// Get a stream of updates to the [`RecoveryState`].
148 ///
149 /// This method will send out the current state as the first update.
150 ///
151 /// # Examples
152 ///
153 /// ```no_run
154 /// # use matrix_sdk::{Client, encryption::recovery::RecoveryState};
155 /// # use url::Url;
156 /// # async {
157 /// # let homeserver = Url::parse("http://example.com")?;
158 /// # let client = Client::new(homeserver).await?;
159 /// use futures_util::StreamExt;
160 ///
161 /// let recovery = client.encryption().recovery();
162 ///
163 /// let mut state_stream = recovery.state_stream();
164 ///
165 /// while let Some(update) = state_stream.next().await {
166 /// match update {
167 /// RecoveryState::Enabled => {
168 /// println!("Recovery has been enabled");
169 /// }
170 /// _ => (),
171 /// }
172 /// }
173 /// # anyhow::Ok(()) };
174 /// ```
175 pub fn state_stream(&self) -> impl Stream<Item = RecoveryState> + use<> {
176 self.client.inner.e2ee.recovery_state.subscribe_reset()
177 }
178
179 /// Enable secret storage _and_ backups.
180 ///
181 /// This method will create a new secret storage key and a new backup if one
182 /// doesn't already exist. It will then upload all the locally cached
183 /// secrets, including the backup recovery key, to the new secret store.
184 ///
185 /// This method will throw an error if a backup already exists on the
186 /// homeserver but this [`Client`] isn't connected to the existing backup.
187 ///
188 /// # Examples
189 ///
190 /// ```no_run
191 /// # use matrix_sdk::{Client, encryption::recovery::EnableProgress};
192 /// # use url::Url;
193 /// # async {
194 /// # let homeserver = Url::parse("http://example.com")?;
195 /// # let client = Client::new(homeserver).await?;
196 /// use futures_util::StreamExt;
197 ///
198 /// let recovery = client.encryption().recovery();
199 ///
200 /// let enable = recovery
201 /// .enable()
202 /// .wait_for_backups_to_upload()
203 /// .with_passphrase("my passphrase");
204 ///
205 /// let mut progress_stream = enable.subscribe_to_progress();
206 ///
207 /// tokio::spawn(async move {
208 /// while let Some(update) = progress_stream.next().await {
209 /// let Ok(update) = update else {
210 /// panic!("Update to the enable progress lagged")
211 /// };
212 ///
213 /// match update {
214 /// EnableProgress::CreatingBackup => {
215 /// println!("Creating a new backup");
216 /// }
217 /// EnableProgress::CreatingRecoveryKey => {
218 /// println!("Creating a new recovery key");
219 /// }
220 /// EnableProgress::Done { .. } => {
221 /// println!("Recovery has been enabled");
222 /// break;
223 /// }
224 /// _ => (),
225 /// }
226 /// }
227 /// });
228 ///
229 /// let recovery_key = enable.await?;
230 ///
231 /// # anyhow::Ok(()) };
232 /// ```
233 #[instrument(skip_all)]
234 pub fn enable(&self) -> Enable<'_> {
235 Enable::new(self)
236 }
237
238 /// Create a new backup if one does not exist yet.
239 ///
240 /// This method will throw an error if a backup already exists on the
241 /// homeserver but this [`Client`] isn't connected to the existing backup.
242 ///
243 /// # Examples
244 ///
245 /// ```no_run
246 /// # use matrix_sdk::{Client, encryption::backups::BackupState};
247 /// # use url::Url;
248 /// # async {
249 /// # let homeserver = Url::parse("http://example.com")?;
250 /// # let client = Client::new(homeserver).await?;
251 /// let recovery = client.encryption().recovery();
252 ///
253 /// recovery.enable_backup().await?;
254 ///
255 /// assert_eq!(client.encryption().backups().state(), BackupState::Enabled);
256 ///
257 /// # anyhow::Ok(()) };
258 /// ```
259 #[instrument(skip_all)]
260 pub async fn enable_backup(&self) -> Result<()> {
261 if !self.client.encryption().backups().fetch_exists_on_server().await? {
262 self.mark_backup_as_enabled().await?;
263
264 self.client.encryption().backups().create().await?;
265 self.client.encryption().backups().maybe_trigger_backup();
266
267 Ok(())
268 } else {
269 Err(RecoveryError::BackupExistsOnServer)
270 }
271 }
272
273 /// Disable recovery completely.
274 ///
275 /// This method will do the following steps:
276 ///
277 /// 1. Disable the uploading of room keys to a currently active backup.
278 /// 2. Delete the currently active backup.
279 /// 3. Set the `m.secret_storage.default_key` global account data event to
280 /// an empty JSON content.
281 /// 4. Set a global account data event so clients won't attempt to
282 /// automatically re-enable a backup.
283 ///
284 /// # Examples
285 ///
286 /// ```no_run
287 /// # use matrix_sdk::{Client, encryption::recovery::RecoveryState};
288 /// # use url::Url;
289 /// # async {
290 /// # let homeserver = Url::parse("http://example.com")?;
291 /// # let client = Client::new(homeserver).await?;
292 /// let recovery = client.encryption().recovery();
293 ///
294 /// recovery.disable().await?;
295 ///
296 /// assert_eq!(recovery.state(), RecoveryState::Disabled);
297 ///
298 /// # anyhow::Ok(()) };
299 /// ```
300 #[instrument(skip_all)]
301 pub async fn disable(&self) -> Result<()> {
302 self.client.encryption().backups().disable().await?;
303
304 // Why oh why, can't we delete account data events?
305 //
306 // Alright, let's attempt to "delete" the content of our current default
307 // key, for this we first need to check if there is a default key, then
308 // deserialize the content and find out the key ID.
309 //
310 // Then we finally set the event to an empty JSON content.
311 if let Ok(Some(default_event)) =
312 self.client.encryption().secret_storage().fetch_default_key_id().await
313 && let Ok(default_event) = default_event.deserialize()
314 {
315 let key_id = default_event.key_id;
316 let event_type = GlobalAccountDataEventType::SecretStorageKey(key_id);
317
318 self.client
319 .account()
320 .set_account_data_raw(event_type, Raw::new(&json!({})).expect("").cast_unchecked())
321 .await?;
322 }
323
324 // Now let's "delete" the actual `m.secret.storage.default_key` event.
325 self.client.account().set_account_data(SecretStorageDisabledContent {}).await?;
326 // Make sure that we don't re-enable backups automatically.
327 self.client.account().set_account_data(KeyBackupContent { enabled: false }).await?;
328 // (Unstable prefix version of KeyBackupContent)
329 self.client.account().set_account_data(BackupDisabledContent { disabled: true }).await?;
330 // Finally, "delete" all the known secrets we have in the account data.
331 self.delete_all_known_secrets().await?;
332
333 self.update_recovery_state().await?;
334
335 Ok(())
336 }
337
338 /// Reset the recovery key.
339 ///
340 /// This will rotate the secret storage key and re-upload all the secrets to
341 /// the [`SecretStore`].
342 ///
343 /// # Examples
344 ///
345 /// ```no_run
346 /// # use matrix_sdk::{Client, encryption::recovery::RecoveryState};
347 /// # use url::Url;
348 /// # async {
349 /// # let homeserver = Url::parse("http://example.com")?;
350 /// # let client = Client::new(homeserver).await?;
351 /// let recovery = client.encryption().recovery();
352 ///
353 /// let new_recovery_key =
354 /// recovery.reset_key().with_passphrase("my passphrase").await;
355 /// # anyhow::Ok(()) };
356 /// ```
357 #[instrument(skip_all)]
358 pub fn reset_key(&self) -> Reset<'_> {
359 // TODO: Should this only be possible if we're in the
360 // RecoveryState::Enabled state? Otherwise we'll create a new secret
361 // store but won't be able to upload all the secrets.
362 Reset::new(self)
363 }
364
365 /// Reset the recovery key but first import all the secrets from secret
366 /// storage.
367 ///
368 /// # Examples
369 ///
370 /// ```no_run
371 /// # use matrix_sdk::{Client, encryption::recovery::RecoveryState};
372 /// # use url::Url;
373 /// # async {
374 /// # let homeserver = Url::parse("http://example.com")?;
375 /// # let client = Client::new(homeserver).await?;
376 /// let recovery = client.encryption().recovery();
377 ///
378 /// let new_recovery_key = recovery
379 /// .recover_and_reset("my old passphrase or key")
380 /// .with_passphrase("my new passphrase")
381 /// .await?;
382 /// # anyhow::Ok(()) };
383 /// ```
384 #[instrument(skip_all)]
385 pub fn recover_and_reset<'a>(&'a self, old_key: &'a str) -> RecoverAndReset<'a> {
386 RecoverAndReset::new(self, old_key)
387 }
388
389 /// Completely reset the current user's crypto identity. This method will go
390 /// through the following steps:
391 ///
392 /// 1. Disable backing up room keys and delete the active backup
393 /// 2. Disable recovery and delete secret storage
394 /// 3. Go through the cross-signing key reset flow
395 /// 4. Finally, re-enable key backups (only if they were already enabled)
396 ///
397 /// Disclaimer: failures in this flow will potentially leave the user in an
398 /// inconsistent state but they're expected to just run the reset flow again
399 /// as presumably the reason they started it to begin with was that they no
400 /// longer had access to any of their data.
401 ///
402 /// # Examples
403 ///
404 /// ```no_run
405 /// # use matrix_sdk::{
406 /// encryption::recovery, encryption::CrossSigningResetAuthType, Client,
407 /// };
408 /// # use url::Url;
409 /// # async {
410 /// # let homeserver = Url::parse("http://example.com")?;
411 /// # let client = Client::new(homeserver).await?;
412 /// # let user_id = unimplemented!();
413 /// let encryption = client.encryption();
414 ///
415 /// if let Some(handle) = encryption.recovery().reset_identity().await? {
416 /// match handle.auth_type() {
417 /// CrossSigningResetAuthType::Uiaa(uiaa) => {
418 /// let password = "1234".to_owned();
419 ///
420 /// handle.reset(uiaa.as_password_auth_data(user_id, password)).await?;
421 /// }
422 /// CrossSigningResetAuthType::OAuth(oauth) => {
423 /// println!(
424 /// "To reset your end-to-end encryption cross-signing identity, \
425 /// you first need to approve it at {}",
426 /// oauth.approval_url
427 /// );
428 /// handle.reset(oauth.as_auth_data()).await?;
429 /// }
430 /// }
431 /// }
432 /// # anyhow::Ok(()) };
433 /// ```
434 pub async fn reset_identity(&self) -> Result<Option<IdentityResetHandle>> {
435 self.client.encryption().backups().disable_and_delete().await?; // 1.
436
437 // 2. (We can't delete account data events)
438 self.client.account().set_account_data(SecretStorageDisabledContent {}).await?;
439 self.client.encryption().recovery().update_recovery_state().await?;
440
441 let cross_signing_reset_handle = self.client.encryption().reset_cross_signing().await?;
442
443 if let Some(handle) = cross_signing_reset_handle {
444 // Authentication required, backups will be re-enabled after the
445 // reset
446 Ok(Some(IdentityResetHandle {
447 client: self.client.clone(),
448 cross_signing_reset_handle: handle,
449 }))
450 } else {
451 // No authentication required, re-enable backups
452 if self.client.encryption().recovery().should_auto_enable_backups().await? {
453 self.client.encryption().recovery().enable_backup().await?; // 4.
454 }
455
456 Ok(None)
457 }
458 }
459
460 /// Recover all the secrets from the homeserver.
461 ///
462 /// This method is a convenience method around the
463 /// [`SecretStore::import_secrets()`] method, please read the documentation
464 /// of this method for more information about what happens if you call this
465 /// method.
466 ///
467 /// In short, this method will turn a newly created [`Client`] into a fully
468 /// end-to-end encryption enabled client.
469 ///
470 /// # Examples
471 ///
472 /// ```no_run
473 /// # use matrix_sdk::{Client, encryption::recovery::RecoveryState};
474 /// # use url::Url;
475 /// # async {
476 /// # let homeserver = Url::parse("http://example.com")?;
477 /// # let client = Client::new(homeserver).await?;
478 /// let recovery = client.encryption().recovery();
479 ///
480 /// recovery.recover("my recovery key or passphrase").await;
481 ///
482 /// assert_eq!(recovery.state(), RecoveryState::Enabled);
483 /// # anyhow::Ok(()) };
484 /// ```
485 #[instrument(skip_all)]
486 pub async fn recover(&self, recovery_key: &str) -> Result<()> {
487 let store =
488 self.client.encryption().secret_storage().open_secret_store(recovery_key).await?;
489
490 store.import_secrets().await?;
491 self.update_recovery_state().await?;
492
493 Ok(())
494 }
495
496 /// Recover all the secrets from the homeserver, and, if the key backup
497 /// information is inconsistent, create a new key backup.
498 ///
499 /// Please read the documentation for [`SecretStore::import_secrets()`] for
500 /// more information about the recovery of identity information.
501 ///
502 /// This will create a new key backup if:
503 ///
504 /// - Key backup is enabled and the backup decryption key is missing from
505 /// Recovery, or
506 /// - Key backup is enabled and the backup decryption key does not match the
507 /// public key
508 ///
509 /// # Examples
510 ///
511 /// ```no_run
512 /// # use matrix_sdk::{Client, encryption::recovery::RecoveryState};
513 /// # use url::Url;
514 /// # async {
515 /// # let homeserver = Url::parse("http://example.com")?;
516 /// # let client = Client::new(homeserver).await?;
517 /// let recovery = client.encryption().recovery();
518 ///
519 /// recovery.recover_and_fix_backup("my recovery key or passphrase").await;
520 ///
521 /// assert_eq!(recovery.state(), RecoveryState::Enabled);
522 /// # anyhow::Ok(()) };
523 /// ```
524 #[instrument(skip_all)]
525 pub async fn recover_and_fix_backup(&self, recovery_key: &str) -> Result<()> {
526 let store =
527 self.client.encryption().secret_storage().open_secret_store(recovery_key).await?;
528
529 let delete_and_recreate_backup = match store.import_secrets().await {
530 Ok(()) => false,
531 Err(SecretStorageError::InconsistentBackupDecryptionKey) => {
532 warn!(
533 "Key storage decryption key does not match the current backup - creating a new key backup"
534 );
535 true
536 }
537 Err(SecretStorageError::MissingOrInvalidBackupDecryptionKey) => {
538 warn!("Missing or invalid backup decryption key - creating a new key backup");
539 true
540 }
541 Err(e) => return Err(e.into()),
542 };
543
544 if delete_and_recreate_backup {
545 self.client.encryption().backups().disable_and_delete().await?;
546 self.enable_backup().await?;
547 store.export_secrets().await?;
548 }
549
550 self.update_recovery_state().await?;
551
552 Ok(())
553 }
554
555 /// Is this device the last device the user has?
556 ///
557 /// This method is useful to check if we should recommend to the user that
558 /// they should enable recovery, typically done before logging out.
559 ///
560 /// If the user does not enable recovery before logging out of their last
561 /// device, they will not be able to decrypt historic messages once they
562 /// create a new device.
563 pub async fn is_last_device(&self) -> Result<bool> {
564 let olm_machine = self.client.olm_machine().await;
565 let olm_machine = olm_machine.as_ref().ok_or(crate::Error::NoOlmMachine)?;
566 let user_id = olm_machine.user_id();
567
568 self.client.encryption().ensure_initial_key_query().await?;
569
570 let devices = self.client.encryption().get_user_devices(user_id).await?;
571
572 Ok(devices.devices().count() == 1)
573 }
574
575 /// Did we correctly set up cross-signing and backups?
576 async fn all_known_secrets_available(&self) -> Result<bool> {
577 // Cross-signing state is fine if we have all the private cross-signing
578 // keys, as indicated in the status.
579 let cross_signing_complete = self
580 .client
581 .encryption()
582 .cross_signing_status()
583 .await
584 .map(|status| status.is_complete());
585 if !cross_signing_complete.unwrap_or_default() {
586 return Ok(false);
587 }
588
589 // The backup state is fine if we have backups enabled locally, or if
590 // backups have been marked as disabled.
591 if self.client.encryption().backups().are_enabled().await {
592 Ok(true)
593 } else {
594 self.are_backups_marked_as_disabled().await
595 }
596 }
597
598 async fn should_auto_enable_backups(&self) -> Result<bool> {
599 // If we didn't already enable backups, we don't see a backup version on
600 // the server, and finally if backups have not been marked to be
601 // explicitly disabled, then we can automatically enable them.
602 Ok(self.client.inner.e2ee.encryption_settings.auto_enable_backups
603 && !self.client.encryption().backups().are_enabled().await
604 && !self.client.encryption().backups().fetch_exists_on_server().await?
605 && !self.are_backups_marked_as_disabled().await?)
606 }
607
608 pub(crate) async fn setup(&self) -> Result<()> {
609 info!("Setting up account data listeners and trying to setup recovery");
610
611 self.client.add_event_handler(Self::default_key_event_handler);
612 self.client.add_event_handler(Self::secret_send_event_handler);
613 self.client.inner.e2ee.initialize_recovery_state_update_task(&self.client);
614
615 self.update_recovery_state().await?;
616
617 if self.should_auto_enable_backups().await? {
618 info!("Trying to automatically enable backups");
619
620 if let Err(e) = self.enable_backup().await {
621 warn!("Could not automatically enable backups: {e:?}");
622 }
623 }
624
625 Ok(())
626 }
627
628 /// Delete all the known secrets we are keeping in secret storage.
629 ///
630 /// The exact list of secrets is defined in [`Recovery::KNOWN_SECRETS`] and
631 /// might change over time.
632 ///
633 /// Since account data events can't actually be deleted, due to a missing
634 /// DELETE API, we're replacing the events with an empty
635 /// [`SecretEventContent`].
636 async fn delete_all_known_secrets(&self) -> Result<()> {
637 for secret_name in Self::KNOWN_SECRETS {
638 let event_type = GlobalAccountDataEventType::from(secret_name.to_owned());
639 let content = SecretEventContent::new(Default::default());
640 let secret_content = Raw::from_json(
641 to_raw_value(&content)
642 .expect("We should be able to serialize a raw empty secret event content"),
643 );
644 self.client.account().set_account_data_raw(event_type, secret_content).await?;
645 }
646
647 Ok(())
648 }
649
650 /// Run a network request to figure whether backups have been disabled at
651 /// the account level.
652 async fn are_backups_marked_as_disabled(&self) -> Result<bool> {
653 if let Some(key_backup_content) =
654 self.client.account().fetch_account_data_static::<KeyBackupContent>().await?
655 {
656 Ok(key_backup_content.deserialize().map(|event| !event.enabled).unwrap_or(false))
657 } else {
658 Ok(self
659 .client
660 .account()
661 .fetch_account_data_static::<BackupDisabledContent>()
662 .await?
663 .map(|event| event.deserialize().map(|event| event.disabled).unwrap_or(false))
664 .unwrap_or(false))
665 }
666 }
667
668 async fn mark_backup_as_enabled(&self) -> Result<()> {
669 self.client.account().set_account_data(KeyBackupContent { enabled: true }).await?;
670
671 // Unstable prefix: will be removed when sufficient time has passed for
672 // clients to use the stable prefix.
673 self.client.account().set_account_data(BackupDisabledContent { disabled: false }).await?;
674
675 Ok(())
676 }
677
678 async fn check_recovery_state(&self) -> Result<RecoveryState> {
679 Ok(if self.client.encryption().secret_storage().is_enabled().await? {
680 if self.all_known_secrets_available().await? {
681 RecoveryState::Enabled
682 } else {
683 RecoveryState::Incomplete
684 }
685 } else {
686 RecoveryState::Disabled
687 })
688 }
689
690 async fn update_recovery_state(&self) -> Result<()> {
691 let new_state = self.check_recovery_state().await?;
692 let old_state = self.client.inner.e2ee.recovery_state.set(new_state);
693
694 if new_state != old_state {
695 info!("Recovery state changed from {old_state:?} to {new_state:?}");
696 }
697
698 Ok(())
699 }
700
701 async fn update_recovery_state_no_fail(&self) {
702 if let Err(e) = self.update_recovery_state().await {
703 error!("Couldn't update the recovery state: {e:?}");
704 }
705 }
706
707 #[instrument]
708 async fn secret_send_event_handler(_: ToDeviceSecretSendEvent, client: Client) {
709 client.encryption().recovery().update_recovery_state_no_fail().await;
710 }
711
712 #[instrument]
713 async fn default_key_event_handler(_: SecretStorageDefaultKeyEvent, client: Client) {
714 client.encryption().recovery().update_recovery_state_no_fail().await;
715 }
716
717 /// Listen for changes in the [`BackupState`] and, if necessary, update the
718 /// [`RecoveryState`] accordingly.
719 ///
720 /// This should not be called directly, this method is put into a background
721 /// task which is always listening for updates in the [`BackupState`].
722 pub(crate) fn update_state_after_backup_state_change(
723 client: &Client,
724 ) -> impl Future<Output = ()> + use<> {
725 let mut stream = client.encryption().backups().state_stream();
726 let weak = client.downgrade();
727
728 async move {
729 while let Some(update) = stream.next().await {
730 if let Some(client) = weak.get() {
731 match update {
732 Ok(update) => {
733 // The recovery state only cares about these two
734 // states, the intermediate states that tell us that
735 // we're creating a backup are not interesting.
736 if matches!(update, BackupState::Unknown | BackupState::Enabled) {
737 client
738 .encryption()
739 .recovery()
740 .update_recovery_state_no_fail()
741 .await;
742 }
743 }
744 Err(_) => {
745 // We missed some updates, let's update our state in
746 // case something changed.
747 client.encryption().recovery().update_recovery_state_no_fail().await;
748 }
749 }
750 } else {
751 break;
752 }
753 }
754 }
755 }
756
757 #[instrument(skip_all)]
758 pub(crate) async fn update_state_after_keys_query(&self, response: &get_keys::v3::Response) {
759 if let Some(user_id) = self.client.user_id()
760 && response.master_keys.contains_key(user_id)
761 {
762 // TODO: This is unnecessarily expensive, we could let the crypto
763 // crate notify us that our private keys got erased... But, the
764 // OlmMachine gets recreated and... You know the drill by now...
765 self.update_recovery_state_no_fail().await;
766 }
767 }
768}
769
770/// A helper struct that handles continues resetting a user's crypto identity
771/// after authentication was required and re-enabling backups (if necessary) at
772/// the end of it
773#[derive(Debug)]
774pub struct IdentityResetHandle {
775 client: Client,
776 cross_signing_reset_handle: CrossSigningResetHandle,
777}
778
779impl IdentityResetHandle {
780 /// Get the underlying [`CrossSigningResetAuthType`] this identity reset
781 /// process is using.
782 pub fn auth_type(&self) -> &CrossSigningResetAuthType {
783 &self.cross_signing_reset_handle.auth_type
784 }
785
786 /// This method will retry to upload the device keys after the previous try
787 /// failed due to required authentication
788 pub async fn reset(&self, auth: AuthData) -> Result<()> {
789 self.cross_signing_reset_handle.auth(auth).await?;
790
791 if self.client.encryption().recovery().should_auto_enable_backups().await? {
792 self.client.encryption().recovery().enable_backup().await?;
793 }
794
795 Ok(())
796 }
797
798 /// Cancel the ongoing identity reset process
799 pub async fn cancel(&self) {
800 self.cross_signing_reset_handle.cancel().await;
801 }
802}
803
804// The http mocking library is not supported for wasm32
805#[cfg(all(test, not(target_family = "wasm")))]
806pub(crate) mod tests {
807 use assert_matches::assert_matches;
808 use matrix_sdk_test::async_test;
809 use ruma::{
810 events::{secret::request::SecretName, secret_storage::key},
811 serde::Base64,
812 };
813 use serde_json::json;
814
815 use super::Recovery;
816 use crate::{
817 encryption::{recovery::types::RecoveryError, secret_storage::SecretStorageError},
818 test_utils::mocks::MatrixMockServer,
819 };
820
821 // If recovery fails due when importing a secret from secret storage, we
822 // should get the `ImportError` variant of `SecretStorageError`. The
823 // following tests test different import failures.
824 #[async_test]
825 async fn test_recover_with_no_cross_signing_key() {
826 let server = MatrixMockServer::new().await;
827 let client = server.client_builder().build().await;
828
829 server
830 .mock_get_secret_storage_key()
831 .ok(
832 client.user_id().unwrap(),
833 &key::SecretStorageKeyEventContent::new(
834 "abc".into(),
835 key::SecretStorageEncryptionAlgorithm::V1AesHmacSha2(
836 key::SecretStorageV1AesHmacSha2Properties::new(
837 Some(Base64::parse("xv5b6/p3ExEw++wTyfSHEg==").unwrap()),
838 Some(
839 Base64::parse("ujBBbXahnTAMkmPUX2/0+VTfUh63pGyVRuBcDMgmJC8=")
840 .unwrap(),
841 ),
842 ),
843 ),
844 ),
845 )
846 .mount()
847 .await;
848 server
849 .mock_get_default_secret_storage_key()
850 .ok(client.user_id().unwrap(), "abc")
851 .mount()
852 .await;
853
854 let recovery = Recovery { client };
855
856 let ret =
857 recovery.recover("EsTj 3yST y93F SLpB jJsz eAXc 2XzA ygD3 w69H fGaN TKBj jXEd").await;
858
859 assert_matches!(
860 ret,
861 Err(RecoveryError::SecretStorage(SecretStorageError::ImportError {
862 name: SecretName::CrossSigningMasterKey,
863 error: _
864 }))
865 );
866 }
867
868 #[async_test]
869 async fn test_recover_with_invalid_cross_signing_key() {
870 let server = MatrixMockServer::new().await;
871 let client = server.client_builder().build().await;
872
873 server
874 .mock_get_secret_storage_key()
875 .ok(
876 client.user_id().unwrap(),
877 &key::SecretStorageKeyEventContent::new(
878 "abc".into(),
879 key::SecretStorageEncryptionAlgorithm::V1AesHmacSha2(
880 key::SecretStorageV1AesHmacSha2Properties::new(
881 Some(Base64::parse("xv5b6/p3ExEw++wTyfSHEg==").unwrap()),
882 Some(
883 Base64::parse("ujBBbXahnTAMkmPUX2/0+VTfUh63pGyVRuBcDMgmJC8=")
884 .unwrap(),
885 ),
886 ),
887 ),
888 ),
889 )
890 .mount()
891 .await;
892 server
893 .mock_get_default_secret_storage_key()
894 .ok(client.user_id().unwrap(), "abc")
895 .mount()
896 .await;
897 server.mock_get_master_signing_key().ok(client.user_id().unwrap(), json!({})).mount().await;
898
899 let recovery = Recovery { client };
900
901 let ret =
902 recovery.recover("EsTj 3yST y93F SLpB jJsz eAXc 2XzA ygD3 w69H fGaN TKBj jXEd").await;
903
904 assert_matches!(
905 ret,
906 Err(RecoveryError::SecretStorage(SecretStorageError::ImportError {
907 name: SecretName::CrossSigningMasterKey,
908 error: _
909 }))
910 );
911 }
912
913 #[async_test]
914 async fn test_recover_with_undecryptable_cross_signing_key() {
915 let server = MatrixMockServer::new().await;
916 let client = server.client_builder().build().await;
917
918 server
919 .mock_get_secret_storage_key()
920 .ok(
921 client.user_id().unwrap(),
922 &key::SecretStorageKeyEventContent::new(
923 "abc".into(),
924 key::SecretStorageEncryptionAlgorithm::V1AesHmacSha2(
925 key::SecretStorageV1AesHmacSha2Properties::new(
926 Some(Base64::parse("xv5b6/p3ExEw++wTyfSHEg==").unwrap()),
927 Some(
928 Base64::parse("ujBBbXahnTAMkmPUX2/0+VTfUh63pGyVRuBcDMgmJC8=")
929 .unwrap(),
930 ),
931 ),
932 ),
933 ),
934 )
935 .mount()
936 .await;
937 server
938 .mock_get_default_secret_storage_key()
939 .ok(client.user_id().unwrap(), "abc")
940 .mount()
941 .await;
942 server
943 .mock_get_master_signing_key()
944 .ok(
945 client.user_id().unwrap(),
946 json!({
947 "encrypted": {
948 "abc": {
949 "iv": "xv5b6/p3ExEw++wTyfSHEg==",
950 "mac": "ujBBbXahnTAMkmPUX2/0+VTfUh63pGyVRuBcDMgmJC8=",
951 "ciphertext": "abcd"
952 }
953 }
954 }),
955 )
956 .mount()
957 .await;
958
959 let recovery = Recovery { client };
960
961 let ret =
962 recovery.recover("EsTj 3yST y93F SLpB jJsz eAXc 2XzA ygD3 w69H fGaN TKBj jXEd").await;
963
964 assert_matches!(
965 ret,
966 Err(RecoveryError::SecretStorage(SecretStorageError::ImportError {
967 name: SecretName::CrossSigningMasterKey,
968 error: _
969 }))
970 );
971 }
972}