bitwarden_crypto/store/mod.rs
1//!
2//! This module contains all the necessary parts to create an in-memory key store that can be used
3//! to securely store key and use them for encryption/decryption operations.
4//!
5//! ## Organization
6//!
7//! ### Key Identifiers
8//! To avoid having to pass key materials over the crate boundaries, the key store API uses key
9//! identifiers in its API. These key identifiers are user-defined types that contain no key
10//! material, and are used to uniquely identify each key in the store. The key store doesn't specify
11//! how these traits should be implemented, but we recommend using `enums`, and we provide an
12//! optional macro ([key_ids](crate::key_ids)) that makes it easier to define them.
13//!
14//! ### Key Store
15//! [KeyStore] is a thread-safe in-memory key store and the main entry point for using this module.
16//! It provides functionality to encrypt and decrypt data using the keys stored in the store. The
17//! store is designed to be used by a single user and should not be shared between users.
18//!
19//! ### Key Store Context
20//! From a [KeyStore], you can also create an instance of [KeyStoreContext], which initializes a
21//! temporary context-local key store for encryption/decryption operations that require the use of
22//! per-item keys (like cipher keys or send keys, for example). Any keys stored in the context-local
23//! store will be cleared when the context is dropped.
24
25use std::sync::{Arc, RwLock};
26
27use rayon::{iter::Either, prelude::*};
28
29use crate::{CompositeEncryptable, Decryptable, IdentifyKey, KeyId, KeyIds};
30
31mod backend;
32mod context;
33
34use backend::{StoreBackend, create_store};
35use context::GlobalKeys;
36pub use context::KeyStoreContext;
37
38mod key_rotation;
39pub use key_rotation::*;
40
41/// An in-memory key store that provides a safe and secure way to store keys and use them for
42/// encryption/decryption operations. The store API is designed to work only on key identifiers
43/// ([KeyId]). These identifiers are user-defined types that contain no key material, which means
44/// the API users don't have to worry about accidentally leaking keys.
45///
46/// Each store is designed to be used by a single user and should not be shared between users, but
47/// the store itself is thread safe and can be cloned to share between threads.
48///
49/// ```rust
50/// # use bitwarden_crypto::*;
51///
52/// // We need to define our own key identifier types. We provide a macro to make this easier.
53/// key_ids! {
54/// #[symmetric]
55/// pub enum SymmKeyId {
56/// User,
57/// #[local]
58/// Local(LocalId),
59/// }
60/// #[private]
61/// pub enum PrivateKeyId {
62/// UserPrivate,
63/// #[local]
64/// Local(LocalId),
65/// }
66/// #[signing]
67/// pub enum SigningKeyId {
68/// UserSigning,
69/// #[local]
70/// Local(LocalId),
71/// }
72/// pub Ids => SymmKeyId, PrivateKeyId, SigningKeyId;
73/// }
74///
75/// // Initialize the store and insert a test key
76/// let store: KeyStore<Ids> = KeyStore::default();
77///
78/// #[allow(deprecated)]
79/// store.context_mut().set_symmetric_key(SymmKeyId::User, SymmetricCryptoKey::make_aes256_cbc_hmac_key());
80///
81/// // Define some data that needs to be encrypted
82/// struct Data(String);
83/// impl IdentifyKey<SymmKeyId> for Data {
84/// fn key_identifier(&self) -> SymmKeyId {
85/// SymmKeyId::User
86/// }
87/// }
88/// impl CompositeEncryptable<Ids, SymmKeyId, EncString> for Data {
89/// fn encrypt_composite(&self, ctx: &mut KeyStoreContext<Ids>, key: SymmKeyId) -> Result<EncString, CryptoError> {
90/// self.0.encrypt(ctx, key)
91/// }
92/// }
93///
94/// // Encrypt the data
95/// let decrypted = Data("Hello, World!".to_string());
96/// let encrypted = store.encrypt(decrypted).unwrap();
97/// ```
98pub struct KeyStore<Ids: KeyIds> {
99 // We use an Arc<> to make it easier to pass this store around, as we can
100 // clone it instead of passing references
101 inner: Arc<RwLock<KeyStoreInner<Ids>>>,
102}
103
104// Manually implement Clone to avoid requiring Ids: Clone
105impl<Ids: KeyIds> Clone for KeyStore<Ids> {
106 fn clone(&self) -> Self {
107 KeyStore {
108 inner: Arc::clone(&self.inner),
109 }
110 }
111}
112
113/// [KeyStore] contains sensitive data, provide a dummy [Debug] implementation.
114impl<Ids: KeyIds> std::fmt::Debug for KeyStore<Ids> {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 f.debug_struct("KeyStore").finish()
117 }
118}
119
120struct KeyStoreInner<Ids: KeyIds> {
121 symmetric_keys: Box<dyn StoreBackend<Ids::Symmetric>>,
122 private_keys: Box<dyn StoreBackend<Ids::Private>>,
123 signing_keys: Box<dyn StoreBackend<Ids::Signing>>,
124 security_state_version: u64,
125}
126
127/// Create a new key store with the best available implementation for the current platform.
128impl<Ids: KeyIds> Default for KeyStore<Ids> {
129 fn default() -> Self {
130 Self {
131 inner: Arc::new(RwLock::new(KeyStoreInner {
132 symmetric_keys: create_store(),
133 private_keys: create_store(),
134 signing_keys: create_store(),
135 security_state_version: 1,
136 })),
137 }
138 }
139}
140
141impl<Ids: KeyIds> KeyStore<Ids> {
142 /// Clear all keys from the store. This can be used to clear all keys from memory in case of
143 /// lock/logout, and is equivalent to destroying the store and creating a new one.
144 pub fn clear(&self) {
145 let mut keys = self.inner.write().expect("RwLock is poisoned");
146 keys.symmetric_keys.clear();
147 keys.private_keys.clear();
148 keys.signing_keys.clear();
149 }
150
151 /// Sets the security state version for this store.
152 pub fn set_security_state_version(&self, version: u64) {
153 let mut data = self.inner.write().expect("RwLock is poisoned");
154 data.security_state_version = version;
155 }
156
157 /// Initiate an encryption/decryption context. This context will have read only access to the
158 /// global keys, and will have its own local key stores with read/write access. This
159 /// context-local store will be cleared when the context is dropped.
160 ///
161 /// If you are only looking to encrypt or decrypt items, you should implement
162 /// [CompositeEncryptable]/[Decryptable] and use the [KeyStore::encrypt], [KeyStore::decrypt],
163 /// [KeyStore::encrypt_list] and [KeyStore::decrypt_list] methods instead.
164 ///
165 /// The current implementation of context only clears the keys automatically when the context is
166 /// dropped, and not between operations. This means that if you are using the same context
167 /// for multiple operations, you may want to clear it manually between them. If possible, we
168 /// recommend using [KeyStore::encrypt_list] and [KeyStore::decrypt_list] instead.
169 ///
170 /// [KeyStoreContext] is not [Send] or [Sync] and should not be shared between threads. Note
171 /// that this can also be problematic in async code, and you should take care to ensure that
172 /// you're not holding references to the context across await points, as that would cause the
173 /// future to also not be [Send].
174 ///
175 /// Some other possible use cases for this API and alternative recommendations are:
176 /// - Decrypting or encrypting multiple [Decryptable] or [CompositeEncryptable] items while
177 /// sharing any local keys. This is not recommended as it can lead to fragile and flaky
178 /// decryption/encryption operations. We recommend any local keys to be used only in the
179 /// context of a single [CompositeEncryptable] or [Decryptable] implementation. In the future
180 /// we might enforce this.
181 /// - Obtaining the key material directly. We strongly recommend against doing this as it can
182 /// lead to key material being leaked, but we need to support it for backwards compatibility.
183 /// If you want to access the key material to encrypt it or derive a new key from it, we
184 /// provide functions for that:
185 /// - [KeyStoreContext::wrap_symmetric_key]
186 /// - [KeyStoreContext::derive_shareable_key]
187 pub fn context(&'_ self) -> KeyStoreContext<'_, Ids> {
188 let data = self.inner.read().expect("RwLock is poisoned");
189 let security_state_version = data.security_state_version;
190 KeyStoreContext {
191 global_keys: GlobalKeys::ReadOnly(data),
192 local_symmetric_keys: create_store(),
193 local_private_keys: create_store(),
194 local_signing_keys: create_store(),
195 security_state_version,
196 _phantom: std::marker::PhantomData,
197 }
198 }
199
200 /// <div class="warning">
201 /// This is an advanced API, use with care and ONLY when needing to modify the global keys.
202 ///
203 /// The same pitfalls as [Self::context] apply here, but with the added risk of accidentally
204 /// modifying the global keys and leaving the store in an inconsistent state.
205 /// If you still need to use it, make sure you read this documentation to understand how to use
206 /// it safely. </div>
207 ///
208 /// Initiate an encryption/decryption context. This context will have MUTABLE access to the
209 /// global keys, and will have its own local key stores with read/write access. This
210 /// context-local store will be cleared up when the context is dropped.
211 ///
212 /// The only supported use case for this API is initializing the store with the user's symetric
213 /// and private keys, and setting the organization keys. This method will be marked as
214 /// `pub(crate)` in the future, once we have a safe API for key initialization and updating.
215 ///
216 /// [KeyStoreContext] is not [Send] or [Sync] and should not be shared between threads. Note
217 /// that this can also be problematic in async code, and you should take care to ensure that
218 /// you're not holding references to the context across await points, as that would cause the
219 /// future to also not be [Send].
220 pub fn context_mut(&'_ self) -> KeyStoreContext<'_, Ids> {
221 let inner = self.inner.write().expect("RwLock is poisoned");
222 let security_state_version = inner.security_state_version;
223 KeyStoreContext {
224 global_keys: GlobalKeys::ReadWrite(inner),
225 local_symmetric_keys: create_store(),
226 local_private_keys: create_store(),
227 local_signing_keys: create_store(),
228 security_state_version,
229 _phantom: std::marker::PhantomData,
230 }
231 }
232
233 /// Decript a single item using this key store. The key returned by `data.key_identifier()` must
234 /// already be present in the store, otherwise this will return an error.
235 /// This method is not parallelized, and is meant for single item decryption.
236 /// If you need to decrypt multiple items, use `decrypt_list` instead.
237 pub fn decrypt<Key: KeyId, Data: Decryptable<Ids, Key, Output> + IdentifyKey<Key>, Output>(
238 &self,
239 data: &Data,
240 ) -> Result<Output, crate::CryptoError> {
241 let key = data.key_identifier();
242 data.decrypt(&mut self.context(), key)
243 }
244
245 /// Encrypt a single item using this key store. The key returned by `data.key_identifier()` must
246 /// already be present in the store, otherwise this will return an error.
247 /// This method is not parallelized, and is meant for single item encryption.
248 /// If you need to encrypt multiple items, use `encrypt_list` instead.
249 pub fn encrypt<
250 Key: KeyId,
251 Data: CompositeEncryptable<Ids, Key, Output> + IdentifyKey<Key>,
252 Output,
253 >(
254 &self,
255 data: Data,
256 ) -> Result<Output, crate::CryptoError> {
257 let key = data.key_identifier();
258 data.encrypt_composite(&mut self.context(), key)
259 }
260
261 /// Decrypt a list of items using this key store. The keys returned by
262 /// `data[i].key_identifier()` must already be present in the store, otherwise this will
263 /// return an error. This method will try to parallelize the decryption of the items, for
264 /// better performance on large lists.
265 pub fn decrypt_list<
266 Key: KeyId,
267 Data: Decryptable<Ids, Key, Output> + IdentifyKey<Key> + Send + Sync,
268 Output: Send + Sync,
269 >(
270 &self,
271 data: &[Data],
272 ) -> Result<Vec<Output>, crate::CryptoError> {
273 let res: Result<Vec<_>, _> = data
274 .par_chunks(batch_chunk_size(data.len()))
275 .map(|chunk| {
276 let mut ctx = self.context();
277
278 let mut result = Vec::with_capacity(chunk.len());
279
280 for item in chunk {
281 let key = item.key_identifier();
282 result.push(item.decrypt(&mut ctx, key));
283 ctx.clear_local();
284 }
285
286 result
287 })
288 .flatten()
289 .collect();
290
291 res
292 }
293
294 /// Decrypt a list of items using this key store, returning a tuple of successful and failed
295 /// items.
296 ///
297 /// # Arguments
298 /// * `data` - The list of items to decrypt.
299 ///
300 /// # Returns
301 /// A tuple containing two vectors: the first vector contains the successfully decrypted items,
302 /// and the second vector contains the original items that failed to decrypt.
303 pub fn decrypt_list_with_failures<
304 'a,
305 Key: KeyId,
306 Data: Decryptable<Ids, Key, Output> + IdentifyKey<Key> + Send + Sync + 'a,
307 Output: Send + Sync,
308 >(
309 &self,
310 data: &'a [Data],
311 ) -> (Vec<Output>, Vec<&'a Data>) {
312 let results: (Vec<_>, Vec<_>) = data
313 .par_chunks(batch_chunk_size(data.len()))
314 .flat_map(|chunk| {
315 let mut ctx = self.context();
316
317 chunk
318 .iter()
319 .map(|item| {
320 let result = item
321 .decrypt(&mut ctx, item.key_identifier())
322 .map_err(|_| item);
323 ctx.clear_local();
324 result
325 })
326 .collect::<Vec<_>>()
327 })
328 .partition_map(|result| match result {
329 Ok(output) => Either::Left(output),
330 Err(original_item) => Either::Right(original_item),
331 });
332
333 results
334 }
335
336 /// Encrypt a list of items using this key store. The keys returned by
337 /// `data[i].key_identifier()` must already be present in the store, otherwise this will
338 /// return an error. This method will try to parallelize the encryption of the items, for
339 /// better performance on large lists. This method is not parallelized, and is meant for
340 /// single item encryption.
341 pub fn encrypt_list<
342 Key: KeyId,
343 Data: CompositeEncryptable<Ids, Key, Output> + IdentifyKey<Key> + Send + Sync,
344 Output: Send + Sync,
345 >(
346 &self,
347 data: &[Data],
348 ) -> Result<Vec<Output>, crate::CryptoError> {
349 let res: Result<Vec<_>, _> = data
350 .par_chunks(batch_chunk_size(data.len()))
351 .map(|chunk| {
352 let mut ctx = self.context();
353
354 let mut result = Vec::with_capacity(chunk.len());
355
356 for item in chunk {
357 let key = item.key_identifier();
358 result.push(item.encrypt_composite(&mut ctx, key));
359 ctx.clear_local();
360 }
361
362 result
363 })
364 .flatten()
365 .collect();
366
367 res
368 }
369}
370
371/// Calculate the optimal chunk size for parallelizing encryption/decryption operations.
372fn batch_chunk_size(len: usize) -> usize {
373 // In an optimal scenario with no overhead, we would split the data evenly between
374 // all available threads, rounding up to the nearest integer.
375 let items_per_thread = usize::div_ceil(len, rayon::current_num_threads());
376
377 // Because the addition of each chunk has some overhead (e.g. creating a new context, thread
378 // synchronization), we want to split the data into chunks that are large enough to amortize
379 // this overhead, but not too large that we get no benefit from multithreading. We've chosen
380 // a value more or less arbitrarily, but it seems to work well in practice.
381 const MINIMUM_CHUNK_SIZE: usize = 50;
382
383 // As a result, we pick whichever of the two values is larger.
384 usize::max(items_per_thread, MINIMUM_CHUNK_SIZE)
385}
386
387#[cfg(test)]
388pub(crate) mod tests {
389 use crate::{
390 EncString, PrimitiveEncryptable, SymmetricKeyAlgorithm,
391 store::{KeyStore, KeyStoreContext},
392 traits::tests::{TestIds, TestSymmKey},
393 };
394
395 pub struct DataView(pub String, pub TestSymmKey);
396 pub struct Data(pub EncString, pub TestSymmKey);
397
398 impl crate::IdentifyKey<TestSymmKey> for DataView {
399 fn key_identifier(&self) -> TestSymmKey {
400 self.1
401 }
402 }
403
404 impl crate::IdentifyKey<TestSymmKey> for Data {
405 fn key_identifier(&self) -> TestSymmKey {
406 self.1
407 }
408 }
409
410 impl crate::CompositeEncryptable<TestIds, TestSymmKey, Data> for DataView {
411 fn encrypt_composite(
412 &self,
413 ctx: &mut KeyStoreContext<TestIds>,
414 key: TestSymmKey,
415 ) -> Result<Data, crate::CryptoError> {
416 Ok(Data(self.0.encrypt(ctx, key)?, key))
417 }
418 }
419
420 impl crate::Decryptable<TestIds, TestSymmKey, DataView> for Data {
421 fn decrypt(
422 &self,
423 ctx: &mut KeyStoreContext<TestIds>,
424 key: TestSymmKey,
425 ) -> Result<DataView, crate::CryptoError> {
426 Ok(DataView(self.0.decrypt(ctx, key)?, key))
427 }
428 }
429
430 #[test]
431 fn test_multithread_decrypt_keeps_order() {
432 let store: KeyStore<TestIds> = KeyStore::default();
433
434 // Create a bunch of random keys
435 for n in 0..15 {
436 let mut ctx = store.context_mut();
437 let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
438 ctx.persist_symmetric_key(local_key_id, TestSymmKey::A(n))
439 .unwrap();
440 }
441
442 // Create some test data
443 let data: Vec<_> = (0..300usize)
444 .map(|n| DataView(format!("Test {n}"), TestSymmKey::A((n % 15) as u8)))
445 .collect();
446
447 // Encrypt the data
448 let encrypted: Vec<_> = store.encrypt_list(&data).unwrap();
449
450 // Decrypt the data
451 let decrypted: Vec<_> = store.decrypt_list(&encrypted).unwrap();
452
453 // Check that the data is the same, and in the same order as the original
454 for (orig, dec) in data.iter().zip(decrypted.iter()) {
455 assert_eq!(orig.0, dec.0);
456 assert_eq!(orig.1, dec.1);
457 }
458 }
459}