Skip to main content

pg_core/client/rust/
mod.rs

1//! This module utilizes the symmetric primitives provided by [`Rust
2//! Crypto`](https://github.com/RustCrypto). The streaming interface, enabled using the feature
3//! `stream` is a small wrapper around [`aead::stream`]. This feature enables an interface
4//! to encrypt data using asynchronous byte streams, specifically from an
5//! [AsyncRead][`futures::io::AsyncRead`] into an [AsyncWrite][`futures::io::AsyncWrite`].
6
7use alloc::string::ToString;
8use alloc::vec::Vec;
9
10use crate::artifacts::{PublicKey, UserSecretKey, VerifyingKey};
11use crate::client::*;
12use crate::error::Error;
13use crate::identity::{EncryptionPolicy, Policy};
14
15use aead::{Aead, KeyInit};
16use aes_gcm::{Aes128Gcm, Nonce};
17use ibe::kem::cgw_kv::CGWKV;
18use ibs::gg::Signer;
19use rand::{CryptoRng, RngCore};
20
21#[cfg(feature = "stream")]
22pub mod stream;
23
24/// In-memory configuration for a [`Sealer`].
25#[derive(Debug)]
26pub struct SealerMemoryConfig {
27    key: [u8; KEY_SIZE],
28    nonce: [u8; IV_SIZE],
29}
30
31/// In-memory configuration for an [`Unsealer`].
32#[derive(Debug)]
33pub struct UnsealerMemoryConfig {
34    message_len: usize,
35}
36
37impl SealerConfig for SealerMemoryConfig {}
38impl super::sealed::SealerConfig for SealerMemoryConfig {}
39
40impl UnsealerConfig for UnsealerMemoryConfig {}
41impl super::sealed::UnsealerConfig for UnsealerMemoryConfig {}
42
43impl From<aead::Error> for Error {
44    fn from(_: aead::Error) -> Self {
45        Self::Symmetric
46    }
47}
48
49impl From<aes_gcm::aes::cipher::InvalidLength> for Error {
50    fn from(_: aes_gcm::aes::cipher::InvalidLength) -> Self {
51        Self::Symmetric
52    }
53}
54
55/// The AEAD plaintext as this version writes it.
56///
57/// `pub_pol` is a copy of the sender's public signing policy, the same value
58/// that goes into `h_sig_ext` outside the AEAD. Only the copy in here is
59/// covered by the AEAD, so a reader can tell that a party on the wire replaced
60/// the header signature block with one made by another signing key.
61///
62/// The copy is authenticated against the DEM key, not against the sender's
63/// signing key, so it covers a party on the wire and nobody who holds the DEM
64/// key themselves. Read it as a check against the wire, not as sender
65/// authentication. Binding the policy under something only the sender controls
66/// is a design change, not this check.
67#[derive(Debug, Serialize, Deserialize)]
68struct MessageAndSignature {
69    message: Vec<u8>,
70    sig: SignatureExt,
71    pub_pol: Policy,
72}
73
74/// The part of that plaintext every version writes.
75///
76/// bincode encodes fields positionally and ignores trailing bytes, so this
77/// decodes both a container sealed by this version and one sealed before
78/// `pub_pol` existed. The reader decodes this and inspects what follows
79/// itself, rather than decoding a shape an older sealer never wrote.
80#[derive(Debug, Serialize, Deserialize)]
81struct MessageAndSignaturePrefix {
82    message: Vec<u8>,
83    sig: SignatureExt,
84}
85
86impl<'r, R: RngCore + CryptoRng> Sealer<'r, R, SealerMemoryConfig> {
87    /// Create a new [`Sealer`].
88    pub fn new(
89        mpk: &PublicKey<CGWKV>,
90        policies: &EncryptionPolicy,
91        pub_sign_key: &SigningKeyExt,
92        rng: &'r mut R,
93    ) -> Result<Self, Error> {
94        let (header, ss) = Header::new(mpk, policies, rng)?;
95        let Algorithm::Aes128Gcm(iv) = header.algo;
96
97        let mut key = [0u8; KEY_SIZE];
98        let mut nonce = [0u8; IV_SIZE];
99        key.copy_from_slice(&ss.0[..KEY_SIZE]);
100        nonce.copy_from_slice(&iv.0[..IV_SIZE]);
101
102        Ok(Self {
103            rng,
104            header,
105            pub_sign_key: crate::client::canonical_signing_key(pub_sign_key),
106            priv_sign_key: None,
107            config: SealerMemoryConfig { key, nonce },
108        })
109    }
110
111    /// Seals the entire payload.
112    pub fn seal(mut self, message: impl AsRef<[u8]>) -> Result<Vec<u8>, Error> {
113        let mut out = Vec::with_capacity(message.as_ref().len() + 1024);
114
115        out.extend_from_slice(&PRELUDE);
116        out.extend_from_slice(&VERSION_2.to_be_bytes());
117
118        self.header = self.header.with_mode(Mode::InMemory {
119            size: message.as_ref().len().try_into()?,
120        });
121
122        let header_buf = crate::bincode_compat::serialize(&self.header)?;
123        out.extend_from_slice(&u32::try_from(header_buf.len())?.to_be_bytes());
124        out.extend_from_slice(&header_buf);
125
126        let signer = Signer::new().chain(header_buf);
127        let h_sig = signer.clone().sign(&self.pub_sign_key.key.0, self.rng);
128
129        let h_sig_ext = SignatureExt {
130            sig: h_sig,
131            pol: self.pub_sign_key.policy.clone(),
132        };
133
134        let h_sig_ext_bytes = crate::bincode_compat::serialize(&h_sig_ext)?;
135        out.extend_from_slice(&u32::try_from(h_sig_ext_bytes.len())?.to_be_bytes());
136        out.extend_from_slice(&h_sig_ext_bytes);
137
138        let pub_pol = self.pub_sign_key.policy.clone();
139        let m_sig_key = self.priv_sign_key.unwrap_or(self.pub_sign_key);
140        let m_sig = signer.chain(&message).sign(&m_sig_key.key.0, self.rng);
141
142        let aead = Aes128Gcm::new_from_slice(&self.config.key)?;
143        let nonce = Nonce::from(self.config.nonce);
144
145        let enc_input = crate::bincode_compat::serialize(&MessageAndSignature {
146            message: message.as_ref().to_vec(),
147            sig: SignatureExt {
148                sig: m_sig,
149                pol: m_sig_key.policy,
150            },
151            pub_pol,
152        })?;
153
154        let ciphertext = aead.encrypt(&nonce, enc_input.as_ref())?;
155
156        out.extend_from_slice(&ciphertext);
157
158        Ok(out)
159    }
160}
161
162impl Unsealer<Vec<u8>, UnsealerMemoryConfig> {
163    /// Create a new [`Unsealer`].
164    pub fn new(input: impl AsRef<[u8]>, vk: &VerifyingKey) -> Result<Self, Error> {
165        let b = input.as_ref();
166        let (preamble_bytes, b) = try_split_at(b, PREAMBLE_SIZE, "preamble")?;
167        let (version, header_len) = preamble_checked(preamble_bytes)?;
168
169        let (header_bytes, b) = try_split_at(b, header_len, "header")?;
170        let (h_sig_len_bytes, b) = try_split_at(b, SIG_SIZE_SIZE, "header signature length")?;
171        let h_sig_len = u32::from_be_bytes(h_sig_len_bytes.try_into()?);
172        let (h_sig_bytes, ct) = try_split_at(b, h_sig_len as usize, "header signature")?;
173
174        let h_sig_ext: SignatureExt = crate::bincode_compat::deserialize(h_sig_bytes)?;
175        let id = h_sig_ext.pol.derive_ibs()?;
176
177        let verifier = Verifier::default().chain(header_bytes);
178
179        if !verifier.clone().verify(&vk.0, &h_sig_ext.sig, &id) {
180            return Err(Error::IncorrectSignature);
181        }
182
183        let header: Header = crate::bincode_compat::deserialize(header_bytes)?;
184        let message_len = match header.mode {
185            Mode::InMemory { size } => size as usize,
186            _ => return Err(Error::ModeNotSupported(header.mode)),
187        };
188
189        Ok(Self {
190            version,
191            header,
192            pub_id: h_sig_ext.pol,
193            r: ct.to_vec(),
194            verifier,
195            vk: vk.clone(),
196            config: UnsealerMemoryConfig { message_len },
197        })
198    }
199
200    /// Unseals the payload.
201    pub fn unseal(
202        self,
203        ident: &str,
204        usk: &UserSecretKey<CGWKV>,
205    ) -> Result<(Vec<u8>, VerificationResult), Error> {
206        let rec_info = self
207            .header
208            .recipients
209            .get(ident)
210            .ok_or_else(|| Error::UnknownIdentifier(ident.to_string()))?;
211
212        let ss = rec_info.decaps(usk)?;
213        let key = &ss.0[..KEY_SIZE];
214
215        let Algorithm::Aes128Gcm(iv) = self.header.algo;
216
217        let aead = Aes128Gcm::new_from_slice(key)?;
218        let nonce = Nonce::from(iv.0);
219
220        let plain = aead.decrypt(&nonce, &*self.r)?;
221
222        let (msg, read): (MessageAndSignaturePrefix, usize) =
223            crate::bincode_compat::deserialize_with_len(&plain)?;
224
225        // A container sealed by this version carries the sender's public
226        // signing policy behind the message signature, under the AEAD. The
227        // header signature outside the AEAD claims a policy too; if they
228        // disagree, that block was swapped. Nothing following means the sealer
229        // predates the copy. Both readings are authenticated against the DEM
230        // key and reach no further, so the absence branch is not the safe half
231        // of the two — see the note on `MessageAndSignature`.
232        if let Some(trailing) = plain.get(read..).filter(|t| !t.is_empty()) {
233            let sealed_pub_pol: Policy = crate::bincode_compat::deserialize(trailing)?;
234
235            if sealed_pub_pol != self.pub_id {
236                return Err(Error::IncorrectSignature);
237            }
238        }
239
240        let id = msg.sig.pol.derive_ibs()?;
241
242        if !self
243            .verifier
244            .chain(&msg.message)
245            .verify(&self.vk.0, &msg.sig.sig, &id)
246        {
247            return Err(Error::IncorrectSignature);
248        }
249
250        debug_assert_eq!(self.config.message_len, msg.message.len());
251
252        let private = if self.pub_id == msg.sig.pol {
253            None
254        } else {
255            Some(msg.sig.pol)
256        };
257
258        Ok((
259            msg.message,
260            VerificationResult {
261                public: self.pub_id,
262                private,
263            },
264        ))
265    }
266}
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271    use crate::test::TestSetup;
272
273    #[test]
274    fn test_seal_memory() {
275        let mut rng = rand::thread_rng();
276        let setup = TestSetup::new(&mut rng);
277
278        // Alice email
279        let pub_sign_key = &setup.signing_keys[0];
280        // Alice bsn
281        let priv_sign_key = &setup.signing_keys[1];
282
283        let input = b"SECRET DATA";
284        let sealed = Sealer::<_, SealerMemoryConfig>::new(
285            &setup.ibe_pk,
286            &setup.policy,
287            pub_sign_key,
288            &mut rng,
289        )
290        .unwrap()
291        .with_priv_signing_key(priv_sign_key.clone())
292        .seal(input)
293        .unwrap();
294
295        // Take Bob's USK for email + name
296        let usk = &setup.usks[2];
297        let (original, verified_policy) =
298            Unsealer::<_, UnsealerMemoryConfig>::new(sealed, &setup.ibs_pk)
299                .unwrap()
300                .unseal("Bob", usk)
301                .unwrap();
302
303        assert_eq!(&input.to_vec(), &original);
304
305        let expected = VerificationResult {
306            public: setup.policies[0].clone(),
307            private: Some(setup.policies[1].clone()),
308        };
309
310        assert_eq!(&verified_policy, &expected);
311    }
312
313    #[test]
314    fn test_seal_unseal_wrong_usk() {
315        let mut rng = rand::thread_rng();
316        let setup = TestSetup::new(&mut rng);
317
318        let pub_sign_key = &setup.signing_keys[0];
319        let priv_sign_key = &setup.signing_keys[1];
320
321        let input = b"SECRET DATA";
322        let sealed = Sealer::<_, SealerMemoryConfig>::new(
323            &setup.ibe_pk,
324            &setup.policy,
325            pub_sign_key,
326            &mut rng,
327        )
328        .unwrap()
329        .with_priv_signing_key(priv_sign_key.clone())
330        .seal(input)
331        .unwrap();
332
333        // Take Charlie's USK for only name.
334        let usk = &setup.usks[4];
335        let res = Unsealer::<_, UnsealerMemoryConfig>::new(sealed, &setup.ibs_pk)
336            .unwrap()
337            .unseal("Charlie", usk);
338
339        assert!(matches!(res, Err(Error::KEM)));
340    }
341
342    #[test]
343    fn test_seal_unseal_wrong_id() {
344        let mut rng = rand::thread_rng();
345        let setup = TestSetup::new(&mut rng);
346
347        let pub_sign_key = &setup.signing_keys[0];
348        let priv_sign_key = &setup.signing_keys[1];
349
350        let input = b"SECRET DATA";
351        let sealed = Sealer::<_, SealerMemoryConfig>::new(
352            &setup.ibe_pk,
353            &setup.policy,
354            pub_sign_key,
355            &mut rng,
356        )
357        .unwrap()
358        .with_priv_signing_key(priv_sign_key.clone())
359        .seal(input)
360        .unwrap();
361
362        let usk = &setup.usks[4];
363        let res = Unsealer::<_, UnsealerMemoryConfig>::new(sealed, &setup.ibs_pk)
364            .unwrap()
365            .unseal("Daniel", usk);
366
367        assert!(matches!(res, Err(Error::UnknownIdentifier(_))));
368    }
369
370    #[test]
371    fn test_unseal_rejects_empty_input() {
372        let mut rng = rand::thread_rng();
373        let setup = TestSetup::new(&mut rng);
374        let res = Unsealer::<_, UnsealerMemoryConfig>::new(&[] as &[u8], &setup.ibs_pk);
375        // Must not panic — should surface as NotPostGuard / FormatViolation.
376        assert!(res.is_err());
377    }
378
379    #[test]
380    fn test_unseal_rejects_truncated_after_preamble() {
381        let mut rng = rand::thread_rng();
382        let setup = TestSetup::new(&mut rng);
383
384        let pub_sign_key = &setup.signing_keys[0];
385        let priv_sign_key = &setup.signing_keys[1];
386
387        let sealed = Sealer::<_, SealerMemoryConfig>::new(
388            &setup.ibe_pk,
389            &setup.policy,
390            pub_sign_key,
391            &mut rng,
392        )
393        .unwrap()
394        .with_priv_signing_key(priv_sign_key.clone())
395        .seal(b"SECRET DATA")
396        .unwrap();
397
398        // Keep the full preamble (so header_len parses) but truncate the body.
399        let mut truncated = sealed;
400        truncated.truncate(PREAMBLE_SIZE + 1);
401
402        let res = Unsealer::<_, UnsealerMemoryConfig>::new(truncated, &setup.ibs_pk);
403        match res {
404            Err(Error::FormatViolation(_)) => {}
405            other => panic!("expected FormatViolation, got {:?}", other),
406        }
407    }
408
409    #[test]
410    fn test_unseal_rejects_garbage_input() {
411        let mut rng = rand::thread_rng();
412        let setup = TestSetup::new(&mut rng);
413        // 1 KiB of zeros — no valid prelude, no valid lengths.
414        let garbage = vec![0u8; 1024];
415        let res = Unsealer::<_, UnsealerMemoryConfig>::new(garbage, &setup.ibs_pk);
416        assert!(res.is_err());
417    }
418
419    fn seal_memory<R: rand::RngCore + rand::CryptoRng>(setup: &TestSetup, rng: &mut R) -> Vec<u8> {
420        let pub_sign_key = &setup.signing_keys[0];
421        let priv_sign_key = &setup.signing_keys[1];
422        Sealer::<_, SealerMemoryConfig>::new(&setup.ibe_pk, &setup.policy, pub_sign_key, rng)
423            .unwrap()
424            .with_priv_signing_key(priv_sign_key.clone())
425            .seal(b"SECRET DATA")
426            .unwrap()
427    }
428
429    #[test]
430    fn test_unseal_rejects_input_shorter_than_preamble() {
431        let mut rng = rand::thread_rng();
432        let setup = TestSetup::new(&mut rng);
433        // One byte short of a preamble — preamble split must fail cleanly.
434        let buf = vec![0u8; PREAMBLE_SIZE - 1];
435        match Unsealer::<_, UnsealerMemoryConfig>::new(buf, &setup.ibs_pk) {
436            Err(Error::FormatViolation(msg)) => assert!(msg.contains("preamble")),
437            other => panic!("expected FormatViolation(preamble), got {:?}", other),
438        }
439    }
440
441    #[test]
442    fn test_unseal_rejects_truncated_inside_header() {
443        let mut rng = rand::thread_rng();
444        let setup = TestSetup::new(&mut rng);
445        let sealed = seal_memory(&setup, &mut rng);
446
447        // Keep preamble intact but drop most of the header.
448        let mut truncated = sealed;
449        truncated.truncate(PREAMBLE_SIZE + 4);
450
451        match Unsealer::<_, UnsealerMemoryConfig>::new(truncated, &setup.ibs_pk) {
452            Err(Error::FormatViolation(msg)) => assert!(msg.contains("header")),
453            other => panic!("expected FormatViolation(header), got {:?}", other),
454        }
455    }
456
457    #[test]
458    fn test_unseal_rejects_truncated_before_sig_len() {
459        let mut rng = rand::thread_rng();
460        let setup = TestSetup::new(&mut rng);
461        let sealed = seal_memory(&setup, &mut rng);
462
463        // Parse the header length so we know where the sig length begins,
464        // then cut the input right before the sig length bytes.
465        let (_, header_len) =
466            preamble_checked(&sealed[..PREAMBLE_SIZE]).expect("preamble should parse");
467        let cut = PREAMBLE_SIZE + header_len;
468
469        // Ensure we're strictly before the end of the sig-length field.
470        assert!(cut + SIG_SIZE_SIZE <= sealed.len());
471
472        let truncated = sealed[..cut + 1].to_vec();
473
474        match Unsealer::<_, UnsealerMemoryConfig>::new(truncated, &setup.ibs_pk) {
475            Err(Error::FormatViolation(msg)) => {
476                assert!(msg.contains("header signature length"))
477            }
478            other => panic!(
479                "expected FormatViolation(header signature length), got {:?}",
480                other
481            ),
482        }
483    }
484
485    #[test]
486    fn test_unseal_rejects_truncated_inside_sig_bytes() {
487        let mut rng = rand::thread_rng();
488        let setup = TestSetup::new(&mut rng);
489        let sealed = seal_memory(&setup, &mut rng);
490
491        let (_, header_len) =
492            preamble_checked(&sealed[..PREAMBLE_SIZE]).expect("preamble should parse");
493        // Keep preamble + header + sig-length + 1 byte of sig — sig is then truncated.
494        let cut = PREAMBLE_SIZE + header_len + SIG_SIZE_SIZE + 1;
495        assert!(cut < sealed.len(), "sealed output unexpectedly short");
496
497        let truncated = sealed[..cut].to_vec();
498
499        match Unsealer::<_, UnsealerMemoryConfig>::new(truncated, &setup.ibs_pk) {
500            Err(Error::FormatViolation(msg)) => {
501                assert!(msg.contains("header signature") && !msg.contains("length"))
502            }
503            other => panic!(
504                "expected FormatViolation(header signature), got {:?}",
505                other
506            ),
507        }
508    }
509
510    /// Splits a sealed in-memory container into the header bytes, the header
511    /// signature block and the ciphertext.
512    fn split_container(sealed: &[u8]) -> (&[u8], &[u8], &[u8]) {
513        let (_, header_len) =
514            preamble_checked(&sealed[..PREAMBLE_SIZE]).expect("preamble should parse");
515        let sig_len_at = PREAMBLE_SIZE + header_len;
516        let sig_len = u32::from_be_bytes(
517            sealed[sig_len_at..sig_len_at + SIG_SIZE_SIZE]
518                .try_into()
519                .unwrap(),
520        ) as usize;
521        let sig_at = sig_len_at + SIG_SIZE_SIZE;
522
523        (
524            &sealed[PREAMBLE_SIZE..sig_len_at],
525            &sealed[sig_at..sig_at + sig_len],
526            &sealed[sig_at + sig_len..],
527        )
528    }
529
530    /// The attack: keep the preamble, header and ciphertext byte for byte, and
531    /// replace only the header signature block with one made over the same
532    /// header bytes by another signing key, carrying that key's policy.
533    fn swap_header_signature<R: rand::RngCore + rand::CryptoRng>(
534        sealed: &[u8],
535        attacker: &crate::artifacts::SigningKeyExt,
536        rng: &mut R,
537    ) -> Vec<u8> {
538        let (header_bytes, _, ct) = split_container(sealed);
539
540        let h_sig_ext = SignatureExt {
541            sig: Signer::new().chain(header_bytes).sign(&attacker.key.0, rng),
542            pol: attacker.policy.clone(),
543        };
544        let h_sig_ext_bytes = crate::bincode_compat::serialize(&h_sig_ext).unwrap();
545
546        let mut out = sealed[..PREAMBLE_SIZE + header_bytes.len()].to_vec();
547        out.extend_from_slice(&(h_sig_ext_bytes.len() as u32).to_be_bytes());
548        out.extend_from_slice(&h_sig_ext_bytes);
549        out.extend_from_slice(ct);
550
551        out
552    }
553
554    /// A container sealed before the AEAD carried a copy of the public signing
555    /// policy: same bytes, but with the appended policy cut off the plaintext
556    /// and the ciphertext recomputed.
557    fn strip_pub_pol(sealed: &[u8], ident: &str, usk: &UserSecretKey<CGWKV>) -> Vec<u8> {
558        let (header_bytes, _, ct) = split_container(sealed);
559        let header: Header = crate::bincode_compat::deserialize(header_bytes).unwrap();
560        let ss = header.recipients.get(ident).unwrap().decaps(usk).unwrap();
561
562        let Algorithm::Aes128Gcm(iv) = header.algo;
563        let aead = Aes128Gcm::new_from_slice(&ss.0[..KEY_SIZE]).unwrap();
564        let nonce = Nonce::from(iv.0);
565
566        let plain = aead.decrypt(&nonce, ct).unwrap();
567        let (_, read): (MessageAndSignaturePrefix, usize) =
568            crate::bincode_compat::deserialize_with_len(&plain).unwrap();
569        assert!(
570            read < plain.len(),
571            "the sealer wrote no appended policy — nothing to strip"
572        );
573
574        let mut out = sealed[..sealed.len() - ct.len()].to_vec();
575        out.extend_from_slice(&aead.encrypt(&nonce, &plain[..read]).unwrap());
576
577        out
578    }
579
580    /// Replacing the header signature with one made by another signing key over
581    /// the same header bytes must be rejected: the AEAD-protected copy of the
582    /// public signing policy no longer matches the one the block claims.
583    #[test]
584    fn test_unseal_rejects_swapped_header_signature() {
585        let mut rng = rand::thread_rng();
586        let setup = TestSetup::new(&mut rng);
587        let sealed = seal_memory(&setup, &mut rng);
588
589        // Charlie's name-only key — a key the PKG hands to whoever authenticates
590        // as Charlie, which is exactly what makes the swap cheap.
591        let swapped = swap_header_signature(&sealed, &setup.signing_keys[4], &mut rng);
592
593        let unsealer = Unsealer::<_, UnsealerMemoryConfig>::new(swapped, &setup.ibs_pk)
594            .expect("the swapped header signature still verifies — that is the attack");
595        assert_eq!(
596            unsealer.pub_id, setup.policies[4],
597            "the container now claims the attacker as public sender"
598        );
599
600        match unsealer.unseal("Bob", &setup.usks[2]) {
601            Err(Error::IncorrectSignature) => {}
602            other => panic!("expected IncorrectSignature, got {:?}", other),
603        }
604    }
605
606    /// A container sealed by a pg-core that predates the appended copy carries
607    /// nothing behind the message signature. It must still unseal, and report
608    /// the same sender it always did.
609    #[test]
610    fn test_unseal_accepts_container_without_appended_policy() {
611        let mut rng = rand::thread_rng();
612        let setup = TestSetup::new(&mut rng);
613        let sealed = seal_memory(&setup, &mut rng);
614        let legacy = strip_pub_pol(&sealed, "Bob", &setup.usks[2]);
615
616        let (plain, verified) = Unsealer::<_, UnsealerMemoryConfig>::new(legacy, &setup.ibs_pk)
617            .unwrap()
618            .unseal("Bob", &setup.usks[2])
619            .expect("a container without the appended policy must still open");
620
621        assert_eq!(&plain, b"SECRET DATA");
622        assert_eq!(
623            verified,
624            VerificationResult {
625                public: setup.policies[0].clone(),
626                private: Some(setup.policies[1].clone()),
627            }
628        );
629    }
630
631    #[test]
632    fn test_unseal_rejects_wrong_prelude() {
633        let mut rng = rand::thread_rng();
634        let setup = TestSetup::new(&mut rng);
635        let mut sealed = seal_memory(&setup, &mut rng);
636
637        // Flip a byte in the prelude — must fall through as NotPostGuard,
638        // never panic.
639        sealed[0] = sealed[0].wrapping_add(1);
640
641        match Unsealer::<_, UnsealerMemoryConfig>::new(sealed, &setup.ibs_pk) {
642            Err(Error::NotPostGuard) => {}
643            other => panic!("expected NotPostGuard, got {:?}", other),
644        }
645    }
646}