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