Skip to main content

bitwarden_core/key_management/
master_password.rs

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/// Error for master password related operations.
21#[allow(dead_code)]
22#[bitwarden_error(flat)]
23#[derive(Debug, thiserror::Error)]
24pub enum MasterPasswordError {
25    /// The wrapped encryption key could not be parsed because the encstring is malformed
26    #[error("Wrapped encryption key is malformed")]
27    EncryptionKeyMalformed,
28    /// The KDF data could not be parsed, because it has an invalid value
29    #[error("KDF is malformed")]
30    KdfMalformed,
31    /// The KDF had an invalid configuration
32    #[error("Invalid KDF configuration")]
33    InvalidKdfConfiguration,
34    /// The wrapped encryption key or salt fields are missing or KDF data is incomplete
35    #[error(transparent)]
36    MissingField(#[from] MissingFieldError),
37    /// Generic crypto error
38    #[error(transparent)]
39    Crypto(#[from] bitwarden_crypto::CryptoError),
40    /// The provided password is incorrect
41    #[error("Wrong password")]
42    WrongPassword,
43}
44
45/// Represents the data required to unlock with the master password.
46#[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    /// The key derivation function used to derive the master key
56    pub kdf: Kdf,
57    /// The master key wrapped user key
58    pub master_key_wrapped_user_key: EncString,
59    /// The salt used in the KDF, typically the user's email
60    pub salt: String,
61}
62
63impl MasterPasswordUnlockData {
64    /// Unwrap the user key into the key store context using the provided password.
65    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    /// Derive master password unlock data from a password and user key in the key store.
98    #[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        // Temporary workaround until lower level functions also work on the key context
108        #[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/// Represents the data required to authenticate with the master password.
162#[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    /// Derive master password authentication data from a password, KDF, and salt.
179    #[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        // Derive master password unlock data from a known password and user key
508        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        // Create a key store and unwrap the user key into the context
516        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        // Verify that the key was added to the context
523        assert!(ctx.has_symmetric_key(key_id));
524
525        // Verify the unwrapped key matches the original
526        #[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        // Derive master password unlock data from a known password and user key
536        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        // Attempt to unwrap with wrong password
544        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        // Derive master password unlock data from a known password and user key
554        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        // Create a key store and unwrap the user key into the context
562        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            // Persist the local key to the User key slot
570            ctx.persist_symmetric_key(local_key_id, SymmetricKeyId::User)
571                .expect("Failed to persist symmetric key");
572        }
573
574        // Verify the key is accessible with the User key id in a new context
575        let ctx = store.context();
576        assert!(ctx.has_symmetric_key(SymmetricKeyId::User));
577    }
578}