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        };
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/// Represents the data required to authenticate with the master password.
159#[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    /// Derive master password authentication data from a password, KDF, and salt.
176    #[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        // Derive master password unlock data from a known password and user key
505        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        // Create a key store and unwrap the user key into the context
513        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        // Verify that the key was added to the context
520        assert!(ctx.has_symmetric_key(key_id));
521
522        // Verify the unwrapped key matches the original
523        #[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        // Derive master password unlock data from a known password and user key
533        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        // Attempt to unwrap with wrong password
541        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        // Derive master password unlock data from a known password and user key
551        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        // Create a key store and unwrap the user key into the context
559        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            // Persist the local key to the User key slot
567            ctx.persist_symmetric_key(local_key_id, SymmetricKeyId::User)
568                .expect("Failed to persist symmetric key");
569        }
570
571        // Verify the key is accessible with the User key id in a new context
572        let ctx = store.context();
573        assert!(ctx.has_symmetric_key(SymmetricKeyId::User));
574    }
575}