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