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