bitwarden_crypto/store/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
//!
//! This module contains all the necessary parts to create an in-memory key store that can be used
//! to securely store key and use them for encryption/decryption operations.
//!
//! ## Organization
//!
//! ### Key Identifiers
//! To avoid having to pass key materials over the crate boundaries, the key store API uses key
//! identifiers in its API. These key identifiers are user-defined types that contain no key
//! material, and are used to uniquely identify each key in the store. The key store doesn't specify
//! how these traits should be implemented, but we recommend using `enums`, and we provide an
//! optional macro ([key_ids](crate::key_ids)) that makes it easier to define them.
//!
//! ### Key Store
//! [KeyStore] is a thread-safe in-memory key store and the main entry point for using this module.
//! It provides functionality to encrypt and decrypt data using the keys stored in the store. The
//! store is designed to be used by a single user and should not be shared between users.
//!
//! ### Key Store Context
//! From a [KeyStore], you can also create an instance of [KeyStoreContext], which initializes a
//! temporary context-local key store for encryption/decryption operations that require the use of
//! per-item keys (like cipher keys or send keys, for example). Any keys stored in the context-local
//! store will be cleared when the context is dropped.

use std::sync::{Arc, RwLock};

use rayon::prelude::*;

use crate::{Decryptable, Encryptable, IdentifyKey, KeyId, KeyIds};

mod backend;
mod context;

use backend::{create_store, StoreBackend};
use context::GlobalKeys;
pub use context::KeyStoreContext;

/// An in-memory key store that provides a safe and secure way to store keys and use them for
/// encryption/decryption operations. The store API is designed to work only on key identifiers
/// ([KeyId]). These identifiers are user-defined types that contain no key material, which means
/// the API users don't have to worry about accidentally leaking keys.
///
/// Each store is designed to be used by a single user and should not be shared between users, but
/// the store itself is thread safe and can be cloned to share between threads.
///
/// ```rust
/// # use bitwarden_crypto::*;
///
/// // We need to define our own key identifier types. We provide a macro to make this easier.
/// key_ids! {
///     #[symmetric]
///     pub enum SymmKeyId {
///         User,
///         #[local]
///         Local(&'static str)
///     }
///     #[asymmetric]
///     pub enum AsymmKeyId {
///         UserPrivate,
///     }
///     pub Ids => SymmKeyId, AsymmKeyId;
/// }
///
/// // Initialize the store and insert a test key
/// let store: KeyStore<Ids> = KeyStore::default();
///
/// #[allow(deprecated)]
/// store.context_mut().set_symmetric_key(SymmKeyId::User, SymmetricCryptoKey::generate(rand::thread_rng()));
///
/// // Define some data that needs to be encrypted
/// struct Data(String);
/// impl IdentifyKey<SymmKeyId> for Data {
///    fn key_identifier(&self) -> SymmKeyId {
///        SymmKeyId::User
///    }
/// }
/// impl Encryptable<Ids, SymmKeyId, EncString> for Data {
///     fn encrypt(&self, ctx: &mut KeyStoreContext<Ids>, key: SymmKeyId) -> Result<EncString, CryptoError> {
///         self.0.encrypt(ctx, key)
///     }
/// }
///
/// // Encrypt the data
/// let decrypted = Data("Hello, World!".to_string());
/// let encrypted = store.encrypt(decrypted).unwrap();
/// ```
#[derive(Clone)]
pub struct KeyStore<Ids: KeyIds> {
    // We use an Arc<> to make it easier to pass this store around, as we can
    // clone it instead of passing references
    inner: Arc<RwLock<KeyStoreInner<Ids>>>,
}

/// [KeyStore] contains sensitive data, provide a dummy [Debug] implementation.
impl<Ids: KeyIds> std::fmt::Debug for KeyStore<Ids> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("KeyStore").finish()
    }
}

struct KeyStoreInner<Ids: KeyIds> {
    symmetric_keys: Box<dyn StoreBackend<Ids::Symmetric>>,
    asymmetric_keys: Box<dyn StoreBackend<Ids::Asymmetric>>,
}

/// Create a new key store with the best available implementation for the current platform.
impl<Ids: KeyIds> Default for KeyStore<Ids> {
    fn default() -> Self {
        Self {
            inner: Arc::new(RwLock::new(KeyStoreInner {
                symmetric_keys: create_store(),
                asymmetric_keys: create_store(),
            })),
        }
    }
}

impl<Ids: KeyIds> KeyStore<Ids> {
    /// Clear all keys from the store. This can be used to clear all keys from memory in case of
    /// lock/logout, and is equivalent to destroying the store and creating a new one.
    pub fn clear(&self) {
        let mut keys = self.inner.write().expect("RwLock is poisoned");
        keys.symmetric_keys.clear();
        keys.asymmetric_keys.clear();
    }

    /// <div class="warning">
    /// This is an advanced API, use with care. If you still need to use it, make sure you read this
    /// documentation to understand how to use it safely. </div>
    ///
    /// Initiate an encryption/decryption context. This context will have read only access to the
    /// global keys, and will have its own local key stores with read/write access. This
    /// context-local store will be cleared up when the context is dropped.
    ///
    /// Some possible use cases for this API and alternative recommendations are:
    /// - Decrypting one or more [crate::EncString]s directly. This was the pattern before the
    ///   introduction of  the [Encryptable] and [Decryptable] traits, but is now considered
    ///   obsolete. We recommend wrapping the values in a struct that implements these traits
    ///   instead, and then using [KeyStore::encrypt], [KeyStore::decrypt], [KeyStore::encrypt_list]
    ///   and [KeyStore::decrypt_list].
    /// - Decrypting or encrypting multiple [Decryptable] or [Encryptable] items while sharing any
    ///   local keys. This is not recommended as it can lead to fragile and flaky
    ///   decryption/encryption operations. We recommend any local keys to be used only in the
    ///   context of a single [Encryptable] or [Decryptable] implementation. In the future we might
    ///   enforce this.
    /// - Obtaining the key material directly. We strongly recommend against doing this as it can
    ///   lead to key material being leaked, but we need to support it for backwards compatibility.
    ///   If you want to access the key material to encrypt it or derive a new key from it, we
    ///   provide functions for that:
    ///     - [KeyStoreContext::encrypt_symmetric_key_with_symmetric_key]
    ///     - [KeyStoreContext::encrypt_symmetric_key_with_asymmetric_key]
    ///     - [KeyStoreContext::encrypt_asymmetric_key_with_asymmetric_key]
    ///     - [KeyStoreContext::derive_shareable_key]
    /// - Some other minor and safe operations, like checking if a key exists. This is not exposed
    ///   in the [KeyStore] directly as we haven't seen many use cases for it, but we can add it if
    ///   needed.
    ///
    /// One of the pitfalls of the current implementations is that keys stored in the context-local
    /// store only get cleared automatically when the context is dropped, and not between
    /// operations. This means that if you are using the same context for multiple operations,
    /// you may want to clear it manually between them.
    pub fn context(&'_ self) -> KeyStoreContext<'_, Ids> {
        KeyStoreContext {
            global_keys: GlobalKeys::ReadOnly(self.inner.read().expect("RwLock is poisoned")),
            local_symmetric_keys: create_store(),
            local_asymmetric_keys: create_store(),
            _phantom: std::marker::PhantomData,
        }
    }

    /// <div class="warning">
    /// This is an advanced API, use with care and ONLY when needing to modify the global keys.
    ///
    /// The same pitfalls as [Self::context] apply here, but with the added risk of accidentally
    /// modifying the global keys and leaving the store in an inconsistent state.
    /// If you still need to use it, make sure you read this documentation to understand how to use
    /// it safely. </div>
    ///
    /// Initiate an encryption/decryption context. This context will have MUTABLE access to the
    /// global keys, and will have its own local key stores with read/write access. This
    /// context-local store will be cleared up when the context is dropped.
    ///
    /// The only supported use case for this API is initializing the store with the user's symetric
    /// and private keys, and setting the organization keys. This method will be marked as
    /// `pub(crate)` in the future, once we have a safe API for key initialization and updating.
    pub fn context_mut(&'_ self) -> KeyStoreContext<'_, Ids> {
        KeyStoreContext {
            global_keys: GlobalKeys::ReadWrite(self.inner.write().expect("RwLock is poisoned")),
            local_symmetric_keys: create_store(),
            local_asymmetric_keys: create_store(),
            _phantom: std::marker::PhantomData,
        }
    }

    /// Decript a single item using this key store. The key returned by `data.key_identifier()` must
    /// already be present in the store, otherwise this will return an error.
    /// This method is not parallelized, and is meant for single item decryption.
    /// If you need to decrypt multiple items, use `decrypt_list` instead.
    pub fn decrypt<Key: KeyId, Data: Decryptable<Ids, Key, Output> + IdentifyKey<Key>, Output>(
        &self,
        data: &Data,
    ) -> Result<Output, crate::CryptoError> {
        let key = data.key_identifier();
        data.decrypt(&mut self.context(), key)
    }

    /// Encrypt a single item using this key store. The key returned by `data.key_identifier()` must
    /// already be present in the store, otherwise this will return an error.
    /// This method is not parallelized, and is meant for single item encryption.
    /// If you need to encrypt multiple items, use `encrypt_list` instead.
    pub fn encrypt<Key: KeyId, Data: Encryptable<Ids, Key, Output> + IdentifyKey<Key>, Output>(
        &self,
        data: Data,
    ) -> Result<Output, crate::CryptoError> {
        let key = data.key_identifier();
        data.encrypt(&mut self.context(), key)
    }

    /// Decrypt a list of items using this key store. The keys returned by
    /// `data[i].key_identifier()` must already be present in the store, otherwise this will
    /// return an error. This method will try to parallelize the decryption of the items, for
    /// better performance on large lists.
    pub fn decrypt_list<
        Key: KeyId,
        Data: Decryptable<Ids, Key, Output> + IdentifyKey<Key> + Send + Sync,
        Output: Send + Sync,
    >(
        &self,
        data: &[Data],
    ) -> Result<Vec<Output>, crate::CryptoError> {
        let res: Result<Vec<_>, _> = data
            .par_chunks(batch_chunk_size(data.len()))
            .map(|chunk| {
                let mut ctx = self.context();

                let mut result = Vec::with_capacity(chunk.len());

                for item in chunk {
                    let key = item.key_identifier();
                    result.push(item.decrypt(&mut ctx, key));
                    ctx.clear_local();
                }

                result
            })
            .flatten()
            .collect();

        res
    }

    /// Encrypt a list of items using this key store. The keys returned by
    /// `data[i].key_identifier()` must already be present in the store, otherwise this will
    /// return an error. This method will try to parallelize the encryption of the items, for
    /// better performance on large lists. This method is not parallelized, and is meant for
    /// single item encryption.
    pub fn encrypt_list<
        Key: KeyId,
        Data: Encryptable<Ids, Key, Output> + IdentifyKey<Key> + Send + Sync,
        Output: Send + Sync,
    >(
        &self,
        data: &[Data],
    ) -> Result<Vec<Output>, crate::CryptoError> {
        let res: Result<Vec<_>, _> = data
            .par_chunks(batch_chunk_size(data.len()))
            .map(|chunk| {
                let mut ctx = self.context();

                let mut result = Vec::with_capacity(chunk.len());

                for item in chunk {
                    let key = item.key_identifier();
                    result.push(item.encrypt(&mut ctx, key));
                    ctx.clear_local();
                }

                result
            })
            .flatten()
            .collect();

        res
    }
}

/// Calculate the optimal chunk size for parallelizing encryption/decryption operations.
fn batch_chunk_size(len: usize) -> usize {
    // In an optimal scenario with no overhead, we would split the data evenly between
    // all available threads, rounding up to the nearest integer.
    let items_per_thread = usize::div_ceil(len, rayon::current_num_threads());

    // Because the addition of each chunk has some overhead (e.g. creating a new context, thread
    // synchronization), we want to split the data into chunks that are large enough to amortize
    // this overhead, but not too large that we get no benefit from multithreading. We've chosen
    // a value more or less arbitrarily, but it seems to work well in practice.
    const MINIMUM_CHUNK_SIZE: usize = 50;

    // As a result, we pick whichever of the two values is larger.
    usize::max(items_per_thread, MINIMUM_CHUNK_SIZE)
}

#[cfg(test)]
pub(crate) mod tests {
    use crate::{
        store::{KeyStore, KeyStoreContext},
        traits::tests::{TestIds, TestSymmKey},
        EncString, SymmetricCryptoKey,
    };

    pub struct DataView(pub String, pub TestSymmKey);
    pub struct Data(pub EncString, pub TestSymmKey);

    impl crate::IdentifyKey<TestSymmKey> for DataView {
        fn key_identifier(&self) -> TestSymmKey {
            self.1
        }
    }

    impl crate::IdentifyKey<TestSymmKey> for Data {
        fn key_identifier(&self) -> TestSymmKey {
            self.1
        }
    }

    impl crate::Encryptable<TestIds, TestSymmKey, Data> for DataView {
        fn encrypt(
            &self,
            ctx: &mut KeyStoreContext<TestIds>,
            key: TestSymmKey,
        ) -> Result<Data, crate::CryptoError> {
            Ok(Data(self.0.encrypt(ctx, key)?, key))
        }
    }

    impl crate::Decryptable<TestIds, TestSymmKey, DataView> for Data {
        fn decrypt(
            &self,
            ctx: &mut KeyStoreContext<TestIds>,
            key: TestSymmKey,
        ) -> Result<DataView, crate::CryptoError> {
            Ok(DataView(self.0.decrypt(ctx, key)?, key))
        }
    }

    #[test]
    fn test_multithread_decrypt_keeps_order() {
        let mut rng = rand::thread_rng();
        let store: KeyStore<TestIds> = KeyStore::default();

        // Create a bunch of random keys
        for n in 0..15 {
            #[allow(deprecated)]
            store
                .context_mut()
                .set_symmetric_key(TestSymmKey::A(n), SymmetricCryptoKey::generate(&mut rng))
                .unwrap();
        }

        // Create some test data
        let data: Vec<_> = (0..300usize)
            .map(|n| DataView(format!("Test {}", n), TestSymmKey::A((n % 15) as u8)))
            .collect();

        // Encrypt the data
        let encrypted: Vec<_> = store.encrypt_list(&data).unwrap();

        // Decrypt the data
        let decrypted: Vec<_> = store.decrypt_list(&encrypted).unwrap();

        // Check that the data is the same, and in the same order as the original
        for (orig, dec) in data.iter().zip(decrypted.iter()) {
            assert_eq!(orig.0, dec.0);
            assert_eq!(orig.1, dec.1);
        }
    }
}