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