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#[derive(Clone)]
17pub struct KeyConnectorKey(pub(super) Pin<Box<GenericArray<u8, U32>>>);
18
19impl KeyConnectorKey {
20 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 #[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 #[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 #[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 #[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 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}