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