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