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