bitwarden_core/key_management/
master_password.rs1use 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#[allow(dead_code)]
14#[bitwarden_error(flat)]
15#[derive(Debug, thiserror::Error)]
16pub(crate) enum MasterPasswordError {
17 #[error("Wrapped encryption key is malformed")]
19 EncryptionKeyMalformed,
20 #[error("KDF is malformed")]
22 KdfMalformed,
23 #[error(transparent)]
25 MissingField(#[from] MissingFieldError),
26}
27
28#[allow(dead_code)]
30#[derive(Serialize, Deserialize, Debug)]
31#[serde(rename_all = "camelCase", deny_unknown_fields)]
32pub(crate) struct MasterPasswordUnlockData {
33 kdf: Kdf,
35 master_key_wrapped_user_key: EncString,
37 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}