1use std::str::FromStr;
2
3use bitwarden_encoding::{B64, FromStrVisitor, NotB64EncodedError};
4#[allow(unused_imports)]
5use coset::{CborSerializable, ProtectedHeader, RegisteredLabel, iana::CoapContentFormat};
6use serde::{Deserialize, Serialize, de::DeserializeOwned};
7use thiserror::Error;
8#[cfg(feature = "wasm")]
9use wasm_bindgen::convert::FromWasmAbi;
10
11use crate::{
12 Aes256GcmKey, CONTENT_TYPE_PADDED_CBOR, CoseEncrypt0Bytes, CoseKeyView, CryptoError, EncString,
13 EncodingError, KeyId, KeySlotIds, SerializedMessage, SymmetricCryptoKey,
14 cose::{
15 ContentNamespace, SafeObjectNamespace,
16 symmetric::{
17 CoseAlgorithmPolicy, CoseContentEncryptionAlgorithm, decrypt_cose0, encrypt_cose0,
18 },
19 },
20 safe::{
21 ContentEncryptionKey,
22 helpers::{debug_fmt, set_safe_namespaces, validate_safe_namespaces},
23 },
24 utils::pad_bytes,
25};
26
27pub(crate) const DATA_ENVELOPE_PADDING_SIZE: usize = 64;
28
29pub trait SealableVersionedData: Serialize + DeserializeOwned {
33 const NAMESPACE: DataEnvelopeNamespace;
35}
36
37pub trait SealableData: Serialize + DeserializeOwned {}
51
52#[derive(Clone)]
63pub struct DataEnvelope {
64 envelope_data: CoseEncrypt0Bytes,
65}
66
67impl DataEnvelope {
68 pub fn seal<Ids: KeySlotIds, T>(
71 data: T,
72 ctx: &mut crate::store::KeyStoreContext<Ids>,
73 ) -> Result<(Self, Ids::Symmetric), DataEnvelopeError>
74 where
75 T: Serialize + SealableVersionedData,
76 {
77 let cek_id = ContentEncryptionKey::make(ctx);
80 let mut cek = match ctx.get_symmetric_key(cek_id) {
81 Ok(SymmetricCryptoKey::Aes256GcmKey(key)) => key.clone(),
82 _ => return Err(DataEnvelopeError::KeyStore),
83 };
84 let envelope = Self::seal_ref(&data, T::NAMESPACE, &cek)?;
85
86 cek.disable_key_operation(coset::iana::KeyOperation::Encrypt)
89 .disable_key_operation(coset::iana::KeyOperation::WrapKey)
90 .disable_key_operation(coset::iana::KeyOperation::UnwrapKey);
91 ctx.set_symmetric_key_internal(cek_id, SymmetricCryptoKey::Aes256GcmKey(cek))
92 .map_err(|_| DataEnvelopeError::KeyStore)?;
93 Ok((envelope, cek_id))
94 }
95
96 pub fn seal_with_wrapping_key<Ids: KeySlotIds, T>(
99 data: T,
100 wrapping_key: &Ids::Symmetric,
101 ctx: &mut crate::store::KeyStoreContext<Ids>,
102 ) -> Result<(Self, EncString), DataEnvelopeError>
103 where
104 T: Serialize + SealableVersionedData,
105 {
106 let (envelope, cek) = Self::seal(data, ctx)?;
107
108 let wrapped_cek = ctx
109 .wrap_symmetric_key(*wrapping_key, cek)
110 .map_err(|_| DataEnvelopeError::Encryption)?;
111
112 Ok((envelope, wrapped_cek))
113 }
114
115 fn seal_ref<T>(
118 data: &T,
119 namespace: DataEnvelopeNamespace,
120 cek: &Aes256GcmKey,
121 ) -> Result<DataEnvelope, DataEnvelopeError>
122 where
123 T: Serialize + SealableVersionedData,
124 {
125 let serialized_message =
127 SerializedMessage::encode(&data).map_err(|_| DataEnvelopeError::Encoding)?;
128 if serialized_message.content_type() != coset::iana::CoapContentFormat::Cbor {
129 return Err(DataEnvelopeError::UnsupportedContentFormat);
130 }
131
132 let serialized_and_padded_message =
133 pad_cbor(serialized_message.as_bytes()).map_err(|_| DataEnvelopeError::Encoding)?;
134
135 let mut protected_header = coset::HeaderBuilder::new()
137 .key_id(cek.key_id.as_slice().to_vec())
138 .content_type(CONTENT_TYPE_PADDED_CBOR.to_string())
139 .build();
140 set_safe_namespaces(
141 &mut protected_header,
142 SafeObjectNamespace::DataEnvelope,
143 namespace,
144 );
145
146 let encrypt0 = encrypt_cose0(
150 CoseContentEncryptionAlgorithm::Aes256Gcm,
151 coset::CoseEncrypt0Builder::new(),
152 protected_header,
153 &serialized_and_padded_message,
154 cek.enc_key.as_slice(),
155 )
156 .map_err(|_| DataEnvelopeError::Encoding)?;
157
158 let envelope_data = encrypt0
160 .to_vec()
161 .map(CoseEncrypt0Bytes::from)
162 .map_err(|_| DataEnvelopeError::Encoding)?;
163
164 Ok(DataEnvelope { envelope_data })
165 }
166
167 pub fn unseal<Ids: KeySlotIds, T>(
170 &self,
171 cek_keyslot: Ids::Symmetric,
172 ctx: &mut crate::store::KeyStoreContext<Ids>,
173 ) -> Result<T, DataEnvelopeError>
174 where
175 T: DeserializeOwned + SealableVersionedData,
176 {
177 let cek = ctx
178 .get_symmetric_key(cek_keyslot)
179 .map_err(|_| DataEnvelopeError::KeyStore)?;
180
181 let view = match cek {
185 SymmetricCryptoKey::Aes256CbcKey(_) | SymmetricCryptoKey::Aes256CbcHmacKey(_) => {
186 return Err(DataEnvelopeError::UnsupportedContentFormat);
187 }
188 cek => cek
189 .as_cose_key_view()
190 .ok_or(DataEnvelopeError::UnsupportedContentFormat)?,
191 };
192 self.unseal_ref(T::NAMESPACE, view)
193 }
194
195 pub fn unseal_with_wrapping_key<Ids: KeySlotIds, T>(
197 &self,
198 wrapping_key: &Ids::Symmetric,
199 wrapped_cek: &EncString,
200 ctx: &mut crate::store::KeyStoreContext<Ids>,
201 ) -> Result<T, DataEnvelopeError>
202 where
203 T: DeserializeOwned + SealableVersionedData,
204 {
205 let cek = ctx
206 .unwrap_symmetric_key(*wrapping_key, wrapped_cek)
207 .map_err(|_| DataEnvelopeError::Decryption)?;
208 self.unseal(cek, ctx)
209 }
210
211 fn unseal_ref<T>(
214 &self,
215 namespace: DataEnvelopeNamespace,
216 cek: CoseKeyView,
217 ) -> Result<T, DataEnvelopeError>
218 where
219 T: DeserializeOwned + SealableVersionedData,
220 {
221 let msg = coset::CoseEncrypt0::from_slice(self.envelope_data.as_ref())
223 .map_err(|_| DataEnvelopeError::CoseDecoding)?;
224 let content_format =
225 content_format(&msg.protected).map_err(|_| DataEnvelopeError::Decoding)?;
226
227 if msg.protected.header.key_id != cek.key_id().as_slice() {
229 return Err(DataEnvelopeError::WrongKey);
230 }
231
232 validate_safe_namespaces(
233 &msg.protected.header,
234 SafeObjectNamespace::DataEnvelope,
235 namespace,
236 )
237 .map_err(|_| DataEnvelopeError::InvalidNamespace)?;
238
239 if content_format != CONTENT_TYPE_PADDED_CBOR {
240 return Err(DataEnvelopeError::UnsupportedContentFormat);
241 }
242
243 let decrypted_message = decrypt_cose0(
246 &msg,
247 CoseAlgorithmPolicy::Exactly(cek.algorithm()),
248 cek.key_bytes(),
249 )
250 .map_err(|_| DataEnvelopeError::Decryption)?;
251
252 let unpadded_message =
253 unpad_cbor(&decrypted_message).map_err(|_| DataEnvelopeError::Decryption)?;
254
255 let serialized_message =
257 SerializedMessage::from_bytes(unpadded_message, CoapContentFormat::Cbor);
258 serialized_message
259 .decode()
260 .map_err(|_| DataEnvelopeError::Decoding)
261 }
262}
263
264pub(super) fn content_format(protected_header: &ProtectedHeader) -> Result<String, EncodingError> {
268 protected_header
269 .header
270 .content_type
271 .as_ref()
272 .and_then(|ct| match ct {
273 RegisteredLabel::Text(content_format) => Some(content_format.clone()),
274 _ => None,
275 })
276 .ok_or(EncodingError::InvalidCoseEncoding)
277}
278
279impl From<&DataEnvelope> for Vec<u8> {
280 fn from(val: &DataEnvelope) -> Self {
281 val.envelope_data.to_vec()
282 }
283}
284
285impl From<Vec<u8>> for DataEnvelope {
286 fn from(data: Vec<u8>) -> Self {
287 DataEnvelope {
288 envelope_data: CoseEncrypt0Bytes::from(data),
289 }
290 }
291}
292
293impl std::fmt::Debug for DataEnvelope {
294 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295 let mut s = f.debug_struct("DataEnvelope");
296 if let Ok(msg) = coset::CoseEncrypt0::from_slice(self.envelope_data.as_ref()) {
297 debug_fmt::<DataEnvelopeNamespace>(&mut s, &msg.protected.header);
298 if let Ok(encrypted_by) = KeyId::try_from(msg.protected.header.key_id.as_slice()) {
299 s.field("encrypted_by", &encrypted_by);
300 }
301 }
302 s.finish()
303 }
304}
305
306impl FromStr for DataEnvelope {
307 type Err = NotB64EncodedError;
308
309 fn from_str(s: &str) -> Result<Self, Self::Err> {
310 let data = B64::try_from(s)?;
311 Ok(Self::from(data.into_bytes()))
312 }
313}
314
315impl From<DataEnvelope> for String {
316 fn from(val: DataEnvelope) -> Self {
317 let serialized: Vec<u8> = (&val).into();
318 B64::from(serialized).to_string()
319 }
320}
321
322impl<'de> Deserialize<'de> for DataEnvelope {
323 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
324 where
325 D: serde::Deserializer<'de>,
326 {
327 deserializer.deserialize_str(FromStrVisitor::new())
328 }
329}
330
331impl Serialize for DataEnvelope {
332 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
333 where
334 S: serde::Serializer,
335 {
336 let serialized: Vec<u8> = self.into();
337 serializer.serialize_str(&B64::from(serialized).to_string())
338 }
339}
340
341impl std::fmt::Display for DataEnvelope {
342 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
343 let serialized: Vec<u8> = self.into();
344 write!(f, "{}", B64::from(serialized))
345 }
346}
347
348#[derive(Debug, Error)]
350pub enum DataEnvelopeError {
351 #[error("Unsupported content format")]
353 UnsupportedContentFormat,
354 #[error("Failed to decode COSE message")]
356 CoseDecoding,
357 #[error("Failed to decode the content of the envelope")]
359 Decoding,
360 #[error("Encoding error")]
362 Encoding,
363 #[error("KeyStore error")]
365 KeyStore,
366 #[error("Decryption error")]
368 Decryption,
369 #[error("Encryption error")]
371 Encryption,
372 #[error("Parsing error: {0}")]
374 Parsing(String),
375 #[error("Invalid namespace")]
377 InvalidNamespace,
378 #[error("Wrong key used for decryption")]
380 WrongKey,
381}
382
383#[cfg(feature = "wasm")]
384#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
385const TS_CUSTOM_TYPES: &'static str = r#"
386export type DataEnvelope = Tagged<string, "DataEnvelope">;
387"#;
388
389#[cfg(feature = "wasm")]
390impl wasm_bindgen::describe::WasmDescribe for DataEnvelope {
391 fn describe() {
392 <String as wasm_bindgen::describe::WasmDescribe>::describe();
393 }
394}
395
396#[cfg(feature = "wasm")]
397impl FromWasmAbi for DataEnvelope {
398 type Abi = <String as FromWasmAbi>::Abi;
399
400 unsafe fn from_abi(abi: Self::Abi) -> Self {
401 use wasm_bindgen::UnwrapThrowExt;
402
403 let s = unsafe { String::from_abi(abi) };
404 Self::from_str(&s).unwrap_throw()
405 }
406}
407
408fn pad_cbor(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
409 let mut data = data.to_vec();
410 pad_bytes(&mut data, DATA_ENVELOPE_PADDING_SIZE).map_err(|_| CryptoError::InvalidPadding)?;
411 Ok(data)
412}
413
414fn unpad_cbor(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
415 let unpadded = crate::utils::unpad_bytes(data).map_err(|_| CryptoError::InvalidPadding)?;
416 Ok(unpadded.to_vec())
417}
418
419#[macro_export]
454macro_rules! generate_versioned_sealable {
455 (
456 $enum_name:ident,
458 $namespace:path,
460 [ $( $variant_ty:ident => $rename:literal ),+ $(,)? ]
462 ) => {
463 #[derive(Serialize, Deserialize, Debug, PartialEq)]
465 #[serde(tag = "version", content = "content")]
466 enum $enum_name {
467 $(
468 #[serde(rename = $rename)]
469 $variant_ty($variant_ty),
471 )+
472 }
473
474 impl SealableVersionedData for $enum_name
476 where
477 $( $variant_ty: SealableData ),+
478 {
479 const NAMESPACE: DataEnvelopeNamespace = $namespace;
481 }
482
483 $(
485 impl From<$variant_ty> for $enum_name {
486 fn from(value: $variant_ty) -> Self {
487 Self::$variant_ty(value)
488 }
489 }
490 )+
491 };
492}
493
494#[derive(Debug, Clone, Copy, PartialEq, Eq)]
501pub enum DataEnvelopeNamespace {
502 VaultItem = 1,
504 OrganizationInvite = 2,
507 RegistrationOpenOrgInviteData = 3,
511 #[cfg(test)]
513 ExampleNamespace = -1,
514 #[cfg(test)]
516 ExampleNamespace2 = -2,
517}
518
519impl DataEnvelopeNamespace {
520 fn as_i64(&self) -> i64 {
522 *self as i64
523 }
524}
525
526impl TryFrom<i128> for DataEnvelopeNamespace {
527 type Error = DataEnvelopeError;
528
529 fn try_from(value: i128) -> Result<Self, Self::Error> {
530 match value {
531 1 => Ok(DataEnvelopeNamespace::VaultItem),
532 2 => Ok(DataEnvelopeNamespace::OrganizationInvite),
533 3 => Ok(DataEnvelopeNamespace::RegistrationOpenOrgInviteData),
534 #[cfg(test)]
535 -1 => Ok(DataEnvelopeNamespace::ExampleNamespace),
536 #[cfg(test)]
537 -2 => Ok(DataEnvelopeNamespace::ExampleNamespace2),
538 _ => Err(DataEnvelopeError::InvalidNamespace),
539 }
540 }
541}
542
543impl TryFrom<i64> for DataEnvelopeNamespace {
544 type Error = DataEnvelopeError;
545
546 fn try_from(value: i64) -> Result<Self, Self::Error> {
547 Self::try_from(i128::from(value))
548 }
549}
550
551impl From<DataEnvelopeNamespace> for i128 {
552 fn from(val: DataEnvelopeNamespace) -> Self {
553 val.as_i64().into()
554 }
555}
556
557impl ContentNamespace for DataEnvelopeNamespace {}
558
559#[cfg(test)]
560mod tests {
561 use serde::Deserialize;
562
563 use super::*;
564 use crate::{SymmetricKeyAlgorithm, safe::KeyEncryptionKey, traits::tests::TestIds};
565
566 #[derive(Serialize, Deserialize, Debug, PartialEq)]
567 struct TestDataV1 {
568 field: u32,
569 }
570 impl SealableData for TestDataV1 {}
571
572 generate_versioned_sealable!(
573 TestData,
574 DataEnvelopeNamespace::ExampleNamespace,
575 [
576 TestDataV1 => "1",
577 ]
578 );
579
580 const TEST_VECTOR_CEK: &str =
583 "pQEEAlB5RTKA0xXdA7C4iQE4QfVUAzoAARFvBIEEIFggQYqnsrAfeFFTaXGXB54YrksB6eQcctMpnaZ8rG6rMJ0B";
584 const TEST_VECTOR_ENVELOPE: &str = "g1hLpQE6AAERbwN4I2FwcGxpY2F0aW9uL3guYml0d2FyZGVuLmNib3ItcGFkZGVkBFB5RTKA0xXdA7C4iQE4QfVUOgABOIECOgABOIAgoQVYGLfQrYHVWxRxO6A8m/yp5DPbBIn3h8nijlhQj4jFwDLWfFz7le1Oy8dTls5vdEFg/FjjsPvXicI2bdb5KDdJCz/YkEu0kqjpQwdCcALpJLVJwgQQeKIeU2klBHEPZjnlLpRRXeCUp5c5BYQ=";
585
586 const TEST_VECTOR_AES_GCM_CEK: &str =
589 "pQEEAlDLIx+izSLk9h9sVjHFzpKoAwMEgQQgWCDWM3iwTX2/LHTIaXS0cPIKCYFZethtKyD6Pucdt4fkGQQEBAQ=";
590 const TEST_VECTOR_AES_GCM_ENVELOPE: &str = "g1hHpQEDA3gjYXBwbGljYXRpb24veC5iaXR3YXJkZW4uY2Jvci1wYWRkZWQEUMsjH6LNIuT2H2xWMcXOkqg6AAE4gQI6AAE4gCChBUyoF/oGEm+lJYrjjgdYUGIH5LnQjqFMWo2BJORVPYH2+hEWkxIn3tRgAMHNwIr0nTXMVD1EyVGZOsHSDMPqn2HaYrDeR5s+Rg0ezZ2WLh8n2FbdC44A/ExOms4IHcyT";
591
592 fn unseal_with_cek<T>(
594 envelope: &DataEnvelope,
595 namespace: DataEnvelopeNamespace,
596 cek: &Aes256GcmKey,
597 ) -> Result<T, DataEnvelopeError>
598 where
599 T: serde::de::DeserializeOwned + SealableVersionedData,
600 {
601 envelope.unseal_ref(namespace, CoseKeyView::Aes256Gcm(cek))
602 }
603
604 #[test]
605 #[ignore = "Manual test to verify debug format"]
606 fn test_debug() {
607 let data: TestData = TestDataV1 { field: 42 }.into();
608 let envelope = DataEnvelope::seal_ref(
609 &data,
610 DataEnvelopeNamespace::ExampleNamespace,
611 &Aes256GcmKey::make(),
612 )
613 .unwrap();
614 println!("{:?}", envelope);
615 }
616
617 #[test]
618 #[ignore]
619 fn generate_aes_gcm_test_vectors() {
620 let data: TestData = TestDataV1 { field: 123 }.into();
621 let cek = Aes256GcmKey::make();
622 let envelope =
623 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace, &cek).unwrap();
624 let unsealed_data: TestData =
625 unseal_with_cek(&envelope, DataEnvelopeNamespace::ExampleNamespace, &cek).unwrap();
626 assert_eq!(unsealed_data, data);
627 println!(
628 "const TEST_VECTOR_AES_GCM_CEK: &str = \"{}\";",
629 B64::from(SymmetricCryptoKey::Aes256GcmKey(cek).to_encoded())
630 );
631 println!(
632 "const TEST_VECTOR_AES_GCM_ENVELOPE: &str = \"{}\";",
633 String::from(envelope)
634 );
635 }
636
637 #[test]
638 fn test_data_envelope_legacy_xchacha20_test_vector() {
639 let cek = SymmetricCryptoKey::try_from(B64::try_from(TEST_VECTOR_CEK).unwrap()).unwrap();
640 let SymmetricCryptoKey::XChaCha20Poly1305Key(ref cek) = cek else {
641 panic!("Invalid CEK type");
642 };
643
644 let envelope: DataEnvelope = TEST_VECTOR_ENVELOPE.parse().unwrap();
645 let unsealed_data: TestData = envelope
646 .unseal_ref(
647 DataEnvelopeNamespace::ExampleNamespace,
648 CoseKeyView::XChaCha20Poly1305(cek),
649 )
650 .unwrap();
651 assert_eq!(unsealed_data, TestDataV1 { field: 123 }.into());
652 }
653
654 #[test]
655 fn test_data_envelope_aes_gcm_test_vector() {
656 let cek =
657 SymmetricCryptoKey::try_from(B64::try_from(TEST_VECTOR_AES_GCM_CEK).unwrap()).unwrap();
658 let SymmetricCryptoKey::Aes256GcmKey(ref cek) = cek else {
659 panic!("Invalid CEK type");
660 };
661
662 let envelope: DataEnvelope = TEST_VECTOR_AES_GCM_ENVELOPE.parse().unwrap();
663 let unsealed_data: TestData =
664 unseal_with_cek(&envelope, DataEnvelopeNamespace::ExampleNamespace, cek).unwrap();
665 assert_eq!(unsealed_data, TestDataV1 { field: 123 }.into());
666 }
667
668 #[test]
669 fn test_data_envelope_uses_aes_gcm() {
670 let data: TestData = TestDataV1 { field: 42 }.into();
671 let envelope = DataEnvelope::seal_ref(
672 &data,
673 DataEnvelopeNamespace::ExampleNamespace,
674 &Aes256GcmKey::make(),
675 )
676 .unwrap();
677
678 let msg = coset::CoseEncrypt0::from_slice(envelope.envelope_data.as_ref()).unwrap();
680 assert_eq!(
681 msg.protected.header.alg,
682 Some(coset::Algorithm::Assigned(coset::iana::Algorithm::A256GCM))
683 );
684 }
685
686 #[test]
687 fn test_data_envelope() {
688 let data: TestData = TestDataV1 { field: 42 }.into();
690
691 let cek = Aes256GcmKey::make();
693 let envelope =
694 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace, &cek).unwrap();
695 let unsealed_data: TestData =
696 unseal_with_cek(&envelope, DataEnvelopeNamespace::ExampleNamespace, &cek).unwrap();
697
698 assert_eq!(unsealed_data, data);
700 }
701
702 #[test]
703 fn test_data_envelope_with_keystore_roundtrip() {
704 let data: TestData = TestDataV1 { field: 7 }.into();
705 let key_store = crate::store::KeyStore::<TestIds>::default();
706 let mut ctx = key_store.context_mut();
707
708 let (envelope, cek_id) = DataEnvelope::seal(data, &mut ctx).unwrap();
709
710 assert_eq!(
712 ctx.get_symmetric_key_algorithm(cek_id).unwrap(),
713 SymmetricKeyAlgorithm::Aes256Gcm
714 );
715
716 let unsealed: TestData = envelope.unseal(cek_id, &mut ctx).unwrap();
717 assert_eq!(unsealed, TestDataV1 { field: 7 }.into());
718 }
719
720 #[test]
721 fn test_data_envelope_wrapping_key_roundtrip() {
722 let data: TestData = TestDataV1 { field: 99 }.into();
723 let key_store = crate::store::KeyStore::<TestIds>::default();
724 let mut ctx = key_store.context_mut();
725
726 let wrapping_key = KeyEncryptionKey::make(&mut ctx);
727
728 let (envelope, wrapped_cek) =
729 DataEnvelope::seal_with_wrapping_key(data, &wrapping_key, &mut ctx).unwrap();
730 let unsealed: TestData = envelope
731 .unseal_with_wrapping_key(&wrapping_key, &wrapped_cek, &mut ctx)
732 .unwrap();
733 assert_eq!(unsealed, TestDataV1 { field: 99 }.into());
734 }
735
736 #[test]
737 fn test_namespace_validation_success() {
738 let data: TestData = TestDataV1 { field: 123 }.into();
739
740 let cek1 = Aes256GcmKey::make();
742 let envelope1 =
743 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace, &cek1).unwrap();
744 let unsealed_data1: TestData =
745 unseal_with_cek(&envelope1, DataEnvelopeNamespace::ExampleNamespace, &cek1).unwrap();
746 assert_eq!(unsealed_data1, data);
747
748 let cek2 = Aes256GcmKey::make();
750 let envelope2 =
751 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace2, &cek2).unwrap();
752 let unsealed_data2: TestData =
753 unseal_with_cek(&envelope2, DataEnvelopeNamespace::ExampleNamespace2, &cek2).unwrap();
754 assert_eq!(unsealed_data2, data);
755 }
756
757 #[test]
758 fn test_namespace_validation_failure() {
759 let data: TestData = TestDataV1 { field: 456 }.into();
760
761 let cek = Aes256GcmKey::make();
763 let envelope =
764 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace, &cek).unwrap();
765
766 let result: Result<TestData, DataEnvelopeError> =
768 unseal_with_cek(&envelope, DataEnvelopeNamespace::ExampleNamespace2, &cek);
769 assert!(matches!(result, Err(DataEnvelopeError::InvalidNamespace)));
770
771 let unsealed_data: TestData =
773 unseal_with_cek(&envelope, DataEnvelopeNamespace::ExampleNamespace, &cek).unwrap();
774 assert_eq!(unsealed_data, data);
775 }
776
777 #[test]
778 fn test_namespace_validation_with_keystore() {
779 let data: TestData = TestDataV1 { field: 789 }.into();
780 let key_store = crate::store::KeyStore::<TestIds>::default();
781 let mut ctx = key_store.context_mut();
782
783 let cek = Aes256GcmKey::make();
785 let envelope =
786 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace2, &cek).unwrap();
787 ctx.set_symmetric_key_internal(
788 crate::traits::tests::TestSymmKey::A(0),
789 SymmetricCryptoKey::Aes256GcmKey(cek),
790 )
791 .unwrap();
792
793 let result: Result<TestData, DataEnvelopeError> =
795 envelope.unseal(crate::traits::tests::TestSymmKey::A(0), &mut ctx);
796 assert!(matches!(result, Err(DataEnvelopeError::InvalidNamespace)));
797 }
798
799 #[test]
800 fn test_registration_open_org_invite_namespace_maps_to_expected_discriminant() {
801 assert_eq!(
804 DataEnvelopeNamespace::try_from(3i128).unwrap(),
805 DataEnvelopeNamespace::RegistrationOpenOrgInviteData
806 );
807 assert_eq!(
808 i128::from(DataEnvelopeNamespace::RegistrationOpenOrgInviteData),
809 3
810 );
811 }
812
813 #[test]
814 fn test_registration_open_org_invite_data_rejects_cross_namespace_unseal() {
815 let data: TestData = TestDataV1 { field: 42 }.into();
820
821 let cek = Aes256GcmKey::make();
822 let envelope = DataEnvelope::seal_ref(
823 &data,
824 DataEnvelopeNamespace::RegistrationOpenOrgInviteData,
825 &cek,
826 )
827 .unwrap();
828
829 assert!(matches!(
830 unseal_with_cek::<TestData>(&envelope, DataEnvelopeNamespace::VaultItem, &cek),
831 Err(DataEnvelopeError::InvalidNamespace)
832 ));
833
834 let round_tripped: TestData = unseal_with_cek(
836 &envelope,
837 DataEnvelopeNamespace::RegistrationOpenOrgInviteData,
838 &cek,
839 )
840 .unwrap();
841 assert_eq!(round_tripped, data);
842 }
843
844 #[test]
845 fn test_namespace_cross_contamination_protection() {
846 let data1: TestData = TestDataV1 { field: 111 }.into();
847 let data2: TestData = TestDataV1 { field: 222 }.into();
848
849 let cek1 = Aes256GcmKey::make();
851 let envelope1 =
852 DataEnvelope::seal_ref(&data1, DataEnvelopeNamespace::ExampleNamespace, &cek1).unwrap();
853 let cek2 = Aes256GcmKey::make();
854 let envelope2 =
855 DataEnvelope::seal_ref(&data2, DataEnvelopeNamespace::ExampleNamespace2, &cek2)
856 .unwrap();
857
858 let unsealed1: TestData =
860 unseal_with_cek(&envelope1, DataEnvelopeNamespace::ExampleNamespace, &cek1).unwrap();
861 assert_eq!(unsealed1, data1);
862
863 let unsealed2: TestData =
864 unseal_with_cek(&envelope2, DataEnvelopeNamespace::ExampleNamespace2, &cek2).unwrap();
865 assert_eq!(unsealed2, data2);
866
867 assert!(matches!(
869 unseal_with_cek::<TestData>(
870 &envelope1,
871 DataEnvelopeNamespace::ExampleNamespace2,
872 &cek1
873 ),
874 Err(DataEnvelopeError::InvalidNamespace)
875 ));
876 assert!(matches!(
877 unseal_with_cek::<TestData>(&envelope2, DataEnvelopeNamespace::ExampleNamespace, &cek2),
878 Err(DataEnvelopeError::InvalidNamespace)
879 ));
880 }
881}