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 = cek
185 .as_cose_key_view()
186 .ok_or(DataEnvelopeError::UnsupportedContentFormat)?;
187 self.unseal_ref(T::NAMESPACE, view)
188 }
189
190 pub fn unseal_with_wrapping_key<Ids: KeySlotIds, T>(
192 &self,
193 wrapping_key: &Ids::Symmetric,
194 wrapped_cek: &EncString,
195 ctx: &mut crate::store::KeyStoreContext<Ids>,
196 ) -> Result<T, DataEnvelopeError>
197 where
198 T: DeserializeOwned + SealableVersionedData,
199 {
200 let cek = ctx
201 .unwrap_symmetric_key(*wrapping_key, wrapped_cek)
202 .map_err(|_| DataEnvelopeError::Decryption)?;
203 self.unseal(cek, ctx)
204 }
205
206 fn unseal_ref<T>(
209 &self,
210 namespace: DataEnvelopeNamespace,
211 cek: CoseKeyView,
212 ) -> Result<T, DataEnvelopeError>
213 where
214 T: DeserializeOwned + SealableVersionedData,
215 {
216 let msg = coset::CoseEncrypt0::from_slice(self.envelope_data.as_ref())
218 .map_err(|_| DataEnvelopeError::CoseDecoding)?;
219 let content_format =
220 content_format(&msg.protected).map_err(|_| DataEnvelopeError::Decoding)?;
221
222 if msg.protected.header.key_id != cek.key_id().as_slice() {
224 return Err(DataEnvelopeError::WrongKey);
225 }
226
227 validate_safe_namespaces(
228 &msg.protected.header,
229 SafeObjectNamespace::DataEnvelope,
230 namespace,
231 )
232 .map_err(|_| DataEnvelopeError::InvalidNamespace)?;
233
234 if content_format != CONTENT_TYPE_PADDED_CBOR {
235 return Err(DataEnvelopeError::UnsupportedContentFormat);
236 }
237
238 let decrypted_message = decrypt_cose0(
241 &msg,
242 CoseAlgorithmPolicy::Exactly(cek.algorithm()),
243 cek.key_bytes(),
244 )
245 .map_err(|_| DataEnvelopeError::Decryption)?;
246
247 let unpadded_message =
248 unpad_cbor(&decrypted_message).map_err(|_| DataEnvelopeError::Decryption)?;
249
250 let serialized_message =
252 SerializedMessage::from_bytes(unpadded_message, CoapContentFormat::Cbor);
253 serialized_message
254 .decode()
255 .map_err(|_| DataEnvelopeError::Decoding)
256 }
257}
258
259pub(super) fn content_format(protected_header: &ProtectedHeader) -> Result<String, EncodingError> {
263 protected_header
264 .header
265 .content_type
266 .as_ref()
267 .and_then(|ct| match ct {
268 RegisteredLabel::Text(content_format) => Some(content_format.clone()),
269 _ => None,
270 })
271 .ok_or(EncodingError::InvalidCoseEncoding)
272}
273
274impl From<&DataEnvelope> for Vec<u8> {
275 fn from(val: &DataEnvelope) -> Self {
276 val.envelope_data.to_vec()
277 }
278}
279
280impl From<Vec<u8>> for DataEnvelope {
281 fn from(data: Vec<u8>) -> Self {
282 DataEnvelope {
283 envelope_data: CoseEncrypt0Bytes::from(data),
284 }
285 }
286}
287
288impl std::fmt::Debug for DataEnvelope {
289 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290 let mut s = f.debug_struct("DataEnvelope");
291 if let Ok(msg) = coset::CoseEncrypt0::from_slice(self.envelope_data.as_ref()) {
292 debug_fmt::<DataEnvelopeNamespace>(&mut s, &msg.protected.header);
293 if let Ok(encrypted_by) = KeyId::try_from(msg.protected.header.key_id.as_slice()) {
294 s.field("encrypted_by", &encrypted_by);
295 }
296 }
297 s.finish()
298 }
299}
300
301impl FromStr for DataEnvelope {
302 type Err = NotB64EncodedError;
303
304 fn from_str(s: &str) -> Result<Self, Self::Err> {
305 let data = B64::try_from(s)?;
306 Ok(Self::from(data.into_bytes()))
307 }
308}
309
310impl From<DataEnvelope> for String {
311 fn from(val: DataEnvelope) -> Self {
312 let serialized: Vec<u8> = (&val).into();
313 B64::from(serialized).to_string()
314 }
315}
316
317impl<'de> Deserialize<'de> for DataEnvelope {
318 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
319 where
320 D: serde::Deserializer<'de>,
321 {
322 deserializer.deserialize_str(FromStrVisitor::new())
323 }
324}
325
326impl Serialize for DataEnvelope {
327 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
328 where
329 S: serde::Serializer,
330 {
331 let serialized: Vec<u8> = self.into();
332 serializer.serialize_str(&B64::from(serialized).to_string())
333 }
334}
335
336impl std::fmt::Display for DataEnvelope {
337 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
338 let serialized: Vec<u8> = self.into();
339 write!(f, "{}", B64::from(serialized))
340 }
341}
342
343#[derive(Debug, Error)]
345pub enum DataEnvelopeError {
346 #[error("Unsupported content format")]
348 UnsupportedContentFormat,
349 #[error("Failed to decode COSE message")]
351 CoseDecoding,
352 #[error("Failed to decode the content of the envelope")]
354 Decoding,
355 #[error("Encoding error")]
357 Encoding,
358 #[error("KeyStore error")]
360 KeyStore,
361 #[error("Decryption error")]
363 Decryption,
364 #[error("Encryption error")]
366 Encryption,
367 #[error("Parsing error: {0}")]
369 Parsing(String),
370 #[error("Invalid namespace")]
372 InvalidNamespace,
373 #[error("Wrong key used for decryption")]
375 WrongKey,
376}
377
378#[cfg(feature = "wasm")]
379#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
380const TS_CUSTOM_TYPES: &'static str = r#"
381export type DataEnvelope = Tagged<string, "DataEnvelope">;
382"#;
383
384#[cfg(feature = "wasm")]
385impl wasm_bindgen::describe::WasmDescribe for DataEnvelope {
386 fn describe() {
387 <String as wasm_bindgen::describe::WasmDescribe>::describe();
388 }
389}
390
391#[cfg(feature = "wasm")]
392impl FromWasmAbi for DataEnvelope {
393 type Abi = <String as FromWasmAbi>::Abi;
394
395 unsafe fn from_abi(abi: Self::Abi) -> Self {
396 use wasm_bindgen::UnwrapThrowExt;
397
398 let s = unsafe { String::from_abi(abi) };
399 Self::from_str(&s).unwrap_throw()
400 }
401}
402
403fn pad_cbor(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
404 let mut data = data.to_vec();
405 pad_bytes(&mut data, DATA_ENVELOPE_PADDING_SIZE).map_err(|_| CryptoError::InvalidPadding)?;
406 Ok(data)
407}
408
409fn unpad_cbor(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
410 let unpadded = crate::utils::unpad_bytes(data).map_err(|_| CryptoError::InvalidPadding)?;
411 Ok(unpadded.to_vec())
412}
413
414#[macro_export]
449macro_rules! generate_versioned_sealable {
450 (
451 $enum_name:ident,
453 $namespace:path,
455 [ $( $variant_ty:ident => $rename:literal ),+ $(,)? ]
457 ) => {
458 #[derive(Serialize, Deserialize, Debug, PartialEq)]
460 #[serde(tag = "version", content = "content")]
461 enum $enum_name {
462 $(
463 #[serde(rename = $rename)]
464 $variant_ty($variant_ty),
466 )+
467 }
468
469 impl SealableVersionedData for $enum_name
471 where
472 $( $variant_ty: SealableData ),+
473 {
474 const NAMESPACE: DataEnvelopeNamespace = $namespace;
476 }
477
478 $(
480 impl From<$variant_ty> for $enum_name {
481 fn from(value: $variant_ty) -> Self {
482 Self::$variant_ty(value)
483 }
484 }
485 )+
486 };
487}
488
489#[derive(Debug, Clone, Copy, PartialEq, Eq)]
496pub enum DataEnvelopeNamespace {
497 VaultItem = 1,
499 OrganizationInvite = 2,
502 RegistrationOpenOrgInviteData = 3,
506 #[cfg(test)]
508 ExampleNamespace = -1,
509 #[cfg(test)]
511 ExampleNamespace2 = -2,
512}
513
514impl DataEnvelopeNamespace {
515 fn as_i64(&self) -> i64 {
517 *self as i64
518 }
519}
520
521impl TryFrom<i128> for DataEnvelopeNamespace {
522 type Error = DataEnvelopeError;
523
524 fn try_from(value: i128) -> Result<Self, Self::Error> {
525 match value {
526 1 => Ok(DataEnvelopeNamespace::VaultItem),
527 2 => Ok(DataEnvelopeNamespace::OrganizationInvite),
528 3 => Ok(DataEnvelopeNamespace::RegistrationOpenOrgInviteData),
529 #[cfg(test)]
530 -1 => Ok(DataEnvelopeNamespace::ExampleNamespace),
531 #[cfg(test)]
532 -2 => Ok(DataEnvelopeNamespace::ExampleNamespace2),
533 _ => Err(DataEnvelopeError::InvalidNamespace),
534 }
535 }
536}
537
538impl TryFrom<i64> for DataEnvelopeNamespace {
539 type Error = DataEnvelopeError;
540
541 fn try_from(value: i64) -> Result<Self, Self::Error> {
542 Self::try_from(i128::from(value))
543 }
544}
545
546impl From<DataEnvelopeNamespace> for i128 {
547 fn from(val: DataEnvelopeNamespace) -> Self {
548 val.as_i64().into()
549 }
550}
551
552impl ContentNamespace for DataEnvelopeNamespace {}
553
554#[cfg(test)]
555mod tests {
556 use serde::Deserialize;
557
558 use super::*;
559 use crate::{SymmetricKeyAlgorithm, safe::KeyEncryptionKey, traits::tests::TestIds};
560
561 #[derive(Serialize, Deserialize, Debug, PartialEq)]
562 struct TestDataV1 {
563 field: u32,
564 }
565 impl SealableData for TestDataV1 {}
566
567 generate_versioned_sealable!(
568 TestData,
569 DataEnvelopeNamespace::ExampleNamespace,
570 [
571 TestDataV1 => "1",
572 ]
573 );
574
575 const TEST_VECTOR_CEK: &str =
578 "pQEEAlB5RTKA0xXdA7C4iQE4QfVUAzoAARFvBIEEIFggQYqnsrAfeFFTaXGXB54YrksB6eQcctMpnaZ8rG6rMJ0B";
579 const TEST_VECTOR_ENVELOPE: &str = "g1hLpQE6AAERbwN4I2FwcGxpY2F0aW9uL3guYml0d2FyZGVuLmNib3ItcGFkZGVkBFB5RTKA0xXdA7C4iQE4QfVUOgABOIECOgABOIAgoQVYGLfQrYHVWxRxO6A8m/yp5DPbBIn3h8nijlhQj4jFwDLWfFz7le1Oy8dTls5vdEFg/FjjsPvXicI2bdb5KDdJCz/YkEu0kqjpQwdCcALpJLVJwgQQeKIeU2klBHEPZjnlLpRRXeCUp5c5BYQ=";
580
581 const TEST_VECTOR_AES_GCM_CEK: &str =
584 "pQEEAlDLIx+izSLk9h9sVjHFzpKoAwMEgQQgWCDWM3iwTX2/LHTIaXS0cPIKCYFZethtKyD6Pucdt4fkGQQEBAQ=";
585 const TEST_VECTOR_AES_GCM_ENVELOPE: &str = "g1hHpQEDA3gjYXBwbGljYXRpb24veC5iaXR3YXJkZW4uY2Jvci1wYWRkZWQEUMsjH6LNIuT2H2xWMcXOkqg6AAE4gQI6AAE4gCChBUyoF/oGEm+lJYrjjgdYUGIH5LnQjqFMWo2BJORVPYH2+hEWkxIn3tRgAMHNwIr0nTXMVD1EyVGZOsHSDMPqn2HaYrDeR5s+Rg0ezZ2WLh8n2FbdC44A/ExOms4IHcyT";
586
587 fn unseal_with_cek<T>(
589 envelope: &DataEnvelope,
590 namespace: DataEnvelopeNamespace,
591 cek: &Aes256GcmKey,
592 ) -> Result<T, DataEnvelopeError>
593 where
594 T: serde::de::DeserializeOwned + SealableVersionedData,
595 {
596 envelope.unseal_ref(namespace, CoseKeyView::Aes256Gcm(cek))
597 }
598
599 #[test]
600 #[ignore = "Manual test to verify debug format"]
601 fn test_debug() {
602 let data: TestData = TestDataV1 { field: 42 }.into();
603 let envelope = DataEnvelope::seal_ref(
604 &data,
605 DataEnvelopeNamespace::ExampleNamespace,
606 &Aes256GcmKey::make(),
607 )
608 .unwrap();
609 println!("{:?}", envelope);
610 }
611
612 #[test]
613 #[ignore]
614 fn generate_aes_gcm_test_vectors() {
615 let data: TestData = TestDataV1 { field: 123 }.into();
616 let cek = Aes256GcmKey::make();
617 let envelope =
618 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace, &cek).unwrap();
619 let unsealed_data: TestData =
620 unseal_with_cek(&envelope, DataEnvelopeNamespace::ExampleNamespace, &cek).unwrap();
621 assert_eq!(unsealed_data, data);
622 println!(
623 "const TEST_VECTOR_AES_GCM_CEK: &str = \"{}\";",
624 B64::from(SymmetricCryptoKey::Aes256GcmKey(cek).to_encoded())
625 );
626 println!(
627 "const TEST_VECTOR_AES_GCM_ENVELOPE: &str = \"{}\";",
628 String::from(envelope)
629 );
630 }
631
632 #[test]
633 fn test_data_envelope_legacy_xchacha20_test_vector() {
634 let cek = SymmetricCryptoKey::try_from(B64::try_from(TEST_VECTOR_CEK).unwrap()).unwrap();
635 let SymmetricCryptoKey::XChaCha20Poly1305Key(ref cek) = cek else {
636 panic!("Invalid CEK type");
637 };
638
639 let envelope: DataEnvelope = TEST_VECTOR_ENVELOPE.parse().unwrap();
640 let unsealed_data: TestData = envelope
641 .unseal_ref(
642 DataEnvelopeNamespace::ExampleNamespace,
643 CoseKeyView::XChaCha20Poly1305(cek),
644 )
645 .unwrap();
646 assert_eq!(unsealed_data, TestDataV1 { field: 123 }.into());
647 }
648
649 #[test]
650 fn test_data_envelope_aes_gcm_test_vector() {
651 let cek =
652 SymmetricCryptoKey::try_from(B64::try_from(TEST_VECTOR_AES_GCM_CEK).unwrap()).unwrap();
653 let SymmetricCryptoKey::Aes256GcmKey(ref cek) = cek else {
654 panic!("Invalid CEK type");
655 };
656
657 let envelope: DataEnvelope = TEST_VECTOR_AES_GCM_ENVELOPE.parse().unwrap();
658 let unsealed_data: TestData =
659 unseal_with_cek(&envelope, DataEnvelopeNamespace::ExampleNamespace, cek).unwrap();
660 assert_eq!(unsealed_data, TestDataV1 { field: 123 }.into());
661 }
662
663 #[test]
664 fn test_data_envelope_uses_aes_gcm() {
665 let data: TestData = TestDataV1 { field: 42 }.into();
666 let envelope = DataEnvelope::seal_ref(
667 &data,
668 DataEnvelopeNamespace::ExampleNamespace,
669 &Aes256GcmKey::make(),
670 )
671 .unwrap();
672
673 let msg = coset::CoseEncrypt0::from_slice(envelope.envelope_data.as_ref()).unwrap();
675 assert_eq!(
676 msg.protected.header.alg,
677 Some(coset::Algorithm::Assigned(coset::iana::Algorithm::A256GCM))
678 );
679 }
680
681 #[test]
682 fn test_data_envelope() {
683 let data: TestData = TestDataV1 { field: 42 }.into();
685
686 let cek = Aes256GcmKey::make();
688 let envelope =
689 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace, &cek).unwrap();
690 let unsealed_data: TestData =
691 unseal_with_cek(&envelope, DataEnvelopeNamespace::ExampleNamespace, &cek).unwrap();
692
693 assert_eq!(unsealed_data, data);
695 }
696
697 #[test]
698 fn test_data_envelope_with_keystore_roundtrip() {
699 let data: TestData = TestDataV1 { field: 7 }.into();
700 let key_store = crate::store::KeyStore::<TestIds>::default();
701 let mut ctx = key_store.context_mut();
702
703 let (envelope, cek_id) = DataEnvelope::seal(data, &mut ctx).unwrap();
704
705 assert_eq!(
707 ctx.get_symmetric_key_algorithm(cek_id).unwrap(),
708 SymmetricKeyAlgorithm::Aes256Gcm
709 );
710
711 let unsealed: TestData = envelope.unseal(cek_id, &mut ctx).unwrap();
712 assert_eq!(unsealed, TestDataV1 { field: 7 }.into());
713 }
714
715 #[test]
716 fn test_data_envelope_wrapping_key_roundtrip() {
717 let data: TestData = TestDataV1 { field: 99 }.into();
718 let key_store = crate::store::KeyStore::<TestIds>::default();
719 let mut ctx = key_store.context_mut();
720
721 let wrapping_key = KeyEncryptionKey::make(&mut ctx);
722
723 let (envelope, wrapped_cek) =
724 DataEnvelope::seal_with_wrapping_key(data, &wrapping_key, &mut ctx).unwrap();
725 let unsealed: TestData = envelope
726 .unseal_with_wrapping_key(&wrapping_key, &wrapped_cek, &mut ctx)
727 .unwrap();
728 assert_eq!(unsealed, TestDataV1 { field: 99 }.into());
729 }
730
731 #[test]
732 fn test_namespace_validation_success() {
733 let data: TestData = TestDataV1 { field: 123 }.into();
734
735 let cek1 = Aes256GcmKey::make();
737 let envelope1 =
738 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace, &cek1).unwrap();
739 let unsealed_data1: TestData =
740 unseal_with_cek(&envelope1, DataEnvelopeNamespace::ExampleNamespace, &cek1).unwrap();
741 assert_eq!(unsealed_data1, data);
742
743 let cek2 = Aes256GcmKey::make();
745 let envelope2 =
746 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace2, &cek2).unwrap();
747 let unsealed_data2: TestData =
748 unseal_with_cek(&envelope2, DataEnvelopeNamespace::ExampleNamespace2, &cek2).unwrap();
749 assert_eq!(unsealed_data2, data);
750 }
751
752 #[test]
753 fn test_namespace_validation_failure() {
754 let data: TestData = TestDataV1 { field: 456 }.into();
755
756 let cek = Aes256GcmKey::make();
758 let envelope =
759 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace, &cek).unwrap();
760
761 let result: Result<TestData, DataEnvelopeError> =
763 unseal_with_cek(&envelope, DataEnvelopeNamespace::ExampleNamespace2, &cek);
764 assert!(matches!(result, Err(DataEnvelopeError::InvalidNamespace)));
765
766 let unsealed_data: TestData =
768 unseal_with_cek(&envelope, DataEnvelopeNamespace::ExampleNamespace, &cek).unwrap();
769 assert_eq!(unsealed_data, data);
770 }
771
772 #[test]
773 fn test_namespace_validation_with_keystore() {
774 let data: TestData = TestDataV1 { field: 789 }.into();
775 let key_store = crate::store::KeyStore::<TestIds>::default();
776 let mut ctx = key_store.context_mut();
777
778 let cek = Aes256GcmKey::make();
780 let envelope =
781 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace2, &cek).unwrap();
782 ctx.set_symmetric_key_internal(
783 crate::traits::tests::TestSymmKey::A(0),
784 SymmetricCryptoKey::Aes256GcmKey(cek),
785 )
786 .unwrap();
787
788 let result: Result<TestData, DataEnvelopeError> =
790 envelope.unseal(crate::traits::tests::TestSymmKey::A(0), &mut ctx);
791 assert!(matches!(result, Err(DataEnvelopeError::InvalidNamespace)));
792 }
793
794 #[test]
795 fn test_registration_open_org_invite_namespace_maps_to_expected_discriminant() {
796 assert_eq!(
799 DataEnvelopeNamespace::try_from(3i128).unwrap(),
800 DataEnvelopeNamespace::RegistrationOpenOrgInviteData
801 );
802 assert_eq!(
803 i128::from(DataEnvelopeNamespace::RegistrationOpenOrgInviteData),
804 3
805 );
806 }
807
808 #[test]
809 fn test_registration_open_org_invite_data_rejects_cross_namespace_unseal() {
810 let data: TestData = TestDataV1 { field: 42 }.into();
815
816 let cek = Aes256GcmKey::make();
817 let envelope = DataEnvelope::seal_ref(
818 &data,
819 DataEnvelopeNamespace::RegistrationOpenOrgInviteData,
820 &cek,
821 )
822 .unwrap();
823
824 assert!(matches!(
825 unseal_with_cek::<TestData>(&envelope, DataEnvelopeNamespace::VaultItem, &cek),
826 Err(DataEnvelopeError::InvalidNamespace)
827 ));
828
829 let round_tripped: TestData = unseal_with_cek(
831 &envelope,
832 DataEnvelopeNamespace::RegistrationOpenOrgInviteData,
833 &cek,
834 )
835 .unwrap();
836 assert_eq!(round_tripped, data);
837 }
838
839 #[test]
840 fn test_namespace_cross_contamination_protection() {
841 let data1: TestData = TestDataV1 { field: 111 }.into();
842 let data2: TestData = TestDataV1 { field: 222 }.into();
843
844 let cek1 = Aes256GcmKey::make();
846 let envelope1 =
847 DataEnvelope::seal_ref(&data1, DataEnvelopeNamespace::ExampleNamespace, &cek1).unwrap();
848 let cek2 = Aes256GcmKey::make();
849 let envelope2 =
850 DataEnvelope::seal_ref(&data2, DataEnvelopeNamespace::ExampleNamespace2, &cek2)
851 .unwrap();
852
853 let unsealed1: TestData =
855 unseal_with_cek(&envelope1, DataEnvelopeNamespace::ExampleNamespace, &cek1).unwrap();
856 assert_eq!(unsealed1, data1);
857
858 let unsealed2: TestData =
859 unseal_with_cek(&envelope2, DataEnvelopeNamespace::ExampleNamespace2, &cek2).unwrap();
860 assert_eq!(unsealed2, data2);
861
862 assert!(matches!(
864 unseal_with_cek::<TestData>(
865 &envelope1,
866 DataEnvelopeNamespace::ExampleNamespace2,
867 &cek1
868 ),
869 Err(DataEnvelopeError::InvalidNamespace)
870 ));
871 assert!(matches!(
872 unseal_with_cek::<TestData>(&envelope2, DataEnvelopeNamespace::ExampleNamespace, &cek2),
873 Err(DataEnvelopeError::InvalidNamespace)
874 ));
875 }
876}