1use 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;
45pub const STREAM_DATA_LEN: usize = 64 * 1024;
47const 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
60pub 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 #[must_use]
79 pub fn into_parts(self) -> (Zeroizing<Vec<u8>>, Vec<u8>) {
80 (self.private_key, self.public_key)
81 }
82
83 #[must_use]
85 pub fn public_key(&self) -> &[u8] {
86 &self.public_key
87 }
88}
89
90pub fn generate_key_pair() -> Result<KeyPair, Error> {
96 generate_key_pair_with_entropy(&mut SystemEntropy)
97}
98
99pub 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#[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 #[must_use]
134 pub fn envelope(&self) -> &[u8] {
135 &self.envelope
136 }
137
138 pub fn into_parts(self) -> (Vec<u8>, ResponseToken) {
140 (self.envelope, self.response_token)
141 }
142}
143
144pub 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 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 pub fn protect(&self, request: &Request) -> Result<ProtectedRequest, Error> {
201 self.protect_at_with_entropy(request, unix_time_seconds()?, &mut SystemEntropy)
202 }
203
204 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 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 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#[derive(Clone, Debug, Eq, PartialEq)]
274pub struct CredentialRequest {
275 pub psk_id: Vec<u8>,
277}
278
279#[must_use]
281pub struct PreparsedRequest {
282 pub credential: CredentialRequest,
284 pub token: StartToken,
286}
287
288#[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#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
305pub struct ReplayRequest {
306 pub id: [u8; 32],
308 pub retain_until_exclusive: u64,
310}
311
312impl ReplayRequest {
313 #[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#[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#[must_use]
352pub struct AuthenticatedRequest {
353 pub replay: ReplayRequest,
355 pub token: ReplayToken,
357}
358
359#[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 pub fn admit(self, decision: ReplayDecision) -> Result<OpenedRequest, Error> {
391 self.admit_at(decision, unix_time_seconds()?)
392 }
393
394 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#[must_use]
423pub struct OpenedRequest {
424 pub request: Request,
426 pub response: ResponseCapability,
428}
429
430pub struct Server {
432 recipient_private_key: <Kem as KemTrait>::PrivateKey,
433 recipient_key_id: Vec<u8>,
434 limits: Limits,
435}
436
437impl fmt::Debug for Server {
438 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
439 formatter
440 .debug_struct("Server")
441 .field("recipient_private_key", &"[REDACTED]")
442 .field("recipient_key_id_len", &self.recipient_key_id.len())
443 .field("limits", &self.limits)
444 .finish()
445 }
446}
447
448impl Server {
449 pub fn new(
456 recipient_private_key: &[u8],
457 recipient_key_id: Vec<u8>,
458 limits: Limits,
459 ) -> Result<Self, Error> {
460 validate_id(&recipient_key_id)?;
461 let limits = limits.validate()?;
462 let recipient_private_key =
463 <Kem as KemTrait>::PrivateKey::from_bytes(recipient_private_key)
464 .map_err(|_| Error::InvalidConfiguration)?;
465 Ok(Self {
466 recipient_private_key,
467 recipient_key_id,
468 limits,
469 })
470 }
471
472 #[must_use]
474 pub fn public_key(&self) -> Vec<u8> {
475 Kem::sk_to_pk(&self.recipient_private_key)
476 .to_bytes()
477 .to_vec()
478 }
479
480 pub fn max_complete_envelope_len(&self) -> Result<usize, Error> {
488 max_complete_envelope_len(self.limits)
489 }
490
491 pub fn preparse(&self, envelope: &[u8]) -> Result<PreparsedRequest, Error> {
497 let credential = self.preparse_credential(envelope)?;
498 Ok(PreparsedRequest {
499 credential,
500 token: StartToken {
501 envelope: envelope.to_vec(),
502 },
503 })
504 }
505
506 pub fn preparse_owned(&self, envelope: Vec<u8>) -> Result<PreparsedRequest, Error> {
516 let credential = self.preparse_credential(&envelope)?;
517 Ok(PreparsedRequest {
518 credential,
519 token: StartToken { envelope },
520 })
521 }
522
523 fn preparse_credential(&self, envelope: &[u8]) -> Result<CredentialRequest, Error> {
524 if envelope.len() > self.max_complete_envelope_len()? {
525 return Err(Error::LimitExceeded);
526 }
527 let first_len = self
528 .stream_start_length(envelope)?
529 .ok_or(Error::MalformedEnvelope)?;
530 if first_len > envelope.len() {
531 return Err(Error::MalformedEnvelope);
532 }
533 let parsed = parse_stream_start(
534 &envelope[..first_len],
535 max_stream_start_ciphertext(self.limits),
536 )?;
537 if parsed.key_id != self.recipient_key_id {
538 return Err(Error::UnknownRecipientKey);
539 }
540 Ok(CredentialRequest {
541 psk_id: parsed.psk_id.to_vec(),
542 })
543 }
544
545 pub fn authenticate(
554 &self,
555 token: StartToken,
556 psk: &[u8],
557 ) -> Result<AuthenticatedRequest, Error> {
558 self.authenticate_at(token, psk, unix_time_seconds()?)
559 }
560
561 pub fn authenticate_at(
567 &self,
568 token: StartToken,
569 psk: &[u8],
570 now_unix_s: u64,
571 ) -> Result<AuthenticatedRequest, Error> {
572 let StartToken { envelope } = token;
573 let first_len = self
574 .stream_start_length(&envelope)?
575 .ok_or(Error::MalformedEnvelope)?;
576 if first_len > envelope.len() {
577 return Err(Error::MalformedEnvelope);
578 }
579 let authenticated = self.authenticate_stream_at(
580 StreamStartToken {
581 first: envelope[..first_len].to_vec(),
582 },
583 psk,
584 now_unix_s,
585 )?;
586 let replay = authenticated.replay;
587 let StreamReplayToken {
588 head, mut reader, ..
589 } = authenticated.token;
590 let mut body = Vec::new();
591 let mut remaining = &envelope[first_len..];
592 while !remaining.is_empty() {
593 let (used, record) = reader.feed(remaining)?;
594 remaining = &remaining[used..];
595 if let Some(StreamRequestRecord::Data(part)) = record {
596 if part.len() > self.limits.max_body_len.saturating_sub(body.len()) {
597 return Err(Error::LimitExceeded);
598 }
599 body.extend_from_slice(&part);
600 }
601 }
602 let response = reader.finish_eof()?;
603 let request = Request {
604 method: head.method,
605 authority: head.authority,
606 path: head.path,
607 headers: head.headers,
608 body,
609 };
610 Ok(AuthenticatedRequest {
611 replay,
612 token: ReplayToken {
613 replay_id: replay.id,
614 retain_until_exclusive: replay.retain_until_exclusive,
615 request,
616 response,
617 },
618 })
619 }
620}
621
622#[cfg(test)]
623mod stream_tests {
624 use super::STREAM_MAX_DATA_RECORDS;
625 use crate::{Client, Error, Limits, Method, RequestHead, Server, generate_key_pair};
626
627 #[test]
628 fn data_record_count_guard_closes_both_sides() -> Result<(), Error> {
629 let keys = generate_key_pair()?;
630 let psk = b"a 32-byte minimum upload credential";
631 let client = Client::new(
632 keys.public_key(),
633 b"key".to_vec(),
634 psk.to_vec(),
635 b"psk".to_vec(),
636 Limits::default(),
637 )?;
638 let server = Server::new(&keys.into_parts().0, b"key".to_vec(), Limits::default())?;
639 let head = RequestHead {
640 method: Method::Post,
641 authority: b"api.example.test".to_vec(),
642 path: b"/upload".to_vec(),
643 headers: Vec::new(),
644 };
645 let (mut writer, _) = client.begin_stream(&head)?;
646 writer.data_records = STREAM_MAX_DATA_RECORDS;
647 assert_eq!(writer.push(b"x").err(), Some(Error::LimitExceeded));
648 assert!(writer.context.is_none());
649
650 let (mut writer, first) = client.begin_stream(&head)?;
651 let (used, record) = writer.push(b"x")?;
652 assert_eq!(used, 1);
653 assert!(record.is_none());
654 let (data, _) = writer.finish()?;
655 let preparsed = server.preparse_stream(&first)?;
656 let authenticated = server.authenticate_stream(preparsed.token, psk)?;
657 let mut reader = authenticated
658 .token
659 .admit(authenticated.replay.decision(true))?
660 .reader;
661 reader.data_records = STREAM_MAX_DATA_RECORDS;
662 assert_eq!(reader.feed(&data).err(), Some(Error::LimitExceeded));
663 assert!(reader.context.is_none());
664 assert!(reader.finish_eof().is_err());
665 Ok(())
666 }
667}
668
669pub(crate) struct ResponseMaterial {
670 pub(crate) secret: Zeroizing<[u8; RESPONSE_SECRET_LEN]>,
671 pub(crate) enc: [u8; ENC_LEN],
672 pub(crate) method: Method,
673 pub(crate) limits: Limits,
674}
675
676impl fmt::Debug for ResponseMaterial {
677 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
678 formatter
679 .debug_struct("ResponseMaterial")
680 .field("secret", &"[REDACTED]")
681 .field("enc_len", &self.enc.len())
682 .field("method", &self.method)
683 .field("limits", &self.limits)
684 .finish()
685 }
686}
687
688#[must_use]
690pub struct ResponseToken(ResponseMaterial);
691
692impl fmt::Debug for ResponseToken {
693 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
694 formatter
695 .debug_tuple("ResponseToken")
696 .field(&self.0)
697 .finish()
698 }
699}
700
701impl ResponseToken {
702 #[must_use]
704 pub fn into_opener(self) -> ResponseOpener {
705 ResponseOpener::new(self.0)
706 }
707
708 pub fn open_finite(self, envelope: &[u8]) -> Result<Response, Error> {
713 let mut opener = self.into_opener();
714 let mut offset = 0;
715 while offset < envelope.len() {
716 let (used, record) = opener.feed(&envelope[offset..])?;
717 offset += used;
718 if matches!(record, Some(ResponseRecord::Start(ref head)) if head.mode == ResponseMode::Sse)
719 {
720 return Err(Error::InvalidConfiguration);
721 }
722 if used == 0 {
723 return Err(Error::MalformedEnvelope);
724 }
725 }
726 opener.finish_eof()?.ok_or(Error::MalformedEnvelope)
727 }
728}
729
730#[must_use]
732pub struct ResponseCapability(ResponseMaterial);
733
734impl fmt::Debug for ResponseCapability {
735 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
736 formatter
737 .debug_tuple("ResponseCapability")
738 .field(&self.0)
739 .finish()
740 }
741}
742
743impl ResponseCapability {
744 pub fn into_sealer(
749 self,
750 status: u16,
751 headers: Vec<HeaderField>,
752 ) -> Result<(ResponseSealer, Vec<u8>), Error> {
753 self.into_sealer_with_entropy(status, headers, &mut SystemEntropy)
754 }
755
756 pub fn into_sealer_with_entropy(
761 self,
762 status: u16,
763 headers: Vec<HeaderField>,
764 entropy: &mut impl EntropySource,
765 ) -> Result<(ResponseSealer, Vec<u8>), Error> {
766 ResponseSealer::new(&self.0, status, headers, entropy)
767 }
768
769 pub fn protect_finite(self, response: &Response) -> Result<Vec<u8>, Error> {
774 self.protect_finite_with_entropy(response, &mut SystemEntropy)
775 }
776
777 pub fn protect_finite_with_entropy(
782 self,
783 response: &Response,
784 entropy: &mut impl EntropySource,
785 ) -> Result<Vec<u8>, Error> {
786 self.protect_finite_parts_with_entropy(
787 response.status,
788 response.headers.clone(),
789 &response.body,
790 entropy,
791 )
792 }
793
794 pub fn protect_finite_parts(
799 self,
800 status: u16,
801 headers: Vec<HeaderField>,
802 body: &[u8],
803 ) -> Result<Vec<u8>, Error> {
804 self.protect_finite_parts_with_entropy(status, headers, body, &mut SystemEntropy)
805 }
806
807 fn protect_finite_parts_with_entropy(
808 self,
809 status: u16,
810 headers: Vec<HeaderField>,
811 body: &[u8],
812 entropy: &mut impl EntropySource,
813 ) -> Result<Vec<u8>, Error> {
814 let (mut sealer, mut output) = self.into_sealer_with_entropy(status, headers, entropy)?;
815 if sealer.head_mode() != ResponseMode::Finite {
816 return Err(Error::InvalidConfiguration);
817 }
818 let data = sealer.seal_finite_body(body)?;
819 let end = sealer.finish()?;
820 output.reserve(data.as_ref().map_or(0, Vec::len) + end.len());
821 if let Some(data) = data {
822 output.extend_from_slice(&data);
823 }
824 output.extend_from_slice(&end);
825 Ok(output)
826 }
827}
828
829fn validate_id(identifier: &[u8]) -> Result<(), Error> {
830 if identifier.is_empty() || identifier.len() > HARD_MAX_ID_LEN {
831 return Err(Error::InvalidConfiguration);
832 }
833 Ok(())
834}
835
836fn validate_credential(psk: &[u8], psk_id: &[u8]) -> Result<(), Error> {
837 validate_id(psk_id)?;
838 if psk.len() < MIN_PSK_LEN || psk == psk_id {
839 return Err(Error::InvalidConfiguration);
840 }
841 Ok(())
842}
843
844fn unix_time_seconds() -> Result<u64, Error> {
845 SystemTime::now()
846 .duration_since(UNIX_EPOCH)
847 .map_err(|_| Error::ClockUnavailable)
848 .map(|duration| duration.as_secs())
849}
850
851fn replay_retention_deadline(issued_at_unix_s: u64, now_unix_s: u64) -> Result<u64, Error> {
852 let latest_issued_at = now_unix_s
853 .checked_add(CLOCK_SKEW_SECS)
854 .ok_or(Error::InvalidRequestTime)?;
855 let retain_until_exclusive = issued_at_unix_s
856 .checked_add(REQUEST_LIFETIME_SECS)
857 .and_then(|value| value.checked_add(CLOCK_SKEW_SECS))
858 .ok_or(Error::InvalidRequestTime)?;
859 if issued_at_unix_s > latest_issued_at || now_unix_s >= retain_until_exclusive {
860 return Err(Error::InvalidRequestTime);
861 }
862 Ok(retain_until_exclusive)
863}
864
865fn replay_id(header: &[u8], enc: &[u8]) -> [u8; 32] {
866 let mut digest = Sha256::new();
867 digest.update(REPLAY_LABEL);
868 digest.update(header);
869 digest.update(enc);
870 digest.finalize().into()
871}
872
873fn export_response_material<C>(
874 context: &C,
875 enc: &[u8],
876 method: Method,
877 limits: Limits,
878) -> Result<ResponseMaterial, Error>
879where
880 C: Exporter,
881{
882 let enc: [u8; ENC_LEN] = enc.try_into().map_err(|_| Error::CryptoFailure)?;
883 let mut secret = Zeroizing::new([0_u8; RESPONSE_SECRET_LEN]);
884 context.export_secret(RESPONSE_EXPORT_LABEL, secret.as_mut())?;
885 Ok(ResponseMaterial {
886 secret,
887 enc,
888 method,
889 limits,
890 })
891}
892
893pub(crate) fn derive_response_key_nonce(
894 material: &ResponseMaterial,
895 response_nonce: &[u8],
896) -> Result<(ResponseKey, ResponseNonce), Error> {
897 if response_nonce.len() != RESPONSE_NONCE_LEN {
898 return Err(Error::MalformedEnvelope);
899 }
900 let mut salt = Zeroizing::new(Vec::with_capacity(ENC_LEN + RESPONSE_NONCE_LEN));
901 salt.extend_from_slice(&material.enc);
902 salt.extend_from_slice(response_nonce);
903 let hkdf = Hkdf::<Sha256>::new(Some(&salt), material.secret.as_ref());
904 let mut key = Zeroizing::new([0_u8; RESPONSE_KEY_LEN]);
905 hkdf.expand(RESPONSE_KEY_INFO, key.as_mut())
906 .map_err(|_| Error::CryptoFailure)?;
907 let mut nonce = Zeroizing::new([0_u8; AEAD_NONCE_LEN]);
908 hkdf.expand(RESPONSE_NONCE_INFO, nonce.as_mut())
909 .map_err(|_| Error::CryptoFailure)?;
910 Ok((key, nonce))
911}
912
913trait Exporter {
914 fn export_secret(&self, context: &[u8], output: &mut [u8]) -> Result<(), Error>;
915}
916
917impl Exporter for hpke::aead::AeadCtxS<HpkeAead, Kdf, Kem> {
918 fn export_secret(&self, context: &[u8], output: &mut [u8]) -> Result<(), Error> {
919 self.export(context, output)
920 .map_err(|_| Error::CryptoFailure)
921 }
922}
923
924impl Exporter for RecipientContext {
925 fn export_secret(&self, context: &[u8], output: &mut [u8]) -> Result<(), Error> {
926 self.export(context, output)
927 .map_err(|_| Error::CryptoFailure)
928 }
929}
930
931fn max_stream_start_ciphertext(limits: Limits) -> usize {
932 limits.max_header_bytes + limits.max_target_len + limits.max_header_count * 16 + 256 + TAG_LEN
933}
934
935fn max_complete_envelope_len(limits: Limits) -> Result<usize, Error> {
936 let records = limits
937 .max_body_len
938 .min(usize::try_from(STREAM_MAX_DATA_RECORDS).map_err(|_| Error::LimitExceeded)?);
939 let data_overhead = records
940 .checked_mul(4 + 2 + TAG_LEN)
941 .ok_or(Error::LimitExceeded)?;
942 REQUEST_FIXED_LEN
943 .checked_add(2 * HARD_MAX_ID_LEN)
944 .and_then(|length| length.checked_add(ENC_LEN + 4))
945 .and_then(|length| length.checked_add(max_stream_start_ciphertext(limits)))
946 .and_then(|length| length.checked_add(limits.max_body_len))
947 .and_then(|length| length.checked_add(data_overhead))
948 .and_then(|length| length.checked_add(4 + 1 + TAG_LEN))
949 .ok_or(Error::LimitExceeded)
950}
951
952fn stream_request_info(header: &[u8]) -> Vec<u8> {
953 let mut info = Vec::with_capacity(STREAM_REQUEST_INFO_LABEL.len() + header.len());
954 info.extend_from_slice(STREAM_REQUEST_INFO_LABEL);
955 info.extend_from_slice(header);
956 info
957}
958
959fn stream_record_aad(header: &[u8], sequence: u64, ciphertext_len: u32) -> Vec<u8> {
960 let mut aad = Vec::with_capacity(STREAM_REQUEST_AAD_LABEL.len() + header.len() + 12);
961 aad.extend_from_slice(STREAM_REQUEST_AAD_LABEL);
962 aad.extend_from_slice(header);
963 aad.extend_from_slice(&sequence.to_be_bytes());
964 aad.extend_from_slice(&ciphertext_len.to_be_bytes());
965 aad
966}
967
968fn seal_stream_record(
969 context: &mut SenderContext,
970 header: &[u8],
971 sequence: u64,
972 kind: u8,
973 coding: Option<u8>,
974 body: &[u8],
975) -> Result<Vec<u8>, Error> {
976 let ciphertext_len = body
977 .len()
978 .checked_add(1 + usize::from(coding.is_some()) + TAG_LEN)
979 .ok_or(Error::LimitExceeded)?;
980 let ciphertext_len = u32::try_from(ciphertext_len).map_err(|_| Error::LimitExceeded)?;
981 let mut frame = Vec::with_capacity(4 + ciphertext_len as usize);
982 frame.extend_from_slice(&ciphertext_len.to_be_bytes());
983 frame.push(kind);
984 if let Some(coding) = coding {
985 frame.push(coding);
986 }
987 frame.extend_from_slice(body);
988 let aad = stream_record_aad(header, sequence, ciphertext_len);
989 let tag = context
990 .seal_inout_detached(InOutBuf::from(&mut frame[4..]), &aad)
991 .map_err(|_| Error::CryptoFailure)?;
992 frame.extend_from_slice(tag.to_bytes().as_ref());
993 Ok(frame)
994}
995
996pub struct StreamRequestSealer {
998 context: Option<SenderContext>,
999 header: Vec<u8>,
1000 sequence: u64,
1001 data_records: u64,
1002 total_bytes: u64,
1003 content_length: Option<u64>,
1004 max_upload_bytes: u64,
1005 pending: Vec<u8>,
1006 response_token: Option<ResponseToken>,
1007}
1008
1009impl fmt::Debug for StreamRequestSealer {
1010 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1011 formatter
1012 .debug_struct("StreamRequestSealer")
1013 .field("sequence", &self.sequence)
1014 .field("total_bytes", &self.total_bytes)
1015 .finish_non_exhaustive()
1016 }
1017}
1018
1019impl StreamRequestSealer {
1020 pub fn push(&mut self, part: &[u8]) -> Result<(usize, Option<Vec<u8>>), Error> {
1027 let result = self.push_inner(part);
1028 if result.is_err() {
1029 self.context = None;
1030 self.response_token = None;
1031 }
1032 result
1033 }
1034
1035 fn push_inner(&mut self, part: &[u8]) -> Result<(usize, Option<Vec<u8>>), Error> {
1036 if self.context.is_none() {
1037 return Err(Error::InvalidConfiguration);
1038 }
1039 if part.is_empty() {
1040 return Ok((0, None));
1041 }
1042 if self.data_records >= STREAM_MAX_DATA_RECORDS {
1043 return Err(Error::LimitExceeded);
1044 }
1045 let consumed = part.len().min(STREAM_DATA_LEN - self.pending.len());
1046 let next_total = self
1047 .total_bytes
1048 .checked_add(consumed as u64)
1049 .ok_or(Error::LimitExceeded)?;
1050 if next_total > self.max_upload_bytes
1051 || self
1052 .content_length
1053 .is_some_and(|length| next_total > length)
1054 {
1055 return Err(Error::LimitExceeded);
1056 }
1057 self.pending.extend_from_slice(&part[..consumed]);
1058 self.total_bytes = next_total;
1059 if self.pending.len() == STREAM_DATA_LEN {
1060 let frame = self.seal_pending()?;
1061 Ok((consumed, Some(frame)))
1062 } else {
1063 Ok((consumed, None))
1064 }
1065 }
1066
1067 fn seal_pending(&mut self) -> Result<Vec<u8>, Error> {
1068 if self.pending.is_empty() || self.data_records >= STREAM_MAX_DATA_RECORDS {
1069 return Err(Error::LimitExceeded);
1070 }
1071 let (coding, coded) = compression::encode(&self.pending);
1072 let context = self.context.as_mut().ok_or(Error::InvalidConfiguration)?;
1073 let frame = seal_stream_record(
1074 context,
1075 &self.header,
1076 self.sequence,
1077 STREAM_DATA,
1078 Some(coding),
1079 &coded,
1080 )?;
1081 self.pending.clear();
1082 self.sequence += 1;
1083 self.data_records += 1;
1084 Ok(frame)
1085 }
1086
1087 pub fn finish(mut self) -> Result<(Vec<u8>, ResponseToken), Error> {
1092 if self
1093 .content_length
1094 .is_some_and(|length| self.total_bytes != length)
1095 {
1096 return Err(Error::InvalidConfiguration);
1097 }
1098 let mut frame = if self.pending.is_empty() {
1099 Vec::new()
1100 } else {
1101 self.seal_pending()?
1102 };
1103 let context = self.context.as_mut().ok_or(Error::InvalidConfiguration)?;
1104 frame.extend_from_slice(&seal_stream_record(
1105 context,
1106 &self.header,
1107 self.sequence,
1108 STREAM_END,
1109 None,
1110 &[],
1111 )?);
1112 let token = self
1113 .response_token
1114 .take()
1115 .ok_or(Error::InvalidConfiguration)?;
1116 Ok((frame, token))
1117 }
1118}
1119
1120impl Client {
1121 pub fn begin_stream(
1126 &self,
1127 head: &RequestHead,
1128 ) -> Result<(StreamRequestSealer, Vec<u8>), Error> {
1129 self.begin_stream_at_with_entropy(head, unix_time_seconds()?, &mut SystemEntropy)
1130 }
1131
1132 pub fn begin_stream_at_with_entropy(
1137 &self,
1138 head: &RequestHead,
1139 now_unix_s: u64,
1140 entropy: &mut impl EntropySource,
1141 ) -> Result<(StreamRequestSealer, Vec<u8>), Error> {
1142 let max_upload_bytes = self.limits.max_request_bytes;
1143 let content_length = message::stream_content_length(&head.headers)?;
1144 if content_length.is_some_and(|length| length > max_upload_bytes) {
1145 return Err(Error::LimitExceeded);
1146 }
1147 let clear_head = message::encode_stream_head(head, self.limits)?;
1148 let header = encode_request_header(&self.recipient_key_id, &self.psk_id, now_unix_s)?;
1149 let psk_bundle = PskBundle::new(self.psk.as_slice(), &self.psk_id)
1150 .map_err(|_| Error::InvalidConfiguration)?;
1151 let mut random_seed = Zeroizing::new([0_u8; 32]);
1152 entropy.fill(random_seed.as_mut())?;
1153 let mut random = ChaCha20Rng::from_seed(*random_seed);
1154 let (encapped_key, mut context) = setup_sender_with_rng::<HpkeAead, Kdf, Kem>(
1155 &OpModeS::Psk(psk_bundle),
1156 &self.recipient_public_key,
1157 &stream_request_info(&header),
1158 &mut random,
1159 )
1160 .map_err(|_| Error::CryptoFailure)?;
1161 let enc = encapped_key.to_bytes();
1162 let response_material =
1163 export_response_material(&context, enc.as_ref(), head.method, self.limits)?;
1164 let start = seal_stream_record(&mut context, &header, 0, STREAM_START, None, &clear_head)?;
1165 let mut first = Vec::with_capacity(header.len() + ENC_LEN + start.len());
1166 first.extend_from_slice(&header);
1167 first.extend_from_slice(enc.as_ref());
1168 first.extend_from_slice(&start);
1169 Ok((
1170 StreamRequestSealer {
1171 context: Some(context),
1172 header,
1173 sequence: 1,
1174 data_records: 0,
1175 total_bytes: 0,
1176 content_length,
1177 max_upload_bytes,
1178 pending: Vec::with_capacity(STREAM_DATA_LEN),
1179 response_token: Some(ResponseToken(response_material)),
1180 },
1181 first,
1182 ))
1183 }
1184}
1185
1186#[must_use]
1188pub struct PreparsedStreamRequest {
1189 pub credential: CredentialRequest,
1191 pub token: StreamStartToken,
1193}
1194
1195#[must_use]
1197pub struct StreamStartToken {
1198 first: Vec<u8>,
1199}
1200
1201#[must_use]
1203pub struct AuthenticatedStreamRequest {
1204 pub replay: ReplayRequest,
1206 pub token: StreamReplayToken,
1208}
1209
1210#[must_use]
1212pub struct StreamReplayToken {
1213 replay_id: [u8; 32],
1214 retain_until_exclusive: u64,
1215 head: RequestHead,
1216 reader: StreamRequestOpener,
1217}
1218
1219impl StreamReplayToken {
1220 pub fn admit(self, decision: ReplayDecision) -> Result<OpenedStreamRequest, Error> {
1225 self.admit_at(decision, unix_time_seconds()?)
1226 }
1227
1228 pub fn admit_at(
1233 self,
1234 decision: ReplayDecision,
1235 now_unix_s: u64,
1236 ) -> Result<OpenedStreamRequest, Error> {
1237 check_replay_decision(
1238 self.replay_id,
1239 self.retain_until_exclusive,
1240 decision,
1241 now_unix_s,
1242 )?;
1243 Ok(OpenedStreamRequest {
1244 head: self.head,
1245 reader: self.reader,
1246 })
1247 }
1248}
1249
1250#[must_use]
1252pub struct OpenedStreamRequest {
1253 pub head: RequestHead,
1255 pub reader: StreamRequestOpener,
1257}
1258
1259#[derive(Debug, Eq, PartialEq)]
1261pub enum StreamRequestRecord {
1262 Data(Vec<u8>),
1264 End,
1266}
1267
1268pub struct StreamRequestOpener {
1270 context: Option<RecipientContext>,
1271 header: Vec<u8>,
1272 sequence: u64,
1273 data_records: u64,
1274 total_bytes: u64,
1275 content_length: Option<u64>,
1276 max_upload_bytes: u64,
1277 frame: Vec<u8>,
1278 ended: bool,
1279 response_material: Option<ResponseMaterial>,
1280}
1281
1282impl fmt::Debug for StreamRequestOpener {
1283 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1284 formatter
1285 .debug_struct("StreamRequestOpener")
1286 .field("sequence", &self.sequence)
1287 .field("total_bytes", &self.total_bytes)
1288 .field("ended", &self.ended)
1289 .finish_non_exhaustive()
1290 }
1291}
1292
1293impl StreamRequestOpener {
1294 pub fn feed(&mut self, input: &[u8]) -> Result<(usize, Option<StreamRequestRecord>), Error> {
1301 let result = self.feed_inner(input);
1302 if result.is_err() {
1303 self.context = None;
1304 self.response_material = None;
1305 self.frame.clear();
1306 }
1307 result
1308 }
1309
1310 fn feed_inner(&mut self, input: &[u8]) -> Result<(usize, Option<StreamRequestRecord>), Error> {
1311 if self.context.is_none() || (self.ended && !input.is_empty()) {
1312 return Err(Error::MalformedEnvelope);
1313 }
1314 let mut used = 0;
1315 if self.frame.len() < 4 {
1316 let take = (4 - self.frame.len()).min(input.len());
1317 self.frame.extend_from_slice(&input[..take]);
1318 used += take;
1319 if self.frame.len() < 4 {
1320 return Ok((used, None));
1321 }
1322 }
1323 let ciphertext_len = u32::from_be_bytes(
1324 self.frame[..4]
1325 .try_into()
1326 .map_err(|_| Error::MalformedEnvelope)?,
1327 ) as usize;
1328 if ciphertext_len < 1 + TAG_LEN {
1329 return Err(Error::MalformedEnvelope);
1330 }
1331 if ciphertext_len > STREAM_DATA_LEN + 2 + TAG_LEN {
1332 return Err(Error::LimitExceeded);
1333 }
1334 let frame_len = 4 + ciphertext_len;
1335 let take = (frame_len - self.frame.len()).min(input.len() - used);
1336 self.frame.extend_from_slice(&input[used..used + take]);
1337 used += take;
1338 if self.frame.len() < frame_len {
1339 return Ok((used, None));
1340 }
1341 let plain_end = frame_len - TAG_LEN;
1342 let tag = AeadTag::<HpkeAead>::from_bytes(&self.frame[plain_end..])
1343 .map_err(|_| Error::AuthenticationFailed)?;
1344 let aad = stream_record_aad(
1345 &self.header,
1346 self.sequence,
1347 u32::try_from(ciphertext_len).map_err(|_| Error::LimitExceeded)?,
1348 );
1349 self.context
1350 .as_mut()
1351 .ok_or(Error::MalformedEnvelope)?
1352 .open_inout_detached(InOutBuf::from(&mut self.frame[4..plain_end]), &aad, &tag)
1353 .map_err(|_| Error::AuthenticationFailed)?;
1354 let kind = self.frame[4];
1355 let body = &self.frame[5..plain_end];
1356 let record = match kind {
1357 STREAM_DATA => {
1358 if body.len() < 2 || self.data_records >= STREAM_MAX_DATA_RECORDS {
1359 return Err(if body.len() < 2 {
1360 Error::MalformedEnvelope
1361 } else {
1362 Error::LimitExceeded
1363 });
1364 }
1365 let clear = compression::decode(body[0], &body[1..], STREAM_DATA_LEN)?;
1366 let next_total = self
1367 .total_bytes
1368 .checked_add(clear.len() as u64)
1369 .ok_or(Error::LimitExceeded)?;
1370 if next_total > self.max_upload_bytes
1371 || self
1372 .content_length
1373 .is_some_and(|length| next_total > length)
1374 {
1375 return Err(Error::LimitExceeded);
1376 }
1377 self.total_bytes = next_total;
1378 self.data_records += 1;
1379 self.sequence += 1;
1380 StreamRequestRecord::Data(clear)
1381 }
1382 STREAM_END => {
1383 if !body.is_empty()
1384 || self
1385 .content_length
1386 .is_some_and(|length| self.total_bytes != length)
1387 {
1388 return Err(Error::MalformedEnvelope);
1389 }
1390 self.ended = true;
1391 StreamRequestRecord::End
1392 }
1393 _ => return Err(Error::MalformedEnvelope),
1394 };
1395 self.frame.clear();
1396 Ok((used, Some(record)))
1397 }
1398
1399 pub fn finish_eof(mut self) -> Result<ResponseCapability, Error> {
1404 if !self.ended || !self.frame.is_empty() || self.context.is_none() {
1405 return Err(Error::MalformedEnvelope);
1406 }
1407 Ok(ResponseCapability(
1408 self.response_material
1409 .take()
1410 .ok_or(Error::MalformedEnvelope)?,
1411 ))
1412 }
1413}
1414
1415impl Server {
1416 pub fn stream_start_length(&self, input: &[u8]) -> Result<Option<usize>, Error> {
1421 stream_start_length(input, max_stream_start_ciphertext(self.limits))
1422 }
1423
1424 pub fn preparse_stream(&self, first: &[u8]) -> Result<PreparsedStreamRequest, Error> {
1429 let parsed = parse_stream_start(first, max_stream_start_ciphertext(self.limits))?;
1430 if parsed.key_id != self.recipient_key_id {
1431 return Err(Error::UnknownRecipientKey);
1432 }
1433 Ok(PreparsedStreamRequest {
1434 credential: CredentialRequest {
1435 psk_id: parsed.psk_id.to_vec(),
1436 },
1437 token: StreamStartToken {
1438 first: first.to_vec(),
1439 },
1440 })
1441 }
1442
1443 pub fn authenticate_stream(
1448 &self,
1449 token: StreamStartToken,
1450 psk: &[u8],
1451 ) -> Result<AuthenticatedStreamRequest, Error> {
1452 self.authenticate_stream_at(token, psk, unix_time_seconds()?)
1453 }
1454
1455 pub fn authenticate_stream_at(
1460 &self,
1461 token: StreamStartToken,
1462 psk: &[u8],
1463 now_unix_s: u64,
1464 ) -> Result<AuthenticatedStreamRequest, Error> {
1465 let StreamStartToken { first } = token;
1466 let max_upload_bytes = self.limits.max_request_bytes;
1467 let parsed = parse_stream_start(&first, max_stream_start_ciphertext(self.limits))?;
1468 if parsed.key_id != self.recipient_key_id {
1469 return Err(Error::UnknownRecipientKey);
1470 }
1471 validate_credential(psk, parsed.psk_id).map_err(|_| Error::InvalidCredential)?;
1472 let encapped_key = <Kem as KemTrait>::EncappedKey::from_bytes(parsed.enc)
1473 .map_err(|_| Error::AuthenticationFailed)?;
1474 let psk_bundle =
1475 PskBundle::new(psk, parsed.psk_id).map_err(|_| Error::InvalidCredential)?;
1476 let mut context = setup_receiver::<HpkeAead, Kdf, Kem>(
1477 &OpModeR::Psk(psk_bundle),
1478 &self.recipient_private_key,
1479 &encapped_key,
1480 &stream_request_info(parsed.header),
1481 )
1482 .map_err(|_| Error::AuthenticationFailed)?;
1483 let mut clear = parsed.ciphertext.to_vec();
1484 let plain_end = clear.len() - TAG_LEN;
1485 let tag = AeadTag::<HpkeAead>::from_bytes(&clear[plain_end..])
1486 .map_err(|_| Error::AuthenticationFailed)?;
1487 let aad = stream_record_aad(
1488 parsed.header,
1489 0,
1490 u32::try_from(clear.len()).map_err(|_| Error::LimitExceeded)?,
1491 );
1492 context
1493 .open_inout_detached(InOutBuf::from(&mut clear[..plain_end]), &aad, &tag)
1494 .map_err(|_| Error::AuthenticationFailed)?;
1495 if clear.first() != Some(&STREAM_START) {
1496 return Err(Error::MalformedEnvelope);
1497 }
1498 let head = message::decode_stream_head(&clear[1..plain_end], self.limits)?;
1499 let content_length = message::stream_content_length(&head.headers)?;
1500 if content_length.is_some_and(|length| length > max_upload_bytes) {
1501 return Err(Error::LimitExceeded);
1502 }
1503 let retain_until_exclusive =
1504 replay_retention_deadline(parsed.issued_at_unix_s, now_unix_s)?;
1505 let replay_id = replay_id(parsed.header, parsed.enc);
1506 let response_material =
1507 export_response_material(&context, parsed.enc, head.method, self.limits)?;
1508 Ok(AuthenticatedStreamRequest {
1509 replay: ReplayRequest {
1510 id: replay_id,
1511 retain_until_exclusive,
1512 },
1513 token: StreamReplayToken {
1514 replay_id,
1515 retain_until_exclusive,
1516 head,
1517 reader: StreamRequestOpener {
1518 context: Some(context),
1519 header: parsed.header.to_vec(),
1520 sequence: 1,
1521 data_records: 0,
1522 total_bytes: 0,
1523 content_length,
1524 max_upload_bytes,
1525 frame: Vec::new(),
1526 ended: false,
1527 response_material: Some(response_material),
1528 },
1529 },
1530 })
1531 }
1532}