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::{
469 KeyStore,
470 traits::tests::{TestIds, TestSymmKey},
471 };
472
473 const TEST_UNSEALED_COSEKEY_ENCODED: &[u8] = &[
474 165, 1, 4, 2, 80, 63, 208, 189, 183, 204, 37, 72, 170, 179, 236, 190, 208, 22, 65, 227,
475 183, 3, 58, 0, 1, 17, 111, 4, 132, 3, 4, 5, 6, 32, 88, 32, 88, 25, 68, 85, 205, 28, 133,
476 28, 90, 147, 160, 145, 48, 3, 178, 184, 30, 11, 122, 132, 64, 59, 51, 233, 191, 117, 159,
477 117, 23, 168, 248, 36, 1,
478 ];
479 const TESTVECTOR_COSEKEY_ENVELOPE: &[u8] = &[
480 132, 68, 161, 3, 24, 101, 161, 5, 88, 24, 1, 31, 58, 230, 10, 92, 195, 233, 212, 7, 166,
481 252, 67, 115, 221, 58, 3, 191, 218, 188, 181, 192, 28, 11, 88, 84, 141, 183, 137, 167, 166,
482 161, 33, 82, 30, 255, 23, 10, 179, 149, 88, 24, 39, 60, 74, 232, 133, 44, 90, 98, 117, 31,
483 41, 69, 251, 76, 250, 141, 229, 83, 191, 6, 237, 107, 127, 93, 238, 110, 49, 125, 201, 37,
484 162, 120, 157, 32, 116, 195, 208, 143, 83, 254, 223, 93, 97, 158, 0, 24, 95, 197, 249, 35,
485 240, 3, 20, 71, 164, 97, 180, 29, 203, 69, 31, 151, 249, 244, 197, 91, 101, 174, 129, 131,
486 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,
487 90, 26, 0, 1, 0, 0, 58, 0, 1, 21, 91, 4, 58, 0, 1, 21, 88, 80, 165, 253, 56, 243, 255, 54,
488 246, 252, 231, 230, 33, 252, 49, 175, 1, 111, 246,
489 ];
490 const TEST_UNSEALED_LEGACYKEY_ENCODED: &[u8] = &[
491 135, 114, 97, 155, 115, 209, 215, 224, 175, 159, 231, 208, 15, 244, 40, 171, 239, 137, 57,
492 98, 207, 167, 231, 138, 145, 254, 28, 136, 236, 60, 23, 163, 4, 246, 219, 117, 104, 246,
493 86, 10, 152, 52, 90, 85, 58, 6, 70, 39, 111, 128, 93, 145, 143, 180, 77, 129, 178, 242, 82,
494 72, 57, 61, 192, 64,
495 ];
496 const TESTVECTOR_LEGACYKEY_ENVELOPE: &[u8] = &[
497 132, 88, 38, 161, 3, 120, 34, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 120,
498 46, 98, 105, 116, 119, 97, 114, 100, 101, 110, 46, 108, 101, 103, 97, 99, 121, 45, 107,
499 101, 121, 161, 5, 88, 24, 218, 72, 22, 79, 149, 30, 12, 36, 180, 212, 44, 21, 167, 208,
500 214, 221, 7, 91, 178, 12, 104, 17, 45, 219, 88, 80, 114, 38, 14, 165, 85, 229, 103, 108,
501 17, 175, 41, 43, 203, 175, 119, 125, 227, 127, 163, 214, 213, 138, 12, 216, 163, 204, 38,
502 222, 47, 11, 44, 231, 239, 170, 63, 8, 249, 56, 102, 18, 134, 34, 232, 193, 44, 19, 228,
503 17, 187, 199, 238, 187, 2, 13, 30, 112, 103, 110, 5, 31, 238, 58, 4, 24, 19, 239, 135, 57,
504 206, 190, 144, 83, 128, 204, 59, 155, 21, 80, 180, 34, 129, 131, 71, 161, 1, 58, 0, 1, 21,
505 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,
506 1, 21, 91, 4, 58, 0, 1, 21, 88, 80, 212, 91, 185, 112, 92, 177, 108, 33, 182, 202, 26, 141,
507 11, 133, 95, 235, 246,
508 ];
509
510 const TESTVECTOR_PASSWORD: &str = "test_password";
511
512 #[test]
513 fn test_testvector_cosekey() {
514 let key_store = KeyStore::<TestIds>::default();
515 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
516 let envelope =
517 PasswordProtectedKeyEnvelope::try_from(&TESTVECTOR_COSEKEY_ENVELOPE.to_vec())
518 .expect("Key envelope should be valid");
519 let key = envelope
520 .unseal(TESTVECTOR_PASSWORD, &mut ctx)
521 .expect("Unsealing should succeed");
522 #[allow(deprecated)]
523 let unsealed_key = ctx
524 .dangerous_get_symmetric_key(key)
525 .expect("Key should exist in the key store");
526 assert_eq!(
527 unsealed_key.to_encoded().to_vec(),
528 TEST_UNSEALED_COSEKEY_ENCODED
529 );
530 }
531
532 #[test]
533 fn test_testvector_legacykey() {
534 let key_store = KeyStore::<TestIds>::default();
535 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
536 let envelope =
537 PasswordProtectedKeyEnvelope::try_from(&TESTVECTOR_LEGACYKEY_ENVELOPE.to_vec())
538 .expect("Key envelope should be valid");
539 let key = envelope
540 .unseal(TESTVECTOR_PASSWORD, &mut ctx)
541 .expect("Unsealing should succeed");
542 #[allow(deprecated)]
543 let unsealed_key = ctx
544 .dangerous_get_symmetric_key(key)
545 .expect("Key should exist in the key store");
546 assert_eq!(
547 unsealed_key.to_encoded().to_vec(),
548 TEST_UNSEALED_LEGACYKEY_ENCODED
549 );
550 }
551
552 #[test]
553 fn test_make_envelope() {
554 let key_store = KeyStore::<TestIds>::default();
555 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
556 let test_key = ctx.make_cose_symmetric_key(TestSymmKey::A(0)).unwrap();
557
558 let password = "test_password";
559
560 let envelope = PasswordProtectedKeyEnvelope::seal(test_key, password, &ctx).unwrap();
562 let serialized: Vec<u8> = (&envelope).into();
563
564 let deserialized: PasswordProtectedKeyEnvelope =
566 PasswordProtectedKeyEnvelope::try_from(&serialized).unwrap();
567 let key = deserialized.unseal(password, &mut ctx).unwrap();
568
569 #[allow(deprecated)]
571 let unsealed_key = ctx
572 .dangerous_get_symmetric_key(key)
573 .expect("Key should exist in the key store");
574
575 #[allow(deprecated)]
576 let key_before_sealing = ctx
577 .dangerous_get_symmetric_key(test_key)
578 .expect("Key should exist in the key store");
579
580 assert_eq!(unsealed_key, key_before_sealing);
581 }
582
583 #[test]
584 fn test_make_envelope_legacy_key() {
585 let key_store = KeyStore::<TestIds>::default();
586 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
587 let test_key = ctx.generate_symmetric_key();
588
589 let password = "test_password";
590
591 let envelope = PasswordProtectedKeyEnvelope::seal(test_key, password, &ctx).unwrap();
593 let serialized: Vec<u8> = (&envelope).into();
594
595 let deserialized: PasswordProtectedKeyEnvelope =
597 PasswordProtectedKeyEnvelope::try_from(&serialized).unwrap();
598 let key = deserialized.unseal(password, &mut ctx).unwrap();
599
600 #[allow(deprecated)]
602 let unsealed_key = ctx
603 .dangerous_get_symmetric_key(key)
604 .expect("Key should exist in the key store");
605
606 #[allow(deprecated)]
607 let key_before_sealing = ctx
608 .dangerous_get_symmetric_key(test_key)
609 .expect("Key should exist in the key store");
610
611 assert_eq!(unsealed_key, key_before_sealing);
612 }
613
614 #[test]
615 fn test_reseal_envelope() {
616 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
617 let password = "test_password";
618 let new_password = "new_test_password";
619
620 let envelope: PasswordProtectedKeyEnvelope =
622 PasswordProtectedKeyEnvelope::seal_ref(&key, password).expect("Sealing should work");
623
624 let envelope = envelope
626 .reseal(password, new_password)
627 .expect("Resealing should work");
628 let unsealed = envelope
629 .unseal_ref(new_password)
630 .expect("Unsealing should work");
631
632 assert_eq!(unsealed, key);
634 }
635
636 #[test]
637 fn test_wrong_password() {
638 let key_store = KeyStore::<TestIds>::default();
639 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
640 let test_key = ctx.make_cose_symmetric_key(TestSymmKey::A(0)).unwrap();
641
642 let password = "test_password";
643 let wrong_password = "wrong_password";
644
645 let envelope = PasswordProtectedKeyEnvelope::seal(test_key, password, &ctx).unwrap();
647
648 let deserialized: PasswordProtectedKeyEnvelope =
650 PasswordProtectedKeyEnvelope::try_from(&(&envelope).into()).unwrap();
651 assert!(matches!(
652 deserialized.unseal(wrong_password, &mut ctx),
653 Err(PasswordProtectedKeyEnvelopeError::WrongPassword)
654 ));
655 }
656}