matrix_sdk/encryption/secret_storage/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//! Secret Storage Support
16//!
17//! This submodule provides essential functionality for secret storage in
18//! compliance with the [Matrix protocol specification][spec].
19//!
20//! Secret storage is a critical component that provides an encrypted
21//! key/value storage system. It leverages [account data] events stored on the
22//! Matrix homeserver to ensure secure and private storage of sensitive
23//! information.
24//!
25//! For detailed information and usage guidelines, refer to the documentation of
26//! the [`SecretStore`] struct.
27//!
28//! # Examples
29//!
30//! ```no_run
31//! # use matrix_sdk::Client;
32//! # use url::Url;
33//! # async {
34//! # let homeserver = Url::parse("http://example.com")?;
35//! # let client = Client::new(homeserver).await?;
36//! use ruma::events::secret::request::SecretName;
37//!
38//! // Open the store.
39//! let secret_store = client
40//! .encryption()
41//! .secret_storage()
42//! .open_secret_store("It's a secret to everybody")
43//! .await?;
44//!
45//! // Import the secrets.
46//! secret_store.import_secrets().await?;
47//!
48//! // Our own device should now be verified.
49//! let device = client
50//! .encryption()
51//! .get_own_device()
52//! .await?
53//! .expect("We should be able to retrieve our own device");
54//!
55//! assert!(device.is_cross_signed_by_owner());
56//!
57//! # anyhow::Ok(()) };
58//! ```
59//!
60//! [spec]: https://spec.matrix.org/v1.8/client-server-api/#secret-storage
61//! [account data]: https://spec.matrix.org/v1.8/client-server-api/#client-config
62
63use std::string::FromUtf8Error;
64
65use matrix_sdk_base::crypto::{
66 CryptoStoreError, SecretImportError,
67 secret_storage::{DecodeError, MacError, SecretStorageKey},
68};
69use ruma::{
70 events::{
71 EventContentFromType, GlobalAccountDataEventType,
72 secret::request::SecretName,
73 secret_storage::{
74 default_key::SecretStorageDefaultKeyEventContent, key::SecretStorageKeyEventContent,
75 },
76 },
77 serde::Raw,
78};
79use serde_json::value::to_raw_value;
80use thiserror::Error;
81
82use super::identities::ManualVerifyError;
83use crate::Client;
84
85mod futures;
86mod secret_store;
87
88pub use futures::CreateStore;
89pub use secret_store::SecretStore;
90
91/// Convenicence type alias for the secret-storage specific results.
92pub type Result<T, E = SecretStorageError> = std::result::Result<T, E>;
93
94/// Error type for errors when importing a secret from secret storage.
95#[derive(Debug, Error)]
96pub enum ImportError {
97 /// A typical SDK error.
98 #[error(transparent)]
99 Sdk(#[from] crate::Error),
100
101 /// Error when deserializing account data events.
102 #[error(transparent)]
103 Json(#[from] serde_json::Error),
104
105 /// The key that we tried to import was invalid.
106 #[error(transparent)]
107 Key(vodozemac::KeyError),
108
109 /// The public key of the imported private key doesn't match the public
110 /// key that was uploaded to the server.
111 #[error(
112 "The public key of the imported private key doesn't match the public\
113 key that was uploaded to the server"
114 )]
115 MismatchedPublicKeys,
116
117 /// Error describing a decryption failure of a secret.
118 #[error(transparent)]
119 Decryption(#[from] DecryptionError),
120}
121
122/// Error type for the secret-storage subsystem.
123#[derive(Debug, Error)]
124pub enum SecretStorageError {
125 /// A typical SDK error.
126 #[error(transparent)]
127 Sdk(#[from] crate::Error),
128
129 /// Error when deserializing account data events.
130 #[error(transparent)]
131 Json(#[from] serde_json::Error),
132
133 /// The secret storage key could not have been decoded or verified
134 /// successfully.
135 #[error(transparent)]
136 SecretStorageKey(#[from] DecodeError),
137
138 /// The secret store could not be opened because info about the
139 /// secret-storage key could not have been found in the account data of
140 /// the user.
141 #[error(
142 "The info about the secret key could not have been found in the account data of the user"
143 )]
144 MissingKeyInfo {
145 /// The key ID of the default key. Will be set to the key ID in the
146 /// `m.secret_storage.default_key` event. If the
147 /// `m.secret_storage.default_key` does not exits, will be
148 /// `None`.
149 key_id: Option<String>,
150 },
151
152 /// An error when importing from the secret store into the local store.
153 #[error("Error while importing {name}: {error}")]
154 ImportError {
155 /// The name of the secret that was being imported when the error
156 /// occurred.
157 name: SecretName,
158 /// The error that occurred.
159 error: ImportError,
160 },
161
162 /// A general storage error.
163 #[error(transparent)]
164 Storage(#[from] CryptoStoreError),
165
166 /// An error happened while trying to mark our own device as verified after
167 /// the private cross-signing keys have been imported.
168 #[error(transparent)]
169 Verification(#[from] ManualVerifyError),
170
171 /// Error describing a decryption failure of a secret.
172 #[error(transparent)]
173 Decryption(#[from] DecryptionError),
174}
175
176impl SecretStorageError {
177 /// Create a `SecretStorageError::ImportError` from a secret name and any
178 /// error that can be converted directly into an `ImportError`
179 fn into_import_error(secret_name: SecretName, error: impl Into<ImportError>) -> Self {
180 SecretStorageError::ImportError { name: secret_name, error: error.into() }
181 }
182
183 /// Create a `SecretStorageError` from a `SecretImportError`
184 ///
185 /// `SecretImportError::Key` and `SecretImportError::MismatchedPublicKeys`
186 /// become `SecretStorageError::ImportError`s, whereas
187 /// `SecretImportError::Store` becomes `SecretStorageError::Storage` since
188 /// the error is with the crypto storage rather than in importing the
189 /// secret.
190 fn from_secret_import_error(error: SecretImportError) -> Self {
191 match error {
192 SecretImportError::Key { name, error } => {
193 SecretStorageError::ImportError { name, error: ImportError::Key(error) }
194 }
195 SecretImportError::MismatchedPublicKeys { name } => {
196 SecretStorageError::ImportError { name, error: ImportError::MismatchedPublicKeys }
197 }
198 SecretImportError::Store(error) => SecretStorageError::Storage(error),
199 }
200 }
201}
202
203/// Error type describing decryption failures of the secret-storage system.
204#[derive(Debug, Error)]
205pub enum DecryptionError {
206 /// The secret could not have been decrypted.
207 #[error("Could not decrypt the secret using the secret storage key, invalid MAC.")]
208 Mac(#[from] MacError),
209
210 /// Could not decode the secret, the secret is not valid UTF-8.
211 #[error("Could not decode the secret, the secret is not valid UTF-8")]
212 Utf8(#[from] FromUtf8Error),
213}
214
215/// A high-level API to manage secret storage.
216///
217/// To get this, use
218/// [`client.encryption().secret_storage()`](super::Encryption::secret_storage).
219#[derive(Debug)]
220pub struct SecretStorage {
221 pub(super) client: Client,
222}
223
224impl SecretStorage {
225 /// Open the [`SecretStore`] with the given `key`.
226 ///
227 /// The `secret_storage_key` can be a passphrase or a Base58 encoded secret
228 /// storage key.
229 ///
230 /// # Examples
231 ///
232 /// ```no_run
233 /// # use matrix_sdk::Client;
234 /// # use url::Url;
235 /// # async {
236 /// # let homeserver = Url::parse("http://example.com")?;
237 /// # let client = Client::new(homeserver).await?;
238 /// use ruma::events::secret::request::SecretName;
239 ///
240 /// let secret_store = client
241 /// .encryption()
242 /// .secret_storage()
243 /// .open_secret_store("It's a secret to everybody")
244 /// .await?;
245 ///
246 /// let my_secret = "Top secret secret";
247 /// let my_secret_name = "m.treasure";
248 ///
249 /// secret_store.put_secret(my_secret_name, my_secret);
250 ///
251 /// # anyhow::Ok(()) };
252 /// ```
253 pub async fn open_secret_store(&self, secret_storage_key: &str) -> Result<SecretStore> {
254 let maybe_default_key_id = self.fetch_default_key_id().await?;
255
256 if let Some(default_key_id) = maybe_default_key_id {
257 let default_key_id = default_key_id.deserialize()?;
258
259 let event_type =
260 GlobalAccountDataEventType::SecretStorageKey(default_key_id.key_id.to_owned());
261 let secret_key =
262 self.client.account().fetch_account_data(event_type.to_owned()).await?;
263
264 if let Some(secret_key_content) = secret_key {
265 let event_type = event_type.to_string();
266 let secret_key_content = to_raw_value(&secret_key_content)?;
267
268 let secret_key_content =
269 SecretStorageKeyEventContent::from_parts(&event_type, &secret_key_content)?;
270
271 let key =
272 SecretStorageKey::from_account_data(secret_storage_key, secret_key_content)?;
273
274 Ok(SecretStore { client: self.client.to_owned(), key })
275 } else {
276 Err(SecretStorageError::MissingKeyInfo { key_id: Some(default_key_id.key_id) })
277 }
278 } else {
279 Err(SecretStorageError::MissingKeyInfo { key_id: None })
280 }
281 }
282
283 /// Create a new [`SecretStore`].
284 ///
285 /// The [`SecretStore`] will be protected by a randomly generated key, or
286 /// optionally a passphrase can be provided as well.
287 ///
288 /// In both cases, whether a passphrase was provided or not, the key to open
289 /// the [`SecretStore`] can be obtained using the
290 /// [`SecretStore::secret_storage_key()`] method.
291 ///
292 /// *Note*: This method will set the new secret storage key as the default
293 /// key in the `m.secret_storage.default_key` event. All the known secrets
294 /// will be re-encrypted and uploaded to the homeserver as well. This
295 /// includes the following secrets:
296 ///
297 /// - `m.cross_signing.master`: The master cross-signing key.
298 /// - `m.cross_signing.self_signing`: The self-signing cross-signing key.
299 /// - `m.cross_signing.user_signing`: The user-signing cross-signing key.
300 ///
301 /// # Examples
302 ///
303 /// ```no_run
304 /// # use matrix_sdk::Client;
305 /// # use url::Url;
306 /// # async {
307 /// # let homeserver = Url::parse("http://example.com")?;
308 /// # let client = Client::new(homeserver).await?;
309 /// use ruma::events::secret::request::SecretName;
310 ///
311 /// let secret_store = client
312 /// .encryption()
313 /// .secret_storage()
314 /// .create_secret_store()
315 /// .await?;
316 ///
317 /// let my_secret = "Top secret secret";
318 /// let my_secret_name = SecretName::from("m.treasure");
319 ///
320 /// secret_store.put_secret(my_secret_name, my_secret);
321 ///
322 /// let secret_storage_key = secret_store.secret_storage_key();
323 ///
324 /// println!("Your secret storage key is {secret_storage_key}, save it somewhere safe.");
325 ///
326 /// # anyhow::Ok(()) };
327 /// ```
328 pub fn create_secret_store(&self) -> CreateStore<'_> {
329 CreateStore { secret_storage: self, passphrase: None }
330 }
331
332 /// Run a network request to find if secret storage is set up for this user.
333 pub async fn is_enabled(&self) -> crate::Result<bool> {
334 if let Some(content) = self.fetch_default_key_id().await? {
335 // Since we can't delete account data events, we're going to treat
336 // deserialization failures as secret storage being disabled.
337 Ok(content.deserialize().is_ok())
338 } else {
339 // No account data event found, must be disabled.
340 Ok(false)
341 }
342 }
343
344 /// Fetch the `m.secret_storage.default_key` event from the server.
345 pub async fn fetch_default_key_id(
346 &self,
347 ) -> crate::Result<Option<Raw<SecretStorageDefaultKeyEventContent>>> {
348 self.client
349 .account()
350 .fetch_account_data_static::<SecretStorageDefaultKeyEventContent>()
351 .await
352 }
353}