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