Skip to main content

hpke_http/
engine.rs

1//! Typed one-shot client and staged server transactions.
2
3use std::{
4    fmt,
5    time::{SystemTime, UNIX_EPOCH},
6};
7
8use hkdf::Hkdf;
9use hpke::{
10    Deserializable, Kem as KemTrait, OpModeR, OpModeS, PskBundle, Serializable,
11    aead::{AeadCtxR, AeadCtxS, AeadTag, ChaCha20Poly1305 as HpkeChaCha20Poly1305},
12    inout::InOutBuf,
13    kdf::HkdfSha256,
14    kem::X25519HkdfSha256,
15    setup_receiver, setup_sender_with_rng,
16};
17use rand_chacha::ChaCha20Rng;
18use rand_core::SeedableRng;
19use sha2::{Digest, Sha256};
20use zeroize::Zeroizing;
21
22use crate::{
23    CLOCK_SKEW_SECS, EntropySource, Error, HARD_MAX_ID_LEN, Limits, Method, REQUEST_LIFETIME_SECS,
24    SystemEntropy,
25    codec::{
26        ENC_LEN, REQUEST_FIXED_LEN, RESPONSE_NONCE_LEN, TAG_LEN, encode_request_header,
27        parse_stream_start, stream_start_length,
28    },
29    compression,
30    message::{self, HeaderField, Request, RequestHead, Response},
31    response::{ResponseMode, ResponseOpener, ResponseRecord, ResponseSealer},
32};
33
34type Kem = X25519HkdfSha256;
35type Kdf = HkdfSha256;
36type HpkeAead = HpkeChaCha20Poly1305;
37type RecipientContext = AeadCtxR<HpkeAead, Kdf, Kem>;
38type SenderContext = AeadCtxS<HpkeAead, Kdf, Kem>;
39
40const STREAM_REQUEST_INFO_LABEL: &[u8] = b"message/hpke-http request\0v3\0";
41const STREAM_REQUEST_AAD_LABEL: &[u8] = b"hpke-http/3 request record\0";
42const STREAM_START: u8 = 1;
43const STREAM_DATA: u8 = 2;
44const STREAM_END: u8 = 3;
45/// Largest clear DATA part in a protected upload.
46pub const STREAM_DATA_LEN: usize = 64 * 1024;
47/// Maximum DATA records, including small writes.
48const STREAM_MAX_DATA_RECORDS: u64 = 1_048_576;
49const RESPONSE_EXPORT_LABEL: &[u8] = b"message/hpke-http response\0v3";
50const REPLAY_LABEL: &[u8] = b"hpke-http/replay\0v3\0";
51const RESPONSE_KEY_INFO: &[u8] = b"hpke-http/3 response key";
52const RESPONSE_NONCE_INFO: &[u8] = b"hpke-http/3 response nonce";
53const RESPONSE_SECRET_LEN: usize = 32;
54const RESPONSE_KEY_LEN: usize = 32;
55const AEAD_NONCE_LEN: usize = 12;
56const MIN_PSK_LEN: usize = 32;
57pub(crate) type ResponseKey = Zeroizing<[u8; RESPONSE_KEY_LEN]>;
58pub(crate) type ResponseNonce = Zeroizing<[u8; AEAD_NONCE_LEN]>;
59
60/// A generated X25519 recipient key pair.
61pub struct KeyPair {
62    private_key: Zeroizing<Vec<u8>>,
63    public_key: Vec<u8>,
64}
65
66impl fmt::Debug for KeyPair {
67    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
68        formatter
69            .debug_struct("KeyPair")
70            .field("private_key", &"[REDACTED]")
71            .field("public_key_len", &self.public_key.len())
72            .finish()
73    }
74}
75
76impl KeyPair {
77    /// Return the encoded private and public keys.
78    #[must_use]
79    pub fn into_parts(self) -> (Zeroizing<Vec<u8>>, Vec<u8>) {
80        (self.private_key, self.public_key)
81    }
82
83    /// Borrow the encoded public key.
84    #[must_use]
85    pub fn public_key(&self) -> &[u8] {
86        &self.public_key
87    }
88}
89
90/// Generate a recipient key pair with the platform entropy source.
91///
92/// # Errors
93///
94/// Returns [`Error::EntropyUnavailable`] if the platform source fails.
95pub fn generate_key_pair() -> Result<KeyPair, Error> {
96    generate_key_pair_with_entropy(&mut SystemEntropy)
97}
98
99/// Generate a recipient key pair with an injected fallible entropy source.
100///
101/// # Errors
102///
103/// Returns the error reported by `entropy` before producing a key pair.
104pub fn generate_key_pair_with_entropy(entropy: &mut impl EntropySource) -> Result<KeyPair, Error> {
105    let mut input_key_material = Zeroizing::new([0_u8; 32]);
106    entropy.fill(input_key_material.as_mut())?;
107    let (private_key, public_key) = Kem::derive_keypair(input_key_material.as_ref());
108    Ok(KeyPair {
109        private_key: Zeroizing::new(private_key.to_bytes().to_vec()),
110        public_key: public_key.to_bytes().to_vec(),
111    })
112}
113
114/// A request envelope and the one-use capability needed to open its response.
115#[must_use]
116pub struct ProtectedRequest {
117    envelope: Vec<u8>,
118    response_token: ResponseToken,
119}
120
121impl fmt::Debug for ProtectedRequest {
122    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
123        formatter
124            .debug_struct("ProtectedRequest")
125            .field("envelope_len", &self.envelope.len())
126            .field("response_token", &self.response_token)
127            .finish()
128    }
129}
130
131impl ProtectedRequest {
132    /// Borrow the complete protected request envelope.
133    #[must_use]
134    pub fn envelope(&self) -> &[u8] {
135        &self.envelope
136    }
137
138    /// Split the envelope from its one-use response token.
139    pub fn into_parts(self) -> (Vec<u8>, ResponseToken) {
140        (self.envelope, self.response_token)
141    }
142}
143
144/// A client configured for one recipient key and one explicit PSK identity.
145pub struct Client {
146    recipient_public_key: <Kem as KemTrait>::PublicKey,
147    recipient_key_id: Vec<u8>,
148    psk: Zeroizing<Vec<u8>>,
149    psk_id: Vec<u8>,
150    limits: Limits,
151}
152
153impl fmt::Debug for Client {
154    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
155        formatter
156            .debug_struct("Client")
157            .field("recipient_key_id_len", &self.recipient_key_id.len())
158            .field("psk", &"[REDACTED]")
159            .field("psk_id_len", &self.psk_id.len())
160            .field("limits", &self.limits)
161            .finish_non_exhaustive()
162    }
163}
164
165impl Client {
166    /// Validate and create a client. The public PSK ID cannot equal the PSK.
167    ///
168    /// # Errors
169    ///
170    /// Returns [`Error::InvalidConfiguration`] for invalid keys, identifiers,
171    /// credentials, or limits.
172    pub fn new(
173        recipient_public_key: &[u8],
174        recipient_key_id: Vec<u8>,
175        psk: Vec<u8>,
176        psk_id: Vec<u8>,
177        limits: Limits,
178    ) -> Result<Self, Error> {
179        let psk = Zeroizing::new(psk);
180        validate_id(&recipient_key_id)?;
181        validate_credential(psk.as_slice(), &psk_id)?;
182        let limits = limits.validate()?;
183        let recipient_public_key = <Kem as KemTrait>::PublicKey::from_bytes(recipient_public_key)
184            .map_err(|_| Error::InvalidConfiguration)?;
185        Ok(Self {
186            recipient_public_key,
187            recipient_key_id,
188            psk,
189            psk_id,
190            limits,
191        })
192    }
193
194    /// Protect one complete request with the platform entropy source.
195    ///
196    /// # Errors
197    ///
198    /// Returns a validation, clock, entropy, or cryptographic
199    /// error before exposing a partially protected envelope.
200    pub fn protect(&self, request: &Request) -> Result<ProtectedRequest, Error> {
201        self.protect_at_with_entropy(request, unix_time_seconds()?, &mut SystemEntropy)
202    }
203
204    /// Protect one complete request at an injected Unix time.
205    ///
206    /// This entry point exists for hosts with their own trusted clock and for
207    /// deterministic conformance tests. Normal native callers use [`Self::protect`].
208    ///
209    /// # Errors
210    ///
211    /// Returns a validation, entropy, or cryptographic error.
212    pub fn protect_at(
213        &self,
214        request: &Request,
215        now_unix_s: u64,
216    ) -> Result<ProtectedRequest, Error> {
217        self.protect_at_with_entropy(request, now_unix_s, &mut SystemEntropy)
218    }
219
220    /// Protect one complete request with an injected fallible entropy source.
221    ///
222    /// # Errors
223    ///
224    /// Returns a validation, clock, entropy, or cryptographic
225    /// error before exposing a partially protected envelope.
226    pub fn protect_with_entropy(
227        &self,
228        request: &Request,
229        entropy: &mut impl EntropySource,
230    ) -> Result<ProtectedRequest, Error> {
231        self.protect_at_with_entropy(request, unix_time_seconds()?, entropy)
232    }
233
234    /// Protect a request with injected time and entropy.
235    ///
236    /// # Errors
237    ///
238    /// Returns a validation, entropy, or cryptographic error
239    /// before exposing a partial envelope.
240    pub fn protect_at_with_entropy(
241        &self,
242        request: &Request,
243        now_unix_s: u64,
244        entropy: &mut impl EntropySource,
245    ) -> Result<ProtectedRequest, Error> {
246        message::validate_request(request, self.limits)?;
247        let head = RequestHead {
248            method: request.method,
249            authority: request.authority.clone(),
250            path: request.path.clone(),
251            headers: request.headers.clone(),
252        };
253        let (mut writer, mut envelope) =
254            self.begin_stream_at_with_entropy(&head, now_unix_s, entropy)?;
255        let mut remaining = request.body.as_slice();
256        while !remaining.is_empty() {
257            let (used, record) = writer.push(remaining)?;
258            remaining = &remaining[used..];
259            if let Some(record) = record {
260                envelope.extend_from_slice(&record);
261            }
262        }
263        let (end, response_token) = writer.finish()?;
264        envelope.extend_from_slice(&end);
265        Ok(ProtectedRequest {
266            envelope,
267            response_token,
268        })
269    }
270}
271
272/// Public information needed by a host credential resolver.
273#[derive(Clone, Debug, Eq, PartialEq)]
274pub struct CredentialRequest {
275    /// Untrusted public PSK ID for credential lookup before START authentication.
276    pub psk_id: Vec<u8>,
277}
278
279/// Result of the bounded public preparse stage.
280#[must_use]
281pub struct PreparsedRequest {
282    /// Input for the host credential resolver.
283    pub credential: CredentialRequest,
284    /// One-use input to [`Server::authenticate`].
285    pub token: StartToken,
286}
287
288/// Opaque one-use continuation after public preparse.
289#[must_use]
290pub struct StartToken {
291    envelope: Vec<u8>,
292}
293
294impl fmt::Debug for StartToken {
295    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
296        formatter
297            .debug_struct("StartToken")
298            .field("envelope_len", &self.envelope.len())
299            .finish()
300    }
301}
302
303/// Stable request identifier passed to an atomic replay-admission provider.
304#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
305pub struct ReplayRequest {
306    /// SHA-256 replay key for this authenticated attempt.
307    pub id: [u8; 32],
308    /// Unix second at which this reservation can be evicted.
309    pub retain_until_exclusive: u64,
310}
311
312impl ReplayRequest {
313    /// Bind one provider result to this exact replay request.
314    #[must_use]
315    pub const fn decision(self, admitted: bool) -> ReplayDecision {
316        ReplayDecision {
317            id: self.id,
318            retain_until_exclusive: self.retain_until_exclusive,
319            admitted,
320        }
321    }
322}
323
324/// One atomic replay-provider result bound to its request ID.
325#[derive(Clone, Copy, Debug, Eq, PartialEq)]
326pub struct ReplayDecision {
327    id: [u8; 32],
328    retain_until_exclusive: u64,
329    admitted: bool,
330}
331
332fn check_replay_decision(
333    replay_id: [u8; 32],
334    retain_until_exclusive: u64,
335    decision: ReplayDecision,
336    now_unix_s: u64,
337) -> Result<(), Error> {
338    if replay_id != decision.id || retain_until_exclusive != decision.retain_until_exclusive {
339        return Err(Error::ReplayDecisionMismatch);
340    }
341    if !decision.admitted {
342        return Err(Error::ReplayRejected);
343    }
344    if now_unix_s >= retain_until_exclusive {
345        return Err(Error::InvalidRequestTime);
346    }
347    Ok(())
348}
349
350/// Result of authentication, paused before replay admission and plaintext release.
351#[must_use]
352pub struct AuthenticatedRequest {
353    /// Input for an atomic replay-admission provider.
354    pub replay: ReplayRequest,
355    /// One-use input to [`ReplayToken::admit`].
356    pub token: ReplayToken,
357}
358
359/// Opaque one-use continuation after request authentication.
360#[must_use]
361pub struct ReplayToken {
362    replay_id: [u8; 32],
363    retain_until_exclusive: u64,
364    request: Request,
365    response: ResponseCapability,
366}
367
368impl fmt::Debug for ReplayToken {
369    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
370        formatter
371            .debug_struct("ReplayToken")
372            .field("replay_id", &self.replay_id)
373            .field("retain_until_exclusive", &self.retain_until_exclusive)
374            .field("request", &"[REDACTED]")
375            .finish_non_exhaustive()
376    }
377}
378
379impl ReplayToken {
380    /// Apply one atomic replay decision before its deadline, then release the
381    /// authenticated request.
382    ///
383    /// # Errors
384    ///
385    /// Returns [`Error::ReplayDecisionMismatch`] when the decision belongs to a
386    /// different request, [`Error::ReplayRejected`] when the provider denies
387    /// admission, [`Error::InvalidRequestTime`] when admission completes after
388    /// the authenticated deadline, or [`Error::ClockUnavailable`] when the
389    /// platform clock cannot be read.
390    pub fn admit(self, decision: ReplayDecision) -> Result<OpenedRequest, Error> {
391        self.admit_at(decision, unix_time_seconds()?)
392    }
393
394    /// Apply one replay decision at a caller-supplied trusted Unix time.
395    ///
396    /// This entry point exists for hosts with their own trusted clock and for
397    /// deterministic conformance tests. Normal native callers use [`Self::admit`].
398    ///
399    /// # Errors
400    ///
401    /// Returns the same decision, freshness, and clock-independent errors as
402    /// [`Self::admit`].
403    pub fn admit_at(
404        self,
405        decision: ReplayDecision,
406        now_unix_s: u64,
407    ) -> Result<OpenedRequest, Error> {
408        check_replay_decision(
409            self.replay_id,
410            self.retain_until_exclusive,
411            decision,
412            now_unix_s,
413        )?;
414        Ok(OpenedRequest {
415            request: self.request,
416            response: self.response,
417        })
418    }
419}
420
421/// A fully authenticated request and its one-use response capability.
422#[must_use]
423pub struct OpenedRequest {
424    /// Verified request data. The host checks its logical target policy before
425    /// dispatch.
426    pub request: Request,
427    /// One-use capability bound to this request.
428    pub response: ResponseCapability,
429}
430
431/// A server with one advertised recipient key and other accepted keys.
432pub struct Server {
433    recipient_private_key: <Kem as KemTrait>::PrivateKey,
434    recipient_key_id: Vec<u8>,
435    accepted_keys: Vec<(Vec<u8>, <Kem as KemTrait>::PrivateKey)>,
436    limits: Limits,
437}
438
439impl fmt::Debug for Server {
440    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
441        formatter
442            .debug_struct("Server")
443            .field("recipient_private_key", &"[REDACTED]")
444            .field("recipient_key_id_len", &self.recipient_key_id.len())
445            .field("accepted_key_count", &self.accepted_keys.len())
446            .field("limits", &self.limits)
447            .finish()
448    }
449}
450
451impl Server {
452    /// Validate and create a server for one static recipient key.
453    ///
454    /// # Errors
455    ///
456    /// Returns [`Error::InvalidConfiguration`] for invalid key material,
457    /// identifiers, or limits.
458    pub fn new(
459        recipient_private_key: &[u8],
460        recipient_key_id: Vec<u8>,
461        limits: Limits,
462    ) -> Result<Self, Error> {
463        Self::with_accepted_keys(recipient_private_key, recipient_key_id, Vec::new(), limits)
464    }
465
466    /// Create a server that advertises one key and also accepts other keys.
467    ///
468    /// Each accepted pair holds a private key followed by its public key ID.
469    ///
470    /// # Errors
471    ///
472    /// Returns [`Error::InvalidConfiguration`] for invalid or duplicate IDs,
473    /// invalid private keys, or invalid limits.
474    pub fn with_accepted_keys(
475        recipient_private_key: &[u8],
476        recipient_key_id: Vec<u8>,
477        accepted: Vec<(Vec<u8>, Vec<u8>)>,
478        limits: Limits,
479    ) -> Result<Self, Error> {
480        validate_id(&recipient_key_id)?;
481        let limits = limits.validate()?;
482        let recipient_private_key =
483            <Kem as KemTrait>::PrivateKey::from_bytes(recipient_private_key)
484                .map_err(|_| Error::InvalidConfiguration)?;
485        let accepted = accepted
486            .into_iter()
487            .map(|(private_key, key_id)| (Zeroizing::new(private_key), key_id))
488            .collect::<Vec<_>>();
489        let mut accepted_keys = Vec::with_capacity(accepted.len());
490        for (private_key, key_id) in accepted {
491            validate_id(&key_id)?;
492            if key_id == recipient_key_id || accepted_keys.iter().any(|(id, _)| *id == key_id) {
493                return Err(Error::InvalidConfiguration);
494            }
495            let private_key = <Kem as KemTrait>::PrivateKey::from_bytes(&private_key)
496                .map_err(|_| Error::InvalidConfiguration)?;
497            accepted_keys.push((key_id, private_key));
498        }
499        Ok(Self {
500            recipient_private_key,
501            recipient_key_id,
502            accepted_keys,
503            limits,
504        })
505    }
506
507    fn private_key_for(&self, key_id: &[u8]) -> Result<&<Kem as KemTrait>::PrivateKey, Error> {
508        if key_id == self.recipient_key_id {
509            return Ok(&self.recipient_private_key);
510        }
511        self.accepted_keys
512            .iter()
513            .find(|(id, _)| id == key_id)
514            .map(|(_, private_key)| private_key)
515            .ok_or(Error::UnknownRecipientKey)
516    }
517
518    /// Return the encoded public key for this server's private key.
519    #[must_use]
520    pub fn public_key(&self) -> Vec<u8> {
521        Kem::sk_to_pk(&self.recipient_private_key)
522            .to_bytes()
523            .to_vec()
524    }
525
526    /// Return the largest complete request envelope accepted by `preparse`.
527    ///
528    /// Bindings use this limit before they copy a complete envelope.
529    ///
530    /// # Errors
531    ///
532    /// Returns [`Error::LimitExceeded`] if the size cannot fit in `usize`.
533    pub fn max_complete_envelope_len(&self) -> Result<usize, Error> {
534        max_complete_envelope_len(self.limits)
535    }
536
537    /// Parse bounded public fields from a supplied complete envelope.
538    /// An HTTP host first reads the outer body through real EOF; this method
539    /// cannot observe transport EOF itself.
540    ///
541    /// # Errors
542    ///
543    /// Returns a stable parse, limit, version, suite, or recipient-key error.
544    pub fn preparse(&self, envelope: &[u8]) -> Result<PreparsedRequest, Error> {
545        let credential = self.preparse_credential(envelope)?;
546        Ok(PreparsedRequest {
547            credential,
548            token: StartToken {
549                envelope: envelope.to_vec(),
550            },
551        })
552    }
553
554    /// Parse an owned envelope without cloning its complete ciphertext.
555    ///
556    /// Bindings which already own their input can move it into the one-shot
557    /// credential-resolution token. The same bounded public checks run before
558    /// the token is created. An HTTP host first reads the outer body through
559    /// real EOF; this method cannot observe transport EOF itself.
560    ///
561    /// # Errors
562    ///
563    /// Returns a stable parse, limit, version, suite, or recipient-key error.
564    pub fn preparse_owned(&self, envelope: Vec<u8>) -> Result<PreparsedRequest, Error> {
565        let credential = self.preparse_credential(&envelope)?;
566        Ok(PreparsedRequest {
567            credential,
568            token: StartToken { envelope },
569        })
570    }
571
572    fn preparse_credential(&self, envelope: &[u8]) -> Result<CredentialRequest, Error> {
573        if envelope.len() > self.max_complete_envelope_len()? {
574            return Err(Error::LimitExceeded);
575        }
576        let first_len = self
577            .stream_start_length(envelope)?
578            .ok_or(Error::MalformedEnvelope)?;
579        if first_len > envelope.len() {
580            return Err(Error::MalformedEnvelope);
581        }
582        let parsed = parse_stream_start(
583            &envelope[..first_len],
584            max_stream_start_ciphertext(self.limits),
585        )?;
586        self.private_key_for(parsed.key_id)?;
587        Ok(CredentialRequest {
588            psk_id: parsed.psk_id.to_vec(),
589        })
590    }
591
592    /// Authenticate and parse the full request, then pause before replay admission.
593    ///
594    /// Plaintext stays inside the returned opaque token until admission.
595    ///
596    /// # Errors
597    ///
598    /// Returns a credential, recipient-key, parse, authentication,
599    /// request-time, or clock error.
600    pub fn authenticate(
601        &self,
602        token: StartToken,
603        psk: &[u8],
604    ) -> Result<AuthenticatedRequest, Error> {
605        self.authenticate_at(token, psk, unix_time_seconds()?)
606    }
607
608    /// Authenticate at an injected Unix time, then pause before replay admission.
609    ///
610    /// # Errors
611    ///
612    /// Returns a credential, recipient-key, parse, authentication, or request-time error.
613    pub fn authenticate_at(
614        &self,
615        token: StartToken,
616        psk: &[u8],
617        now_unix_s: u64,
618    ) -> Result<AuthenticatedRequest, Error> {
619        let StartToken { envelope } = token;
620        let first_len = self
621            .stream_start_length(&envelope)?
622            .ok_or(Error::MalformedEnvelope)?;
623        if first_len > envelope.len() {
624            return Err(Error::MalformedEnvelope);
625        }
626        let authenticated = self.authenticate_stream_at(
627            StreamStartToken {
628                first: envelope[..first_len].to_vec(),
629            },
630            psk,
631            now_unix_s,
632        )?;
633        let replay = authenticated.replay;
634        let StreamReplayToken {
635            head, mut reader, ..
636        } = authenticated.token;
637        let mut body = Vec::new();
638        let mut remaining = &envelope[first_len..];
639        while !remaining.is_empty() {
640            let (used, record) = reader.feed(remaining)?;
641            remaining = &remaining[used..];
642            if let Some(StreamRequestRecord::Data(part)) = record {
643                if part.len() > self.limits.max_body_len.saturating_sub(body.len()) {
644                    return Err(Error::LimitExceeded);
645                }
646                body.extend_from_slice(&part);
647            }
648        }
649        let response = reader.finish_eof()?;
650        let request = Request {
651            method: head.method,
652            authority: head.authority,
653            path: head.path,
654            headers: head.headers,
655            body,
656        };
657        Ok(AuthenticatedRequest {
658            replay,
659            token: ReplayToken {
660                replay_id: replay.id,
661                retain_until_exclusive: replay.retain_until_exclusive,
662                request,
663                response,
664            },
665        })
666    }
667}
668
669#[cfg(test)]
670mod stream_tests {
671    use super::STREAM_MAX_DATA_RECORDS;
672    use crate::{Client, Error, Limits, Method, Request, RequestHead, Server, generate_key_pair};
673
674    #[test]
675    fn server_accepts_old_key_during_rotation() -> Result<(), Error> {
676        let old = generate_key_pair()?;
677        let new = generate_key_pair()?;
678        let old_public = old.public_key().to_vec();
679        let new_public = new.public_key().to_vec();
680        let (old_private, _) = old.into_parts();
681        let (new_private, _) = new.into_parts();
682        let psk = b"a 32-byte minimum rotation credential";
683        let server = Server::with_accepted_keys(
684            &new_private,
685            b"new".to_vec(),
686            vec![(old_private.to_vec(), b"old".to_vec())],
687            Limits::default(),
688        )?;
689        assert_eq!(server.public_key(), new_public);
690        let request = Request {
691            method: Method::Get,
692            authority: b"api.example.test".to_vec(),
693            path: b"/status".to_vec(),
694            headers: Vec::new(),
695            body: Vec::new(),
696        };
697        for (public_key, id) in [
698            (&old_public, b"old".as_slice()),
699            (&new_public, b"new".as_slice()),
700        ] {
701            let client = Client::new(
702                public_key,
703                id.to_vec(),
704                psk.to_vec(),
705                b"token-id".to_vec(),
706                Limits::default(),
707            )?;
708            let protected = client.protect(&request)?;
709            let preparsed = server.preparse(protected.envelope())?;
710            let _authenticated = server.authenticate(preparsed.token, psk)?;
711        }
712        let unknown = Client::new(
713            &old_public,
714            b"unknown".to_vec(),
715            psk.to_vec(),
716            b"token-id".to_vec(),
717            Limits::default(),
718        )?;
719        let protected = unknown.protect(&request)?;
720        assert_eq!(
721            server.preparse(protected.envelope()).err(),
722            Some(Error::UnknownRecipientKey)
723        );
724        assert_eq!(
725            Server::with_accepted_keys(
726                &new_private,
727                b"new".to_vec(),
728                vec![(new_private.to_vec(), b"new".to_vec())],
729                Limits::default(),
730            )
731            .err(),
732            Some(Error::InvalidConfiguration),
733        );
734        Ok(())
735    }
736
737    #[test]
738    fn data_record_count_guard_closes_both_sides() -> Result<(), Error> {
739        let keys = generate_key_pair()?;
740        let psk = b"a 32-byte minimum upload credential";
741        let client = Client::new(
742            keys.public_key(),
743            b"key".to_vec(),
744            psk.to_vec(),
745            b"psk".to_vec(),
746            Limits::default(),
747        )?;
748        let server = Server::new(&keys.into_parts().0, b"key".to_vec(), Limits::default())?;
749        let head = RequestHead {
750            method: Method::Post,
751            authority: b"api.example.test".to_vec(),
752            path: b"/upload".to_vec(),
753            headers: Vec::new(),
754        };
755        let (mut writer, _) = client.begin_stream(&head)?;
756        writer.data_records = STREAM_MAX_DATA_RECORDS;
757        assert_eq!(writer.push(b"x").err(), Some(Error::LimitExceeded));
758        assert!(writer.context.is_none());
759
760        let (mut writer, first) = client.begin_stream(&head)?;
761        let (used, record) = writer.push(b"x")?;
762        assert_eq!(used, 1);
763        assert!(record.is_none());
764        let (data, _) = writer.finish()?;
765        let preparsed = server.preparse_stream(&first)?;
766        let authenticated = server.authenticate_stream(preparsed.token, psk)?;
767        let mut reader = authenticated
768            .token
769            .admit(authenticated.replay.decision(true))?
770            .reader;
771        reader.data_records = STREAM_MAX_DATA_RECORDS;
772        assert_eq!(reader.feed(&data).err(), Some(Error::LimitExceeded));
773        assert!(reader.context.is_none());
774        assert!(reader.finish_eof().is_err());
775        Ok(())
776    }
777}
778
779pub(crate) struct ResponseMaterial {
780    pub(crate) secret: Zeroizing<[u8; RESPONSE_SECRET_LEN]>,
781    pub(crate) enc: [u8; ENC_LEN],
782    pub(crate) method: Method,
783    pub(crate) limits: Limits,
784}
785
786impl fmt::Debug for ResponseMaterial {
787    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
788        formatter
789            .debug_struct("ResponseMaterial")
790            .field("secret", &"[REDACTED]")
791            .field("enc_len", &self.enc.len())
792            .field("method", &self.method)
793            .field("limits", &self.limits)
794            .finish()
795    }
796}
797
798/// One-use client capability for opening the response to one request.
799#[must_use]
800pub struct ResponseToken(ResponseMaterial);
801
802impl fmt::Debug for ResponseToken {
803    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
804        formatter
805            .debug_tuple("ResponseToken")
806            .field(&self.0)
807            .finish()
808    }
809}
810
811impl ResponseToken {
812    /// Consume this token to check response records as they arrive.
813    #[must_use]
814    pub fn into_opener(self) -> ResponseOpener {
815        ResponseOpener::new(self.0)
816    }
817
818    /// Open one complete finite response with the v3 record reader.
819    /// An HTTP caller first reads the outer body through real EOF.
820    ///
821    /// # Errors
822    /// Returns a parse, limit, authentication, or response-mode error.
823    pub fn open_finite(self, envelope: &[u8]) -> Result<Response, Error> {
824        let mut opener = self.into_opener();
825        let mut offset = 0;
826        while offset < envelope.len() {
827            let (used, record) = opener.feed(&envelope[offset..])?;
828            offset += used;
829            if matches!(record, Some(ResponseRecord::Start(ref head)) if head.mode == ResponseMode::Sse)
830            {
831                return Err(Error::InvalidConfiguration);
832            }
833            if used == 0 {
834                return Err(Error::MalformedEnvelope);
835            }
836        }
837        opener.finish_eof()?.ok_or(Error::MalformedEnvelope)
838    }
839}
840
841/// One-use server capability for protecting the response to one opened request.
842#[must_use]
843pub struct ResponseCapability(ResponseMaterial);
844
845impl fmt::Debug for ResponseCapability {
846    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
847        formatter
848            .debug_tuple("ResponseCapability")
849            .field(&self.0)
850            .finish()
851    }
852}
853
854impl ResponseCapability {
855    /// Start one checked response. The returned bytes hold the prefix and START.
856    ///
857    /// # Errors
858    /// Returns a validation, entropy, or cryptographic error.
859    pub fn into_sealer(
860        self,
861        status: u16,
862        headers: Vec<HeaderField>,
863    ) -> Result<(ResponseSealer, Vec<u8>), Error> {
864        self.into_sealer_with_entropy(status, headers, &mut SystemEntropy)
865    }
866
867    /// Start one checked response with an injected entropy source.
868    ///
869    /// # Errors
870    /// Returns a validation, entropy, or cryptographic error.
871    pub fn into_sealer_with_entropy(
872        self,
873        status: u16,
874        headers: Vec<HeaderField>,
875        entropy: &mut impl EntropySource,
876    ) -> Result<(ResponseSealer, Vec<u8>), Error> {
877        ResponseSealer::new(&self.0, status, headers, entropy)
878    }
879
880    /// Protect one complete finite response with the v3 record writer.
881    ///
882    /// # Errors
883    /// Returns a validation, limit, entropy, or cryptographic error.
884    pub fn protect_finite(self, response: &Response) -> Result<Vec<u8>, Error> {
885        self.protect_finite_with_entropy(response, &mut SystemEntropy)
886    }
887
888    /// Protect one complete finite response with injected entropy.
889    ///
890    /// # Errors
891    /// Returns a validation, limit, entropy, or cryptographic error.
892    pub fn protect_finite_with_entropy(
893        self,
894        response: &Response,
895        entropy: &mut impl EntropySource,
896    ) -> Result<Vec<u8>, Error> {
897        self.protect_finite_parts_with_entropy(
898            response.status,
899            response.headers.clone(),
900            &response.body,
901            entropy,
902        )
903    }
904
905    /// Protect a finite response from a borrowed body without copying it first.
906    ///
907    /// # Errors
908    /// Returns a validation, limit, entropy, or cryptographic error.
909    pub fn protect_finite_parts(
910        self,
911        status: u16,
912        headers: Vec<HeaderField>,
913        body: &[u8],
914    ) -> Result<Vec<u8>, Error> {
915        self.protect_finite_parts_with_entropy(status, headers, body, &mut SystemEntropy)
916    }
917
918    fn protect_finite_parts_with_entropy(
919        self,
920        status: u16,
921        headers: Vec<HeaderField>,
922        body: &[u8],
923        entropy: &mut impl EntropySource,
924    ) -> Result<Vec<u8>, Error> {
925        let (mut sealer, mut output) = self.into_sealer_with_entropy(status, headers, entropy)?;
926        if sealer.head_mode() != ResponseMode::Finite {
927            return Err(Error::InvalidConfiguration);
928        }
929        let data = sealer.seal_finite_body(body)?;
930        let end = sealer.finish()?;
931        output.reserve(data.as_ref().map_or(0, Vec::len) + end.len());
932        if let Some(data) = data {
933            output.extend_from_slice(&data);
934        }
935        output.extend_from_slice(&end);
936        Ok(output)
937    }
938}
939
940fn validate_id(identifier: &[u8]) -> Result<(), Error> {
941    if identifier.is_empty() || identifier.len() > HARD_MAX_ID_LEN {
942        return Err(Error::InvalidConfiguration);
943    }
944    Ok(())
945}
946
947fn validate_credential(psk: &[u8], psk_id: &[u8]) -> Result<(), Error> {
948    validate_id(psk_id)?;
949    if psk.len() < MIN_PSK_LEN || psk == psk_id {
950        return Err(Error::InvalidConfiguration);
951    }
952    Ok(())
953}
954
955fn unix_time_seconds() -> Result<u64, Error> {
956    SystemTime::now()
957        .duration_since(UNIX_EPOCH)
958        .map_err(|_| Error::ClockUnavailable)
959        .map(|duration| duration.as_secs())
960}
961
962fn replay_retention_deadline(issued_at_unix_s: u64, now_unix_s: u64) -> Result<u64, Error> {
963    let latest_issued_at = now_unix_s
964        .checked_add(CLOCK_SKEW_SECS)
965        .ok_or(Error::InvalidRequestTime)?;
966    let retain_until_exclusive = issued_at_unix_s
967        .checked_add(REQUEST_LIFETIME_SECS)
968        .and_then(|value| value.checked_add(CLOCK_SKEW_SECS))
969        .ok_or(Error::InvalidRequestTime)?;
970    if issued_at_unix_s > latest_issued_at || now_unix_s >= retain_until_exclusive {
971        return Err(Error::InvalidRequestTime);
972    }
973    Ok(retain_until_exclusive)
974}
975
976fn replay_id(header: &[u8], enc: &[u8]) -> [u8; 32] {
977    let mut digest = Sha256::new();
978    digest.update(REPLAY_LABEL);
979    digest.update(header);
980    digest.update(enc);
981    digest.finalize().into()
982}
983
984fn export_response_material<C>(
985    context: &C,
986    enc: &[u8],
987    method: Method,
988    limits: Limits,
989) -> Result<ResponseMaterial, Error>
990where
991    C: Exporter,
992{
993    let enc: [u8; ENC_LEN] = enc.try_into().map_err(|_| Error::CryptoFailure)?;
994    let mut secret = Zeroizing::new([0_u8; RESPONSE_SECRET_LEN]);
995    context.export_secret(RESPONSE_EXPORT_LABEL, secret.as_mut())?;
996    Ok(ResponseMaterial {
997        secret,
998        enc,
999        method,
1000        limits,
1001    })
1002}
1003
1004pub(crate) fn derive_response_key_nonce(
1005    material: &ResponseMaterial,
1006    response_nonce: &[u8],
1007) -> Result<(ResponseKey, ResponseNonce), Error> {
1008    if response_nonce.len() != RESPONSE_NONCE_LEN {
1009        return Err(Error::MalformedEnvelope);
1010    }
1011    let mut salt = Zeroizing::new(Vec::with_capacity(ENC_LEN + RESPONSE_NONCE_LEN));
1012    salt.extend_from_slice(&material.enc);
1013    salt.extend_from_slice(response_nonce);
1014    let hkdf = Hkdf::<Sha256>::new(Some(&salt), material.secret.as_ref());
1015    let mut key = Zeroizing::new([0_u8; RESPONSE_KEY_LEN]);
1016    hkdf.expand(RESPONSE_KEY_INFO, key.as_mut())
1017        .map_err(|_| Error::CryptoFailure)?;
1018    let mut nonce = Zeroizing::new([0_u8; AEAD_NONCE_LEN]);
1019    hkdf.expand(RESPONSE_NONCE_INFO, nonce.as_mut())
1020        .map_err(|_| Error::CryptoFailure)?;
1021    Ok((key, nonce))
1022}
1023
1024trait Exporter {
1025    fn export_secret(&self, context: &[u8], output: &mut [u8]) -> Result<(), Error>;
1026}
1027
1028impl Exporter for hpke::aead::AeadCtxS<HpkeAead, Kdf, Kem> {
1029    fn export_secret(&self, context: &[u8], output: &mut [u8]) -> Result<(), Error> {
1030        self.export(context, output)
1031            .map_err(|_| Error::CryptoFailure)
1032    }
1033}
1034
1035impl Exporter for RecipientContext {
1036    fn export_secret(&self, context: &[u8], output: &mut [u8]) -> Result<(), Error> {
1037        self.export(context, output)
1038            .map_err(|_| Error::CryptoFailure)
1039    }
1040}
1041
1042fn max_stream_start_ciphertext(limits: Limits) -> usize {
1043    limits.max_header_bytes + limits.max_target_len + limits.max_header_count * 16 + 256 + TAG_LEN
1044}
1045
1046fn max_complete_envelope_len(limits: Limits) -> Result<usize, Error> {
1047    let records = limits
1048        .max_body_len
1049        .min(usize::try_from(STREAM_MAX_DATA_RECORDS).map_err(|_| Error::LimitExceeded)?);
1050    let data_overhead = records
1051        .checked_mul(4 + 2 + TAG_LEN)
1052        .ok_or(Error::LimitExceeded)?;
1053    REQUEST_FIXED_LEN
1054        .checked_add(2 * HARD_MAX_ID_LEN)
1055        .and_then(|length| length.checked_add(ENC_LEN + 4))
1056        .and_then(|length| length.checked_add(max_stream_start_ciphertext(limits)))
1057        .and_then(|length| length.checked_add(limits.max_body_len))
1058        .and_then(|length| length.checked_add(data_overhead))
1059        .and_then(|length| length.checked_add(4 + 1 + TAG_LEN))
1060        .ok_or(Error::LimitExceeded)
1061}
1062
1063fn stream_request_info(header: &[u8]) -> Vec<u8> {
1064    let mut info = Vec::with_capacity(STREAM_REQUEST_INFO_LABEL.len() + header.len());
1065    info.extend_from_slice(STREAM_REQUEST_INFO_LABEL);
1066    info.extend_from_slice(header);
1067    info
1068}
1069
1070fn stream_record_aad(header: &[u8], sequence: u64, ciphertext_len: u32) -> Vec<u8> {
1071    let mut aad = Vec::with_capacity(STREAM_REQUEST_AAD_LABEL.len() + header.len() + 12);
1072    aad.extend_from_slice(STREAM_REQUEST_AAD_LABEL);
1073    aad.extend_from_slice(header);
1074    aad.extend_from_slice(&sequence.to_be_bytes());
1075    aad.extend_from_slice(&ciphertext_len.to_be_bytes());
1076    aad
1077}
1078
1079fn seal_stream_record(
1080    context: &mut SenderContext,
1081    header: &[u8],
1082    sequence: u64,
1083    kind: u8,
1084    coding: Option<u8>,
1085    body: &[u8],
1086) -> Result<Vec<u8>, Error> {
1087    let ciphertext_len = body
1088        .len()
1089        .checked_add(1 + usize::from(coding.is_some()) + TAG_LEN)
1090        .ok_or(Error::LimitExceeded)?;
1091    let ciphertext_len = u32::try_from(ciphertext_len).map_err(|_| Error::LimitExceeded)?;
1092    let mut frame = Vec::with_capacity(4 + ciphertext_len as usize);
1093    frame.extend_from_slice(&ciphertext_len.to_be_bytes());
1094    frame.push(kind);
1095    if let Some(coding) = coding {
1096        frame.push(coding);
1097    }
1098    frame.extend_from_slice(body);
1099    let aad = stream_record_aad(header, sequence, ciphertext_len);
1100    let tag = context
1101        .seal_inout_detached(InOutBuf::from(&mut frame[4..]), &aad)
1102        .map_err(|_| Error::CryptoFailure)?;
1103    frame.extend_from_slice(tag.to_bytes().as_ref());
1104    Ok(frame)
1105}
1106
1107/// One request writer. A failed call closes it and prevents a valid END.
1108pub struct StreamRequestSealer {
1109    context: Option<SenderContext>,
1110    header: Vec<u8>,
1111    sequence: u64,
1112    data_records: u64,
1113    total_bytes: u64,
1114    content_length: Option<u64>,
1115    max_upload_bytes: u64,
1116    pending: Vec<u8>,
1117    response_token: Option<ResponseToken>,
1118}
1119
1120impl fmt::Debug for StreamRequestSealer {
1121    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1122        formatter
1123            .debug_struct("StreamRequestSealer")
1124            .field("sequence", &self.sequence)
1125            .field("total_bytes", &self.total_bytes)
1126            .finish_non_exhaustive()
1127    }
1128}
1129
1130impl StreamRequestSealer {
1131    /// Add clear bytes. A full 64 KiB DATA record is returned when ready.
1132    /// The returned count can be less than `part.len()`. Pass `&part[used..]`
1133    /// to the next call until all input bytes are used.
1134    ///
1135    /// # Errors
1136    /// Returns a state, length, count, or cryptographic error and closes the writer.
1137    pub fn push(&mut self, part: &[u8]) -> Result<(usize, Option<Vec<u8>>), Error> {
1138        let result = self.push_inner(part);
1139        if result.is_err() {
1140            self.context = None;
1141            self.response_token = None;
1142        }
1143        result
1144    }
1145
1146    fn push_inner(&mut self, part: &[u8]) -> Result<(usize, Option<Vec<u8>>), Error> {
1147        if self.context.is_none() {
1148            return Err(Error::InvalidConfiguration);
1149        }
1150        if part.is_empty() {
1151            return Ok((0, None));
1152        }
1153        if self.data_records >= STREAM_MAX_DATA_RECORDS {
1154            return Err(Error::LimitExceeded);
1155        }
1156        let consumed = part.len().min(STREAM_DATA_LEN - self.pending.len());
1157        let next_total = self
1158            .total_bytes
1159            .checked_add(consumed as u64)
1160            .ok_or(Error::LimitExceeded)?;
1161        if next_total > self.max_upload_bytes
1162            || self
1163                .content_length
1164                .is_some_and(|length| next_total > length)
1165        {
1166            return Err(Error::LimitExceeded);
1167        }
1168        self.pending.extend_from_slice(&part[..consumed]);
1169        self.total_bytes = next_total;
1170        if self.pending.len() == STREAM_DATA_LEN {
1171            let frame = self.seal_pending()?;
1172            Ok((consumed, Some(frame)))
1173        } else {
1174            Ok((consumed, None))
1175        }
1176    }
1177
1178    fn seal_pending(&mut self) -> Result<Vec<u8>, Error> {
1179        if self.pending.is_empty() || self.data_records >= STREAM_MAX_DATA_RECORDS {
1180            return Err(Error::LimitExceeded);
1181        }
1182        let (coding, coded) = compression::encode(&self.pending);
1183        let context = self.context.as_mut().ok_or(Error::InvalidConfiguration)?;
1184        let frame = seal_stream_record(
1185            context,
1186            &self.header,
1187            self.sequence,
1188            STREAM_DATA,
1189            Some(coding),
1190            &coded,
1191        )?;
1192        self.pending.clear();
1193        self.sequence += 1;
1194        self.data_records += 1;
1195        Ok(frame)
1196    }
1197
1198    /// Protect final DATA, if any, then END, and release the one-use response right.
1199    /// Send all returned bytes before ending the outer HTTP body.
1200    ///
1201    /// # Errors
1202    /// Returns a state, logical length, or cryptographic error.
1203    pub fn finish(mut self) -> Result<(Vec<u8>, ResponseToken), Error> {
1204        if self
1205            .content_length
1206            .is_some_and(|length| self.total_bytes != length)
1207        {
1208            return Err(Error::InvalidConfiguration);
1209        }
1210        let mut frame = if self.pending.is_empty() {
1211            Vec::new()
1212        } else {
1213            self.seal_pending()?
1214        };
1215        let context = self.context.as_mut().ok_or(Error::InvalidConfiguration)?;
1216        frame.extend_from_slice(&seal_stream_record(
1217            context,
1218            &self.header,
1219            self.sequence,
1220            STREAM_END,
1221            None,
1222            &[],
1223        )?);
1224        let token = self
1225            .response_token
1226            .take()
1227            .ok_or(Error::InvalidConfiguration)?;
1228        Ok((frame, token))
1229    }
1230}
1231
1232impl Client {
1233    /// Start a protected request and return its bounded prefix and checked START.
1234    ///
1235    /// # Errors
1236    /// Returns a validation, clock, entropy, or cryptographic error before
1237    /// source bytes are read.
1238    pub fn begin_stream(
1239        &self,
1240        head: &RequestHead,
1241    ) -> Result<(StreamRequestSealer, Vec<u8>), Error> {
1242        self.begin_stream_at_with_entropy(head, unix_time_seconds()?, &mut SystemEntropy)
1243    }
1244
1245    /// Start a stream with caller-supplied time and entropy for wire tests.
1246    ///
1247    /// # Errors
1248    /// Returns a validation, entropy, or cryptographic error.
1249    pub fn begin_stream_at_with_entropy(
1250        &self,
1251        head: &RequestHead,
1252        now_unix_s: u64,
1253        entropy: &mut impl EntropySource,
1254    ) -> Result<(StreamRequestSealer, Vec<u8>), Error> {
1255        let max_upload_bytes = self.limits.max_request_bytes;
1256        let content_length = message::stream_content_length(&head.headers)?;
1257        if content_length.is_some_and(|length| length > max_upload_bytes) {
1258            return Err(Error::LimitExceeded);
1259        }
1260        let clear_head = message::encode_stream_head(head, self.limits)?;
1261        let header = encode_request_header(&self.recipient_key_id, &self.psk_id, now_unix_s)?;
1262        let psk_bundle = PskBundle::new(self.psk.as_slice(), &self.psk_id)
1263            .map_err(|_| Error::InvalidConfiguration)?;
1264        let mut random_seed = Zeroizing::new([0_u8; 32]);
1265        entropy.fill(random_seed.as_mut())?;
1266        let mut random = ChaCha20Rng::from_seed(*random_seed);
1267        let (encapped_key, mut context) = setup_sender_with_rng::<HpkeAead, Kdf, Kem>(
1268            &OpModeS::Psk(psk_bundle),
1269            &self.recipient_public_key,
1270            &stream_request_info(&header),
1271            &mut random,
1272        )
1273        .map_err(|_| Error::CryptoFailure)?;
1274        let enc = encapped_key.to_bytes();
1275        let response_material =
1276            export_response_material(&context, enc.as_ref(), head.method, self.limits)?;
1277        let start = seal_stream_record(&mut context, &header, 0, STREAM_START, None, &clear_head)?;
1278        let mut first = Vec::with_capacity(header.len() + ENC_LEN + start.len());
1279        first.extend_from_slice(&header);
1280        first.extend_from_slice(enc.as_ref());
1281        first.extend_from_slice(&start);
1282        Ok((
1283            StreamRequestSealer {
1284                context: Some(context),
1285                header,
1286                sequence: 1,
1287                data_records: 0,
1288                total_bytes: 0,
1289                content_length,
1290                max_upload_bytes,
1291                pending: Vec::with_capacity(STREAM_DATA_LEN),
1292                response_token: Some(ResponseToken(response_material)),
1293            },
1294            first,
1295        ))
1296    }
1297}
1298
1299/// Public credential ID and a one-use bounded START token.
1300#[must_use]
1301pub struct PreparsedStreamRequest {
1302    /// Input for the host credential resolver.
1303    pub credential: CredentialRequest,
1304    /// One-use input for START authentication.
1305    pub token: StreamStartToken,
1306}
1307
1308/// Opaque START bytes before credential resolution.
1309#[must_use]
1310pub struct StreamStartToken {
1311    first: Vec<u8>,
1312}
1313
1314/// Authenticated START, with plaintext withheld until replay admission.
1315#[must_use]
1316pub struct AuthenticatedStreamRequest {
1317    /// Input for an atomic replay-admission provider.
1318    pub replay: ReplayRequest,
1319    /// One-use input for replay admission.
1320    pub token: StreamReplayToken,
1321}
1322
1323/// One-use continuation after START authentication.
1324#[must_use]
1325pub struct StreamReplayToken {
1326    replay_id: [u8; 32],
1327    retain_until_exclusive: u64,
1328    head: RequestHead,
1329    reader: StreamRequestOpener,
1330}
1331
1332impl StreamReplayToken {
1333    /// Apply the replay result before plaintext or DATA is released.
1334    ///
1335    /// # Errors
1336    /// Returns a replay or clock error.
1337    pub fn admit(self, decision: ReplayDecision) -> Result<OpenedStreamRequest, Error> {
1338        self.admit_at(decision, unix_time_seconds()?)
1339    }
1340
1341    /// Apply the replay result at a caller-supplied trusted Unix time.
1342    ///
1343    /// # Errors
1344    /// Returns a replay or freshness error.
1345    pub fn admit_at(
1346        self,
1347        decision: ReplayDecision,
1348        now_unix_s: u64,
1349    ) -> Result<OpenedStreamRequest, Error> {
1350        check_replay_decision(
1351            self.replay_id,
1352            self.retain_until_exclusive,
1353            decision,
1354            now_unix_s,
1355        )?;
1356        Ok(OpenedStreamRequest {
1357            head: self.head,
1358            reader: self.reader,
1359        })
1360    }
1361}
1362
1363/// Checked request head and a live body reader. The host checks its logical
1364/// target policy and holds DATA from the app until END and real outer EOF.
1365#[must_use]
1366pub struct OpenedStreamRequest {
1367    /// Authenticated request fields; the host still checks its target policy.
1368    pub head: RequestHead,
1369    /// Checks each DATA part before it is released.
1370    pub reader: StreamRequestOpener,
1371}
1372
1373/// One checked upload record.
1374#[derive(Debug, Eq, PartialEq)]
1375pub enum StreamRequestRecord {
1376    /// One checked nonempty clear DATA part.
1377    Data(Vec<u8>),
1378    /// Checked END. True outer EOF must still follow.
1379    End,
1380}
1381
1382/// Incremental request reader with bounded frame storage.
1383pub struct StreamRequestOpener {
1384    context: Option<RecipientContext>,
1385    header: Vec<u8>,
1386    sequence: u64,
1387    data_records: u64,
1388    total_bytes: u64,
1389    content_length: Option<u64>,
1390    max_upload_bytes: u64,
1391    frame: Vec<u8>,
1392    ended: bool,
1393    response_material: Option<ResponseMaterial>,
1394}
1395
1396impl fmt::Debug for StreamRequestOpener {
1397    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1398        formatter
1399            .debug_struct("StreamRequestOpener")
1400            .field("sequence", &self.sequence)
1401            .field("total_bytes", &self.total_bytes)
1402            .field("ended", &self.ended)
1403            .finish_non_exhaustive()
1404    }
1405}
1406
1407impl StreamRequestOpener {
1408    /// Read at most one record from any byte cut.
1409    /// The returned count is the number of input bytes used. Pass the unread
1410    /// suffix to the next call.
1411    ///
1412    /// # Errors
1413    /// Returns a parse, order, limit, or authentication error and closes the reader.
1414    pub fn feed(&mut self, input: &[u8]) -> Result<(usize, Option<StreamRequestRecord>), Error> {
1415        let result = self.feed_inner(input);
1416        if result.is_err() {
1417            self.context = None;
1418            self.response_material = None;
1419            self.frame.clear();
1420        }
1421        result
1422    }
1423
1424    fn feed_inner(&mut self, input: &[u8]) -> Result<(usize, Option<StreamRequestRecord>), Error> {
1425        if self.context.is_none() || (self.ended && !input.is_empty()) {
1426            return Err(Error::MalformedEnvelope);
1427        }
1428        let mut used = 0;
1429        if self.frame.len() < 4 {
1430            let take = (4 - self.frame.len()).min(input.len());
1431            self.frame.extend_from_slice(&input[..take]);
1432            used += take;
1433            if self.frame.len() < 4 {
1434                return Ok((used, None));
1435            }
1436        }
1437        let ciphertext_len = u32::from_be_bytes(
1438            self.frame[..4]
1439                .try_into()
1440                .map_err(|_| Error::MalformedEnvelope)?,
1441        ) as usize;
1442        if ciphertext_len < 1 + TAG_LEN {
1443            return Err(Error::MalformedEnvelope);
1444        }
1445        if ciphertext_len > STREAM_DATA_LEN + 2 + TAG_LEN {
1446            return Err(Error::LimitExceeded);
1447        }
1448        let frame_len = 4 + ciphertext_len;
1449        let take = (frame_len - self.frame.len()).min(input.len() - used);
1450        self.frame.extend_from_slice(&input[used..used + take]);
1451        used += take;
1452        if self.frame.len() < frame_len {
1453            return Ok((used, None));
1454        }
1455        let plain_end = frame_len - TAG_LEN;
1456        let tag = AeadTag::<HpkeAead>::from_bytes(&self.frame[plain_end..])
1457            .map_err(|_| Error::AuthenticationFailed)?;
1458        let aad = stream_record_aad(
1459            &self.header,
1460            self.sequence,
1461            u32::try_from(ciphertext_len).map_err(|_| Error::LimitExceeded)?,
1462        );
1463        self.context
1464            .as_mut()
1465            .ok_or(Error::MalformedEnvelope)?
1466            .open_inout_detached(InOutBuf::from(&mut self.frame[4..plain_end]), &aad, &tag)
1467            .map_err(|_| Error::AuthenticationFailed)?;
1468        let kind = self.frame[4];
1469        let body = &self.frame[5..plain_end];
1470        let record = match kind {
1471            STREAM_DATA => {
1472                if body.len() < 2 || self.data_records >= STREAM_MAX_DATA_RECORDS {
1473                    return Err(if body.len() < 2 {
1474                        Error::MalformedEnvelope
1475                    } else {
1476                        Error::LimitExceeded
1477                    });
1478                }
1479                let clear = compression::decode(body[0], &body[1..], STREAM_DATA_LEN)?;
1480                let next_total = self
1481                    .total_bytes
1482                    .checked_add(clear.len() as u64)
1483                    .ok_or(Error::LimitExceeded)?;
1484                if next_total > self.max_upload_bytes
1485                    || self
1486                        .content_length
1487                        .is_some_and(|length| next_total > length)
1488                {
1489                    return Err(Error::LimitExceeded);
1490                }
1491                self.total_bytes = next_total;
1492                self.data_records += 1;
1493                self.sequence += 1;
1494                StreamRequestRecord::Data(clear)
1495            }
1496            STREAM_END => {
1497                if !body.is_empty()
1498                    || self
1499                        .content_length
1500                        .is_some_and(|length| self.total_bytes != length)
1501                {
1502                    return Err(Error::MalformedEnvelope);
1503                }
1504                self.ended = true;
1505                StreamRequestRecord::End
1506            }
1507            _ => return Err(Error::MalformedEnvelope),
1508        };
1509        self.frame.clear();
1510        Ok((used, Some(record)))
1511    }
1512
1513    /// Call after the host observes real outer EOF. Check END and release the
1514    /// response right. This method cannot observe transport EOF itself.
1515    ///
1516    /// # Errors
1517    /// Returns an error for a missing END or partial frame.
1518    pub fn finish_eof(mut self) -> Result<ResponseCapability, Error> {
1519        if !self.ended || !self.frame.is_empty() || self.context.is_none() {
1520            return Err(Error::MalformedEnvelope);
1521        }
1522        Ok(ResponseCapability(
1523            self.response_material
1524                .take()
1525                .ok_or(Error::MalformedEnvelope)?,
1526        ))
1527    }
1528}
1529
1530impl Server {
1531    /// Return the START boundary once its bounded frame length is known.
1532    ///
1533    /// # Errors
1534    /// Returns a parse, version, suite, or size error.
1535    pub fn stream_start_length(&self, input: &[u8]) -> Result<Option<usize>, Error> {
1536        stream_start_length(input, max_stream_start_ciphertext(self.limits))
1537    }
1538
1539    /// Read the exact public prefix and START frame before credential lookup.
1540    ///
1541    /// # Errors
1542    /// Returns a parse, limit, or recipient-key error.
1543    pub fn preparse_stream(&self, first: &[u8]) -> Result<PreparsedStreamRequest, Error> {
1544        let parsed = parse_stream_start(first, max_stream_start_ciphertext(self.limits))?;
1545        self.private_key_for(parsed.key_id)?;
1546        Ok(PreparsedStreamRequest {
1547            credential: CredentialRequest {
1548                psk_id: parsed.psk_id.to_vec(),
1549            },
1550            token: StreamStartToken {
1551                first: first.to_vec(),
1552            },
1553        })
1554    }
1555
1556    /// Authenticate START, then pause before replay admission.
1557    ///
1558    /// # Errors
1559    /// Returns a credential, time, authentication, or parse error.
1560    pub fn authenticate_stream(
1561        &self,
1562        token: StreamStartToken,
1563        psk: &[u8],
1564    ) -> Result<AuthenticatedStreamRequest, Error> {
1565        self.authenticate_stream_at(token, psk, unix_time_seconds()?)
1566    }
1567
1568    /// Authenticate START at a caller-supplied trusted Unix time.
1569    ///
1570    /// # Errors
1571    /// Returns a credential, time, authentication, or parse error.
1572    pub fn authenticate_stream_at(
1573        &self,
1574        token: StreamStartToken,
1575        psk: &[u8],
1576        now_unix_s: u64,
1577    ) -> Result<AuthenticatedStreamRequest, Error> {
1578        let StreamStartToken { first } = token;
1579        let max_upload_bytes = self.limits.max_request_bytes;
1580        let parsed = parse_stream_start(&first, max_stream_start_ciphertext(self.limits))?;
1581        let private_key = self.private_key_for(parsed.key_id)?;
1582        validate_credential(psk, parsed.psk_id).map_err(|_| Error::InvalidCredential)?;
1583        let encapped_key = <Kem as KemTrait>::EncappedKey::from_bytes(parsed.enc)
1584            .map_err(|_| Error::AuthenticationFailed)?;
1585        let psk_bundle =
1586            PskBundle::new(psk, parsed.psk_id).map_err(|_| Error::InvalidCredential)?;
1587        let mut context = setup_receiver::<HpkeAead, Kdf, Kem>(
1588            &OpModeR::Psk(psk_bundle),
1589            private_key,
1590            &encapped_key,
1591            &stream_request_info(parsed.header),
1592        )
1593        .map_err(|_| Error::AuthenticationFailed)?;
1594        let mut clear = parsed.ciphertext.to_vec();
1595        let plain_end = clear.len() - TAG_LEN;
1596        let tag = AeadTag::<HpkeAead>::from_bytes(&clear[plain_end..])
1597            .map_err(|_| Error::AuthenticationFailed)?;
1598        let aad = stream_record_aad(
1599            parsed.header,
1600            0,
1601            u32::try_from(clear.len()).map_err(|_| Error::LimitExceeded)?,
1602        );
1603        context
1604            .open_inout_detached(InOutBuf::from(&mut clear[..plain_end]), &aad, &tag)
1605            .map_err(|_| Error::AuthenticationFailed)?;
1606        if clear.first() != Some(&STREAM_START) {
1607            return Err(Error::MalformedEnvelope);
1608        }
1609        let head = message::decode_stream_head(&clear[1..plain_end], self.limits)?;
1610        let content_length = message::stream_content_length(&head.headers)?;
1611        if content_length.is_some_and(|length| length > max_upload_bytes) {
1612            return Err(Error::LimitExceeded);
1613        }
1614        let retain_until_exclusive =
1615            replay_retention_deadline(parsed.issued_at_unix_s, now_unix_s)?;
1616        let replay_id = replay_id(parsed.header, parsed.enc);
1617        let response_material =
1618            export_response_material(&context, parsed.enc, head.method, self.limits)?;
1619        Ok(AuthenticatedStreamRequest {
1620            replay: ReplayRequest {
1621                id: replay_id,
1622                retain_until_exclusive,
1623            },
1624            token: StreamReplayToken {
1625                replay_id,
1626                retain_until_exclusive,
1627                head,
1628                reader: StreamRequestOpener {
1629                    context: Some(context),
1630                    header: parsed.header.to_vec(),
1631                    sequence: 1,
1632                    data_records: 0,
1633                    total_bytes: 0,
1634                    content_length,
1635                    max_upload_bytes,
1636                    frame: Vec::new(),
1637                    ended: false,
1638                    response_material: Some(response_material),
1639                },
1640            },
1641        })
1642    }
1643}