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 util::{self},
16};
17
18#[allow(missing_docs)]
19#[derive(Copy, Clone)]
20#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
21#[cfg_attr(feature = "dangerous-crypto-debug", derive(Debug))]
22pub enum HashPurpose {
23 ServerAuthorization = 1,
24 LocalAuthorization = 2,
25}
26
27#[allow(missing_docs)]
32#[cfg_attr(feature = "dangerous-crypto-debug", derive(Debug))]
33pub enum MasterKey {
34 KdfKey(KdfDerivedKeyMaterial),
35 KeyConnectorKey(Pin<Box<Array<u8, U32>>>),
36}
37
38impl MasterKey {
39 pub(crate) fn new(key: KdfDerivedKeyMaterial) -> Self {
40 Self::KdfKey(key)
41 }
42
43 pub fn generate(mut rng: impl rand::Rng) -> Self {
45 let mut key = Box::pin(Array::<u8, U32>::default());
46
47 rng.fill(key.as_mut_slice());
48 Self::KeyConnectorKey(key)
49 }
50
51 fn inner_bytes(&self) -> &Pin<Box<Array<u8, U32>>> {
52 match self {
53 Self::KdfKey(key) => &key.0,
54 Self::KeyConnectorKey(key) => key,
55 }
56 }
57
58 #[cfg_attr(
66 feature = "dangerous-crypto-debug",
67 allow(unknown_lints, tracing_instrument)
68 )]
69 #[cfg_attr(feature = "dangerous-crypto-debug", tracing::instrument)]
70 pub fn derive(password: &str, email: &str, kdf: &Kdf) -> Result<Self, CryptoError> {
71 Ok(KdfDerivedKeyMaterial::derive(password, email, kdf)?.into())
72 }
73
74 #[cfg_attr(
76 feature = "dangerous-crypto-debug",
77 allow(unknown_lints, tracing_instrument)
78 )]
79 #[cfg_attr(feature = "dangerous-crypto-debug", tracing::instrument)]
80 pub fn derive_master_key_hash(&self, password: &[u8], purpose: HashPurpose) -> B64 {
81 let hash = util::pbkdf2(self.inner_bytes().as_slice(), password, purpose as u32);
82
83 hash.as_slice().into()
84 }
85
86 pub fn make_user_key(&self) -> Result<(UserKey, EncString)> {
88 make_user_key(bitwarden_random::rng(), self)
89 }
90
91 #[cfg_attr(
93 feature = "dangerous-crypto-debug",
94 allow(unknown_lints, tracing_instrument)
95 )]
96 #[cfg_attr(feature = "dangerous-crypto-debug", tracing::instrument(err))]
97 #[cfg_attr(
98 not(feature = "dangerous-crypto-debug"),
99 bitwarden_logging::instrument(err)
100 )]
101 pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result<EncString> {
102 encrypt_user_key(self.inner_bytes(), user_key)
103 }
104
105 #[cfg_attr(
107 feature = "dangerous-crypto-debug",
108 allow(unknown_lints, tracing_instrument)
109 )]
110 #[cfg_attr(feature = "dangerous-crypto-debug", tracing::instrument(err))]
111 #[cfg_attr(
112 not(feature = "dangerous-crypto-debug"),
113 bitwarden_logging::instrument(err)
114 )]
115 pub fn decrypt_user_key(&self, user_key: EncString) -> Result<SymmetricCryptoKey> {
116 decrypt_user_key(self.inner_bytes(), user_key)
117 }
118
119 #[allow(missing_docs)]
120 pub fn to_base64(&self) -> B64 {
121 B64::from(self.inner_bytes().as_slice())
122 }
123}
124
125impl TryFrom<Vec<u8>> for MasterKey {
126 type Error = CryptoError;
127
128 fn try_from(value: Vec<u8>) -> Result<Self> {
129 let material = KdfDerivedKeyMaterial(Box::pin(
130 Array::<u8, U32>::try_from(value).map_err(|_| CryptoError::InvalidKey)?,
131 ));
132 Ok(Self::new(material))
133 }
134}
135
136impl From<KdfDerivedKeyMaterial> for MasterKey {
137 fn from(key: KdfDerivedKeyMaterial) -> Self {
138 Self::new(key)
139 }
140}
141
142impl TryFrom<&SymmetricCryptoKey> for MasterKey {
143 type Error = CryptoError;
144
145 fn try_from(value: &SymmetricCryptoKey) -> Result<Self> {
146 match value {
147 SymmetricCryptoKey::Aes256CbcKey(key) => {
148 Ok(Self::KdfKey(KdfDerivedKeyMaterial(key.enc_key.clone())))
149 }
150 _ => Err(CryptoError::InvalidKey),
151 }
152 }
153}
154
155pub(super) fn encrypt_user_key(
157 master_key: &Pin<Box<Array<u8, U32>>>,
158 user_key: &SymmetricCryptoKey,
159) -> Result<EncString> {
160 let stretched_master_key = stretch_key(master_key);
161 let user_key_bytes = user_key.to_encoded();
162 EncString::encrypt_aes256_hmac(user_key_bytes.as_ref(), &stretched_master_key)
163}
164
165pub(super) fn decrypt_user_key(
167 key: &Pin<Box<Array<u8, U32>>>,
168 user_key: EncString,
169) -> Result<SymmetricCryptoKey> {
170 let dec: Vec<u8> = match user_key {
171 EncString::Aes256Cbc_B64 { iv, ref data } => {
175 crate::aes::decrypt_aes256(&iv, data.clone(), &key.clone())
176 .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}