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 secret_storage::{DecodeError, MacError, SecretStorageKey},
67 CryptoStoreError, SecretImportError,
68};
69use ruma::{
70 events::{
71 secret_storage::{
72 default_key::SecretStorageDefaultKeyEventContent, key::SecretStorageKeyEventContent,
73 },
74 EventContentFromType, GlobalAccountDataEventType,
75 },
76 serde::Raw,
77};
78use serde_json::value::to_raw_value;
79use thiserror::Error;
80
81use super::identities::ManualVerifyError;
82use crate::Client;
83
84mod futures;
85mod secret_store;
86
87pub use futures::CreateStore;
88pub use secret_store::SecretStore;
89
90/// Convenicence type alias for the secret-storage specific results.
91pub type Result<T, E = SecretStorageError> = std::result::Result<T, E>;
92
93/// Error type for the secret-storage subsystem.
94#[derive(Debug, Error)]
95pub enum SecretStorageError {
96 /// A typical SDK error.
97 #[error(transparent)]
98 Sdk(#[from] crate::Error),
99
100 /// Error when deserializing account data events.
101 #[error(transparent)]
102 Json(#[from] serde_json::Error),
103
104 /// The secret storage key could not have been decoded or verified
105 /// successfully.
106 #[error(transparent)]
107 SecretStorageKey(#[from] DecodeError),
108
109 /// The secret store could not be opened because info about the
110 /// secret-storage key could not have been found in the account data of
111 /// the user.
112 #[error(
113 "The info about the secret key could not have been found in the account data of the user"
114 )]
115 MissingKeyInfo {
116 /// The key ID of the default key. Will be set to the key ID in the
117 /// `m.secret_storage.default_key` event. If the
118 /// `m.secret_storage.default_key` does not exits, will be
119 /// `None`.
120 key_id: Option<String>,
121 },
122
123 /// A secret could not have been imported from the secret store into the
124 /// local store.
125 #[error(transparent)]
126 SecretImportError(#[from] SecretImportError),
127
128 /// A general storage error.
129 #[error(transparent)]
130 Storage(#[from] CryptoStoreError),
131
132 /// An error happened while trying to mark our own device as verified after
133 /// the private cross-signing keys have been imported.
134 #[error(transparent)]
135 Verification(#[from] ManualVerifyError),
136
137 /// Error describing a decryption failure of a secret.
138 #[error(transparent)]
139 Decryption(#[from] DecryptionError),
140}
141
142/// Error type describing decryption failures of the secret-storage system.
143#[derive(Debug, Error)]
144pub enum DecryptionError {
145 /// The secret could not have been decrypted.
146 #[error("Could not decrypt the secret using the secret storage key, invalid MAC.")]
147 Mac(#[from] MacError),
148
149 /// Could not decode the secret, the secret is not valid UTF-8.
150 #[error("Could not decode the secret, the secret is not valid UTF-8")]
151 Utf8(#[from] FromUtf8Error),
152}
153
154/// A high-level API to manage secret storage.
155///
156/// To get this, use [`Client::encryption()::secret_storage()`].
157#[derive(Debug)]
158pub struct SecretStorage {
159 pub(super) client: Client,
160}
161
162impl SecretStorage {
163 /// Open the [`SecretStore`] with the given `key`.
164 ///
165 /// The `secret_storage_key` can be a passphrase or a Base58 encoded secret
166 /// storage key.
167 ///
168 /// # Examples
169 ///
170 /// ```no_run
171 /// # use matrix_sdk::Client;
172 /// # use url::Url;
173 /// # async {
174 /// # let homeserver = Url::parse("http://example.com")?;
175 /// # let client = Client::new(homeserver).await?;
176 /// use ruma::events::secret::request::SecretName;
177 ///
178 /// let secret_store = client
179 /// .encryption()
180 /// .secret_storage()
181 /// .open_secret_store("It's a secret to everybody")
182 /// .await?;
183 ///
184 /// let my_secret = "Top secret secret";
185 /// let my_secret_name = "m.treasure";
186 ///
187 /// secret_store.put_secret(my_secret_name, my_secret);
188 ///
189 /// # anyhow::Ok(()) };
190 /// ```
191 pub async fn open_secret_store(&self, secret_storage_key: &str) -> Result<SecretStore> {
192 let maybe_default_key_id = self.fetch_default_key_id().await?;
193
194 if let Some(default_key_id) = maybe_default_key_id {
195 let default_key_id =
196 default_key_id.deserialize_as::<SecretStorageDefaultKeyEventContent>()?;
197
198 let event_type =
199 GlobalAccountDataEventType::SecretStorageKey(default_key_id.key_id.to_owned());
200 let secret_key =
201 self.client.account().fetch_account_data(event_type.to_owned()).await?;
202
203 if let Some(secret_key_content) = secret_key {
204 let event_type = event_type.to_string();
205 let secret_key_content = to_raw_value(&secret_key_content)?;
206
207 let secret_key_content =
208 SecretStorageKeyEventContent::from_parts(&event_type, &secret_key_content)?;
209
210 let key =
211 SecretStorageKey::from_account_data(secret_storage_key, secret_key_content)?;
212
213 Ok(SecretStore { client: self.client.to_owned(), key })
214 } else {
215 Err(SecretStorageError::MissingKeyInfo { key_id: Some(default_key_id.key_id) })
216 }
217 } else {
218 Err(SecretStorageError::MissingKeyInfo { key_id: None })
219 }
220 }
221
222 /// Create a new [`SecretStore`].
223 ///
224 /// The [`SecretStore`] will be protected by a randomly generated key, or
225 /// optionally a passphrase can be provided as well.
226 ///
227 /// In both cases, whether a passphrase was provided or not, the key to open
228 /// the [`SecretStore`] can be obtained using the
229 /// [`SecretStore::secret_storage_key()`] method.
230 ///
231 /// *Note*: This method will set the new secret storage key as the default
232 /// key in the `m.secret_storage.default_key` event. All the known secrets
233 /// will be re-encrypted and uploaded to the homeserver as well. This
234 /// includes the following secrets:
235 ///
236 /// - `m.cross_signing.master`: The master cross-signing key.
237 /// - `m.cross_signing.self_signing`: The self-signing cross-signing key.
238 /// - `m.cross_signing.user_signing`: The user-signing cross-signing key.
239 ///
240 /// # Examples
241 ///
242 /// ```no_run
243 /// # use matrix_sdk::Client;
244 /// # use url::Url;
245 /// # async {
246 /// # let homeserver = Url::parse("http://example.com")?;
247 /// # let client = Client::new(homeserver).await?;
248 /// use ruma::events::secret::request::SecretName;
249 ///
250 /// let secret_store = client
251 /// .encryption()
252 /// .secret_storage()
253 /// .create_secret_store()
254 /// .await?;
255 ///
256 /// let my_secret = "Top secret secret";
257 /// let my_secret_name = SecretName::from("m.treasure");
258 ///
259 /// secret_store.put_secret(my_secret_name, my_secret);
260 ///
261 /// let secret_storage_key = secret_store.secret_storage_key();
262 ///
263 /// println!("Your secret storage key is {secret_storage_key}, save it somewhere safe.");
264 ///
265 /// # anyhow::Ok(()) };
266 /// ```
267 pub fn create_secret_store(&self) -> CreateStore<'_> {
268 CreateStore { secret_storage: self, passphrase: None }
269 }
270
271 /// Run a network request to find if secret storage is set up for this user.
272 pub async fn is_enabled(&self) -> crate::Result<bool> {
273 if let Some(content) = self.fetch_default_key_id().await? {
274 // Since we can't delete account data events, we're going to treat
275 // deserialization failures as secret storage being disabled.
276 Ok(content.deserialize_as::<SecretStorageDefaultKeyEventContent>().is_ok())
277 } else {
278 // No account data event found, must be disabled.
279 Ok(false)
280 }
281 }
282
283 /// Fetch the `m.secret_storage.default_key` event from the server.
284 pub async fn fetch_default_key_id(
285 &self,
286 ) -> crate::Result<Option<Raw<SecretStorageDefaultKeyEventContent>>> {
287 let maybe_default_key_id = self
288 .client
289 .account()
290 .fetch_account_data(GlobalAccountDataEventType::SecretStorageDefaultKey)
291 .await?;
292
293 Ok(maybe_default_key_id.map(|event| event.cast()))
294 }
295}