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