1use std::pin::Pin;
2
3use bitwarden_encoding::B64;
4use hybrid_array::Array;
5use rand::RngExt;
6use typenum::U32;
7
8use super::{
9 kdf::{Kdf, KdfDerivedKeyMaterial},
10 utils::stretch_key,
11};
12use crate::{
13 BitwardenLegacyKeyBytes, CryptoError, EncString, KeyDecryptable, Result, SymmetricCryptoKey,
14 UserKey,
15 hazmat::symmetric_encryption::Aes256Cbc,
16 util::{self},
17};
18
19#[allow(missing_docs)]
20#[derive(Copy, Clone)]
21#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
22#[cfg_attr(feature = "dangerous-crypto-debug", derive(Debug))]
23pub enum HashPurpose {
24 ServerAuthorization = 1,
25 LocalAuthorization = 2,
26}
27
28#[allow(missing_docs)]
33#[cfg_attr(feature = "dangerous-crypto-debug", derive(Debug))]
34pub enum MasterKey {
35 KdfKey(KdfDerivedKeyMaterial),
36 KeyConnectorKey(Pin<Box<Array<u8, U32>>>),
37}
38
39impl MasterKey {
40 pub(crate) fn new(key: KdfDerivedKeyMaterial) -> Self {
41 Self::KdfKey(key)
42 }
43
44 pub fn generate(mut rng: impl rand::Rng) -> Self {
46 let mut key = Box::pin(Array::<u8, U32>::default());
47
48 rng.fill(key.as_mut_slice());
49 Self::KeyConnectorKey(key)
50 }
51
52 fn inner_bytes(&self) -> &Pin<Box<Array<u8, U32>>> {
53 match self {
54 Self::KdfKey(key) => &key.0,
55 Self::KeyConnectorKey(key) => key,
56 }
57 }
58
59 #[cfg_attr(
67 feature = "dangerous-crypto-debug",
68 allow(unknown_lints, tracing_instrument)
69 )]
70 #[cfg_attr(feature = "dangerous-crypto-debug", tracing::instrument)]
71 pub fn derive(password: &str, email: &str, kdf: &Kdf) -> Result<Self, CryptoError> {
72 Ok(KdfDerivedKeyMaterial::derive(password, email, kdf)?.into())
73 }
74
75 #[cfg_attr(
77 feature = "dangerous-crypto-debug",
78 allow(unknown_lints, tracing_instrument)
79 )]
80 #[cfg_attr(feature = "dangerous-crypto-debug", tracing::instrument)]
81 pub fn derive_master_key_hash(&self, password: &[u8], purpose: HashPurpose) -> B64 {
82 let hash = util::pbkdf2(self.inner_bytes().as_slice(), password, purpose as u32);
83
84 hash.as_slice().into()
85 }
86
87 pub fn make_user_key(&self) -> Result<(UserKey, EncString)> {
89 make_user_key(bitwarden_random::rng(), self)
90 }
91
92 #[cfg_attr(
94 feature = "dangerous-crypto-debug",
95 allow(unknown_lints, tracing_instrument)
96 )]
97 #[cfg_attr(feature = "dangerous-crypto-debug", tracing::instrument(err))]
98 #[cfg_attr(
99 not(feature = "dangerous-crypto-debug"),
100 bitwarden_logging::instrument(err)
101 )]
102 pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result<EncString> {
103 encrypt_user_key(self.inner_bytes(), user_key)
104 }
105
106 #[cfg_attr(
108 feature = "dangerous-crypto-debug",
109 allow(unknown_lints, tracing_instrument)
110 )]
111 #[cfg_attr(feature = "dangerous-crypto-debug", tracing::instrument(err))]
112 #[cfg_attr(
113 not(feature = "dangerous-crypto-debug"),
114 bitwarden_logging::instrument(err)
115 )]
116 pub fn decrypt_user_key(&self, user_key: EncString) -> Result<SymmetricCryptoKey> {
117 decrypt_user_key(self.inner_bytes(), user_key)
118 }
119
120 #[allow(missing_docs)]
121 pub fn to_base64(&self) -> B64 {
122 B64::from(self.inner_bytes().as_slice())
123 }
124}
125
126impl TryFrom<Vec<u8>> for MasterKey {
127 type Error = CryptoError;
128
129 fn try_from(value: Vec<u8>) -> Result<Self> {
130 let material = KdfDerivedKeyMaterial(Box::pin(
131 Array::<u8, U32>::try_from(value).map_err(|_| CryptoError::InvalidKey)?,
132 ));
133 Ok(Self::new(material))
134 }
135}
136
137impl From<KdfDerivedKeyMaterial> for MasterKey {
138 fn from(key: KdfDerivedKeyMaterial) -> Self {
139 Self::new(key)
140 }
141}
142
143impl TryFrom<&SymmetricCryptoKey> for MasterKey {
144 type Error = CryptoError;
145
146 fn try_from(value: &SymmetricCryptoKey) -> Result<Self> {
147 match value {
148 SymmetricCryptoKey::Aes256CbcKey(key) => {
149 Ok(Self::KdfKey(KdfDerivedKeyMaterial(key.enc_key.clone())))
150 }
151 _ => Err(CryptoError::InvalidKey),
152 }
153 }
154}
155
156pub(super) fn encrypt_user_key(
158 master_key: &Pin<Box<Array<u8, U32>>>,
159 user_key: &SymmetricCryptoKey,
160) -> Result<EncString> {
161 let stretched_master_key = stretch_key(master_key);
162 let user_key_bytes = user_key.to_encoded();
163 EncString::encrypt_aes256_hmac(user_key_bytes.as_ref(), &stretched_master_key)
164}
165
166pub(super) fn decrypt_user_key(
168 key: &Pin<Box<Array<u8, U32>>>,
169 user_key: EncString,
170) -> Result<SymmetricCryptoKey> {
171 let dec: Vec<u8> = match user_key {
172 EncString::Aes256Cbc_B64 { iv, ref data } => {
176 Aes256Cbc::decrypt(&iv, data, &(**key).into()).map_err(|_| CryptoError::Decrypt)?
177 }
178 EncString::Aes256Cbc_HmacSha256_B64 { .. } => {
179 let stretched_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(key));
180 user_key.decrypt_with_key(&stretched_key)?
181 }
182 EncString::Cose_Encrypt0_B64 { .. } => {
183 return Err(CryptoError::OperationNotSupported(
184 crate::error::UnsupportedOperationError::DecryptionNotImplementedForKey,
185 ));
186 }
187 };
188
189 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(dec))
190}
191
192fn make_user_key(
199 rng: impl rand::CryptoRng,
200 master_key: &MasterKey,
201) -> Result<(UserKey, EncString)> {
202 let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key_internal(rng);
203 let protected = master_key.encrypt_user_key(&user_key)?;
204 Ok((UserKey::new(user_key), protected))
205}
206
207#[cfg(test)]
208mod tests {
209 use std::num::NonZeroU32;
210
211 use rand::SeedableRng;
212
213 use super::{HashPurpose, Kdf, MasterKey, make_user_key};
214 use crate::{
215 EncString, SymmetricCryptoKey,
216 keys::{master_key::KdfDerivedKeyMaterial, symmetric_crypto_key::derive_symmetric_key},
217 };
218
219 #[test]
220 fn test_password_hash_pbkdf2() {
221 let password = "asdfasdf";
222 let salts = [
223 "[email protected]",
224 "[email protected]",
225 " [email protected]",
226 ];
227 let kdf = Kdf::PBKDF2 {
228 iterations: NonZeroU32::new(100_000).unwrap(),
229 };
230
231 for salt in salts.iter() {
232 let master_key: MasterKey = KdfDerivedKeyMaterial::derive(password, salt, &kdf)
233 .unwrap()
234 .into();
235
236 assert_eq!(
237 "wmyadRMyBZOH7P/a/ucTCbSghKgdzDpPqUnu/DAVtSw=",
238 String::from(
239 master_key.derive_master_key_hash(
240 password.as_bytes(),
241 HashPurpose::ServerAuthorization
242 )
243 ),
244 );
245 }
246 }
247
248 #[test]
249 fn test_password_hash_argon2id() {
250 let password = "asdfasdf";
251 let salt = "test_salt";
252 let kdf = Kdf::Argon2id {
253 iterations: NonZeroU32::new(4).unwrap(),
254 memory: NonZeroU32::new(32).unwrap(),
255 parallelism: NonZeroU32::new(2).unwrap(),
256 };
257
258 let master_key: MasterKey = KdfDerivedKeyMaterial::derive(password, salt, &kdf)
259 .unwrap()
260 .into();
261
262 assert_eq!(
263 "PR6UjYmjmppTYcdyTiNbAhPJuQQOmynKbdEl1oyi/iQ=",
264 master_key
265 .derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization)
266 .to_string(),
267 );
268 }
269
270 #[test]
271 fn test_make_user_key() {
272 let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]);
273
274 let master_key: MasterKey = KdfDerivedKeyMaterial(Box::pin(
275 [
276 31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138, 167,
277 69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75,
278 ]
279 .into(),
280 ))
281 .into();
282
283 let (user_key, protected) = make_user_key(&mut rng, &master_key).unwrap();
284 let SymmetricCryptoKey::Aes256CbcHmacKey(user_key_unwrapped) = &user_key.0 else {
285 panic!("User key is not an Aes256CbcHmacKey");
286 };
287
288 assert_eq!(
289 user_key_unwrapped.enc_key.as_slice(),
290 [
291 62, 0, 239, 47, 137, 95, 64, 214, 127, 91, 184, 232, 31, 9, 165, 161, 44, 132, 14,
292 195, 206, 154, 127, 59, 24, 27, 225, 136, 239, 113, 26, 30
293 ]
294 );
295 assert_eq!(
296 user_key_unwrapped.mac_key.as_slice(),
297 [
298 152, 76, 225, 114, 185, 33, 111, 65, 159, 68, 83, 103, 69, 109, 86, 25, 49, 74, 66,
299 163, 218, 134, 176, 1, 56, 123, 253, 184, 14, 12, 254, 66
300 ]
301 );
302
303 let decrypted = master_key.decrypt_user_key(protected).unwrap();
305
306 assert_eq!(
307 decrypted, user_key.0,
308 "Decrypted key doesn't match user key"
309 );
310 }
311
312 #[test]
313 fn test_make_user_key2() {
314 let kdf_material = KdfDerivedKeyMaterial(derive_symmetric_key("test1").enc_key.clone());
315 let master_key = MasterKey::KdfKey(kdf_material);
316
317 let user_key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test2"));
318
319 let encrypted = master_key.encrypt_user_key(&user_key).unwrap();
320 let decrypted = master_key.decrypt_user_key(encrypted).unwrap();
321
322 assert_eq!(decrypted, user_key, "Decrypted key doesn't match user key");
323 }
324
325 #[test]
326 fn test_decrypt_user_key_aes_cbc256_b64() {
327 let password = "asdfasdfasdf";
328 let salt = "[email protected]";
329 let kdf = Kdf::PBKDF2 {
330 iterations: NonZeroU32::new(600_000).unwrap(),
331 };
332
333 let master_key: MasterKey = KdfDerivedKeyMaterial::derive(password, salt, &kdf)
334 .unwrap()
335 .into();
336
337 let user_key: EncString = "0.8UClLa8IPE1iZT7chy5wzQ==|6PVfHnVk5S3XqEtQemnM5yb4JodxmPkkWzmDRdfyHtjORmvxqlLX40tBJZ+CKxQWmS8tpEB5w39rbgHg/gqs0haGdZG4cPbywsgGzxZ7uNI=".parse().unwrap();
338
339 let decrypted = master_key.decrypt_user_key(user_key).unwrap();
340 let SymmetricCryptoKey::Aes256CbcHmacKey(decrypted) = &decrypted else {
341 panic!("Decrypted key is not an Aes256CbcHmacKey");
342 };
343
344 assert_eq!(
345 decrypted.enc_key.as_slice(),
346 [
347 12, 95, 151, 203, 37, 4, 236, 67, 137, 97, 90, 58, 6, 127, 242, 28, 209, 168, 125,
348 29, 118, 24, 213, 44, 117, 202, 2, 115, 132, 165, 125, 148
349 ]
350 );
351 assert_eq!(
352 decrypted.mac_key.as_slice(),
353 [
354 186, 215, 234, 137, 24, 169, 227, 29, 218, 57, 180, 237, 73, 91, 189, 51, 253, 26,
355 17, 52, 226, 4, 134, 75, 194, 208, 178, 133, 128, 224, 140, 167
356 ]
357 );
358 }
359
360 #[test]
361 fn test_decrypt_cbc256() {
362 let master_key = "hvBMMb1t79YssFZkpetYsM3deyVuQv4r88Uj9gvYe08=".to_string();
363 let master_key = SymmetricCryptoKey::try_from(master_key).unwrap();
364 let SymmetricCryptoKey::Aes256CbcKey(master_key) = &master_key else {
365 panic!("Incorrect key type for test used");
366 };
367
368 let master_key = MasterKey::KdfKey(KdfDerivedKeyMaterial(master_key.enc_key.clone()));
369
370 let enc_str = "0.tn/heK4HLbbEe+yEkC+kvw==|8QM94f7aVTtjm/bmvRdVxOxiLiiZtHYYO7+oBdjFCkilncesx0iVrXPl+tMKqW+Jo7+FtZdPNsTrL6RdoG7i5QbCRVwK+9010+xm7MTQY8s=";
371 let enc_string: EncString = enc_str.parse().unwrap();
372
373 let decrypted = master_key.decrypt_user_key(enc_string).unwrap();
374 let SymmetricCryptoKey::Aes256CbcHmacKey(decrypted) = &decrypted else {
375 panic!("Failed to decrypt Aes256CbcHmacKey from Aes256CbcKey encrypted EncString")
376 };
377
378 assert_eq!(
379 decrypted.enc_key.as_slice(),
380 [
381 116, 170, 187, 43, 80, 212, 193, 202, 234, 181, 57, 66, 151, 249, 59, 47, 70, 16,
382 57, 4, 170, 78, 85, 241, 152, 232, 91, 57, 9, 87, 209, 245,
383 ]
384 );
385 assert_eq!(
386 decrypted.mac_key.as_slice(),
387 [
388 40, 245, 106, 140, 2, 225, 138, 213, 98, 223, 92, 168, 135, 208, 22, 194, 31, 21,
389 178, 252, 203, 198, 35, 174, 53, 218, 254, 151, 235, 57, 7, 98,
390 ]
391 );
392 }
393}