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::{
9 EncString, Kdf, KeyId, KeySlotIds, KeyStoreContext, MasterKey, SymmetricCryptoKey,
10};
11use bitwarden_encoding::B64;
12use bitwarden_error::bitwarden_error;
13use serde::{Deserialize, Serialize};
14use tracing::Level;
15
16use crate::{MissingFieldError, require};
17
18#[allow(dead_code)]
20#[bitwarden_error(flat)]
21#[derive(Debug, thiserror::Error)]
22pub enum MasterPasswordError {
23 #[error("Wrapped encryption key is malformed")]
25 EncryptionKeyMalformed,
26 #[error("KDF is malformed")]
28 KdfMalformed,
29 #[error("Invalid KDF configuration")]
31 InvalidKdfConfiguration,
32 #[error("Key id is malformed")]
34 KeyIdMalformed,
35 #[error(transparent)]
37 MissingField(#[from] MissingFieldError),
38 #[error(transparent)]
40 Crypto(#[from] bitwarden_crypto::CryptoError),
41 #[error("Wrong password")]
43 WrongPassword,
44}
45
46#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
48#[serde(rename_all = "camelCase", deny_unknown_fields)]
49#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
50#[bitwarden_ffi::wasm_record]
51pub struct MasterPasswordUnlockData {
52 pub kdf: Kdf,
54 pub master_key_wrapped_user_key: EncString,
56 pub salt: String,
58 #[serde(default)]
61 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
62 #[cfg_attr(feature = "wasm", tsify(optional))]
63 pub contained_key_id: Option<KeyId>,
64}
65
66#[cfg(feature = "wasm")]
67impl TryFrom<wasm_bindgen::JsValue> for MasterPasswordUnlockData {
68 type Error = serde_wasm_bindgen::Error;
69
70 fn try_from(value: wasm_bindgen::JsValue) -> Result<Self, Self::Error> {
71 serde_wasm_bindgen::from_value(value)
72 }
73}
74
75impl MasterPasswordUnlockData {
76 pub fn unwrap_to_context<Ids: KeySlotIds>(
78 &self,
79 password: &str,
80 ctx: &mut KeyStoreContext<Ids>,
81 ) -> Result<Ids::Symmetric, MasterPasswordError> {
82 let master_key = MasterKey::derive(password, &self.salt, &self.kdf)
83 .map_err(|_| MasterPasswordError::InvalidKdfConfiguration)?;
84 let user_key = master_key
85 .decrypt_user_key(self.master_key_wrapped_user_key.clone())
86 .map_err(|_| MasterPasswordError::WrongPassword)?;
87 Ok(ctx.add_local_symmetric_key(user_key))
88 }
89
90 pub(crate) fn derive_ref(
91 password: &str,
92 kdf: &Kdf,
93 salt: &str,
94 user_key: &SymmetricCryptoKey,
95 ) -> Result<Self, MasterPasswordError> {
96 let master_key = MasterKey::derive(password, salt, kdf)
97 .map_err(|_| MasterPasswordError::InvalidKdfConfiguration)?;
98 let master_key_wrapped_user_key = master_key
99 .encrypt_user_key(user_key)
100 .map_err(MasterPasswordError::Crypto)?;
101
102 Ok(Self {
103 kdf: kdf.to_owned(),
104 salt: salt.to_owned(),
105 master_key_wrapped_user_key,
106 contained_key_id: user_key.key_id(),
107 })
108 }
109
110 #[bitwarden_logging::instrument(fields(kdf = ?kdf, user_key_id = ?user_key_id))]
112 pub fn derive<Ids: KeySlotIds>(
113 password: &str,
114 kdf: &Kdf,
115 salt: &str,
116 user_key_id: Ids::Symmetric,
117 ctx: &KeyStoreContext<Ids>,
118 ) -> Result<Self, MasterPasswordError> {
119 tracing::event!(Level::INFO, "deriving master password unlock data");
120 #[expect(deprecated)]
122 let key = ctx.dangerous_get_symmetric_key(user_key_id)?;
123 Self::derive_ref(password, kdf, salt, key)
124 }
125}
126
127impl TryFrom<&MasterPasswordUnlockResponseModel> for MasterPasswordUnlockData {
128 type Error = MasterPasswordError;
129
130 fn try_from(response: &MasterPasswordUnlockResponseModel) -> Result<Self, Self::Error> {
131 let kdf = match response.kdf.kdf_type {
132 KdfType::PBKDF2_SHA256 => Kdf::PBKDF2 {
133 iterations: kdf_parse_nonzero_u32(response.kdf.iterations)?,
134 },
135 KdfType::Argon2id => Kdf::Argon2id {
136 iterations: kdf_parse_nonzero_u32(response.kdf.iterations)?,
137 memory: kdf_parse_nonzero_u32(require!(response.kdf.memory))?,
138 parallelism: kdf_parse_nonzero_u32(require!(response.kdf.parallelism))?,
139 },
140 KdfType::__Unknown(_) => return Err(MasterPasswordError::KdfMalformed),
141 };
142
143 let master_key_wrapped_user_key =
144 EncString::parse_strict(require!(&response.master_key_encrypted_user_key))
145 .map_err(|_| MasterPasswordError::EncryptionKeyMalformed)?;
146
147 let salt = require!(&response.salt).clone();
148 let contained_key_id = response
149 .contained_key_id
150 .as_deref()
151 .map(str::parse)
152 .transpose()
153 .map_err(|_| MasterPasswordError::KeyIdMalformed)?;
154
155 Ok(MasterPasswordUnlockData {
156 kdf,
157 master_key_wrapped_user_key,
158 salt,
159 contained_key_id,
160 })
161 }
162}
163
164impl From<&MasterPasswordUnlockData> for MasterPasswordUnlockDataRequestModel {
165 fn from(data: &MasterPasswordUnlockData) -> Self {
166 Self {
167 kdf: Box::new(kdf_to_api_kdf_request_model(&data.kdf)),
168 master_key_wrapped_user_key: data.master_key_wrapped_user_key.to_string(),
169 salt: data.salt.to_owned(),
170 contained_key_id: data.contained_key_id.as_ref().map(|id| id.to_string()),
171 }
172 }
173}
174
175impl From<&MasterPasswordUnlockData>
176 for bitwarden_api_identity::models::MasterPasswordUnlockDataRequestModel
177{
178 fn from(data: &MasterPasswordUnlockData) -> Self {
179 Self {
180 kdf: Box::new(kdf_to_identity_kdf_request_model(&data.kdf)),
181 master_key_wrapped_user_key: data.master_key_wrapped_user_key.to_string(),
182 salt: data.salt.to_owned(),
183 contained_key_id: data.contained_key_id.as_ref().map(|id| id.to_string()),
184 }
185 }
186}
187
188fn kdf_parse_nonzero_u32(value: impl TryInto<u32>) -> Result<NonZeroU32, MasterPasswordError> {
189 value
190 .try_into()
191 .ok()
192 .and_then(NonZeroU32::new)
193 .ok_or(MasterPasswordError::KdfMalformed)
194}
195
196#[allow(missing_docs)]
198#[derive(Serialize, Deserialize, Clone, Debug)]
199#[serde(rename_all = "camelCase", deny_unknown_fields)]
200#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
201#[bitwarden_ffi::wasm_record]
202pub struct MasterPasswordAuthenticationData {
203 pub kdf: Kdf,
204 pub salt: String,
205 pub master_password_authentication_hash: B64,
206}
207
208impl MasterPasswordAuthenticationData {
209 #[bitwarden_logging::instrument]
211 pub fn derive(password: &str, kdf: &Kdf, salt: &str) -> Result<Self, MasterPasswordError> {
212 tracing::event!(Level::INFO, "deriving master password authentication data");
213 let master_key = MasterKey::derive(password, salt, kdf)
214 .map_err(|_| MasterPasswordError::InvalidKdfConfiguration)?;
215 let hash = master_key.derive_master_key_hash(
216 password.as_bytes(),
217 bitwarden_crypto::HashPurpose::ServerAuthorization,
218 );
219
220 Ok(Self {
221 kdf: kdf.to_owned(),
222 salt: salt.to_owned(),
223 master_password_authentication_hash: hash,
224 })
225 }
226}
227
228impl From<&MasterPasswordAuthenticationData> for MasterPasswordAuthenticationDataRequestModel {
229 fn from(data: &MasterPasswordAuthenticationData) -> Self {
230 Self {
231 kdf: Box::new(kdf_to_api_kdf_request_model(&data.kdf)),
232 master_password_authentication_hash: data
233 .master_password_authentication_hash
234 .to_string(),
235 salt: data.salt.to_owned(),
236 }
237 }
238}
239
240impl From<&MasterPasswordAuthenticationData>
241 for bitwarden_api_identity::models::MasterPasswordAuthenticationDataRequestModel
242{
243 fn from(data: &MasterPasswordAuthenticationData) -> Self {
244 Self {
245 kdf: Box::new(kdf_to_identity_kdf_request_model(&data.kdf)),
246 master_password_authentication_hash: data
247 .master_password_authentication_hash
248 .to_string(),
249 salt: data.salt.to_owned(),
250 }
251 }
252}
253
254fn kdf_to_api_kdf_request_model(kdf: &Kdf) -> KdfRequestModel {
255 match kdf {
256 Kdf::PBKDF2 { iterations } => KdfRequestModel {
257 kdf_type: KdfType::PBKDF2_SHA256,
258 iterations: iterations.get() as i32,
259 memory: None,
260 parallelism: None,
261 },
262 Kdf::Argon2id {
263 iterations,
264 memory,
265 parallelism,
266 } => KdfRequestModel {
267 kdf_type: KdfType::Argon2id,
268 iterations: iterations.get() as i32,
269 memory: Some(memory.get() as i32),
270 parallelism: Some(parallelism.get() as i32),
271 },
272 }
273}
274
275fn kdf_to_identity_kdf_request_model(kdf: &Kdf) -> bitwarden_api_identity::models::KdfRequestModel {
276 match kdf {
277 Kdf::PBKDF2 { iterations } => bitwarden_api_identity::models::KdfRequestModel {
278 kdf_type: bitwarden_api_identity::models::KdfType::PBKDF2_SHA256,
279 iterations: iterations.get() as i32,
280 memory: None,
281 parallelism: None,
282 },
283 Kdf::Argon2id {
284 iterations,
285 memory,
286 parallelism,
287 } => bitwarden_api_identity::models::KdfRequestModel {
288 kdf_type: bitwarden_api_identity::models::KdfType::Argon2id,
289 iterations: iterations.get() as i32,
290 memory: Some(memory.get() as i32),
291 parallelism: Some(parallelism.get() as i32),
292 },
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use bitwarden_api_api::models::{KdfType, MasterPasswordUnlockKdfResponseModel};
299 use bitwarden_crypto::{KeyStore, SymmetricKeyAlgorithm};
300
301 use super::*;
302 use crate::key_management::{KeySlotIds, SymmetricKeySlotId};
303
304 const TEST_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
305 const TEST_INVALID_USER_KEY: &str = "-1.8UClLa8IPE1iZT7chy5wzQ==|6PVfHnVk5S3XqEtQemnM5yb4JodxmPkkWzmDRdfyHtjORmvxqlLX40tBJZ+CKxQWmS8tpEB5w39rbgHg/gqs0haGdZG4cPbywsgGzxZ7uNI=";
306 const TEST_SALT: &str = "[email protected]";
307 const TEST_PASSWORD: &str = "test_password";
308 const TEST_MASTER_PASSWORD_AUTHENTICATION_HASH: &str =
309 "Lyry95vlXEJ5FE0EXjeR9zgcsFSU0qGhP9l5X2jwE38=";
310
311 #[test]
312 fn test_master_password_unlock_data_derive() {
313 let kdf = Kdf::PBKDF2 {
314 iterations: NonZeroU32::new(600_000).unwrap(),
315 };
316 let salt = TEST_SALT.to_string();
317 let user_key = SymmetricCryptoKey::make(SymmetricKeyAlgorithm::Aes256CbcHmac);
318 let data = MasterPasswordUnlockData::derive_ref(TEST_PASSWORD, &kdf, &salt, &user_key)
319 .expect("Failed to derive master password unlock data");
320 assert_eq!(data.salt, salt);
321 assert!(matches!(data.kdf, Kdf::PBKDF2 { iterations } if iterations.get() == 600_000));
322
323 let master_key = MasterKey::derive(TEST_PASSWORD, &salt, &data.kdf)
324 .expect("Failed to derive master key");
325 let decrypted_user_key = master_key
326 .decrypt_user_key(data.master_key_wrapped_user_key)
327 .expect("Failed to decrypt user key");
328 assert_eq!(decrypted_user_key, user_key);
329 }
330
331 #[test]
332 fn test_master_password_authentication_data_derive() {
333 let kdf = Kdf::PBKDF2 {
334 iterations: NonZeroU32::new(600_000).unwrap(),
335 };
336 let salt = TEST_SALT.to_string();
337 let data = MasterPasswordAuthenticationData::derive(TEST_PASSWORD, &kdf, &salt)
338 .expect("Failed to derive master password authentication data");
339 assert_eq!(data.salt, salt);
340 assert!(matches!(data.kdf, Kdf::PBKDF2 { iterations } if iterations.get() == 600_000));
341 assert_eq!(
342 data.master_password_authentication_hash.to_string(),
343 TEST_MASTER_PASSWORD_AUTHENTICATION_HASH
344 );
345 }
346
347 fn create_pbkdf2_response(
348 master_key_encrypted_user_key: Option<String>,
349 salt: Option<String>,
350 iterations: i32,
351 ) -> MasterPasswordUnlockResponseModel {
352 MasterPasswordUnlockResponseModel {
353 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
354 kdf_type: KdfType::PBKDF2_SHA256,
355 iterations,
356 memory: None,
357 parallelism: None,
358 }),
359 master_key_encrypted_user_key,
360 salt,
361 contained_key_id: None,
362 }
363 }
364
365 #[test]
366 fn test_try_from_master_password_unlock_response_model_pbkdf2_success() {
367 let response = create_pbkdf2_response(
368 Some(TEST_USER_KEY.to_string()),
369 Some(TEST_SALT.to_string()),
370 600_000,
371 );
372
373 let data = MasterPasswordUnlockData::try_from(&response).unwrap();
374
375 if let Kdf::PBKDF2 { iterations } = data.kdf {
376 assert_eq!(iterations.get(), 600_000);
377 } else {
378 panic!("Expected PBKDF2 KDF")
379 }
380
381 assert_eq!(data.salt, TEST_SALT);
382 assert_eq!(data.master_key_wrapped_user_key.to_string(), TEST_USER_KEY);
383 }
384
385 #[test]
386 fn test_try_from_master_password_unlock_response_model_argon2id_success() {
387 let response = MasterPasswordUnlockResponseModel {
388 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
389 kdf_type: KdfType::Argon2id,
390 iterations: 3,
391 memory: Some(64),
392 parallelism: Some(4),
393 }),
394 master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
395 salt: Some(TEST_SALT.to_string()),
396 contained_key_id: None,
397 };
398
399 let data = MasterPasswordUnlockData::try_from(&response).unwrap();
400
401 if let Kdf::Argon2id {
402 iterations,
403 memory,
404 parallelism,
405 } = data.kdf
406 {
407 assert_eq!(iterations.get(), 3);
408 assert_eq!(memory.get(), 64);
409 assert_eq!(parallelism.get(), 4);
410 } else {
411 panic!("Expected Argon2id KDF")
412 }
413
414 assert_eq!(data.salt, TEST_SALT);
415 assert_eq!(data.master_key_wrapped_user_key.to_string(), TEST_USER_KEY);
416 }
417
418 #[test]
419 fn test_try_from_master_password_unlock_response_model_invalid_user_key_encryption_kdf_malformed_error()
420 {
421 let response = create_pbkdf2_response(
422 Some(TEST_INVALID_USER_KEY.to_string()),
423 Some(TEST_SALT.to_string()),
424 600_000,
425 );
426
427 let result = MasterPasswordUnlockData::try_from(&response);
428 assert!(matches!(
429 result,
430 Err(MasterPasswordError::EncryptionKeyMalformed)
431 ));
432 }
433
434 #[test]
435 fn test_try_from_master_password_unlock_response_model_user_key_none_missing_field_error() {
436 let response = create_pbkdf2_response(None, Some(TEST_SALT.to_string()), 600_000);
437
438 let result = MasterPasswordUnlockData::try_from(&response);
439 assert!(matches!(
440 result,
441 Err(MasterPasswordError::MissingField(MissingFieldError(
442 "&response.master_key_encrypted_user_key"
443 )))
444 ));
445 }
446
447 #[test]
448 fn test_try_from_master_password_unlock_response_model_salt_none_missing_field_error() {
449 let response = create_pbkdf2_response(Some(TEST_USER_KEY.to_string()), None, 600_000);
450
451 let result = MasterPasswordUnlockData::try_from(&response);
452 assert!(matches!(
453 result,
454 Err(MasterPasswordError::MissingField(MissingFieldError(
455 "&response.salt"
456 )))
457 ));
458 }
459
460 #[test]
461 fn test_try_from_master_password_unlock_response_model_argon2id_kdf_memory_none_missing_field_error()
462 {
463 let response = MasterPasswordUnlockResponseModel {
464 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
465 kdf_type: KdfType::Argon2id,
466 iterations: 3,
467 memory: None,
468 parallelism: Some(4),
469 }),
470 master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
471 salt: Some(TEST_SALT.to_string()),
472 contained_key_id: None,
473 };
474
475 let result = MasterPasswordUnlockData::try_from(&response);
476 assert!(matches!(
477 result,
478 Err(MasterPasswordError::MissingField(MissingFieldError(
479 "response.kdf.memory"
480 )))
481 ));
482 }
483
484 #[test]
485 fn test_try_from_master_password_unlock_response_model_argon2id_kdf_memory_zero_kdf_malformed_error()
486 {
487 let response = MasterPasswordUnlockResponseModel {
488 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
489 kdf_type: KdfType::Argon2id,
490 iterations: 3,
491 memory: Some(0),
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 contained_key_id: None,
497 };
498
499 let result = MasterPasswordUnlockData::try_from(&response);
500 assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
501 }
502
503 #[test]
504 fn test_try_from_master_password_unlock_response_model_argon2id_kdf_parallelism_none_missing_field_error()
505 {
506 let response = MasterPasswordUnlockResponseModel {
507 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
508 kdf_type: KdfType::Argon2id,
509 iterations: 3,
510 memory: Some(64),
511 parallelism: None,
512 }),
513 master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
514 salt: Some(TEST_SALT.to_string()),
515 contained_key_id: None,
516 };
517
518 let result = MasterPasswordUnlockData::try_from(&response);
519 assert!(matches!(
520 result,
521 Err(MasterPasswordError::MissingField(MissingFieldError(
522 "response.kdf.parallelism"
523 )))
524 ));
525 }
526
527 #[test]
528 fn test_try_from_master_password_unlock_response_model_argon2id_kdf_parallelism_zero_kdf_malformed_error()
529 {
530 let response = MasterPasswordUnlockResponseModel {
531 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
532 kdf_type: KdfType::Argon2id,
533 iterations: 3,
534 memory: Some(64),
535 parallelism: Some(0),
536 }),
537 master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
538 salt: Some(TEST_SALT.to_string()),
539 contained_key_id: None,
540 };
541
542 let result = MasterPasswordUnlockData::try_from(&response);
543 assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
544 }
545
546 #[test]
547 fn test_try_from_master_password_unlock_response_model_pbkdf2_kdf_iterations_zero_kdf_malformed_error()
548 {
549 let response = create_pbkdf2_response(
550 Some(TEST_USER_KEY.to_string()),
551 Some(TEST_SALT.to_string()),
552 0,
553 );
554
555 let result = MasterPasswordUnlockData::try_from(&response);
556 assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
557 }
558
559 #[test]
560 fn test_try_from_master_password_unlock_response_model_argon2id_kdf_iterations_zero_kdf_malformed_error()
561 {
562 let response = MasterPasswordUnlockResponseModel {
563 kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
564 kdf_type: KdfType::Argon2id,
565 iterations: 0,
566 memory: Some(64),
567 parallelism: Some(4),
568 }),
569 master_key_encrypted_user_key: Some(TEST_USER_KEY.to_string()),
570 salt: Some(TEST_SALT.to_string()),
571 contained_key_id: None,
572 };
573
574 let result = MasterPasswordUnlockData::try_from(&response);
575 assert!(matches!(result, Err(MasterPasswordError::KdfMalformed)));
576 }
577
578 #[test]
579 fn test_unwrap_to_context_success() {
580 let kdf = Kdf::PBKDF2 {
582 iterations: NonZeroU32::new(600_000).expect("non-zero"),
583 };
584 let user_key = SymmetricCryptoKey::make(SymmetricKeyAlgorithm::Aes256CbcHmac);
585 let data = MasterPasswordUnlockData::derive_ref(TEST_PASSWORD, &kdf, TEST_SALT, &user_key)
586 .expect("Failed to derive master password unlock data");
587
588 let store: KeyStore<KeySlotIds> = KeyStore::default();
590 let mut ctx = store.context_mut();
591 let key_id = data
592 .unwrap_to_context::<KeySlotIds>(TEST_PASSWORD, &mut ctx)
593 .expect("Failed to unwrap to context");
594
595 assert!(ctx.has_symmetric_key(key_id));
597
598 #[expect(deprecated)]
600 let unwrapped_key = ctx
601 .dangerous_get_symmetric_key(key_id)
602 .expect("Failed to get symmetric key");
603 assert_eq!(*unwrapped_key, user_key);
604 }
605
606 #[test]
607 fn test_unwrap_to_context_wrong_password() {
608 let kdf = Kdf::PBKDF2 {
610 iterations: NonZeroU32::new(600_000).expect("non-zero"),
611 };
612 let user_key = SymmetricCryptoKey::make(SymmetricKeyAlgorithm::Aes256CbcHmac);
613 let data = MasterPasswordUnlockData::derive_ref(TEST_PASSWORD, &kdf, TEST_SALT, &user_key)
614 .expect("Failed to derive master password unlock data");
615
616 let store: KeyStore<KeySlotIds> = KeyStore::default();
618 let mut ctx = store.context_mut();
619 let result = data.unwrap_to_context::<KeySlotIds>("wrong_password", &mut ctx);
620
621 assert!(matches!(result, Err(MasterPasswordError::WrongPassword)));
622 }
623
624 #[test]
625 fn test_unwrap_to_context_persists_key() {
626 let kdf = Kdf::PBKDF2 {
628 iterations: NonZeroU32::new(600_000).expect("non-zero"),
629 };
630 let user_key = SymmetricCryptoKey::make(SymmetricKeyAlgorithm::Aes256CbcHmac);
631 let data = MasterPasswordUnlockData::derive_ref(TEST_PASSWORD, &kdf, TEST_SALT, &user_key)
632 .expect("Failed to derive master password unlock data");
633
634 let store: KeyStore<KeySlotIds> = KeyStore::default();
636 {
637 let mut ctx = store.context_mut();
638 let local_key_id = data
639 .unwrap_to_context::<KeySlotIds>(TEST_PASSWORD, &mut ctx)
640 .expect("Failed to unwrap to context");
641
642 ctx.persist_symmetric_key(local_key_id, SymmetricKeySlotId::User)
644 .expect("Failed to persist symmetric key");
645 }
646
647 let ctx = store.context();
649 assert!(ctx.has_symmetric_key(SymmetricKeySlotId::User));
650 }
651}