Skip to main content

matrix_sdk/encryption/recovery/
futures.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//! Named futures for the recovery support.
16
17use std::{future::IntoFuture, pin::pin};
18
19use futures_core::Stream;
20use futures_util::StreamExt;
21use matrix_sdk_base::crypto::secret_storage::SecretStorageKey;
22use matrix_sdk_common::boxed_into_future;
23use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
24use tracing::{Instrument, Span, warn};
25
26use super::{EnableProgress, Recovery, RecoveryError, Result};
27use crate::{
28    encryption::{
29        backups::UploadState,
30        secret_storage::{SecretStore, futures::KeyOrPassphrase},
31    },
32    utils::ChannelObservable,
33};
34
35/// Named future for the [`Recovery::enable()`] method.
36#[derive(Debug)]
37pub struct Enable<'a> {
38    pub(super) recovery: &'a Recovery,
39    pub(super) progress: ChannelObservable<EnableProgress>,
40    pub(super) wait_for_backups_upload: bool,
41    pub(super) key: KeyOrPassphrase<'a>,
42    tracing_span: Span,
43}
44
45impl<'a> Enable<'a> {
46    pub(super) fn new(recovery: &'a Recovery) -> Self {
47        Self {
48            recovery,
49            progress: Default::default(),
50            wait_for_backups_upload: false,
51            key: KeyOrPassphrase::None,
52            tracing_span: Span::current(),
53        }
54    }
55
56    /// Subscribe to updates to the recovery enabling progress.
57    pub fn subscribe_to_progress(
58        &self,
59    ) -> impl Stream<Item = Result<EnableProgress, BroadcastStreamRecvError>> + use<> {
60        self.progress.subscribe()
61    }
62
63    /// Should the enabling of the recovery also wait for _all_ room keys to be
64    /// uploaded to the server-side key backup?
65    ///
66    /// This is useful if the user is enabling recovery and the room key backup
67    /// just before logging out. Otherwise the logout might finish before all
68    /// room keys have been backed up and thus historic messages will fail to
69    /// decrypt once the user logs back in again.
70    pub fn wait_for_backups_to_upload(mut self) -> Self {
71        self.wait_for_backups_upload = true;
72
73        self
74    }
75
76    /// In addition to the recovery key the [`Recovery::enable()`] method
77    /// returns, allow this passphrase to be used for the
78    /// [`Recovery::recover()`] method.
79    pub fn with_passphrase(mut self, passphrase: &'a str) -> Self {
80        self.key = KeyOrPassphrase::Passphrase(passphrase);
81
82        self
83    }
84
85    /// Use this key for the [`Recovery::recover()`] method.
86    pub fn with_key(mut self, key: &'a SecretStorageKey) -> Self {
87        self.key = KeyOrPassphrase::Key(key);
88
89        self
90    }
91}
92
93impl<'a> IntoFuture for Enable<'a> {
94    type Output = Result<String>;
95    boxed_into_future!(extra_bounds: 'a);
96
97    fn into_future(self) -> Self::IntoFuture {
98        let Self { recovery, progress, wait_for_backups_upload, key, tracing_span } = self;
99
100        let future = async move {
101            if !recovery.client.encryption().backups().are_enabled().await {
102                if recovery.client.encryption().backups().fetch_exists_on_server().await? {
103                    return Err(RecoveryError::BackupExistsOnServer);
104                } else {
105                    progress.set(EnableProgress::CreatingBackup);
106                    recovery.mark_backup_as_enabled().await?;
107                    recovery.client.encryption().backups().create().await?;
108                }
109            }
110
111            progress.set(EnableProgress::CreatingRecoveryKey);
112
113            let secret_storage = recovery.client.encryption().secret_storage();
114
115            let create_store = match key {
116                KeyOrPassphrase::None => secret_storage.create_secret_store(),
117                KeyOrPassphrase::Key(k) => secret_storage.create_secret_store().with_key(k),
118                KeyOrPassphrase::Passphrase(p) => {
119                    secret_storage.create_secret_store().with_passphrase(p)
120                }
121            };
122
123            let store: SecretStore = create_store.await?;
124
125            if wait_for_backups_upload {
126                let backups = recovery.client.encryption().backups();
127                let upload_future = backups.wait_for_steady_state();
128                let upload_progress = upload_future.subscribe_to_progress();
129
130                #[allow(unused_variables)]
131                let progress_task = matrix_sdk_common::executor::spawn({
132                    let progress = progress.clone();
133                    async move {
134                        let mut upload_progress = pin!(upload_progress);
135
136                        while let Some(update) = upload_progress.next().await {
137                            match update {
138                                Ok(UploadState::Uploading(count)) => {
139                                    progress.set(EnableProgress::BackingUp(count));
140                                }
141                                Ok(UploadState::Done | UploadState::Error) | Err(_) => break,
142                                _ => (),
143                            }
144                        }
145                    }
146                });
147
148                if let Err(e) = upload_future.await {
149                    warn!("Couldn't upload all the room keys to the backup: {e:?}");
150                    progress.set(EnableProgress::RoomKeyUploadError);
151                }
152
153                #[cfg(not(target_family = "wasm"))]
154                progress_task.abort();
155            } else {
156                recovery.client.encryption().backups().maybe_trigger_backup();
157            }
158
159            let key = store.secret_storage_key();
160
161            progress.set(EnableProgress::Done { recovery_key: key });
162            recovery.update_recovery_state().await?;
163
164            Ok(store.secret_storage_key())
165        };
166
167        Box::pin(future.instrument(tracing_span))
168    }
169}
170
171/// Named future for the [`Recovery::reset_key()`] method.
172#[derive(Debug)]
173pub struct Reset<'a> {
174    pub(super) recovery: &'a Recovery,
175    pub(super) key: KeyOrPassphrase<'a>,
176    tracing_span: Span,
177}
178
179impl<'a> Reset<'a> {
180    pub(super) fn new(recovery: &'a Recovery) -> Self {
181        Self { recovery, key: KeyOrPassphrase::None, tracing_span: Span::current() }
182    }
183
184    /// In addition to the recovery key the [`Recovery::reset_key()`] method
185    /// returns, allow this passphrase to be used for the
186    /// [`Recovery::recover()`] method.
187    pub fn with_passphrase(mut self, passphrase: &'a str) -> Self {
188        self.key = KeyOrPassphrase::Passphrase(passphrase);
189
190        self
191    }
192
193    /// Use this key for the [`Recovery::recover()`] method.
194    pub fn with_key(mut self, key: &'a SecretStorageKey) -> Self {
195        self.key = KeyOrPassphrase::Key(key);
196
197        self
198    }
199}
200
201impl<'a> IntoFuture for Reset<'a> {
202    type Output = Result<String>;
203    boxed_into_future!(extra_bounds: 'a);
204
205    fn into_future(self) -> Self::IntoFuture {
206        let Self { recovery, key, tracing_span } = self;
207
208        let future = async move {
209            let secret_storage = recovery.client.encryption().secret_storage();
210
211            let create_store = match key {
212                KeyOrPassphrase::None => secret_storage.create_secret_store(),
213                KeyOrPassphrase::Key(k) => secret_storage.create_secret_store().with_key(k),
214                KeyOrPassphrase::Passphrase(p) => {
215                    secret_storage.create_secret_store().with_passphrase(p)
216                }
217            };
218
219            let store: SecretStore = create_store.await?;
220            recovery.update_recovery_state().await?;
221
222            Ok(store.secret_storage_key())
223        };
224
225        Box::pin(future.instrument(tracing_span))
226    }
227}
228
229/// Named future for the [`Recovery::recover_and_reset()`] method.
230#[derive(Debug)]
231pub struct RecoverAndReset<'a> {
232    pub(super) recovery: &'a Recovery,
233    pub(super) old_recovery_key: &'a str,
234    pub(super) key: KeyOrPassphrase<'a>,
235    tracing_span: Span,
236}
237
238impl<'a> RecoverAndReset<'a> {
239    pub(super) fn new(recovery: &'a Recovery, old_recovery_key: &'a str) -> Self {
240        Self {
241            recovery,
242            old_recovery_key,
243            key: KeyOrPassphrase::None,
244            tracing_span: Span::current(),
245        }
246    }
247
248    /// In addition to the new recovery key the
249    /// [`Recovery::recover_and_reset()`] method returns, allow this passphrase
250    /// to be used for the [`Recovery::recover()`] method.
251    pub fn with_passphrase(mut self, passphrase: &'a str) -> Self {
252        self.key = KeyOrPassphrase::Passphrase(passphrase);
253
254        self
255    }
256
257    /// Use this key for the [`Recovery::recover()`] method.
258    pub fn with_key(mut self, key: &'a SecretStorageKey) -> Self {
259        self.key = KeyOrPassphrase::Key(key);
260
261        self
262    }
263}
264
265impl<'a> IntoFuture for RecoverAndReset<'a> {
266    type Output = Result<String>;
267    boxed_into_future!(extra_bounds: 'a);
268
269    fn into_future(self) -> Self::IntoFuture {
270        let Self { recovery, old_recovery_key, key, tracing_span } = self;
271
272        let future = async move {
273            recovery.recover(old_recovery_key).await?;
274
275            let reset = match key {
276                KeyOrPassphrase::None => recovery.reset_key(),
277                KeyOrPassphrase::Key(k) => recovery.reset_key().with_key(k),
278                KeyOrPassphrase::Passphrase(p) => recovery.reset_key().with_passphrase(p),
279            };
280
281            reset.await
282        };
283
284        Box::pin(future.instrument(tracing_span))
285    }
286}