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;
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.try_into().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.encrypted.insert(self.key.key_id().to_owned(), encrypted_secret.into());
272 let secret_content = Raw::from_json(to_raw_value(&secret_content)?);
273
274 // Upload the modified account data event, now that the new secret has been
275 // inserted.
276 self.client.account().set_account_data_raw(event_type, secret_content).await?;
277
278 Ok(())
279 }
280
281 /// Get all the well-known private parts/keys of the [`OwnUserIdentity`] as
282 /// a [`CrossSigningKeyExport`].
283 ///
284 /// The export can be imported into the [`OlmMachine`] using
285 /// [`OlmMachine::import_cross_signing_keys()`].
286 async fn get_cross_signing_keys(&self) -> Result<CrossSigningKeyExport> {
287 let mut export = CrossSigningKeyExport::default();
288
289 export.master_key = self.get_secret(SecretName::CrossSigningMasterKey).await?;
290 export.self_signing_key = self.get_secret(SecretName::CrossSigningSelfSigningKey).await?;
291 export.user_signing_key = self.get_secret(SecretName::CrossSigningUserSigningKey).await?;
292
293 Ok(export)
294 }
295
296 async fn put_cross_signing_keys(&self, export: CrossSigningKeyExport) -> Result<()> {
297 if let Some(master_key) = &export.master_key {
298 self.put_secret(SecretName::CrossSigningMasterKey, master_key).await?;
299 }
300
301 if let Some(user_signing_key) = &export.user_signing_key {
302 self.put_secret(SecretName::CrossSigningUserSigningKey, user_signing_key).await?;
303 }
304
305 if let Some(self_signing_key) = &export.self_signing_key {
306 self.put_secret(SecretName::CrossSigningSelfSigningKey, self_signing_key).await?;
307 }
308
309 Ok(())
310 }
311
312 async fn maybe_enable_backups(&self) -> Result<()> {
313 match self.get_secret(SecretName::RecoveryKey).await {
314 Ok(Some(mut secret)) => {
315 let ret =
316 self.client.encryption().backups().maybe_enable_backups(&secret).await.map_err(
317 |e| SecretStorageError::into_import_error(SecretName::RecoveryKey, e),
318 );
319
320 if let Err(e) = &ret {
321 warn!("Could not enable backups from secret storage: {e:?}");
322 }
323
324 secret.zeroize();
325
326 Ok(ret.map(|_| ())?)
327 }
328 Err(e) => {
329 warn!("Could not enable backups from secret storage: {e:?}");
330
331 Err(e)
332 }
333 _ => {
334 info!("No backup recovery key found.");
335
336 Ok(())
337 }
338 }
339 }
340
341 /// Retrieve and store well-known secrets locally
342 ///
343 /// This method retrieves and stores all well-known secrets from the account
344 /// data on the Matrix homeserver to enhance local security and identity
345 /// verification.
346 ///
347 /// The following secrets are retrieved by this method:
348 ///
349 /// - `m.cross_signing.master`: The master cross-signing key.
350 /// - `m.cross_signing.self_signing`: The self-signing cross-signing key.
351 /// - `m.cross_signing.user_signing`: The user-signing cross-signing key.
352 /// - `m.megolm_backup.v1`: The backup recovery key.
353 ///
354 /// If the `m.cross_signing.self_signing` key is successfully imported, it
355 /// is used to sign our own [`Device`], marking it as verified. This step is
356 /// establishes trust in your own device's identity.
357 ///
358 /// By invoking this method, you ensure that your device has access to
359 /// the necessary secrets for device and identity verification.
360 ///
361 /// # Examples
362 ///
363 /// ```no_run
364 /// # use matrix_sdk::Client;
365 /// # use url::Url;
366 /// # async {
367 /// # let homeserver = Url::parse("http://example.com")?;
368 /// # let client = Client::new(homeserver).await?;
369 /// use ruma::events::secret::request::SecretName;
370 ///
371 /// let secret_store = client
372 /// .encryption()
373 /// .secret_storage()
374 /// .open_secret_store("It's a secret to everybody")
375 /// .await?;
376 ///
377 /// secret_store.import_secrets().await?;
378 ///
379 /// let status = client
380 /// .encryption()
381 /// .cross_signing_status()
382 /// .await
383 /// .expect("We should be able to check out cross-signing status");
384 ///
385 /// println!("Cross-signing status {status:?}");
386 ///
387 /// # anyhow::Ok(()) };
388 /// ```
389 ///
390 /// [`Device`]: crate::encryption::identities::Device
391 #[instrument(fields(user_id, device_id, cross_signing_status))]
392 pub async fn import_secrets(&self) -> Result<()> {
393 let olm_machine = self.client.olm_machine().await;
394 let olm_machine = olm_machine.as_ref().ok_or(crate::Error::NoOlmMachine)?;
395
396 Span::current()
397 .record("user_id", display(olm_machine.user_id()))
398 .record("device_id", display(olm_machine.device_id()));
399
400 info!("Fetching the private cross-signing keys from the secret store");
401
402 // Get all our private cross-signing keys from the secret store.
403 let export = self.get_cross_signing_keys().await?;
404
405 info!(cross_signing_keys = ?export, "Received the cross signing keys from the server");
406
407 // We need to ensure that we have the public parts of the cross-signing keys,
408 // those are represented as the `OwnUserIdentity` struct. The public
409 // parts from the server are compared to the public parts re-derived from the
410 // private parts. We will only import the private parts of the cross-signing
411 // keys if they match to the public parts, otherwise we would risk
412 // importing some stale cross-signing keys leftover in the secret store.
413 let (request_id, request) = olm_machine.query_keys_for_users([olm_machine.user_id()]);
414 self.client.keys_query(&request_id, request.device_keys).await?;
415
416 // Let's now try to import our private cross-signing keys.
417 let status = olm_machine
418 .import_cross_signing_keys(export)
419 .await
420 .map_err(SecretStorageError::from_secret_import_error)?;
421
422 Span::current().record("cross_signing_status", debug(&status));
423
424 info!("Done importing the cross signing keys");
425
426 if status.has_self_signing {
427 info!("Successfully imported the self-signing key, attempting to sign our own device");
428
429 // Now that we successfully imported them, the self-signing key can be used to
430 // verify our own device so other devices and user identities trust
431 // it if the trust our user identity.
432 if let Some(own_device) = self.client.encryption().get_own_device().await? {
433 own_device.verify().await?;
434
435 // Another /keys/query request to ensure that the signatures we uploaded using
436 // `own_device.verify()` are attached to the `Device` we have in storage.
437 let (request_id, request) =
438 olm_machine.query_keys_for_users([olm_machine.user_id()]);
439 self.client.keys_query(&request_id, request.device_keys).await?;
440
441 info!("Successfully signed our own device, the device is now verified");
442 } else {
443 error!("Couldn't find our own device in the store");
444 }
445 }
446
447 self.maybe_enable_backups().await?;
448
449 Ok(())
450 }
451
452 pub(super) async fn export_secrets(&self) -> Result<()> {
453 let olm_machine = self.client.olm_machine().await;
454 let olm_machine = olm_machine.as_ref().ok_or(crate::Error::NoOlmMachine)?;
455
456 if let Some(cross_signing_keys) = olm_machine.export_cross_signing_keys().await? {
457 self.put_cross_signing_keys(cross_signing_keys).await?;
458 }
459
460 let backup_keys = olm_machine.backup_machine().get_backup_keys().await?;
461
462 if let Some(backup_recovery_key) = backup_keys.decryption_key {
463 let mut key = backup_recovery_key.to_base64();
464 self.put_secret(SecretName::RecoveryKey, &key).await?;
465
466 key.zeroize();
467 }
468
469 Ok(())
470 }
471}
472
473impl fmt::Debug for SecretStore {
474 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
475 f.debug_struct("SecretStore").field("key", &self.key).finish_non_exhaustive()
476 }
477}