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