Skip to main content

pg_core/client/web/
stream.rs

1//! Streaming mode.
2
3use super::aesgcm::{decrypt, encrypt, get_key};
4
5use crate::artifacts::{PublicKey, SigningKeyExt, UserSecretKey, VerifyingKey};
6use crate::client::*;
7use crate::error::Error;
8use crate::identity::{EncryptionPolicy, Policy};
9use crate::util::preamble_checked;
10use ibs::gg::{Identity, Signature, Signer, Verifier, SIG_BYTES};
11
12use futures::{Sink, SinkExt, Stream, StreamExt};
13use ibe::kem::cgw_kv::CGWKV;
14use js_sys::Uint8Array;
15use rand::{CryptoRng, RngCore};
16use wasm_bindgen::{JsCast, JsValue};
17
18use alloc::string::ToString;
19use alloc::vec::Vec;
20
21/// Configures an [`Sealer`] to process a payload stream.
22#[derive(Debug)]
23pub struct StreamSealerConfig {
24    segment_size: u32,
25    key: [u8; KEY_SIZE],
26    nonce: [u8; STREAM_NONCE_SIZE],
27}
28
29/// Configures an [`Unsealer`] to process a payload stream.
30#[derive(Debug)]
31pub struct StreamUnsealerConfig {
32    segment_size: u32,
33    spill: Vec<u8>,
34}
35
36impl SealerConfig for StreamSealerConfig {}
37impl UnsealerConfig for StreamUnsealerConfig {}
38impl crate::client::sealed::SealerConfig for StreamSealerConfig {}
39impl crate::client::sealed::UnsealerConfig for StreamUnsealerConfig {}
40
41impl<'r, Rng: RngCore + CryptoRng> Sealer<'r, Rng, StreamSealerConfig> {
42    /// Construct a new [`Sealer`] that can process payloads streamingly.
43    pub fn new(
44        pk: &PublicKey<CGWKV>,
45        policies: &EncryptionPolicy,
46        pub_sign_key: &SigningKeyExt,
47        rng: &'r mut Rng,
48    ) -> Result<Self, Error> {
49        let (header, ss) = Header::new(pk, policies, rng)?;
50
51        let (segment_size, _) = stream_mode_checked(&header)?;
52        let Algorithm::Aes128Gcm(iv) = header.algo;
53
54        let mut key = [0u8; KEY_SIZE];
55        let mut nonce = [0u8; STREAM_NONCE_SIZE];
56
57        key.copy_from_slice(&ss.0[..KEY_SIZE]);
58        nonce.copy_from_slice(&iv.0[..STREAM_NONCE_SIZE]);
59
60        Ok(Sealer {
61            rng,
62            header,
63            pub_sign_key: crate::client::canonical_signing_key(pub_sign_key),
64            priv_sign_key: None,
65            config: StreamSealerConfig {
66                segment_size,
67                key,
68                nonce,
69            },
70        })
71    }
72
73    /// Seals payload data from a [`Stream`] of [`JsValue`] to a Sink of [`JsValue`].
74    ///
75    /// # Errors
76    ///
77    /// Make sure the [`JsValue`]s *can* dynamically be cast to [`Uint8Array`],
78    /// otherwise this operation *will* error.
79    pub async fn seal<R, W>(mut self, mut r: R, mut w: W) -> Result<(), Error>
80    where
81        R: Stream<Item = Result<JsValue, JsValue>> + Unpin,
82        W: Sink<JsValue, Error = JsValue> + Unpin,
83    {
84        let size_hint = r.size_hint();
85        let new_hint = (size_hint.0 as u64, size_hint.1.map(|x| x as u64));
86
87        self.header = self.header.with_mode(Mode::Streaming {
88            segment_size: self.config.segment_size,
89            size_hint: new_hint,
90        });
91
92        w.feed(Uint8Array::from(&PRELUDE[..]).into()).await?;
93        w.feed(Uint8Array::from(&VERSION_2.to_be_bytes()[..]).into())
94            .await?;
95
96        let header_vec = crate::bincode_compat::serialize(&self.header)?;
97
98        w.feed(Uint8Array::from(&(header_vec.len() as u32).to_be_bytes()[..]).into())
99            .await?;
100
101        w.feed(Uint8Array::from(&header_vec[..]).into()).await?;
102
103        let mut signer = Signer::default().chain(&header_vec);
104        let header_sig = signer.clone().sign(&self.pub_sign_key.key.0, self.rng);
105        let header_sig_ext = SignatureExt {
106            sig: header_sig,
107            pol: self.pub_sign_key.policy.clone(),
108        };
109        let header_sig_bytes = crate::bincode_compat::serialize(&header_sig_ext)?;
110
111        w.feed(Uint8Array::from(&(header_sig_bytes.len() as u32).to_be_bytes()[..]).into())
112            .await?;
113        w.feed(Uint8Array::from(&header_sig_bytes[..]).into())
114            .await?;
115
116        let key = get_key(&self.config.key).await?;
117
118        // Check for a private signing key, otherwise fall back to the public one.
119        let pub_pol_bytes = crate::bincode_compat::serialize(&self.pub_sign_key.policy)?;
120        let signing_key = self.priv_sign_key.unwrap_or(self.pub_sign_key);
121
122        let pol_bytes = crate::bincode_compat::serialize(&signing_key.policy)?;
123        let pol_len: u32 = (pol_bytes.len() + pub_pol_bytes.len()) as u32;
124
125        if pol_len + POL_SIZE_SIZE as u32 > self.config.segment_size {
126            return Err(Error::ConstraintViolation.into());
127        }
128
129        let buf = Uint8Array::new_with_length(self.config.segment_size + SIG_BYTES as u32);
130
131        // First segment: DEM.K (pol_len || pol || pub_pol || m_0 || sig_0 )
132        // Other segments: DEM.K (m_i || sig_0)
133        //
134        // `pub_pol` is the sender's public signing policy, the same value that
135        // goes into the header signature outside the AEAD. It sits inside the
136        // length-delimited policy region because that is the one place a reader
137        // skips wholesale: `pol_len` covers both policies and the reader drains
138        // the region before splitting the segment at `len - SIG_BYTES`. Anything
139        // appended after `sig_0` would be read as message or signature bytes.
140        // The message signature covers the message only — the region is excluded
141        // from it here and stays excluded.
142        buf.set(
143            &Uint8Array::from(&(pol_len as u32).to_be_bytes()[..]).into(),
144            0,
145        );
146        buf.set(
147            &Uint8Array::from(&pol_bytes[..]).into(),
148            POL_SIZE_SIZE as u32,
149        );
150        buf.set(
151            &Uint8Array::from(&pub_pol_bytes[..]).into(),
152            POL_SIZE_SIZE as u32 + pol_bytes.len() as u32,
153        );
154
155        let mut counter = 0u32;
156        let mut buf_tail: u32 = POL_SIZE_SIZE as u32 + pol_len;
157        let mut start: u32 = buf_tail;
158
159        while let Some(Ok(data)) = r.next().await {
160            let mut array: Uint8Array = data.dyn_into()?;
161
162            while array.byte_length() != 0 {
163                let len = array.byte_length();
164                let rem = self.config.segment_size - buf_tail;
165
166                if len < rem {
167                    buf.set(&array, buf_tail);
168                    array = Uint8Array::new_with_length(0);
169                    buf_tail += len;
170                } else {
171                    buf.set(&array.slice(0, rem), buf_tail);
172                    array = array.slice(rem, len);
173                    buf_tail += rem;
174
175                    signer.update(&buf.slice(start, buf_tail).to_vec());
176                    let sig = signer
177                        .clone()
178                        .chain(&counter.to_be_bytes())
179                        .chain(&[0x00])
180                        .sign(&signing_key.key.0, self.rng);
181                    let sig_bytes = crate::bincode_compat::serialize(&sig)?;
182
183                    buf.set(&Uint8Array::from(&sig_bytes[..]).into(), buf_tail);
184
185                    let ct = encrypt(
186                        &key,
187                        &aead_nonce(&self.config.nonce, counter, false),
188                        &Uint8Array::new_with_length(0),
189                        &buf,
190                    )
191                    .await?;
192
193                    w.feed(ct.into()).await?;
194
195                    counter = counter.checked_add(1).ok_or(Error::Symmetric)?;
196                    buf_tail = 0;
197                    start = 0;
198                }
199            }
200        }
201
202        signer.update(&buf.slice(start, buf_tail).to_vec());
203        let sig = signer
204            .chain(&counter.to_be_bytes())
205            .chain(&[0x01])
206            .sign(&signing_key.key.0, self.rng);
207        let sig_bytes = crate::bincode_compat::serialize(&sig)?;
208
209        buf.set(&Uint8Array::from(&sig_bytes[..]).into(), buf_tail);
210        buf_tail += SIG_BYTES as u32;
211
212        let final_ct = encrypt(
213            &key,
214            &aead_nonce(&self.config.nonce, counter, true),
215            &Uint8Array::new_with_length(0),
216            &buf.slice(0, buf_tail),
217        )
218        .await?;
219
220        w.feed(final_ct.into()).await?;
221
222        w.flush().await?;
223        w.close().await?;
224
225        Ok(())
226    }
227}
228
229// Nonce generation as defined in the STREAM construction.
230fn aead_nonce(nonce: &[u8], counter: u32, last_block: bool) -> [u8; IV_SIZE] {
231    let mut iv = [0u8; IV_SIZE];
232
233    iv[..STREAM_NONCE_SIZE].copy_from_slice(nonce);
234    iv[STREAM_NONCE_SIZE..IV_SIZE - 1].copy_from_slice(&counter.to_be_bytes());
235    iv[IV_SIZE - 1] = last_block as u8;
236
237    iv
238}
239
240async fn read_atleast<R>(mut r: R, buf: &mut [u8], spill: &mut Vec<u8>) -> Result<(), Error>
241where
242    R: Stream<Item = Result<JsValue, JsValue>> + Unpin,
243{
244    let buf_len = buf.len();
245    let spill_len = spill.len();
246
247    if buf_len <= spill_len {
248        buf.copy_from_slice(&spill[..buf_len]);
249        spill.drain(..buf_len);
250
251        Ok(())
252    } else {
253        buf[..spill_len].copy_from_slice(&spill);
254        let mut rem = buf_len - spill_len;
255        spill.clear();
256
257        while let Some(Ok(data)) = r.next().await {
258            let arr: Uint8Array = data.dyn_into()?;
259            let len = arr.byte_length();
260
261            if len as usize >= rem {
262                buf[buf_len - rem..].copy_from_slice(&arr.slice(0, rem as u32).to_vec()[..]);
263                spill.extend_from_slice(&arr.slice(rem as u32, len).to_vec()[..]);
264                rem = 0;
265                break;
266            } else {
267                buf[buf_len - rem..buf_len - rem + len as usize].copy_from_slice(&arr.to_vec()[..]);
268                rem -= len as usize;
269            }
270        }
271
272        if rem == 0 {
273            Ok(())
274        } else {
275            Err(Error::FormatViolation("unexpected EOF".to_string()).into())
276        }
277    }
278}
279
280// Note: It might be easier to work with R: ReadableStream.
281
282impl<R> Unsealer<R, StreamUnsealerConfig>
283where
284    R: Stream<Item = Result<JsValue, JsValue>> + Unpin,
285{
286    /// Create a new [`Unsealer`] that starts reading from a [`Stream<Item = Result<Uint8Array, JsValue>>`][Stream].
287    ///
288    /// # Errors
289    ///
290    /// Errors if the bytestream is not a legitimate PostGuard bytestream.
291    /// Also errors if the items (of type [`JsValue`]) cannot be cast into [`Uint8Array`].
292    pub async fn new(mut r: R, vk: &VerifyingKey) -> Result<Self, Error> {
293        let mut spill = Vec::new();
294
295        let mut preamble = [0u8; PREAMBLE_SIZE];
296        read_atleast(&mut r, &mut preamble, &mut spill).await?;
297        let (version, header_len) = preamble_checked(&preamble)?;
298
299        let mut header_raw = vec![0u8; header_len];
300        read_atleast(&mut r, &mut header_raw, &mut spill).await?;
301
302        let mut h_sig_len_bytes = [0u8; SIG_SIZE_SIZE];
303        read_atleast(&mut r, &mut h_sig_len_bytes, &mut spill).await?;
304        let header_sig_len = u32::from_be_bytes(h_sig_len_bytes) as usize;
305
306        // Bound the length prefix to a sane maximum before it sizes an
307        // allocation, mirroring the MAX_HEADER_SIZE check in preamble_checked.
308        if header_sig_len > MAX_SIG_SIZE {
309            return Err(Error::ConstraintViolation);
310        }
311
312        let mut header_sig_raw = vec![0u8; header_sig_len];
313        read_atleast(&mut r, &mut header_sig_raw, &mut spill).await?;
314        let h_sig_ext: SignatureExt = crate::bincode_compat::deserialize(&header_sig_raw)?;
315
316        let verifier = Verifier::default().chain(&header_raw);
317        let pub_id = h_sig_ext.pol.derive_ibs()?;
318
319        if !verifier.clone().verify(&vk.0, &h_sig_ext.sig, &pub_id) {
320            return Err(Error::IncorrectSignature.into());
321        }
322
323        let header: Header = crate::bincode_compat::deserialize(&header_raw)?;
324        let (segment_size, _) = stream_mode_checked(&header)?;
325
326        Ok(Unsealer {
327            version,
328            header,
329            pub_id: h_sig_ext.pol,
330            verifier,
331            vk: vk.clone(),
332            r,
333            config: StreamUnsealerConfig {
334                spill,
335                segment_size,
336            },
337        })
338    }
339
340    /// Unseal into an [`Sink<Uint8Array, Error = JsValue>`][Sink].
341    pub async fn unseal<W>(
342        &mut self,
343        ident: &str,
344        usk: &UserSecretKey<CGWKV>,
345        mut w: W,
346    ) -> Result<VerificationResult, Error>
347    where
348        W: Sink<JsValue, Error = JsValue> + Unpin,
349    {
350        let rec_info = self
351            .header
352            .recipients
353            .get(ident)
354            .ok_or_else(|| Error::UnknownIdentifier(ident.to_string()))?;
355
356        let ss = rec_info.decaps(usk)?;
357        let key = get_key(&ss.0[..KEY_SIZE]).await?;
358
359        let Algorithm::Aes128Gcm(iv) = self.header.algo;
360        let nonce = &iv.0[..STREAM_NONCE_SIZE];
361
362        let segment_size: u32 = self.config.segment_size + (SIG_BYTES + TAG_SIZE) as u32;
363
364        let buf = Uint8Array::new_with_length(segment_size);
365        let mut counter = 0u32;
366        let mut buf_tail = 0;
367        let mut pol_id: Option<(Policy, Identity)> = None;
368
369        fn extract_policy(
370            plain: Uint8Array,
371            pub_id: &Policy,
372        ) -> Result<(Option<(Policy, Identity)>, Uint8Array), Error> {
373            if plain.byte_length() < POL_SIZE_SIZE as u32 {
374                return Err(Error::FormatViolation(alloc::string::String::from(
375                    "policy length",
376                )));
377            }
378            let pol_len =
379                u32::from_be_bytes(plain.slice(0, POL_SIZE_SIZE as u32).to_vec()[..].try_into()?);
380            let pol_end = (POL_SIZE_SIZE as u32).checked_add(pol_len).ok_or_else(|| {
381                Error::FormatViolation(alloc::string::String::from("policy length overflow"))
382            })?;
383            if plain.byte_length() < pol_end {
384                return Err(Error::FormatViolation(alloc::string::String::from(
385                    "policy truncated",
386                )));
387            }
388            let pol_bytes = plain.slice(POL_SIZE_SIZE as u32, pol_end).to_vec();
389            let (pol, read): (Policy, usize) =
390                crate::bincode_compat::deserialize_with_len(&pol_bytes)?;
391
392            // The rest of the region, if the sealer wrote one, is a copy of the
393            // sender's public signing policy. The header signature outside the
394            // AEAD claims a policy too; if they disagree, that block was
395            // swapped. An exhausted region means the sealer predates the copy.
396            // Both readings are authenticated against the DEM key and reach no
397            // further, so the exhausted case is not the safe half of the two —
398            // see the note on `MessageAndSignature` in `client/web/mod.rs`.
399            if read < pol_bytes.len() {
400                let sealed_pub_pol: Policy =
401                    crate::bincode_compat::deserialize(&pol_bytes[read..])?;
402
403                if &sealed_pub_pol != pub_id {
404                    return Err(Error::IncorrectSignature.into());
405                }
406            }
407
408            let id = pol.derive_ibs()?;
409            let new_plain = plain.slice(pol_end, plain.byte_length());
410
411            Ok((Some((pol, id)), new_plain))
412        }
413
414        loop {
415            // First exhaust the spillage, then the rest of the stream.
416            let mut array: Uint8Array = if !self.config.spill.is_empty() {
417                let arr = Uint8Array::from(&self.config.spill[..]);
418                self.config.spill.clear();
419                arr
420            } else if let Some(Ok(data)) = self.r.next().await {
421                data.dyn_into()?
422            } else {
423                break;
424            };
425
426            while array.byte_length() != 0 {
427                let len = array.byte_length();
428                let rem = buf.byte_length() - buf_tail;
429
430                if len < rem {
431                    buf.set(&array, buf_tail);
432                    array = Uint8Array::new_with_length(0);
433                    buf_tail += len;
434                } else {
435                    buf.set(&array.slice(0, rem), buf_tail);
436                    array = array.slice(rem, len);
437
438                    let mut plain = decrypt(
439                        &key,
440                        &aead_nonce(nonce, counter, false),
441                        &Uint8Array::new_with_length(0),
442                        &buf,
443                    )
444                    .await?;
445
446                    if counter == 0 {
447                        (pol_id, plain) = extract_policy(plain, &self.pub_id)?;
448                    }
449
450                    if plain.byte_length() < SIG_BYTES as u32 {
451                        return Err(Error::FormatViolation(alloc::string::String::from(
452                            "segment too short for signature",
453                        ))
454                        .into());
455                    }
456
457                    let m = plain.slice(0, plain.byte_length() - SIG_BYTES as u32);
458                    let sig =
459                        plain.slice(plain.byte_length() - SIG_BYTES as u32, plain.byte_length());
460                    let sig: Signature = crate::bincode_compat::deserialize(&sig.to_vec())?;
461
462                    self.verifier.update(&m.to_vec());
463
464                    if !self
465                        .verifier
466                        .clone()
467                        .chain(&counter.to_be_bytes())
468                        .chain(&[0x00])
469                        .verify(&self.vk.0, &sig, &pol_id.as_ref().unwrap().1)
470                    {
471                        return Err(Error::IncorrectSignature.into());
472                    }
473
474                    w.feed(m.into()).await?;
475
476                    counter = counter.checked_add(1).ok_or(Error::Symmetric)?;
477                    buf_tail = 0;
478                }
479            }
480        }
481
482        let mut final_plain = decrypt(
483            &key,
484            &aead_nonce(nonce, counter, true),
485            &Uint8Array::new_with_length(0),
486            &buf.slice(0, buf_tail),
487        )
488        .await?;
489
490        if counter == 0 {
491            (pol_id, final_plain) = extract_policy(final_plain, &self.pub_id)?;
492        }
493
494        if final_plain.byte_length() < SIG_BYTES as u32 {
495            return Err(Error::FormatViolation(alloc::string::String::from(
496                "final segment too short for signature",
497            ))
498            .into());
499        }
500        let m = final_plain.slice(0, final_plain.byte_length() - SIG_BYTES as u32);
501        let sig = final_plain.slice(
502            final_plain.byte_length() - SIG_BYTES as u32,
503            final_plain.byte_length(),
504        );
505
506        let sig: Signature = crate::bincode_compat::deserialize(&sig.to_vec())?;
507        self.verifier.update(&m.to_vec());
508        if !self
509            .verifier
510            .clone()
511            .chain(&counter.to_be_bytes())
512            .chain(&[0x01])
513            .verify(&self.vk.0, &sig, &pol_id.as_ref().unwrap().1)
514        {
515            return Err(Error::IncorrectSignature.into());
516        }
517
518        w.feed(m.into()).await?;
519
520        w.flush().await?;
521        w.close().await?;
522
523        let private_id = pol_id.unwrap().0;
524        let private = if self.pub_id == private_id {
525            None
526        } else {
527            Some(private_id)
528        };
529
530        Ok(VerificationResult {
531            public: self.pub_id.clone(),
532            private,
533        })
534    }
535}