bitwarden_crypto/keys/
key_connector_key.rs

1use std::pin::Pin;
2
3use bitwarden_encoding::B64;
4use generic_array::GenericArray;
5use rand::Rng;
6use typenum::U32;
7
8use crate::{
9    BitwardenLegacyKeyBytes, CryptoError, EncString, KeyDecryptable, SymmetricCryptoKey,
10    keys::utils::stretch_key,
11};
12
13/// Key connector key, used to protect the user key.
14#[derive(Clone)]
15pub struct KeyConnectorKey(pub(super) Pin<Box<GenericArray<u8, U32>>>);
16
17impl KeyConnectorKey {
18    /// Make a new random key for KeyConnector.
19    pub fn make() -> Self {
20        let mut rng = rand::thread_rng();
21        let mut key = Box::pin(GenericArray::<u8, U32>::default());
22
23        rng.fill(key.as_mut_slice());
24        KeyConnectorKey(key)
25    }
26
27    /// Wraps the user key with this key connector key.
28    pub fn encrypt_user_key(
29        &self,
30        user_key: &SymmetricCryptoKey,
31    ) -> crate::error::Result<EncString> {
32        let stretched_key = stretch_key(&self.0)?;
33        let user_key_bytes = user_key.to_encoded();
34        EncString::encrypt_aes256_hmac(user_key_bytes.as_ref(), &stretched_key)
35    }
36
37    /// Unwraps the user key with this key connector key.
38    pub fn decrypt_user_key(
39        &self,
40        user_key: EncString,
41    ) -> crate::error::Result<SymmetricCryptoKey> {
42        let dec: Vec<u8> = match user_key {
43            // Legacy. user_keys were encrypted using `Aes256Cbc_B64` a long time ago. We've since
44            // moved to using `Aes256Cbc_HmacSha256_B64`. However, we still need to support
45            // decrypting these old keys.
46            EncString::Aes256Cbc_B64 { iv, ref data } => {
47                let legacy_key = Box::pin(GenericArray::clone_from_slice(&self.0));
48                crate::aes::decrypt_aes256(&iv, data.clone(), &legacy_key)?
49            }
50            EncString::Aes256Cbc_HmacSha256_B64 { .. } => {
51                let stretched_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(&self.0)?);
52                user_key.decrypt_with_key(&stretched_key)?
53            }
54            _ => {
55                return Err(CryptoError::OperationNotSupported(
56                    crate::error::UnsupportedOperationError::EncryptionNotImplementedForKey,
57                ));
58            }
59        };
60
61        SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(dec))
62    }
63}
64
65impl std::fmt::Debug for KeyConnectorKey {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        f.debug_struct("KeyConnectorKey").finish()
68    }
69}
70
71impl From<KeyConnectorKey> for B64 {
72    fn from(key: KeyConnectorKey) -> Self {
73        B64::from(key.0.as_slice())
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use bitwarden_encoding::B64;
80    use coset::iana::KeyOperation;
81    use rand_chacha::rand_core::SeedableRng;
82
83    use super::KeyConnectorKey;
84    use crate::{BitwardenLegacyKeyBytes, EncString, SymmetricCryptoKey, UserKey};
85
86    const KEY_CONNECTOR_KEY_BYTES: [u8; 32] = [
87        31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138, 167, 69, 167,
88        254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75,
89    ];
90
91    #[test]
92    fn test_make_two_different_keys() {
93        let key1 = KeyConnectorKey::make();
94        let key2 = KeyConnectorKey::make();
95        assert_ne!(key1.0.as_slice(), key2.0.as_slice());
96    }
97
98    #[test]
99    fn test_into_base64() {
100        let key: B64 = KeyConnectorKey(Box::pin(KEY_CONNECTOR_KEY_BYTES.into())).into();
101
102        assert_eq!(
103            "H09o4pZHsVrCUKzREYGEUYqnRaf+lQIbJ8VAKhbDVks=",
104            key.to_string()
105        );
106    }
107
108    #[test]
109    fn test_decrypt_user_key_aes256_cbc() {
110        let key_connector_key = "hvBMMb1t79YssFZkpetYsM3deyVuQv4r88Uj9gvYe08=".to_string();
111        let key_connector_key = SymmetricCryptoKey::try_from(key_connector_key).unwrap();
112        let SymmetricCryptoKey::Aes256CbcKey(key_connector_key) = &key_connector_key else {
113            panic!("Key Connector key is not an Aes256CbcKey");
114        };
115
116        let key_connector_key = KeyConnectorKey(key_connector_key.enc_key.clone());
117
118        let user_key: EncString = "0.tn/heK4HLbbEe+yEkC+kvw==|8QM94f7aVTtjm/bmvRdVxOxiLiiZtHYYO7+oBdjFCkilncesx0iVrXPl+tMKqW+Jo7+FtZdPNsTrL6RdoG7i5QbCRVwK+9010+xm7MTQY8s=".parse().unwrap();
119
120        let decrypted_user_key = key_connector_key.decrypt_user_key(user_key).unwrap();
121        let SymmetricCryptoKey::Aes256CbcHmacKey(user_key_unwrapped) = &decrypted_user_key else {
122            panic!("User key is not an Aes256CbcHmacKey");
123        };
124
125        assert_eq!(
126            user_key_unwrapped.enc_key.as_slice(),
127            [
128                116, 170, 187, 43, 80, 212, 193, 202, 234, 181, 57, 66, 151, 249, 59, 47, 70, 16,
129                57, 4, 170, 78, 85, 241, 152, 232, 91, 57, 9, 87, 209, 245,
130            ]
131        );
132        assert_eq!(
133            user_key_unwrapped.mac_key.as_slice(),
134            [
135                40, 245, 106, 140, 2, 225, 138, 213, 98, 223, 92, 168, 135, 208, 22, 194, 31, 21,
136                178, 252, 203, 198, 35, 174, 53, 218, 254, 151, 235, 57, 7, 98,
137            ]
138        );
139    }
140
141    #[test]
142    fn test_encrypt_decrypt_user_key_aes256_cbc_hmac() {
143        let rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]);
144
145        let key_connector_key = KeyConnectorKey(Box::pin(KEY_CONNECTOR_KEY_BYTES.into()));
146
147        let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key_internal(rng);
148        let wrapped_user_key = key_connector_key.encrypt_user_key(&user_key).unwrap();
149        let user_key = UserKey::new(user_key);
150
151        let decrypted_user_key = key_connector_key
152            .decrypt_user_key(wrapped_user_key)
153            .unwrap();
154
155        let SymmetricCryptoKey::Aes256CbcHmacKey(user_key_unwrapped) = &decrypted_user_key else {
156            panic!("User key is not an Aes256CbcHmacKey");
157        };
158
159        assert_eq!(
160            user_key_unwrapped.enc_key.as_slice(),
161            [
162                62, 0, 239, 47, 137, 95, 64, 214, 127, 91, 184, 232, 31, 9, 165, 161, 44, 132, 14,
163                195, 206, 154, 127, 59, 24, 27, 225, 136, 239, 113, 26, 30
164            ]
165        );
166        assert_eq!(
167            user_key_unwrapped.mac_key.as_slice(),
168            [
169                152, 76, 225, 114, 185, 33, 111, 65, 159, 68, 83, 103, 69, 109, 86, 25, 49, 74, 66,
170                163, 218, 134, 176, 1, 56, 123, 253, 184, 14, 12, 254, 66
171            ]
172        );
173
174        assert_eq!(
175            decrypted_user_key, user_key.0,
176            "Decrypted key doesn't match user key"
177        );
178    }
179
180    #[test]
181    fn test_encrypt_decrypt_user_key_xchacha20_poly1305() {
182        let key_connector_key = KeyConnectorKey(Box::pin(KEY_CONNECTOR_KEY_BYTES.into()));
183
184        let user_key_b64: B64 = "pQEEAlDib+JxbqMBlcd3KTUesbufAzoAARFvBIQDBAUGIFggt79surJXmqhPhYuuqi9ZyPfieebmtw2OsmN5SDrb4yUB".parse()
185            .unwrap();
186        let user_key =
187            SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(&user_key_b64)).unwrap();
188        let wrapped_user_key = key_connector_key.encrypt_user_key(&user_key).unwrap();
189        let user_key = UserKey::new(user_key);
190
191        let decrypted_user_key = key_connector_key
192            .decrypt_user_key(wrapped_user_key)
193            .unwrap();
194
195        let SymmetricCryptoKey::XChaCha20Poly1305Key(user_key_unwrapped) = &decrypted_user_key
196        else {
197            panic!("User key is not an XChaCha20Poly1305Key");
198        };
199
200        assert_eq!(
201            user_key_unwrapped.enc_key.as_slice(),
202            [
203                183, 191, 108, 186, 178, 87, 154, 168, 79, 133, 139, 174, 170, 47, 89, 200, 247,
204                226, 121, 230, 230, 183, 13, 142, 178, 99, 121, 72, 58, 219, 227, 37
205            ]
206        );
207        assert_eq!(
208            user_key_unwrapped.key_id.as_slice(),
209            [
210                226, 111, 226, 113, 110, 163, 1, 149, 199, 119, 41, 53, 30, 177, 187, 159
211            ]
212        );
213        assert_eq!(
214            user_key_unwrapped.supported_operations,
215            [
216                KeyOperation::Encrypt,
217                KeyOperation::Decrypt,
218                KeyOperation::WrapKey,
219                KeyOperation::UnwrapKey
220            ]
221        );
222
223        assert_eq!(
224            decrypted_user_key, user_key.0,
225            "Decrypted key doesn't match user key"
226        );
227    }
228}