Skip to main content

bitwarden_crypto/safe/key_hierarchy/
key_encryption_key.rs

1use crate::{KeySlotIds, KeyStoreContext, SymmetricKeyAlgorithm};
2
3/// A key-encryption-key (KEK): a key that can be used to encrypt other keys, and shared between
4/// users. It MAY be re-used for multiple encrypt operations.
5///
6/// See the `key_hierarchy` module documentation for its place in the key hierarchy.
7pub struct KeyEncryptionKey;
8
9impl KeyEncryptionKey {
10    /// Generates a fresh key-encryption-key, stores it in the key store context, and returns its
11    /// key id. Key material never leaves the key store.
12    pub fn make<Ids: KeySlotIds>(ctx: &mut KeyStoreContext<Ids>) -> Ids::Symmetric {
13        // XAES-256-GCM is used because a KEK is reused: the same key wraps many other keys over its
14        // lifetime, and each wrap draws a fresh random nonce under that one key. Plain AES-256-GCM
15        // has only a 96-bit nonce, so across the many encryptions a long-lived KEK performs, two
16        // random nonces could eventually collide -- and a nonce reuse in GCM is catastrophic (it
17        // leaks the authentication subkey and the XOR of the affected plaintexts). XAES-256-GCM
18        // extends the nonce to 192 bits, making random-nonce collisions negligible even under heavy
19        // reuse.
20        ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm)
21    }
22
23    /// Returns whether the symmetric key `key_id` refers to uses an algorithm permitted for a
24    /// key-encryption-key. Returns `false` if the key is missing or uses an unsupported algorithm.
25    pub(crate) fn is_key_algorithm_valid<Ids: KeySlotIds>(
26        ctx: &KeyStoreContext<Ids>,
27        key_id: Ids::Symmetric,
28    ) -> bool {
29        let Ok(algorithm) = ctx.get_symmetric_key_algorithm(key_id) else {
30            return false;
31        };
32        matches!(algorithm, SymmetricKeyAlgorithm::XAes256Gcm)
33    }
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39    use crate::{KeyStore, SymmetricCryptoKey, traits::tests::TestIds};
40
41    #[test]
42    fn make_generates_xaes256_gcm_key_in_context() {
43        let key_store = KeyStore::<TestIds>::default();
44        let mut ctx = key_store.context_mut();
45
46        let kek_id = KeyEncryptionKey::make(&mut ctx);
47
48        let key = ctx.get_symmetric_key(kek_id).expect("KEK should be stored");
49        assert!(matches!(key, SymmetricCryptoKey::XAes256GcmKey(_)));
50    }
51
52    #[test]
53    fn make_generates_distinct_keys() {
54        let key_store = KeyStore::<TestIds>::default();
55        let mut ctx = key_store.context_mut();
56
57        let first = KeyEncryptionKey::make(&mut ctx);
58        let second = KeyEncryptionKey::make(&mut ctx);
59
60        ctx.assert_symmetric_keys_not_equal(first, second);
61    }
62
63    #[test]
64    fn is_key_algorithm_valid_accepts_kek_algorithms() {
65        let key_store = KeyStore::<TestIds>::default();
66        let mut ctx = key_store.context_mut();
67
68        // We will add support for AES-256-CBC-HMAC in the future, to migrate to safe quicker.
69        #[allow(clippy::single_element_loop)]
70        for algorithm in [SymmetricKeyAlgorithm::XAes256Gcm] {
71            let key_id = ctx.make_symmetric_key(algorithm);
72            assert!(KeyEncryptionKey::is_key_algorithm_valid(&ctx, key_id));
73        }
74    }
75
76    #[test]
77    fn is_key_algorithm_valid_rejects_non_kek_algorithms() {
78        let key_store = KeyStore::<TestIds>::default();
79        let mut ctx = key_store.context_mut();
80
81        for algorithm in [
82            SymmetricKeyAlgorithm::Aes256Gcm,
83            SymmetricKeyAlgorithm::XChaCha20Poly1305,
84            SymmetricKeyAlgorithm::Aes256CbcHmac,
85        ] {
86            let key_id = ctx.make_symmetric_key(algorithm);
87            assert!(!KeyEncryptionKey::is_key_algorithm_valid(&ctx, key_id));
88        }
89    }
90}