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: crate::client::canonical_signing_key(pub_sign_key),
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_2.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 pub_pol_bytes = crate::bincode_compat::serialize(&self.pub_sign_key.policy)?;
118        let signing_key = self.priv_sign_key.unwrap_or(self.pub_sign_key);
119
120        let pol_bytes = crate::bincode_compat::serialize(&signing_key.policy)?;
121        let pol_len = pol_bytes.len() + pub_pol_bytes.len();
122
123        if pol_len + POL_SIZE_SIZE > self.config.segment_size as usize {
124            return Err(Error::ConstraintViolation);
125        }
126
127        let mut buf = vec![0; self.config.segment_size as usize + TAG_SIZE];
128
129        buf[..POL_SIZE_SIZE].copy_from_slice(&u32::try_from(pol_len)?.to_be_bytes());
130        buf[POL_SIZE_SIZE..POL_SIZE_SIZE + pol_bytes.len()].copy_from_slice(&pol_bytes);
131        buf[POL_SIZE_SIZE + pol_bytes.len()..POL_SIZE_SIZE + pol_len]
132            .copy_from_slice(&pub_pol_bytes);
133
134        let mut buf_tail = POL_SIZE_SIZE + pol_len;
135        let mut start = buf_tail;
136
137        // First segment: DEM.K (pol_len || pol || pub_pol || m_0 || sig_0 )
138        // Other segments: DEM.K (m_i || sig_0)
139        //
140        // `pub_pol` is the sender's public signing policy, the same value that
141        // goes into the header signature outside the AEAD. It sits inside the
142        // length-delimited policy region because that is the one place a reader
143        // skips wholesale: `pol_len` covers both policies and the reader drains
144        // the region before splitting the segment at `len - SIG_BYTES`. Anything
145        // appended after `sig_0` would be read as message or signature bytes.
146        // The message signature covers the message only — the region is excluded
147        // from it here and stays excluded.
148
149        let mut counter: u32 = 0;
150
151        loop {
152            let read = r
153                .read(&mut buf[buf_tail..self.config.segment_size as usize])
154                .await?;
155            buf_tail += read;
156
157            if buf_tail == self.config.segment_size as usize {
158                buf.truncate(buf_tail);
159
160                signer.update(&buf[start..]);
161                let sig = signer
162                    .clone()
163                    .chain(counter.to_be_bytes())
164                    .chain([0x00])
165                    .sign(&signing_key.key.0, self.rng);
166                crate::bincode_compat::serialize_into_vec(&mut buf, &sig)?;
167
168                enc.encrypt_next_in_place(b"", &mut buf)?;
169
170                w.write_all(&buf).await?;
171
172                buf_tail = 0;
173                start = 0;
174                counter = counter.checked_add(1).unwrap(); // cannot fail, otherwise
175                                                           // encrypt_next_in_place would have
176                                                           // failed too.                                                // encrypt_next_in_place not failing
177            } else if read == 0 {
178                buf.truncate(buf_tail);
179
180                signer.update(&buf[start..]);
181                let sig_final = signer
182                    .chain(counter.to_be_bytes())
183                    .chain([0x01])
184                    .sign(&signing_key.key.0, self.rng);
185                crate::bincode_compat::serialize_into_vec(&mut buf, &sig_final)?;
186
187                enc.encrypt_last_in_place(b"", &mut buf)?;
188
189                w.write_all(&buf).await?;
190                break;
191            }
192        }
193
194        w.flush().await?;
195        w.close().await?;
196
197        Ok(())
198    }
199}
200
201impl<R> Unsealer<R, UnsealerStreamConfig>
202where
203    R: AsyncRead + Unpin,
204{
205    /// Create a new [`Unsealer`] that starts reading from an [`AsyncRead`].
206    ///
207    /// Errors if the bytestream is not a legitimate PostGuard bytestream.
208    pub async fn new(mut r: R, pk: &VerifyingKey) -> Result<Self, Error> {
209        let mut preamble = [0u8; PREAMBLE_SIZE];
210        r.read_exact(&mut preamble)
211            .map_err(|_e| Error::NotPostGuard)
212            .await?;
213
214        let (version, header_len) = preamble_checked(&preamble)?;
215        let mut header_raw = Vec::with_capacity(header_len);
216
217        // Limit reader to not read past header
218        let mut r = r.take(header_len as u64);
219
220        r.read_to_end(&mut header_raw)
221            .map_err(|_e| Error::ConstraintViolation)
222            .await?;
223
224        let mut r = r.into_inner();
225
226        let mut header_sig_len_bytes = [0u8; SIG_SIZE_SIZE];
227        r.read_exact(&mut header_sig_len_bytes)
228            .map_err(|_e| Error::FormatViolation("no header signature length".to_string()))
229            .await?;
230        let header_sig_len = u32::from_be_bytes(header_sig_len_bytes) as usize;
231
232        // Bound the length prefix to a sane maximum before it sizes an
233        // allocation, mirroring the MAX_HEADER_SIZE check in preamble_checked.
234        if header_sig_len > MAX_SIG_SIZE {
235            return Err(Error::ConstraintViolation);
236        }
237
238        let mut header_sig_raw = Vec::with_capacity(header_sig_len);
239        let mut r = r.take(header_sig_len as u64);
240
241        r.read_to_end(&mut header_sig_raw).await?;
242
243        let h_sig_ext: SignatureExt = crate::bincode_compat::deserialize(&header_sig_raw)?;
244
245        let verifier = Verifier::default().chain(&header_raw);
246        let pub_id = h_sig_ext.pol.derive_ibs()?;
247
248        if !verifier.clone().verify(&pk.0, &h_sig_ext.sig, &pub_id) {
249            return Err(Error::IncorrectSignature);
250        }
251
252        let header: Header = crate::bincode_compat::deserialize(&header_raw)?;
253        let (segment_size, _) = stream_mode_checked(&header)?;
254
255        Ok(Unsealer {
256            version,
257            header,
258            pub_id: h_sig_ext.pol,
259            config: UnsealerStreamConfig { segment_size },
260            r: r.into_inner(), // This (new) reader is locked to the payload.
261            verifier,
262            vk: pk.clone(),
263        })
264    }
265
266    /// Unseal the remaining data (which is now only payload) into an [`AsyncWrite`].
267    pub async fn unseal<W: AsyncWrite + Unpin>(
268        mut self,
269        ident: &str,
270        usk: &UserSecretKey<CGWKV>,
271        mut w: W,
272    ) -> Result<VerificationResult, Error> {
273        let rec_info = self
274            .header
275            .recipients
276            .get(ident)
277            .ok_or_else(|| Error::UnknownIdentifier(ident.to_string()))?;
278
279        let ss = rec_info.decaps(usk)?;
280        let key = &ss.0[..KEY_SIZE];
281        let aead = Aes128Gcm::new_from_slice(key)?;
282
283        let Algorithm::Aes128Gcm(iv) = self.header.algo;
284        let nonce = &iv.0[..STREAM_NONCE_SIZE];
285
286        let mut dec = DecryptorBE32::from_aead(aead, nonce.into());
287
288        let bufsize: usize = self.config.segment_size as usize + SIG_BYTES + TAG_SIZE;
289        let mut buf = vec![0u8; bufsize];
290        let mut buf_tail = 0;
291        let mut counter: u32 = 0;
292        let mut pol_id: Option<(Policy, Identity)> = None;
293
294        fn extract_policy(
295            buf: &mut Vec<u8>,
296            pub_id: &Policy,
297        ) -> Result<Option<(Policy, Identity)>, Error> {
298            if buf.len() < POL_SIZE_SIZE {
299                return Err(Error::FormatViolation(alloc::string::String::from(
300                    "policy length",
301                )));
302            }
303            let pol_len = u32::from_be_bytes(buf[..POL_SIZE_SIZE].try_into()?) as usize;
304            let pol_end = POL_SIZE_SIZE.checked_add(pol_len).ok_or_else(|| {
305                Error::FormatViolation(alloc::string::String::from("policy length overflow"))
306            })?;
307            if buf.len() < pol_end {
308                return Err(Error::FormatViolation(alloc::string::String::from(
309                    "policy truncated",
310                )));
311            }
312            let pol_bytes = &buf[POL_SIZE_SIZE..pol_end];
313            let (pol, read): (Policy, usize) =
314                crate::bincode_compat::deserialize_with_len(pol_bytes)?;
315
316            // The rest of the region, if the sealer wrote one, is a copy of the
317            // sender's public signing policy. The header signature outside the
318            // AEAD claims a policy too; if they disagree, that block was
319            // swapped. An exhausted region means the sealer predates the copy.
320            // Both readings are authenticated against the DEM key and reach no
321            // further, so the exhausted case is not the safe half of the two —
322            // see the note on `MessageAndSignature` in `client/rust/mod.rs`.
323            if read < pol_bytes.len() {
324                let sealed_pub_pol: Policy =
325                    crate::bincode_compat::deserialize(&pol_bytes[read..])?;
326
327                if &sealed_pub_pol != pub_id {
328                    return Err(Error::IncorrectSignature);
329                }
330            }
331
332            let id = pol.derive_ibs()?;
333
334            buf.drain(..pol_end);
335
336            Ok(Some((pol, id)))
337        }
338
339        fn verify_segment<'a>(
340            seg: &'a [u8],
341            verifier: &mut Verifier,
342            vk: &VerifyingKey,
343            id: &Identity,
344            counter: u32,
345            is_last: bool,
346        ) -> Result<&'a [u8], Error> {
347            if seg.len() < SIG_BYTES {
348                return Err(Error::FormatViolation(alloc::string::String::from(
349                    "segment too short for signature",
350                )));
351            }
352
353            let (m, sig_bytes) = seg.split_at(seg.len() - SIG_BYTES);
354            let sig: Signature = crate::bincode_compat::deserialize(sig_bytes)?;
355            verifier.update(m);
356
357            if !verifier
358                .clone()
359                .chain(counter.to_be_bytes())
360                .chain([is_last as u8])
361                .verify(&vk.0, &sig, id)
362            {
363                return Err(Error::IncorrectSignature);
364            }
365
366            Ok(m)
367        }
368
369        loop {
370            let read = self.r.read(&mut buf[buf_tail..bufsize]).await?;
371            buf_tail += read;
372
373            if buf_tail == bufsize {
374                dec.decrypt_next_in_place(b"", &mut buf)?;
375
376                if counter == 0 {
377                    pol_id = extract_policy(&mut buf, &self.pub_id)?;
378                }
379
380                let m = verify_segment(
381                    &buf,
382                    &mut self.verifier,
383                    &self.vk,
384                    &pol_id.as_ref().unwrap().1,
385                    counter,
386                    false,
387                )?;
388
389                w.write_all(m).await?;
390
391                buf_tail = 0;
392                buf.resize(bufsize, 0);
393                counter += 1;
394            } else if read == 0 {
395                buf.truncate(buf_tail);
396                dec.decrypt_last_in_place(b"", &mut buf)?;
397
398                if counter == 0 {
399                    pol_id = extract_policy(&mut buf, &self.pub_id)?;
400                }
401
402                let m = verify_segment(
403                    &buf,
404                    &mut self.verifier,
405                    &self.vk,
406                    &pol_id.as_ref().unwrap().1,
407                    counter,
408                    true,
409                )?;
410
411                w.write_all(m).await?;
412
413                break;
414            }
415        }
416
417        w.close().await?;
418
419        let private_id = pol_id.unwrap().0;
420        let private = if self.pub_id == private_id {
421            None
422        } else {
423            Some(private_id)
424        };
425
426        Ok(VerificationResult {
427            public: self.pub_id,
428            private,
429        })
430    }
431}
432
433#[cfg(test)]
434mod tests {
435    use super::{Sealer, SealerStreamConfig, Unsealer, UnsealerStreamConfig};
436    use crate::client::VerificationResult;
437    use crate::error::Error;
438    use crate::test::TestSetup;
439    use crate::{PREAMBLE_SIZE, SYMMETRIC_CRYPTO_DEFAULT_CHUNK, TAG_SIZE};
440    use aead::stream::{DecryptorBE32, EncryptorBE32};
441    use aead::KeyInit;
442    use aes_gcm::Aes128Gcm;
443    use alloc::string::String;
444    use alloc::vec::Vec;
445    use futures::{executor::block_on, io::AllowStdIo};
446    use ibe::kem::cgw_kv::CGWKV;
447    use rand::{thread_rng, Rng, RngCore};
448    use std::io::Cursor;
449    use tokio::io::AsyncReadExt;
450
451    const LENGTHS: &[u32] = &[
452        1,
453        512,
454        SYMMETRIC_CRYPTO_DEFAULT_CHUNK - 3,
455        SYMMETRIC_CRYPTO_DEFAULT_CHUNK,
456        SYMMETRIC_CRYPTO_DEFAULT_CHUNK + 3,
457        3 * SYMMETRIC_CRYPTO_DEFAULT_CHUNK,
458        3 * SYMMETRIC_CRYPTO_DEFAULT_CHUNK + 16,
459        3 * SYMMETRIC_CRYPTO_DEFAULT_CHUNK - 17,
460    ];
461
462    fn seal_helper(setup: &TestSetup, plain: &[u8]) -> Vec<u8> {
463        let mut rng = rand::thread_rng();
464
465        let mut input = AllowStdIo::new(Cursor::new(plain));
466        let mut output = AllowStdIo::new(Vec::new());
467
468        let signing_key = &setup.signing_keys[0];
469
470        block_on(async {
471            Sealer::<_, SealerStreamConfig>::new(
472                &setup.ibe_pk,
473                &setup.policy,
474                signing_key,
475                &mut rng,
476            )
477            .unwrap()
478            .seal(&mut input, &mut output)
479            .await
480            .unwrap();
481        });
482
483        output.into_inner()
484    }
485
486    fn unseal_helper(setup: &TestSetup, ct: &[u8]) -> (Vec<u8>, VerificationResult) {
487        let mut input = AllowStdIo::new(Cursor::new(ct));
488        let mut output = AllowStdIo::new(Vec::new());
489
490        // sometimes decrypt as Bob, sometimes decrypt as Charlie
491        let (id, usk_id) = if thread_rng().gen::<bool>() {
492            ("Bob", setup.usks[2].clone())
493        } else {
494            ("Charlie", setup.usks[3].clone())
495        };
496
497        let vr = block_on(async {
498            let unsealer = Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk)
499                .await
500                .unwrap();
501
502            // Normally, a user would need to retrieve a usk here via the PKG,
503            // but in this case we own the master key pair.
504            unsealer.unseal(id, &usk_id, &mut output).await.unwrap()
505        });
506
507        (output.into_inner(), vr)
508    }
509
510    fn seal_and_unseal(setup: &TestSetup, plain: Vec<u8>) {
511        let ct = seal_helper(setup, &plain);
512        let (plain2, vr) = unseal_helper(setup, &ct);
513
514        assert_eq!(&plain, &plain2);
515        assert_eq!(&vr.public, &setup.signing_keys[0].policy);
516        assert_eq!(vr.private, None);
517    }
518
519    fn rand_vec(length: usize) -> Vec<u8> {
520        let mut vec = vec![0u8; length];
521        rand::thread_rng().fill_bytes(&mut vec);
522        vec
523    }
524
525    #[test]
526    fn test_reflection_seal_unsealer() {
527        let mut rng = rand::thread_rng();
528        let setup = TestSetup::new(&mut rng);
529
530        for l in LENGTHS {
531            seal_and_unseal(&setup, rand_vec(*l as usize));
532        }
533    }
534
535    #[test]
536    #[should_panic]
537    fn test_corrupt_header() {
538        let mut rng = rand::thread_rng();
539        let setup = TestSetup::new(&mut rng);
540
541        let plain = rand_vec(100);
542        let mut ct = seal_helper(&setup, &plain);
543
544        // Flip a byte that is guaranteed to be in the header.
545        ct[PREAMBLE_SIZE + 2] = !ct[PREAMBLE_SIZE + 2];
546
547        // This should panic, because of the header signature.
548        let _plain2 = unseal_helper(&setup, &ct);
549    }
550
551    #[test]
552    #[should_panic]
553    fn test_corrupt_payload() {
554        let mut rng = rand::thread_rng();
555        let setup = TestSetup::new(&mut rng);
556
557        let plain = rand_vec(100);
558        let mut ct = seal_helper(&setup, &plain);
559
560        // Flip a byte that is guaranteed to be in the encrypted payload.
561        let ct_len = ct.len();
562        ct[ct_len - TAG_SIZE - 5] = !ct[ct_len - TAG_SIZE - 5];
563
564        // This should panic, because of the AEAD.
565        let _plain2 = unseal_helper(&setup, &ct);
566    }
567
568    #[test]
569    #[should_panic]
570    fn test_corrupt_tag() {
571        let mut rng = rand::thread_rng();
572        let setup = TestSetup::new(&mut rng);
573
574        let plain = rand_vec(100);
575        let mut ct = seal_helper(&setup, &plain);
576
577        let len = ct.len();
578        ct[len - 5] = !ct[len - 5];
579
580        // This should panic as well.
581        let _plain2 = unseal_helper(&setup, &ct);
582    }
583
584    #[tokio::test]
585    async fn test_tokio_file() -> Result<(), Error> {
586        use futures::AsyncWriteExt;
587        use tokio::fs::{File, OpenOptions};
588        use tokio_util::compat::TokioAsyncReadCompatExt;
589
590        let mut rng = rand::thread_rng();
591        let setup = TestSetup::new(&mut rng);
592
593        let signing_key = &setup.signing_keys[0];
594
595        let in_name = std::env::temp_dir().join("foo.txt");
596        let out_name = std::env::temp_dir().join("foo.enc");
597        let orig_name = std::env::temp_dir().join("foo2.txt");
598
599        let mut file = OpenOptions::new()
600            .create(true)
601            .write(true)
602            .truncate(true)
603            .open(&in_name)
604            .await?
605            .compat();
606
607        file.write_all(b"SECRET DATA").await?;
608        file.close().await?;
609
610        let mut in_file = File::open(&in_name).await?.compat();
611        let mut out_file = OpenOptions::new()
612            .create(true)
613            .write(true)
614            .truncate(true)
615            .open(&out_name)
616            .await?
617            .compat();
618
619        Sealer::<_, SealerStreamConfig>::new(&setup.ibe_pk, &setup.policy, signing_key, &mut rng)?
620            .seal(&mut in_file, &mut out_file)
621            .await?;
622
623        in_file.close().await?;
624        out_file.close().await?;
625
626        let mut out_file = File::open(&out_name).await?.compat();
627        let mut orig_file = OpenOptions::new()
628            .create(true)
629            .write(true)
630            .truncate(true)
631            .open(&orig_name)
632            .await?
633            .compat();
634
635        let id = "Bob";
636        let usk = &setup.usks[2];
637
638        Unsealer::<_, UnsealerStreamConfig>::new(&mut out_file, &setup.ibs_pk)
639            .await?
640            .unseal(id, usk, &mut orig_file)
641            .await?;
642
643        out_file.close().await?;
644        orig_file.close().await?;
645
646        let mut buf = String::new();
647        File::open(&orig_name)
648            .await?
649            .read_to_string(&mut buf)
650            .await?;
651
652        assert_eq!(buf.as_bytes(), b"SECRET DATA");
653
654        Ok(())
655    }
656
657    #[tokio::test]
658    async fn test_cursor() -> Result<(), Error> {
659        use futures::io::Cursor;
660
661        let mut rng = rand::thread_rng();
662        let setup = TestSetup::new(&mut rng);
663
664        let signing_key = &setup.signing_keys[0];
665
666        let mut input = Cursor::new(b"SECRET DATA");
667        let mut encrypted = Vec::new();
668
669        Sealer::<_, SealerStreamConfig>::new(&setup.ibe_pk, &setup.policy, signing_key, &mut rng)?
670            .seal(&mut input, &mut encrypted)
671            .await?;
672
673        let mut original = Vec::new();
674        let id = "Bob";
675        let usk = &setup.usks[2];
676        Unsealer::<_, UnsealerStreamConfig>::new(&mut Cursor::new(encrypted), &setup.ibs_pk)
677            .await?
678            .unseal(id, usk, &mut original)
679            .await?;
680
681        assert_eq!(input.into_inner().to_vec(), original);
682        Ok(())
683    }
684
685    #[tokio::test]
686    async fn test_stream_unseal_rejects_empty_input() {
687        use futures::io::Cursor;
688
689        let mut rng = rand::thread_rng();
690        let setup = TestSetup::new(&mut rng);
691
692        // Empty reader must not panic — the preamble read should fail cleanly.
693        let mut input = Cursor::new(Vec::<u8>::new());
694        let res = Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk).await;
695        assert!(matches!(res, Err(Error::NotPostGuard)));
696    }
697
698    #[tokio::test]
699    async fn test_stream_unseal_rejects_truncated_preamble() {
700        use futures::io::Cursor;
701
702        let mut rng = rand::thread_rng();
703        let setup = TestSetup::new(&mut rng);
704
705        // A few bytes — enough to look like the start of a preamble but
706        // not enough to finish reading one.
707        let mut input = Cursor::new(vec![0u8; PREAMBLE_SIZE - 1]);
708        let res = Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk).await;
709        assert!(matches!(res, Err(Error::NotPostGuard)));
710    }
711
712    #[tokio::test]
713    async fn test_stream_unseal_rejects_garbage_input() {
714        use futures::io::Cursor;
715
716        let mut rng = rand::thread_rng();
717        let setup = TestSetup::new(&mut rng);
718
719        // 4 KiB of zeros — the prelude check rejects this before any unchecked
720        // length-prefixed read can panic.
721        let mut input = Cursor::new(vec![0u8; 4096]);
722        let res = Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk).await;
723        assert!(res.is_err());
724    }
725
726    #[tokio::test]
727    async fn test_stream_unseal_rejects_flipped_prelude() {
728        use futures::io::Cursor;
729
730        let mut rng = rand::thread_rng();
731        let setup = TestSetup::new(&mut rng);
732        let mut ct = seal_helper(&setup, b"SECRET DATA");
733
734        // Flip a byte in the prelude — must be rejected as NotPostGuard,
735        // never panic.
736        ct[0] = ct[0].wrapping_add(1);
737
738        let mut input = Cursor::new(ct);
739        let res = Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk).await;
740        assert!(matches!(res, Err(Error::NotPostGuard)));
741    }
742
743    /// Splits a sealed streaming container into the header bytes, the header
744    /// signature block and the payload.
745    fn split_container(ct: &[u8]) -> (&[u8], &[u8], &[u8]) {
746        use crate::util::preamble_checked;
747        use crate::SIG_SIZE_SIZE;
748
749        let (_, header_len) =
750            preamble_checked(&ct[..PREAMBLE_SIZE]).expect("preamble should parse");
751        let sig_len_at = PREAMBLE_SIZE + header_len;
752        let sig_len = u32::from_be_bytes(
753            ct[sig_len_at..sig_len_at + SIG_SIZE_SIZE]
754                .try_into()
755                .unwrap(),
756        ) as usize;
757        let sig_at = sig_len_at + SIG_SIZE_SIZE;
758
759        (
760            &ct[PREAMBLE_SIZE..sig_len_at],
761            &ct[sig_at..sig_at + sig_len],
762            &ct[sig_at + sig_len..],
763        )
764    }
765
766    /// The attack: keep the preamble, header and payload byte for byte, and
767    /// replace only the header signature block with one made over the same
768    /// header bytes by another signing key, carrying that key's policy.
769    fn swap_header_signature(
770        ct: &[u8],
771        attacker: &crate::artifacts::SigningKeyExt,
772        rng: &mut (impl rand::RngCore + rand::CryptoRng),
773    ) -> Vec<u8> {
774        use crate::client::header::SignatureExt;
775        use ibs::gg::Signer;
776
777        let (header_bytes, _, payload) = split_container(ct);
778
779        let h_sig_ext = SignatureExt {
780            sig: Signer::default()
781                .chain(header_bytes)
782                .sign(&attacker.key.0, rng),
783            pol: attacker.policy.clone(),
784        };
785        let h_sig_ext_bytes = crate::bincode_compat::serialize(&h_sig_ext).unwrap();
786
787        let mut out = ct[..PREAMBLE_SIZE + header_bytes.len()].to_vec();
788        out.extend_from_slice(&(h_sig_ext_bytes.len() as u32).to_be_bytes());
789        out.extend_from_slice(&h_sig_ext_bytes);
790        out.extend_from_slice(payload);
791
792        out
793    }
794
795    /// A container sealed before the policy region carried the public signing
796    /// policy: same bytes, but with the region narrowed back to the signing
797    /// policy alone and the single segment re-encrypted.
798    fn strip_pub_pol(
799        ct: &[u8],
800        ident: &str,
801        usk: &crate::artifacts::UserSecretKey<CGWKV>,
802    ) -> Vec<u8> {
803        use crate::client::{Algorithm, Header};
804        use crate::identity::Policy;
805        use crate::{KEY_SIZE, POL_SIZE_SIZE, STREAM_NONCE_SIZE};
806
807        let (header_bytes, _, payload) = split_container(ct);
808        let header: Header = crate::bincode_compat::deserialize(header_bytes).unwrap();
809        let ss = header.recipients.get(ident).unwrap().decaps(usk).unwrap();
810
811        let Algorithm::Aes128Gcm(iv) = header.algo;
812        let aead = Aes128Gcm::new_from_slice(&ss.0[..KEY_SIZE]).unwrap();
813        let nonce = &iv.0[..STREAM_NONCE_SIZE];
814
815        // The helper seals a message that fits in one segment, so the payload
816        // is a single final segment.
817        let mut plain = payload.to_vec();
818        DecryptorBE32::from_aead(aead.clone(), nonce.into())
819            .decrypt_last_in_place(b"", &mut plain)
820            .unwrap();
821
822        let pol_len = u32::from_be_bytes(plain[..POL_SIZE_SIZE].try_into().unwrap()) as usize;
823        let region = &plain[POL_SIZE_SIZE..POL_SIZE_SIZE + pol_len];
824        let (_, read): (Policy, usize) =
825            crate::bincode_compat::deserialize_with_len(region).unwrap();
826        assert!(
827            read < pol_len,
828            "the sealer wrote no public policy into the region — nothing to strip"
829        );
830
831        let mut legacy = (read as u32).to_be_bytes().to_vec();
832        legacy.extend_from_slice(&region[..read]);
833        legacy.extend_from_slice(&plain[POL_SIZE_SIZE + pol_len..]);
834
835        EncryptorBE32::from_aead(aead, nonce.into())
836            .encrypt_last_in_place(b"", &mut legacy)
837            .unwrap();
838
839        let mut out = ct[..ct.len() - payload.len()].to_vec();
840        out.extend_from_slice(&legacy);
841
842        out
843    }
844
845    fn try_unseal(setup: &TestSetup, ct: &[u8]) -> Result<(Vec<u8>, VerificationResult), Error> {
846        let mut input = AllowStdIo::new(Cursor::new(ct));
847        let mut output = AllowStdIo::new(Vec::new());
848
849        let vr = block_on(async {
850            Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk)
851                .await?
852                .unseal("Bob", &setup.usks[2], &mut output)
853                .await
854        })?;
855
856        Ok((output.into_inner(), vr))
857    }
858
859    /// Replacing the header signature with one made by another signing key over
860    /// the same header bytes must be rejected: the copy of the public signing
861    /// policy inside the AEAD-protected policy region no longer matches the one
862    /// the block claims.
863    #[test]
864    fn test_stream_unseal_rejects_swapped_header_signature() {
865        let mut rng = rand::thread_rng();
866        let setup = TestSetup::new(&mut rng);
867        let ct = seal_helper(&setup, b"SECRET DATA");
868
869        // Charlie's name-only key — a key the PKG hands to whoever authenticates
870        // as Charlie, which is exactly what makes the swap cheap.
871        let swapped = swap_header_signature(&ct, &setup.signing_keys[4], &mut rng);
872
873        block_on(async {
874            let mut input = AllowStdIo::new(Cursor::new(&swapped));
875            let unsealer = Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk)
876                .await
877                .expect("the swapped header signature still verifies — that is the attack");
878            assert_eq!(
879                unsealer.pub_id, setup.policies[4],
880                "the container now claims the attacker as public sender"
881            );
882        });
883
884        match try_unseal(&setup, &swapped) {
885            Err(Error::IncorrectSignature) => {}
886            other => panic!(
887                "expected IncorrectSignature, got {:?}",
888                other.map(|(m, vr)| (m.len(), vr))
889            ),
890        }
891    }
892
893    /// A container sealed by a pg-core that predates the copy has a policy
894    /// region holding the signing policy and nothing else. It must still
895    /// unseal, and report the same sender it always did.
896    #[test]
897    fn test_stream_unseal_accepts_container_without_public_policy() {
898        let mut rng = rand::thread_rng();
899        let setup = TestSetup::new(&mut rng);
900        let ct = seal_helper(&setup, b"SECRET DATA");
901        let legacy = strip_pub_pol(&ct, "Bob", &setup.usks[2]);
902
903        let (plain, vr) = try_unseal(&setup, &legacy)
904            .expect("a container without the public policy must still open");
905
906        assert_eq!(&plain, b"SECRET DATA");
907        assert_eq!(&vr.public, &setup.signing_keys[0].policy);
908        assert_eq!(vr.private, None);
909    }
910
911    #[tokio::test]
912    async fn test_stream_unseal_rejects_oversized_sig_len() {
913        use crate::util::preamble_checked;
914        use crate::{MAX_SIG_SIZE, SIG_SIZE_SIZE};
915        use futures::io::Cursor;
916
917        let mut rng = rand::thread_rng();
918        let setup = TestSetup::new(&mut rng);
919        let mut ct = seal_helper(&setup, b"SECRET DATA");
920
921        // The header-signature length prefix sits right after the header. Overwrite
922        // it with a value larger than MAX_SIG_SIZE and assert the unsealer rejects it
923        // with a ConstraintViolation before allocating a buffer of that size.
924        let (_, header_len) =
925            preamble_checked(&ct[..PREAMBLE_SIZE]).expect("preamble should parse");
926        let sig_len_off = PREAMBLE_SIZE + header_len;
927        assert!(sig_len_off + SIG_SIZE_SIZE <= ct.len());
928        assert!((MAX_SIG_SIZE as u64) < u32::MAX as u64);
929        ct[sig_len_off..sig_len_off + SIG_SIZE_SIZE].copy_from_slice(&u32::MAX.to_be_bytes());
930
931        let mut input = Cursor::new(ct);
932        let res = Unsealer::<_, UnsealerStreamConfig>::new(&mut input, &setup.ibs_pk).await;
933        assert!(
934            matches!(res, Err(Error::ConstraintViolation)),
935            "expected ConstraintViolation, got {res:?}"
936        );
937    }
938}