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