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);
219
220        let mut header_sig_raw = Vec::with_capacity(header_sig_len as usize);
221        let mut r = r.take(header_sig_len as u64);
222
223        r.read_to_end(&mut header_sig_raw).await?;
224
225        let h_sig_ext: SignatureExt = crate::bincode_compat::deserialize(&header_sig_raw)?;
226
227        let verifier = Verifier::default().chain(&header_raw);
228        let pub_id = h_sig_ext.pol.derive_ibs()?;
229
230        if !verifier.clone().verify(&pk.0, &h_sig_ext.sig, &pub_id) {
231            return Err(Error::IncorrectSignature);
232        }
233
234        let header: Header = crate::bincode_compat::deserialize(&header_raw)?;
235        let (segment_size, _) = stream_mode_checked(&header)?;
236
237        Ok(Unsealer {
238            version,
239            header,
240            pub_id: h_sig_ext.pol,
241            config: UnsealerStreamConfig { segment_size },
242            r: r.into_inner(), // This (new) reader is locked to the payload.
243            verifier,
244            vk: pk.clone(),
245        })
246    }
247
248    /// Unseal the remaining data (which is now only payload) into an [`AsyncWrite`].
249    pub async fn unseal<W: AsyncWrite + Unpin>(
250        mut self,
251        ident: &str,
252        usk: &UserSecretKey<CGWKV>,
253        mut w: W,
254    ) -> Result<VerificationResult, Error> {
255        let rec_info = self
256            .header
257            .recipients
258            .get(ident)
259            .ok_or_else(|| Error::UnknownIdentifier(ident.to_string()))?;
260
261        let ss = rec_info.decaps(usk)?;
262        let key = &ss.0[..KEY_SIZE];
263        let aead = Aes128Gcm::new_from_slice(key)?;
264
265        let Algorithm::Aes128Gcm(iv) = self.header.algo;
266        let nonce = &iv.0[..STREAM_NONCE_SIZE];
267
268        let mut dec = DecryptorBE32::from_aead(aead, nonce.into());
269
270        let bufsize: usize = self.config.segment_size as usize + SIG_BYTES + TAG_SIZE;
271        let mut buf = vec![0u8; bufsize];
272        let mut buf_tail = 0;
273        let mut counter: u32 = 0;
274        let mut pol_id: Option<(Policy, Identity)> = None;
275
276        fn extract_policy(buf: &mut Vec<u8>) -> Result<Option<(Policy, Identity)>, Error> {
277            if buf.len() < POL_SIZE_SIZE {
278                return Err(Error::FormatViolation(alloc::string::String::from(
279                    "policy length",
280                )));
281            }
282            let pol_len = u32::from_be_bytes(buf[..POL_SIZE_SIZE].try_into()?) as usize;
283            let pol_end = POL_SIZE_SIZE.checked_add(pol_len).ok_or_else(|| {
284                Error::FormatViolation(alloc::string::String::from("policy length overflow"))
285            })?;
286            if buf.len() < pol_end {
287                return Err(Error::FormatViolation(alloc::string::String::from(
288                    "policy truncated",
289                )));
290            }
291            let pol_bytes = &buf[POL_SIZE_SIZE..pol_end];
292            let pol: Policy = crate::bincode_compat::deserialize(pol_bytes)?;
293            let id = pol.derive_ibs()?;
294
295            buf.drain(..pol_end);
296
297            Ok(Some((pol, id)))
298        }
299
300        fn verify_segment<'a>(
301            seg: &'a [u8],
302            verifier: &mut Verifier,
303            vk: &VerifyingKey,
304            id: &Identity,
305            counter: u32,
306            is_last: bool,
307        ) -> Result<&'a [u8], Error> {
308            if seg.len() < SIG_BYTES {
309                return Err(Error::FormatViolation(alloc::string::String::from(
310                    "segment too short for signature",
311                )));
312            }
313
314            let (m, sig_bytes) = seg.split_at(seg.len() - SIG_BYTES);
315            let sig: Signature = crate::bincode_compat::deserialize(sig_bytes)?;
316            verifier.update(m);
317
318            if !verifier
319                .clone()
320                .chain(&counter.to_be_bytes())
321                .chain(&[is_last as u8])
322                .verify(&vk.0, &sig, id)
323            {
324                return Err(Error::IncorrectSignature);
325            }
326
327            Ok(m)
328        }
329
330        loop {
331            let read = self.r.read(&mut buf[buf_tail..bufsize]).await?;
332            buf_tail += read;
333
334            if buf_tail == bufsize {
335                dec.decrypt_next_in_place(b"", &mut buf)?;
336
337                if counter == 0 {
338                    pol_id = extract_policy(&mut buf)?;
339                }
340
341                let m = verify_segment(
342                    &buf,
343                    &mut self.verifier,
344                    &self.vk,
345                    &pol_id.as_ref().unwrap().1,
346                    counter,
347                    false,
348                )?;
349
350                w.write_all(m).await?;
351
352                buf_tail = 0;
353                buf.resize(bufsize, 0);
354                counter += 1;
355            } else if read == 0 {
356                buf.truncate(buf_tail);
357                dec.decrypt_last_in_place(b"", &mut buf)?;
358
359                if counter == 0 {
360                    pol_id = extract_policy(&mut buf)?;
361                }
362
363                let m = verify_segment(
364                    &buf,
365                    &mut self.verifier,
366                    &self.vk,
367                    &pol_id.as_ref().unwrap().1,
368                    counter,
369                    true,
370                )?;
371
372                w.write_all(m).await?;
373
374                break;
375            }
376        }
377
378        w.close().await?;
379
380        let private_id = pol_id.unwrap().0;
381        let private = if self.pub_id == private_id {
382            None
383        } else {
384            Some(private_id)
385        };
386
387        Ok(VerificationResult {
388            public: self.pub_id,
389            private,
390        })
391    }
392}
393
394#[cfg(test)]
395mod tests {
396    use super::{Sealer, SealerStreamConfig, Unsealer, UnsealerStreamConfig};
397    use crate::client::VerificationResult;
398    use crate::error::Error;
399    use crate::test::TestSetup;
400    use crate::{PREAMBLE_SIZE, SYMMETRIC_CRYPTO_DEFAULT_CHUNK, TAG_SIZE};
401    use alloc::string::String;
402    use alloc::vec::Vec;
403    use futures::{executor::block_on, io::AllowStdIo};
404    use rand::{thread_rng, Rng, RngCore};
405    use std::io::Cursor;
406    use tokio::io::AsyncReadExt;
407
408    const LENGTHS: &[u32] = &[
409        1,
410        512,
411        SYMMETRIC_CRYPTO_DEFAULT_CHUNK - 3,
412        SYMMETRIC_CRYPTO_DEFAULT_CHUNK,
413        SYMMETRIC_CRYPTO_DEFAULT_CHUNK + 3,
414        3 * SYMMETRIC_CRYPTO_DEFAULT_CHUNK,
415        3 * SYMMETRIC_CRYPTO_DEFAULT_CHUNK + 16,
416        3 * SYMMETRIC_CRYPTO_DEFAULT_CHUNK - 17,
417    ];
418
419    fn seal_helper(setup: &TestSetup, plain: &[u8]) -> Vec<u8> {
420        let mut rng = rand::thread_rng();
421
422        let mut input = AllowStdIo::new(Cursor::new(plain));
423        let mut output = AllowStdIo::new(Vec::new());
424
425        let signing_key = &setup.signing_keys[0];
426
427        block_on(async {
428            Sealer::<_, SealerStreamConfig>::new(
429                &setup.ibe_pk,
430                &setup.policy,
431                &signing_key,
432                &mut rng,
433            )
434            .unwrap()
435            .seal(&mut input, &mut output)
436            .await
437            .unwrap();
438        });
439
440        output.into_inner()
441    }
442
443    fn unseal_helper(setup: &TestSetup, ct: &[u8]) -> (Vec<u8>, VerificationResult) {
444        let mut input = AllowStdIo::new(Cursor::new(ct));
445        let mut output = AllowStdIo::new(Vec::new());
446
447        // sometimes decrypt as Bob, sometimes decrypt as Charlie
448        let (id, usk_id) = if thread_rng().gen::<bool>() {
449            ("Bob", setup.usks[2].clone())
450        } else {
451            ("Charlie", setup.usks[3].clone())
452        };
453
454        let vr = block_on(async {
455            let unsealer = Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk)
456                .await
457                .unwrap();
458
459            // Normally, a user would need to retrieve a usk here via the PKG,
460            // but in this case we own the master key pair.
461            unsealer.unseal(id, &usk_id, &mut output).await.unwrap()
462        });
463
464        (output.into_inner(), vr)
465    }
466
467    fn seal_and_unseal(setup: &TestSetup, plain: Vec<u8>) {
468        let ct = seal_helper(setup, &plain);
469        let (plain2, vr) = unseal_helper(setup, &ct);
470
471        assert_eq!(&plain, &plain2);
472        assert_eq!(&vr.public, &setup.signing_keys[0].policy);
473        assert_eq!(vr.private, None);
474    }
475
476    fn rand_vec(length: usize) -> Vec<u8> {
477        let mut vec = vec![0u8; length];
478        rand::thread_rng().fill_bytes(&mut vec);
479        vec
480    }
481
482    #[test]
483    fn test_reflection_seal_unsealer() {
484        let mut rng = rand::thread_rng();
485        let setup = TestSetup::new(&mut rng);
486
487        for l in LENGTHS {
488            seal_and_unseal(&setup, rand_vec(*l as usize));
489        }
490    }
491
492    #[test]
493    #[should_panic]
494    fn test_corrupt_header() {
495        let mut rng = rand::thread_rng();
496        let setup = TestSetup::new(&mut rng);
497
498        let plain = rand_vec(100);
499        let mut ct = seal_helper(&setup, &plain);
500
501        // Flip a byte that is guaranteed to be in the header.
502        ct[PREAMBLE_SIZE + 2] = !ct[PREAMBLE_SIZE + 2];
503
504        // This should panic, because of the header signature.
505        let _plain2 = unseal_helper(&setup, &ct);
506    }
507
508    #[test]
509    #[should_panic]
510    fn test_corrupt_payload() {
511        let mut rng = rand::thread_rng();
512        let setup = TestSetup::new(&mut rng);
513
514        let plain = rand_vec(100);
515        let mut ct = seal_helper(&setup, &plain);
516
517        // Flip a byte that is guaranteed to be in the encrypted payload.
518        let ct_len = ct.len();
519        ct[ct_len - TAG_SIZE - 5] = !ct[ct_len - TAG_SIZE - 5];
520
521        // This should panic, because of the AEAD.
522        let _plain2 = unseal_helper(&setup, &ct);
523    }
524
525    #[test]
526    #[should_panic]
527    fn test_corrupt_tag() {
528        let mut rng = rand::thread_rng();
529        let setup = TestSetup::new(&mut rng);
530
531        let plain = rand_vec(100);
532        let mut ct = seal_helper(&setup, &plain);
533
534        let len = ct.len();
535        ct[len - 5] = !ct[len - 5];
536
537        // This should panic as well.
538        let _plain2 = unseal_helper(&setup, &ct);
539    }
540
541    #[tokio::test]
542    async fn test_tokio_file() -> Result<(), Error> {
543        use futures::AsyncWriteExt;
544        use tokio::fs::{File, OpenOptions};
545        use tokio_util::compat::TokioAsyncReadCompatExt;
546
547        let mut rng = rand::thread_rng();
548        let setup = TestSetup::new(&mut rng);
549
550        let signing_key = &setup.signing_keys[0];
551
552        let in_name = std::env::temp_dir().join("foo.txt");
553        let out_name = std::env::temp_dir().join("foo.enc");
554        let orig_name = std::env::temp_dir().join("foo2.txt");
555
556        let mut file = OpenOptions::new()
557            .create(true)
558            .write(true)
559            .truncate(true)
560            .open(&in_name)
561            .await?
562            .compat();
563
564        file.write_all(b"SECRET DATA").await?;
565        file.close().await?;
566
567        let mut in_file = File::open(&in_name).await?.compat();
568        let mut out_file = OpenOptions::new()
569            .create(true)
570            .write(true)
571            .truncate(true)
572            .open(&out_name)
573            .await?
574            .compat();
575
576        Sealer::<_, SealerStreamConfig>::new(&setup.ibe_pk, &setup.policy, signing_key, &mut rng)?
577            .seal(&mut in_file, &mut out_file)
578            .await?;
579
580        in_file.close().await?;
581        out_file.close().await?;
582
583        let mut out_file = File::open(&out_name).await?.compat();
584        let mut orig_file = OpenOptions::new()
585            .create(true)
586            .write(true)
587            .truncate(true)
588            .open(&orig_name)
589            .await?
590            .compat();
591
592        let id = "Bob";
593        let usk = &setup.usks[2];
594
595        Unsealer::<_, UnsealerStreamConfig>::new(&mut out_file, &setup.ibs_pk)
596            .await?
597            .unseal(id, usk, &mut orig_file)
598            .await?;
599
600        out_file.close().await?;
601        orig_file.close().await?;
602
603        let mut buf = String::new();
604        File::open(&orig_name)
605            .await?
606            .read_to_string(&mut buf)
607            .await?;
608
609        assert_eq!(buf.as_bytes(), b"SECRET DATA");
610
611        Ok(())
612    }
613
614    #[tokio::test]
615    async fn test_cursor() -> Result<(), Error> {
616        use futures::io::Cursor;
617
618        let mut rng = rand::thread_rng();
619        let setup = TestSetup::new(&mut rng);
620
621        let signing_key = &setup.signing_keys[0];
622
623        let mut input = Cursor::new(b"SECRET DATA");
624        let mut encrypted = Vec::new();
625
626        Sealer::<_, SealerStreamConfig>::new(&setup.ibe_pk, &setup.policy, signing_key, &mut rng)?
627            .seal(&mut input, &mut encrypted)
628            .await?;
629
630        let mut original = Vec::new();
631        let id = "Bob";
632        let usk = &setup.usks[2];
633        Unsealer::<_, UnsealerStreamConfig>::new(&mut Cursor::new(encrypted), &setup.ibs_pk)
634            .await?
635            .unseal(id, usk, &mut original)
636            .await?;
637
638        assert_eq!(input.into_inner().to_vec(), original);
639        Ok(())
640    }
641
642    #[tokio::test]
643    async fn test_stream_unseal_rejects_empty_input() {
644        use futures::io::Cursor;
645
646        let mut rng = rand::thread_rng();
647        let setup = TestSetup::new(&mut rng);
648
649        // Empty reader must not panic — the preamble read should fail cleanly.
650        let mut input = Cursor::new(Vec::<u8>::new());
651        let res = Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk).await;
652        assert!(matches!(res, Err(Error::NotPostGuard)));
653    }
654
655    #[tokio::test]
656    async fn test_stream_unseal_rejects_truncated_preamble() {
657        use futures::io::Cursor;
658
659        let mut rng = rand::thread_rng();
660        let setup = TestSetup::new(&mut rng);
661
662        // A few bytes — enough to look like the start of a preamble but
663        // not enough to finish reading one.
664        let mut input = Cursor::new(vec![0u8; PREAMBLE_SIZE - 1]);
665        let res = Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk).await;
666        assert!(matches!(res, Err(Error::NotPostGuard)));
667    }
668
669    #[tokio::test]
670    async fn test_stream_unseal_rejects_garbage_input() {
671        use futures::io::Cursor;
672
673        let mut rng = rand::thread_rng();
674        let setup = TestSetup::new(&mut rng);
675
676        // 4 KiB of zeros — the prelude check rejects this before any unchecked
677        // length-prefixed read can panic.
678        let mut input = Cursor::new(vec![0u8; 4096]);
679        let res = Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk).await;
680        assert!(res.is_err());
681    }
682
683    #[tokio::test]
684    async fn test_stream_unseal_rejects_flipped_prelude() {
685        use futures::io::Cursor;
686
687        let mut rng = rand::thread_rng();
688        let setup = TestSetup::new(&mut rng);
689        let mut ct = seal_helper(&setup, b"SECRET DATA");
690
691        // Flip a byte in the prelude — must be rejected as NotPostGuard,
692        // never panic.
693        ct[0] = ct[0].wrapping_add(1);
694
695        let mut input = Cursor::new(ct);
696        let res = Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk).await;
697        assert!(matches!(res, Err(Error::NotPostGuard)));
698    }
699}