Skip to main content

bitwarden_crypto/safe/
data_envelope.rs

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    CONTENT_TYPE_PADDED_CBOR, CoseEncrypt0Bytes, CryptoError, EncString, EncodingError, KeyIds,
13    SerializedMessage, SymmetricCryptoKey, XChaCha20Poly1305Key,
14    cose::{ContentNamespace, SafeObjectNamespace, XCHACHA20_POLY1305},
15    safe::helpers::{debug_fmt, set_safe_namespaces, validate_safe_namespaces},
16    utils::pad_bytes,
17    xchacha20,
18};
19
20pub(crate) const DATA_ENVELOPE_PADDING_SIZE: usize = 64;
21
22/// Marker trait for data that can be sealed in a `DataEnvelope`.
23///
24/// Do not manually implement this! Use the generate_versioned_sealable! macro instead.
25pub trait SealableVersionedData: Serialize + DeserializeOwned {
26    /// The namespace to use when sealing this type of data. This must be unique per struct.
27    const NAMESPACE: DataEnvelopeNamespace;
28}
29
30/// Marker trait for data that can be sealed in a `DataEnvelope`.
31///
32/// Note: If you implement this trait, you agree to the following:
33/// The struct serialization format is stable. Struct modifications must maintain backward
34/// compatibility with existing serialized data. Changes that break deserialization are considered
35/// breaking changes and require a new version and struct.
36///
37/// Ideally, when creating a new struct, create a test vector (a sealed DataEnvelope for a test
38/// value), and create a unit test ensuring that it permanently deserializes correctly.
39///
40/// To make breaking changes, introduce a new version. This should use the
41/// `generate_versioned_sealable!` macro to auto-generate the versioning code. Please see the
42/// examples directory.
43pub trait SealableData: Serialize + DeserializeOwned {}
44
45/// `DataEnvelope` allows sealing structs entire structs to encrypted blobs.
46///
47/// Sealing a struct results in an encrypted blob, and a content-encryption-key. The
48/// content-encryption-key must be provided again when unsealing the data. A content encryption key
49/// allows easy key-rotation of the encrypting-key, as now just the content-encryption-keys need to
50/// be re-uploaded, instead of all data.
51///
52/// The content-encryption-key cannot be re-used for encrypting other data.
53///
54/// Note: This is explicitly meant for structured data, not large binary blobs (files).
55#[derive(Clone)]
56pub struct DataEnvelope {
57    envelope_data: CoseEncrypt0Bytes,
58}
59
60impl DataEnvelope {
61    /// Seals a struct into an encrypted blob, and stores the content-encryption-key in the provided
62    /// context.
63    pub fn seal<Ids: KeyIds, T>(
64        data: T,
65        ctx: &mut crate::store::KeyStoreContext<Ids>,
66    ) -> Result<(Self, Ids::Symmetric), DataEnvelopeError>
67    where
68        T: Serialize + SealableVersionedData,
69    {
70        let (envelope, cek) = Self::seal_ref(&data, T::NAMESPACE)?;
71        let cek_id = ctx.generate_symmetric_key();
72        ctx.set_symmetric_key_internal(cek_id, SymmetricCryptoKey::XChaCha20Poly1305Key(cek))
73            .map_err(|_| DataEnvelopeError::KeyStoreError)?;
74        Ok((envelope, cek_id))
75    }
76
77    /// Seals a struct into an encrypted blob. The content encryption key is wrapped with the
78    /// provided wrapping key
79    pub fn seal_with_wrapping_key<Ids: KeyIds, T>(
80        data: T,
81        wrapping_key: &Ids::Symmetric,
82        ctx: &mut crate::store::KeyStoreContext<Ids>,
83    ) -> Result<(Self, EncString), DataEnvelopeError>
84    where
85        T: Serialize + SealableVersionedData,
86    {
87        let (envelope, cek) = Self::seal(data, ctx)?;
88
89        let wrapped_cek = ctx
90            .wrap_symmetric_key(*wrapping_key, cek)
91            .map_err(|_| DataEnvelopeError::EncryptionError)?;
92
93        Ok((envelope, wrapped_cek))
94    }
95
96    /// Seals a struct into an encrypted blob, and returns the encrypted blob and the
97    /// content-encryption-key.
98    fn seal_ref<T>(
99        data: &T,
100        namespace: DataEnvelopeNamespace,
101    ) -> Result<(DataEnvelope, XChaCha20Poly1305Key), DataEnvelopeError>
102    where
103        T: Serialize + SealableVersionedData,
104    {
105        let mut cek = XChaCha20Poly1305Key::make();
106
107        // Serialize the message
108        let serialized_message =
109            SerializedMessage::encode(&data).map_err(|_| DataEnvelopeError::EncodingError)?;
110        if serialized_message.content_type() != coset::iana::CoapContentFormat::Cbor {
111            return Err(DataEnvelopeError::UnsupportedContentFormat);
112        }
113
114        let serialized_and_padded_message = pad_cbor(serialized_message.as_bytes())
115            .map_err(|_| DataEnvelopeError::EncodingError)?;
116
117        // Build the COSE headers
118        let mut protected_header = coset::HeaderBuilder::new()
119            .key_id(cek.key_id.as_slice().to_vec())
120            .content_type(CONTENT_TYPE_PADDED_CBOR.to_string())
121            .build();
122        set_safe_namespaces(
123            &mut protected_header,
124            SafeObjectNamespace::DataEnvelope,
125            namespace,
126        );
127        protected_header.alg = Some(coset::Algorithm::PrivateUse(XCHACHA20_POLY1305));
128
129        // Encrypt the message
130        let mut nonce = [0u8; xchacha20::NONCE_SIZE];
131        let encrypt0 = coset::CoseEncrypt0Builder::new()
132            .protected(protected_header)
133            .create_ciphertext(&serialized_and_padded_message, &[], |data, aad| {
134                let ciphertext =
135                    crate::xchacha20::encrypt_xchacha20_poly1305(&(*cek.enc_key).into(), data, aad);
136                nonce = ciphertext.nonce();
137                ciphertext.encrypted_bytes().to_vec()
138            })
139            .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build())
140            .build();
141
142        // Serialize the COSE message
143        let envelope_data = encrypt0
144            .to_vec()
145            .map(CoseEncrypt0Bytes::from)
146            .map_err(|_| DataEnvelopeError::EncodingError)?;
147
148        // Disable key operations other than decrypt on the CEK
149        cek.disable_key_operation(coset::iana::KeyOperation::Encrypt)
150            .disable_key_operation(coset::iana::KeyOperation::WrapKey)
151            .disable_key_operation(coset::iana::KeyOperation::UnwrapKey);
152
153        Ok((DataEnvelope { envelope_data }, cek))
154    }
155
156    /// Unseals the data from the encrypted blob using a content-encryption-key stored in the
157    /// context.
158    pub fn unseal<Ids: KeyIds, T>(
159        &self,
160        cek_keyslot: Ids::Symmetric,
161        ctx: &mut crate::store::KeyStoreContext<Ids>,
162    ) -> Result<T, DataEnvelopeError>
163    where
164        T: DeserializeOwned + SealableVersionedData,
165    {
166        let cek = ctx
167            .get_symmetric_key(cek_keyslot)
168            .map_err(|_| DataEnvelopeError::KeyStoreError)?;
169
170        match cek {
171            SymmetricCryptoKey::XChaCha20Poly1305Key(key) => self.unseal_ref(T::NAMESPACE, key),
172            _ => Err(DataEnvelopeError::UnsupportedContentFormat),
173        }
174    }
175
176    /// Unseals the data from the encrypted blob and wrapped content-encryption-key.
177    pub fn unseal_with_wrapping_key<Ids: KeyIds, T>(
178        &self,
179        wrapping_key: &Ids::Symmetric,
180        wrapped_cek: &EncString,
181        ctx: &mut crate::store::KeyStoreContext<Ids>,
182    ) -> Result<T, DataEnvelopeError>
183    where
184        T: DeserializeOwned + SealableVersionedData,
185    {
186        let cek = ctx
187            .unwrap_symmetric_key(*wrapping_key, wrapped_cek)
188            .map_err(|_| DataEnvelopeError::DecryptionError)?;
189        self.unseal(cek, ctx)
190    }
191
192    /// Unseals the data from the encrypted blob using the provided content-encryption-key.
193    fn unseal_ref<T>(
194        &self,
195        namespace: DataEnvelopeNamespace,
196        cek: &XChaCha20Poly1305Key,
197    ) -> Result<T, DataEnvelopeError>
198    where
199        T: DeserializeOwned + SealableVersionedData,
200    {
201        // Parse the COSE message
202        let msg = coset::CoseEncrypt0::from_slice(self.envelope_data.as_ref())
203            .map_err(|_| DataEnvelopeError::CoseDecodingError)?;
204        let content_format =
205            content_format(&msg.protected).map_err(|_| DataEnvelopeError::DecodingError)?;
206
207        // Validate the message
208        if !matches!(
209            msg.protected.header.alg,
210            Some(coset::Algorithm::PrivateUse(XCHACHA20_POLY1305)),
211        ) {
212            return Err(DataEnvelopeError::DecryptionError);
213        }
214        if msg.protected.header.key_id != cek.key_id.as_slice() {
215            return Err(DataEnvelopeError::WrongKey);
216        }
217
218        validate_safe_namespaces(
219            &msg.protected.header,
220            SafeObjectNamespace::DataEnvelope,
221            namespace,
222        )
223        .map_err(|_| DataEnvelopeError::InvalidNamespace)?;
224
225        if content_format != CONTENT_TYPE_PADDED_CBOR {
226            return Err(DataEnvelopeError::UnsupportedContentFormat);
227        }
228
229        // Decrypt the message
230        let decrypted_message = msg
231            .decrypt_ciphertext(
232                &[],
233                || CryptoError::MissingField("ciphertext"),
234                |data, aad| {
235                    let nonce = msg.unprotected.iv.as_slice();
236                    crate::xchacha20::decrypt_xchacha20_poly1305(
237                        nonce
238                            .try_into()
239                            .map_err(|_| CryptoError::InvalidNonceLength)?,
240                        &(*cek.enc_key).into(),
241                        data,
242                        aad,
243                    )
244                },
245            )
246            .map_err(|_| DataEnvelopeError::DecryptionError)?;
247
248        let unpadded_message =
249            unpad_cbor(&decrypted_message).map_err(|_| DataEnvelopeError::DecryptionError)?;
250
251        // Deserialize the message
252        let serialized_message =
253            SerializedMessage::from_bytes(unpadded_message, CoapContentFormat::Cbor);
254        serialized_message
255            .decode()
256            .map_err(|_| DataEnvelopeError::DecodingError)
257    }
258}
259
260/// Helper function to extract the content type from a `ProtectedHeader`. The content type is a
261/// standardized header set on the protected headers of the signature object. Currently we only
262/// support registered values, but PrivateUse values are also allowed in the COSE specification.
263pub(super) fn content_format(protected_header: &ProtectedHeader) -> Result<String, EncodingError> {
264    protected_header
265        .header
266        .content_type
267        .as_ref()
268        .and_then(|ct| match ct {
269            RegisteredLabel::Text(content_format) => Some(content_format.clone()),
270            _ => None,
271        })
272        .ok_or(EncodingError::InvalidCoseEncoding)
273}
274
275impl From<&DataEnvelope> for Vec<u8> {
276    fn from(val: &DataEnvelope) -> Self {
277        val.envelope_data.to_vec()
278    }
279}
280
281impl From<Vec<u8>> for DataEnvelope {
282    fn from(data: Vec<u8>) -> Self {
283        DataEnvelope {
284            envelope_data: CoseEncrypt0Bytes::from(data),
285        }
286    }
287}
288
289impl std::fmt::Debug for DataEnvelope {
290    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291        let mut s = f.debug_struct("DataEnvelope");
292        if let Ok(msg) = coset::CoseEncrypt0::from_slice(self.envelope_data.as_ref()) {
293            debug_fmt::<DataEnvelopeNamespace>(&mut s, &msg.protected.header);
294        }
295        s.finish()
296    }
297}
298
299impl FromStr for DataEnvelope {
300    type Err = NotB64EncodedError;
301
302    fn from_str(s: &str) -> Result<Self, Self::Err> {
303        let data = B64::try_from(s)?;
304        Ok(Self::from(data.into_bytes()))
305    }
306}
307
308impl From<DataEnvelope> for String {
309    fn from(val: DataEnvelope) -> Self {
310        let serialized: Vec<u8> = (&val).into();
311        B64::from(serialized).to_string()
312    }
313}
314
315impl<'de> Deserialize<'de> for DataEnvelope {
316    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
317    where
318        D: serde::Deserializer<'de>,
319    {
320        deserializer.deserialize_str(FromStrVisitor::new())
321    }
322}
323
324impl Serialize for DataEnvelope {
325    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
326    where
327        S: serde::Serializer,
328    {
329        let serialized: Vec<u8> = self.into();
330        serializer.serialize_str(&B64::from(serialized).to_string())
331    }
332}
333
334impl std::fmt::Display for DataEnvelope {
335    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
336        let serialized: Vec<u8> = self.into();
337        write!(f, "{}", B64::from(serialized))
338    }
339}
340
341/// Error type for `DataEnvelope` operations.
342#[derive(Debug, Error)]
343pub enum DataEnvelopeError {
344    /// Indicates that the content format is not supported.
345    #[error("Unsupported content format")]
346    UnsupportedContentFormat,
347    /// Indicates that there was an error during decoding of the message.
348    #[error("Failed to decode COSE message")]
349    CoseDecodingError,
350    /// Indicates that there was an error during decoding of the message.
351    #[error("Failed to decode the content of the envelope")]
352    DecodingError,
353    /// Indicates that there was an error during encoding of the message.
354    #[error("Encoding error")]
355    EncodingError,
356    /// Indicates that there was an error with the key store.
357    #[error("KeyStore error")]
358    KeyStoreError,
359    /// Indicates that there was an error during decryption.
360    #[error("Decryption error")]
361    DecryptionError,
362    /// Indicates that there was an error during encryption.
363    #[error("Encryption error")]
364    EncryptionError,
365    /// Indicates that there was an error parsing the DataEnvelope.
366    #[error("Parsing error: {0}")]
367    ParsingError(String),
368    /// Indicates that the data envelope namespace is invalid.
369    #[error("Invalid namespace")]
370    InvalidNamespace,
371    /// Indicates that the wrong key was used for decryption.
372    #[error("Wrong key used for decryption")]
373    WrongKey,
374}
375
376#[cfg(feature = "wasm")]
377#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
378const TS_CUSTOM_TYPES: &'static str = r#"
379export type DataEnvelope = Tagged<string, "DataEnvelope">;
380"#;
381
382#[cfg(feature = "wasm")]
383impl wasm_bindgen::describe::WasmDescribe for DataEnvelope {
384    fn describe() {
385        <String as wasm_bindgen::describe::WasmDescribe>::describe();
386    }
387}
388
389#[cfg(feature = "wasm")]
390impl FromWasmAbi for DataEnvelope {
391    type Abi = <String as FromWasmAbi>::Abi;
392
393    unsafe fn from_abi(abi: Self::Abi) -> Self {
394        use wasm_bindgen::UnwrapThrowExt;
395
396        let s = unsafe { String::from_abi(abi) };
397        Self::from_str(&s).unwrap_throw()
398    }
399}
400
401fn pad_cbor(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
402    let mut data = data.to_vec();
403    pad_bytes(&mut data, DATA_ENVELOPE_PADDING_SIZE).map_err(|_| CryptoError::InvalidPadding)?;
404    Ok(data)
405}
406
407fn unpad_cbor(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
408    let unpadded = crate::utils::unpad_bytes(data).map_err(|_| CryptoError::InvalidPadding)?;
409    Ok(unpadded.to_vec())
410}
411
412/// Generates a versioned enum that implements `SealableData`.
413///
414/// This serializes to an adjacently tagged enum, with the "version" field being set to the provided
415/// version, and the "content" field being the serialized struct.
416///
417///
418/// ```
419/// use bitwarden_crypto::{safe::{DataEnvelopeNamespace, SealableData, SealableVersionedData}, generate_versioned_sealable};
420/// use serde::{Deserialize, Serialize};
421///
422/// #[derive(Serialize, Deserialize, PartialEq, Debug)]
423/// struct MyItemV1 {
424///     a: u32,
425///     b: String,
426/// }
427/// impl SealableData for MyItemV1 {}
428///
429/// #[derive(Serialize, Deserialize, PartialEq, Debug)]
430/// struct MyItemV2 {
431///     a: u32,
432///     b: bool,
433///     c: bool,
434/// }
435/// impl SealableData for MyItemV2 {}
436///
437/// generate_versioned_sealable!(
438///     MyItem,
439///     DataEnvelopeNamespace::VaultItem,
440///     [
441///         MyItemV1 => "1",
442///         MyItemV2 => "2",
443///     ]
444/// );
445/// ```
446#[macro_export]
447macro_rules! generate_versioned_sealable {
448    (
449        // Provide the name
450        $enum_name:ident,
451        // Provide the namespace
452        $namespace:path,
453        // Provide mappings from the variant to version. This must not be changed later.
454        [ $( $variant_ty:ident => $rename:literal ),+ $(,)? ]
455    ) => {
456        // Implement the enum
457        #[derive(Serialize, Deserialize, Debug, PartialEq)]
458        #[serde(tag = "version", content = "content")]
459        enum $enum_name {
460            $(
461                #[serde(rename = $rename)]
462                // Strip the `MyItem` prefix from type name if you want shorter variant names
463                $variant_ty($variant_ty),
464            )+
465        }
466
467        // Implement the SealableVersionedData trait for the enum
468        impl SealableVersionedData for $enum_name
469        where
470            $( $variant_ty: SealableData ),+
471        {
472            // Implement with the specified namespace
473            const NAMESPACE: DataEnvelopeNamespace = $namespace;
474        }
475
476        // Implement Into from each variant to the enum
477        $(
478            impl From<$variant_ty> for $enum_name {
479                fn from(value: $variant_ty) -> Self {
480                    Self::$variant_ty(value)
481                }
482            }
483        )+
484    };
485}
486
487/// Data envelopes are domain-separated within bitwarden, to prevent cross protocol attacks.
488///
489/// A new struct shall use a new data envelope namespace. Generally, this means
490/// that a data envelope namespace has exactly one associated valid message struct. Internal
491/// versioning within a namespace is permitted and up to the domain owner to ensure is done
492/// correctly.
493#[derive(Debug, Clone, Copy, PartialEq, Eq)]
494pub enum DataEnvelopeNamespace {
495    /// The namespace for vault items ("ciphers")
496    VaultItem = 1,
497    /// This namespace is only used in tests
498    #[cfg(test)]
499    ExampleNamespace = -1,
500    /// This namespace is only used in tests
501    #[cfg(test)]
502    ExampleNamespace2 = -2,
503}
504
505impl DataEnvelopeNamespace {
506    /// Returns the numeric value of the namespace.
507    fn as_i64(&self) -> i64 {
508        *self as i64
509    }
510}
511
512impl TryFrom<i128> for DataEnvelopeNamespace {
513    type Error = DataEnvelopeError;
514
515    fn try_from(value: i128) -> Result<Self, Self::Error> {
516        match value {
517            1 => Ok(DataEnvelopeNamespace::VaultItem),
518            #[cfg(test)]
519            -1 => Ok(DataEnvelopeNamespace::ExampleNamespace),
520            #[cfg(test)]
521            -2 => Ok(DataEnvelopeNamespace::ExampleNamespace2),
522            _ => Err(DataEnvelopeError::InvalidNamespace),
523        }
524    }
525}
526
527impl TryFrom<i64> for DataEnvelopeNamespace {
528    type Error = DataEnvelopeError;
529
530    fn try_from(value: i64) -> Result<Self, Self::Error> {
531        Self::try_from(i128::from(value))
532    }
533}
534
535impl From<DataEnvelopeNamespace> for i128 {
536    fn from(val: DataEnvelopeNamespace) -> Self {
537        val.as_i64().into()
538    }
539}
540
541impl ContentNamespace for DataEnvelopeNamespace {}
542
543#[cfg(test)]
544mod tests {
545    use serde::Deserialize;
546
547    use super::*;
548    use crate::traits::tests::TestIds;
549
550    #[derive(Serialize, Deserialize, Debug, PartialEq)]
551    struct TestDataV1 {
552        field: u32,
553    }
554    impl SealableData for TestDataV1 {}
555
556    generate_versioned_sealable!(
557        TestData,
558        DataEnvelopeNamespace::ExampleNamespace,
559        [
560            TestDataV1 => "1",
561        ]
562    );
563
564    const TEST_VECTOR_CEK: &str =
565        "pQEEAlB5RTKA0xXdA7C4iQE4QfVUAzoAARFvBIEEIFggQYqnsrAfeFFTaXGXB54YrksB6eQcctMpnaZ8rG6rMJ0B";
566    const TEST_VECTOR_ENVELOPE: &str = "g1hLpQE6AAERbwN4I2FwcGxpY2F0aW9uL3guYml0d2FyZGVuLmNib3ItcGFkZGVkBFB5RTKA0xXdA7C4iQE4QfVUOgABOIECOgABOIAgoQVYGLfQrYHVWxRxO6A8m/yp5DPbBIn3h8nijlhQj4jFwDLWfFz7le1Oy8dTls5vdEFg/FjjsPvXicI2bdb5KDdJCz/YkEu0kqjpQwdCcALpJLVJwgQQeKIeU2klBHEPZjnlLpRRXeCUp5c5BYQ=";
567
568    #[test]
569    #[ignore = "Manual test to verify debug format"]
570    fn test_debug() {
571        let data: TestData = TestDataV1 { field: 42 }.into();
572        let (envelope, _cek) =
573            DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace).unwrap();
574        println!("{:?}", envelope);
575    }
576
577    #[test]
578    #[ignore]
579    fn generate_test_vectors() {
580        let data: TestData = TestDataV1 { field: 123 }.into();
581        let (envelope, cek) =
582            DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace).unwrap();
583        let unsealed_data: TestData = envelope
584            .unseal_ref(DataEnvelopeNamespace::ExampleNamespace, &cek)
585            .unwrap();
586        assert_eq!(unsealed_data, data);
587        println!(
588            "const TEST_VECTOR_CEK: &str = \"{}\";",
589            B64::from(SymmetricCryptoKey::XChaCha20Poly1305Key(cek).to_encoded())
590        );
591        println!(
592            "const TEST_VECTOR_ENVELOPE: &str = \"{}\";",
593            String::from(envelope)
594        );
595    }
596
597    #[test]
598    fn test_data_envelope_test_vector() {
599        let cek = SymmetricCryptoKey::try_from(B64::try_from(TEST_VECTOR_CEK).unwrap()).unwrap();
600        let cek = match cek {
601            SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) => key.clone(),
602            _ => panic!("Invalid CEK type"),
603        };
604
605        let envelope: DataEnvelope = TEST_VECTOR_ENVELOPE.parse().unwrap();
606        let unsealed_data: TestData = envelope
607            .unseal_ref(DataEnvelopeNamespace::ExampleNamespace, &cek)
608            .unwrap();
609        assert_eq!(unsealed_data, TestDataV1 { field: 123 }.into());
610    }
611
612    #[test]
613    fn test_data_envelope() {
614        // Create an instance of TestData
615        let data: TestData = TestDataV1 { field: 42 }.into();
616
617        // Seal the data
618        let (envelope, cek) =
619            DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace).unwrap();
620        let unsealed_data: TestData = envelope
621            .unseal_ref(DataEnvelopeNamespace::ExampleNamespace, &cek)
622            .unwrap();
623
624        // Verify that the unsealed data matches the original data
625        assert_eq!(unsealed_data, data);
626    }
627
628    #[test]
629    fn test_namespace_validation_success() {
630        let data: TestData = TestDataV1 { field: 123 }.into();
631
632        // Test with ExampleNamespace
633        let (envelope1, cek1) =
634            DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace).unwrap();
635        let unsealed_data1: TestData = envelope1
636            .unseal_ref(DataEnvelopeNamespace::ExampleNamespace, &cek1)
637            .unwrap();
638        assert_eq!(unsealed_data1, data);
639
640        // Test with ExampleNamespace2
641        let (envelope2, cek2) =
642            DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace2).unwrap();
643        let unsealed_data2: TestData = envelope2
644            .unseal_ref(DataEnvelopeNamespace::ExampleNamespace2, &cek2)
645            .unwrap();
646        assert_eq!(unsealed_data2, data);
647    }
648
649    #[test]
650    fn test_namespace_validation_failure() {
651        let data: TestData = TestDataV1 { field: 456 }.into();
652
653        // Seal with ExampleNamespace
654        let (envelope, cek) =
655            DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace).unwrap();
656
657        // Try to unseal with wrong namespace - should fail
658        let result: Result<TestData, DataEnvelopeError> =
659            envelope.unseal_ref(DataEnvelopeNamespace::ExampleNamespace2, &cek);
660        assert!(matches!(result, Err(DataEnvelopeError::InvalidNamespace)));
661
662        // Verify correct namespace still works
663        let unsealed_data: TestData = envelope
664            .unseal_ref(DataEnvelopeNamespace::ExampleNamespace, &cek)
665            .unwrap();
666        assert_eq!(unsealed_data, data);
667    }
668
669    #[test]
670    fn test_namespace_validation_with_keystore() {
671        let data: TestData = TestDataV1 { field: 789 }.into();
672        let key_store = crate::store::KeyStore::<TestIds>::default();
673        let mut ctx = key_store.context_mut();
674
675        // Seal with keystore using ExampleNamespace2
676        let (envelope, cek) =
677            DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace2).unwrap();
678        ctx.set_symmetric_key_internal(
679            crate::traits::tests::TestSymmKey::A(0),
680            SymmetricCryptoKey::XChaCha20Poly1305Key(cek),
681        )
682        .unwrap();
683
684        // Try to unseal with wrong namespace - should fail
685        let result: Result<TestData, DataEnvelopeError> =
686            envelope.unseal(crate::traits::tests::TestSymmKey::A(0), &mut ctx);
687        assert!(matches!(result, Err(DataEnvelopeError::InvalidNamespace)));
688    }
689
690    #[test]
691    fn test_namespace_cross_contamination_protection() {
692        let data1: TestData = TestDataV1 { field: 111 }.into();
693        let data2: TestData = TestDataV1 { field: 222 }.into();
694
695        // Seal two different pieces of data with different namespaces
696        let (envelope1, cek1) =
697            DataEnvelope::seal_ref(&data1, DataEnvelopeNamespace::ExampleNamespace).unwrap();
698        let (envelope2, cek2) =
699            DataEnvelope::seal_ref(&data2, DataEnvelopeNamespace::ExampleNamespace2).unwrap();
700
701        // Verify each envelope only opens with its correct namespace
702        let unsealed1: TestData = envelope1
703            .unseal_ref(DataEnvelopeNamespace::ExampleNamespace, &cek1)
704            .unwrap();
705        assert_eq!(unsealed1, data1);
706
707        let unsealed2: TestData = envelope2
708            .unseal_ref(DataEnvelopeNamespace::ExampleNamespace2, &cek2)
709            .unwrap();
710        assert_eq!(unsealed2, data2);
711
712        // Cross-unsealing should fail
713        assert!(matches!(
714            envelope1.unseal_ref::<TestData>(DataEnvelopeNamespace::ExampleNamespace2, &cek1),
715            Err(DataEnvelopeError::InvalidNamespace)
716        ));
717        assert!(matches!(
718            envelope2.unseal_ref::<TestData>(DataEnvelopeNamespace::ExampleNamespace, &cek2),
719            Err(DataEnvelopeError::InvalidNamespace)
720        ));
721    }
722}