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