Skip to main content

bitwarden_crypto/keys/
key_connector_key.rs

1use std::pin::Pin;
2
3use bitwarden_api_key_connector::models::user_key_response_model::UserKeyResponseModel;
4use bitwarden_encoding::B64;
5use generic_array::GenericArray;
6use rand::Rng;
7use tracing::instrument;
8use typenum::U32;
9
10use crate::{
11    BitwardenLegacyKeyBytes, CryptoError, EncString, KeyDecryptable, KeyIds, KeyStoreContext,
12    SymmetricCryptoKey, keys::utils::stretch_key,
13};
14
15/// Key connector key, used to protect the user key.
16#[derive(Clone)]
17pub struct KeyConnectorKey(pub(super) Pin<Box<GenericArray<u8, U32>>>);
18
19impl KeyConnectorKey {
20    /// Make a new random key for KeyConnector.
21    pub fn make() -> Self {
22        let mut rng = rand::thread_rng();
23        let mut key = Box::pin(GenericArray::<u8, U32>::default());
24
25        rng.fill(key.as_mut_slice());
26        KeyConnectorKey(key)
27    }
28
29    /// Wraps (encrypts) a user key from the key store using this key connector key.
30    ///
31    /// The user key identified by `user_key_id` is read from the context and encrypted.
32    #[cfg_attr(feature = "dangerous-crypto-debug", instrument(skip(ctx), err))]
33    #[cfg_attr(not(feature = "dangerous-crypto-debug"), instrument(skip_all, err))]
34    pub fn wrap_user_key<Ids: KeyIds>(
35        &self,
36        user_key_id: Ids::Symmetric,
37        ctx: &KeyStoreContext<Ids>,
38    ) -> crate::error::Result<EncString> {
39        #[allow(deprecated)]
40        let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?;
41        self.encrypt_user_key(user_key)
42    }
43
44    /// Unwraps (decrypts) a user key and stores it in the key store context.
45    ///
46    /// Returns the local key identifier for the unwrapped user key.
47    #[cfg_attr(feature = "dangerous-crypto-debug", instrument(skip(ctx), err))]
48    #[cfg_attr(not(feature = "dangerous-crypto-debug"), instrument(skip_all, err))]
49    pub fn unwrap_user_key<Ids: KeyIds>(
50        &self,
51        wrapped_user_key: EncString,
52        ctx: &mut KeyStoreContext<Ids>,
53    ) -> crate::error::Result<Ids::Symmetric> {
54        let user_key = self.decrypt_user_key(wrapped_user_key)?;
55        Ok(ctx.add_local_symmetric_key(user_key))
56    }
57
58    /// Wraps the user key with this key connector key.
59    #[cfg_attr(feature = "dangerous-crypto-debug", instrument(err))]
60    #[cfg_attr(not(feature = "dangerous-crypto-debug"), instrument(skip_all, err))]
61    pub fn encrypt_user_key(
62        &self,
63        user_key: &SymmetricCryptoKey,
64    ) -> crate::error::Result<EncString> {
65        let stretched_key = stretch_key(&self.0);
66        let user_key_bytes = user_key.to_encoded();
67        EncString::encrypt_aes256_hmac(user_key_bytes.as_ref(), &stretched_key)
68    }
69
70    /// Unwraps the user key with this key connector key.
71    #[cfg_attr(feature = "dangerous-crypto-debug", instrument(err))]
72    #[cfg_attr(not(feature = "dangerous-crypto-debug"), instrument(skip_all, err))]
73    pub fn decrypt_user_key(
74        &self,
75        user_key: EncString,
76    ) -> crate::error::Result<SymmetricCryptoKey> {
77        let dec: Vec<u8> = match user_key {
78            // Legacy. user_keys were encrypted using `Aes256Cbc_B64` a long time ago. We've since
79            // moved to using `Aes256Cbc_HmacSha256_B64`. However, we still need to support
80            // decrypting these old keys.
81            EncString::Aes256Cbc_B64 { iv, ref data } => {
82                let legacy_key = Box::pin(GenericArray::clone_from_slice(&self.0));
83                crate::aes::decrypt_aes256(&iv, data.clone(), &legacy_key)
84                    .map_err(|_| CryptoError::Decrypt)?
85            }
86            EncString::Aes256Cbc_HmacSha256_B64 { .. } => {
87                let stretched_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(&self.0));
88                user_key.decrypt_with_key(&stretched_key)?
89            }
90            _ => {
91                return Err(CryptoError::OperationNotSupported(
92                    crate::error::UnsupportedOperationError::EncryptionNotImplementedForKey,
93                ));
94            }
95        };
96
97        SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(dec))
98    }
99}
100
101impl std::fmt::Debug for KeyConnectorKey {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        let mut debug_struct = f.debug_struct("KeyConnectorKey");
104        #[cfg(feature = "dangerous-crypto-debug")]
105        debug_struct.field("key", &self.0.as_slice());
106        debug_struct.finish()
107    }
108}
109
110impl From<KeyConnectorKey> for B64 {
111    fn from(key: KeyConnectorKey) -> Self {
112        B64::from(key.0.as_slice())
113    }
114}
115
116impl TryFrom<UserKeyResponseModel> for KeyConnectorKey {
117    type Error = CryptoError;
118
119    fn try_from(s: UserKeyResponseModel) -> Result<Self, Self::Error> {
120        let bytes = B64::try_from(s.key).map_err(|_| CryptoError::InvalidKey)?;
121
122        if bytes.as_bytes().len() != 32 {
123            return Err(CryptoError::InvalidKeyLen);
124        }
125
126        Ok(KeyConnectorKey(Box::pin(GenericArray::clone_from_slice(
127            bytes.as_bytes(),
128        ))))
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use bitwarden_encoding::B64;
135    use coset::iana::KeyOperation;
136    use rand_chacha::rand_core::SeedableRng;
137
138    use super::KeyConnectorKey;
139    use crate::{
140        BitwardenLegacyKeyBytes, EncString, SymmetricCryptoKey, UserKey,
141        store::KeyStore,
142        traits::tests::{TestIds, TestSymmKey},
143    };
144
145    const KEY_CONNECTOR_KEY_BYTES: [u8; 32] = [
146        31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138, 167, 69, 167,
147        254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75,
148    ];
149
150    #[test]
151    fn test_make_two_different_keys() {
152        let key1 = KeyConnectorKey::make();
153        let key2 = KeyConnectorKey::make();
154        assert_ne!(key1.0.as_slice(), key2.0.as_slice());
155    }
156
157    #[test]
158    fn test_into_base64() {
159        let key: B64 = KeyConnectorKey(Box::pin(KEY_CONNECTOR_KEY_BYTES.into())).into();
160
161        assert_eq!(
162            "H09o4pZHsVrCUKzREYGEUYqnRaf+lQIbJ8VAKhbDVks=",
163            key.to_string()
164        );
165    }
166
167    #[test]
168    fn test_decrypt_user_key_aes256_cbc() {
169        let key_connector_key = "hvBMMb1t79YssFZkpetYsM3deyVuQv4r88Uj9gvYe08=".to_string();
170        let key_connector_key = SymmetricCryptoKey::try_from(key_connector_key).unwrap();
171        let SymmetricCryptoKey::Aes256CbcKey(key_connector_key) = &key_connector_key else {
172            panic!("Key Connector key is not an Aes256CbcKey");
173        };
174
175        let key_connector_key = KeyConnectorKey(key_connector_key.enc_key.clone());
176
177        let user_key: EncString = "0.tn/heK4HLbbEe+yEkC+kvw==|8QM94f7aVTtjm/bmvRdVxOxiLiiZtHYYO7+oBdjFCkilncesx0iVrXPl+tMKqW+Jo7+FtZdPNsTrL6RdoG7i5QbCRVwK+9010+xm7MTQY8s=".parse().unwrap();
178
179        let decrypted_user_key = key_connector_key.decrypt_user_key(user_key).unwrap();
180        let SymmetricCryptoKey::Aes256CbcHmacKey(user_key_unwrapped) = &decrypted_user_key else {
181            panic!("User key is not an Aes256CbcHmacKey");
182        };
183
184        assert_eq!(
185            user_key_unwrapped.enc_key.as_slice(),
186            [
187                116, 170, 187, 43, 80, 212, 193, 202, 234, 181, 57, 66, 151, 249, 59, 47, 70, 16,
188                57, 4, 170, 78, 85, 241, 152, 232, 91, 57, 9, 87, 209, 245,
189            ]
190        );
191        assert_eq!(
192            user_key_unwrapped.mac_key.as_slice(),
193            [
194                40, 245, 106, 140, 2, 225, 138, 213, 98, 223, 92, 168, 135, 208, 22, 194, 31, 21,
195                178, 252, 203, 198, 35, 174, 53, 218, 254, 151, 235, 57, 7, 98,
196            ]
197        );
198    }
199
200    #[test]
201    fn test_encrypt_decrypt_user_key_aes256_cbc_hmac() {
202        let rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]);
203
204        let key_connector_key = KeyConnectorKey(Box::pin(KEY_CONNECTOR_KEY_BYTES.into()));
205
206        let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key_internal(rng);
207        let wrapped_user_key = key_connector_key.encrypt_user_key(&user_key).unwrap();
208        let user_key = UserKey::new(user_key);
209
210        let decrypted_user_key = key_connector_key
211            .decrypt_user_key(wrapped_user_key)
212            .unwrap();
213
214        let SymmetricCryptoKey::Aes256CbcHmacKey(user_key_unwrapped) = &decrypted_user_key else {
215            panic!("User key is not an Aes256CbcHmacKey");
216        };
217
218        assert_eq!(
219            user_key_unwrapped.enc_key.as_slice(),
220            [
221                62, 0, 239, 47, 137, 95, 64, 214, 127, 91, 184, 232, 31, 9, 165, 161, 44, 132, 14,
222                195, 206, 154, 127, 59, 24, 27, 225, 136, 239, 113, 26, 30
223            ]
224        );
225        assert_eq!(
226            user_key_unwrapped.mac_key.as_slice(),
227            [
228                152, 76, 225, 114, 185, 33, 111, 65, 159, 68, 83, 103, 69, 109, 86, 25, 49, 74, 66,
229                163, 218, 134, 176, 1, 56, 123, 253, 184, 14, 12, 254, 66
230            ]
231        );
232
233        assert_eq!(
234            decrypted_user_key, user_key.0,
235            "Decrypted key doesn't match user key"
236        );
237    }
238
239    #[test]
240    fn test_encrypt_decrypt_user_key_xchacha20_poly1305() {
241        let key_connector_key = KeyConnectorKey(Box::pin(KEY_CONNECTOR_KEY_BYTES.into()));
242
243        let user_key_b64: B64 = "pQEEAlDib+JxbqMBlcd3KTUesbufAzoAARFvBIQDBAUGIFggt79surJXmqhPhYuuqi9ZyPfieebmtw2OsmN5SDrb4yUB".parse()
244            .unwrap();
245        let user_key =
246            SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(&user_key_b64)).unwrap();
247        let wrapped_user_key = key_connector_key.encrypt_user_key(&user_key).unwrap();
248        let user_key = UserKey::new(user_key);
249
250        let decrypted_user_key = key_connector_key
251            .decrypt_user_key(wrapped_user_key)
252            .unwrap();
253
254        let SymmetricCryptoKey::XChaCha20Poly1305Key(user_key_unwrapped) = &decrypted_user_key
255        else {
256            panic!("User key is not an XChaCha20Poly1305Key");
257        };
258
259        assert_eq!(
260            user_key_unwrapped.enc_key.as_slice(),
261            [
262                183, 191, 108, 186, 178, 87, 154, 168, 79, 133, 139, 174, 170, 47, 89, 200, 247,
263                226, 121, 230, 230, 183, 13, 142, 178, 99, 121, 72, 58, 219, 227, 37
264            ]
265        );
266        assert_eq!(
267            user_key_unwrapped.key_id.as_slice(),
268            [
269                226, 111, 226, 113, 110, 163, 1, 149, 199, 119, 41, 53, 30, 177, 187, 159
270            ]
271        );
272        assert_eq!(
273            user_key_unwrapped.supported_operations,
274            [
275                KeyOperation::Encrypt,
276                KeyOperation::Decrypt,
277                KeyOperation::WrapKey,
278                KeyOperation::UnwrapKey
279            ]
280        );
281
282        assert_eq!(
283            decrypted_user_key, user_key.0,
284            "Decrypted key doesn't match user key"
285        );
286    }
287
288    #[test]
289    fn test_wrap_unwrap_user_key_aes256_cbc_hmac() {
290        let rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]);
291        let key_connector_key = KeyConnectorKey(Box::pin(KEY_CONNECTOR_KEY_BYTES.into()));
292
293        let store: KeyStore<TestIds> = KeyStore::default();
294        let mut ctx = store.context_mut();
295
296        let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key_internal(rng);
297        #[allow(deprecated)]
298        ctx.set_symmetric_key(TestSymmKey::A(0), user_key.clone())
299            .expect("set_symmetric_key should succeed");
300
301        let wrapped = key_connector_key
302            .wrap_user_key(TestSymmKey::A(0), &ctx)
303            .expect("wrap_user_key should succeed");
304
305        let unwrapped_id = key_connector_key
306            .unwrap_user_key(wrapped, &mut ctx)
307            .expect("unwrap_user_key should succeed");
308
309        #[allow(deprecated)]
310        let unwrapped = ctx
311            .dangerous_get_symmetric_key(unwrapped_id)
312            .expect("unwrapped key should be in context");
313
314        assert_eq!(&user_key, unwrapped);
315    }
316}