Skip to main content

bitwarden_crypto/safe/
symmetric_key_envelope.rs

1//! Wrapped symmetric key envelope for sealing a symmetric key with another symmetric key.
2
3use std::str::FromStr;
4
5use bitwarden_encoding::{B64, FromStrVisitor};
6use coset::{CborSerializable, CoseEncrypt0Builder, HeaderBuilder};
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9#[cfg(feature = "wasm")]
10use wasm_bindgen::convert::FromWasmAbi;
11
12use crate::{
13    ContentFormat, EncodedSymmetricKey, KeySlotIds, KeyStoreContext, SymmetricCryptoKey,
14    XAes256GcmKey,
15    cose::{
16        ContentNamespace, SafeObjectNamespace,
17        symmetric::{
18            CoseAlgorithmPolicy, CoseContentEncryptionAlgorithm, decrypt_cose0, encrypt_cose0,
19        },
20    },
21    keys::KeyId,
22    safe::{
23        DecodeSealedKeyError, KeyEncryptionKey, decode_sealed_symmetric_key, extract_key_id,
24        helpers::{debug_fmt, set_safe_namespaces, validate_safe_namespaces},
25        set_contained_key_id,
26    },
27};
28
29/// Errors that can occur when sealing or unsealing a symmetric key with envelope operations.
30#[derive(Debug, Error)]
31pub enum SymmetricKeyEnvelopeError {
32    /// The wrapping key provided is incorrect or the envelope was tampered with
33    #[error("Wrong key")]
34    WrongKey,
35    /// The envelope could not be parsed correctly
36    #[error("Parsing error {0}")]
37    Parsing(String),
38    /// There is no key for the provided key id in the key store
39    #[error("Key missing error")]
40    KeyMissing,
41    /// The key store could not be written to, for example due to being read-only
42    #[error("Could not write to key store")]
43    KeyStore,
44    /// A wrapping or contained key has an unsupported type
45    #[error("Wrong key type")]
46    WrongKeyType,
47    /// The symmetric key envelope namespace is invalid.
48    #[error("Invalid namespace")]
49    InvalidNamespace,
50}
51
52/// A symmetric key protected by an XAES-256-GCM wrapping key.
53#[derive(Clone)]
54pub struct SymmetricKeyEnvelope {
55    cose_encrypt0: coset::CoseEncrypt0,
56}
57
58impl SymmetricKeyEnvelope {
59    /// Seals a symmetric key with an XAES-256-GCM key from the key store.
60    pub fn seal<Ids: KeySlotIds>(
61        key_to_seal: Ids::Symmetric,
62        sealing_key: Ids::Symmetric,
63        namespace: SymmetricKeyEnvelopeNamespace,
64        ctx: &KeyStoreContext<Ids>,
65    ) -> Result<Self, SymmetricKeyEnvelopeError> {
66        if !KeyEncryptionKey::is_key_algorithm_valid(ctx, sealing_key) {
67            return Err(SymmetricKeyEnvelopeError::WrongKeyType);
68        }
69
70        // Get the keys from the key store.
71        let key_to_seal = ctx
72            .get_symmetric_key(key_to_seal)
73            .map_err(|_| SymmetricKeyEnvelopeError::KeyMissing)?;
74        let wrapping_key = ctx
75            .get_symmetric_key(sealing_key)
76            .map_err(|_| SymmetricKeyEnvelopeError::KeyMissing)?;
77
78        let wrapping_key: &XAes256GcmKey = match wrapping_key {
79            SymmetricCryptoKey::XAes256GcmKey(key) => key,
80            _ => return Err(SymmetricKeyEnvelopeError::WrongKeyType),
81        };
82
83        let (content_format, key_bytes) = match key_to_seal.to_encoded_raw() {
84            EncodedSymmetricKey::BitwardenLegacyKey(key_bytes) => {
85                (ContentFormat::BitwardenLegacyKey, key_bytes.to_vec())
86            }
87            EncodedSymmetricKey::CoseKey(key_bytes) => (ContentFormat::CoseKey, key_bytes.to_vec()),
88        };
89
90        let mut protected_header = HeaderBuilder::from(content_format).build();
91
92        set_contained_key_id(&mut protected_header, key_to_seal.key_id());
93
94        set_safe_namespaces(
95            &mut protected_header,
96            SafeObjectNamespace::SymmetricKeyEnvelope,
97            namespace,
98        );
99        protected_header.key_id = wrapping_key.key_id.as_slice().into();
100
101        let cose_encrypt0 = encrypt_cose0(
102            CoseContentEncryptionAlgorithm::XAes256Gcm,
103            CoseEncrypt0Builder::new(),
104            protected_header,
105            &key_bytes,
106            wrapping_key.enc_key.as_slice(),
107        )
108        .map_err(|_| SymmetricKeyEnvelopeError::WrongKeyType)?;
109
110        Ok(SymmetricKeyEnvelope { cose_encrypt0 })
111    }
112
113    /// Unseals a symmetric key with an XAES-256-GCM key and stores it in the key store context.
114    pub fn unseal<Ids: KeySlotIds>(
115        &self,
116        wrapping_key: Ids::Symmetric,
117        namespace: SymmetricKeyEnvelopeNamespace,
118        ctx: &mut KeyStoreContext<Ids>,
119    ) -> Result<Ids::Symmetric, SymmetricKeyEnvelopeError> {
120        let wrapping_key_ref = ctx
121            .get_symmetric_key(wrapping_key)
122            .map_err(|_| SymmetricKeyEnvelopeError::KeyMissing)?;
123
124        let wrapping_key_inner = match wrapping_key_ref {
125            SymmetricCryptoKey::XAes256GcmKey(key) => key,
126            _ => return Err(SymmetricKeyEnvelopeError::WrongKeyType),
127        };
128
129        validate_safe_namespaces(
130            &self.cose_encrypt0.protected.header,
131            SafeObjectNamespace::SymmetricKeyEnvelope,
132            namespace,
133        )
134        .map_err(|_| SymmetricKeyEnvelopeError::InvalidNamespace)?;
135
136        // The wrapping key is independently typed as XAES-256-GCM, so require the protected
137        // content-encryption algorithm to match it before attempting decryption.
138        let key_bytes = decrypt_cose0(
139            &self.cose_encrypt0,
140            CoseAlgorithmPolicy::Exactly(CoseContentEncryptionAlgorithm::XAes256Gcm),
141            wrapping_key_inner.enc_key.as_slice(),
142        )
143        .map_err(|_| SymmetricKeyEnvelopeError::WrongKey)?;
144
145        let key = decode_sealed_symmetric_key(&self.cose_encrypt0.protected.header, key_bytes)
146            .map_err(|e| match e {
147                DecodeSealedKeyError::InvalidContentFormat => {
148                    SymmetricKeyEnvelopeError::Parsing("Invalid content format".to_string())
149                }
150                DecodeSealedKeyError::UnsupportedContentFormat
151                | DecodeSealedKeyError::InvalidKey => SymmetricKeyEnvelopeError::WrongKeyType,
152            })?;
153
154        Ok(ctx.add_local_symmetric_key(key))
155    }
156
157    /// Get the key ID of the contained key.
158    pub fn contained_key_id(&self) -> Result<Option<KeyId>, SymmetricKeyEnvelopeError> {
159        extract_key_id(&self.cose_encrypt0.protected.header)
160            .map_err(|_| SymmetricKeyEnvelopeError::Parsing("Invalid contained key id".to_string()))
161    }
162}
163
164impl From<&SymmetricKeyEnvelope> for Vec<u8> {
165    fn from(val: &SymmetricKeyEnvelope) -> Self {
166        val.cose_encrypt0
167            .clone()
168            .to_vec()
169            .expect("Serialization to cose should not fail")
170    }
171}
172
173impl TryFrom<&Vec<u8>> for SymmetricKeyEnvelope {
174    type Error = coset::CoseError;
175
176    fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
177        let cose_encrypt0 = coset::CoseEncrypt0::from_slice(value)?;
178        Ok(SymmetricKeyEnvelope { cose_encrypt0 })
179    }
180}
181
182impl std::fmt::Debug for SymmetricKeyEnvelope {
183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        let mut s = f.debug_struct("SymmetricKeyEnvelope");
185
186        if !self.cose_encrypt0.protected.header.key_id.is_empty() {
187            s.field(
188                "sealing_key_id",
189                &self.cose_encrypt0.protected.header.key_id,
190            );
191        }
192
193        debug_fmt::<SymmetricKeyEnvelopeNamespace>(&mut s, &self.cose_encrypt0.protected.header);
194
195        if let Ok(Some(key_id)) = self.contained_key_id() {
196            s.field("contained_key_id", &key_id);
197        }
198
199        s.finish()
200    }
201}
202
203impl FromStr for SymmetricKeyEnvelope {
204    type Err = SymmetricKeyEnvelopeError;
205
206    fn from_str(s: &str) -> Result<Self, Self::Err> {
207        let data = B64::try_from(s).map_err(|_| {
208            SymmetricKeyEnvelopeError::Parsing(
209                "Invalid WrappedSymmetricKey Base64 encoding".to_string(),
210            )
211        })?;
212        Self::try_from(&data.into_bytes()).map_err(|_| {
213            SymmetricKeyEnvelopeError::Parsing("Failed to parse SymmetricKeyEnvelope".to_string())
214        })
215    }
216}
217
218impl From<SymmetricKeyEnvelope> for String {
219    fn from(val: SymmetricKeyEnvelope) -> Self {
220        let serialized: Vec<u8> = (&val).into();
221        B64::from(serialized).to_string()
222    }
223}
224
225impl<'de> Deserialize<'de> for SymmetricKeyEnvelope {
226    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
227    where
228        D: serde::Deserializer<'de>,
229    {
230        deserializer.deserialize_str(FromStrVisitor::new())
231    }
232}
233
234impl Serialize for SymmetricKeyEnvelope {
235    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
236    where
237        S: serde::Serializer,
238    {
239        let serialized: Vec<u8> = self.into();
240        serializer.serialize_str(&B64::from(serialized).to_string())
241    }
242}
243
244#[cfg(feature = "wasm")]
245#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
246const TS_CUSTOM_TYPES: &'static str = r#"
247export type SymmetricKeyEnvelope = Tagged<string, "SymmetricKeyEnvelope">;
248"#;
249
250#[cfg(feature = "wasm")]
251impl wasm_bindgen::describe::WasmDescribe for SymmetricKeyEnvelope {
252    fn describe() {
253        <String as wasm_bindgen::describe::WasmDescribe>::describe();
254    }
255}
256
257#[cfg(feature = "wasm")]
258impl FromWasmAbi for SymmetricKeyEnvelope {
259    type Abi = <String as FromWasmAbi>::Abi;
260
261    unsafe fn from_abi(abi: Self::Abi) -> Self {
262        use wasm_bindgen::UnwrapThrowExt;
263        let string = unsafe { String::from_abi(abi) };
264        SymmetricKeyEnvelope::from_str(&string).unwrap_throw()
265    }
266}
267
268/// Content namespace for the symmetric key envelope
269#[allow(clippy::enum_variant_names)]
270#[derive(Debug, Copy, Clone, PartialEq)]
271pub enum SymmetricKeyEnvelopeNamespace {
272    /// A key used for re-hydration of the SDK
273    SessionKey = 1,
274    /// Organization member invites. Used to seal the invite-data content-encryption key and the
275    /// organization key with the invite key.
276    OrganizationInvite = 2,
277    #[cfg(test)]
278    /// Example namespace for testing purposes.
279    ExampleNamespace = -3,
280    #[cfg(test)]
281    /// Another example namespace for testing purposes.
282    ExampleNamespace2 = -4,
283}
284
285impl SymmetricKeyEnvelopeNamespace {
286    /// Returns the numeric value of the namespace.
287    pub fn as_i64(&self) -> i64 {
288        *self as i64
289    }
290}
291
292impl TryFrom<i128> for SymmetricKeyEnvelopeNamespace {
293    type Error = SymmetricKeyEnvelopeError;
294
295    fn try_from(value: i128) -> Result<Self, Self::Error> {
296        match value {
297            1 => Ok(SymmetricKeyEnvelopeNamespace::SessionKey),
298            2 => Ok(SymmetricKeyEnvelopeNamespace::OrganizationInvite),
299            #[cfg(test)]
300            -3 => Ok(SymmetricKeyEnvelopeNamespace::ExampleNamespace),
301            #[cfg(test)]
302            -4 => Ok(SymmetricKeyEnvelopeNamespace::ExampleNamespace2),
303            _ => Err(SymmetricKeyEnvelopeError::InvalidNamespace),
304        }
305    }
306}
307
308impl TryFrom<i64> for SymmetricKeyEnvelopeNamespace {
309    type Error = SymmetricKeyEnvelopeError;
310
311    fn try_from(value: i64) -> Result<Self, Self::Error> {
312        Self::try_from(i128::from(value))
313    }
314}
315
316impl From<SymmetricKeyEnvelopeNamespace> for i128 {
317    fn from(value: SymmetricKeyEnvelopeNamespace) -> Self {
318        value.as_i64().into()
319    }
320}
321
322impl ContentNamespace for SymmetricKeyEnvelopeNamespace {}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327    use crate::{KeyStore, SymmetricKeyAlgorithm, traits::tests::TestIds};
328
329    const TEST_VECTOR_SEALING_KEY: &str = "pQEEAlAJiRm3TVKQVUpm9gqA4tm6AzoAARF5BIQDBAUGIFggQyO5bN7Uto3hpXUyqluuArn+zppBmhdnahDRJ6p4s84B";
330    const TEST_VECTOR_KEY_TO_SEAL: &str = "pQEEAlDpQoswNPD5xaz7sYLHZXXXAzoAARFvBIQDBAUGIFgg2YO7eUZhb9WSxxsGvdURTunDOBV0W4FRk9E4TV7c00QB";
331    const TEST_VECTOR_ENVELOPE: &str = "g1g+pgE6AAEReQMYZQRQCYkZt01SkFVKZvYKgOLZujoAARVcUOlCizA08PnFrPuxgsdlddc6AAE4gQM6AAE4gCKhBVgYLLX2iYq+Ko+2Hp2fByDfGd5AC0Lw4+zxWFS9n8fBbBKHfiarETj6Q3uRwZYpDS5p29MBEJW7WxOaf5Az51+Qtr8ugnoocUJhb8l6vmkDxFy1blU6iHuoBvpd+3MSFOeCdiuMLbKrf469fmUlDn0=";
332
333    #[test]
334    #[ignore = "Manual test to verify debug format"]
335    fn test_debug() {
336        let key_store = KeyStore::<TestIds>::default();
337        let mut ctx = key_store.context_mut();
338        let key1 = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
339        let key2 = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
340
341        let envelope = SymmetricKeyEnvelope::seal(
342            key1,
343            key2,
344            SymmetricKeyEnvelopeNamespace::ExampleNamespace,
345            &ctx,
346        );
347        println!("{:?}", envelope);
348    }
349
350    #[test]
351    fn test_seal_unseal_symmetric() {
352        let key_store = KeyStore::<TestIds>::default();
353        let mut ctx = key_store.context_mut();
354
355        let key_to_seal = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
356        let wrapping_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
357
358        let envelope = SymmetricKeyEnvelope::seal(
359            key_to_seal,
360            wrapping_key,
361            SymmetricKeyEnvelopeNamespace::ExampleNamespace,
362            &ctx,
363        )
364        .unwrap();
365
366        assert_eq!(
367            envelope.cose_encrypt0.protected.header.alg,
368            Some(coset::Algorithm::PrivateUse(crate::cose::XAES_256_GCM))
369        );
370
371        let unsealed_key = envelope
372            .unseal(
373                wrapping_key,
374                SymmetricKeyEnvelopeNamespace::ExampleNamespace,
375                &mut ctx,
376            )
377            .unwrap();
378
379        let unsealed_key_ref = ctx
380            .get_symmetric_key(unsealed_key)
381            .expect("Key should exist in the key store");
382
383        let original_key_ref = ctx
384            .get_symmetric_key(key_to_seal)
385            .expect("Key should exist in the key store");
386
387        assert_eq!(unsealed_key_ref, original_key_ref);
388    }
389
390    #[test]
391    fn test_contained_key_id_symmetric() {
392        let key_store = KeyStore::<TestIds>::default();
393        let mut ctx = key_store.context_mut();
394
395        let key_to_seal = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
396        let wrapping_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
397
398        let envelope = SymmetricKeyEnvelope::seal(
399            key_to_seal,
400            wrapping_key,
401            SymmetricKeyEnvelopeNamespace::ExampleNamespace,
402            &ctx,
403        )
404        .unwrap();
405
406        let key_to_seal_ref = ctx
407            .get_symmetric_key(key_to_seal)
408            .expect("Key should exist in the key store");
409
410        let contained_key_id = envelope.contained_key_id().unwrap();
411
412        assert_eq!(key_to_seal_ref.key_id(), contained_key_id);
413    }
414
415    #[test]
416    fn test_string_serialization() {
417        let key_store = KeyStore::<TestIds>::default();
418        let mut ctx = key_store.context_mut();
419
420        let key_to_seal = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
421        let wrapping_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
422
423        let envelope = SymmetricKeyEnvelope::seal(
424            key_to_seal,
425            wrapping_key,
426            SymmetricKeyEnvelopeNamespace::ExampleNamespace,
427            &ctx,
428        )
429        .unwrap();
430
431        let serialized: String = envelope.into();
432        let deserialized = SymmetricKeyEnvelope::from_str(&serialized).unwrap();
433
434        let unsealed_key = deserialized
435            .unseal(
436                wrapping_key,
437                SymmetricKeyEnvelopeNamespace::ExampleNamespace,
438                &mut ctx,
439            )
440            .unwrap();
441
442        let unsealed_key_ref = ctx
443            .get_symmetric_key(unsealed_key)
444            .expect("Key should exist in the key store");
445
446        let original_key_ref = ctx
447            .get_symmetric_key(key_to_seal)
448            .expect("Key should exist in the key store");
449
450        assert_eq!(unsealed_key_ref, original_key_ref);
451    }
452
453    #[test]
454    fn test_wrong_key() {
455        let key_store = KeyStore::<TestIds>::default();
456        let mut ctx = key_store.context_mut();
457
458        let key_to_seal = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
459        let wrapping_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
460        let wrong_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
461
462        let envelope = SymmetricKeyEnvelope::seal(
463            key_to_seal,
464            wrapping_key,
465            SymmetricKeyEnvelopeNamespace::ExampleNamespace,
466            &ctx,
467        )
468        .unwrap();
469
470        assert!(matches!(
471            envelope.unseal(
472                wrong_key,
473                SymmetricKeyEnvelopeNamespace::ExampleNamespace,
474                &mut ctx
475            ),
476            Err(SymmetricKeyEnvelopeError::WrongKey)
477        ));
478    }
479
480    #[test]
481    fn test_rejects_non_xaes_wrapping_keys() {
482        let key_store = KeyStore::<TestIds>::default();
483        let mut ctx = key_store.context_mut();
484
485        let key_to_seal = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
486        let wrapping_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
487        let envelope = SymmetricKeyEnvelope::seal(
488            key_to_seal,
489            wrapping_key,
490            SymmetricKeyEnvelopeNamespace::ExampleNamespace,
491            &ctx,
492        )
493        .unwrap();
494
495        let unsupported_wrapping_keys = [
496            ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac),
497            ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256Gcm),
498            ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305),
499        ];
500
501        for unsupported_wrapping_key in unsupported_wrapping_keys {
502            assert!(matches!(
503                SymmetricKeyEnvelope::seal(
504                    key_to_seal,
505                    unsupported_wrapping_key,
506                    SymmetricKeyEnvelopeNamespace::ExampleNamespace,
507                    &ctx,
508                ),
509                Err(SymmetricKeyEnvelopeError::WrongKeyType)
510            ));
511            assert!(matches!(
512                envelope.unseal(
513                    unsupported_wrapping_key,
514                    SymmetricKeyEnvelopeNamespace::ExampleNamespace,
515                    &mut ctx,
516                ),
517                Err(SymmetricKeyEnvelopeError::WrongKeyType)
518            ));
519        }
520    }
521
522    #[test]
523    fn test_wrong_namespace() {
524        let key_store = KeyStore::<TestIds>::default();
525        let mut ctx = key_store.context_mut();
526
527        let key_to_seal = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
528        let wrapping_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
529
530        let envelope = SymmetricKeyEnvelope::seal(
531            key_to_seal,
532            wrapping_key,
533            SymmetricKeyEnvelopeNamespace::ExampleNamespace,
534            &ctx,
535        )
536        .unwrap();
537
538        assert!(matches!(
539            envelope.unseal(
540                wrapping_key,
541                SymmetricKeyEnvelopeNamespace::ExampleNamespace2,
542                &mut ctx
543            ),
544            Err(SymmetricKeyEnvelopeError::InvalidNamespace)
545        ));
546    }
547
548    #[test]
549    #[ignore]
550    fn generate_test_vectors() {
551        let key_store = KeyStore::<TestIds>::default();
552        let mut ctx = key_store.context_mut();
553
554        let key_to_seal = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
555        let wrapping_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
556
557        let envelope = SymmetricKeyEnvelope::seal(
558            key_to_seal,
559            wrapping_key,
560            SymmetricKeyEnvelopeNamespace::ExampleNamespace,
561            &ctx,
562        )
563        .unwrap();
564
565        println!(
566            "const TEST_VECTOR_SEALING_KEY: &str = \"{}\";",
567            bitwarden_encoding::B64::from(
568                ctx.get_symmetric_key(wrapping_key)
569                    .unwrap()
570                    .to_encoded()
571                    .to_vec()
572                    .as_slice()
573            )
574        );
575        println!(
576            "const TEST_VECTOR_KEY_TO_SEAL: &str = \"{}\";",
577            bitwarden_encoding::B64::from(
578                ctx.get_symmetric_key(key_to_seal)
579                    .unwrap()
580                    .to_encoded()
581                    .to_vec()
582                    .as_slice()
583            )
584        );
585        let serialized: String = envelope.into();
586        println!("const TEST_VECTOR_ENVELOPE: &str = \"{}\";", serialized);
587    }
588
589    #[test]
590    fn decrypt_test_vectors() {
591        let key_store = KeyStore::<TestIds>::default();
592        let mut ctx = key_store.context_mut();
593
594        let sealing_key = SymmetricCryptoKey::try_from(TEST_VECTOR_SEALING_KEY.to_string())
595            .expect("Failed to parse sealing key from test vector");
596        let sealed_key_test_vector =
597            SymmetricCryptoKey::try_from(TEST_VECTOR_KEY_TO_SEAL.to_string())
598                .expect("Failed to parse key to seal from test vector");
599
600        let sealing_key_id = ctx.add_local_symmetric_key(sealing_key);
601
602        let envelope = SymmetricKeyEnvelope::from_str(TEST_VECTOR_ENVELOPE).unwrap();
603
604        let unsealed_key_id = envelope
605            .unseal(
606                sealing_key_id,
607                SymmetricKeyEnvelopeNamespace::ExampleNamespace,
608                &mut ctx,
609            )
610            .unwrap();
611        let unsealed_key = ctx.get_symmetric_key(unsealed_key_id).unwrap();
612        assert_eq!(unsealed_key.to_owned(), sealed_key_test_vector.to_owned());
613    }
614}