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