Skip to main content

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}