Skip to main content

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_slot_ids](crate::key_slot_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, KeySlotId, KeySlotIds};
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/// ([KeySlotId]). These identifiers are user-defined types that contain no key material, which
44/// means 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_slot_ids! {
54///     #[symmetric]
55///     pub enum SymmKeySlotIds {
56///         User,
57///         #[local]
58///         Local(LocalId),
59///     }
60///     #[private]
61///     pub enum PrivateKeySlotIds {
62///         UserPrivate,
63///         #[local]
64///         Local(LocalId),
65///     }
66///     #[signing]
67///     pub enum SigningKeySlotIds {
68///        UserSigning,
69///        #[local]
70///        Local(LocalId),
71///     }
72///     pub Ids => SymmKeySlotIds, PrivateKeySlotIds, SigningKeySlotIds;
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(SymmKeySlotIds::User, SymmetricCryptoKey::make_aes256_cbc_hmac_key());
80///
81/// // Define some data that needs to be encrypted
82/// struct Data(String);
83/// impl IdentifyKey<SymmKeySlotIds> for Data {
84///    fn key_identifier(&self) -> SymmKeySlotIds {
85///        SymmKeySlotIds::User
86///    }
87/// }
88/// impl CompositeEncryptable<Ids, SymmKeySlotIds, EncString> for Data {
89///     fn encrypt_composite(&self, ctx: &mut KeyStoreContext<Ids>, key: SymmKeySlotIds) -> 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: KeySlotIds> {
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: KeySlotIds> 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: KeySlotIds> 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: KeySlotIds> {
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: KeySlotIds> 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: KeySlotIds> 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<
238        Key: KeySlotId,
239        Data: Decryptable<Ids, Key, Output> + IdentifyKey<Key>,
240        Output,
241    >(
242        &self,
243        data: &Data,
244    ) -> Result<Output, crate::CryptoError> {
245        let key = data.key_identifier();
246        data.decrypt(&mut self.context(), key)
247    }
248
249    /// Encrypt a single item using this key store. The key returned by `data.key_identifier()` must
250    /// already be present in the store, otherwise this will return an error.
251    /// This method is not parallelized, and is meant for single item encryption.
252    /// If you need to encrypt multiple items, use `encrypt_list` instead.
253    pub fn encrypt<
254        Key: KeySlotId,
255        Data: CompositeEncryptable<Ids, Key, Output> + IdentifyKey<Key>,
256        Output,
257    >(
258        &self,
259        data: Data,
260    ) -> Result<Output, crate::CryptoError> {
261        let key = data.key_identifier();
262        data.encrypt_composite(&mut self.context(), key)
263    }
264
265    /// Decrypt a list of items using this key store. The keys returned by
266    /// `data[i].key_identifier()` must already be present in the store, otherwise this will
267    /// return an error. This method will try to parallelize the decryption of the items, for
268    /// better performance on large lists.
269    pub fn decrypt_list<
270        Key: KeySlotId,
271        Data: Decryptable<Ids, Key, Output> + IdentifyKey<Key> + Send + Sync,
272        Output: Send + Sync,
273    >(
274        &self,
275        data: &[Data],
276    ) -> Result<Vec<Output>, crate::CryptoError> {
277        let res: Result<Vec<_>, _> = data
278            .par_chunks(batch_chunk_size(data.len()))
279            .map(|chunk| {
280                let mut ctx = self.context();
281
282                let mut result = Vec::with_capacity(chunk.len());
283
284                for item in chunk {
285                    let key = item.key_identifier();
286                    result.push(item.decrypt(&mut ctx, key));
287                    ctx.clear_local();
288                }
289
290                result
291            })
292            .flatten()
293            .collect();
294
295        res
296    }
297
298    /// Decrypt a list of items using this key store, returning a tuple of successful and failed
299    /// items.
300    ///
301    /// # Arguments
302    /// * `data` - The list of items to decrypt.
303    ///
304    /// # Returns
305    /// A tuple containing two vectors: the first vector contains the successfully decrypted items,
306    /// and the second vector contains the original items that failed to decrypt.
307    pub fn decrypt_list_with_failures<
308        'a,
309        Key: KeySlotId,
310        Data: Decryptable<Ids, Key, Output> + IdentifyKey<Key> + Send + Sync + 'a,
311        Output: Send + Sync,
312    >(
313        &self,
314        data: &'a [Data],
315    ) -> (Vec<Output>, Vec<&'a Data>) {
316        let results: (Vec<_>, Vec<_>) = data
317            .par_chunks(batch_chunk_size(data.len()))
318            .flat_map(|chunk| {
319                let mut ctx = self.context();
320
321                chunk
322                    .iter()
323                    .map(|item| {
324                        let result = item
325                            .decrypt(&mut ctx, item.key_identifier())
326                            .map_err(|_| item);
327                        ctx.clear_local();
328                        result
329                    })
330                    .collect::<Vec<_>>()
331            })
332            .partition_map(|result| match result {
333                Ok(output) => Either::Left(output),
334                Err(original_item) => Either::Right(original_item),
335            });
336
337        results
338    }
339
340    /// Encrypt a list of items using this key store. The keys returned by
341    /// `data[i].key_identifier()` must already be present in the store, otherwise this will
342    /// return an error. This method will try to parallelize the encryption of the items, for
343    /// better performance on large lists. This method is not parallelized, and is meant for
344    /// single item encryption.
345    pub fn encrypt_list<
346        Key: KeySlotId,
347        Data: CompositeEncryptable<Ids, Key, Output> + IdentifyKey<Key> + Send + Sync,
348        Output: Send + Sync,
349    >(
350        &self,
351        data: &[Data],
352    ) -> Result<Vec<Output>, crate::CryptoError> {
353        let res: Result<Vec<_>, _> = data
354            .par_chunks(batch_chunk_size(data.len()))
355            .map(|chunk| {
356                let mut ctx = self.context();
357
358                let mut result = Vec::with_capacity(chunk.len());
359
360                for item in chunk {
361                    let key = item.key_identifier();
362                    result.push(item.encrypt_composite(&mut ctx, key));
363                    ctx.clear_local();
364                }
365
366                result
367            })
368            .flatten()
369            .collect();
370
371        res
372    }
373}
374
375/// Calculate the optimal chunk size for parallelizing encryption/decryption operations.
376fn batch_chunk_size(len: usize) -> usize {
377    // In an optimal scenario with no overhead, we would split the data evenly between
378    // all available threads, rounding up to the nearest integer.
379    let items_per_thread = usize::div_ceil(len, rayon::current_num_threads());
380
381    // Because the addition of each chunk has some overhead (e.g. creating a new context, thread
382    // synchronization), we want to split the data into chunks that are large enough to amortize
383    // this overhead, but not too large that we get no benefit from multithreading. We've chosen
384    // a value more or less arbitrarily, but it seems to work well in practice.
385    const MINIMUM_CHUNK_SIZE: usize = 50;
386
387    // As a result, we pick whichever of the two values is larger.
388    usize::max(items_per_thread, MINIMUM_CHUNK_SIZE)
389}
390
391#[cfg(test)]
392pub(crate) mod tests {
393    use crate::{
394        EncString, PrimitiveEncryptable, SymmetricKeyAlgorithm,
395        store::{KeyStore, KeyStoreContext},
396        traits::tests::{TestIds, TestSymmKey},
397    };
398
399    pub struct DataView(pub String, pub TestSymmKey);
400    pub struct Data(pub EncString, pub TestSymmKey);
401
402    impl crate::IdentifyKey<TestSymmKey> for DataView {
403        fn key_identifier(&self) -> TestSymmKey {
404            self.1
405        }
406    }
407
408    impl crate::IdentifyKey<TestSymmKey> for Data {
409        fn key_identifier(&self) -> TestSymmKey {
410            self.1
411        }
412    }
413
414    impl crate::CompositeEncryptable<TestIds, TestSymmKey, Data> for DataView {
415        fn encrypt_composite(
416            &self,
417            ctx: &mut KeyStoreContext<TestIds>,
418            key: TestSymmKey,
419        ) -> Result<Data, crate::CryptoError> {
420            Ok(Data(self.0.encrypt(ctx, key)?, key))
421        }
422    }
423
424    impl crate::Decryptable<TestIds, TestSymmKey, DataView> for Data {
425        fn decrypt(
426            &self,
427            ctx: &mut KeyStoreContext<TestIds>,
428            key: TestSymmKey,
429        ) -> Result<DataView, crate::CryptoError> {
430            Ok(DataView(self.0.decrypt(ctx, key)?, key))
431        }
432    }
433
434    #[test]
435    fn test_multithread_decrypt_keeps_order() {
436        let store: KeyStore<TestIds> = KeyStore::default();
437
438        // Create a bunch of random keys
439        for n in 0..15 {
440            let mut ctx = store.context_mut();
441            let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
442            ctx.persist_symmetric_key(local_key_id, TestSymmKey::A(n))
443                .unwrap();
444        }
445
446        // Create some test data
447        let data: Vec<_> = (0..300usize)
448            .map(|n| DataView(format!("Test {n}"), TestSymmKey::A((n % 15) as u8)))
449            .collect();
450
451        // Encrypt the data
452        let encrypted: Vec<_> = store.encrypt_list(&data).unwrap();
453
454        // Decrypt the data
455        let decrypted: Vec<_> = store.decrypt_list(&encrypted).unwrap();
456
457        // Check that the data is the same, and in the same order as the original
458        for (orig, dec) in data.iter().zip(decrypted.iter()) {
459            assert_eq!(orig.0, dec.0);
460            assert_eq!(orig.1, dec.1);
461        }
462    }
463}