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