bitwarden_core/key_management/
master_password.rs

1use std::num::NonZeroU32;
2
3use bitwarden_api_api::models::{
4    KdfType, master_password_unlock_response_model::MasterPasswordUnlockResponseModel,
5};
6use bitwarden_crypto::{EncString, Kdf, MasterKey, SymmetricCryptoKey};
7use bitwarden_encoding::B64;
8use bitwarden_error::bitwarden_error;
9use serde::{Deserialize, Serialize};
10#[cfg(feature = "wasm")]
11use wasm_bindgen::prelude::*;
12
13use crate::{MissingFieldError, require};
14
15/// Error for master password related operations.
16#[allow(dead_code)]
17#[bitwarden_error(flat)]
18#[derive(Debug, thiserror::Error)]
19pub enum MasterPasswordError {
20    /// The wrapped encryption key could not be parsed because the encstring is malformed
21    #[error("Wrapped encryption key is malformed")]
22    EncryptionKeyMalformed,
23    /// The KDF data could not be parsed, because it has an invalid value
24    #[error("KDF is malformed")]
25    KdfMalformed,
26    /// The KDF had an invalid configuration
27    #[error("Invalid KDF configuration")]
28    InvalidKdfConfiguration,
29    /// The wrapped encryption key or salt fields are missing or KDF data is incomplete
30    #[error(transparent)]
31    MissingField(#[from] MissingFieldError),
32    /// Generic crypto error
33    #[error(transparent)]
34    Crypto(#[from] bitwarden_crypto::CryptoError),
35}
36
37/// Represents the data required to unlock with the master password.
38#[derive(Serialize, Deserialize, Debug)]
39#[serde(rename_all = "camelCase", deny_unknown_fields)]
40#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
41#[cfg_attr(
42    feature = "wasm",
43    derive(tsify::Tsify),
44    tsify(into_wasm_abi, from_wasm_abi)
45)]
46pub struct MasterPasswordUnlockData {
47    /// The key derivation function used to derive the master key
48    pub kdf: Kdf,
49    /// The master key wrapped user key
50    pub master_key_wrapped_user_key: EncString,
51    /// The salt used in the KDF, typically the user's email
52    pub salt: String,
53}
54
55impl MasterPasswordUnlockData {
56    pub(crate) fn derive(
57        password: &str,
58        kdf: &Kdf,
59        salt: &str,
60        user_key: &SymmetricCryptoKey,
61    ) -> Result<Self, MasterPasswordError> {
62        let master_key = MasterKey::derive(password, salt, kdf)
63            .map_err(|_| MasterPasswordError::InvalidKdfConfiguration)?;
64        let master_key_wrapped_user_key = master_key
65            .encrypt_user_key(user_key)
66            .map_err(MasterPasswordError::Crypto)?;
67
68        Ok(Self {
69            kdf: kdf.to_owned(),
70            salt: salt.to_owned(),
71            master_key_wrapped_user_key,
72        })
73    }
74}
75
76impl TryFrom<MasterPasswordUnlockResponseModel> for MasterPasswordUnlockData {
77    type Error = MasterPasswordError;
78
79    fn try_from(response: MasterPasswordUnlockResponseModel) -> Result<Self, Self::Error> {
80        let kdf = match response.kdf.kdf_type {
81            KdfType::PBKDF2_SHA256 => Kdf::PBKDF2 {
82                iterations: kdf_parse_nonzero_u32(response.kdf.iterations)?,
83            },
84            KdfType::Argon2id => Kdf::Argon2id {
85                iterations: kdf_parse_nonzero_u32(response.kdf.iterations)?,
86                memory: kdf_parse_nonzero_u32(require!(response.kdf.memory))?,
87                parallelism: kdf_parse_nonzero_u32(require!(response.kdf.parallelism))?,
88            },
89        };
90
91        let master_key_encrypted_user_key = require!(response.master_key_encrypted_user_key);
92        let salt = require!(response.salt);
93
94        Ok(MasterPasswordUnlockData {
95            kdf,
96            master_key_wrapped_user_key: master_key_encrypted_user_key
97                .parse()
98                .map_err(|_| MasterPasswordError::EncryptionKeyMalformed)?,
99            salt,
100        })
101    }
102}
103
104fn kdf_parse_nonzero_u32(value: impl TryInto<u32>) -> Result<NonZeroU32, MasterPasswordError> {
105    value
106        .try_into()
107        .ok()
108        .and_then(NonZeroU32::new)
109        .ok_or(MasterPasswordError::KdfMalformed)
110}
111
112/// Represents the data required to authenticate with the master password.
113#[allow(missing_docs)]
114#[derive(Serialize, Deserialize, Debug)]
115#[serde(rename_all = "camelCase", deny_unknown_fields)]
116#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
117#[cfg_attr(
118    feature = "wasm",
119    derive(tsify::Tsify),
120    tsify(into_wasm_abi, from_wasm_abi)
121)]
122pub struct MasterPasswordAuthenticationData {
123    pub kdf: Kdf,
124    pub salt: String,
125    pub master_password_authentication_hash: B64,
126}
127
128impl MasterPasswordAuthenticationData {
129    pub(crate) fn derive(
130        password: &str,
131        kdf: &Kdf,
132        salt: &str,
133    ) -> Result<Self, MasterPasswordError> {
134        let master_key = MasterKey::derive(password, salt, kdf)
135            .map_err(|_| MasterPasswordError::InvalidKdfConfiguration)?;
136        let hash = master_key.derive_master_key_hash(
137            password.as_bytes(),
138            bitwarden_crypto::HashPurpose::ServerAuthorization,
139        );
140
141        Ok(Self {
142            kdf: kdf.to_owned(),
143            salt: salt.to_owned(),
144            master_password_authentication_hash: hash,
145        })
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use bitwarden_api_api::models::{KdfType, MasterPasswordUnlockKdfResponseModel};
152
153    use super::*;
154
155    const TEST_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
156    const TEST_INVALID_USER_KEY: &str = "-1.8UClLa8IPE1iZT7chy5wzQ==|6PVfHnVk5S3XqEtQemnM5yb4JodxmPkkWzmDRdfyHtjORmvxqlLX40tBJZ+CKxQWmS8tpEB5w39rbgHg/gqs0haGdZG4cPbywsgGzxZ7uNI=";
157    const TEST_SALT: &str = "[email protected]";
158    const TEST_PASSWORD: &str = "test_password";
159    const TEST_MASTER_PASSWORD_AUTHENTICATION_HASH: &str =
160        "Lyry95vlXEJ5FE0EXjeR9zgcsFSU0qGhP9l5X2jwE38=";
161
162    #[test]
163    fn test_master_password_unlock_data_derive() {
164        let kdf = Kdf::PBKDF2 {
165            iterations: NonZeroU32::new(600_000).unwrap(),
166        };
167        let salt = TEST_SALT.to_string();
168        let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
169        let data = MasterPasswordUnlockData::derive(TEST_PASSWORD, &kdf, &salt, &user_key)
170            .expect("Failed to derive master password unlock data");
171        assert_eq!(data.salt, salt);
172        assert!(matches!(data.kdf, Kdf::PBKDF2 { iterations } if iterations.get() == 600_000));
173
174        let master_key = MasterKey::derive(TEST_PASSWORD, &salt, &data.kdf)
175            .expect("Failed to derive master key");
176        let decrypted_user_key = master_key
177            .decrypt_user_key(data.master_key_wrapped_user_key)
178            .expect("Failed to decrypt user key");
179        assert_eq!(decrypted_user_key, user_key);
180    }
181
182    #[test]
183    fn test_master_password_authentication_data_derive() {
184        let kdf = Kdf::PBKDF2 {
185            iterations: NonZeroU32::new(600_000).unwrap(),
186        };
187        let salt = TEST_SALT.to_string();
188        let data = MasterPasswordAuthenticationData::derive(TEST_PASSWORD, &kdf, &salt)
189            .expect("Failed to derive master password authentication data");
190        assert_eq!(data.salt, salt);
191        assert!(matches!(data.kdf, Kdf::PBKDF2 { iterations } if iterations.get() == 600_000));
192        assert_eq!(
193            data.master_password_authentication_hash.to_string(),
194            TEST_MASTER_PASSWORD_AUTHENTICATION_HASH
195        );
196    }
197
198    fn create_pbkdf2_response(
199        master_key_encrypted_user_key: Option<String>,
200        salt: Option<String>,
201        iterations: i32,
202    ) -> MasterPasswordUnlockResponseModel {
203        MasterPasswordUnlockResponseModel {
204            kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
205                kdf_type: KdfType::PBKDF2_SHA256,
206                iterations,
207                memory: None,
208                parallelism: None,
209            }),
210            master_key_encrypted_user_key,
211            salt,
212        }
213    }
214
215    #[test]
216    fn test_try_from_master_password_unlock_response_model_pbkdf2_success() {
217        let response = create_pbkdf2_response(
218            Some(TEST_USER_KEY.to_string()),
219            Some(TEST_SALT.to_string()),
220            600_000,
221        );
222
223        let data = MasterPasswordUnlockData::try_from(response).unwrap();
224
225        if let Kdf::PBKDF2 { iterations } = data.kdf {
226            assert_eq!(iterations.get(), 600_000);
227        } else {
228            panic!("Expected PBKDF2 KDF")
229        }
230
231        assert_eq!(data.salt, TEST_SALT);
232        assert_eq!(data.master_key_wrapped_user_key.to_string(), TEST_USER_KEY);
233    }
234
235    #[test]
236    fn test_try_from_master_password_unlock_response_model_argon2id_success() {
237        let response = MasterPasswordUnlockResponseModel {
238            kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
239                kdf_type: KdfType::Argon2id,
240                iterations: 3,
241                memory: Some(64),
242                parallelism: Some(4),
243            }),
244            master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
245            salt: Some(TEST_SALT.to_string()),
246        };
247
248        let data = MasterPasswordUnlockData::try_from(response).unwrap();
249
250        if let Kdf::Argon2id {
251            iterations,
252            memory,
253            parallelism,
254        } = data.kdf
255        {
256            assert_eq!(iterations.get(), 3);
257            assert_eq!(memory.get(), 64);
258            assert_eq!(parallelism.get(), 4);
259        } else {
260            panic!("Expected Argon2id KDF")
261        }
262
263        assert_eq!(data.salt, TEST_SALT);
264        assert_eq!(data.master_key_wrapped_user_key.to_string(), TEST_USER_KEY);
265    }
266
267    #[test]
268    fn test_try_from_master_password_unlock_response_model_invalid_user_key_encryption_kdf_malformed_error()
269     {
270        let response = create_pbkdf2_response(
271            Some(TEST_INVALID_USER_KEY.to_string()),
272            Some(TEST_SALT.to_string()),
273            600_000,
274        );
275
276        let result = MasterPasswordUnlockData::try_from(response);
277        assert!(matches!(
278            result,
279            Err(MasterPasswordError::EncryptionKeyMalformed)
280        ));
281    }
282
283    #[test]
284    fn test_try_from_master_password_unlock_response_model_user_key_none_missing_field_error() {
285        let response = create_pbkdf2_response(None, Some(TEST_SALT.to_string()), 600_000);
286
287        let result = MasterPasswordUnlockData::try_from(response);
288        assert!(matches!(
289            result,
290            Err(MasterPasswordError::MissingField(MissingFieldError(
291                "response.master_key_encrypted_user_key"
292            )))
293        ));
294    }
295
296    #[test]
297    fn test_try_from_master_password_unlock_response_model_salt_none_missing_field_error() {
298        let response = create_pbkdf2_response(Some(TEST_USER_KEY.to_string()), None, 600_000);
299
300        let result = MasterPasswordUnlockData::try_from(response);
301        assert!(matches!(
302            result,
303            Err(MasterPasswordError::MissingField(MissingFieldError(
304                "response.salt"
305            )))
306        ));
307    }
308
309    #[test]
310    fn test_try_from_master_password_unlock_response_model_argon2id_kdf_memory_none_missing_field_error()
311     {
312        let response = MasterPasswordUnlockResponseModel {
313            kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
314                kdf_type: KdfType::Argon2id,
315                iterations: 3,
316                memory: None,
317                parallelism: Some(4),
318            }),
319            master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
320            salt: Some(TEST_SALT.to_string()),
321        };
322
323        let result = MasterPasswordUnlockData::try_from(response);
324        assert!(matches!(
325            result,
326            Err(MasterPasswordError::MissingField(MissingFieldError(
327                "response.kdf.memory"
328            )))
329        ));
330    }
331
332    #[test]
333    fn test_try_from_master_password_unlock_response_model_argon2id_kdf_memory_zero_kdf_malformed_error()
334     {
335        let response = MasterPasswordUnlockResponseModel {
336            kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
337                kdf_type: KdfType::Argon2id,
338                iterations: 3,
339                memory: Some(0),
340                parallelism: Some(4),
341            }),
342            master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
343            salt: Some(TEST_SALT.to_string()),
344        };
345
346        let result = MasterPasswordUnlockData::try_from(response);
347        assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
348    }
349
350    #[test]
351    fn test_try_from_master_password_unlock_response_model_argon2id_kdf_parallelism_none_missing_field_error()
352     {
353        let response = MasterPasswordUnlockResponseModel {
354            kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
355                kdf_type: KdfType::Argon2id,
356                iterations: 3,
357                memory: Some(64),
358                parallelism: None,
359            }),
360            master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
361            salt: Some(TEST_SALT.to_string()),
362        };
363
364        let result = MasterPasswordUnlockData::try_from(response);
365        assert!(matches!(
366            result,
367            Err(MasterPasswordError::MissingField(MissingFieldError(
368                "response.kdf.parallelism"
369            )))
370        ));
371    }
372
373    #[test]
374    fn test_try_from_master_password_unlock_response_model_argon2id_kdf_parallelism_zero_kdf_malformed_error()
375     {
376        let response = MasterPasswordUnlockResponseModel {
377            kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
378                kdf_type: KdfType::Argon2id,
379                iterations: 3,
380                memory: Some(64),
381                parallelism: Some(0),
382            }),
383            master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
384            salt: Some(TEST_SALT.to_string()),
385        };
386
387        let result = MasterPasswordUnlockData::try_from(response);
388        assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
389    }
390
391    #[test]
392    fn test_try_from_master_password_unlock_response_model_pbkdf2_kdf_iterations_zero_kdf_malformed_error()
393     {
394        let response = create_pbkdf2_response(
395            Some(TEST_USER_KEY.to_string()),
396            Some(TEST_SALT.to_string()),
397            0,
398        );
399
400        let result = MasterPasswordUnlockData::try_from(response);
401        assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
402    }
403
404    #[test]
405    fn test_try_from_master_password_unlock_response_model_argon2id_kdf_iterations_zero_kdf_malformed_error()
406     {
407        let response = MasterPasswordUnlockResponseModel {
408            kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
409                kdf_type: KdfType::Argon2id,
410                iterations: 0,
411                memory: Some(64),
412                parallelism: Some(4),
413            }),
414            master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
415            salt: Some(TEST_SALT.to_string()),
416        };
417
418        let result = MasterPasswordUnlockData::try_from(response);
419        assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
420    }
421}