1use std::{num::TryFromIntError, str::FromStr};
17
18use argon2::Params;
19use bitwarden_encoding::{B64, FromStrVisitor};
20use ciborium::{Value, value::Integer};
21use coset::{CborSerializable, CoseError, Header, HeaderBuilder};
22use rand::RngCore;
23use serde::{Deserialize, Serialize};
24use thiserror::Error;
25#[cfg(feature = "wasm")]
26use wasm_bindgen::convert::FromWasmAbi;
27
28use crate::{
29 BitwardenLegacyKeyBytes, ContentFormat, CoseKeyBytes, CryptoError, EncodedSymmetricKey, KeyIds,
30 KeyStoreContext, SymmetricCryptoKey,
31 cose::{
32 ALG_ARGON2ID13, ARGON2_ITERATIONS, ARGON2_MEMORY, ARGON2_PARALLELISM, ARGON2_SALT,
33 CoseExtractError, extract_bytes, extract_integer,
34 },
35 xchacha20,
36};
37
38const ENVELOPE_ARGON2_SALT_SIZE: usize = 16;
41const ENVELOPE_ARGON2_OUTPUT_KEY_SIZE: usize = 32;
43
44pub struct PasswordProtectedKeyEnvelope {
53 cose_encrypt: coset::CoseEncrypt,
54}
55
56impl PasswordProtectedKeyEnvelope {
57 pub fn seal<Ids: KeyIds>(
62 key_to_seal: Ids::Symmetric,
63 password: &str,
64 ctx: &KeyStoreContext<Ids>,
65 ) -> Result<Self, PasswordProtectedKeyEnvelopeError> {
66 #[allow(deprecated)]
67 let key_ref = ctx
68 .dangerous_get_symmetric_key(key_to_seal)
69 .map_err(|_| PasswordProtectedKeyEnvelopeError::KeyMissing)?;
70 Self::seal_ref(key_ref, password)
71 }
72
73 fn seal_ref(
76 key_to_seal: &SymmetricCryptoKey,
77 password: &str,
78 ) -> Result<Self, PasswordProtectedKeyEnvelopeError> {
79 Self::seal_ref_with_settings(
80 key_to_seal,
81 password,
82 &Argon2RawSettings::local_kdf_settings(),
83 )
84 }
85
86 fn seal_ref_with_settings(
90 key_to_seal: &SymmetricCryptoKey,
91 password: &str,
92 kdf_settings: &Argon2RawSettings,
93 ) -> Result<Self, PasswordProtectedKeyEnvelopeError> {
94 let envelope_key = derive_key(kdf_settings, password)
102 .map_err(|_| PasswordProtectedKeyEnvelopeError::Kdf)?;
103
104 let (content_format, key_to_seal_bytes) = match key_to_seal.to_encoded_raw() {
105 EncodedSymmetricKey::BitwardenLegacyKey(key_bytes) => {
106 (ContentFormat::BitwardenLegacyKey, key_bytes.to_vec())
107 }
108 EncodedSymmetricKey::CoseKey(key_bytes) => (ContentFormat::CoseKey, key_bytes.to_vec()),
109 };
110
111 let mut nonce = [0u8; crate::xchacha20::NONCE_SIZE];
112
113 let mut cose_encrypt = coset::CoseEncryptBuilder::new()
117 .add_recipient({
118 let mut recipient = coset::CoseRecipientBuilder::new()
119 .unprotected(kdf_settings.into())
120 .build();
121 recipient.protected.header.alg = Some(coset::Algorithm::PrivateUse(ALG_ARGON2ID13));
122 recipient
123 })
124 .protected(HeaderBuilder::from(content_format).build())
125 .create_ciphertext(&key_to_seal_bytes, &[], |data, aad| {
126 let ciphertext = xchacha20::encrypt_xchacha20_poly1305(&envelope_key, data, aad);
127 nonce.copy_from_slice(&ciphertext.nonce());
128 ciphertext.encrypted_bytes().to_vec()
129 })
130 .build();
131 cose_encrypt.unprotected.iv = nonce.into();
132
133 Ok(PasswordProtectedKeyEnvelope { cose_encrypt })
134 }
135
136 pub fn unseal<Ids: KeyIds>(
139 &self,
140 password: &str,
141 ctx: &mut KeyStoreContext<Ids>,
142 ) -> Result<Ids::Symmetric, PasswordProtectedKeyEnvelopeError> {
143 let key = self.unseal_ref(password)?;
144 Ok(ctx.add_local_symmetric_key(key))
145 }
146
147 fn unseal_ref(
148 &self,
149 password: &str,
150 ) -> Result<SymmetricCryptoKey, PasswordProtectedKeyEnvelopeError> {
151 let recipient = self
154 .cose_encrypt
155 .recipients
156 .first()
157 .filter(|_| self.cose_encrypt.recipients.len() == 1)
158 .ok_or_else(|| {
159 PasswordProtectedKeyEnvelopeError::Parsing(
160 "Invalid number of recipients".to_string(),
161 )
162 })?;
163
164 if recipient.protected.header.alg != Some(coset::Algorithm::PrivateUse(ALG_ARGON2ID13)) {
165 return Err(PasswordProtectedKeyEnvelopeError::Parsing(
166 "Unknown or unsupported KDF algorithm".to_string(),
167 ));
168 }
169
170 let kdf_settings: Argon2RawSettings =
171 (&recipient.unprotected).try_into().map_err(|_| {
172 PasswordProtectedKeyEnvelopeError::Parsing(
173 "Invalid or missing KDF parameters".to_string(),
174 )
175 })?;
176 let envelope_key = derive_key(&kdf_settings, password)
177 .map_err(|_| PasswordProtectedKeyEnvelopeError::Kdf)?;
178 let nonce: [u8; crate::xchacha20::NONCE_SIZE] = self
179 .cose_encrypt
180 .unprotected
181 .iv
182 .clone()
183 .try_into()
184 .map_err(|_| PasswordProtectedKeyEnvelopeError::Parsing("Invalid IV".to_string()))?;
185
186 let key_bytes = self
187 .cose_encrypt
188 .decrypt_ciphertext(
189 &[],
190 || CryptoError::MissingField("ciphertext"),
191 |data, aad| xchacha20::decrypt_xchacha20_poly1305(&nonce, &envelope_key, data, aad),
192 )
193 .map_err(|_| PasswordProtectedKeyEnvelopeError::WrongPassword)?;
196
197 SymmetricCryptoKey::try_from(
198 match ContentFormat::try_from(&self.cose_encrypt.protected.header).map_err(|_| {
199 PasswordProtectedKeyEnvelopeError::Parsing("Invalid content format".to_string())
200 })? {
201 ContentFormat::BitwardenLegacyKey => EncodedSymmetricKey::BitwardenLegacyKey(
202 BitwardenLegacyKeyBytes::from(key_bytes),
203 ),
204 ContentFormat::CoseKey => {
205 EncodedSymmetricKey::CoseKey(CoseKeyBytes::from(key_bytes))
206 }
207 _ => {
208 return Err(PasswordProtectedKeyEnvelopeError::Parsing(
209 "Unknown or unsupported content format".to_string(),
210 ));
211 }
212 },
213 )
214 .map_err(|_| PasswordProtectedKeyEnvelopeError::Parsing("Failed to decode key".to_string()))
215 }
216
217 pub fn reseal(
219 &self,
220 password: &str,
221 new_password: &str,
222 ) -> Result<Self, PasswordProtectedKeyEnvelopeError> {
223 let unsealed = self.unseal_ref(password)?;
224 Self::seal_ref(&unsealed, new_password)
225 }
226}
227
228impl From<&PasswordProtectedKeyEnvelope> for Vec<u8> {
229 fn from(val: &PasswordProtectedKeyEnvelope) -> Self {
230 val.cose_encrypt
231 .clone()
232 .to_vec()
233 .expect("Serialization to cose should not fail")
234 }
235}
236
237impl TryFrom<&Vec<u8>> for PasswordProtectedKeyEnvelope {
238 type Error = CoseError;
239
240 fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
241 let cose_encrypt = coset::CoseEncrypt::from_slice(value)?;
242 Ok(PasswordProtectedKeyEnvelope { cose_encrypt })
243 }
244}
245
246impl std::fmt::Debug for PasswordProtectedKeyEnvelope {
247 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248 f.debug_struct("PasswordProtectedKeyEnvelope")
249 .field("cose_encrypt", &self.cose_encrypt)
250 .finish()
251 }
252}
253
254impl FromStr for PasswordProtectedKeyEnvelope {
255 type Err = PasswordProtectedKeyEnvelopeError;
256
257 fn from_str(s: &str) -> Result<Self, Self::Err> {
258 let data = B64::try_from(s).map_err(|_| {
259 PasswordProtectedKeyEnvelopeError::Parsing(
260 "Invalid PasswordProtectedKeyEnvelope Base64 encoding".to_string(),
261 )
262 })?;
263 Self::try_from(&data.into_bytes()).map_err(|_| {
264 PasswordProtectedKeyEnvelopeError::Parsing(
265 "Failed to parse PasswordProtectedKeyEnvelope".to_string(),
266 )
267 })
268 }
269}
270
271impl From<PasswordProtectedKeyEnvelope> for String {
272 fn from(val: PasswordProtectedKeyEnvelope) -> Self {
273 let serialized: Vec<u8> = (&val).into();
274 B64::from(serialized).to_string()
275 }
276}
277
278impl<'de> Deserialize<'de> for PasswordProtectedKeyEnvelope {
279 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
280 where
281 D: serde::Deserializer<'de>,
282 {
283 deserializer.deserialize_str(FromStrVisitor::new())
284 }
285}
286
287impl Serialize for PasswordProtectedKeyEnvelope {
288 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
289 where
290 S: serde::Serializer,
291 {
292 let serialized: Vec<u8> = self.into();
293 serializer.serialize_str(&B64::from(serialized).to_string())
294 }
295}
296
297struct Argon2RawSettings {
302 iterations: u32,
303 memory: u32,
305 parallelism: u32,
306 salt: [u8; ENVELOPE_ARGON2_SALT_SIZE],
307}
308
309impl Argon2RawSettings {
310 fn local_kdf_settings() -> Self {
313 if cfg!(target_os = "ios") {
316 Self {
318 iterations: 6,
319 memory: 32 * 1024, parallelism: 4,
321 salt: make_salt(),
322 }
323 } else {
324 Self {
328 iterations: 3,
329 memory: 64 * 1024, parallelism: 4,
331 salt: make_salt(),
332 }
333 }
334 }
335}
336
337impl From<&Argon2RawSettings> for Header {
338 fn from(settings: &Argon2RawSettings) -> Header {
339 let builder = HeaderBuilder::new()
340 .value(ARGON2_ITERATIONS, Integer::from(settings.iterations).into())
341 .value(ARGON2_MEMORY, Integer::from(settings.memory).into())
342 .value(
343 ARGON2_PARALLELISM,
344 Integer::from(settings.parallelism).into(),
345 )
346 .value(ARGON2_SALT, Value::from(settings.salt.to_vec()));
347
348 let mut header = builder.build();
349 header.alg = Some(coset::Algorithm::PrivateUse(ALG_ARGON2ID13));
350 header
351 }
352}
353
354impl TryInto<Params> for &Argon2RawSettings {
355 type Error = PasswordProtectedKeyEnvelopeError;
356
357 fn try_into(self) -> Result<Params, PasswordProtectedKeyEnvelopeError> {
358 Params::new(
359 self.memory,
360 self.iterations,
361 self.parallelism,
362 Some(ENVELOPE_ARGON2_OUTPUT_KEY_SIZE),
363 )
364 .map_err(|_| PasswordProtectedKeyEnvelopeError::Kdf)
365 }
366}
367
368impl TryInto<Argon2RawSettings> for &Header {
369 type Error = PasswordProtectedKeyEnvelopeError;
370
371 fn try_into(self) -> Result<Argon2RawSettings, PasswordProtectedKeyEnvelopeError> {
372 Ok(Argon2RawSettings {
373 iterations: extract_integer(self, ARGON2_ITERATIONS, "iterations")?.try_into()?,
374 memory: extract_integer(self, ARGON2_MEMORY, "memory")?.try_into()?,
375 parallelism: extract_integer(self, ARGON2_PARALLELISM, "parallelism")?.try_into()?,
376 salt: extract_bytes(self, ARGON2_SALT, "salt")?
377 .try_into()
378 .map_err(|_| {
379 PasswordProtectedKeyEnvelopeError::Parsing("Invalid Argon2 salt".to_string())
380 })?,
381 })
382 }
383}
384
385fn make_salt() -> [u8; ENVELOPE_ARGON2_SALT_SIZE] {
386 let mut salt = [0u8; ENVELOPE_ARGON2_SALT_SIZE];
387 rand::thread_rng().fill_bytes(&mut salt);
388 salt
389}
390
391fn derive_key(
392 argon2_settings: &Argon2RawSettings,
393 password: &str,
394) -> Result<[u8; ENVELOPE_ARGON2_OUTPUT_KEY_SIZE], PasswordProtectedKeyEnvelopeError> {
395 use argon2::*;
396
397 let mut hash = [0u8; ENVELOPE_ARGON2_OUTPUT_KEY_SIZE];
398 Argon2::new(
399 Algorithm::Argon2id,
400 Version::V0x13,
401 argon2_settings.try_into()?,
402 )
403 .hash_password_into(password.as_bytes(), &argon2_settings.salt, &mut hash)
404 .map_err(|_| PasswordProtectedKeyEnvelopeError::Kdf)?;
405
406 Ok(hash)
407}
408
409#[derive(Debug, Error)]
411pub enum PasswordProtectedKeyEnvelopeError {
412 #[error("Wrong password")]
414 WrongPassword,
415 #[error("Parsing error {0}")]
417 Parsing(String),
418 #[error("Kdf error")]
421 Kdf,
422 #[error("Key missing error")]
424 KeyMissing,
425 #[error("Could not write to key store")]
427 KeyStore,
428}
429
430impl From<CoseExtractError> for PasswordProtectedKeyEnvelopeError {
431 fn from(err: CoseExtractError) -> Self {
432 let CoseExtractError::MissingValue(label) = err;
433 PasswordProtectedKeyEnvelopeError::Parsing(format!("Missing value for {}", label))
434 }
435}
436
437impl From<TryFromIntError> for PasswordProtectedKeyEnvelopeError {
438 fn from(err: TryFromIntError) -> Self {
439 PasswordProtectedKeyEnvelopeError::Parsing(format!("Invalid integer: {}", err))
440 }
441}
442
443#[cfg(feature = "wasm")]
444#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
445const TS_CUSTOM_TYPES: &'static str = r#"
446export type PasswordProtectedKeyEnvelope = Tagged<string, "PasswordProtectedKeyEnvelope">;
447"#;
448
449#[cfg(feature = "wasm")]
450impl wasm_bindgen::describe::WasmDescribe for PasswordProtectedKeyEnvelope {
451 fn describe() {
452 <String as wasm_bindgen::describe::WasmDescribe>::describe();
453 }
454}
455
456#[cfg(feature = "wasm")]
457impl FromWasmAbi for PasswordProtectedKeyEnvelope {
458 type Abi = <String as FromWasmAbi>::Abi;
459
460 unsafe fn from_abi(abi: Self::Abi) -> Self {
461 use wasm_bindgen::UnwrapThrowExt;
462 let string = unsafe { String::from_abi(abi) };
463 PasswordProtectedKeyEnvelope::from_str(&string).unwrap_throw()
464 }
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470 use crate::{KeyStore, SymmetricKeyAlgorithm, traits::tests::TestIds};
471
472 const TEST_UNSEALED_COSEKEY_ENCODED: &[u8] = &[
473 165, 1, 4, 2, 80, 63, 208, 189, 183, 204, 37, 72, 170, 179, 236, 190, 208, 22, 65, 227,
474 183, 3, 58, 0, 1, 17, 111, 4, 132, 3, 4, 5, 6, 32, 88, 32, 88, 25, 68, 85, 205, 28, 133,
475 28, 90, 147, 160, 145, 48, 3, 178, 184, 30, 11, 122, 132, 64, 59, 51, 233, 191, 117, 159,
476 117, 23, 168, 248, 36, 1,
477 ];
478 const TESTVECTOR_COSEKEY_ENVELOPE: &[u8] = &[
479 132, 68, 161, 3, 24, 101, 161, 5, 88, 24, 1, 31, 58, 230, 10, 92, 195, 233, 212, 7, 166,
480 252, 67, 115, 221, 58, 3, 191, 218, 188, 181, 192, 28, 11, 88, 84, 141, 183, 137, 167, 166,
481 161, 33, 82, 30, 255, 23, 10, 179, 149, 88, 24, 39, 60, 74, 232, 133, 44, 90, 98, 117, 31,
482 41, 69, 251, 76, 250, 141, 229, 83, 191, 6, 237, 107, 127, 93, 238, 110, 49, 125, 201, 37,
483 162, 120, 157, 32, 116, 195, 208, 143, 83, 254, 223, 93, 97, 158, 0, 24, 95, 197, 249, 35,
484 240, 3, 20, 71, 164, 97, 180, 29, 203, 69, 31, 151, 249, 244, 197, 91, 101, 174, 129, 131,
485 71, 161, 1, 58, 0, 1, 21, 87, 165, 1, 58, 0, 1, 21, 87, 58, 0, 1, 21, 89, 3, 58, 0, 1, 21,
486 90, 26, 0, 1, 0, 0, 58, 0, 1, 21, 91, 4, 58, 0, 1, 21, 88, 80, 165, 253, 56, 243, 255, 54,
487 246, 252, 231, 230, 33, 252, 49, 175, 1, 111, 246,
488 ];
489 const TEST_UNSEALED_LEGACYKEY_ENCODED: &[u8] = &[
490 135, 114, 97, 155, 115, 209, 215, 224, 175, 159, 231, 208, 15, 244, 40, 171, 239, 137, 57,
491 98, 207, 167, 231, 138, 145, 254, 28, 136, 236, 60, 23, 163, 4, 246, 219, 117, 104, 246,
492 86, 10, 152, 52, 90, 85, 58, 6, 70, 39, 111, 128, 93, 145, 143, 180, 77, 129, 178, 242, 82,
493 72, 57, 61, 192, 64,
494 ];
495 const TESTVECTOR_LEGACYKEY_ENVELOPE: &[u8] = &[
496 132, 88, 38, 161, 3, 120, 34, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 120,
497 46, 98, 105, 116, 119, 97, 114, 100, 101, 110, 46, 108, 101, 103, 97, 99, 121, 45, 107,
498 101, 121, 161, 5, 88, 24, 218, 72, 22, 79, 149, 30, 12, 36, 180, 212, 44, 21, 167, 208,
499 214, 221, 7, 91, 178, 12, 104, 17, 45, 219, 88, 80, 114, 38, 14, 165, 85, 229, 103, 108,
500 17, 175, 41, 43, 203, 175, 119, 125, 227, 127, 163, 214, 213, 138, 12, 216, 163, 204, 38,
501 222, 47, 11, 44, 231, 239, 170, 63, 8, 249, 56, 102, 18, 134, 34, 232, 193, 44, 19, 228,
502 17, 187, 199, 238, 187, 2, 13, 30, 112, 103, 110, 5, 31, 238, 58, 4, 24, 19, 239, 135, 57,
503 206, 190, 144, 83, 128, 204, 59, 155, 21, 80, 180, 34, 129, 131, 71, 161, 1, 58, 0, 1, 21,
504 87, 165, 1, 58, 0, 1, 21, 87, 58, 0, 1, 21, 89, 3, 58, 0, 1, 21, 90, 26, 0, 1, 0, 0, 58, 0,
505 1, 21, 91, 4, 58, 0, 1, 21, 88, 80, 212, 91, 185, 112, 92, 177, 108, 33, 182, 202, 26, 141,
506 11, 133, 95, 235, 246,
507 ];
508
509 const TESTVECTOR_PASSWORD: &str = "test_password";
510
511 #[test]
512 fn test_testvector_cosekey() {
513 let key_store = KeyStore::<TestIds>::default();
514 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
515 let envelope =
516 PasswordProtectedKeyEnvelope::try_from(&TESTVECTOR_COSEKEY_ENVELOPE.to_vec())
517 .expect("Key envelope should be valid");
518 let key = envelope
519 .unseal(TESTVECTOR_PASSWORD, &mut ctx)
520 .expect("Unsealing should succeed");
521 #[allow(deprecated)]
522 let unsealed_key = ctx
523 .dangerous_get_symmetric_key(key)
524 .expect("Key should exist in the key store");
525 assert_eq!(
526 unsealed_key.to_encoded().to_vec(),
527 TEST_UNSEALED_COSEKEY_ENCODED
528 );
529 }
530
531 #[test]
532 fn test_testvector_legacykey() {
533 let key_store = KeyStore::<TestIds>::default();
534 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
535 let envelope =
536 PasswordProtectedKeyEnvelope::try_from(&TESTVECTOR_LEGACYKEY_ENVELOPE.to_vec())
537 .expect("Key envelope should be valid");
538 let key = envelope
539 .unseal(TESTVECTOR_PASSWORD, &mut ctx)
540 .expect("Unsealing should succeed");
541 #[allow(deprecated)]
542 let unsealed_key = ctx
543 .dangerous_get_symmetric_key(key)
544 .expect("Key should exist in the key store");
545 assert_eq!(
546 unsealed_key.to_encoded().to_vec(),
547 TEST_UNSEALED_LEGACYKEY_ENCODED
548 );
549 }
550
551 #[test]
552 fn test_make_envelope() {
553 let key_store = KeyStore::<TestIds>::default();
554 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
555 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
556
557 let password = "test_password";
558
559 let envelope = PasswordProtectedKeyEnvelope::seal(test_key, password, &ctx).unwrap();
561 let serialized: Vec<u8> = (&envelope).into();
562
563 let deserialized: PasswordProtectedKeyEnvelope =
565 PasswordProtectedKeyEnvelope::try_from(&serialized).unwrap();
566 let key = deserialized.unseal(password, &mut ctx).unwrap();
567
568 #[allow(deprecated)]
570 let unsealed_key = ctx
571 .dangerous_get_symmetric_key(key)
572 .expect("Key should exist in the key store");
573
574 #[allow(deprecated)]
575 let key_before_sealing = ctx
576 .dangerous_get_symmetric_key(test_key)
577 .expect("Key should exist in the key store");
578
579 assert_eq!(unsealed_key, key_before_sealing);
580 }
581
582 #[test]
583 fn test_make_envelope_legacy_key() {
584 let key_store = KeyStore::<TestIds>::default();
585 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
586 let test_key = ctx.generate_symmetric_key();
587
588 let password = "test_password";
589
590 let envelope = PasswordProtectedKeyEnvelope::seal(test_key, password, &ctx).unwrap();
592 let serialized: Vec<u8> = (&envelope).into();
593
594 let deserialized: PasswordProtectedKeyEnvelope =
596 PasswordProtectedKeyEnvelope::try_from(&serialized).unwrap();
597 let key = deserialized.unseal(password, &mut ctx).unwrap();
598
599 #[allow(deprecated)]
601 let unsealed_key = ctx
602 .dangerous_get_symmetric_key(key)
603 .expect("Key should exist in the key store");
604
605 #[allow(deprecated)]
606 let key_before_sealing = ctx
607 .dangerous_get_symmetric_key(test_key)
608 .expect("Key should exist in the key store");
609
610 assert_eq!(unsealed_key, key_before_sealing);
611 }
612
613 #[test]
614 fn test_reseal_envelope() {
615 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
616 let password = "test_password";
617 let new_password = "new_test_password";
618
619 let envelope: PasswordProtectedKeyEnvelope =
621 PasswordProtectedKeyEnvelope::seal_ref(&key, password).expect("Sealing should work");
622
623 let envelope = envelope
625 .reseal(password, new_password)
626 .expect("Resealing should work");
627 let unsealed = envelope
628 .unseal_ref(new_password)
629 .expect("Unsealing should work");
630
631 assert_eq!(unsealed, key);
633 }
634
635 #[test]
636 fn test_wrong_password() {
637 let key_store = KeyStore::<TestIds>::default();
638 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
639 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
640
641 let password = "test_password";
642 let wrong_password = "wrong_password";
643
644 let envelope = PasswordProtectedKeyEnvelope::seal(test_key, password, &ctx).unwrap();
646
647 let deserialized: PasswordProtectedKeyEnvelope =
649 PasswordProtectedKeyEnvelope::try_from(&(&envelope).into()).unwrap();
650 assert!(matches!(
651 deserialized.unseal(wrong_password, &mut ctx),
652 Err(PasswordProtectedKeyEnvelopeError::WrongPassword)
653 ));
654 }
655}