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_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
153fn kdf_parse_nonzero_u32(value: impl TryInto<u32>) -> Result<NonZeroU32, MasterPasswordError> {
154 value
155 .try_into()
156 .ok()
157 .and_then(NonZeroU32::new)
158 .ok_or(MasterPasswordError::KdfMalformed)
159}
160
161#[allow(missing_docs)]
163#[derive(Serialize, Deserialize, Debug)]
164#[serde(rename_all = "camelCase", deny_unknown_fields)]
165#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
166#[cfg_attr(
167 feature = "wasm",
168 derive(tsify::Tsify),
169 tsify(into_wasm_abi, from_wasm_abi)
170)]
171pub struct MasterPasswordAuthenticationData {
172 pub kdf: Kdf,
173 pub salt: String,
174 pub master_password_authentication_hash: B64,
175}
176
177impl MasterPasswordAuthenticationData {
178 #[tracing::instrument(skip(password, kdf, salt))]
180 pub fn derive(password: &str, kdf: &Kdf, salt: &str) -> Result<Self, MasterPasswordError> {
181 tracing::event!(Level::INFO, "deriving master password authentication data");
182 let master_key = MasterKey::derive(password, salt, kdf)
183 .map_err(|_| MasterPasswordError::InvalidKdfConfiguration)?;
184 let hash = master_key.derive_master_key_hash(
185 password.as_bytes(),
186 bitwarden_crypto::HashPurpose::ServerAuthorization,
187 );
188
189 Ok(Self {
190 kdf: kdf.to_owned(),
191 salt: salt.to_owned(),
192 master_password_authentication_hash: hash,
193 })
194 }
195}
196
197impl From<&MasterPasswordAuthenticationData> for MasterPasswordAuthenticationDataRequestModel {
198 fn from(data: &MasterPasswordAuthenticationData) -> Self {
199 Self {
200 kdf: Box::new(kdf_to_kdf_request_model(&data.kdf)),
201 master_password_authentication_hash: data
202 .master_password_authentication_hash
203 .to_string(),
204 salt: data.salt.to_owned(),
205 }
206 }
207}
208
209fn kdf_to_kdf_request_model(kdf: &Kdf) -> KdfRequestModel {
210 match kdf {
211 Kdf::PBKDF2 { iterations } => KdfRequestModel {
212 kdf_type: KdfType::PBKDF2_SHA256,
213 iterations: iterations.get() as i32,
214 memory: None,
215 parallelism: None,
216 },
217 Kdf::Argon2id {
218 iterations,
219 memory,
220 parallelism,
221 } => KdfRequestModel {
222 kdf_type: KdfType::Argon2id,
223 iterations: iterations.get() as i32,
224 memory: Some(memory.get() as i32),
225 parallelism: Some(parallelism.get() as i32),
226 },
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use bitwarden_api_api::models::{KdfType, MasterPasswordUnlockKdfResponseModel};
233 use bitwarden_crypto::KeyStore;
234
235 use super::*;
236 use crate::key_management::{KeyIds, SymmetricKeyId};
237
238 const TEST_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
239 const TEST_INVALID_USER_KEY: &str = "-1.8UClLa8IPE1iZT7chy5wzQ==|6PVfHnVk5S3XqEtQemnM5yb4JodxmPkkWzmDRdfyHtjORmvxqlLX40tBJZ+CKxQWmS8tpEB5w39rbgHg/gqs0haGdZG4cPbywsgGzxZ7uNI=";
240 const TEST_SALT: &str = "[email protected]";
241 const TEST_PASSWORD: &str = "test_password";
242 const TEST_MASTER_PASSWORD_AUTHENTICATION_HASH: &str =
243 "Lyry95vlXEJ5FE0EXjeR9zgcsFSU0qGhP9l5X2jwE38=";
244
245 #[test]
246 fn test_master_password_unlock_data_derive() {
247 let kdf = Kdf::PBKDF2 {
248 iterations: NonZeroU32::new(600_000).unwrap(),
249 };
250 let salt = TEST_SALT.to_string();
251 let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
252 let data = MasterPasswordUnlockData::derive_ref(TEST_PASSWORD, &kdf, &salt, &user_key)
253 .expect("Failed to derive master password unlock data");
254 assert_eq!(data.salt, salt);
255 assert!(matches!(data.kdf, Kdf::PBKDF2 { iterations } if iterations.get() == 600_000));
256
257 let master_key = MasterKey::derive(TEST_PASSWORD, &salt, &data.kdf)
258 .expect("Failed to derive master key");
259 let decrypted_user_key = master_key
260 .decrypt_user_key(data.master_key_wrapped_user_key)
261 .expect("Failed to decrypt user key");
262 assert_eq!(decrypted_user_key, user_key);
263 }
264
265 #[test]
266 fn test_master_password_authentication_data_derive() {
267 let kdf = Kdf::PBKDF2 {
268 iterations: NonZeroU32::new(600_000).unwrap(),
269 };
270 let salt = TEST_SALT.to_string();
271 let data = MasterPasswordAuthenticationData::derive(TEST_PASSWORD, &kdf, &salt)
272 .expect("Failed to derive master password authentication data");
273 assert_eq!(data.salt, salt);
274 assert!(matches!(data.kdf, Kdf::PBKDF2 { iterations } if iterations.get() == 600_000));
275 assert_eq!(
276 data.master_password_authentication_hash.to_string(),
277 TEST_MASTER_PASSWORD_AUTHENTICATION_HASH
278 );
279 }
280
281 fn create_pbkdf2_response(
282 master_key_encrypted_user_key: Option<String>,
283 salt: Option<String>,
284 iterations: i32,
285 ) -> MasterPasswordUnlockResponseModel {
286 MasterPasswordUnlockResponseModel {
287 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
288 kdf_type: KdfType::PBKDF2_SHA256,
289 iterations,
290 memory: None,
291 parallelism: None,
292 }),
293 master_key_encrypted_user_key,
294 salt,
295 }
296 }
297
298 #[test]
299 fn test_try_from_master_password_unlock_response_model_pbkdf2_success() {
300 let response = create_pbkdf2_response(
301 Some(TEST_USER_KEY.to_string()),
302 Some(TEST_SALT.to_string()),
303 600_000,
304 );
305
306 let data = MasterPasswordUnlockData::try_from(&response).unwrap();
307
308 if let Kdf::PBKDF2 { iterations } = data.kdf {
309 assert_eq!(iterations.get(), 600_000);
310 } else {
311 panic!("Expected PBKDF2 KDF")
312 }
313
314 assert_eq!(data.salt, TEST_SALT);
315 assert_eq!(data.master_key_wrapped_user_key.to_string(), TEST_USER_KEY);
316 }
317
318 #[test]
319 fn test_try_from_master_password_unlock_response_model_argon2id_success() {
320 let response = MasterPasswordUnlockResponseModel {
321 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
322 kdf_type: KdfType::Argon2id,
323 iterations: 3,
324 memory: Some(64),
325 parallelism: Some(4),
326 }),
327 master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
328 salt: Some(TEST_SALT.to_string()),
329 };
330
331 let data = MasterPasswordUnlockData::try_from(&response).unwrap();
332
333 if let Kdf::Argon2id {
334 iterations,
335 memory,
336 parallelism,
337 } = data.kdf
338 {
339 assert_eq!(iterations.get(), 3);
340 assert_eq!(memory.get(), 64);
341 assert_eq!(parallelism.get(), 4);
342 } else {
343 panic!("Expected Argon2id KDF")
344 }
345
346 assert_eq!(data.salt, TEST_SALT);
347 assert_eq!(data.master_key_wrapped_user_key.to_string(), TEST_USER_KEY);
348 }
349
350 #[test]
351 fn test_try_from_master_password_unlock_response_model_invalid_user_key_encryption_kdf_malformed_error()
352 {
353 let response = create_pbkdf2_response(
354 Some(TEST_INVALID_USER_KEY.to_string()),
355 Some(TEST_SALT.to_string()),
356 600_000,
357 );
358
359 let result = MasterPasswordUnlockData::try_from(&response);
360 assert!(matches!(
361 result,
362 Err(MasterPasswordError::EncryptionKeyMalformed)
363 ));
364 }
365
366 #[test]
367 fn test_try_from_master_password_unlock_response_model_user_key_none_missing_field_error() {
368 let response = create_pbkdf2_response(None, Some(TEST_SALT.to_string()), 600_000);
369
370 let result = MasterPasswordUnlockData::try_from(&response);
371 assert!(matches!(
372 result,
373 Err(MasterPasswordError::MissingField(MissingFieldError(
374 "&response.master_key_encrypted_user_key"
375 )))
376 ));
377 }
378
379 #[test]
380 fn test_try_from_master_password_unlock_response_model_salt_none_missing_field_error() {
381 let response = create_pbkdf2_response(Some(TEST_USER_KEY.to_string()), None, 600_000);
382
383 let result = MasterPasswordUnlockData::try_from(&response);
384 assert!(matches!(
385 result,
386 Err(MasterPasswordError::MissingField(MissingFieldError(
387 "&response.salt"
388 )))
389 ));
390 }
391
392 #[test]
393 fn test_try_from_master_password_unlock_response_model_argon2id_kdf_memory_none_missing_field_error()
394 {
395 let response = MasterPasswordUnlockResponseModel {
396 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
397 kdf_type: KdfType::Argon2id,
398 iterations: 3,
399 memory: None,
400 parallelism: Some(4),
401 }),
402 master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
403 salt: Some(TEST_SALT.to_string()),
404 };
405
406 let result = MasterPasswordUnlockData::try_from(&response);
407 assert!(matches!(
408 result,
409 Err(MasterPasswordError::MissingField(MissingFieldError(
410 "response.kdf.memory"
411 )))
412 ));
413 }
414
415 #[test]
416 fn test_try_from_master_password_unlock_response_model_argon2id_kdf_memory_zero_kdf_malformed_error()
417 {
418 let response = MasterPasswordUnlockResponseModel {
419 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
420 kdf_type: KdfType::Argon2id,
421 iterations: 3,
422 memory: Some(0),
423 parallelism: Some(4),
424 }),
425 master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
426 salt: Some(TEST_SALT.to_string()),
427 };
428
429 let result = MasterPasswordUnlockData::try_from(&response);
430 assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
431 }
432
433 #[test]
434 fn test_try_from_master_password_unlock_response_model_argon2id_kdf_parallelism_none_missing_field_error()
435 {
436 let response = MasterPasswordUnlockResponseModel {
437 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
438 kdf_type: KdfType::Argon2id,
439 iterations: 3,
440 memory: Some(64),
441 parallelism: None,
442 }),
443 master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
444 salt: Some(TEST_SALT.to_string()),
445 };
446
447 let result = MasterPasswordUnlockData::try_from(&response);
448 assert!(matches!(
449 result,
450 Err(MasterPasswordError::MissingField(MissingFieldError(
451 "response.kdf.parallelism"
452 )))
453 ));
454 }
455
456 #[test]
457 fn test_try_from_master_password_unlock_response_model_argon2id_kdf_parallelism_zero_kdf_malformed_error()
458 {
459 let response = MasterPasswordUnlockResponseModel {
460 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
461 kdf_type: KdfType::Argon2id,
462 iterations: 3,
463 memory: Some(64),
464 parallelism: Some(0),
465 }),
466 master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
467 salt: Some(TEST_SALT.to_string()),
468 };
469
470 let result = MasterPasswordUnlockData::try_from(&response);
471 assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
472 }
473
474 #[test]
475 fn test_try_from_master_password_unlock_response_model_pbkdf2_kdf_iterations_zero_kdf_malformed_error()
476 {
477 let response = create_pbkdf2_response(
478 Some(TEST_USER_KEY.to_string()),
479 Some(TEST_SALT.to_string()),
480 0,
481 );
482
483 let result = MasterPasswordUnlockData::try_from(&response);
484 assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
485 }
486
487 #[test]
488 fn test_try_from_master_password_unlock_response_model_argon2id_kdf_iterations_zero_kdf_malformed_error()
489 {
490 let response = MasterPasswordUnlockResponseModel {
491 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
492 kdf_type: KdfType::Argon2id,
493 iterations: 0,
494 memory: Some(64),
495 parallelism: Some(4),
496 }),
497 master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
498 salt: Some(TEST_SALT.to_string()),
499 };
500
501 let result = MasterPasswordUnlockData::try_from(&response);
502 assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
503 }
504
505 #[test]
506 fn test_unwrap_to_context_success() {
507 let kdf = Kdf::PBKDF2 {
509 iterations: NonZeroU32::new(600_000).expect("non-zero"),
510 };
511 let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
512 let data = MasterPasswordUnlockData::derive_ref(TEST_PASSWORD, &kdf, TEST_SALT, &user_key)
513 .expect("Failed to derive master password unlock data");
514
515 let store: KeyStore<KeyIds> = KeyStore::default();
517 let mut ctx = store.context_mut();
518 let key_id = data
519 .unwrap_to_context::<KeyIds>(TEST_PASSWORD, &mut ctx)
520 .expect("Failed to unwrap to context");
521
522 assert!(ctx.has_symmetric_key(key_id));
524
525 #[expect(deprecated)]
527 let unwrapped_key = ctx
528 .dangerous_get_symmetric_key(key_id)
529 .expect("Failed to get symmetric key");
530 assert_eq!(*unwrapped_key, user_key);
531 }
532
533 #[test]
534 fn test_unwrap_to_context_wrong_password() {
535 let kdf = Kdf::PBKDF2 {
537 iterations: NonZeroU32::new(600_000).expect("non-zero"),
538 };
539 let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
540 let data = MasterPasswordUnlockData::derive_ref(TEST_PASSWORD, &kdf, TEST_SALT, &user_key)
541 .expect("Failed to derive master password unlock data");
542
543 let store: KeyStore<KeyIds> = KeyStore::default();
545 let mut ctx = store.context_mut();
546 let result = data.unwrap_to_context::<KeyIds>("wrong_password", &mut ctx);
547
548 assert!(matches!(result, Err(MasterPasswordError::WrongPassword)));
549 }
550
551 #[test]
552 fn test_unwrap_to_context_persists_key() {
553 let kdf = Kdf::PBKDF2 {
555 iterations: NonZeroU32::new(600_000).expect("non-zero"),
556 };
557 let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
558 let data = MasterPasswordUnlockData::derive_ref(TEST_PASSWORD, &kdf, TEST_SALT, &user_key)
559 .expect("Failed to derive master password unlock data");
560
561 let store: KeyStore<KeyIds> = KeyStore::default();
563 {
564 let mut ctx = store.context_mut();
565 let local_key_id = data
566 .unwrap_to_context::<KeyIds>(TEST_PASSWORD, &mut ctx)
567 .expect("Failed to unwrap to context");
568
569 ctx.persist_symmetric_key(local_key_id, SymmetricKeyId::User)
571 .expect("Failed to persist symmetric key");
572 }
573
574 let ctx = store.context();
576 assert!(ctx.has_symmetric_key(SymmetricKeyId::User));
577 }
578}