1use std::{num::TryFromIntError, str::FromStr};
18
19use argon2::Params;
20use bitwarden_encoding::{B64, FromStrVisitor};
21use ciborium::{Value, value::Integer};
22use coset::{CborSerializable, CoseError, Header, HeaderBuilder};
23use rand::Rng;
24use serde::{Deserialize, Serialize};
25use thiserror::Error;
26#[cfg(feature = "wasm")]
27use wasm_bindgen::convert::{FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi};
28
29use crate::{
30 CipherSuite, ContentFormat, EncodedSymmetricKey, KeySlotIds, KeyStoreContext,
31 SymmetricCryptoKey,
32 cose::{
33 ALG_ARGON2ID13, ALG_PBKDF2_SHA256, ARGON2_ITERATIONS, ARGON2_MEMORY, ARGON2_PARALLELISM,
34 ARGON2_SALT, ContentNamespace, CoseExtractError, PBKDF2_ITERATIONS, PBKDF2_SALT,
35 SafeObjectNamespace, extract_bytes, extract_integer,
36 symmetric::{
37 CoseAlgorithmPolicy, CoseContentEncryptionAlgorithm, decrypt_cose, encrypt_cose,
38 },
39 },
40 keys::KeyId,
41 safe::{
42 DecodeSealedKeyError, decode_sealed_symmetric_key, extract_key_id,
43 helpers::{debug_fmt, set_safe_namespaces, validate_safe_namespaces},
44 set_contained_key_id,
45 },
46};
47
48const ENVELOPE_ARGON2_SALT_SIZE: usize = 16;
51const ENVELOPE_ARGON2_OUTPUT_KEY_SIZE: usize = 32;
53const ENVELOPE_PBKDF2_SALT_SIZE: usize = 16;
54
55#[derive(Clone)]
67pub struct PasswordProtectedKeyEnvelope {
68 cose_encrypt: coset::CoseEncrypt,
69}
70
71impl PasswordProtectedKeyEnvelope {
72 pub fn seal<Ids: KeySlotIds>(
77 key_to_seal: Ids::Symmetric,
78 password: &str,
79 namespace: PasswordProtectedKeyEnvelopeNamespace,
80 ctx: &KeyStoreContext<Ids>,
81 ) -> Result<Self, PasswordProtectedKeyEnvelopeError> {
82 let key_ref = ctx
83 .get_symmetric_key(key_to_seal)
84 .map_err(|_| PasswordProtectedKeyEnvelopeError::KeyMissing)?;
85 let kdf = suite_kdf(ctx.cipher_suite());
86 Self::seal_ref_with_settings(key_ref, password, &kdf, namespace)
87 }
88
89 #[cfg(test)]
93 fn seal_ref(
94 key_to_seal: &SymmetricCryptoKey,
95 password: &str,
96 namespace: PasswordProtectedKeyEnvelopeNamespace,
97 ) -> Result<Self, PasswordProtectedKeyEnvelopeError> {
98 let kdf = suite_kdf(CipherSuite::Standard);
99 Self::seal_ref_with_settings(key_to_seal, password, &kdf, namespace)
100 }
101
102 fn seal_ref_with_settings(
106 key_to_seal: &SymmetricCryptoKey,
107 password: &str,
108 kdf: &EnvelopeKdf,
109 namespace: PasswordProtectedKeyEnvelopeNamespace,
110 ) -> Result<Self, PasswordProtectedKeyEnvelopeError> {
111 let envelope_key =
119 derive_key(kdf, password).map_err(|_| PasswordProtectedKeyEnvelopeError::Kdf)?;
120
121 let (content_format, key_to_seal_bytes) = match key_to_seal.to_encoded_raw() {
122 EncodedSymmetricKey::BitwardenLegacyKey(key_bytes) => {
123 (ContentFormat::BitwardenLegacyKey, key_bytes.to_vec())
124 }
125 EncodedSymmetricKey::CoseKey(key_bytes) => (ContentFormat::CoseKey, key_bytes.to_vec()),
126 };
127
128 let protected_header = {
129 let mut header = HeaderBuilder::from(content_format).build();
130 set_contained_key_id(&mut header, key_to_seal.key_id());
131 set_safe_namespaces(
132 &mut header,
133 SafeObjectNamespace::PasswordProtectedKeyEnvelope,
134 namespace,
135 );
136 header
137 };
138
139 let builder = coset::CoseEncryptBuilder::new().add_recipient({
143 let mut recipient = coset::CoseRecipientBuilder::new()
144 .unprotected(kdf.into())
145 .build();
146 recipient.protected.header.alg = Some(kdf.cose_alg());
147 recipient
148 });
149
150 let cose_encrypt = encrypt_cose(
151 CoseContentEncryptionAlgorithm::Aes256Gcm,
152 builder,
153 protected_header,
154 &key_to_seal_bytes,
155 &envelope_key,
156 )
157 .map_err(|_| PasswordProtectedKeyEnvelopeError::Kdf)?;
158
159 Ok(PasswordProtectedKeyEnvelope { cose_encrypt })
160 }
161
162 pub fn unseal<Ids: KeySlotIds>(
165 &self,
166 password: &str,
167 namespace: PasswordProtectedKeyEnvelopeNamespace,
168 ctx: &mut KeyStoreContext<Ids>,
169 ) -> Result<Ids::Symmetric, PasswordProtectedKeyEnvelopeError> {
170 let key = self.unseal_ref(password, namespace)?;
171 Ok(ctx.add_local_symmetric_key(key))
172 }
173
174 fn unseal_ref(
175 &self,
176 password: &str,
177 content_namespace: PasswordProtectedKeyEnvelopeNamespace,
178 ) -> Result<SymmetricCryptoKey, PasswordProtectedKeyEnvelopeError> {
179 let recipient = self
182 .cose_encrypt
183 .recipients
184 .first()
185 .filter(|_| self.cose_encrypt.recipients.len() == 1)
186 .ok_or_else(|| {
187 PasswordProtectedKeyEnvelopeError::Parsing(
188 "Invalid number of recipients".to_string(),
189 )
190 })?;
191
192 validate_safe_namespaces(
193 &self.cose_encrypt.protected.header,
194 SafeObjectNamespace::PasswordProtectedKeyEnvelope,
195 content_namespace,
196 )
197 .map_err(|_| PasswordProtectedKeyEnvelopeError::InvalidNamespace)?;
198
199 let kdf = EnvelopeKdf::try_from(recipient)?;
202 let envelope_key =
203 derive_key(&kdf, password).map_err(|_| PasswordProtectedKeyEnvelopeError::Kdf)?;
204
205 let key_bytes = decrypt_cose(
212 &self.cose_encrypt,
213 CoseAlgorithmPolicy::ProtectedHeaderAlgorithmOrLegacyDefault(
214 CoseContentEncryptionAlgorithm::XChaCha20Poly1305,
215 ),
216 &envelope_key,
217 )
218 .map_err(|_| PasswordProtectedKeyEnvelopeError::WrongPassword)?;
219
220 decode_sealed_symmetric_key(&self.cose_encrypt.protected.header, key_bytes).map_err(|e| {
221 match e {
222 DecodeSealedKeyError::InvalidContentFormat => {
223 PasswordProtectedKeyEnvelopeError::Parsing("Invalid content format".to_string())
224 }
225 DecodeSealedKeyError::UnsupportedContentFormat => {
226 PasswordProtectedKeyEnvelopeError::Parsing(
227 "Unknown or unsupported content format".to_string(),
228 )
229 }
230 DecodeSealedKeyError::InvalidKey => {
231 PasswordProtectedKeyEnvelopeError::Parsing("Failed to decode key".to_string())
232 }
233 }
234 })
235 }
236
237 pub fn reseal(
243 &self,
244 password: &str,
245 new_password: &str,
246 namespace: PasswordProtectedKeyEnvelopeNamespace,
247 ) -> Result<Self, PasswordProtectedKeyEnvelopeError> {
248 let recipient = self
252 .cose_encrypt
253 .recipients
254 .first()
255 .filter(|_| self.cose_encrypt.recipients.len() == 1)
256 .ok_or_else(|| {
257 PasswordProtectedKeyEnvelopeError::Parsing(
258 "Invalid number of recipients".to_string(),
259 )
260 })?;
261
262 let unsealed = self.unseal_ref(password, namespace)?;
263 Self::seal_ref_with_settings(
264 &unsealed,
265 new_password,
266 &EnvelopeKdf::try_from(recipient)?,
267 namespace,
268 )
269 }
270
271 pub fn contained_key_id(&self) -> Result<Option<KeyId>, PasswordProtectedKeyEnvelopeError> {
274 extract_key_id(&self.cose_encrypt.protected.header)
275 .map_err(|_| PasswordProtectedKeyEnvelopeError::Parsing("Invalid key id".to_string()))
276 }
277}
278
279impl From<&PasswordProtectedKeyEnvelope> for Vec<u8> {
280 fn from(val: &PasswordProtectedKeyEnvelope) -> Self {
281 val.cose_encrypt
282 .clone()
283 .to_vec()
284 .expect("Serialization to cose should not fail")
285 }
286}
287
288impl TryFrom<&Vec<u8>> for PasswordProtectedKeyEnvelope {
289 type Error = CoseError;
290
291 fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
292 let cose_encrypt = coset::CoseEncrypt::from_slice(value)?;
293 Ok(PasswordProtectedKeyEnvelope { cose_encrypt })
294 }
295}
296
297impl std::fmt::Debug for PasswordProtectedKeyEnvelope {
298 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299 let mut s = f.debug_struct("PasswordProtectedKeyEnvelope");
300
301 if let Some(recipient) = self.cose_encrypt.recipients.first() {
302 match EnvelopeKdf::try_from(recipient) {
303 Ok(EnvelopeKdf::Argon2id(settings)) => {
304 s.field("kdf", &"Argon2id");
305 s.field("argon2_iterations", &settings.iterations);
306 s.field("argon2_memory_kib", &settings.memory);
307 s.field("argon2_parallelism", &settings.parallelism);
308 }
309 Ok(EnvelopeKdf::Pbkdf2(settings)) => {
310 s.field("kdf", &"PBKDF2-HMAC-SHA256");
311 s.field("pbkdf2_iterations", &settings.iterations);
312 }
313 Err(_) => {}
314 }
315 }
316
317 debug_fmt::<PasswordProtectedKeyEnvelopeNamespace>(
318 &mut s,
319 &self.cose_encrypt.protected.header,
320 );
321
322 if let Ok(Some(key_id)) = self.contained_key_id() {
323 s.field("contained_key_id", &key_id);
324 }
325
326 s.finish()
327 }
328}
329
330impl FromStr for PasswordProtectedKeyEnvelope {
331 type Err = PasswordProtectedKeyEnvelopeError;
332
333 fn from_str(s: &str) -> Result<Self, Self::Err> {
334 let data = B64::try_from(s).map_err(|_| {
335 PasswordProtectedKeyEnvelopeError::Parsing(
336 "Invalid PasswordProtectedKeyEnvelope Base64 encoding".to_string(),
337 )
338 })?;
339 Self::try_from(&data.into_bytes()).map_err(|_| {
340 PasswordProtectedKeyEnvelopeError::Parsing(
341 "Failed to parse PasswordProtectedKeyEnvelope".to_string(),
342 )
343 })
344 }
345}
346
347impl From<PasswordProtectedKeyEnvelope> for String {
348 fn from(val: PasswordProtectedKeyEnvelope) -> Self {
349 let serialized: Vec<u8> = (&val).into();
350 B64::from(serialized).to_string()
351 }
352}
353
354impl<'de> Deserialize<'de> for PasswordProtectedKeyEnvelope {
355 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
356 where
357 D: serde::Deserializer<'de>,
358 {
359 deserializer.deserialize_str(FromStrVisitor::new())
360 }
361}
362
363impl Serialize for PasswordProtectedKeyEnvelope {
364 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
365 where
366 S: serde::Serializer,
367 {
368 let serialized: Vec<u8> = self.into();
369 serializer.serialize_str(&B64::from(serialized).to_string())
370 }
371}
372
373const ENVELOPE_PBKDF2_ITERATIONS: u32 = 600_000;
375
376enum EnvelopeKdf {
380 Argon2id(Argon2RawSettings),
381 Pbkdf2(Pbkdf2RawSettings),
382}
383
384impl EnvelopeKdf {
385 fn cose_alg(&self) -> coset::Algorithm {
387 match self {
388 EnvelopeKdf::Argon2id(_) => coset::Algorithm::PrivateUse(ALG_ARGON2ID13),
389 EnvelopeKdf::Pbkdf2(_) => coset::Algorithm::PrivateUse(ALG_PBKDF2_SHA256),
390 }
391 }
392}
393
394impl From<&EnvelopeKdf> for Header {
395 fn from(kdf: &EnvelopeKdf) -> Header {
396 match kdf {
397 EnvelopeKdf::Argon2id(settings) => settings.into(),
398 EnvelopeKdf::Pbkdf2(settings) => settings.into(),
399 }
400 }
401}
402
403impl TryFrom<&coset::CoseRecipient> for EnvelopeKdf {
404 type Error = PasswordProtectedKeyEnvelopeError;
405
406 fn try_from(recipient: &coset::CoseRecipient) -> Result<Self, Self::Error> {
407 let alg = recipient.protected.header.alg.as_ref();
408 if alg == Some(&coset::Algorithm::PrivateUse(ALG_ARGON2ID13)) {
409 Ok(EnvelopeKdf::Argon2id((&recipient.unprotected).try_into()?))
410 } else if alg == Some(&coset::Algorithm::PrivateUse(ALG_PBKDF2_SHA256)) {
411 Ok(EnvelopeKdf::Pbkdf2((&recipient.unprotected).try_into()?))
412 } else {
413 Err(PasswordProtectedKeyEnvelopeError::Parsing(
414 "Unknown or unsupported KDF algorithm".to_string(),
415 ))
416 }
417 }
418}
419
420fn suite_kdf(suite: CipherSuite) -> EnvelopeKdf {
422 match suite {
423 CipherSuite::Standard => EnvelopeKdf::Argon2id(Argon2RawSettings::local_kdf_settings()),
424 CipherSuite::Fips => EnvelopeKdf::Pbkdf2(Pbkdf2RawSettings::local_kdf_settings()),
425 }
426}
427
428struct Argon2RawSettings {
433 iterations: u32,
434 memory: u32,
436 parallelism: u32,
437 salt: [u8; ENVELOPE_ARGON2_SALT_SIZE],
438}
439
440impl Argon2RawSettings {
441 fn local_kdf_settings() -> Self {
444 let mut salt = [0u8; ENVELOPE_ARGON2_SALT_SIZE];
445 bitwarden_random::rng().fill_bytes(&mut salt);
446
447 if cfg!(target_os = "ios") {
450 Self {
452 iterations: 6,
453 memory: 32 * 1024, parallelism: 4,
455 salt,
456 }
457 } else {
458 Self {
462 iterations: 3,
463 memory: 64 * 1024, parallelism: 4,
465 salt,
466 }
467 }
468 }
469}
470
471impl From<&Argon2RawSettings> for Header {
472 fn from(settings: &Argon2RawSettings) -> Header {
473 let builder = HeaderBuilder::new()
474 .value(ARGON2_ITERATIONS, Integer::from(settings.iterations).into())
475 .value(ARGON2_MEMORY, Integer::from(settings.memory).into())
476 .value(
477 ARGON2_PARALLELISM,
478 Integer::from(settings.parallelism).into(),
479 )
480 .value(ARGON2_SALT, Value::from(settings.salt.to_vec()));
481
482 let mut header = builder.build();
483 header.alg = Some(coset::Algorithm::PrivateUse(ALG_ARGON2ID13));
484 header
485 }
486}
487
488impl TryInto<Params> for &Argon2RawSettings {
489 type Error = PasswordProtectedKeyEnvelopeError;
490
491 fn try_into(self) -> Result<Params, PasswordProtectedKeyEnvelopeError> {
492 Params::new(
493 self.memory,
494 self.iterations,
495 self.parallelism,
496 Some(ENVELOPE_ARGON2_OUTPUT_KEY_SIZE),
497 )
498 .map_err(|_| PasswordProtectedKeyEnvelopeError::Kdf)
499 }
500}
501
502impl TryInto<Argon2RawSettings> for &Header {
503 type Error = PasswordProtectedKeyEnvelopeError;
504
505 fn try_into(self) -> Result<Argon2RawSettings, PasswordProtectedKeyEnvelopeError> {
506 Ok(Argon2RawSettings {
507 iterations: extract_integer(self, ARGON2_ITERATIONS, "iterations")?.try_into()?,
508 memory: extract_integer(self, ARGON2_MEMORY, "memory")?.try_into()?,
509 parallelism: extract_integer(self, ARGON2_PARALLELISM, "parallelism")?.try_into()?,
510 salt: extract_bytes(self, ARGON2_SALT, "salt")?
511 .try_into()
512 .map_err(|_| {
513 PasswordProtectedKeyEnvelopeError::Parsing("Invalid Argon2 salt".to_string())
514 })?,
515 })
516 }
517}
518
519struct Pbkdf2RawSettings {
522 iterations: u32,
523 salt: [u8; ENVELOPE_PBKDF2_SALT_SIZE],
524}
525
526impl Pbkdf2RawSettings {
527 fn local_kdf_settings() -> Self {
529 let mut salt = [0u8; ENVELOPE_PBKDF2_SALT_SIZE];
530 bitwarden_random::rng().fill_bytes(&mut salt);
531
532 Self {
533 iterations: ENVELOPE_PBKDF2_ITERATIONS,
534 salt,
535 }
536 }
537}
538
539impl From<&Pbkdf2RawSettings> for Header {
540 fn from(settings: &Pbkdf2RawSettings) -> Header {
541 let builder = HeaderBuilder::new()
542 .value(PBKDF2_ITERATIONS, Integer::from(settings.iterations).into())
543 .value(PBKDF2_SALT, Value::from(settings.salt.to_vec()));
544
545 let mut header = builder.build();
546 header.alg = Some(coset::Algorithm::PrivateUse(ALG_PBKDF2_SHA256));
547 header
548 }
549}
550
551impl TryInto<Pbkdf2RawSettings> for &Header {
552 type Error = PasswordProtectedKeyEnvelopeError;
553
554 fn try_into(self) -> Result<Pbkdf2RawSettings, PasswordProtectedKeyEnvelopeError> {
555 Ok(Pbkdf2RawSettings {
556 iterations: extract_integer(self, PBKDF2_ITERATIONS, "iterations")?.try_into()?,
557 salt: extract_bytes(self, PBKDF2_SALT, "salt")?
558 .try_into()
559 .map_err(|_| {
560 PasswordProtectedKeyEnvelopeError::Parsing("Invalid PBKDF2 salt".to_string())
561 })?,
562 })
563 }
564}
565
566fn derive_key(
568 kdf: &EnvelopeKdf,
569 password: &str,
570) -> Result<[u8; ENVELOPE_ARGON2_OUTPUT_KEY_SIZE], PasswordProtectedKeyEnvelopeError> {
571 match kdf {
572 EnvelopeKdf::Argon2id(settings) => derive_argon2_key(settings, password),
573 EnvelopeKdf::Pbkdf2(settings) => derive_pbkdf2_key(settings, password),
574 }
575}
576
577fn derive_argon2_key(
578 argon2_settings: &Argon2RawSettings,
579 password: &str,
580) -> Result<[u8; ENVELOPE_ARGON2_OUTPUT_KEY_SIZE], PasswordProtectedKeyEnvelopeError> {
581 use argon2::*;
582
583 let mut hash = [0u8; ENVELOPE_ARGON2_OUTPUT_KEY_SIZE];
584 Argon2::new(
585 Algorithm::Argon2id,
586 Version::V0x13,
587 argon2_settings.try_into()?,
588 )
589 .hash_password_into(password.as_bytes(), &argon2_settings.salt, &mut hash)
590 .map_err(|_| PasswordProtectedKeyEnvelopeError::Kdf)?;
591
592 Ok(hash)
593}
594
595fn derive_pbkdf2_key(
596 pbkdf2_settings: &Pbkdf2RawSettings,
597 password: &str,
598) -> Result<[u8; ENVELOPE_ARGON2_OUTPUT_KEY_SIZE], PasswordProtectedKeyEnvelopeError> {
599 Ok(crate::util::pbkdf2(
600 password.as_bytes(),
601 &pbkdf2_settings.salt,
602 pbkdf2_settings.iterations,
603 ))
604}
605
606#[derive(Debug, Error)]
608pub enum PasswordProtectedKeyEnvelopeError {
609 #[error("Wrong password")]
611 WrongPassword,
612 #[error("Parsing error {0}")]
614 Parsing(String),
615 #[error("Kdf error")]
618 Kdf,
619 #[error("Key missing error")]
621 KeyMissing,
622 #[error("Could not write to key store")]
624 KeyStore,
625 #[error("Invalid namespace")]
627 InvalidNamespace,
628}
629
630impl From<CoseExtractError> for PasswordProtectedKeyEnvelopeError {
631 fn from(err: CoseExtractError) -> Self {
632 let CoseExtractError::MissingValue(label) = err;
633 PasswordProtectedKeyEnvelopeError::Parsing(format!("Missing value for {}", label))
634 }
635}
636
637impl From<TryFromIntError> for PasswordProtectedKeyEnvelopeError {
638 fn from(err: TryFromIntError) -> Self {
639 PasswordProtectedKeyEnvelopeError::Parsing(format!("Invalid integer: {}", err))
640 }
641}
642
643#[cfg(feature = "wasm")]
644#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
645const TS_CUSTOM_TYPES: &'static str = r#"
646export type PasswordProtectedKeyEnvelope = Tagged<string, "PasswordProtectedKeyEnvelope">;
647"#;
648
649#[cfg(feature = "wasm")]
650impl wasm_bindgen::describe::WasmDescribe for PasswordProtectedKeyEnvelope {
651 fn describe() {
652 <String as wasm_bindgen::describe::WasmDescribe>::describe();
653 }
654}
655
656#[cfg(feature = "wasm")]
657impl FromWasmAbi for PasswordProtectedKeyEnvelope {
658 type Abi = <String as FromWasmAbi>::Abi;
659
660 unsafe fn from_abi(abi: Self::Abi) -> Self {
661 use wasm_bindgen::UnwrapThrowExt;
662 let string = unsafe { String::from_abi(abi) };
663 PasswordProtectedKeyEnvelope::from_str(&string).unwrap_throw()
664 }
665}
666
667#[cfg(feature = "wasm")]
668impl OptionFromWasmAbi for PasswordProtectedKeyEnvelope {
669 fn is_none(abi: &Self::Abi) -> bool {
670 <String as OptionFromWasmAbi>::is_none(abi)
671 }
672}
673
674#[cfg(feature = "wasm")]
675impl IntoWasmAbi for PasswordProtectedKeyEnvelope {
676 type Abi = <String as IntoWasmAbi>::Abi;
677
678 fn into_abi(self) -> Self::Abi {
679 let string: String = self.into();
680 string.into_abi()
681 }
682}
683
684#[cfg(feature = "wasm")]
685impl TryFrom<wasm_bindgen::JsValue> for PasswordProtectedKeyEnvelope {
686 type Error = PasswordProtectedKeyEnvelopeError;
687
688 fn try_from(value: wasm_bindgen::JsValue) -> Result<Self, Self::Error> {
689 let string = value.as_string().ok_or_else(|| {
690 PasswordProtectedKeyEnvelopeError::Parsing(
691 "PasswordProtectedKeyEnvelope JsValue is not a string".to_string(),
692 )
693 })?;
694 PasswordProtectedKeyEnvelope::from_str(&string)
695 }
696}
697
698#[derive(Debug, Clone, Copy, PartialEq, Eq)]
700pub enum PasswordProtectedKeyEnvelopeNamespace {
701 PinUnlock = 1,
703 #[cfg(test)]
705 ExampleNamespace = -1,
706 #[cfg(test)]
708 ExampleNamespace2 = -2,
709}
710
711impl PasswordProtectedKeyEnvelopeNamespace {
712 fn as_i64(&self) -> i64 {
714 *self as i64
715 }
716}
717
718impl TryFrom<i128> for PasswordProtectedKeyEnvelopeNamespace {
719 type Error = PasswordProtectedKeyEnvelopeError;
720
721 fn try_from(value: i128) -> Result<Self, Self::Error> {
722 match value {
723 1 => Ok(PasswordProtectedKeyEnvelopeNamespace::PinUnlock),
724 #[cfg(test)]
725 -1 => Ok(PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace),
726 #[cfg(test)]
727 -2 => Ok(PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace2),
728 _ => Err(PasswordProtectedKeyEnvelopeError::InvalidNamespace),
729 }
730 }
731}
732
733impl TryFrom<i64> for PasswordProtectedKeyEnvelopeNamespace {
734 type Error = PasswordProtectedKeyEnvelopeError;
735
736 fn try_from(value: i64) -> Result<Self, Self::Error> {
737 Self::try_from(i128::from(value))
738 }
739}
740
741impl From<PasswordProtectedKeyEnvelopeNamespace> for i128 {
742 fn from(val: PasswordProtectedKeyEnvelopeNamespace) -> Self {
743 val.as_i64().into()
744 }
745}
746
747impl ContentNamespace for PasswordProtectedKeyEnvelopeNamespace {}
748
749#[cfg(test)]
750mod tests {
751 use super::*;
752 use crate::{KeyStore, SymmetricKeyAlgorithm, traits::tests::TestIds};
753
754 const TEST_UNSEALED_COSEKEY_ENCODED: &[u8] = &[
755 165, 1, 4, 2, 80, 63, 208, 189, 183, 204, 37, 72, 170, 179, 236, 190, 208, 22, 65, 227,
756 183, 3, 58, 0, 1, 17, 111, 4, 132, 3, 4, 5, 6, 32, 88, 32, 88, 25, 68, 85, 205, 28, 133,
757 28, 90, 147, 160, 145, 48, 3, 178, 184, 30, 11, 122, 132, 64, 59, 51, 233, 191, 117, 159,
758 117, 23, 168, 248, 36, 1,
759 ];
760 const TESTVECTOR_COSEKEY_ENVELOPE: &[u8] = &[
761 132, 68, 161, 3, 24, 101, 161, 5, 88, 24, 1, 31, 58, 230, 10, 92, 195, 233, 212, 7, 166,
762 252, 67, 115, 221, 58, 3, 191, 218, 188, 181, 192, 28, 11, 88, 84, 141, 183, 137, 167, 166,
763 161, 33, 82, 30, 255, 23, 10, 179, 149, 88, 24, 39, 60, 74, 232, 133, 44, 90, 98, 117, 31,
764 41, 69, 251, 76, 250, 141, 229, 83, 191, 6, 237, 107, 127, 93, 238, 110, 49, 125, 201, 37,
765 162, 120, 157, 32, 116, 195, 208, 143, 83, 254, 223, 93, 97, 158, 0, 24, 95, 197, 249, 35,
766 240, 3, 20, 71, 164, 97, 180, 29, 203, 69, 31, 151, 249, 244, 197, 91, 101, 174, 129, 131,
767 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,
768 90, 26, 0, 1, 0, 0, 58, 0, 1, 21, 91, 4, 58, 0, 1, 21, 88, 80, 165, 253, 56, 243, 255, 54,
769 246, 252, 231, 230, 33, 252, 49, 175, 1, 111, 246,
770 ];
771 const TEST_UNSEALED_LEGACYKEY_ENCODED: &[u8] = &[
772 135, 114, 97, 155, 115, 209, 215, 224, 175, 159, 231, 208, 15, 244, 40, 171, 239, 137, 57,
773 98, 207, 167, 231, 138, 145, 254, 28, 136, 236, 60, 23, 163, 4, 246, 219, 117, 104, 246,
774 86, 10, 152, 52, 90, 85, 58, 6, 70, 39, 111, 128, 93, 145, 143, 180, 77, 129, 178, 242, 82,
775 72, 57, 61, 192, 64,
776 ];
777 const TESTVECTOR_LEGACYKEY_ENVELOPE: &[u8] = &[
778 132, 88, 38, 161, 3, 120, 34, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 120,
779 46, 98, 105, 116, 119, 97, 114, 100, 101, 110, 46, 108, 101, 103, 97, 99, 121, 45, 107,
780 101, 121, 161, 5, 88, 24, 218, 72, 22, 79, 149, 30, 12, 36, 180, 212, 44, 21, 167, 208,
781 214, 221, 7, 91, 178, 12, 104, 17, 45, 219, 88, 80, 114, 38, 14, 165, 85, 229, 103, 108,
782 17, 175, 41, 43, 203, 175, 119, 125, 227, 127, 163, 214, 213, 138, 12, 216, 163, 204, 38,
783 222, 47, 11, 44, 231, 239, 170, 63, 8, 249, 56, 102, 18, 134, 34, 232, 193, 44, 19, 228,
784 17, 187, 199, 238, 187, 2, 13, 30, 112, 103, 110, 5, 31, 238, 58, 4, 24, 19, 239, 135, 57,
785 206, 190, 144, 83, 128, 204, 59, 155, 21, 80, 180, 34, 129, 131, 71, 161, 1, 58, 0, 1, 21,
786 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,
787 1, 21, 91, 4, 58, 0, 1, 21, 88, 80, 212, 91, 185, 112, 92, 177, 108, 33, 182, 202, 26, 141,
788 11, 133, 95, 235, 246,
789 ];
790
791 const TESTVECTOR_PASSWORD: &str = "test_password";
792
793 const TEST_UNSEALED_FIPS_ENCODED: &[u8] = &[
796 165, 1, 4, 2, 80, 40, 136, 73, 38, 113, 191, 48, 29, 141, 167, 113, 250, 16, 155, 74, 5, 3,
797 58, 0, 1, 17, 111, 4, 132, 3, 4, 5, 6, 32, 88, 32, 3, 78, 100, 122, 143, 20, 156, 26, 63,
798 89, 134, 100, 93, 32, 137, 121, 214, 98, 147, 183, 198, 61, 176, 86, 176, 65, 220, 38, 211,
799 233, 121, 100, 1,
800 ];
801 const TESTVECTOR_FIPS_ENVELOPE: &[u8] = &[
802 132, 88, 40, 165, 1, 3, 3, 24, 101, 58, 0, 1, 21, 92, 80, 40, 136, 73, 38, 113, 191, 48,
803 29, 141, 167, 113, 250, 16, 155, 74, 5, 58, 0, 1, 56, 129, 1, 58, 0, 1, 56, 128, 32, 161,
804 5, 76, 26, 141, 71, 238, 227, 209, 118, 32, 47, 159, 178, 11, 88, 84, 3, 184, 23, 73, 5,
805 232, 28, 144, 109, 242, 86, 211, 155, 157, 221, 76, 72, 122, 18, 64, 20, 190, 25, 34, 166,
806 3, 34, 125, 59, 172, 105, 238, 123, 17, 123, 55, 203, 107, 157, 37, 186, 240, 79, 103, 95,
807 160, 196, 38, 71, 202, 212, 241, 77, 159, 165, 31, 43, 163, 126, 182, 127, 90, 119, 35, 29,
808 155, 119, 121, 163, 98, 158, 18, 120, 199, 91, 197, 46, 224, 63, 215, 119, 191, 60, 21,
809 129, 131, 71, 161, 1, 58, 0, 1, 21, 97, 163, 1, 58, 0, 1, 21, 97, 58, 0, 1, 21, 98, 25, 19,
810 136, 58, 0, 1, 21, 99, 80, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 246,
811 ];
812
813 #[test]
814 #[ignore = "Manual test to verify debug format"]
815 fn test_debug() {
816 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
817 let envelope = PasswordProtectedKeyEnvelope::seal_ref(
818 &key,
819 "test_password",
820 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
821 )
822 .unwrap();
823 println!("{:?}", envelope);
824 }
825
826 #[test]
827 fn test_testvector_cosekey() {
828 let key_store = KeyStore::<TestIds>::default();
829 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
830 let envelope =
831 PasswordProtectedKeyEnvelope::try_from(&TESTVECTOR_COSEKEY_ENVELOPE.to_vec())
832 .expect("Key envelope should be valid");
833 let key = envelope
834 .unseal(
835 TESTVECTOR_PASSWORD,
836 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
837 &mut ctx,
838 )
839 .expect("Unsealing should succeed");
840 let unsealed_key = ctx
841 .get_symmetric_key(key)
842 .expect("Key should exist in the key store");
843 assert_eq!(
844 unsealed_key.to_encoded().to_vec(),
845 TEST_UNSEALED_COSEKEY_ENCODED
846 );
847 }
848
849 #[test]
850 fn test_testvector_legacykey() {
851 let key_store = KeyStore::<TestIds>::default();
852 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
853 let envelope =
854 PasswordProtectedKeyEnvelope::try_from(&TESTVECTOR_LEGACYKEY_ENVELOPE.to_vec())
855 .expect("Key envelope should be valid");
856 let key = envelope
857 .unseal(
858 TESTVECTOR_PASSWORD,
859 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
860 &mut ctx,
861 )
862 .expect("Unsealing should succeed");
863 let unsealed_key = ctx
864 .get_symmetric_key(key)
865 .expect("Key should exist in the key store");
866 assert_eq!(
867 unsealed_key.to_encoded().to_vec(),
868 TEST_UNSEALED_LEGACYKEY_ENCODED
869 );
870 }
871
872 #[test]
873 #[ignore = "Manual test to generate a FIPS (PBKDF2 + AES-GCM) test vector"]
874 fn generate_fips_test_vector() {
875 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
876 let envelope = PasswordProtectedKeyEnvelope::seal_ref_with_settings(
877 &key,
878 TESTVECTOR_PASSWORD,
879 &EnvelopeKdf::Pbkdf2(Pbkdf2RawSettings {
880 iterations: 5000,
882 salt: [7u8; ENVELOPE_PBKDF2_SALT_SIZE],
883 }),
884 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
885 )
886 .unwrap();
887 println!(
888 "const TEST_UNSEALED_FIPS_ENCODED: &[u8] = &{:?};",
889 key.to_encoded().to_vec()
890 );
891 println!(
892 "const TESTVECTOR_FIPS_ENVELOPE: &[u8] = &{:?};",
893 Vec::<u8>::from(&envelope)
894 );
895 }
896
897 #[test]
898 fn test_testvector_fips() {
899 let envelope = PasswordProtectedKeyEnvelope::try_from(&TESTVECTOR_FIPS_ENVELOPE.to_vec())
900 .expect("Key envelope should be valid");
901 let key = envelope
902 .unseal_ref(
903 TESTVECTOR_PASSWORD,
904 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
905 )
906 .expect("Unsealing should succeed");
907 assert_eq!(key.to_encoded().to_vec(), TEST_UNSEALED_FIPS_ENCODED);
908 }
909
910 #[test]
911 fn test_make_envelope() {
912 let key_store = KeyStore::<TestIds>::default();
913 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
914 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
915
916 let password = "test_password";
917
918 let envelope = PasswordProtectedKeyEnvelope::seal(
920 test_key,
921 password,
922 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
923 &ctx,
924 )
925 .unwrap();
926 let serialized: Vec<u8> = (&envelope).into();
927
928 let deserialized: PasswordProtectedKeyEnvelope =
930 PasswordProtectedKeyEnvelope::try_from(&serialized).unwrap();
931 let key = deserialized
932 .unseal(
933 password,
934 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
935 &mut ctx,
936 )
937 .unwrap();
938
939 let unsealed_key = ctx
941 .get_symmetric_key(key)
942 .expect("Key should exist in the key store");
943
944 let key_before_sealing = ctx
945 .get_symmetric_key(test_key)
946 .expect("Key should exist in the key store");
947
948 assert_eq!(unsealed_key, key_before_sealing);
949 }
950
951 #[test]
952 fn test_make_envelope_legacy_key() {
953 let key_store = KeyStore::<TestIds>::default();
954 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
955 let test_key = ctx.generate_symmetric_key();
956
957 let password = "test_password";
958
959 let envelope = PasswordProtectedKeyEnvelope::seal(
961 test_key,
962 password,
963 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
964 &ctx,
965 )
966 .unwrap();
967 let serialized: Vec<u8> = (&envelope).into();
968
969 let deserialized: PasswordProtectedKeyEnvelope =
971 PasswordProtectedKeyEnvelope::try_from(&serialized).unwrap();
972 let key = deserialized
973 .unseal(
974 password,
975 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
976 &mut ctx,
977 )
978 .unwrap();
979
980 let unsealed_key = ctx
982 .get_symmetric_key(key)
983 .expect("Key should exist in the key store");
984
985 let key_before_sealing = ctx
986 .get_symmetric_key(test_key)
987 .expect("Key should exist in the key store");
988
989 assert_eq!(unsealed_key, key_before_sealing);
990 }
991
992 #[test]
993 fn test_reseal_envelope() {
994 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
995 let password = "test_password";
996 let new_password = "new_test_password";
997
998 let envelope: PasswordProtectedKeyEnvelope = PasswordProtectedKeyEnvelope::seal_ref(
1000 &key,
1001 password,
1002 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
1003 )
1004 .expect("Sealing should work");
1005
1006 let envelope = envelope
1008 .reseal(
1009 password,
1010 new_password,
1011 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
1012 )
1013 .expect("Resealing should work");
1014
1015 assert_eq!(
1018 envelope.cose_encrypt.recipients[0].protected.header.alg,
1019 Some(coset::Algorithm::PrivateUse(ALG_ARGON2ID13))
1020 );
1021 assert_eq!(
1022 envelope.cose_encrypt.protected.header.alg,
1023 Some(coset::Algorithm::Assigned(coset::iana::Algorithm::A256GCM))
1024 );
1025
1026 let unsealed = envelope
1027 .unseal_ref(
1028 new_password,
1029 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
1030 )
1031 .expect("Unsealing should work");
1032
1033 assert_eq!(unsealed, key);
1035 }
1036
1037 #[test]
1038 fn test_wrong_password() {
1039 let key_store = KeyStore::<TestIds>::default();
1040 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
1041 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
1042
1043 let password = "test_password";
1044 let wrong_password = "wrong_password";
1045
1046 let envelope = PasswordProtectedKeyEnvelope::seal(
1048 test_key,
1049 password,
1050 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
1051 &ctx,
1052 )
1053 .unwrap();
1054
1055 let deserialized: PasswordProtectedKeyEnvelope =
1057 PasswordProtectedKeyEnvelope::try_from(&(&envelope).into()).unwrap();
1058 assert!(matches!(
1059 deserialized.unseal(
1060 wrong_password,
1061 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
1062 &mut ctx
1063 ),
1064 Err(PasswordProtectedKeyEnvelopeError::WrongPassword)
1065 ));
1066 }
1067
1068 #[test]
1069 fn test_wrong_safe_namespace() {
1070 let key_store = KeyStore::<TestIds>::default();
1071 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
1072 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
1073 let password = "test_password";
1074
1075 let mut envelope = PasswordProtectedKeyEnvelope::seal(
1076 test_key,
1077 password,
1078 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
1079 &ctx,
1080 )
1081 .expect("Seal works");
1082
1083 if let Some((_, value)) = envelope
1084 .cose_encrypt
1085 .protected
1086 .header
1087 .rest
1088 .iter_mut()
1089 .find(|(label, _)| {
1090 matches!(label, coset::Label::Int(label_value) if *label_value == crate::cose::SAFE_OBJECT_NAMESPACE)
1091 })
1092 {
1093 *value = Value::Integer((SafeObjectNamespace::DataEnvelope as i64).into());
1094 }
1095
1096 let deserialized: PasswordProtectedKeyEnvelope =
1097 PasswordProtectedKeyEnvelope::try_from(&(&envelope).into())
1098 .expect("Envelope should be valid");
1099
1100 let a = deserialized.unseal(
1101 password,
1102 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
1103 &mut ctx,
1104 );
1105 println!("Error: {a:?}");
1106 assert!(matches!(
1107 a,
1108 Err(PasswordProtectedKeyEnvelopeError::InvalidNamespace)
1109 ));
1110 }
1111
1112 #[test]
1113 fn test_key_id() {
1114 let key_store = KeyStore::<TestIds>::default();
1115 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
1116 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
1117 let key_id = ctx.get_symmetric_key(test_key).unwrap().key_id().unwrap();
1118
1119 let password = "test_password";
1120
1121 let envelope = PasswordProtectedKeyEnvelope::seal(
1123 test_key,
1124 password,
1125 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
1126 &ctx,
1127 )
1128 .unwrap();
1129 let contained_key_id = envelope.contained_key_id().unwrap();
1130 assert_eq!(Some(key_id), contained_key_id);
1131 }
1132
1133 #[test]
1134 fn test_no_key_id() {
1135 let key_store = KeyStore::<TestIds>::default();
1136 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
1137 let test_key = ctx.generate_symmetric_key();
1138
1139 let password = "test_password";
1140
1141 let envelope = PasswordProtectedKeyEnvelope::seal(
1143 test_key,
1144 password,
1145 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
1146 &ctx,
1147 )
1148 .unwrap();
1149 let contained_key_id = envelope.contained_key_id().unwrap();
1150 assert_eq!(None, contained_key_id);
1151 }
1152
1153 fn key_store_with_suite(suite: CipherSuite) -> KeyStore<TestIds> {
1155 let key_store = KeyStore::<TestIds>::default();
1156 key_store.set_cipher_suite(suite);
1157 key_store
1158 }
1159
1160 #[test]
1161 fn test_standard_suite_uses_argon2_and_aes_gcm() {
1162 let key_store = key_store_with_suite(CipherSuite::Standard);
1163 let mut ctx = key_store.context_mut();
1164 let test_key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
1165 let key_id = ctx.add_local_symmetric_key(test_key);
1166
1167 let envelope = PasswordProtectedKeyEnvelope::seal(
1168 key_id,
1169 "test_password",
1170 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
1171 &ctx,
1172 )
1173 .unwrap();
1174
1175 assert_eq!(
1177 envelope.cose_encrypt.recipients[0].protected.header.alg,
1178 Some(coset::Algorithm::PrivateUse(ALG_ARGON2ID13))
1179 );
1180 assert_eq!(
1181 envelope.cose_encrypt.protected.header.alg,
1182 Some(coset::Algorithm::Assigned(coset::iana::Algorithm::A256GCM))
1183 );
1184 }
1185
1186 #[test]
1187 fn test_fips_suite_uses_pbkdf2_and_aes_gcm() {
1188 let key_store = key_store_with_suite(CipherSuite::Fips);
1189 let mut ctx = key_store.context_mut();
1190 let test_key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
1191 let key_id = ctx.add_local_symmetric_key(test_key);
1192
1193 let password = "test_password";
1194 let envelope = PasswordProtectedKeyEnvelope::seal(
1195 key_id,
1196 password,
1197 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
1198 &ctx,
1199 )
1200 .unwrap();
1201
1202 assert_eq!(
1204 envelope.cose_encrypt.recipients[0].protected.header.alg,
1205 Some(coset::Algorithm::PrivateUse(ALG_PBKDF2_SHA256))
1206 );
1207 assert_eq!(
1208 envelope.cose_encrypt.protected.header.alg,
1209 Some(coset::Algorithm::Assigned(coset::iana::Algorithm::A256GCM))
1210 );
1211
1212 let serialized: Vec<u8> = (&envelope).into();
1214 let deserialized = PasswordProtectedKeyEnvelope::try_from(&serialized).unwrap();
1215 let unsealed = deserialized
1216 .unseal(
1217 password,
1218 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
1219 &mut ctx,
1220 )
1221 .unwrap();
1222 let unsealed_key = ctx.get_symmetric_key(unsealed).unwrap();
1223 let original = ctx.get_symmetric_key(key_id).unwrap();
1224 assert_eq!(unsealed_key, original);
1225 }
1226
1227 #[test]
1228 fn test_fips_wrong_password() {
1229 let key_store = key_store_with_suite(CipherSuite::Fips);
1230 let mut ctx = key_store.context_mut();
1231 let key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
1232
1233 let envelope = PasswordProtectedKeyEnvelope::seal(
1234 key_id,
1235 "test_password",
1236 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
1237 &ctx,
1238 )
1239 .unwrap();
1240
1241 assert!(matches!(
1242 envelope.unseal(
1243 "wrong_password",
1244 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
1245 &mut ctx,
1246 ),
1247 Err(PasswordProtectedKeyEnvelopeError::WrongPassword)
1248 ));
1249 }
1250
1251 #[test]
1252 fn test_reseal_preserves_fips_suite() {
1253 let key_store = key_store_with_suite(CipherSuite::Fips);
1254 let mut ctx = key_store.context_mut();
1255 let key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
1256
1257 let envelope = PasswordProtectedKeyEnvelope::seal(
1258 key_id,
1259 "test_password",
1260 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
1261 &ctx,
1262 )
1263 .unwrap();
1264
1265 let resealed = envelope
1266 .reseal(
1267 "test_password",
1268 "new_password",
1269 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
1270 )
1271 .unwrap();
1272
1273 assert_eq!(
1275 resealed.cose_encrypt.recipients[0].protected.header.alg,
1276 Some(coset::Algorithm::PrivateUse(ALG_PBKDF2_SHA256))
1277 );
1278 assert_eq!(
1279 resealed.cose_encrypt.protected.header.alg,
1280 Some(coset::Algorithm::Assigned(coset::iana::Algorithm::A256GCM))
1281 );
1282
1283 let unsealed = resealed
1284 .unseal_ref(
1285 "new_password",
1286 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
1287 )
1288 .unwrap();
1289 assert!(matches!(
1290 unsealed,
1291 SymmetricCryptoKey::XChaCha20Poly1305Key(_)
1292 ));
1293 }
1294}