1use std::{num::TryFromIntError, str::FromStr};
26
27use bitwarden_encoding::{B64, FromStrVisitor};
28use bitwarden_sensitive_value::ExposeSensitive;
29use ciborium::Value;
30use coset::{CborSerializable, CoseError, Header, HeaderBuilder, iana};
31use rand::Rng;
32use serde::{Deserialize, Serialize};
33use thiserror::Error;
34#[cfg(feature = "wasm")]
35use wasm_bindgen::convert::{FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi};
36
37use crate::{
38 ContentFormat, EncodedSymmetricKey, KeySlotIds, KeyStoreContext, SymmetricCryptoKey,
39 cose::{
40 ContentNamespace, CoseExtractError, SafeObjectNamespace, extract_bytes,
41 symmetric::{
42 CoseAlgorithmPolicy, CoseContentEncryptionAlgorithm, decrypt_cose, encrypt_cose,
43 },
44 },
45 keys::KeyId,
46 safe::{
47 DecodeSealedKeyError, HighEntropySecret, decode_sealed_symmetric_key, extract_key_id,
48 extract_single_recipient,
49 helpers::{debug_fmt, set_safe_namespaces, validate_safe_namespaces},
50 set_contained_key_id,
51 },
52};
53
54const HKDF_ALGORITHM: coset::iana::Algorithm = iana::Algorithm::Direct_HKDF_SHA_256;
57const HKDF_SALT_LABEL: i64 = iana::HeaderAlgorithmParameter::Salt as i64;
59const ENVELOPE_HKDF_SALT_SIZE: usize = 32;
62const ENVELOPE_HKDF_OUTPUT_KEY_SIZE: usize = 32;
64
65#[derive(Clone)]
75pub struct SecretProtectedKeyEnvelope {
76 cose_encrypt: coset::CoseEncrypt,
77}
78
79impl SecretProtectedKeyEnvelope {
80 pub fn seal<Ids: KeySlotIds>(
88 key_to_seal: Ids::Symmetric,
89 secret: &HighEntropySecret,
90 namespace: SecretProtectedKeyEnvelopeNamespace,
91 ctx: &KeyStoreContext<Ids>,
92 ) -> Result<Self, SecretProtectedKeyEnvelopeError> {
93 let key_ref = ctx
94 .get_symmetric_key(key_to_seal)
95 .map_err(|_| SecretProtectedKeyEnvelopeError::KeyMissing)?;
96 Self::seal_ref(key_ref, secret, namespace)
97 }
98
99 fn seal_ref(
102 key_to_seal: &SymmetricCryptoKey,
103 secret: &HighEntropySecret,
104 namespace: SecretProtectedKeyEnvelopeNamespace,
105 ) -> Result<Self, SecretProtectedKeyEnvelopeError> {
106 Self::seal_ref_with_settings(key_to_seal, secret, &HkdfSettings::new(), namespace)
107 }
108
109 fn seal_ref_with_settings(
112 key_to_seal: &SymmetricCryptoKey,
113 secret: &HighEntropySecret,
114 hkdf_settings: &HkdfSettings,
115 namespace: SecretProtectedKeyEnvelopeNamespace,
116 ) -> Result<Self, SecretProtectedKeyEnvelopeError> {
117 let cek = derive_cek(
125 hkdf_settings,
126 secret.as_bytes().expose_owned(),
127 iana::Algorithm::A256GCM,
128 )?;
129
130 let (content_format, key_to_seal_bytes) = match key_to_seal.to_encoded_raw() {
131 EncodedSymmetricKey::BitwardenLegacyKey(key_bytes) => {
132 (ContentFormat::BitwardenLegacyKey, key_bytes.to_vec())
133 }
134 EncodedSymmetricKey::CoseKey(key_bytes) => (ContentFormat::CoseKey, key_bytes.to_vec()),
135 };
136
137 let protected_header = {
138 let mut header = HeaderBuilder::from(content_format).build();
139 set_contained_key_id(&mut header, key_to_seal.key_id());
140 set_safe_namespaces(
141 &mut header,
142 SafeObjectNamespace::SecretProtectedKeyEnvelope,
143 namespace,
144 );
145 header
146 };
147
148 let builder = coset::CoseEncryptBuilder::new().add_recipient(
152 coset::CoseRecipientBuilder::new()
153 .unprotected(hkdf_settings.into())
154 .build(),
155 );
156 let cose_encrypt = encrypt_cose(
159 CoseContentEncryptionAlgorithm::Aes256Gcm,
160 builder,
161 protected_header,
162 &key_to_seal_bytes,
163 &cek,
164 )
165 .map_err(|_| SecretProtectedKeyEnvelopeError::Kdf)?;
166
167 Ok(SecretProtectedKeyEnvelope { cose_encrypt })
168 }
169
170 pub fn unseal<Ids: KeySlotIds>(
173 &self,
174 secret: &HighEntropySecret,
175 namespace: SecretProtectedKeyEnvelopeNamespace,
176 ctx: &mut KeyStoreContext<Ids>,
177 ) -> Result<Ids::Symmetric, SecretProtectedKeyEnvelopeError> {
178 let key = self.unseal_ref(secret, namespace)?;
179 Ok(ctx.add_local_symmetric_key(key))
180 }
181
182 fn unseal_ref(
183 &self,
184 secret: &HighEntropySecret,
185 content_namespace: SecretProtectedKeyEnvelopeNamespace,
186 ) -> Result<SymmetricCryptoKey, SecretProtectedKeyEnvelopeError> {
187 let recipient = extract_single_recipient(&self.cose_encrypt).map_err(|_| {
190 SecretProtectedKeyEnvelopeError::Parsing("Invalid number of recipients".to_string())
191 })?;
192
193 if recipient.unprotected.alg
194 != Some(coset::RegisteredLabelWithPrivate::Assigned(HKDF_ALGORITHM))
195 {
196 return Err(SecretProtectedKeyEnvelopeError::Parsing(
197 "Unknown or unsupported KDF algorithm".to_string(),
198 ));
199 }
200
201 validate_safe_namespaces(
202 &self.cose_encrypt.protected.header,
203 SafeObjectNamespace::SecretProtectedKeyEnvelope,
204 content_namespace,
205 )
206 .map_err(|_| SecretProtectedKeyEnvelopeError::InvalidNamespace)?;
207
208 let kdf_settings: HkdfSettings = (&recipient.unprotected).try_into().map_err(|_| {
209 SecretProtectedKeyEnvelopeError::Parsing(
210 "Invalid or missing KDF parameters".to_string(),
211 )
212 })?;
213 let content_alg = content_encryption_algorithm(&self.cose_encrypt.protected.header)?;
217 let cek = derive_cek(&kdf_settings, secret.as_bytes().expose_owned(), content_alg)?;
220
221 let key_bytes = decrypt_cose(
226 &self.cose_encrypt,
227 CoseAlgorithmPolicy::RequireProtectedHeaderAlgorithm,
228 &cek,
229 )
230 .map_err(|_| SecretProtectedKeyEnvelopeError::WrongSecret)?;
231
232 decode_sealed_symmetric_key(&self.cose_encrypt.protected.header, key_bytes).map_err(|e| {
233 match e {
234 DecodeSealedKeyError::InvalidContentFormat => {
235 SecretProtectedKeyEnvelopeError::Parsing("Invalid content format".to_string())
236 }
237 DecodeSealedKeyError::UnsupportedContentFormat => {
238 SecretProtectedKeyEnvelopeError::Parsing(
239 "Unknown or unsupported content format".to_string(),
240 )
241 }
242 DecodeSealedKeyError::InvalidKey => {
243 SecretProtectedKeyEnvelopeError::Parsing("Failed to decode key".to_string())
244 }
245 }
246 })
247 }
248
249 pub fn reseal(
251 &self,
252 secret: &HighEntropySecret,
253 new_secret: &HighEntropySecret,
254 namespace: SecretProtectedKeyEnvelopeNamespace,
255 ) -> Result<Self, SecretProtectedKeyEnvelopeError> {
256 let unsealed = self.unseal_ref(secret, namespace)?;
257 Self::seal_ref(&unsealed, new_secret, namespace)
258 }
259
260 pub fn contained_key_id(&self) -> Result<Option<KeyId>, SecretProtectedKeyEnvelopeError> {
263 extract_key_id(&self.cose_encrypt.protected.header)
264 .map_err(|_| SecretProtectedKeyEnvelopeError::Parsing("Invalid key id".to_string()))
265 }
266}
267
268impl From<&SecretProtectedKeyEnvelope> for Vec<u8> {
269 fn from(val: &SecretProtectedKeyEnvelope) -> Self {
270 val.cose_encrypt
271 .clone()
272 .to_vec()
273 .expect("Serialization to cose should not fail")
274 }
275}
276
277impl TryFrom<&Vec<u8>> for SecretProtectedKeyEnvelope {
278 type Error = CoseError;
279
280 fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
281 let cose_encrypt = coset::CoseEncrypt::from_slice(value)?;
282 Ok(SecretProtectedKeyEnvelope { cose_encrypt })
283 }
284}
285
286impl std::fmt::Debug for SecretProtectedKeyEnvelope {
287 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288 let mut s = f.debug_struct("SecretProtectedKeyEnvelope");
289
290 debug_fmt::<SecretProtectedKeyEnvelopeNamespace>(
291 &mut s,
292 &self.cose_encrypt.protected.header,
293 );
294
295 if let Ok(Some(key_id)) = self.contained_key_id() {
296 s.field("contained_key_id", &key_id);
297 }
298
299 s.finish()
300 }
301}
302
303impl FromStr for SecretProtectedKeyEnvelope {
304 type Err = SecretProtectedKeyEnvelopeError;
305
306 fn from_str(s: &str) -> Result<Self, Self::Err> {
307 let data = B64::try_from(s).map_err(|_| {
308 SecretProtectedKeyEnvelopeError::Parsing(
309 "Invalid SecretProtectedKeyEnvelope Base64 encoding".to_string(),
310 )
311 })?;
312 Self::try_from(&data.into_bytes()).map_err(|_| {
313 SecretProtectedKeyEnvelopeError::Parsing(
314 "Failed to parse SecretProtectedKeyEnvelope".to_string(),
315 )
316 })
317 }
318}
319
320impl From<SecretProtectedKeyEnvelope> for String {
321 fn from(val: SecretProtectedKeyEnvelope) -> Self {
322 let serialized: Vec<u8> = (&val).into();
323 B64::from(serialized).to_string()
324 }
325}
326
327impl<'de> Deserialize<'de> for SecretProtectedKeyEnvelope {
328 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
329 where
330 D: serde::Deserializer<'de>,
331 {
332 deserializer.deserialize_str(FromStrVisitor::new())
333 }
334}
335
336impl Serialize for SecretProtectedKeyEnvelope {
337 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
338 where
339 S: serde::Serializer,
340 {
341 let serialized: Vec<u8> = self.into();
342 serializer.serialize_str(&B64::from(serialized).to_string())
343 }
344}
345
346struct HkdfSettings {
349 alg: iana::Algorithm,
350 salt: [u8; ENVELOPE_HKDF_SALT_SIZE],
351}
352
353impl HkdfSettings {
354 fn new() -> Self {
356 Self {
357 alg: HKDF_ALGORITHM,
358 salt: make_salt(),
359 }
360 }
361}
362
363impl From<&HkdfSettings> for Header {
364 fn from(settings: &HkdfSettings) -> Header {
365 HeaderBuilder::new()
366 .value(HKDF_SALT_LABEL, Value::from(settings.salt.to_vec()))
367 .algorithm(settings.alg)
368 .build()
369 }
370}
371
372impl TryInto<HkdfSettings> for &Header {
373 type Error = SecretProtectedKeyEnvelopeError;
374
375 fn try_into(self) -> Result<HkdfSettings, SecretProtectedKeyEnvelopeError> {
376 Ok(HkdfSettings {
377 alg: match self.alg {
378 Some(coset::RegisteredLabelWithPrivate::Assigned(alg)) => alg,
379 _ => {
380 return Err(SecretProtectedKeyEnvelopeError::Parsing(
381 "Missing KDF algorithm".to_string(),
382 ));
383 }
384 },
385 salt: extract_bytes(self, HKDF_SALT_LABEL, "salt")?
386 .try_into()
387 .map_err(|_| {
388 SecretProtectedKeyEnvelopeError::Parsing("Invalid HKDF salt".to_string())
389 })?,
390 })
391 }
392}
393
394fn make_salt() -> [u8; ENVELOPE_HKDF_SALT_SIZE] {
395 let mut salt = [0u8; ENVELOPE_HKDF_SALT_SIZE];
396 bitwarden_random::rng().fill_bytes(&mut salt);
397 salt
398}
399
400fn kdf_context_info(alg: iana::Algorithm) -> Result<Vec<u8>, SecretProtectedKeyEnvelopeError> {
417 let empty_party_info = || Value::Array(vec![Value::Null, Value::Null, Value::Null]);
418 let context = Value::Array(vec![
419 Value::Integer((alg as i64).into()),
422 empty_party_info(),
423 empty_party_info(),
424 Value::Array(vec![
425 Value::Integer((ENVELOPE_HKDF_OUTPUT_KEY_SIZE as u64 * 8).into()),
427 Value::Bytes(vec![]),
429 ]),
430 ]);
431
432 let mut info = Vec::new();
433 ciborium::into_writer(&context, &mut info).map_err(|_| SecretProtectedKeyEnvelopeError::Kdf)?;
434 Ok(info)
435}
436
437fn content_encryption_algorithm(
441 header: &Header,
442) -> Result<iana::Algorithm, SecretProtectedKeyEnvelopeError> {
443 match header.alg {
444 Some(coset::RegisteredLabelWithPrivate::Assigned(alg)) => Ok(alg),
445 _ => Err(SecretProtectedKeyEnvelopeError::Parsing(
446 "Missing or unsupported content encryption algorithm".to_string(),
447 )),
448 }
449}
450
451fn derive_cek(
452 hkdf_settings: &HkdfSettings,
453 secret: &[u8],
454 alg: iana::Algorithm,
455) -> Result<[u8; ENVELOPE_HKDF_OUTPUT_KEY_SIZE], SecretProtectedKeyEnvelopeError> {
456 let info = kdf_context_info(alg)?;
461 let hkdf = hkdf::Hkdf::<sha2::Sha256>::new(Some(&hkdf_settings.salt), secret);
462 let mut key = [0u8; ENVELOPE_HKDF_OUTPUT_KEY_SIZE];
463 hkdf.expand(&info, &mut key)
464 .map_err(|_| SecretProtectedKeyEnvelopeError::Kdf)?;
465 Ok(key)
466}
467
468#[derive(Debug, Error)]
470pub enum SecretProtectedKeyEnvelopeError {
471 #[error("Wrong secret")]
473 WrongSecret,
474 #[error("Parsing error {0}")]
476 Parsing(String),
477 #[error("Kdf error")]
479 Kdf,
480 #[error("Key missing error")]
482 KeyMissing,
483 #[error("Could not write to key store")]
485 KeyStore,
486 #[error("Invalid namespace")]
488 InvalidNamespace,
489}
490
491impl From<CoseExtractError> for SecretProtectedKeyEnvelopeError {
492 fn from(err: CoseExtractError) -> Self {
493 let CoseExtractError::MissingValue(label) = err;
494 SecretProtectedKeyEnvelopeError::Parsing(format!("Missing value for {}", label))
495 }
496}
497
498impl From<TryFromIntError> for SecretProtectedKeyEnvelopeError {
499 fn from(err: TryFromIntError) -> Self {
500 SecretProtectedKeyEnvelopeError::Parsing(format!("Invalid integer: {}", err))
501 }
502}
503
504#[cfg(feature = "wasm")]
505#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
506const TS_CUSTOM_TYPES: &'static str = r#"
507export type SecretProtectedKeyEnvelope = Tagged<string, "SecretProtectedKeyEnvelope">;
508"#;
509
510#[cfg(feature = "wasm")]
511impl wasm_bindgen::describe::WasmDescribe for SecretProtectedKeyEnvelope {
512 fn describe() {
513 <String as wasm_bindgen::describe::WasmDescribe>::describe();
514 }
515}
516
517#[cfg(feature = "wasm")]
518impl FromWasmAbi for SecretProtectedKeyEnvelope {
519 type Abi = <String as FromWasmAbi>::Abi;
520
521 unsafe fn from_abi(abi: Self::Abi) -> Self {
522 use wasm_bindgen::UnwrapThrowExt;
523 let string = unsafe { String::from_abi(abi) };
524 SecretProtectedKeyEnvelope::from_str(&string).unwrap_throw()
525 }
526}
527
528#[cfg(feature = "wasm")]
529impl OptionFromWasmAbi for SecretProtectedKeyEnvelope {
530 fn is_none(abi: &Self::Abi) -> bool {
531 <String as OptionFromWasmAbi>::is_none(abi)
532 }
533}
534
535#[cfg(feature = "wasm")]
536impl IntoWasmAbi for SecretProtectedKeyEnvelope {
537 type Abi = <String as IntoWasmAbi>::Abi;
538
539 fn into_abi(self) -> Self::Abi {
540 let string: String = self.into();
541 string.into_abi()
542 }
543}
544
545#[cfg(feature = "wasm")]
546impl TryFrom<wasm_bindgen::JsValue> for SecretProtectedKeyEnvelope {
547 type Error = SecretProtectedKeyEnvelopeError;
548
549 fn try_from(value: wasm_bindgen::JsValue) -> Result<Self, Self::Error> {
550 let string = value.as_string().ok_or_else(|| {
551 SecretProtectedKeyEnvelopeError::Parsing(
552 "SecretProtectedKeyEnvelope JsValue is not a string".to_string(),
553 )
554 })?;
555 SecretProtectedKeyEnvelope::from_str(&string)
556 }
557}
558
559#[derive(Debug, Clone, Copy, PartialEq, Eq)]
561pub enum SecretProtectedKeyEnvelopeNamespace {
562 ExampleUse = 1,
565 DesktopBiometricUnlock = 2,
568 #[cfg(test)]
570 ExampleNamespace = -1,
571 #[cfg(test)]
573 ExampleNamespace2 = -2,
574}
575
576impl SecretProtectedKeyEnvelopeNamespace {
577 fn as_i64(&self) -> i64 {
579 *self as i64
580 }
581}
582
583impl TryFrom<i128> for SecretProtectedKeyEnvelopeNamespace {
584 type Error = SecretProtectedKeyEnvelopeError;
585
586 fn try_from(value: i128) -> Result<Self, Self::Error> {
587 match value {
588 1 => Ok(SecretProtectedKeyEnvelopeNamespace::ExampleUse),
589 2 => Ok(SecretProtectedKeyEnvelopeNamespace::DesktopBiometricUnlock),
590 #[cfg(test)]
591 -1 => Ok(SecretProtectedKeyEnvelopeNamespace::ExampleNamespace),
592 #[cfg(test)]
593 -2 => Ok(SecretProtectedKeyEnvelopeNamespace::ExampleNamespace2),
594 _ => Err(SecretProtectedKeyEnvelopeError::InvalidNamespace),
595 }
596 }
597}
598
599impl TryFrom<i64> for SecretProtectedKeyEnvelopeNamespace {
600 type Error = SecretProtectedKeyEnvelopeError;
601
602 fn try_from(value: i64) -> Result<Self, Self::Error> {
603 Self::try_from(i128::from(value))
604 }
605}
606
607impl From<SecretProtectedKeyEnvelopeNamespace> for i128 {
608 fn from(val: SecretProtectedKeyEnvelopeNamespace) -> Self {
609 val.as_i64().into()
610 }
611}
612
613impl ContentNamespace for SecretProtectedKeyEnvelopeNamespace {}
614
615#[cfg(test)]
616mod tests {
617 use super::*;
618 use crate::{KeyStore, SymmetricKeyAlgorithm, traits::tests::TestIds};
619
620 const TESTVECTOR_SECRET_BYTES: &[u8] = &[
623 174, 83, 45, 9, 235, 3, 186, 62, 199, 125, 198, 108, 129, 205, 24, 21, 174, 148, 88, 80,
624 10, 238, 169, 66, 75, 202, 41, 201, 186, 244, 169, 67,
625 ];
626
627 fn testvector_secret() -> HighEntropySecret {
628 HighEntropySecret::from_internal(TESTVECTOR_SECRET_BYTES)
629 }
630
631 const TEST_UNSEALED_COSEKEY_ENCODED: &[u8] = &[
635 165, 1, 4, 2, 80, 214, 124, 137, 200, 1, 180, 227, 27, 77, 48, 119, 198, 210, 9, 149, 144,
636 3, 58, 0, 1, 17, 111, 4, 132, 3, 4, 5, 6, 32, 88, 32, 111, 105, 200, 46, 142, 185, 114,
637 127, 136, 152, 153, 40, 8, 62, 120, 184, 252, 175, 210, 2, 245, 237, 175, 195, 73, 211,
638 136, 23, 217, 203, 35, 10, 1,
639 ];
640 const TESTVECTOR_COSEKEY_ENVELOPE: &[u8] = &[
641 132, 88, 40, 165, 1, 3, 3, 24, 101, 58, 0, 1, 21, 92, 80, 214, 124, 137, 200, 1, 180, 227,
642 27, 77, 48, 119, 198, 210, 9, 149, 144, 58, 0, 1, 56, 129, 6, 58, 0, 1, 56, 128, 32, 161,
643 5, 76, 155, 157, 246, 33, 115, 165, 158, 222, 125, 222, 199, 188, 88, 84, 132, 235, 37,
644 236, 53, 75, 63, 253, 184, 134, 147, 83, 103, 87, 56, 81, 69, 202, 114, 23, 82, 25, 163,
645 68, 36, 13, 104, 187, 54, 143, 167, 113, 63, 62, 88, 146, 50, 214, 209, 170, 6, 235, 122,
646 44, 129, 149, 67, 213, 112, 112, 55, 51, 183, 165, 61, 168, 174, 215, 147, 110, 133, 164,
647 198, 29, 177, 84, 20, 203, 8, 0, 211, 218, 226, 62, 121, 51, 129, 230, 248, 66, 170, 83,
648 106, 109, 129, 131, 64, 162, 1, 41, 51, 88, 32, 123, 254, 226, 185, 81, 106, 88, 73, 109,
649 191, 241, 1, 143, 230, 179, 47, 36, 100, 235, 131, 4, 180, 12, 96, 125, 91, 184, 5, 175,
650 125, 188, 16, 246,
651 ];
652 const TEST_UNSEALED_LEGACYKEY_ENCODED: &[u8] = &[
653 23, 37, 64, 225, 53, 59, 143, 179, 18, 121, 128, 120, 86, 134, 93, 166, 214, 151, 210, 46,
654 240, 216, 69, 249, 247, 222, 110, 100, 185, 38, 173, 84, 202, 107, 132, 251, 144, 245, 105,
655 244, 220, 93, 212, 227, 98, 208, 173, 122, 245, 78, 244, 106, 174, 124, 109, 91, 53, 119,
656 96, 182, 45, 174, 206, 131,
657 ];
658 const TESTVECTOR_LEGACYKEY_ENVELOPE: &[u8] = &[
659 132, 88, 52, 164, 1, 3, 3, 120, 34, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47,
660 120, 46, 98, 105, 116, 119, 97, 114, 100, 101, 110, 46, 108, 101, 103, 97, 99, 121, 45,
661 107, 101, 121, 58, 0, 1, 56, 129, 6, 58, 0, 1, 56, 128, 32, 161, 5, 76, 20, 11, 52, 107,
662 155, 203, 125, 143, 165, 38, 59, 135, 88, 80, 84, 46, 227, 50, 142, 191, 103, 207, 31, 192,
663 201, 215, 163, 102, 18, 93, 181, 247, 229, 12, 166, 221, 143, 98, 86, 74, 138, 12, 165, 1,
664 206, 101, 240, 222, 51, 239, 216, 4, 85, 61, 212, 62, 44, 29, 1, 184, 4, 191, 189, 248,
665 174, 159, 11, 133, 205, 19, 22, 28, 148, 138, 238, 136, 253, 173, 250, 69, 186, 232, 91,
666 222, 238, 9, 175, 178, 214, 27, 120, 254, 212, 110, 129, 131, 64, 162, 1, 41, 51, 88, 32,
667 222, 10, 249, 242, 57, 196, 223, 240, 234, 177, 19, 72, 201, 32, 1, 129, 46, 6, 76, 38,
668 149, 151, 217, 94, 84, 67, 50, 107, 103, 74, 88, 72, 246,
669 ];
670
671 #[test]
672 #[ignore = "Manual test to verify debug format"]
673 fn test_debug() {
674 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
675 let envelope = SecretProtectedKeyEnvelope::seal_ref(
676 &key,
677 &testvector_secret(),
678 SecretProtectedKeyEnvelopeNamespace::ExampleNamespace,
679 )
680 .unwrap();
681 println!("{:?}", envelope);
682 }
683
684 #[test]
685 fn test_testvector_cosekey() {
686 let key_store = KeyStore::<TestIds>::default();
687 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
688 let envelope = SecretProtectedKeyEnvelope::try_from(&TESTVECTOR_COSEKEY_ENVELOPE.to_vec())
689 .expect("Key envelope should be valid");
690 let key = envelope
691 .unseal(
692 &testvector_secret(),
693 SecretProtectedKeyEnvelopeNamespace::ExampleNamespace,
694 &mut ctx,
695 )
696 .expect("Unsealing should succeed");
697 let unsealed_key = ctx
698 .get_symmetric_key(key)
699 .expect("Key should exist in the key store");
700 assert_eq!(
701 unsealed_key.to_encoded().to_vec(),
702 TEST_UNSEALED_COSEKEY_ENCODED
703 );
704 }
705
706 #[test]
707 fn test_testvector_legacykey() {
708 let key_store = KeyStore::<TestIds>::default();
709 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
710 let envelope =
711 SecretProtectedKeyEnvelope::try_from(&TESTVECTOR_LEGACYKEY_ENVELOPE.to_vec())
712 .expect("Key envelope should be valid");
713 let key = envelope
714 .unseal(
715 &testvector_secret(),
716 SecretProtectedKeyEnvelopeNamespace::ExampleNamespace,
717 &mut ctx,
718 )
719 .expect("Unsealing should succeed");
720 let unsealed_key = ctx
721 .get_symmetric_key(key)
722 .expect("Key should exist in the key store");
723 assert_eq!(
724 unsealed_key.to_encoded().to_vec(),
725 TEST_UNSEALED_LEGACYKEY_ENCODED
726 );
727 }
728
729 #[test]
730 fn test_make_envelope() {
731 let key_store = KeyStore::<TestIds>::default();
732 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
733 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
734
735 let secret = testvector_secret();
736
737 let envelope = SecretProtectedKeyEnvelope::seal(
739 test_key,
740 &secret,
741 SecretProtectedKeyEnvelopeNamespace::ExampleNamespace,
742 &ctx,
743 )
744 .unwrap();
745 let serialized: Vec<u8> = (&envelope).into();
746
747 let deserialized: SecretProtectedKeyEnvelope =
749 SecretProtectedKeyEnvelope::try_from(&serialized).unwrap();
750 let key = deserialized
751 .unseal(
752 &secret,
753 SecretProtectedKeyEnvelopeNamespace::ExampleNamespace,
754 &mut ctx,
755 )
756 .unwrap();
757
758 let unsealed_key = ctx
760 .get_symmetric_key(key)
761 .expect("Key should exist in the key store");
762
763 let key_before_sealing = ctx
764 .get_symmetric_key(test_key)
765 .expect("Key should exist in the key store");
766
767 assert_eq!(unsealed_key, key_before_sealing);
768 }
769
770 #[test]
771 fn test_make_envelope_legacy_key() {
772 let key_store = KeyStore::<TestIds>::default();
773 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
774 let test_key = ctx.generate_symmetric_key();
775
776 let secret = testvector_secret();
777
778 let envelope = SecretProtectedKeyEnvelope::seal(
780 test_key,
781 &secret,
782 SecretProtectedKeyEnvelopeNamespace::ExampleNamespace,
783 &ctx,
784 )
785 .unwrap();
786 let serialized: Vec<u8> = (&envelope).into();
787
788 let deserialized: SecretProtectedKeyEnvelope =
790 SecretProtectedKeyEnvelope::try_from(&serialized).unwrap();
791 let key = deserialized
792 .unseal(
793 &secret,
794 SecretProtectedKeyEnvelopeNamespace::ExampleNamespace,
795 &mut ctx,
796 )
797 .unwrap();
798
799 let unsealed_key = ctx
801 .get_symmetric_key(key)
802 .expect("Key should exist in the key store");
803
804 let key_before_sealing = ctx
805 .get_symmetric_key(test_key)
806 .expect("Key should exist in the key store");
807
808 assert_eq!(unsealed_key, key_before_sealing);
809 }
810
811 #[test]
812 fn test_reseal_envelope() {
813 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
814 let secret = testvector_secret();
815 let new_secret = HighEntropySecret::make(32).unwrap();
816
817 let envelope: SecretProtectedKeyEnvelope = SecretProtectedKeyEnvelope::seal_ref(
819 &key,
820 &secret,
821 SecretProtectedKeyEnvelopeNamespace::ExampleNamespace,
822 )
823 .expect("Sealing should work");
824
825 let envelope = envelope
827 .reseal(
828 &secret,
829 &new_secret,
830 SecretProtectedKeyEnvelopeNamespace::ExampleNamespace,
831 )
832 .expect("Resealing should work");
833 let unsealed = envelope
834 .unseal_ref(
835 &new_secret,
836 SecretProtectedKeyEnvelopeNamespace::ExampleNamespace,
837 )
838 .expect("Unsealing should work");
839
840 assert_eq!(unsealed, key);
842 }
843
844 #[test]
845 fn test_wrong_secret() {
846 let key_store = KeyStore::<TestIds>::default();
847 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
848 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
849
850 let secret = testvector_secret();
851 let wrong_secret = HighEntropySecret::make(32).unwrap();
852
853 let envelope = SecretProtectedKeyEnvelope::seal(
855 test_key,
856 &secret,
857 SecretProtectedKeyEnvelopeNamespace::ExampleNamespace,
858 &ctx,
859 )
860 .unwrap();
861
862 let deserialized: SecretProtectedKeyEnvelope =
864 SecretProtectedKeyEnvelope::try_from(&(&envelope).into()).unwrap();
865 assert!(matches!(
866 deserialized.unseal(
867 &wrong_secret,
868 SecretProtectedKeyEnvelopeNamespace::ExampleNamespace,
869 &mut ctx
870 ),
871 Err(SecretProtectedKeyEnvelopeError::WrongSecret)
872 ));
873 }
874
875 #[test]
876 fn test_wrong_safe_namespace() {
877 let key_store = KeyStore::<TestIds>::default();
878 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
879 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
880 let secret = testvector_secret();
881
882 let mut envelope = SecretProtectedKeyEnvelope::seal(
883 test_key,
884 &secret,
885 SecretProtectedKeyEnvelopeNamespace::ExampleNamespace,
886 &ctx,
887 )
888 .expect("Seal works");
889
890 if let Some((_, value)) = envelope
891 .cose_encrypt
892 .protected
893 .header
894 .rest
895 .iter_mut()
896 .find(|(label, _)| {
897 matches!(label, coset::Label::Int(label_value) if *label_value == crate::cose::SAFE_OBJECT_NAMESPACE)
898 })
899 {
900 *value = Value::Integer((SafeObjectNamespace::DataEnvelope as i64).into());
901 }
902
903 let deserialized: SecretProtectedKeyEnvelope =
904 SecretProtectedKeyEnvelope::try_from(&(&envelope).into())
905 .expect("Envelope should be valid");
906
907 assert!(matches!(
908 deserialized.unseal(
909 &secret,
910 SecretProtectedKeyEnvelopeNamespace::ExampleNamespace,
911 &mut ctx,
912 ),
913 Err(SecretProtectedKeyEnvelopeError::InvalidNamespace)
914 ));
915 }
916
917 #[test]
918 fn test_key_id() {
919 let key_store = KeyStore::<TestIds>::default();
920 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
921 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
922 let key_id = ctx.get_symmetric_key(test_key).unwrap().key_id().unwrap();
923
924 let secret = testvector_secret();
925
926 let envelope = SecretProtectedKeyEnvelope::seal(
928 test_key,
929 &secret,
930 SecretProtectedKeyEnvelopeNamespace::ExampleNamespace,
931 &ctx,
932 )
933 .unwrap();
934 let contained_key_id = envelope.contained_key_id().unwrap();
935 assert_eq!(Some(key_id), contained_key_id);
936 }
937
938 #[test]
939 fn test_no_key_id() {
940 let key_store = KeyStore::<TestIds>::default();
941 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
942 let test_key = ctx.generate_symmetric_key();
943
944 let secret = testvector_secret();
945
946 let envelope = SecretProtectedKeyEnvelope::seal(
948 test_key,
949 &secret,
950 SecretProtectedKeyEnvelopeNamespace::ExampleNamespace,
951 &ctx,
952 )
953 .unwrap();
954 let contained_key_id = envelope.contained_key_id().unwrap();
955 assert_eq!(None, contained_key_id);
956 }
957}