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