Skip to main content

matrix_sdk/encryption/secret_storage/
secret_store.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
15use std::fmt;
16
17use matrix_sdk_base::crypto::{CrossSigningKeyExport, secret_storage::SecretStorageKey};
18use ruma::{
19    events::{
20        GlobalAccountDataEventType, secret::request::SecretName,
21        secret_storage::secret::SecretEventContent,
22    },
23    serde::Raw,
24};
25use serde_json::value::to_raw_value;
26use tracing::{
27    Span, error,
28    field::{debug, display},
29    info, instrument, warn,
30};
31use zeroize::Zeroize;
32
33use super::{DecryptionError, Result, SecretStorageError};
34use crate::{Client, encryption::backups::EnableBackupError};
35
36#[cfg_attr(doc, aquamarine::aquamarine)]
37/// Secure key/value storage for Matrix users.
38///
39/// The `SecretStore` struct encapsulates the secret storage mechanism for
40/// Matrix users, as it is specified in the [Matrix specification].
41///
42/// This specialized storage is tied to the user's Matrix account and serves as
43/// an encrypted key/value store, backed by [account data] residing on the
44/// homeserver. Any secrets uploaded to the homeserver using the
45/// [`SecretStore::put_secret()`] method are automatically encrypted by the
46/// [`SecretStore`].
47///
48/// [`SecretStore`] enables you to safely manage and access sensitive
49/// information while ensuring that it remains protected from unauthorized
50/// access. It plays a crucial role in maintaining the privacy and security of a
51/// Matrix user's data.
52///
53/// **Data Flow Overview:**
54/// ```mermaid
55/// flowchart LR
56///    subgraph Client
57///        SecretStore
58///    end
59///    subgraph Homeserver
60///        data[Account Data]
61///    end
62///    SecretStore <== Encrypted ==> data
63/// ```
64///
65/// **Note**: It's important to emphasize that the `SecretStore` should not be
66/// used for storing large volumes of data due to its nature as a key/value
67/// store for sensitive information.
68///
69/// # Examples
70///
71/// ```no_run
72/// # use matrix_sdk::Client;
73/// # use url::Url;
74/// # async {
75/// # let homeserver = Url::parse("http://example.com")?;
76/// # let client = Client::new(homeserver).await?;
77/// use ruma::events::secret::request::SecretName;
78///
79/// let secret_store = client
80///     .encryption()
81///     .secret_storage()
82///     .open_secret_store("It's a secret to everybody")
83///     .await?;
84///
85/// let my_secret = "Top secret secret";
86/// let my_secret_name = SecretName::from("m.treasure");
87///
88/// secret_store.put_secret(my_secret_name, my_secret);
89///
90/// # anyhow::Ok(()) };
91/// ```
92///
93/// [Matrix specification]: https://spec.matrix.org/v1.8/client-server-api/#secret-storage
94/// [account data]: https://spec.matrix.org/v1.8/client-server-api/#client-config
95pub struct SecretStore {
96    pub(super) client: Client,
97    pub(super) key: SecretStorageKey,
98}
99
100impl SecretStore {
101    /// Export the [`SecretStorageKey`] of this [`SecretStore`] as a
102    /// base58-encoded string as defined in the [spec].
103    ///
104    /// *Note*: This returns a copy of the private key material of the
105    /// [`SecretStorageKey`] as a string. The caller needs to ensure that this
106    /// string is zeroized.
107    ///
108    /// [spec]: https://spec.matrix.org/v1.8/client-server-api/#key-representation
109    pub fn secret_storage_key(&self) -> String {
110        self.key.to_base58()
111    }
112
113    /// Retrieve a secret from the homeserver's account data
114    ///
115    /// This method allows you to retrieve a secret from the account data stored
116    /// on the Matrix homeserver.
117    ///
118    /// # Arguments
119    ///
120    /// - `secret_name`: The name of the secret. The provided `secret_name`
121    ///   serves as the event type for the associated account data event.
122    ///
123    /// The `retrieve_secret` method enables you to access and decrypt secrets
124    /// previously stored in the user's account data on the homeserver. You can
125    /// use the `secret_name` parameter to specify the desired secret to
126    /// retrieve.
127    ///
128    /// # Examples
129    ///
130    /// ```no_run
131    /// # use matrix_sdk::Client;
132    /// # use url::Url;
133    /// # async {
134    /// # let homeserver = Url::parse("http://example.com")?;
135    /// # let client = Client::new(homeserver).await?;
136    /// use ruma::events::secret::request::SecretName;
137    ///
138    /// let secret_store = client
139    ///     .encryption()
140    ///     .secret_storage()
141    ///     .open_secret_store("It's a secret to everybody")
142    ///     .await?;
143    ///
144    /// let my_secret_name = SecretName::from("m.treasure");
145    ///
146    /// let secret = secret_store.get_secret(my_secret_name).await?;
147    ///
148    /// # anyhow::Ok(()) };
149    /// ```
150    pub async fn get_secret(&self, secret_name: impl Into<SecretName>) -> Result<Option<String>> {
151        let secret_name = secret_name.into();
152        let event_type = GlobalAccountDataEventType::from(secret_name.to_owned());
153
154        if let Some(secret_content) = self
155            .client
156            .account()
157            .fetch_account_data(event_type)
158            .await
159            .map_err(|e| SecretStorageError::into_import_error(secret_name.clone(), e))?
160        {
161            let mut secret_content = secret_content
162                .deserialize_as_unchecked::<SecretEventContent>()
163                .map_err(|e| SecretStorageError::into_import_error(secret_name.clone(), e))?;
164
165            // The `SecretEventContent` contains a map from the secret storage key ID to the
166            // ciphertext. Let's try to find a secret which was encrypted using our
167            // [`SecretStorageKey`].
168            if let Some(secret_content) = secret_content.encrypted.remove(self.key.key_id()) {
169                // We found a secret we should be able to decrypt, let's try to do so.
170                let decrypted = self
171                    .key
172                    .decrypt(
173                        &secret_content.deserialize_as().map_err(|e| {
174                            SecretStorageError::into_import_error(secret_name.clone(), e)
175                        })?,
176                        &secret_name,
177                    )
178                    .map_err(DecryptionError::from)
179                    .map_err(|e| SecretStorageError::into_import_error(secret_name.clone(), e))?;
180
181                let secret = String::from_utf8(decrypted)
182                    .map_err(DecryptionError::from)
183                    .map_err(|e| SecretStorageError::into_import_error(secret_name.clone(), e))?;
184
185                Ok(Some(secret))
186            } else {
187                // We did not find a secret which was encrypted using our [`SecretStorageKey`],
188                // no need to try to decrypt.
189                Ok(None)
190            }
191        } else {
192            Ok(None)
193        }
194    }
195
196    /// Store a secret in the homeserver's account data
197    ///
198    /// This method allows you to securely store a secret on the Matrix
199    /// homeserver as an encrypted account data event.
200    ///
201    /// # Arguments
202    ///
203    /// - `secret_name`: The name of the secret. The provided `secret_name`
204    ///   serves as the event type for the account data event on the homeserver.
205    ///
206    /// - `secret`: The secret to be stored on the homeserver. The secret is
207    ///   encrypted before being stored, ensuring its confidentiality and
208    ///   integrity.
209    ///
210    /// # Examples
211    ///
212    /// ```no_run
213    /// # use matrix_sdk::Client;
214    /// # use url::Url;
215    /// # async {
216    /// # let homeserver = Url::parse("http://example.com")?;
217    /// # let client = Client::new(homeserver).await?;
218    /// use ruma::events::secret::request::SecretName;
219    ///
220    /// let secret_store = client
221    ///     .encryption()
222    ///     .secret_storage()
223    ///     .open_secret_store("It's a secret to everybody")
224    ///     .await?;
225    ///
226    /// let my_secret = "Top secret secret";
227    /// let my_secret_name = SecretName::from("m.treasure");
228    ///
229    /// secret_store.put_secret(my_secret_name, my_secret);
230    ///
231    /// # anyhow::Ok(()) };
232    /// ```
233    pub async fn put_secret(&self, secret_name: impl Into<SecretName>, secret: &str) -> Result<()> {
234        // This function does a read/update/store of an account data event stored on the
235        // homeserver. We first fetch the existing account data event, the event
236        // contains a map which gets updated by this method, finally we upload the
237        // modified event.
238        //
239        // To prevent multiple calls to this method trying to update a secret at the
240        // same time, and thus trampling on each other we introduce a lock which
241        // acts as a semaphore.
242        //
243        // Technically there's a low chance of this happening since we're not storing
244        // many secrets and the bigger problem is that another client might be
245        // doing this as well and the server doesn't have a mechanism to protect against
246        // this.
247        //
248        // We could make this lock be per `secret_name` but this is not a performance
249        // critical method.
250        let _guard = self.client.locks().store_secret_lock.lock().await;
251
252        let secret_name = secret_name.into();
253        let event_type = GlobalAccountDataEventType::from(secret_name.to_owned());
254
255        // Get the existing account data event or create a new empty one.
256        let mut secret_content = if let Some(secret_content) =
257            self.client.account().fetch_account_data(event_type.to_owned()).await?
258        {
259            secret_content
260                .deserialize_as_unchecked::<SecretEventContent>()
261                .unwrap_or_else(|_| SecretEventContent::new(Default::default()))
262        } else {
263            SecretEventContent::new(Default::default())
264        };
265
266        // Encrypt the secret.
267        let secret = secret.as_bytes().to_vec();
268        let encrypted_secret = self.key.encrypt(secret, &secret_name);
269
270        // Insert the encrypted secret into the account data event.
271        secret_content
272            .encrypted
273            .insert(self.key.key_id().to_owned(), Raw::new(&encrypted_secret)?.cast());
274        let secret_content = Raw::from_json(to_raw_value(&secret_content)?);
275
276        // Upload the modified account data event, now that the new secret has been
277        // inserted.
278        self.client.account().set_account_data_raw(event_type, secret_content).await?;
279
280        Ok(())
281    }
282
283    /// Get all the well-known private parts/keys of the [`OwnUserIdentity`] as
284    /// a [`CrossSigningKeyExport`].
285    ///
286    /// The export can be imported into the [`OlmMachine`] using
287    /// [`OlmMachine::import_cross_signing_keys()`].
288    async fn get_cross_signing_keys(&self) -> Result<CrossSigningKeyExport> {
289        let mut export = CrossSigningKeyExport::default();
290
291        export.master_key = self.get_secret(SecretName::CrossSigningMasterKey).await?;
292        export.self_signing_key = self.get_secret(SecretName::CrossSigningSelfSigningKey).await?;
293        export.user_signing_key = self.get_secret(SecretName::CrossSigningUserSigningKey).await?;
294
295        Ok(export)
296    }
297
298    async fn put_cross_signing_keys(&self, export: CrossSigningKeyExport) -> Result<()> {
299        if let Some(master_key) = &export.master_key {
300            self.put_secret(SecretName::CrossSigningMasterKey, master_key).await?;
301        }
302
303        if let Some(user_signing_key) = &export.user_signing_key {
304            self.put_secret(SecretName::CrossSigningUserSigningKey, user_signing_key).await?;
305        }
306
307        if let Some(self_signing_key) = &export.self_signing_key {
308            self.put_secret(SecretName::CrossSigningSelfSigningKey, self_signing_key).await?;
309        }
310
311        Ok(())
312    }
313
314    async fn maybe_enable_backups(&self) -> Result<()> {
315        match self.get_secret(SecretName::RecoveryKey).await {
316            Ok(Some(mut secret)) => {
317                let ret = self
318                    .client
319                    .encryption()
320                    .backups()
321                    .maybe_enable_backups(&secret)
322                    .await
323                    .map_err(|e| match e {
324                        EnableBackupError::InconsistentBackupDecryptionKey => {
325                            SecretStorageError::InconsistentBackupDecryptionKey
326                        }
327                        EnableBackupError::Error(error) => {
328                            SecretStorageError::into_import_error(SecretName::RecoveryKey, error)
329                        }
330                    });
331
332                if let Err(e) = &ret {
333                    warn!("Could not enable backups from secret storage: {e:?}");
334                }
335
336                secret.zeroize();
337
338                Ok(ret.map(|_| ())?)
339            }
340            Err(e) => {
341                warn!("Could not enable backups from secret storage: {e:?}");
342                Err(SecretStorageError::MissingOrInvalidBackupDecryptionKey)
343            }
344            Ok(None) => {
345                info!("No backup recovery key found.");
346
347                Ok(())
348            }
349        }
350    }
351
352    /// Retrieve and store well-known secrets locally
353    ///
354    /// This method retrieves and stores all well-known secrets from the account
355    /// data on the Matrix homeserver to enhance local security and identity
356    /// verification.
357    ///
358    /// The following secrets are retrieved by this method:
359    ///
360    /// - `m.cross_signing.master`: The master cross-signing key.
361    /// - `m.cross_signing.self_signing`: The self-signing cross-signing key.
362    /// - `m.cross_signing.user_signing`: The user-signing cross-signing key.
363    /// - `m.megolm_backup.v1`: The backup recovery key.
364    ///
365    /// If the `m.cross_signing.self_signing` key is successfully imported, it
366    /// is used to sign our own [`Device`], marking it as verified. This step is
367    /// establishes trust in your own device's identity.
368    ///
369    /// By invoking this method, you ensure that your device has access to
370    /// the necessary secrets for device and identity verification.
371    ///
372    /// # Examples
373    ///
374    /// ```no_run
375    /// # use matrix_sdk::Client;
376    /// # use url::Url;
377    /// # async {
378    /// # let homeserver = Url::parse("http://example.com")?;
379    /// # let client = Client::new(homeserver).await?;
380    /// use ruma::events::secret::request::SecretName;
381    ///
382    /// let secret_store = client
383    ///     .encryption()
384    ///     .secret_storage()
385    ///     .open_secret_store("It's a secret to everybody")
386    ///     .await?;
387    ///
388    /// secret_store.import_secrets().await?;
389    ///
390    /// let status = client
391    ///     .encryption()
392    ///     .cross_signing_status()
393    ///     .await
394    ///     .expect("We should be able to check out cross-signing status");
395    ///
396    /// println!("Cross-signing status {status:?}");
397    ///
398    /// # anyhow::Ok(()) };
399    /// ```
400    ///
401    /// [`Device`]: crate::encryption::identities::Device
402    #[instrument(fields(user_id, device_id, cross_signing_status))]
403    pub async fn import_secrets(&self) -> Result<()> {
404        let olm_machine = self.client.olm_machine().await;
405        let olm_machine = olm_machine.as_ref().ok_or(crate::Error::NoOlmMachine)?;
406
407        Span::current()
408            .record("user_id", display(olm_machine.user_id()))
409            .record("device_id", display(olm_machine.device_id()));
410
411        info!("Fetching the private cross-signing keys from the secret store");
412
413        // Get all our private cross-signing keys from the secret store.
414        let export = self.get_cross_signing_keys().await?;
415
416        info!(cross_signing_keys = ?export, "Received the cross signing keys from the server");
417
418        // We need to ensure that we have the public parts of the cross-signing keys,
419        // those are represented as the `OwnUserIdentity` struct. The public
420        // parts from the server are compared to the public parts re-derived from the
421        // private parts. We will only import the private parts of the cross-signing
422        // keys if they match to the public parts, otherwise we would risk
423        // importing some stale cross-signing keys leftover in the secret store.
424        let (request_id, request) = olm_machine.query_keys_for_users([olm_machine.user_id()]);
425        self.client.keys_query(&request_id, request.device_keys).await?;
426
427        // Let's now try to import our private cross-signing keys.
428        let status = olm_machine
429            .import_cross_signing_keys(export)
430            .await
431            .map_err(SecretStorageError::from_secret_import_error)?;
432
433        Span::current().record("cross_signing_status", debug(&status));
434
435        info!("Done importing the cross signing keys");
436
437        if status.has_self_signing {
438            info!("Successfully imported the self-signing key, attempting to sign our own device");
439
440            // Now that we successfully imported them, the self-signing key can be used to
441            // verify our own device so other devices and user identities trust
442            // it if the trust our user identity.
443            if let Some(own_device) = self.client.encryption().get_own_device().await? {
444                own_device.verify().await?;
445
446                // Another /keys/query request to ensure that the signatures we uploaded using
447                // `own_device.verify()` are attached to the `Device` we have in storage.
448                let (request_id, request) =
449                    olm_machine.query_keys_for_users([olm_machine.user_id()]);
450                self.client.keys_query(&request_id, request.device_keys).await?;
451
452                info!("Successfully signed our own device, the device is now verified");
453            } else {
454                error!("Couldn't find our own device in the store");
455            }
456        }
457
458        self.maybe_enable_backups().await?;
459
460        Ok(())
461    }
462
463    /// Upload well-known secrets to the server
464    ///
465    /// This method uploads all well-known secrets to the account data on the
466    /// Matrix homeserver.
467    ///
468    /// The following secrets are uploaded by this method:
469    ///
470    /// - `m.cross_signing.master`: The master cross-signing key.
471    /// - `m.cross_signing.self_signing`: The self-signing cross-signing key.
472    /// - `m.cross_signing.user_signing`: The user-signing cross-signing key.
473    /// - `m.megolm_backup.v1`: The backup recovery key.
474    ///
475    /// By invoking this method, you ensure that the account data holds a copy
476    /// of the necessary secrets for device and identity verification and key
477    /// storage (if enabled).
478    pub async fn export_secrets(&self) -> Result<()> {
479        let olm_machine = self.client.olm_machine().await;
480        let olm_machine = olm_machine.as_ref().ok_or(crate::Error::NoOlmMachine)?;
481
482        if let Some(cross_signing_keys) = olm_machine.export_cross_signing_keys().await? {
483            self.put_cross_signing_keys(cross_signing_keys).await?;
484        }
485
486        let backup_keys = olm_machine.backup_machine().get_backup_keys().await?;
487
488        if let Some(backup_recovery_key) = backup_keys.decryption_key {
489            let mut key = backup_recovery_key.to_base64();
490            self.put_secret(SecretName::RecoveryKey, &key).await?;
491
492            key.zeroize();
493        }
494
495        Ok(())
496    }
497}
498
499impl fmt::Debug for SecretStore {
500    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501        f.debug_struct("SecretStore").field("key", &self.key).finish_non_exhaustive()
502    }
503}