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