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