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#[allow(dead_code)]
17#[bitwarden_error(flat)]
18#[derive(Debug, thiserror::Error)]
19pub enum MasterPasswordError {
20 #[error("Wrapped encryption key is malformed")]
22 EncryptionKeyMalformed,
23 #[error("KDF is malformed")]
25 KdfMalformed,
26 #[error("Invalid KDF configuration")]
28 InvalidKdfConfiguration,
29 #[error(transparent)]
31 MissingField(#[from] MissingFieldError),
32 #[error(transparent)]
34 Crypto(#[from] bitwarden_crypto::CryptoError),
35}
36
37#[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 pub kdf: Kdf,
49 pub master_key_wrapped_user_key: EncString,
51 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#[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}