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 PamTargetSystem = 4,
515 PamRotationConfig = 5,
518 #[cfg(test)]
520 ExampleNamespace = -1,
521 #[cfg(test)]
523 ExampleNamespace2 = -2,
524}
525
526impl DataEnvelopeNamespace {
527 fn as_i64(&self) -> i64 {
529 *self as i64
530 }
531}
532
533impl TryFrom<i128> for DataEnvelopeNamespace {
534 type Error = DataEnvelopeError;
535
536 fn try_from(value: i128) -> Result<Self, Self::Error> {
537 match value {
538 1 => Ok(DataEnvelopeNamespace::VaultItem),
539 2 => Ok(DataEnvelopeNamespace::OrganizationInvite),
540 3 => Ok(DataEnvelopeNamespace::RegistrationOpenOrgInviteData),
541 4 => Ok(DataEnvelopeNamespace::PamTargetSystem),
542 5 => Ok(DataEnvelopeNamespace::PamRotationConfig),
543 #[cfg(test)]
544 -1 => Ok(DataEnvelopeNamespace::ExampleNamespace),
545 #[cfg(test)]
546 -2 => Ok(DataEnvelopeNamespace::ExampleNamespace2),
547 _ => Err(DataEnvelopeError::InvalidNamespace),
548 }
549 }
550}
551
552impl TryFrom<i64> for DataEnvelopeNamespace {
553 type Error = DataEnvelopeError;
554
555 fn try_from(value: i64) -> Result<Self, Self::Error> {
556 Self::try_from(i128::from(value))
557 }
558}
559
560impl From<DataEnvelopeNamespace> for i128 {
561 fn from(val: DataEnvelopeNamespace) -> Self {
562 val.as_i64().into()
563 }
564}
565
566impl ContentNamespace for DataEnvelopeNamespace {}
567
568#[cfg(test)]
569mod tests {
570 use serde::Deserialize;
571
572 use super::*;
573 use crate::{SymmetricKeyAlgorithm, safe::KeyEncryptionKey, traits::tests::TestIds};
574
575 #[derive(Serialize, Deserialize, Debug, PartialEq)]
576 struct TestDataV1 {
577 field: u32,
578 }
579 impl SealableData for TestDataV1 {}
580
581 generate_versioned_sealable!(
582 TestData,
583 DataEnvelopeNamespace::ExampleNamespace,
584 [
585 TestDataV1 => "1",
586 ]
587 );
588
589 const TEST_VECTOR_CEK: &str =
592 "pQEEAlB5RTKA0xXdA7C4iQE4QfVUAzoAARFvBIEEIFggQYqnsrAfeFFTaXGXB54YrksB6eQcctMpnaZ8rG6rMJ0B";
593 const TEST_VECTOR_ENVELOPE: &str = "g1hLpQE6AAERbwN4I2FwcGxpY2F0aW9uL3guYml0d2FyZGVuLmNib3ItcGFkZGVkBFB5RTKA0xXdA7C4iQE4QfVUOgABOIECOgABOIAgoQVYGLfQrYHVWxRxO6A8m/yp5DPbBIn3h8nijlhQj4jFwDLWfFz7le1Oy8dTls5vdEFg/FjjsPvXicI2bdb5KDdJCz/YkEu0kqjpQwdCcALpJLVJwgQQeKIeU2klBHEPZjnlLpRRXeCUp5c5BYQ=";
594
595 const TEST_VECTOR_AES_GCM_CEK: &str =
598 "pQEEAlDLIx+izSLk9h9sVjHFzpKoAwMEgQQgWCDWM3iwTX2/LHTIaXS0cPIKCYFZethtKyD6Pucdt4fkGQQEBAQ=";
599 const TEST_VECTOR_AES_GCM_ENVELOPE: &str = "g1hHpQEDA3gjYXBwbGljYXRpb24veC5iaXR3YXJkZW4uY2Jvci1wYWRkZWQEUMsjH6LNIuT2H2xWMcXOkqg6AAE4gQI6AAE4gCChBUyoF/oGEm+lJYrjjgdYUGIH5LnQjqFMWo2BJORVPYH2+hEWkxIn3tRgAMHNwIr0nTXMVD1EyVGZOsHSDMPqn2HaYrDeR5s+Rg0ezZ2WLh8n2FbdC44A/ExOms4IHcyT";
600
601 fn unseal_with_cek<T>(
603 envelope: &DataEnvelope,
604 namespace: DataEnvelopeNamespace,
605 cek: &Aes256GcmKey,
606 ) -> Result<T, DataEnvelopeError>
607 where
608 T: serde::de::DeserializeOwned + SealableVersionedData,
609 {
610 envelope.unseal_ref(namespace, CoseKeyView::Aes256Gcm(cek))
611 }
612
613 #[test]
614 #[ignore = "Manual test to verify debug format"]
615 fn test_debug() {
616 let data: TestData = TestDataV1 { field: 42 }.into();
617 let envelope = DataEnvelope::seal_ref(
618 &data,
619 DataEnvelopeNamespace::ExampleNamespace,
620 &Aes256GcmKey::make(),
621 )
622 .unwrap();
623 println!("{:?}", envelope);
624 }
625
626 #[test]
627 #[ignore]
628 fn generate_aes_gcm_test_vectors() {
629 let data: TestData = TestDataV1 { field: 123 }.into();
630 let cek = Aes256GcmKey::make();
631 let envelope =
632 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace, &cek).unwrap();
633 let unsealed_data: TestData =
634 unseal_with_cek(&envelope, DataEnvelopeNamespace::ExampleNamespace, &cek).unwrap();
635 assert_eq!(unsealed_data, data);
636 println!(
637 "const TEST_VECTOR_AES_GCM_CEK: &str = \"{}\";",
638 B64::from(SymmetricCryptoKey::Aes256GcmKey(cek).to_encoded())
639 );
640 println!(
641 "const TEST_VECTOR_AES_GCM_ENVELOPE: &str = \"{}\";",
642 String::from(envelope)
643 );
644 }
645
646 #[test]
647 fn test_data_envelope_legacy_xchacha20_test_vector() {
648 let cek = SymmetricCryptoKey::try_from(B64::try_from(TEST_VECTOR_CEK).unwrap()).unwrap();
649 let SymmetricCryptoKey::XChaCha20Poly1305Key(ref cek) = cek else {
650 panic!("Invalid CEK type");
651 };
652
653 let envelope: DataEnvelope = TEST_VECTOR_ENVELOPE.parse().unwrap();
654 let unsealed_data: TestData = envelope
655 .unseal_ref(
656 DataEnvelopeNamespace::ExampleNamespace,
657 CoseKeyView::XChaCha20Poly1305(cek),
658 )
659 .unwrap();
660 assert_eq!(unsealed_data, TestDataV1 { field: 123 }.into());
661 }
662
663 #[test]
664 fn test_data_envelope_aes_gcm_test_vector() {
665 let cek =
666 SymmetricCryptoKey::try_from(B64::try_from(TEST_VECTOR_AES_GCM_CEK).unwrap()).unwrap();
667 let SymmetricCryptoKey::Aes256GcmKey(ref cek) = cek else {
668 panic!("Invalid CEK type");
669 };
670
671 let envelope: DataEnvelope = TEST_VECTOR_AES_GCM_ENVELOPE.parse().unwrap();
672 let unsealed_data: TestData =
673 unseal_with_cek(&envelope, DataEnvelopeNamespace::ExampleNamespace, cek).unwrap();
674 assert_eq!(unsealed_data, TestDataV1 { field: 123 }.into());
675 }
676
677 #[test]
678 fn test_data_envelope_uses_aes_gcm() {
679 let data: TestData = TestDataV1 { field: 42 }.into();
680 let envelope = DataEnvelope::seal_ref(
681 &data,
682 DataEnvelopeNamespace::ExampleNamespace,
683 &Aes256GcmKey::make(),
684 )
685 .unwrap();
686
687 let msg = coset::CoseEncrypt0::from_slice(envelope.envelope_data.as_ref()).unwrap();
689 assert_eq!(
690 msg.protected.header.alg,
691 Some(coset::Algorithm::Assigned(coset::iana::Algorithm::A256GCM))
692 );
693 }
694
695 #[test]
696 fn test_data_envelope() {
697 let data: TestData = TestDataV1 { field: 42 }.into();
699
700 let cek = Aes256GcmKey::make();
702 let envelope =
703 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace, &cek).unwrap();
704 let unsealed_data: TestData =
705 unseal_with_cek(&envelope, DataEnvelopeNamespace::ExampleNamespace, &cek).unwrap();
706
707 assert_eq!(unsealed_data, data);
709 }
710
711 #[test]
712 fn test_data_envelope_with_keystore_roundtrip() {
713 let data: TestData = TestDataV1 { field: 7 }.into();
714 let key_store = crate::store::KeyStore::<TestIds>::default();
715 let mut ctx = key_store.context_mut();
716
717 let (envelope, cek_id) = DataEnvelope::seal(data, &mut ctx).unwrap();
718
719 assert_eq!(
721 ctx.get_symmetric_key_algorithm(cek_id).unwrap(),
722 SymmetricKeyAlgorithm::Aes256Gcm
723 );
724
725 let unsealed: TestData = envelope.unseal(cek_id, &mut ctx).unwrap();
726 assert_eq!(unsealed, TestDataV1 { field: 7 }.into());
727 }
728
729 #[test]
730 fn test_data_envelope_wrapping_key_roundtrip() {
731 let data: TestData = TestDataV1 { field: 99 }.into();
732 let key_store = crate::store::KeyStore::<TestIds>::default();
733 let mut ctx = key_store.context_mut();
734
735 let wrapping_key = KeyEncryptionKey::make(&mut ctx);
736
737 let (envelope, wrapped_cek) =
738 DataEnvelope::seal_with_wrapping_key(data, &wrapping_key, &mut ctx).unwrap();
739 let unsealed: TestData = envelope
740 .unseal_with_wrapping_key(&wrapping_key, &wrapped_cek, &mut ctx)
741 .unwrap();
742 assert_eq!(unsealed, TestDataV1 { field: 99 }.into());
743 }
744
745 #[test]
746 fn test_namespace_validation_success() {
747 let data: TestData = TestDataV1 { field: 123 }.into();
748
749 let cek1 = Aes256GcmKey::make();
751 let envelope1 =
752 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace, &cek1).unwrap();
753 let unsealed_data1: TestData =
754 unseal_with_cek(&envelope1, DataEnvelopeNamespace::ExampleNamespace, &cek1).unwrap();
755 assert_eq!(unsealed_data1, data);
756
757 let cek2 = Aes256GcmKey::make();
759 let envelope2 =
760 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace2, &cek2).unwrap();
761 let unsealed_data2: TestData =
762 unseal_with_cek(&envelope2, DataEnvelopeNamespace::ExampleNamespace2, &cek2).unwrap();
763 assert_eq!(unsealed_data2, data);
764 }
765
766 #[test]
767 fn test_namespace_validation_failure() {
768 let data: TestData = TestDataV1 { field: 456 }.into();
769
770 let cek = Aes256GcmKey::make();
772 let envelope =
773 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace, &cek).unwrap();
774
775 let result: Result<TestData, DataEnvelopeError> =
777 unseal_with_cek(&envelope, DataEnvelopeNamespace::ExampleNamespace2, &cek);
778 assert!(matches!(result, Err(DataEnvelopeError::InvalidNamespace)));
779
780 let unsealed_data: TestData =
782 unseal_with_cek(&envelope, DataEnvelopeNamespace::ExampleNamespace, &cek).unwrap();
783 assert_eq!(unsealed_data, data);
784 }
785
786 #[test]
787 fn test_namespace_validation_with_keystore() {
788 let data: TestData = TestDataV1 { field: 789 }.into();
789 let key_store = crate::store::KeyStore::<TestIds>::default();
790 let mut ctx = key_store.context_mut();
791
792 let cek = Aes256GcmKey::make();
794 let envelope =
795 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace2, &cek).unwrap();
796 ctx.set_symmetric_key_internal(
797 crate::traits::tests::TestSymmKey::A(0),
798 SymmetricCryptoKey::Aes256GcmKey(cek),
799 )
800 .unwrap();
801
802 let result: Result<TestData, DataEnvelopeError> =
804 envelope.unseal(crate::traits::tests::TestSymmKey::A(0), &mut ctx);
805 assert!(matches!(result, Err(DataEnvelopeError::InvalidNamespace)));
806 }
807
808 #[test]
809 fn test_registration_open_org_invite_namespace_maps_to_expected_discriminant() {
810 assert_eq!(
813 DataEnvelopeNamespace::try_from(3i128).unwrap(),
814 DataEnvelopeNamespace::RegistrationOpenOrgInviteData
815 );
816 assert_eq!(
817 i128::from(DataEnvelopeNamespace::RegistrationOpenOrgInviteData),
818 3
819 );
820 }
821
822 #[test]
823 fn test_registration_open_org_invite_data_rejects_cross_namespace_unseal() {
824 let data: TestData = TestDataV1 { field: 42 }.into();
829
830 let cek = Aes256GcmKey::make();
831 let envelope = DataEnvelope::seal_ref(
832 &data,
833 DataEnvelopeNamespace::RegistrationOpenOrgInviteData,
834 &cek,
835 )
836 .unwrap();
837
838 assert!(matches!(
839 unseal_with_cek::<TestData>(&envelope, DataEnvelopeNamespace::VaultItem, &cek),
840 Err(DataEnvelopeError::InvalidNamespace)
841 ));
842
843 let round_tripped: TestData = unseal_with_cek(
845 &envelope,
846 DataEnvelopeNamespace::RegistrationOpenOrgInviteData,
847 &cek,
848 )
849 .unwrap();
850 assert_eq!(round_tripped, data);
851 }
852
853 #[test]
854 fn test_pam_namespaces_map_to_expected_discriminants() {
855 assert_eq!(
858 DataEnvelopeNamespace::try_from(4i128).unwrap(),
859 DataEnvelopeNamespace::PamTargetSystem
860 );
861 assert_eq!(i128::from(DataEnvelopeNamespace::PamTargetSystem), 4);
862
863 assert_eq!(
864 DataEnvelopeNamespace::try_from(5i128).unwrap(),
865 DataEnvelopeNamespace::PamRotationConfig
866 );
867 assert_eq!(i128::from(DataEnvelopeNamespace::PamRotationConfig), 5);
868 }
869
870 #[test]
871 fn test_pam_target_system_rejects_cross_namespace_unseal() {
872 let data: TestData = TestDataV1 { field: 42 }.into();
875
876 let cek = Aes256GcmKey::make();
877 let envelope =
878 DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::PamTargetSystem, &cek).unwrap();
879
880 assert!(matches!(
881 unseal_with_cek::<TestData>(&envelope, DataEnvelopeNamespace::PamRotationConfig, &cek),
882 Err(DataEnvelopeError::InvalidNamespace)
883 ));
884
885 let round_tripped: TestData =
886 unseal_with_cek(&envelope, DataEnvelopeNamespace::PamTargetSystem, &cek).unwrap();
887 assert_eq!(round_tripped, data);
888 }
889
890 #[test]
891 fn test_namespace_cross_contamination_protection() {
892 let data1: TestData = TestDataV1 { field: 111 }.into();
893 let data2: TestData = TestDataV1 { field: 222 }.into();
894
895 let cek1 = Aes256GcmKey::make();
897 let envelope1 =
898 DataEnvelope::seal_ref(&data1, DataEnvelopeNamespace::ExampleNamespace, &cek1).unwrap();
899 let cek2 = Aes256GcmKey::make();
900 let envelope2 =
901 DataEnvelope::seal_ref(&data2, DataEnvelopeNamespace::ExampleNamespace2, &cek2)
902 .unwrap();
903
904 let unsealed1: TestData =
906 unseal_with_cek(&envelope1, DataEnvelopeNamespace::ExampleNamespace, &cek1).unwrap();
907 assert_eq!(unsealed1, data1);
908
909 let unsealed2: TestData =
910 unseal_with_cek(&envelope2, DataEnvelopeNamespace::ExampleNamespace2, &cek2).unwrap();
911 assert_eq!(unsealed2, data2);
912
913 assert!(matches!(
915 unseal_with_cek::<TestData>(
916 &envelope1,
917 DataEnvelopeNamespace::ExampleNamespace2,
918 &cek1
919 ),
920 Err(DataEnvelopeError::InvalidNamespace)
921 ));
922 assert!(matches!(
923 unseal_with_cek::<TestData>(&envelope2, DataEnvelopeNamespace::ExampleNamespace, &cek2),
924 Err(DataEnvelopeError::InvalidNamespace)
925 ));
926 }
927}