Skip to main content

pg_core/client/rust/
stream.rs

1//! Streaming mode.
2
3use alloc::string::ToString;
4
5use crate::artifacts::{PublicKey, SigningKeyExt, UserSecretKey, VerifyingKey};
6use crate::client::*;
7use crate::error::Error;
8use crate::identity::{EncryptionPolicy, Policy};
9use ibe::kem::cgw_kv::CGWKV;
10use ibs::gg::{Identity, Signature, Signer, Verifier, SIG_BYTES};
11
12use aead::stream::{DecryptorBE32, EncryptorBE32};
13use aead::KeyInit;
14use aes_gcm::Aes128Gcm;
15use alloc::vec::Vec;
16use futures::io::{AsyncRead, AsyncWrite};
17use futures::io::{AsyncReadExt, AsyncWriteExt};
18use futures::TryFutureExt;
19use rand::{CryptoRng, RngCore};
20
21/// Configures an [`Sealer`] to process a payload stream.
22#[derive(Debug)]
23pub struct SealerStreamConfig {
24    /// Segment size.
25    segment_size: u32,
26    /// AEAD key.
27    key: [u8; KEY_SIZE],
28    /// AEAD nonce.
29    nonce: [u8; STREAM_NONCE_SIZE],
30}
31
32/// Configures an [`Unsealer`] to process a payload stream.
33#[derive(Debug)]
34pub struct UnsealerStreamConfig {
35    segment_size: u32,
36}
37
38impl SealerConfig for SealerStreamConfig {}
39impl UnsealerConfig for UnsealerStreamConfig {}
40impl crate::client::sealed::SealerConfig for SealerStreamConfig {}
41impl crate::client::sealed::UnsealerConfig for UnsealerStreamConfig {}
42
43impl<'r, Rng: RngCore + CryptoRng> Sealer<'r, Rng, SealerStreamConfig> {
44    /// Construct a new [`Sealer`] that can process streaming payloads.
45    pub fn new(
46        pk: &PublicKey<CGWKV>,
47        policies: &EncryptionPolicy,
48        pub_sign_key: &SigningKeyExt,
49        rng: &'r mut Rng,
50    ) -> Result<Self, Error> {
51        let (header, ss) = Header::new(pk, policies, rng)?;
52
53        let (segment_size, _) = stream_mode_checked(&header)?;
54        let Algorithm::Aes128Gcm(iv) = header.algo;
55
56        let mut key = [0u8; KEY_SIZE];
57        let mut nonce = [0u8; STREAM_NONCE_SIZE];
58
59        key.copy_from_slice(&ss.0[..KEY_SIZE]);
60        nonce.copy_from_slice(&iv.0[..STREAM_NONCE_SIZE]);
61
62        Ok(Sealer {
63            rng,
64            header,
65            pub_sign_key: pub_sign_key.clone(),
66            priv_sign_key: None,
67            config: SealerStreamConfig {
68                segment_size,
69                key,
70                nonce,
71            },
72        })
73    }
74
75    /// Optional: Add a size hint.
76    ///
77    /// This can help the receiver save some reallocations.
78    pub fn with_size_hint(mut self, size_hint: (u64, Option<u64>)) -> Self {
79        self.header.mode = Mode::Streaming {
80            segment_size: self.config.segment_size,
81            size_hint,
82        };
83
84        self
85    }
86
87    /// Seals payload data from an [`AsyncRead`] into an [`AsyncWrite`].
88    pub async fn seal<R, W>(self, mut r: R, mut w: W) -> Result<(), Error>
89    where
90        R: AsyncRead + Unpin,
91        W: AsyncWrite + Unpin,
92    {
93        w.write_all(&PRELUDE).await?;
94        w.write_all(&VERSION_V3.to_be_bytes()).await?;
95
96        let header_vec = crate::bincode_compat::serialize(&self.header)?;
97        w.write_all(&u32::try_from(header_vec.len())?.to_be_bytes())
98            .await?;
99        w.write_all(&header_vec).await?;
100
101        let mut signer = Signer::default().chain(&header_vec);
102        let header_sig = signer.clone().sign(&self.pub_sign_key.key.0, self.rng);
103        let header_sig_ext = SignatureExt {
104            sig: header_sig,
105            pol: self.pub_sign_key.policy.clone(),
106        };
107        let header_sig_bytes = crate::bincode_compat::serialize(&header_sig_ext)?;
108
109        w.write_all(&u32::try_from(header_sig_bytes.len())?.to_be_bytes())
110            .await?;
111        w.write_all(&header_sig_bytes).await?;
112
113        let aead = Aes128Gcm::new_from_slice(&self.config.key)?;
114        let mut enc = EncryptorBE32::from_aead(aead, &self.config.nonce.into());
115
116        // Check for a private signing key, otherwise fall back to the public one.
117        let signing_key = self.priv_sign_key.unwrap_or(self.pub_sign_key);
118
119        let pol_bytes = crate::bincode_compat::serialize(&signing_key.policy)?;
120        let pol_len = pol_bytes.len();
121
122        if pol_len + POL_SIZE_SIZE > self.config.segment_size as usize {
123            return Err(Error::ConstraintViolation);
124        }
125
126        let mut buf = vec![0; self.config.segment_size as usize + TAG_SIZE];
127
128        buf[..POL_SIZE_SIZE].copy_from_slice(&u32::try_from(pol_len)?.to_be_bytes());
129        buf[POL_SIZE_SIZE..POL_SIZE_SIZE + pol_len].copy_from_slice(&pol_bytes);
130
131        let mut buf_tail = POL_SIZE_SIZE + pol_len;
132        let mut start = buf_tail;
133
134        // First segment: DEM.K (pol_len || pol || m_0 || sig_0 )
135        // Other segments: DEM.K (m_i || sig_0)
136
137        let mut counter: u32 = 0;
138
139        loop {
140            let read = r
141                .read(&mut buf[buf_tail..self.config.segment_size as usize])
142                .await?;
143            buf_tail += read;
144
145            if buf_tail == self.config.segment_size as usize {
146                buf.truncate(buf_tail);
147
148                signer.update(&buf[start..]);
149                let sig = signer
150                    .clone()
151                    .chain(counter.to_be_bytes())
152                    .chain([0x00])
153                    .sign(&signing_key.key.0, self.rng);
154                crate::bincode_compat::serialize_into_vec(&mut buf, &sig)?;
155
156                enc.encrypt_next_in_place(b"", &mut buf)?;
157
158                w.write_all(&buf).await?;
159
160                buf_tail = 0;
161                start = 0;
162                counter = counter.checked_add(1).unwrap(); // cannot fail, otherwise
163                                                           // encrypt_next_in_place would have
164                                                           // failed too.                                                // encrypt_next_in_place not failing
165            } else if read == 0 {
166                buf.truncate(buf_tail);
167
168                signer.update(&buf[start..]);
169                let sig_final = signer
170                    .chain(counter.to_be_bytes())
171                    .chain([0x01])
172                    .sign(&signing_key.key.0, self.rng);
173                crate::bincode_compat::serialize_into_vec(&mut buf, &sig_final)?;
174
175                enc.encrypt_last_in_place(b"", &mut buf)?;
176
177                w.write_all(&buf).await?;
178                break;
179            }
180        }
181
182        w.flush().await?;
183        w.close().await?;
184
185        Ok(())
186    }
187}
188
189impl<R> Unsealer<R, UnsealerStreamConfig>
190where
191    R: AsyncRead + Unpin,
192{
193    /// Create a new [`Unsealer`] that starts reading from an [`AsyncRead`].
194    ///
195    /// Errors if the bytestream is not a legitimate PostGuard bytestream.
196    pub async fn new(mut r: R, pk: &VerifyingKey) -> Result<Self, Error> {
197        let mut preamble = [0u8; PREAMBLE_SIZE];
198        r.read_exact(&mut preamble)
199            .map_err(|_e| Error::NotPostGuard)
200            .await?;
201
202        let (version, header_len) = preamble_checked(&preamble)?;
203        let mut header_raw = Vec::with_capacity(header_len);
204
205        // Limit reader to not read past header
206        let mut r = r.take(header_len as u64);
207
208        r.read_to_end(&mut header_raw)
209            .map_err(|_e| Error::ConstraintViolation)
210            .await?;
211
212        let mut r = r.into_inner();
213
214        let mut header_sig_len_bytes = [0u8; SIG_SIZE_SIZE];
215        r.read_exact(&mut header_sig_len_bytes)
216            .map_err(|_e| Error::FormatViolation("no header signature length".to_string()))
217            .await?;
218        let header_sig_len = u32::from_be_bytes(header_sig_len_bytes) as usize;
219
220        // Bound the length prefix to a sane maximum before it sizes an
221        // allocation, mirroring the MAX_HEADER_SIZE check in preamble_checked.
222        if header_sig_len > MAX_SIG_SIZE {
223            return Err(Error::ConstraintViolation);
224        }
225
226        let mut header_sig_raw = Vec::with_capacity(header_sig_len);
227        let mut r = r.take(header_sig_len as u64);
228
229        r.read_to_end(&mut header_sig_raw).await?;
230
231        let h_sig_ext: SignatureExt = crate::bincode_compat::deserialize(&header_sig_raw)?;
232
233        let verifier = Verifier::default().chain(&header_raw);
234        let pub_id = h_sig_ext.pol.derive_ibs()?;
235
236        if !verifier.clone().verify(&pk.0, &h_sig_ext.sig, &pub_id) {
237            return Err(Error::IncorrectSignature);
238        }
239
240        let header: Header = crate::bincode_compat::deserialize(&header_raw)?;
241        let (segment_size, _) = stream_mode_checked(&header)?;
242
243        Ok(Unsealer {
244            version,
245            header,
246            pub_id: h_sig_ext.pol,
247            config: UnsealerStreamConfig { segment_size },
248            r: r.into_inner(), // This (new) reader is locked to the payload.
249            verifier,
250            vk: pk.clone(),
251        })
252    }
253
254    /// Unseal the remaining data (which is now only payload) into an [`AsyncWrite`].
255    pub async fn unseal<W: AsyncWrite + Unpin>(
256        mut self,
257        ident: &str,
258        usk: &UserSecretKey<CGWKV>,
259        mut w: W,
260    ) -> Result<VerificationResult, Error> {
261        let rec_info = self
262            .header
263            .recipients
264            .get(ident)
265            .ok_or_else(|| Error::UnknownIdentifier(ident.to_string()))?;
266
267        let ss = rec_info.decaps(usk)?;
268        let key = &ss.0[..KEY_SIZE];
269        let aead = Aes128Gcm::new_from_slice(key)?;
270
271        let Algorithm::Aes128Gcm(iv) = self.header.algo;
272        let nonce = &iv.0[..STREAM_NONCE_SIZE];
273
274        let mut dec = DecryptorBE32::from_aead(aead, nonce.into());
275
276        let bufsize: usize = self.config.segment_size as usize + SIG_BYTES + TAG_SIZE;
277        let mut buf = vec![0u8; bufsize];
278        let mut buf_tail = 0;
279        let mut counter: u32 = 0;
280        let mut pol_id: Option<(Policy, Identity)> = None;
281
282        fn extract_policy(buf: &mut Vec<u8>) -> Result<Option<(Policy, Identity)>, Error> {
283            if buf.len() < POL_SIZE_SIZE {
284                return Err(Error::FormatViolation(alloc::string::String::from(
285                    "policy length",
286                )));
287            }
288            let pol_len = u32::from_be_bytes(buf[..POL_SIZE_SIZE].try_into()?) as usize;
289            let pol_end = POL_SIZE_SIZE.checked_add(pol_len).ok_or_else(|| {
290                Error::FormatViolation(alloc::string::String::from("policy length overflow"))
291            })?;
292            if buf.len() < pol_end {
293                return Err(Error::FormatViolation(alloc::string::String::from(
294                    "policy truncated",
295                )));
296            }
297            let pol_bytes = &buf[POL_SIZE_SIZE..pol_end];
298            let pol: Policy = crate::bincode_compat::deserialize(pol_bytes)?;
299            let id = pol.derive_ibs()?;
300
301            buf.drain(..pol_end);
302
303            Ok(Some((pol, id)))
304        }
305
306        fn verify_segment<'a>(
307            seg: &'a [u8],
308            verifier: &mut Verifier,
309            vk: &VerifyingKey,
310            id: &Identity,
311            counter: u32,
312            is_last: bool,
313        ) -> Result<&'a [u8], Error> {
314            if seg.len() < SIG_BYTES {
315                return Err(Error::FormatViolation(alloc::string::String::from(
316                    "segment too short for signature",
317                )));
318            }
319
320            let (m, sig_bytes) = seg.split_at(seg.len() - SIG_BYTES);
321            let sig: Signature = crate::bincode_compat::deserialize(sig_bytes)?;
322            verifier.update(m);
323
324            if !verifier
325                .clone()
326                .chain(counter.to_be_bytes())
327                .chain([is_last as u8])
328                .verify(&vk.0, &sig, id)
329            {
330                return Err(Error::IncorrectSignature);
331            }
332
333            Ok(m)
334        }
335
336        loop {
337            let read = self.r.read(&mut buf[buf_tail..bufsize]).await?;
338            buf_tail += read;
339
340            if buf_tail == bufsize {
341                dec.decrypt_next_in_place(b"", &mut buf)?;
342
343                if counter == 0 {
344                    pol_id = extract_policy(&mut buf)?;
345                }
346
347                let m = verify_segment(
348                    &buf,
349                    &mut self.verifier,
350                    &self.vk,
351                    &pol_id.as_ref().unwrap().1,
352                    counter,
353                    false,
354                )?;
355
356                w.write_all(m).await?;
357
358                buf_tail = 0;
359                buf.resize(bufsize, 0);
360                counter += 1;
361            } else if read == 0 {
362                buf.truncate(buf_tail);
363                dec.decrypt_last_in_place(b"", &mut buf)?;
364
365                if counter == 0 {
366                    pol_id = extract_policy(&mut buf)?;
367                }
368
369                let m = verify_segment(
370                    &buf,
371                    &mut self.verifier,
372                    &self.vk,
373                    &pol_id.as_ref().unwrap().1,
374                    counter,
375                    true,
376                )?;
377
378                w.write_all(m).await?;
379
380                break;
381            }
382        }
383
384        w.close().await?;
385
386        let private_id = pol_id.unwrap().0;
387        let private = if self.pub_id == private_id {
388            None
389        } else {
390            Some(private_id)
391        };
392
393        Ok(VerificationResult {
394            public: self.pub_id,
395            private,
396        })
397    }
398}
399
400#[cfg(test)]
401mod tests {
402    use super::{Sealer, SealerStreamConfig, Unsealer, UnsealerStreamConfig};
403    use crate::client::VerificationResult;
404    use crate::error::Error;
405    use crate::test::TestSetup;
406    use crate::{PREAMBLE_SIZE, SYMMETRIC_CRYPTO_DEFAULT_CHUNK, TAG_SIZE};
407    use alloc::string::String;
408    use alloc::vec::Vec;
409    use futures::{executor::block_on, io::AllowStdIo};
410    use rand::{thread_rng, Rng, RngCore};
411    use std::io::Cursor;
412    use tokio::io::AsyncReadExt;
413
414    const LENGTHS: &[u32] = &[
415        1,
416        512,
417        SYMMETRIC_CRYPTO_DEFAULT_CHUNK - 3,
418        SYMMETRIC_CRYPTO_DEFAULT_CHUNK,
419        SYMMETRIC_CRYPTO_DEFAULT_CHUNK + 3,
420        3 * SYMMETRIC_CRYPTO_DEFAULT_CHUNK,
421        3 * SYMMETRIC_CRYPTO_DEFAULT_CHUNK + 16,
422        3 * SYMMETRIC_CRYPTO_DEFAULT_CHUNK - 17,
423    ];
424
425    fn seal_helper(setup: &TestSetup, plain: &[u8]) -> Vec<u8> {
426        let mut rng = rand::thread_rng();
427
428        let mut input = AllowStdIo::new(Cursor::new(plain));
429        let mut output = AllowStdIo::new(Vec::new());
430
431        let signing_key = &setup.signing_keys[0];
432
433        block_on(async {
434            Sealer::<_, SealerStreamConfig>::new(
435                &setup.ibe_pk,
436                &setup.policy,
437                signing_key,
438                &mut rng,
439            )
440            .unwrap()
441            .seal(&mut input, &mut output)
442            .await
443            .unwrap();
444        });
445
446        output.into_inner()
447    }
448
449    fn unseal_helper(setup: &TestSetup, ct: &[u8]) -> (Vec<u8>, VerificationResult) {
450        let mut input = AllowStdIo::new(Cursor::new(ct));
451        let mut output = AllowStdIo::new(Vec::new());
452
453        // sometimes decrypt as Bob, sometimes decrypt as Charlie
454        let (id, usk_id) = if thread_rng().gen::<bool>() {
455            ("Bob", setup.usks[2].clone())
456        } else {
457            ("Charlie", setup.usks[3].clone())
458        };
459
460        let vr = block_on(async {
461            let unsealer = Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk)
462                .await
463                .unwrap();
464
465            // Normally, a user would need to retrieve a usk here via the PKG,
466            // but in this case we own the master key pair.
467            unsealer.unseal(id, &usk_id, &mut output).await.unwrap()
468        });
469
470        (output.into_inner(), vr)
471    }
472
473    fn seal_and_unseal(setup: &TestSetup, plain: Vec<u8>) {
474        let ct = seal_helper(setup, &plain);
475        let (plain2, vr) = unseal_helper(setup, &ct);
476
477        assert_eq!(&plain, &plain2);
478        assert_eq!(&vr.public, &setup.signing_keys[0].policy);
479        assert_eq!(vr.private, None);
480    }
481
482    fn rand_vec(length: usize) -> Vec<u8> {
483        let mut vec = vec![0u8; length];
484        rand::thread_rng().fill_bytes(&mut vec);
485        vec
486    }
487
488    #[test]
489    fn test_reflection_seal_unsealer() {
490        let mut rng = rand::thread_rng();
491        let setup = TestSetup::new(&mut rng);
492
493        for l in LENGTHS {
494            seal_and_unseal(&setup, rand_vec(*l as usize));
495        }
496    }
497
498    #[test]
499    #[should_panic]
500    fn test_corrupt_header() {
501        let mut rng = rand::thread_rng();
502        let setup = TestSetup::new(&mut rng);
503
504        let plain = rand_vec(100);
505        let mut ct = seal_helper(&setup, &plain);
506
507        // Flip a byte that is guaranteed to be in the header.
508        ct[PREAMBLE_SIZE + 2] = !ct[PREAMBLE_SIZE + 2];
509
510        // This should panic, because of the header signature.
511        let _plain2 = unseal_helper(&setup, &ct);
512    }
513
514    #[test]
515    #[should_panic]
516    fn test_corrupt_payload() {
517        let mut rng = rand::thread_rng();
518        let setup = TestSetup::new(&mut rng);
519
520        let plain = rand_vec(100);
521        let mut ct = seal_helper(&setup, &plain);
522
523        // Flip a byte that is guaranteed to be in the encrypted payload.
524        let ct_len = ct.len();
525        ct[ct_len - TAG_SIZE - 5] = !ct[ct_len - TAG_SIZE - 5];
526
527        // This should panic, because of the AEAD.
528        let _plain2 = unseal_helper(&setup, &ct);
529    }
530
531    #[test]
532    #[should_panic]
533    fn test_corrupt_tag() {
534        let mut rng = rand::thread_rng();
535        let setup = TestSetup::new(&mut rng);
536
537        let plain = rand_vec(100);
538        let mut ct = seal_helper(&setup, &plain);
539
540        let len = ct.len();
541        ct[len - 5] = !ct[len - 5];
542
543        // This should panic as well.
544        let _plain2 = unseal_helper(&setup, &ct);
545    }
546
547    #[tokio::test]
548    async fn test_tokio_file() -> Result<(), Error> {
549        use futures::AsyncWriteExt;
550        use tokio::fs::{File, OpenOptions};
551        use tokio_util::compat::TokioAsyncReadCompatExt;
552
553        let mut rng = rand::thread_rng();
554        let setup = TestSetup::new(&mut rng);
555
556        let signing_key = &setup.signing_keys[0];
557
558        let in_name = std::env::temp_dir().join("foo.txt");
559        let out_name = std::env::temp_dir().join("foo.enc");
560        let orig_name = std::env::temp_dir().join("foo2.txt");
561
562        let mut file = OpenOptions::new()
563            .create(true)
564            .write(true)
565            .truncate(true)
566            .open(&in_name)
567            .await?
568            .compat();
569
570        file.write_all(b"SECRET DATA").await?;
571        file.close().await?;
572
573        let mut in_file = File::open(&in_name).await?.compat();
574        let mut out_file = OpenOptions::new()
575            .create(true)
576            .write(true)
577            .truncate(true)
578            .open(&out_name)
579            .await?
580            .compat();
581
582        Sealer::<_, SealerStreamConfig>::new(&setup.ibe_pk, &setup.policy, signing_key, &mut rng)?
583            .seal(&mut in_file, &mut out_file)
584            .await?;
585
586        in_file.close().await?;
587        out_file.close().await?;
588
589        let mut out_file = File::open(&out_name).await?.compat();
590        let mut orig_file = OpenOptions::new()
591            .create(true)
592            .write(true)
593            .truncate(true)
594            .open(&orig_name)
595            .await?
596            .compat();
597
598        let id = "Bob";
599        let usk = &setup.usks[2];
600
601        Unsealer::<_, UnsealerStreamConfig>::new(&mut out_file, &setup.ibs_pk)
602            .await?
603            .unseal(id, usk, &mut orig_file)
604            .await?;
605
606        out_file.close().await?;
607        orig_file.close().await?;
608
609        let mut buf = String::new();
610        File::open(&orig_name)
611            .await?
612            .read_to_string(&mut buf)
613            .await?;
614
615        assert_eq!(buf.as_bytes(), b"SECRET DATA");
616
617        Ok(())
618    }
619
620    #[tokio::test]
621    async fn test_cursor() -> Result<(), Error> {
622        use futures::io::Cursor;
623
624        let mut rng = rand::thread_rng();
625        let setup = TestSetup::new(&mut rng);
626
627        let signing_key = &setup.signing_keys[0];
628
629        let mut input = Cursor::new(b"SECRET DATA");
630        let mut encrypted = Vec::new();
631
632        Sealer::<_, SealerStreamConfig>::new(&setup.ibe_pk, &setup.policy, signing_key, &mut rng)?
633            .seal(&mut input, &mut encrypted)
634            .await?;
635
636        let mut original = Vec::new();
637        let id = "Bob";
638        let usk = &setup.usks[2];
639        Unsealer::<_, UnsealerStreamConfig>::new(&mut Cursor::new(encrypted), &setup.ibs_pk)
640            .await?
641            .unseal(id, usk, &mut original)
642            .await?;
643
644        assert_eq!(input.into_inner().to_vec(), original);
645        Ok(())
646    }
647
648    #[tokio::test]
649    async fn test_stream_unseal_rejects_empty_input() {
650        use futures::io::Cursor;
651
652        let mut rng = rand::thread_rng();
653        let setup = TestSetup::new(&mut rng);
654
655        // Empty reader must not panic — the preamble read should fail cleanly.
656        let mut input = Cursor::new(Vec::<u8>::new());
657        let res = Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk).await;
658        assert!(matches!(res, Err(Error::NotPostGuard)));
659    }
660
661    #[tokio::test]
662    async fn test_stream_unseal_rejects_truncated_preamble() {
663        use futures::io::Cursor;
664
665        let mut rng = rand::thread_rng();
666        let setup = TestSetup::new(&mut rng);
667
668        // A few bytes — enough to look like the start of a preamble but
669        // not enough to finish reading one.
670        let mut input = Cursor::new(vec![0u8; PREAMBLE_SIZE - 1]);
671        let res = Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk).await;
672        assert!(matches!(res, Err(Error::NotPostGuard)));
673    }
674
675    #[tokio::test]
676    async fn test_stream_unseal_rejects_garbage_input() {
677        use futures::io::Cursor;
678
679        let mut rng = rand::thread_rng();
680        let setup = TestSetup::new(&mut rng);
681
682        // 4 KiB of zeros — the prelude check rejects this before any unchecked
683        // length-prefixed read can panic.
684        let mut input = Cursor::new(vec![0u8; 4096]);
685        let res = Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk).await;
686        assert!(res.is_err());
687    }
688
689    #[tokio::test]
690    async fn test_stream_unseal_rejects_flipped_prelude() {
691        use futures::io::Cursor;
692
693        let mut rng = rand::thread_rng();
694        let setup = TestSetup::new(&mut rng);
695        let mut ct = seal_helper(&setup, b"SECRET DATA");
696
697        // Flip a byte in the prelude — must be rejected as NotPostGuard,
698        // never panic.
699        ct[0] = ct[0].wrapping_add(1);
700
701        let mut input = Cursor::new(ct);
702        let res = Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk).await;
703        assert!(matches!(res, Err(Error::NotPostGuard)));
704    }
705
706    #[tokio::test]
707    async fn test_stream_unseal_rejects_oversized_sig_len() {
708        use crate::util::preamble_checked;
709        use crate::{MAX_SIG_SIZE, SIG_SIZE_SIZE};
710        use futures::io::Cursor;
711
712        let mut rng = rand::thread_rng();
713        let setup = TestSetup::new(&mut rng);
714        let mut ct = seal_helper(&setup, b"SECRET DATA");
715
716        // The header-signature length prefix sits right after the header. Overwrite
717        // it with a value larger than MAX_SIG_SIZE and assert the unsealer rejects it
718        // with a ConstraintViolation before allocating a buffer of that size.
719        let (_, header_len) =
720            preamble_checked(&ct[..PREAMBLE_SIZE]).expect("preamble should parse");
721        let sig_len_off = PREAMBLE_SIZE + header_len;
722        assert!(sig_len_off + SIG_SIZE_SIZE <= ct.len());
723        assert!((MAX_SIG_SIZE as u64) < u32::MAX as u64);
724        ct[sig_len_off..sig_len_off + SIG_SIZE_SIZE].copy_from_slice(&u32::MAX.to_be_bytes());
725
726        let mut input = Cursor::new(ct);
727        let res = Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk).await;
728        assert!(
729            matches!(res, Err(Error::ConstraintViolation)),
730            "expected ConstraintViolation, got {res:?}"
731        );
732    }
733}