bitwarden_crypto/keys/
key_connector_key.rs

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