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