From 86a6a350167b528e4e589719d0c75942d7f7f4db Mon Sep 17 00:00:00 2001 From: Yury Yarashevich Date: Wed, 20 Nov 2024 12:01:40 +0100 Subject: [PATCH] #2057: Use randomly generated GREASE transport parameter. --- quinn-proto/src/endpoint.rs | 2 + quinn-proto/src/transport_parameters.rs | 137 +++++++++++++++++++++++- 2 files changed, 135 insertions(+), 4 deletions(-) diff --git a/quinn-proto/src/endpoint.rs b/quinn-proto/src/endpoint.rs index fc652a8bc..bd18b011a 100644 --- a/quinn-proto/src/endpoint.rs +++ b/quinn-proto/src/endpoint.rs @@ -411,6 +411,7 @@ impl Endpoint { self.local_cid_generator.as_ref(), loc_cid, None, + &mut self.rng, ); let tls = config .crypto @@ -632,6 +633,7 @@ impl Endpoint { self.local_cid_generator.as_ref(), loc_cid, Some(&server_config), + &mut self.rng, ); params.stateless_reset_token = Some(ResetToken::new(&*self.config.reset_key, &loc_cid)); params.original_dst_cid = Some(incoming.orig_dst_cid); diff --git a/quinn-proto/src/transport_parameters.rs b/quinn-proto/src/transport_parameters.rs index aa279d48f..396a7076a 100644 --- a/quinn-proto/src/transport_parameters.rs +++ b/quinn-proto/src/transport_parameters.rs @@ -12,6 +12,7 @@ use std::{ }; use bytes::{Buf, BufMut}; +use rand::Rng as _; use thiserror::Error; use crate::{ @@ -99,6 +100,10 @@ macro_rules! make_struct { pub(crate) stateless_reset_token: Option, /// The server's preferred address for communication after handshake completion pub(crate) preferred_address: Option, + /// The randomly generated reserved transport parameter to sustain future extensibility + /// of transport parameter extension. + /// When present included during serialization, bug ignored during deserialization + pub(crate) grease_transport_parameter: Option, } // We deliberately don't implement the `Default` trait, since that would be public, and @@ -120,6 +125,7 @@ macro_rules! make_struct { retry_src_cid: None, stateless_reset_token: None, preferred_address: None, + grease_transport_parameter: None, } } } @@ -129,12 +135,13 @@ macro_rules! make_struct { apply_params!(make_struct); impl TransportParameters { - pub(crate) fn new( + pub(crate) fn new( config: &TransportConfig, endpoint_config: &EndpointConfig, cid_gen: &dyn ConnectionIdGenerator, initial_src_cid: ConnectionId, server_config: Option<&ServerConfig>, + rng: &mut R, ) -> Self { Self { initial_src_cid: Some(initial_src_cid), @@ -160,6 +167,7 @@ impl TransportParameters { min_ack_delay: Some( VarInt::from_u64(u64::try_from(TIMER_GRANULARITY.as_micros()).unwrap()).unwrap(), ), + grease_transport_parameter: Some(ReservedTransportParameter::random(rng)), ..Self::default() } } @@ -300,9 +308,9 @@ impl TransportParameters { } apply_params!(write_params); - // Add a reserved parameter to keep people on their toes - w.write_var(31 * 5 + 27); - w.write_var(0); + if let Some(param) = self.grease_transport_parameter { + param.write(w); + } if let Some(ref x) = self.stateless_reset_token { w.write_var(0x02); @@ -467,6 +475,81 @@ impl TransportParameters { } } +/// A reserved transport parameter. +/// +/// It has identifier of the form 31 * N + 27 for integer value of N. +/// Such identifiers reserved to exercise the requirement that unknown transport parameters be ignored. +/// The reserved transport parameter have no semantics and can carry arbitrary values. +/// It may be included into quic_transport_parameters TLS extensions when sent and should be ignored when received. +/// +/// See spec: https://www.rfc-editor.org/rfc/rfc9000.html#section-18.1 +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(crate) struct ReservedTransportParameter { + /// The reserved identifier of transport parameter + id: VarInt, + + /// Buffer to store parameter payload + payload: [u8; Self::MAX_PAYLOAD_LEN], + + /// The number of bytes to include into wire format from the `payload` buffer + payload_len: usize, +} + +impl ReservedTransportParameter { + /// The maximum length of payload to include as parameter payload. + /// This value is not a specification-imposed limit and picked such that + /// it matches the limit by other implementations of quic, e.g. quic-go & quiche. + const MAX_PAYLOAD_LEN: usize = 16; + + // Generate a random reserved identifier of the form `31 * N + 27` as required by RFC 9000. + // Reserved transport parameter identifiers are used to test compliance with the requirement + // that unknown transport parameters be ignored by peers. + // See: https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2 + fn generate_reserved_id(rng: &mut R) -> VarInt { + let id = { + let rand = rng.gen_range(0u64..(1 << 62) - 27); + let n = rand / 31; + 31 * n + 27 + }; + debug_assert!( + id % 31 == 27, + "generated id does not have the form of 31 * N + 27" + ); + VarInt::from_u64(id).expect( + "generated id does fit into range of allowed transport parameter IDs: [0; 2^62)", + ) + } + + /// Generate transport parameter with random payload and reserved id. + /// + /// Implementation is inspired by quic-go & quiche: + /// [1] https://github.com/quic-go/quic-go/blob/3e0a67b2476e1819752f04d75968de042b197b56/internal/wire/transport_parameters.go#L338-L344 + /// [2] https://github.com/google/quiche/blob/cb1090b20c40e2f0815107857324e99acf6ec567/quiche/quic/core/crypto/transport_parameters.cc#L843-L860 + fn random(rng: &mut R) -> Self { + let id = Self::generate_reserved_id(rng); + + let payload_len = rng.gen_range(0..Self::MAX_PAYLOAD_LEN); + + let payload = { + let mut slice = [0u8; Self::MAX_PAYLOAD_LEN]; + rng.fill_bytes(&mut slice[..payload_len]); + slice + }; + + Self { + id, + payload, + payload_len, + } + } + + fn write(&self, w: &mut W) { + w.write_var(self.id.0); + w.write_var(self.payload_len as u64); + w.put_slice(&self.payload[..self.payload_len]); + } +} + fn decode_cid(len: usize, value: &mut Option, r: &mut impl Buf) -> Result<(), Error> { if len > MAX_CID_SIZE || value.is_some() || r.remaining() < len { return Err(Error::Malformed); @@ -507,6 +590,52 @@ mod test { ); } + #[test] + fn reserved_transport_parameter_generate_reserved_id() { + use rand::rngs::mock::StepRng; + let mut rngs = [ + StepRng::new(0, 1), + StepRng::new(1, 1), + StepRng::new(27, 1), + StepRng::new(31, 1), + StepRng::new(u32::MAX as u64, 1), + StepRng::new(u32::MAX as u64 - 1, 1), + StepRng::new(u32::MAX as u64 + 1, 1), + StepRng::new(u32::MAX as u64 - 27, 1), + StepRng::new(u32::MAX as u64 + 27, 1), + StepRng::new(u32::MAX as u64 - 31, 1), + StepRng::new(u32::MAX as u64 + 31, 1), + StepRng::new(u64::MAX, 1), + StepRng::new(u64::MAX - 1, 1), + StepRng::new(u64::MAX - 27, 1), + StepRng::new(u64::MAX - 31, 1), + StepRng::new(1 << 62, 1), + StepRng::new((1 << 62) - 1, 1), + StepRng::new((1 << 62) + 1, 1), + StepRng::new((1 << 62) - 27, 1), + StepRng::new((1 << 62) + 27, 1), + StepRng::new((1 << 62) - 31, 1), + StepRng::new((1 << 62) + 31, 1), + ]; + for rng in &mut rngs { + let id = ReservedTransportParameter::generate_reserved_id(rng); + assert!(id.0 % 31 == 27) + } + } + + #[test] + fn reserved_transport_parameter_ignored_when_read() { + let mut buf = Vec::new(); + let reserved_parameter = ReservedTransportParameter::random(&mut rand::thread_rng()); + assert!(reserved_parameter.payload_len < ReservedTransportParameter::MAX_PAYLOAD_LEN); + assert!(reserved_parameter.id.0 % 31 == 27); + + reserved_parameter.write(&mut buf); + assert!(!buf.is_empty()); + let read_params = TransportParameters::read(Side::Server, &mut buf.as_slice()).unwrap(); + assert_eq!(read_params, TransportParameters::default()); + } + #[test] fn read_semantic_validation() { #[allow(clippy::type_complexity)]