bitwarden_core/key_management/
master_password.rs

1use std::num::NonZeroU32;
2
3use bitwarden_api_api::models::{
4    master_password_unlock_response_model::MasterPasswordUnlockResponseModel, KdfType,
5};
6use bitwarden_crypto::{EncString, Kdf};
7use bitwarden_error::bitwarden_error;
8use serde::{Deserialize, Serialize};
9
10use crate::{require, MissingFieldError};
11
12/// Error for master password related operations.
13#[allow(dead_code)]
14#[bitwarden_error(flat)]
15#[derive(Debug, thiserror::Error)]
16pub(crate) enum MasterPasswordError {
17    /// The wrapped encryption key could not be parsed because the encstring is malformed
18    #[error("Wrapped encryption key is malformed")]
19    EncryptionKeyMalformed,
20    /// The KDF data could not be parsed, because it has an invalid value
21    #[error("KDF is malformed")]
22    KdfMalformed,
23    /// The wrapped encryption key or salt fields are missing or KDF data is incomplete
24    #[error(transparent)]
25    MissingField(#[from] MissingFieldError),
26}
27
28/// Represents the data required to unlock with the master password.
29#[allow(dead_code)]
30#[derive(Serialize, Deserialize, Debug)]
31#[serde(rename_all = "camelCase", deny_unknown_fields)]
32pub(crate) struct MasterPasswordUnlockData {
33    /// The key derivation function used to derive the master key
34    kdf: Kdf,
35    /// The master key wrapped user key
36    master_key_wrapped_user_key: EncString,
37    /// The salt used in the KDF, typically the user's email
38    salt: String,
39}
40
41impl TryFrom<MasterPasswordUnlockResponseModel> for MasterPasswordUnlockData {
42    type Error = MasterPasswordError;
43
44    fn try_from(response: MasterPasswordUnlockResponseModel) -> Result<Self, Self::Error> {
45        let kdf = match response.kdf.kdf_type {
46            KdfType::PBKDF2_SHA256 => Kdf::PBKDF2 {
47                iterations: kdf_parse_nonzero_u32(response.kdf.iterations)?,
48            },
49            KdfType::Argon2id => Kdf::Argon2id {
50                iterations: kdf_parse_nonzero_u32(response.kdf.iterations)?,
51                memory: kdf_parse_nonzero_u32(require!(response.kdf.memory))?,
52                parallelism: kdf_parse_nonzero_u32(require!(response.kdf.parallelism))?,
53            },
54        };
55
56        let master_key_encrypted_user_key = require!(response.master_key_encrypted_user_key);
57        let salt = require!(response.salt);
58
59        Ok(MasterPasswordUnlockData {
60            kdf,
61            master_key_wrapped_user_key: master_key_encrypted_user_key
62                .parse()
63                .map_err(|_| MasterPasswordError::EncryptionKeyMalformed)?,
64            salt,
65        })
66    }
67}
68
69fn kdf_parse_nonzero_u32(value: impl TryInto<u32>) -> Result<NonZeroU32, MasterPasswordError> {
70    value
71        .try_into()
72        .ok()
73        .and_then(NonZeroU32::new)
74        .ok_or(MasterPasswordError::KdfMalformed)
75}
76
77#[cfg(test)]
78mod tests {
79    use bitwarden_api_api::models::{KdfType, MasterPasswordUnlockKdfResponseModel};
80
81    use super::*;
82
83    const TEST_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
84    const TEST_INVALID_USER_KEY: &str = "-1.8UClLa8IPE1iZT7chy5wzQ==|6PVfHnVk5S3XqEtQemnM5yb4JodxmPkkWzmDRdfyHtjORmvxqlLX40tBJZ+CKxQWmS8tpEB5w39rbgHg/gqs0haGdZG4cPbywsgGzxZ7uNI=";
85    const TEST_SALT: &str = "[email protected]";
86
87    fn create_pbkdf2_response(
88        master_key_encrypted_user_key: Option<String>,
89        salt: Option<String>,
90        iterations: i32,
91    ) -> MasterPasswordUnlockResponseModel {
92        MasterPasswordUnlockResponseModel {
93            kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
94                kdf_type: KdfType::PBKDF2_SHA256,
95                iterations,
96                memory: None,
97                parallelism: None,
98            }),
99            master_key_encrypted_user_key,
100            salt,
101        }
102    }
103
104    #[test]
105    fn test_try_from_master_password_unlock_response_model_pbkdf2_success() {
106        let response = create_pbkdf2_response(
107            Some(TEST_USER_KEY.to_string()),
108            Some(TEST_SALT.to_string()),
109            600_000,
110        );
111
112        let data = MasterPasswordUnlockData::try_from(response).unwrap();
113
114        if let Kdf::PBKDF2 { iterations } = data.kdf {
115            assert_eq!(iterations.get(), 600_000);
116        } else {
117            panic!("Expected PBKDF2 KDF")
118        }
119
120        assert_eq!(data.salt, TEST_SALT);
121        assert_eq!(data.master_key_wrapped_user_key.to_string(), TEST_USER_KEY);
122    }
123
124    #[test]
125    fn test_try_from_master_password_unlock_response_model_argon2id_success() {
126        let response = MasterPasswordUnlockResponseModel {
127            kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
128                kdf_type: KdfType::Argon2id,
129                iterations: 3,
130                memory: Some(64),
131                parallelism: Some(4),
132            }),
133            master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
134            salt: Some(TEST_SALT.to_string()),
135        };
136
137        let data = MasterPasswordUnlockData::try_from(response).unwrap();
138
139        if let Kdf::Argon2id {
140            iterations,
141            memory,
142            parallelism,
143        } = data.kdf
144        {
145            assert_eq!(iterations.get(), 3);
146            assert_eq!(memory.get(), 64);
147            assert_eq!(parallelism.get(), 4);
148        } else {
149            panic!("Expected Argon2id KDF")
150        }
151
152        assert_eq!(data.salt, TEST_SALT);
153        assert_eq!(data.master_key_wrapped_user_key.to_string(), TEST_USER_KEY);
154    }
155
156    #[test]
157    fn test_try_from_master_password_unlock_response_model_invalid_user_key_encryption_kdf_malformed_error(
158    ) {
159        let response = create_pbkdf2_response(
160            Some(TEST_INVALID_USER_KEY.to_string()),
161            Some(TEST_SALT.to_string()),
162            600_000,
163        );
164
165        let result = MasterPasswordUnlockData::try_from(response);
166        assert!(matches!(
167            result,
168            Err(MasterPasswordError::EncryptionKeyMalformed)
169        ));
170    }
171
172    #[test]
173    fn test_try_from_master_password_unlock_response_model_user_key_none_missing_field_error() {
174        let response = create_pbkdf2_response(None, Some(TEST_SALT.to_string()), 600_000);
175
176        let result = MasterPasswordUnlockData::try_from(response);
177        assert!(matches!(
178            result,
179            Err(MasterPasswordError::MissingField(MissingFieldError(
180                "response.master_key_encrypted_user_key"
181            )))
182        ));
183    }
184
185    #[test]
186    fn test_try_from_master_password_unlock_response_model_salt_none_missing_field_error() {
187        let response = create_pbkdf2_response(Some(TEST_USER_KEY.to_string()), None, 600_000);
188
189        let result = MasterPasswordUnlockData::try_from(response);
190        assert!(matches!(
191            result,
192            Err(MasterPasswordError::MissingField(MissingFieldError(
193                "response.salt"
194            )))
195        ));
196    }
197
198    #[test]
199    fn test_try_from_master_password_unlock_response_model_argon2id_kdf_memory_none_missing_field_error(
200    ) {
201        let response = MasterPasswordUnlockResponseModel {
202            kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
203                kdf_type: KdfType::Argon2id,
204                iterations: 3,
205                memory: None,
206                parallelism: Some(4),
207            }),
208            master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
209            salt: Some(TEST_SALT.to_string()),
210        };
211
212        let result = MasterPasswordUnlockData::try_from(response);
213        assert!(matches!(
214            result,
215            Err(MasterPasswordError::MissingField(MissingFieldError(
216                "response.kdf.memory"
217            )))
218        ));
219    }
220
221    #[test]
222    fn test_try_from_master_password_unlock_response_model_argon2id_kdf_memory_zero_kdf_malformed_error(
223    ) {
224        let response = MasterPasswordUnlockResponseModel {
225            kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
226                kdf_type: KdfType::Argon2id,
227                iterations: 3,
228                memory: Some(0),
229                parallelism: Some(4),
230            }),
231            master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
232            salt: Some(TEST_SALT.to_string()),
233        };
234
235        let result = MasterPasswordUnlockData::try_from(response);
236        assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
237    }
238
239    #[test]
240    fn test_try_from_master_password_unlock_response_model_argon2id_kdf_parallelism_none_missing_field_error(
241    ) {
242        let response = MasterPasswordUnlockResponseModel {
243            kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
244                kdf_type: KdfType::Argon2id,
245                iterations: 3,
246                memory: Some(64),
247                parallelism: None,
248            }),
249            master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
250            salt: Some(TEST_SALT.to_string()),
251        };
252
253        let result = MasterPasswordUnlockData::try_from(response);
254        assert!(matches!(
255            result,
256            Err(MasterPasswordError::MissingField(MissingFieldError(
257                "response.kdf.parallelism"
258            )))
259        ));
260    }
261
262    #[test]
263    fn test_try_from_master_password_unlock_response_model_argon2id_kdf_parallelism_zero_kdf_malformed_error(
264    ) {
265        let response = MasterPasswordUnlockResponseModel {
266            kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
267                kdf_type: KdfType::Argon2id,
268                iterations: 3,
269                memory: Some(64),
270                parallelism: Some(0),
271            }),
272            master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
273            salt: Some(TEST_SALT.to_string()),
274        };
275
276        let result = MasterPasswordUnlockData::try_from(response);
277        assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
278    }
279
280    #[test]
281    fn test_try_from_master_password_unlock_response_model_pbkdf2_kdf_iterations_zero_kdf_malformed_error(
282    ) {
283        let response = create_pbkdf2_response(
284            Some(TEST_USER_KEY.to_string()),
285            Some(TEST_SALT.to_string()),
286            0,
287        );
288
289        let result = MasterPasswordUnlockData::try_from(response);
290        assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
291    }
292
293    #[test]
294    fn test_try_from_master_password_unlock_response_model_argon2id_kdf_iterations_zero_kdf_malformed_error(
295    ) {
296        let response = MasterPasswordUnlockResponseModel {
297            kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
298                kdf_type: KdfType::Argon2id,
299                iterations: 0,
300                memory: Some(64),
301                parallelism: Some(4),
302            }),
303            master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
304            salt: Some(TEST_SALT.to_string()),
305        };
306
307        let result = MasterPasswordUnlockData::try_from(response);
308        assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
309    }
310}