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