1use std::num::NonZeroU32;
2
3use bitwarden_api_api::models::{
4 KdfRequestModel, KdfType, MasterPasswordAuthenticationDataRequestModel,
5 MasterPasswordUnlockDataRequestModel,
6 master_password_unlock_response_model::MasterPasswordUnlockResponseModel,
7};
8use bitwarden_crypto::{
9 EncString, Kdf, KeySlotIds, KeyStoreContext, MasterKey, SymmetricCryptoKey,
10};
11use bitwarden_encoding::B64;
12use bitwarden_error::bitwarden_error;
13use serde::{Deserialize, Serialize};
14use tracing::Level;
15#[cfg(feature = "wasm")]
16use wasm_bindgen::prelude::*;
17
18use crate::{MissingFieldError, require};
19
20#[allow(dead_code)]
22#[bitwarden_error(flat)]
23#[derive(Debug, thiserror::Error)]
24pub enum MasterPasswordError {
25 #[error("Wrapped encryption key is malformed")]
27 EncryptionKeyMalformed,
28 #[error("KDF is malformed")]
30 KdfMalformed,
31 #[error("Invalid KDF configuration")]
33 InvalidKdfConfiguration,
34 #[error(transparent)]
36 MissingField(#[from] MissingFieldError),
37 #[error(transparent)]
39 Crypto(#[from] bitwarden_crypto::CryptoError),
40 #[error("Wrong password")]
42 WrongPassword,
43}
44
45#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
47#[serde(rename_all = "camelCase", deny_unknown_fields)]
48#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
49#[cfg_attr(
50 feature = "wasm",
51 derive(tsify::Tsify),
52 tsify(into_wasm_abi, from_wasm_abi)
53)]
54pub struct MasterPasswordUnlockData {
55 pub kdf: Kdf,
57 pub master_key_wrapped_user_key: EncString,
59 pub salt: String,
61}
62
63impl MasterPasswordUnlockData {
64 pub fn unwrap_to_context<Ids: KeySlotIds>(
66 &self,
67 password: &str,
68 ctx: &mut KeyStoreContext<Ids>,
69 ) -> Result<Ids::Symmetric, MasterPasswordError> {
70 let master_key = MasterKey::derive(password, &self.salt, &self.kdf)
71 .map_err(|_| MasterPasswordError::InvalidKdfConfiguration)?;
72 let user_key = master_key
73 .decrypt_user_key(self.master_key_wrapped_user_key.clone())
74 .map_err(|_| MasterPasswordError::WrongPassword)?;
75 Ok(ctx.add_local_symmetric_key(user_key))
76 }
77
78 pub(crate) fn derive_ref(
79 password: &str,
80 kdf: &Kdf,
81 salt: &str,
82 user_key: &SymmetricCryptoKey,
83 ) -> Result<Self, MasterPasswordError> {
84 let master_key = MasterKey::derive(password, salt, kdf)
85 .map_err(|_| MasterPasswordError::InvalidKdfConfiguration)?;
86 let master_key_wrapped_user_key = master_key
87 .encrypt_user_key(user_key)
88 .map_err(MasterPasswordError::Crypto)?;
89
90 Ok(Self {
91 kdf: kdf.to_owned(),
92 salt: salt.to_owned(),
93 master_key_wrapped_user_key,
94 })
95 }
96
97 #[tracing::instrument(skip(password, salt, ctx))]
99 pub fn derive<Ids: KeySlotIds>(
100 password: &str,
101 kdf: &Kdf,
102 salt: &str,
103 user_key_id: Ids::Symmetric,
104 ctx: &KeyStoreContext<Ids>,
105 ) -> Result<Self, MasterPasswordError> {
106 tracing::event!(Level::INFO, "deriving master password unlock data");
107 #[expect(deprecated)]
109 let key = ctx.dangerous_get_symmetric_key(user_key_id)?;
110 Self::derive_ref(password, kdf, salt, key)
111 }
112}
113
114impl TryFrom<&MasterPasswordUnlockResponseModel> for MasterPasswordUnlockData {
115 type Error = MasterPasswordError;
116
117 fn try_from(response: &MasterPasswordUnlockResponseModel) -> Result<Self, Self::Error> {
118 let kdf = match response.kdf.kdf_type {
119 KdfType::PBKDF2_SHA256 => Kdf::PBKDF2 {
120 iterations: kdf_parse_nonzero_u32(response.kdf.iterations)?,
121 },
122 KdfType::Argon2id => Kdf::Argon2id {
123 iterations: kdf_parse_nonzero_u32(response.kdf.iterations)?,
124 memory: kdf_parse_nonzero_u32(require!(response.kdf.memory))?,
125 parallelism: kdf_parse_nonzero_u32(require!(response.kdf.parallelism))?,
126 },
127 KdfType::__Unknown(_) => return Err(MasterPasswordError::KdfMalformed),
128 };
129
130 let master_key_wrapped_user_key = require!(&response.master_key_encrypted_user_key)
131 .parse()
132 .map_err(|_| MasterPasswordError::EncryptionKeyMalformed)?;
133 let salt = require!(&response.salt).clone();
134
135 Ok(MasterPasswordUnlockData {
136 kdf,
137 master_key_wrapped_user_key,
138 salt,
139 })
140 }
141}
142
143impl From<&MasterPasswordUnlockData> for MasterPasswordUnlockDataRequestModel {
144 fn from(data: &MasterPasswordUnlockData) -> Self {
145 Self {
146 kdf: Box::new(kdf_to_api_kdf_request_model(&data.kdf)),
147 master_key_wrapped_user_key: data.master_key_wrapped_user_key.to_string(),
148 salt: data.salt.to_owned(),
149 }
150 }
151}
152
153impl From<&MasterPasswordUnlockData>
154 for bitwarden_api_identity::models::MasterPasswordUnlockDataRequestModel
155{
156 fn from(data: &MasterPasswordUnlockData) -> Self {
157 Self {
158 kdf: Box::new(kdf_to_identity_kdf_request_model(&data.kdf)),
159 master_key_wrapped_user_key: data.master_key_wrapped_user_key.to_string(),
160 salt: data.salt.to_owned(),
161 }
162 }
163}
164
165fn kdf_parse_nonzero_u32(value: impl TryInto<u32>) -> Result<NonZeroU32, MasterPasswordError> {
166 value
167 .try_into()
168 .ok()
169 .and_then(NonZeroU32::new)
170 .ok_or(MasterPasswordError::KdfMalformed)
171}
172
173#[allow(missing_docs)]
175#[derive(Serialize, Deserialize, Clone, Debug)]
176#[serde(rename_all = "camelCase", deny_unknown_fields)]
177#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
178#[cfg_attr(
179 feature = "wasm",
180 derive(tsify::Tsify),
181 tsify(into_wasm_abi, from_wasm_abi)
182)]
183pub struct MasterPasswordAuthenticationData {
184 pub kdf: Kdf,
185 pub salt: String,
186 pub master_password_authentication_hash: B64,
187}
188
189impl MasterPasswordAuthenticationData {
190 #[tracing::instrument(skip(password, kdf, salt))]
192 pub fn derive(password: &str, kdf: &Kdf, salt: &str) -> Result<Self, MasterPasswordError> {
193 tracing::event!(Level::INFO, "deriving master password authentication data");
194 let master_key = MasterKey::derive(password, salt, kdf)
195 .map_err(|_| MasterPasswordError::InvalidKdfConfiguration)?;
196 let hash = master_key.derive_master_key_hash(
197 password.as_bytes(),
198 bitwarden_crypto::HashPurpose::ServerAuthorization,
199 );
200
201 Ok(Self {
202 kdf: kdf.to_owned(),
203 salt: salt.to_owned(),
204 master_password_authentication_hash: hash,
205 })
206 }
207}
208
209impl From<&MasterPasswordAuthenticationData> for MasterPasswordAuthenticationDataRequestModel {
210 fn from(data: &MasterPasswordAuthenticationData) -> Self {
211 Self {
212 kdf: Box::new(kdf_to_api_kdf_request_model(&data.kdf)),
213 master_password_authentication_hash: data
214 .master_password_authentication_hash
215 .to_string(),
216 salt: data.salt.to_owned(),
217 }
218 }
219}
220
221impl From<&MasterPasswordAuthenticationData>
222 for bitwarden_api_identity::models::MasterPasswordAuthenticationDataRequestModel
223{
224 fn from(data: &MasterPasswordAuthenticationData) -> Self {
225 Self {
226 kdf: Box::new(kdf_to_identity_kdf_request_model(&data.kdf)),
227 master_password_authentication_hash: data
228 .master_password_authentication_hash
229 .to_string(),
230 salt: data.salt.to_owned(),
231 }
232 }
233}
234
235fn kdf_to_api_kdf_request_model(kdf: &Kdf) -> KdfRequestModel {
236 match kdf {
237 Kdf::PBKDF2 { iterations } => KdfRequestModel {
238 kdf_type: KdfType::PBKDF2_SHA256,
239 iterations: iterations.get() as i32,
240 memory: None,
241 parallelism: None,
242 },
243 Kdf::Argon2id {
244 iterations,
245 memory,
246 parallelism,
247 } => KdfRequestModel {
248 kdf_type: KdfType::Argon2id,
249 iterations: iterations.get() as i32,
250 memory: Some(memory.get() as i32),
251 parallelism: Some(parallelism.get() as i32),
252 },
253 }
254}
255
256fn kdf_to_identity_kdf_request_model(kdf: &Kdf) -> bitwarden_api_identity::models::KdfRequestModel {
257 match kdf {
258 Kdf::PBKDF2 { iterations } => bitwarden_api_identity::models::KdfRequestModel {
259 kdf_type: bitwarden_api_identity::models::KdfType::PBKDF2_SHA256,
260 iterations: iterations.get() as i32,
261 memory: None,
262 parallelism: None,
263 },
264 Kdf::Argon2id {
265 iterations,
266 memory,
267 parallelism,
268 } => bitwarden_api_identity::models::KdfRequestModel {
269 kdf_type: bitwarden_api_identity::models::KdfType::Argon2id,
270 iterations: iterations.get() as i32,
271 memory: Some(memory.get() as i32),
272 parallelism: Some(parallelism.get() as i32),
273 },
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use bitwarden_api_api::models::{KdfType, MasterPasswordUnlockKdfResponseModel};
280 use bitwarden_crypto::KeyStore;
281
282 use super::*;
283 use crate::key_management::{KeySlotIds, SymmetricKeySlotId};
284
285 const TEST_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
286 const TEST_INVALID_USER_KEY: &str = "-1.8UClLa8IPE1iZT7chy5wzQ==|6PVfHnVk5S3XqEtQemnM5yb4JodxmPkkWzmDRdfyHtjORmvxqlLX40tBJZ+CKxQWmS8tpEB5w39rbgHg/gqs0haGdZG4cPbywsgGzxZ7uNI=";
287 const TEST_SALT: &str = "[email protected]";
288 const TEST_PASSWORD: &str = "test_password";
289 const TEST_MASTER_PASSWORD_AUTHENTICATION_HASH: &str =
290 "Lyry95vlXEJ5FE0EXjeR9zgcsFSU0qGhP9l5X2jwE38=";
291
292 #[test]
293 fn test_master_password_unlock_data_derive() {
294 let kdf = Kdf::PBKDF2 {
295 iterations: NonZeroU32::new(600_000).unwrap(),
296 };
297 let salt = TEST_SALT.to_string();
298 let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
299 let data = MasterPasswordUnlockData::derive_ref(TEST_PASSWORD, &kdf, &salt, &user_key)
300 .expect("Failed to derive master password unlock data");
301 assert_eq!(data.salt, salt);
302 assert!(matches!(data.kdf, Kdf::PBKDF2 { iterations } if iterations.get() == 600_000));
303
304 let master_key = MasterKey::derive(TEST_PASSWORD, &salt, &data.kdf)
305 .expect("Failed to derive master key");
306 let decrypted_user_key = master_key
307 .decrypt_user_key(data.master_key_wrapped_user_key)
308 .expect("Failed to decrypt user key");
309 assert_eq!(decrypted_user_key, user_key);
310 }
311
312 #[test]
313 fn test_master_password_authentication_data_derive() {
314 let kdf = Kdf::PBKDF2 {
315 iterations: NonZeroU32::new(600_000).unwrap(),
316 };
317 let salt = TEST_SALT.to_string();
318 let data = MasterPasswordAuthenticationData::derive(TEST_PASSWORD, &kdf, &salt)
319 .expect("Failed to derive master password authentication data");
320 assert_eq!(data.salt, salt);
321 assert!(matches!(data.kdf, Kdf::PBKDF2 { iterations } if iterations.get() == 600_000));
322 assert_eq!(
323 data.master_password_authentication_hash.to_string(),
324 TEST_MASTER_PASSWORD_AUTHENTICATION_HASH
325 );
326 }
327
328 fn create_pbkdf2_response(
329 master_key_encrypted_user_key: Option<String>,
330 salt: Option<String>,
331 iterations: i32,
332 ) -> MasterPasswordUnlockResponseModel {
333 MasterPasswordUnlockResponseModel {
334 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
335 kdf_type: KdfType::PBKDF2_SHA256,
336 iterations,
337 memory: None,
338 parallelism: None,
339 }),
340 master_key_encrypted_user_key,
341 salt,
342 }
343 }
344
345 #[test]
346 fn test_try_from_master_password_unlock_response_model_pbkdf2_success() {
347 let response = create_pbkdf2_response(
348 Some(TEST_USER_KEY.to_string()),
349 Some(TEST_SALT.to_string()),
350 600_000,
351 );
352
353 let data = MasterPasswordUnlockData::try_from(&response).unwrap();
354
355 if let Kdf::PBKDF2 { iterations } = data.kdf {
356 assert_eq!(iterations.get(), 600_000);
357 } else {
358 panic!("Expected PBKDF2 KDF")
359 }
360
361 assert_eq!(data.salt, TEST_SALT);
362 assert_eq!(data.master_key_wrapped_user_key.to_string(), TEST_USER_KEY);
363 }
364
365 #[test]
366 fn test_try_from_master_password_unlock_response_model_argon2id_success() {
367 let response = MasterPasswordUnlockResponseModel {
368 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
369 kdf_type: KdfType::Argon2id,
370 iterations: 3,
371 memory: Some(64),
372 parallelism: Some(4),
373 }),
374 master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
375 salt: Some(TEST_SALT.to_string()),
376 };
377
378 let data = MasterPasswordUnlockData::try_from(&response).unwrap();
379
380 if let Kdf::Argon2id {
381 iterations,
382 memory,
383 parallelism,
384 } = data.kdf
385 {
386 assert_eq!(iterations.get(), 3);
387 assert_eq!(memory.get(), 64);
388 assert_eq!(parallelism.get(), 4);
389 } else {
390 panic!("Expected Argon2id KDF")
391 }
392
393 assert_eq!(data.salt, TEST_SALT);
394 assert_eq!(data.master_key_wrapped_user_key.to_string(), TEST_USER_KEY);
395 }
396
397 #[test]
398 fn test_try_from_master_password_unlock_response_model_invalid_user_key_encryption_kdf_malformed_error()
399 {
400 let response = create_pbkdf2_response(
401 Some(TEST_INVALID_USER_KEY.to_string()),
402 Some(TEST_SALT.to_string()),
403 600_000,
404 );
405
406 let result = MasterPasswordUnlockData::try_from(&response);
407 assert!(matches!(
408 result,
409 Err(MasterPasswordError::EncryptionKeyMalformed)
410 ));
411 }
412
413 #[test]
414 fn test_try_from_master_password_unlock_response_model_user_key_none_missing_field_error() {
415 let response = create_pbkdf2_response(None, Some(TEST_SALT.to_string()), 600_000);
416
417 let result = MasterPasswordUnlockData::try_from(&response);
418 assert!(matches!(
419 result,
420 Err(MasterPasswordError::MissingField(MissingFieldError(
421 "&response.master_key_encrypted_user_key"
422 )))
423 ));
424 }
425
426 #[test]
427 fn test_try_from_master_password_unlock_response_model_salt_none_missing_field_error() {
428 let response = create_pbkdf2_response(Some(TEST_USER_KEY.to_string()), None, 600_000);
429
430 let result = MasterPasswordUnlockData::try_from(&response);
431 assert!(matches!(
432 result,
433 Err(MasterPasswordError::MissingField(MissingFieldError(
434 "&response.salt"
435 )))
436 ));
437 }
438
439 #[test]
440 fn test_try_from_master_password_unlock_response_model_argon2id_kdf_memory_none_missing_field_error()
441 {
442 let response = MasterPasswordUnlockResponseModel {
443 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
444 kdf_type: KdfType::Argon2id,
445 iterations: 3,
446 memory: None,
447 parallelism: Some(4),
448 }),
449 master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
450 salt: Some(TEST_SALT.to_string()),
451 };
452
453 let result = MasterPasswordUnlockData::try_from(&response);
454 assert!(matches!(
455 result,
456 Err(MasterPasswordError::MissingField(MissingFieldError(
457 "response.kdf.memory"
458 )))
459 ));
460 }
461
462 #[test]
463 fn test_try_from_master_password_unlock_response_model_argon2id_kdf_memory_zero_kdf_malformed_error()
464 {
465 let response = MasterPasswordUnlockResponseModel {
466 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
467 kdf_type: KdfType::Argon2id,
468 iterations: 3,
469 memory: Some(0),
470 parallelism: Some(4),
471 }),
472 master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
473 salt: Some(TEST_SALT.to_string()),
474 };
475
476 let result = MasterPasswordUnlockData::try_from(&response);
477 assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
478 }
479
480 #[test]
481 fn test_try_from_master_password_unlock_response_model_argon2id_kdf_parallelism_none_missing_field_error()
482 {
483 let response = MasterPasswordUnlockResponseModel {
484 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
485 kdf_type: KdfType::Argon2id,
486 iterations: 3,
487 memory: Some(64),
488 parallelism: None,
489 }),
490 master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
491 salt: Some(TEST_SALT.to_string()),
492 };
493
494 let result = MasterPasswordUnlockData::try_from(&response);
495 assert!(matches!(
496 result,
497 Err(MasterPasswordError::MissingField(MissingFieldError(
498 "response.kdf.parallelism"
499 )))
500 ));
501 }
502
503 #[test]
504 fn test_try_from_master_password_unlock_response_model_argon2id_kdf_parallelism_zero_kdf_malformed_error()
505 {
506 let response = MasterPasswordUnlockResponseModel {
507 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
508 kdf_type: KdfType::Argon2id,
509 iterations: 3,
510 memory: Some(64),
511 parallelism: Some(0),
512 }),
513 master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
514 salt: Some(TEST_SALT.to_string()),
515 };
516
517 let result = MasterPasswordUnlockData::try_from(&response);
518 assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
519 }
520
521 #[test]
522 fn test_try_from_master_password_unlock_response_model_pbkdf2_kdf_iterations_zero_kdf_malformed_error()
523 {
524 let response = create_pbkdf2_response(
525 Some(TEST_USER_KEY.to_string()),
526 Some(TEST_SALT.to_string()),
527 0,
528 );
529
530 let result = MasterPasswordUnlockData::try_from(&response);
531 assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
532 }
533
534 #[test]
535 fn test_try_from_master_password_unlock_response_model_argon2id_kdf_iterations_zero_kdf_malformed_error()
536 {
537 let response = MasterPasswordUnlockResponseModel {
538 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
539 kdf_type: KdfType::Argon2id,
540 iterations: 0,
541 memory: Some(64),
542 parallelism: Some(4),
543 }),
544 master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
545 salt: Some(TEST_SALT.to_string()),
546 };
547
548 let result = MasterPasswordUnlockData::try_from(&response);
549 assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
550 }
551
552 #[test]
553 fn test_unwrap_to_context_success() {
554 let kdf = Kdf::PBKDF2 {
556 iterations: NonZeroU32::new(600_000).expect("non-zero"),
557 };
558 let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
559 let data = MasterPasswordUnlockData::derive_ref(TEST_PASSWORD, &kdf, TEST_SALT, &user_key)
560 .expect("Failed to derive master password unlock data");
561
562 let store: KeyStore<KeySlotIds> = KeyStore::default();
564 let mut ctx = store.context_mut();
565 let key_id = data
566 .unwrap_to_context::<KeySlotIds>(TEST_PASSWORD, &mut ctx)
567 .expect("Failed to unwrap to context");
568
569 assert!(ctx.has_symmetric_key(key_id));
571
572 #[expect(deprecated)]
574 let unwrapped_key = ctx
575 .dangerous_get_symmetric_key(key_id)
576 .expect("Failed to get symmetric key");
577 assert_eq!(*unwrapped_key, user_key);
578 }
579
580 #[test]
581 fn test_unwrap_to_context_wrong_password() {
582 let kdf = Kdf::PBKDF2 {
584 iterations: NonZeroU32::new(600_000).expect("non-zero"),
585 };
586 let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
587 let data = MasterPasswordUnlockData::derive_ref(TEST_PASSWORD, &kdf, TEST_SALT, &user_key)
588 .expect("Failed to derive master password unlock data");
589
590 let store: KeyStore<KeySlotIds> = KeyStore::default();
592 let mut ctx = store.context_mut();
593 let result = data.unwrap_to_context::<KeySlotIds>("wrong_password", &mut ctx);
594
595 assert!(matches!(result, Err(MasterPasswordError::WrongPassword)));
596 }
597
598 #[test]
599 fn test_unwrap_to_context_persists_key() {
600 let kdf = Kdf::PBKDF2 {
602 iterations: NonZeroU32::new(600_000).expect("non-zero"),
603 };
604 let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
605 let data = MasterPasswordUnlockData::derive_ref(TEST_PASSWORD, &kdf, TEST_SALT, &user_key)
606 .expect("Failed to derive master password unlock data");
607
608 let store: KeyStore<KeySlotIds> = KeyStore::default();
610 {
611 let mut ctx = store.context_mut();
612 let local_key_id = data
613 .unwrap_to_context::<KeySlotIds>(TEST_PASSWORD, &mut ctx)
614 .expect("Failed to unwrap to context");
615
616 ctx.persist_symmetric_key(local_key_id, SymmetricKeySlotId::User)
618 .expect("Failed to persist symmetric key");
619 }
620
621 let ctx = store.context();
623 assert!(ctx.has_symmetric_key(SymmetricKeySlotId::User));
624 }
625}