1use chacha20poly1305::{
4 ChaCha20Poly1305,
5 aead::{AeadInOut, KeyInit, array::Array},
6};
7
8use crate::{
9 EntropySource, Error, HeaderField, Method, Response,
10 codec::{RESPONSE_NONCE_LEN, TAG_LEN, VERSION},
11 compression,
12 engine::{ResponseMaterial, derive_response_key_nonce},
13 message,
14 sse::validate_block,
15};
16
17const MAGIC: &[u8; 4] = b"HHRP";
18const PREFIX_LEN: usize = 4 + 1 + RESPONSE_NONCE_LEN;
19const AAD_DOMAIN: &[u8] = b"hpke-http/3 response record\0";
20const AAD_LEN: usize = AAD_DOMAIN.len() + PREFIX_LEN + 8 + 4;
21const START: u8 = 1;
22const DATA: u8 = 2;
23const END: u8 = 3;
24const MIN_CIPHERTEXT_LEN: usize = 1 + TAG_LEN;
25const RETAIN_FRAME_CAPACITY: usize = 64 * 1024;
26
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub enum ResponseMode {
30 Finite,
32 Sse,
34}
35
36#[derive(Clone, Debug, Eq, PartialEq)]
38pub struct ResponseHead {
39 pub status: u16,
41 pub headers: Vec<HeaderField>,
43 pub mode: ResponseMode,
45}
46
47#[derive(Debug, Eq, PartialEq)]
49pub enum ResponseRecord {
50 Start(ResponseHead),
52 SseData(Vec<u8>),
54 End,
56}
57
58struct Crypto {
59 key: zeroize::Zeroizing<[u8; 32]>,
60 base_nonce: zeroize::Zeroizing<[u8; 12]>,
61 prefix: [u8; PREFIX_LEN],
62 sequence: u64,
63}
64
65impl Crypto {
66 fn new(material: &ResponseMaterial, prefix: [u8; PREFIX_LEN]) -> Result<Self, Error> {
67 let (key, base_nonce) = derive_response_key_nonce(material, &prefix[5..])?;
68 Ok(Self {
69 key,
70 base_nonce,
71 prefix,
72 sequence: 0,
73 })
74 }
75
76 fn nonce(&self) -> [u8; 12] {
77 let mut nonce = *self.base_nonce;
78 for (target, byte) in nonce[4..].iter_mut().zip(self.sequence.to_be_bytes()) {
79 *target ^= byte;
80 }
81 nonce
82 }
83
84 fn aad(&self, ciphertext_len: u32) -> [u8; AAD_LEN] {
85 let mut aad = [0; AAD_LEN];
86 let prefix_end = AAD_DOMAIN.len() + PREFIX_LEN;
87 aad[..AAD_DOMAIN.len()].copy_from_slice(AAD_DOMAIN);
88 aad[AAD_DOMAIN.len()..prefix_end].copy_from_slice(&self.prefix);
89 aad[prefix_end..prefix_end + 8].copy_from_slice(&self.sequence.to_be_bytes());
90 aad[prefix_end + 8..].copy_from_slice(&ciphertext_len.to_be_bytes());
91 aad
92 }
93
94 fn seal_record(
95 &mut self,
96 kind: u8,
97 coding: Option<u8>,
98 body: &[u8],
99 terminal: bool,
100 ) -> Result<Vec<u8>, Error> {
101 if self.sequence == u64::MAX && !terminal {
102 return Err(Error::LimitExceeded);
103 }
104 let length = body
105 .len()
106 .checked_add(1 + usize::from(coding.is_some()))
107 .and_then(|size| size.checked_add(TAG_LEN))
108 .ok_or(Error::LimitExceeded)?;
109 let length = u32::try_from(length).map_err(|_| Error::LimitExceeded)?;
110 let frame_len = 4_usize
111 .checked_add(length as usize)
112 .ok_or(Error::LimitExceeded)?;
113 let mut framed = Vec::with_capacity(frame_len);
114 framed.extend_from_slice(&length.to_be_bytes());
115 framed.push(kind);
116 if let Some(coding) = coding {
117 framed.push(coding);
118 }
119 framed.extend_from_slice(body);
120 let cipher = ChaCha20Poly1305::new(&Array(*self.key));
121 let tag = cipher
122 .encrypt_inout_detached(
123 &Array(self.nonce()),
124 &self.aad(length),
125 (&mut framed[4..]).into(),
126 )
127 .map_err(|_| Error::CryptoFailure)?;
128 framed.extend_from_slice(&tag);
129 if !terminal {
130 self.sequence += 1;
131 }
132 Ok(framed)
133 }
134
135 fn open(&mut self, ciphertext: &mut Vec<u8>, length: u32) -> Result<(), Error> {
136 let cipher = ChaCha20Poly1305::new(&Array(*self.key));
137 cipher
138 .decrypt_in_place(&Array(self.nonce()), &self.aad(length), ciphertext)
139 .map_err(|_| Error::AuthenticationFailed)
140 }
141
142 fn advance(&mut self) -> Result<(), Error> {
143 self.sequence = self.sequence.checked_add(1).ok_or(Error::LimitExceeded)?;
144 Ok(())
145 }
146}
147
148fn classify_head(
149 status: u16,
150 headers: &[HeaderField],
151 material: &ResponseMaterial,
152) -> Result<ResponseMode, Error> {
153 message::validate_response_head(status, headers, material.limits)?;
154 let mut content_type = None;
155 for field in headers {
156 if field.name == b"content-type" && content_type.replace(field.value.as_slice()).is_some() {
157 return Err(Error::InvalidConfiguration);
158 }
159 }
160 let is_sse = content_type.is_some_and(|value| {
161 value
162 .split(|byte| *byte == b';')
163 .next()
164 .unwrap_or_default()
165 .trim_ascii()
166 .eq_ignore_ascii_case(b"text/event-stream")
167 });
168 if is_sse {
169 if status != 200
170 || material.method == Method::Head
171 || headers
172 .iter()
173 .any(|field| field.name == b"content-length" || field.name == b"content-encoding")
174 {
175 return Err(Error::InvalidConfiguration);
176 }
177 Ok(ResponseMode::Sse)
178 } else {
179 Ok(ResponseMode::Finite)
180 }
181}
182
183pub struct ResponseSealer {
185 crypto: Option<Crypto>,
186 head: ResponseHead,
187 method: Method,
188 limits: crate::Limits,
189 finite_data_sent: bool,
190 done: bool,
191}
192
193impl ResponseSealer {
194 #[must_use]
196 pub const fn head_mode(&self) -> ResponseMode {
197 self.head.mode
198 }
199
200 pub(crate) fn new(
201 material: &ResponseMaterial,
202 status: u16,
203 headers: Vec<HeaderField>,
204 entropy: &mut impl EntropySource,
205 ) -> Result<(Self, Vec<u8>), Error> {
206 let mode = classify_head(status, &headers, material)?;
207 let mut prefix = [0_u8; PREFIX_LEN];
208 prefix[..4].copy_from_slice(MAGIC);
209 prefix[4] = VERSION;
210 entropy.fill(&mut prefix[5..])?;
211 let mut crypto = Crypto::new(material, prefix)?;
212 let fields = message::encode_fields(&headers)?;
213 let mut start = Vec::with_capacity(2 + fields.len());
214 start.extend_from_slice(&status.to_be_bytes());
215 start.extend_from_slice(&fields);
216 let frame = crypto.seal_record(START, None, &start, false)?;
217 let mut output = Vec::with_capacity(PREFIX_LEN + frame.len());
218 output.extend_from_slice(&prefix);
219 output.extend_from_slice(&frame);
220 Ok((
221 Self {
222 crypto: Some(crypto),
223 head: ResponseHead {
224 status,
225 headers,
226 mode,
227 },
228 method: material.method,
229 limits: material.limits,
230 finite_data_sent: false,
231 done: false,
232 },
233 output,
234 ))
235 }
236
237 pub fn seal_sse_block(&mut self, block: &[u8]) -> Result<Vec<u8>, Error> {
242 let result = self.seal_sse_block_inner(block);
243 if result.is_err() {
244 self.poison();
245 }
246 result
247 }
248
249 fn seal_sse_block_inner(&mut self, block: &[u8]) -> Result<Vec<u8>, Error> {
250 if self.done || self.head.mode != ResponseMode::Sse {
251 return Err(Error::InvalidConfiguration);
252 }
253 validate_block(block, self.limits.max_body_len)?;
254 let (coding, coded) = compression::encode(block);
255 self.crypto
256 .as_mut()
257 .ok_or(Error::InvalidConfiguration)?
258 .seal_record(DATA, Some(coding), &coded, false)
259 }
260
261 pub fn seal_finite_body(&mut self, body: &[u8]) -> Result<Option<Vec<u8>>, Error> {
266 let result = self.seal_finite_body_inner(body);
267 if result.is_err() {
268 self.poison();
269 }
270 result
271 }
272
273 fn seal_finite_body_inner(&mut self, body: &[u8]) -> Result<Option<Vec<u8>>, Error> {
274 if self.done || self.head.mode != ResponseMode::Finite || self.finite_data_sent {
275 return Err(Error::InvalidConfiguration);
276 }
277 if body.len() > self.limits.max_body_len {
278 return Err(Error::LimitExceeded);
279 }
280 message::validate_response_content_length(
281 self.head.status,
282 &self.head.headers,
283 body.len(),
284 self.method,
285 )?;
286 if !body.is_empty()
287 && (self.method == Method::Head || matches!(self.head.status, 204 | 205 | 304))
288 {
289 return Err(Error::InvalidConfiguration);
290 }
291 if body.is_empty() {
292 self.finite_data_sent = true;
293 return Ok(None);
294 }
295 let (coding, encoded) = compression::encode(body);
296 let frame = self
297 .crypto
298 .as_mut()
299 .ok_or(Error::InvalidConfiguration)?
300 .seal_record(DATA, Some(coding), &encoded, false)?;
301 self.finite_data_sent = true;
302 Ok(Some(frame))
303 }
304
305 pub fn finish(&mut self) -> Result<Vec<u8>, Error> {
310 if self.done || (self.head.mode == ResponseMode::Finite && !self.finite_data_sent) {
311 self.poison();
312 return Err(Error::InvalidConfiguration);
313 }
314 let result = self
315 .crypto
316 .as_mut()
317 .ok_or(Error::InvalidConfiguration)?
318 .seal_record(END, None, &[], true);
319 self.poison();
320 result
321 }
322
323 pub fn close(&mut self) {
325 self.poison();
326 }
327
328 fn poison(&mut self) {
329 self.crypto = None;
330 self.done = true;
331 }
332}
333
334pub struct ResponseOpener {
336 material: Option<ResponseMaterial>,
337 crypto: Option<Crypto>,
338 prefix: Vec<u8>,
339 length: Vec<u8>,
340 frame: Vec<u8>,
341 expected: Option<usize>,
342 head: Option<ResponseHead>,
343 finite_body: Option<Vec<u8>>,
344 finite_response: Option<Response>,
345 done: bool,
346 failed: bool,
347 eof_checked: bool,
348}
349
350impl ResponseOpener {
351 pub(crate) fn new(material: ResponseMaterial) -> Self {
352 Self {
353 material: Some(material),
354 crypto: None,
355 prefix: Vec::with_capacity(PREFIX_LEN),
356 length: Vec::with_capacity(4),
357 frame: Vec::new(),
358 expected: None,
359 head: None,
360 finite_body: None,
361 finite_response: None,
362 done: false,
363 failed: false,
364 eof_checked: false,
365 }
366 }
367
368 pub fn feed(&mut self, input: &[u8]) -> Result<(usize, Option<ResponseRecord>), Error> {
375 let result = self.feed_inner(input);
376 if result.is_err() {
377 self.poison();
378 }
379 result
380 }
381
382 fn feed_inner(&mut self, input: &[u8]) -> Result<(usize, Option<ResponseRecord>), Error> {
383 if self.failed || self.eof_checked || (self.done && !input.is_empty()) {
384 return Err(Error::MalformedEnvelope);
385 }
386 let mut used = 0;
387 if used < input.len() {
388 if self.crypto.is_none() {
389 let take = (PREFIX_LEN - self.prefix.len()).min(input.len() - used);
390 self.prefix.extend_from_slice(&input[used..used + take]);
391 used += take;
392 if self.prefix.len() < PREFIX_LEN {
393 return Ok((used, None));
394 }
395 if self.prefix[..4] != *MAGIC || self.prefix[4] != VERSION {
396 return Err(Error::MalformedEnvelope);
397 }
398 let prefix: [u8; PREFIX_LEN] = self
399 .prefix
400 .as_slice()
401 .try_into()
402 .map_err(|_| Error::MalformedEnvelope)?;
403 let material = self.material.as_ref().ok_or(Error::MalformedEnvelope)?;
404 self.crypto = Some(Crypto::new(material, prefix)?);
405 }
406 if self.expected.is_none() {
407 let take = (4 - self.length.len()).min(input.len() - used);
408 self.length.extend_from_slice(&input[used..used + take]);
409 used += take;
410 if self.length.len() < 4 {
411 return Ok((used, None));
412 }
413 let length = u32::from_be_bytes(
414 self.length
415 .as_slice()
416 .try_into()
417 .map_err(|_| Error::MalformedEnvelope)?,
418 ) as usize;
419 let material = self.material.as_ref().ok_or(Error::MalformedEnvelope)?;
420 let maximum = if self.head.is_none() {
421 4 + material.limits.max_header_bytes
422 + material.limits.max_header_count * 16
423 + TAG_LEN
424 } else {
425 2 + material.limits.max_body_len + TAG_LEN
426 };
427 if length < MIN_CIPHERTEXT_LEN {
428 return Err(Error::MalformedEnvelope);
429 }
430 if length > maximum {
431 return Err(Error::LimitExceeded);
432 }
433 self.expected = Some(length);
434 }
435 let expected = self.expected.ok_or(Error::MalformedEnvelope)?;
436 let take = (expected - self.frame.len()).min(input.len() - used);
437 self.frame.extend_from_slice(&input[used..used + take]);
438 used += take;
439 if self.frame.len() < expected {
440 return Ok((used, None));
441 }
442 let record = self.open_frame()?;
443 if self.frame.capacity() > RETAIN_FRAME_CAPACITY {
444 self.frame = Vec::new();
445 } else {
446 self.frame.clear();
447 }
448 self.length.clear();
449 self.expected = None;
450 if self.done && used < input.len() {
451 return Err(Error::MalformedEnvelope);
452 }
453 return Ok((used, record));
454 }
455 Ok((used, None))
456 }
457
458 fn open_frame(&mut self) -> Result<Option<ResponseRecord>, Error> {
459 let length = u32::try_from(self.frame.len()).map_err(|_| Error::LimitExceeded)?;
460 self.crypto
461 .as_mut()
462 .ok_or(Error::MalformedEnvelope)?
463 .open(&mut self.frame, length)?;
464 let (&kind, body) = self.frame.split_first().ok_or(Error::MalformedEnvelope)?;
465 if self.head.is_none() {
466 if kind != START || body.len() < 2 {
467 return Err(Error::MalformedEnvelope);
468 }
469 let status = u16::from_be_bytes([body[0], body[1]]);
470 let material = self.material.as_ref().ok_or(Error::MalformedEnvelope)?;
471 let headers =
472 message::decode_fields(&body[2..], material.limits).map_err(decode_error)?;
473 let mode = classify_head(status, &headers, material).map_err(decode_error)?;
474 let head = ResponseHead {
475 status,
476 headers,
477 mode,
478 };
479 self.head = Some(head.clone());
480 self.crypto
481 .as_mut()
482 .ok_or(Error::MalformedEnvelope)?
483 .advance()?;
484 return Ok(Some(ResponseRecord::Start(head)));
485 }
486 let head = self.head.as_ref().ok_or(Error::MalformedEnvelope)?;
487 match kind {
488 DATA => {
489 if body.len() < 2 {
490 return Err(Error::MalformedEnvelope);
491 }
492 if head.mode == ResponseMode::Sse {
493 let material = self.material.as_ref().ok_or(Error::MalformedEnvelope)?;
494 let block =
495 compression::decode(body[0], &body[1..], material.limits.max_body_len)?;
496 validate_block(&block, material.limits.max_body_len)?;
497 self.crypto
498 .as_mut()
499 .ok_or(Error::MalformedEnvelope)?
500 .advance()?;
501 Ok(Some(ResponseRecord::SseData(block)))
502 } else {
503 if self.finite_body.is_some() {
504 return Err(Error::MalformedEnvelope);
505 }
506 self.crypto
507 .as_mut()
508 .ok_or(Error::MalformedEnvelope)?
509 .advance()?;
510 let material = self.material.as_ref().ok_or(Error::MalformedEnvelope)?;
511 self.finite_body = Some(compression::decode(
512 body[0],
513 &body[1..],
514 material.limits.max_body_len,
515 )?);
516 Ok(None)
517 }
518 }
519 END if body.is_empty() && length as usize == MIN_CIPHERTEXT_LEN => {
520 if head.mode == ResponseMode::Finite {
521 self.finish_finite()?;
522 }
523 self.done = true;
524 self.crypto = None;
525 Ok(Some(ResponseRecord::End))
526 }
527 _ => Err(Error::MalformedEnvelope),
528 }
529 }
530
531 fn finish_finite(&mut self) -> Result<(), Error> {
532 let head = self.head.as_ref().ok_or(Error::MalformedEnvelope)?;
533 let material = self.material.as_ref().ok_or(Error::MalformedEnvelope)?;
534 let body = self.finite_body.take().unwrap_or_default();
535 let response = Response {
536 status: head.status,
537 headers: head.headers.clone(),
538 body,
539 };
540 message::validate_decoded_response_body(&response, material.method)?;
541 if !response.body.is_empty()
542 && (material.method == Method::Head || matches!(response.status, 204 | 205 | 304))
543 {
544 return Err(Error::MalformedEnvelope);
545 }
546 self.finite_response = Some(response);
547 Ok(())
548 }
549
550 pub fn finish_eof(&mut self) -> Result<Option<Response>, Error> {
555 if self.failed || !self.done || self.eof_checked {
556 self.poison();
557 return Err(Error::MalformedEnvelope);
558 }
559 self.eof_checked = true;
560 self.material = None;
561 Ok(self.finite_response.take())
562 }
563
564 pub fn close(&mut self) {
566 self.poison();
567 }
568
569 fn poison(&mut self) {
570 self.crypto = None;
571 self.material = None;
572 self.frame = Vec::new();
573 self.finite_body = None;
574 self.finite_response = None;
575 self.failed = true;
576 }
577}
578
579const fn decode_error(error: Error) -> Error {
580 match error {
581 Error::InvalidConfiguration => Error::MalformedEnvelope,
582 other => other,
583 }
584}
585
586#[cfg(test)]
587mod tests {
588 use super::*;
589
590 #[test]
591 fn final_counter_value_is_reserved_for_end() -> Result<(), Error> {
592 let material = ResponseMaterial {
593 secret: zeroize::Zeroizing::new([7; 32]),
594 enc: [8; 32],
595 method: Method::Get,
596 limits: crate::Limits::default(),
597 };
598 let mut prefix = [0; PREFIX_LEN];
599 prefix[..4].copy_from_slice(MAGIC);
600 prefix[4] = VERSION;
601 let mut writer = Crypto::new(&material, prefix)?;
602 writer.sequence = u64::MAX - 1;
603 let data = writer.seal_record(DATA, None, b"x", false)?;
604 assert_eq!(writer.sequence, u64::MAX);
605 assert_eq!(
606 writer.seal_record(DATA, None, b"y", false),
607 Err(Error::LimitExceeded)
608 );
609 let end = writer.seal_record(END, None, &[], true)?;
610 let mut reader = Crypto::new(&material, prefix)?;
611 reader.sequence = u64::MAX - 1;
612 let mut data_body = data[4..].to_vec();
613 reader.open(
614 &mut data_body,
615 u32::from_be_bytes(data[..4].try_into().map_err(|_| Error::MalformedEnvelope)?),
616 )?;
617 assert_eq!(data_body, [DATA, b'x']);
618 reader.advance()?;
619 let mut end_body = end[4..].to_vec();
620 reader.open(
621 &mut end_body,
622 u32::from_be_bytes(end[..4].try_into().map_err(|_| Error::MalformedEnvelope)?),
623 )?;
624 assert_eq!(end_body, [END]);
625 assert_eq!(reader.advance(), Err(Error::LimitExceeded));
626 Ok(())
627 }
628}