diff --git a/quic/s2n-quic-core/src/crypto/tls.rs b/quic/s2n-quic-core/src/crypto/tls.rs index 88cbf70ff3..79a90f88b4 100644 --- a/quic/s2n-quic-core/src/crypto/tls.rs +++ b/quic/s2n-quic-core/src/crypto/tls.rs @@ -17,10 +17,6 @@ pub mod testing; /// Holds all application parameters which are exchanged within the TLS handshake. #[derive(Debug)] pub struct ApplicationParameters<'a> { - /// The negotiated Application Layer Protocol - pub application_protocol: &'a [u8], - /// Server Name Indication - pub server_name: Option, /// Encoded transport parameters pub transport_parameters: &'a [u8], } @@ -53,6 +49,16 @@ pub trait Context { application_parameters: ApplicationParameters, ) -> Result<(), transport::Error>; + fn on_server_name( + &mut self, + server_name: crate::application::ServerName, + ) -> Result<(), transport::Error>; + + fn on_application_protocol( + &mut self, + application_protocol: Bytes, + ) -> Result<(), transport::Error>; + //= https://www.rfc-editor.org/rfc/rfc9001#section-4.1.1 //# The TLS handshake is considered complete when the //# TLS stack has reported that the handshake is complete. This happens diff --git a/quic/s2n-quic-core/src/crypto/tls/testing.rs b/quic/s2n-quic-core/src/crypto/tls/testing.rs index cc6584ee67..7c49a9b183 100644 --- a/quic/s2n-quic-core/src/crypto/tls/testing.rs +++ b/quic/s2n-quic-core/src/crypto/tls/testing.rs @@ -7,7 +7,7 @@ use crate::{ header_crypto::{LONG_HEADER_MASK, SHORT_HEADER_MASK}, tls, CryptoSuite, HeaderKey, Key, }, - transport, + endpoint, transport, }; use bytes::Bytes; use core::{ @@ -113,12 +113,12 @@ impl Pair { use crate::crypto::InitialKey; let server = server_endpoint.new_server_session(&TEST_SERVER_TRANSPORT_PARAMS); - let mut server_context = Context::default(); + let mut server_context = Context::new(endpoint::Type::Server); server_context.initial.crypto = Some(S::InitialKey::new_server(server_name.as_bytes())); let client = client_endpoint.new_client_session(&TEST_CLIENT_TRANSPORT_PARAMS, server_name.clone()); - let mut client_context = Context::default(); + let mut client_context = Context::new(endpoint::Type::Client); client_context.initial.crypto = Some(C::InitialKey::new_client(server_name.as_bytes())); Self { @@ -215,8 +215,14 @@ impl Pair { TEST_CLIENT_TRANSPORT_PARAMS, "server did not receive the client transport parameters" ); - // TODO fix sni bug in s2n-quic-rustls - // assert_eq!(self.client.1.server_name.as_ref().expect("missing SNI on client"), &self.server_name[..]); + assert_eq!( + self.client + .1 + .server_name + .as_ref() + .expect("missing SNI on client"), + &self.server_name[..] + ); assert_eq!( self.server .1 @@ -240,26 +246,10 @@ pub struct Context { pub server_name: Option, pub application_protocol: Option, pub transport_parameters: Option, + endpoint: endpoint::Type, waker: Waker, } -impl Default for Context { - fn default() -> Self { - let (waker, _wake_counter) = new_count_waker(); - Self { - initial: Space::default(), - handshake: Space::default(), - application: Space::default(), - zero_rtt_crypto: None, - handshake_complete: false, - server_name: None, - application_protocol: None, - transport_parameters: None, - waker, - } - } -} - impl fmt::Debug for Context { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Context") @@ -276,6 +266,22 @@ impl fmt::Debug for Context { } impl Context { + fn new(endpoint: endpoint::Type) -> Self { + let (waker, _wake_counter) = new_count_waker(); + Self { + initial: Space::default(), + handshake: Space::default(), + application: Space::default(), + zero_rtt_crypto: None, + handshake_complete: false, + server_name: None, + application_protocol: None, + transport_parameters: None, + endpoint, + waker, + } + } + /// Transfers incoming and outgoing buffers between two contexts pub fn transfer(&mut self, other: &mut Context) { self.initial.transfer(&mut other.initial); @@ -288,11 +294,10 @@ impl Context { self.assert_done(); other.assert_done(); - // TODO fix sni bug in s2n-quic-rustls - //assert_eq!( - // self.sni, other.sni, - // "sni is not consistent between endpoints" - //); + assert_eq!( + self.server_name, other.server_name, + "sni is not consistent between endpoints" + ); assert_eq!( self.application_protocol, other.application_protocol, "application_protocol is not consistent between endpoints" @@ -322,13 +327,16 @@ impl Context { } fn on_application_params(&mut self, params: tls::ApplicationParameters) { - self.application_protocol = Some(Bytes::copy_from_slice(params.application_protocol)); - self.server_name = params.server_name.map(|sni| sni.into_bytes()); self.transport_parameters = Some(Bytes::copy_from_slice(params.transport_parameters)); } fn log(&self, event: &str) { - eprintln!("{}: {}", core::any::type_name::(), event); + eprintln!( + "{:?}: {}: {}", + self.endpoint, + core::any::type_name::(), + event, + ); } } @@ -507,11 +515,33 @@ impl tls::Context for Context { Ok(()) } + fn on_server_name( + &mut self, + server_name: crate::application::ServerName, + ) -> Result<(), transport::Error> { + self.log("server name"); + self.server_name = Some(server_name.into_bytes()); + Ok(()) + } + + fn on_application_protocol( + &mut self, + application_protocol: Bytes, + ) -> Result<(), transport::Error> { + self.log("application protocol"); + self.application_protocol = Some(application_protocol); + Ok(()) + } + fn on_handshake_complete(&mut self) -> Result<(), transport::Error> { assert!( !self.handshake_complete, "handshake complete called multiple times" ); + assert!( + !self.application_protocol.as_ref().unwrap().is_empty(), + "application_protocol is empty at handshake complete" + ); self.handshake_complete = true; self.log("handshake complete"); Ok(()) diff --git a/quic/s2n-quic-rustls/Cargo.toml b/quic/s2n-quic-rustls/Cargo.toml index 44d81c30de..ab6a491c21 100644 --- a/quic/s2n-quic-rustls/Cargo.toml +++ b/quic/s2n-quic-rustls/Cargo.toml @@ -10,6 +10,7 @@ license = "Apache-2.0" exclude = ["corpus.tar.gz"] [dependencies] +bytes = { version = "1", default-features = false } rustls = { version = "0.20", features = ["quic"] } rustls-pemfile = "0.3" s2n-codec = { version = "=0.1.0", path = "../../common/s2n-codec", default-features = false } diff --git a/quic/s2n-quic-rustls/src/client.rs b/quic/s2n-quic-rustls/src/client.rs index f0ec4b02f3..fa40c62b4e 100644 --- a/quic/s2n-quic-rustls/src/client.rs +++ b/quic/s2n-quic-rustls/src/client.rs @@ -60,18 +60,18 @@ impl tls::Endpoint for Client { //# Endpoints MUST send the quic_transport_parameters extension; let transport_parameters = encode_transport_parameters(transport_parameters); - let server_name = + let rustls_server_name = rustls::ServerName::try_from(server_name.as_ref()).expect("invalid server name"); let session = rustls::ClientConnection::new_quic( self.config.clone(), crate::QUIC_VERSION, - server_name, + rustls_server_name, transport_parameters, ) .expect("could not create rustls client session"); - Session::new(session.into()) + Session::new(session.into(), Some(server_name)) } fn max_tag_length(&self) -> usize { diff --git a/quic/s2n-quic-rustls/src/server.rs b/quic/s2n-quic-rustls/src/server.rs index a809c3466a..e55f20c8e6 100644 --- a/quic/s2n-quic-rustls/src/server.rs +++ b/quic/s2n-quic-rustls/src/server.rs @@ -57,7 +57,7 @@ impl tls::Endpoint for Server { ) .expect("could not create rustls server session"); - Session::new(session.into()) + Session::new(session.into(), None) } fn new_client_session( diff --git a/quic/s2n-quic-rustls/src/session.rs b/quic/s2n-quic-rustls/src/session.rs index 0b0ee425fb..1e92f28ac9 100644 --- a/quic/s2n-quic-rustls/src/session.rs +++ b/quic/s2n-quic-rustls/src/session.rs @@ -4,6 +4,7 @@ use crate::cipher_suite::{ HeaderProtectionKey, HeaderProtectionKeys, OneRttKey, PacketKey, PacketKeys, }; +use bytes::Bytes; use core::{fmt, fmt::Debug, task::Poll}; use rustls::{ quic::{self, QuicExt}, @@ -21,6 +22,9 @@ pub struct Session { tx_phase: HandshakePhase, emitted_zero_rtt_keys: bool, emitted_handshake_complete: bool, + emitted_server_name: bool, + emitted_application_protocol: bool, + server_name: Option, } impl fmt::Debug for Session { @@ -33,13 +37,16 @@ impl fmt::Debug for Session { } impl Session { - pub fn new(connection: Connection) -> Self { + pub fn new(connection: Connection, server_name: Option) -> Self { Self { connection, rx_phase: Default::default(), tx_phase: Default::default(), emitted_zero_rtt_keys: false, emitted_handshake_complete: false, + emitted_server_name: false, + emitted_application_protocol: false, + server_name, } } @@ -72,23 +79,6 @@ impl Session { } fn application_parameters(&self) -> Result { - //= https://www.rfc-editor.org/rfc/rfc9001#section-8.1 - //# Unless - //# another mechanism is used for agreeing on an application protocol, - //# endpoints MUST use ALPN for this purpose. - let application_protocol = self.connection.alpn_protocol().ok_or_else(|| - //= https://www.rfc-editor.org/rfc/rfc9001#section-8.1 - //# When using ALPN, endpoints MUST immediately close a connection (see - //# Section 10.2 of [QUIC-TRANSPORT]) with a no_application_protocol TLS - //# alert (QUIC error code 0x178; see Section 4.8) if an application - //# protocol is not negotiated. - - //= https://www.rfc-editor.org/rfc/rfc9001#section-8.1 - //# While [ALPN] only specifies that servers - //# use this alert, QUIC clients MUST use error 0x178 to terminate a - //# connection when ALPN negotiation fails. - CryptoError::NO_APPLICATION_PROTOCOL.with_reason("Missing ALPN protocol"))?; - //= https://www.rfc-editor.org/rfc/rfc9001#section-8.2 //# endpoints that //# receive ClientHello or EncryptedExtensions messages without the @@ -100,19 +90,34 @@ impl Session { CryptoError::MISSING_EXTENSION.with_reason("Missing QUIC transport parameters") })?; - let server_name = self.server_name(); - Ok(tls::ApplicationParameters { - application_protocol, - server_name, transport_parameters, }) } + //= https://www.rfc-editor.org/rfc/rfc9001#section-8.1 + //# Unless + //# another mechanism is used for agreeing on an application protocol, + //# endpoints MUST use ALPN for this purpose. + // + //= https://www.rfc-editor.org/rfc/rfc7301#section-3.1 + //# Client Server + //# + //# ClientHello --------> ServerHello + //# (ALPN extension & (ALPN extension & + //# list of protocols) selected protocol) + //# [ChangeCipherSpec] + //# <-------- Finished + //# [ChangeCipherSpec] + //# Finished --------> + //# Application Data <-------> Application Data + fn application_protocol(&self) -> Option<&[u8]> { + self.connection.alpn_protocol() + } + fn server_name(&self) -> Option { match &self.connection { - // TODO return the original value - Connection::Client(_) => None, + Connection::Client(_) => self.server_name.clone(), Connection::Server(server) => { server.sni_hostname().map(|server_name| server_name.into()) } @@ -137,6 +142,10 @@ impl Session { context: &mut C, ) -> Poll> { if self.tx_phase == HandshakePhase::Application && !self.connection.is_handshaking() { + // attempt to emit server_name and application_protocol events prior to completing the + // handshake + self.emit_events(context)?; + // the handshake is complete! if !self.emitted_handshake_complete { self.rx_phase.transition(); @@ -152,22 +161,8 @@ impl Session { Poll::Pending } } -} -impl crypto::CryptoSuite for Session { - type HandshakeKey = PacketKeys; - type HandshakeHeaderKey = HeaderProtectionKeys; - type InitialKey = s2n_quic_crypto::initial::InitialKey; - type InitialHeaderKey = s2n_quic_crypto::initial::InitialHeaderKey; - type OneRttKey = OneRttKey; - type OneRttHeaderKey = HeaderProtectionKeys; - type ZeroRttKey = PacketKey; - type ZeroRttHeaderKey = HeaderProtectionKey; - type RetryKey = s2n_quic_crypto::retry::RetryKey; -} - -impl tls::Session for Session { - fn poll>( + fn poll_impl>( &mut self, context: &mut C, ) -> Poll> { @@ -255,6 +250,7 @@ impl tls::Session for Session { } quic::KeyChange::OneRtt { keys, next } => { let (key, header_key) = OneRttKey::new(keys, next, cipher_suite); + let application_parameters = self.application_parameters()?; context.on_one_rtt_keys(key, header_key, application_parameters)?; @@ -268,6 +264,52 @@ impl tls::Session for Session { } } } + + fn emit_events>( + &mut self, + context: &mut C, + ) -> Result<(), transport::Error> { + if !self.emitted_server_name { + if let Some(server_name) = self.server_name() { + context.on_server_name(server_name)?; + self.emitted_server_name = true; + } + } + if !self.emitted_application_protocol { + if let Some(application_protocol) = self.application_protocol() { + let application_protocol = Bytes::copy_from_slice(application_protocol); + context.on_application_protocol(application_protocol)?; + self.emitted_application_protocol = true; + } + } + + Ok(()) + } +} + +impl crypto::CryptoSuite for Session { + type HandshakeKey = PacketKeys; + type HandshakeHeaderKey = HeaderProtectionKeys; + type InitialKey = s2n_quic_crypto::initial::InitialKey; + type InitialHeaderKey = s2n_quic_crypto::initial::InitialHeaderKey; + type OneRttKey = OneRttKey; + type OneRttHeaderKey = HeaderProtectionKeys; + type ZeroRttKey = PacketKey; + type ZeroRttHeaderKey = HeaderProtectionKey; + type RetryKey = s2n_quic_crypto::retry::RetryKey; +} + +impl tls::Session for Session { + fn poll>( + &mut self, + context: &mut C, + ) -> Poll> { + let result = self.poll_impl(context); + // attempt to emit server_name and application_protocol events prior to possibly + // returning with an error + self.emit_events(context)?; + result + } } #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] diff --git a/quic/s2n-quic-tls/src/callback.rs b/quic/s2n-quic-tls/src/callback.rs index f792fc9e9b..3f5d493d25 100644 --- a/quic/s2n-quic-tls/src/callback.rs +++ b/quic/s2n-quic-tls/src/callback.rs @@ -1,7 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use bytes::BytesMut; +use bytes::{Bytes, BytesMut}; use core::{ffi::c_void, marker::PhantomData}; use s2n_quic_core::{ application::ServerName, @@ -30,6 +30,8 @@ pub struct Callback<'a, T, C> { pub suite: PhantomData, pub err: Option, pub send_buffer: &'a mut BytesMut, + pub emitted_server_name: &'a mut bool, + pub server_name: &'a Option, } impl<'a, T, C> Callback<'a, T, C> @@ -113,6 +115,17 @@ where // Flush the send buffer before returning to the connection self.flush(); + // attempt to emit server name after making progress and prior to error handling + if !*self.emitted_server_name { + if let Some(server_name) = self.server_name.clone().or_else(|| { + connection + .server_name() + .map(|server_name| server_name.into()) + }) { + self.context.on_server_name(server_name)?; + *self.emitted_server_name = true; + } + } if let Some(err) = self.err { return Err(err); @@ -205,8 +218,8 @@ where HandshakePhase::Initial => { let (key, header_key) = HandshakeKey::new(self.endpoint, aead_algo, pair) .expect("invalid cipher"); - self.context.on_handshake_keys(key, header_key)?; + self.context.on_handshake_keys(key, header_key)?; self.state.tx_phase.transition(); self.state.rx_phase.transition(); } @@ -216,6 +229,15 @@ where let params = unsafe { // Safety: conn needs to outlive params + // + // TODO use interning for these values + // issue: https://github.com/aws/s2n-quic/issues/248 + // + // Move this event to where `on_server_name` is emitted once we expose + // the functionality in s2n_tls bindings + let application_protocol = + Bytes::copy_from_slice(get_application_protocol(conn)?); + self.context.on_application_protocol(application_protocol)?; get_application_params(conn)? }; @@ -425,13 +447,6 @@ fn get_algo_type( unsafe fn get_application_params<'a>( connection: *mut s2n_connection, ) -> Result, CryptoError> { - //= https://www.rfc-editor.org/rfc/rfc9001#section-8.1 - //# Unless - //# another mechanism is used for agreeing on an application protocol, - //# endpoints MUST use ALPN for this purpose. - let application_protocol = - get_application_protocol(connection).ok_or(CryptoError::NO_APPLICATION_PROTOCOL)?; - //= https://www.rfc-editor.org/rfc/rfc9001#section-8.1 //# When using ALPN, endpoints MUST immediately close a connection (see //# Section 10.2 of [QUIC-TRANSPORT]) with a no_application_protocol TLS @@ -445,29 +460,33 @@ unsafe fn get_application_params<'a>( let transport_parameters = get_transport_parameters(connection).ok_or(CryptoError::MISSING_EXTENSION)?; - let server_name = get_server_name(connection); - Ok(tls::ApplicationParameters { - application_protocol, - server_name, transport_parameters, }) } -unsafe fn get_server_name(connection: *mut s2n_connection) -> Option { - let ptr = s2n_get_server_name(connection).into_result().ok()?; - let data = get_cstr_slice(ptr)?; - - // validate sni is a valid UTF-8 string - let string = core::str::from_utf8(data).ok()?; - Some(string.into()) -} - -unsafe fn get_application_protocol<'a>(connection: *mut s2n_connection) -> Option<&'a [u8]> { - let ptr = s2n_get_application_protocol(connection) - .into_result() - .ok()?; - get_cstr_slice(ptr) +//= https://www.rfc-editor.org/rfc/rfc9001#section-8.1 +//# Unless +//# another mechanism is used for agreeing on an application protocol, +//# endpoints MUST use ALPN for this purpose. +// +//= https://www.rfc-editor.org/rfc/rfc7301#section-3.1 +//# Client Server +//# +//# ClientHello --------> ServerHello +//# (ALPN extension & (ALPN extension & +//# list of protocols) selected protocol) +//# [ChangeCipherSpec] +//# <-------- Finished +//# [ChangeCipherSpec] +//# Finished --------> +//# Application Data <-------> Application Data +unsafe fn get_application_protocol<'a>( + connection: *mut s2n_connection, +) -> Result<&'a [u8], CryptoError> { + let ptr = s2n_get_application_protocol(connection).into_result().ok(); + ptr.and_then(|ptr| get_cstr_slice(ptr)) + .ok_or(CryptoError::MISSING_EXTENSION) } unsafe fn get_transport_parameters<'a>(connection: *mut s2n_connection) -> Option<&'a [u8]> { diff --git a/quic/s2n-quic-tls/src/client.rs b/quic/s2n-quic-tls/src/client.rs index 632e590b47..67f794d64d 100644 --- a/quic/s2n-quic-tls/src/client.rs +++ b/quic/s2n-quic-tls/src/client.rs @@ -123,12 +123,7 @@ impl tls::Endpoint for Client { ) -> Self::Session { let config = self.config.clone(); self.params.with(params, |params| { - let mut session = Session::new(endpoint::Type::Client, config, params).unwrap(); - session - .connection - .set_server_name(&server_name) - .expect("invalid server name value"); - session + Session::new(endpoint::Type::Client, config, params, Some(server_name)).unwrap() }) } diff --git a/quic/s2n-quic-tls/src/server.rs b/quic/s2n-quic-tls/src/server.rs index 745ff689db..71542f1785 100644 --- a/quic/s2n-quic-tls/src/server.rs +++ b/quic/s2n-quic-tls/src/server.rs @@ -138,7 +138,7 @@ impl tls::Endpoint for Server { fn new_server_session(&mut self, params: &Params) -> Self::Session { let config = self.config.clone(); self.params.with(params, |params| { - Session::new(endpoint::Type::Server, config, params).unwrap() + Session::new(endpoint::Type::Server, config, params, None).unwrap() }) } diff --git a/quic/s2n-quic-tls/src/session.rs b/quic/s2n-quic-tls/src/session.rs index 47a36b1abe..c36606a015 100644 --- a/quic/s2n-quic-tls/src/session.rs +++ b/quic/s2n-quic-tls/src/session.rs @@ -5,6 +5,7 @@ use crate::callback::{self, Callback}; use bytes::BytesMut; use core::{marker::PhantomData, task::Poll}; use s2n_quic_core::{ + application::ServerName, crypto::{tls, CryptoError, CryptoSuite}, endpoint, transport, }; @@ -23,10 +24,18 @@ pub struct Session { state: callback::State, handshake_complete: bool, send_buffer: BytesMut, + emitted_server_name: bool, + // This is only set for the client to avoid an extra allocation + server_name: Option, } impl Session { - pub fn new(endpoint: endpoint::Type, config: Config, params: &[u8]) -> Result { + pub fn new( + endpoint: endpoint::Type, + config: Config, + params: &[u8], + server_name: Option, + ) -> Result { let mut connection = Connection::new(match endpoint { endpoint::Type::Server => s2n_mode::SERVER, endpoint::Type::Client => s2n_mode::CLIENT, @@ -38,12 +47,20 @@ impl Session { // QUIC handles sending alerts, so no need to apply TLS blinding connection.set_blinding(s2n_blinding::SELF_SERVICE_BLINDING)?; + if let Some(server_name) = server_name.as_ref() { + connection + .set_server_name(server_name) + .expect("invalid server name value"); + } + Ok(Self { endpoint, connection, state: Default::default(), handshake_complete: false, send_buffer: BytesMut::new(), + emitted_server_name: false, + server_name, }) } } @@ -72,6 +89,8 @@ impl tls::Session for Session { suite: PhantomData, err: None, send_buffer: &mut self.send_buffer, + emitted_server_name: &mut self.emitted_server_name, + server_name: &self.server_name, }; unsafe { diff --git a/quic/s2n-quic-transport/src/connection/connection_impl.rs b/quic/s2n-quic-transport/src/connection/connection_impl.rs index 4bf82fa993..590e99cf59 100644 --- a/quic/s2n-quic-transport/src/connection/connection_impl.rs +++ b/quic/s2n-quic-transport/src/connection/connection_impl.rs @@ -1717,17 +1717,11 @@ impl connection::Trait for ConnectionImpl { } fn server_name(&self) -> Option { - // TODO move SNI to connection - self.space_manager.application()?.server_name.clone() + self.space_manager.server_name.clone() } fn application_protocol(&self) -> Bytes { - // TODO move ALPN to connection - if let Some(space) = self.space_manager.application() { - space.application_protocol.clone() - } else { - Bytes::from_static(&[]) - } + self.space_manager.application_protocol.clone() } fn ping(&mut self) -> Result<(), connection::Error> { diff --git a/quic/s2n-quic-transport/src/space/application.rs b/quic/s2n-quic-transport/src/space/application.rs index 552362f687..463bbd7d36 100644 --- a/quic/s2n-quic-transport/src/space/application.rs +++ b/quic/s2n-quic-transport/src/space/application.rs @@ -15,12 +15,10 @@ use crate::{ sync::flag, transmission, }; -use bytes::Bytes; use core::{convert::TryInto, fmt, marker::PhantomData}; use once_cell::sync::OnceCell; use s2n_codec::EncoderBuffer; use s2n_quic_core::{ - application::ServerName, crypto::{application::KeySet, limited, tls, CryptoSuite}, event::{self, ConnectionPublisher as _, IntoEvent}, frame::{ @@ -62,15 +60,6 @@ pub struct ApplicationSpace { key_set: KeySet<<::Session as CryptoSuite>::OneRttKey>, header_key: <::Session as CryptoSuite>::OneRttHeaderKey, - //= https://www.rfc-editor.org/rfc/rfc9000#section-7 - //# Endpoints MUST explicitly negotiate an application protocol. - - //= https://www.rfc-editor.org/rfc/rfc9001#section-8.1 - //# Unless - //# another mechanism is used for agreeing on an application protocol, - //# endpoints MUST use ALPN for this purpose. - pub application_protocol: Bytes, - pub server_name: Option, ping: flag::Ping, keep_alive: KeepAlive, processed_packet_numbers: SlidingWindow, @@ -81,11 +70,9 @@ impl fmt::Debug for ApplicationSpace { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ApplicationSpace") .field("ack_manager", &self.ack_manager) - .field("application_protocol", &self.application_protocol) .field("ping", &self.ping) .field("processed_packet_numbers", &self.processed_packet_numbers) .field("recovery_manager", &self.recovery_manager) - .field("server_name", &self.server_name) .field("stream_manager", &self.stream_manager) .field("tx_packet_numbers", &self.tx_packet_numbers) .finish() @@ -101,8 +88,6 @@ impl ApplicationSpace { stream_manager: AbstractStreamManager, ack_manager: AckManager, keep_alive: KeepAlive, - server_name: Option, - application_protocol: Bytes, max_mtu: MaxMtu, ) -> Self { let key_set = KeySet::new(key, Self::key_limits(max_mtu)); @@ -114,8 +99,6 @@ impl ApplicationSpace { stream_manager, key_set, header_key, - server_name, - application_protocol, ping: flag::Ping::default(), keep_alive, processed_packet_numbers: SlidingWindow::default(), diff --git a/quic/s2n-quic-transport/src/space/mod.rs b/quic/s2n-quic-transport/src/space/mod.rs index 3f0cf8a427..755d0326ce 100644 --- a/quic/s2n-quic-transport/src/space/mod.rs +++ b/quic/s2n-quic-transport/src/space/mod.rs @@ -16,6 +16,7 @@ use core::{ use s2n_codec::DecoderBufferMut; use s2n_quic_core::{ ack, + application::ServerName, connection::{limits::Limits, InitialId, PeerId}, crypto::{tls, tls::Session, CryptoSuite, Key}, event::{self, IntoEvent}, @@ -63,6 +64,16 @@ pub struct PacketSpaceManager { zero_rtt_crypto: Option::Session as CryptoSuite>::ZeroRttKey>>, handshake_status: HandshakeStatus, + /// Server Name Indication + pub server_name: Option, + //= https://www.rfc-editor.org/rfc/rfc9000#section-7 + //# Endpoints MUST explicitly negotiate an application protocol. + + //= https://www.rfc-editor.org/rfc/rfc9001#section-8.1 + //# Unless + //# another mechanism is used for agreeing on an application protocol, + //# endpoints MUST use ALPN for this purpose. + pub application_protocol: Bytes, } impl fmt::Debug for PacketSpaceManager { @@ -155,6 +166,8 @@ impl PacketSpaceManager { application: None, zero_rtt_crypto: None, handshake_status: HandshakeStatus::default(), + server_name: None, + application_protocol: Bytes::new(), } } @@ -203,6 +216,8 @@ impl PacketSpaceManager { handshake_status: &mut self.handshake_status, local_id_registry, limits, + server_name: &mut self.server_name, + application_protocol: &mut self.application_protocol, waker, publisher, }; diff --git a/quic/s2n-quic-transport/src/space/session_context.rs b/quic/s2n-quic-transport/src/space/session_context.rs index 1f097e0f0b..337f64ef88 100644 --- a/quic/s2n-quic-transport/src/space/session_context.rs +++ b/quic/s2n-quic-transport/src/space/session_context.rs @@ -15,7 +15,9 @@ use core::{ops::Not, task::Waker}; use s2n_codec::{DecoderBuffer, DecoderValue}; use s2n_quic_core::{ ack, + application::ServerName, connection::{InitialId, PeerId}, + crypto, crypto::{tls, CryptoSuite, Key}, ct::ConstantTimeEq, event, @@ -45,6 +47,8 @@ pub struct SessionContext<'a, Config: endpoint::Config, Pub: event::ConnectionPu pub handshake_status: &'a mut HandshakeStatus, pub local_id_registry: &'a mut connection::LocalIdRegistry, pub limits: &'a mut Limits, + pub server_name: &'a mut Option, + pub application_protocol: &'a mut Bytes, pub waker: &'a Waker, pub publisher: &'a mut Pub, } @@ -362,24 +366,6 @@ impl<'a, Config: endpoint::Config, Pub: event::ConnectionPublisher> self.limits.max_keep_alive_period(), ); - // TODO use interning for these values - // issue: https://github.com/aws/s2n-quic/issues/248 - let server_name = application_parameters.server_name; - let application_protocol = - Bytes::copy_from_slice(application_parameters.application_protocol); - - self.publisher.on_application_protocol_information( - event::builder::ApplicationProtocolInformation { - chosen_application_protocol: &application_protocol, - }, - ); - if let Some(chosen_server_name) = &server_name { - self.publisher - .on_server_name_information(event::builder::ServerNameInformation { - chosen_server_name, - }); - }; - let cipher_suite = key.cipher_suite().into_event(); let max_mtu = self.path_manager.max_mtu(); *self.application = Some(Box::new(ApplicationSpace::new( @@ -389,8 +375,6 @@ impl<'a, Config: endpoint::Config, Pub: event::ConnectionPublisher> stream_manager, ack_manager, keep_alive, - server_name, - application_protocol, max_mtu, ))); self.publisher.on_key_update(event::builder::KeyUpdate { @@ -401,6 +385,30 @@ impl<'a, Config: endpoint::Config, Pub: event::ConnectionPublisher> Ok(()) } + fn on_server_name(&mut self, server_name: ServerName) -> Result<(), transport::Error> { + self.publisher + .on_server_name_information(event::builder::ServerNameInformation { + chosen_server_name: &server_name, + }); + *self.server_name = Some(server_name); + + Ok(()) + } + + fn on_application_protocol( + &mut self, + application_protocol: Bytes, + ) -> Result<(), transport::Error> { + self.publisher.on_application_protocol_information( + event::builder::ApplicationProtocolInformation { + chosen_application_protocol: &application_protocol, + }, + ); + *self.application_protocol = application_protocol; + + Ok(()) + } + fn on_handshake_complete(&mut self) -> Result<(), transport::Error> { // After the handshake is complete, the handshake crypto stream should be completely // finished @@ -408,6 +416,23 @@ impl<'a, Config: endpoint::Config, Pub: event::ConnectionPublisher> space.crypto_stream.finish()?; } + if self.application_protocol.is_empty() { + //= https://www.rfc-editor.org/rfc/rfc9001#section-8.1 + //# When using ALPN, endpoints MUST immediately close a connection (see + //# Section 10.2 of [QUIC-TRANSPORT]) with a no_application_protocol TLS + //# alert (QUIC error code 0x178; see Section 4.8) if an application + //# protocol is not negotiated. + + //= https://www.rfc-editor.org/rfc/rfc9001#section-8.1 + //# While [ALPN] only specifies that servers + //# use this alert, QUIC clients MUST use error 0x178 to terminate a + //# connection when ALPN negotiation fails. + let err = crypto::CryptoError::NO_APPLICATION_PROTOCOL + .with_reason("Missing ALPN protocol") + .into(); + return Err(err); + } + self.handshake_status .on_handshake_complete(Config::ENDPOINT_TYPE, self.publisher); diff --git a/specs/www.rfc-editor.org/rfc/rfc7301.txt b/specs/www.rfc-editor.org/rfc/rfc7301.txt new file mode 100644 index 0000000000..717f9f4c74 --- /dev/null +++ b/specs/www.rfc-editor.org/rfc/rfc7301.txt @@ -0,0 +1,507 @@ + + + + + + +Internet Engineering Task Force (IETF) S. Friedl +Request for Comments: 7301 Cisco Systems, Inc. +Category: Standards Track A. Popov +ISSN: 2070-1721 Microsoft Corp. + A. Langley + Google Inc. + E. Stephan + Orange + July 2014 + + + Transport Layer Security (TLS) + Application-Layer Protocol Negotiation Extension + +Abstract + + This document describes a Transport Layer Security (TLS) extension + for application-layer protocol negotiation within the TLS handshake. + For instances in which multiple application protocols are supported + on the same TCP or UDP port, this extension allows the application + layer to negotiate which protocol will be used within the TLS + connection. + +Status of This Memo + + This is an Internet Standards Track document. + + This document is a product of the Internet Engineering Task Force + (IETF). It represents the consensus of the IETF community. It has + received public review and has been approved for publication by the + Internet Engineering Steering Group (IESG). Further information on + Internet Standards is available in Section 2 of RFC 5741. + + Information about the current status of this document, any errata, + and how to provide feedback on it may be obtained at + http://www.rfc-editor.org/info/rfc7301. + + + + + + + + + + + + + + + +Friedl, et al. Standards Track [Page 1] + +RFC 7301 TLS App-Layer Protocol Negotiation Ext July 2014 + + +Copyright Notice + + Copyright (c) 2014 IETF Trust and the persons identified as the + document authors. All rights reserved. + + This document is subject to BCP 78 and the IETF Trust's Legal + Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info) in effect on the date of + publication of this document. Please review these documents + carefully, as they describe your rights and restrictions with respect + to this document. Code Components extracted from this document must + include Simplified BSD License text as described in Section 4.e of + the Trust Legal Provisions and are provided without warranty as + described in the Simplified BSD License. + +Table of Contents + + 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 2 + 2. Requirements Language . . . . . . . . . . . . . . . . . . . . 3 + 3. Application-Layer Protocol Negotiation . . . . . . . . . . . 3 + 3.1. The Application-Layer Protocol Negotiation Extension . . 3 + 3.2. Protocol Selection . . . . . . . . . . . . . . . . . . . 5 + 4. Design Considerations . . . . . . . . . . . . . . . . . . . . 6 + 5. Security Considerations . . . . . . . . . . . . . . . . . . . 6 + 6. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 7 + 7. Acknowledgements . . . . . . . . . . . . . . . . . . . . . . 8 + 8. References . . . . . . . . . . . . . . . . . . . . . . . . . 8 + 8.1. Normative References . . . . . . . . . . . . . . . . . . 8 + 8.2. Informative References . . . . . . . . . . . . . . . . . 8 + +1. Introduction + + Increasingly, application-layer protocols are encapsulated in the TLS + protocol [RFC5246]. This encapsulation enables applications to use + the existing, secure communications links already present on port 443 + across virtually the entire global IP infrastructure. + + When multiple application protocols are supported on a single server- + side port number, such as port 443, the client and the server need to + negotiate an application protocol for use with each connection. It + is desirable to accomplish this negotiation without adding network + round-trips between the client and the server, as each round-trip + will degrade an end-user's experience. Further, it would be + advantageous to allow certificate selection based on the negotiated + application protocol. + + + + + + +Friedl, et al. Standards Track [Page 2] + +RFC 7301 TLS App-Layer Protocol Negotiation Ext July 2014 + + + This document specifies a TLS extension that permits the application + layer to negotiate protocol selection within the TLS handshake. This + work was requested by the HTTPbis WG to address the negotiation of + HTTP/2 ([HTTP2]) over TLS; however, ALPN facilitates negotiation of + arbitrary application-layer protocols. + + With ALPN, the client sends the list of supported application + protocols as part of the TLS ClientHello message. The server chooses + a protocol and sends the selected protocol as part of the TLS + ServerHello message. The application protocol negotiation can thus + be accomplished within the TLS handshake, without adding network + round-trips, and allows the server to associate a different + certificate with each application protocol, if desired. + +2. Requirements Language + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [RFC2119]. + +3. Application-Layer Protocol Negotiation + +3.1. The Application-Layer Protocol Negotiation Extension + + A new extension type ("application_layer_protocol_negotiation(16)") + is defined and MAY be included by the client in its "ClientHello" + message. + + enum { + application_layer_protocol_negotiation(16), (65535) + } ExtensionType; + + The "extension_data" field of the + ("application_layer_protocol_negotiation(16)") extension SHALL + contain a "ProtocolNameList" value. + + opaque ProtocolName<1..2^8-1>; + + struct { + ProtocolName protocol_name_list<2..2^16-1> + } ProtocolNameList; + + "ProtocolNameList" contains the list of protocols advertised by the + client, in descending order of preference. Protocols are named by + IANA-registered, opaque, non-empty byte strings, as described further + in Section 6 ("IANA Considerations") of this document. Empty strings + MUST NOT be included and byte strings MUST NOT be truncated. + + + + +Friedl, et al. Standards Track [Page 3] + +RFC 7301 TLS App-Layer Protocol Negotiation Ext July 2014 + + + Servers that receive a ClientHello containing the + "application_layer_protocol_negotiation" extension MAY return a + suitable protocol selection response to the client. The server will + ignore any protocol name that it does not recognize. A new + ServerHello extension type + ("application_layer_protocol_negotiation(16)") MAY be returned to the + client within the extended ServerHello message. The "extension_data" + field of the ("application_layer_protocol_negotiation(16)") extension + is structured the same as described above for the client + "extension_data", except that the "ProtocolNameList" MUST contain + exactly one "ProtocolName". + + Therefore, a full handshake with the + "application_layer_protocol_negotiation" extension in the ClientHello + and ServerHello messages has the following flow (contrast with + Section 7.3 of [RFC5246]): + + Client Server + + ClientHello --------> ServerHello + (ALPN extension & (ALPN extension & + list of protocols) selected protocol) + Certificate* + ServerKeyExchange* + CertificateRequest* + <-------- ServerHelloDone + Certificate* + ClientKeyExchange + CertificateVerify* + [ChangeCipherSpec] + Finished --------> + [ChangeCipherSpec] + <-------- Finished + Application Data <-------> Application Data + + Figure 1 + + * Indicates optional or situation-dependent messages that are not + always sent. + + + + + + + + + + + + +Friedl, et al. Standards Track [Page 4] + +RFC 7301 TLS App-Layer Protocol Negotiation Ext July 2014 + + + An abbreviated handshake with the + "application_layer_protocol_negotiation" extension has the following + flow: + + Client Server + + ClientHello --------> ServerHello + (ALPN extension & (ALPN extension & + list of protocols) selected protocol) + [ChangeCipherSpec] + <-------- Finished + [ChangeCipherSpec] + Finished --------> + Application Data <-------> Application Data + + Figure 2 + + Unlike many other TLS extensions, this extension does not establish + properties of the session, only of the connection. When session + resumption or session tickets [RFC5077] are used, the previous + contents of this extension are irrelevant, and only the values in the + new handshake messages are considered. + +3.2. Protocol Selection + + It is expected that a server will have a list of protocols that it + supports, in preference order, and will only select a protocol if the + client supports it. In that case, the server SHOULD select the most + highly preferred protocol that it supports and that is also + advertised by the client. In the event that the server supports no + protocols that the client advertises, then the server SHALL respond + with a fatal "no_application_protocol" alert. + + enum { + no_application_protocol(120), + (255) + } AlertDescription; + + The protocol identified in the + "application_layer_protocol_negotiation" extension type in the + ServerHello SHALL be definitive for the connection, until + renegotiated. The server SHALL NOT respond with a selected protocol + and subsequently use a different protocol for application data + exchange. + + + + + + + +Friedl, et al. Standards Track [Page 5] + +RFC 7301 TLS App-Layer Protocol Negotiation Ext July 2014 + + +4. Design Considerations + + The ALPN extension is intended to follow the typical design of TLS + protocol extensions. Specifically, the negotiation is performed + entirely within the client/server hello exchange in accordance with + the established TLS architecture. The + "application_layer_protocol_negotiation" ServerHello extension is + intended to be definitive for the connection (until the connection is + renegotiated) and is sent in plaintext to permit network elements to + provide differentiated service for the connection when the TCP or UDP + port number is not definitive for the application-layer protocol to + be used in the connection. By placing ownership of protocol + selection on the server, ALPN facilitates scenarios in which + certificate selection or connection rerouting may be based on the + negotiated protocol. + + Finally, by managing protocol selection in the clear as part of the + handshake, ALPN avoids introducing false confidence with respect to + the ability to hide the negotiated protocol in advance of + establishing the connection. If hiding the protocol is required, + then renegotiation after connection establishment, which would + provide true TLS security guarantees, would be a preferred + methodology. + +5. Security Considerations + + The ALPN extension does not impact the security of TLS session + establishment or application data exchange. ALPN serves to provide + an externally visible marker for the application-layer protocol + associated with the TLS connection. Historically, the application- + layer protocol associated with a connection could be ascertained from + the TCP or UDP port number in use. + + Implementers and document editors who intend to extend the protocol + identifier registry by adding new protocol identifiers should + consider that in TLS versions 1.2 and below the client sends these + identifiers in the clear. They should also consider that, for at + least the next decade, it is expected that browsers would normally + use these earlier versions of TLS in the initial ClientHello. + + Care must be taken when such identifiers may leak personally + identifiable information, or when such leakage may lead to profiling + or to leaking of sensitive information. If any of these apply to + this new protocol identifier, the identifier SHOULD NOT be used in + TLS configurations where it would be visible in the clear, and + documents specifying such protocol identifiers SHOULD recommend + against such unsafe use. + + + + +Friedl, et al. Standards Track [Page 6] + +RFC 7301 TLS App-Layer Protocol Negotiation Ext July 2014 + + +6. IANA Considerations + + The IANA has updated its "ExtensionType Values" registry to include + the following entry: + + 16 application_layer_protocol_negotiation + + This document establishes a registry for protocol identifiers + entitled "Application-Layer Protocol Negotiation (ALPN) Protocol IDs" + under the existing "Transport Layer Security (TLS) Extensions" + heading. + + Entries in this registry require the following fields: + + o Protocol: The name of the protocol. + o Identification Sequence: The precise set of octet values that + identifies the protocol. This could be the UTF-8 encoding + [RFC3629] of the protocol name. + o Reference: A reference to a specification that defines the + protocol. + + This registry operates under the "Expert Review" policy as defined in + [RFC5226]. The designated expert is advised to encourage the + inclusion of a reference to a permanent and readily available + specification that enables the creation of interoperable + implementations of the identified protocol. + + The initial set of registrations for this registry is as follows: + + Protocol: HTTP/1.1 + Identification Sequence: + 0x68 0x74 0x74 0x70 0x2f 0x31 0x2e 0x31 ("http/1.1") + Reference: [RFC7230] + + Protocol: SPDY/1 + Identification Sequence: + 0x73 0x70 0x64 0x79 0x2f 0x31 ("spdy/1") + Reference: + http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft1 + + Protocol: SPDY/2 + Identification Sequence: + 0x73 0x70 0x64 0x79 0x2f 0x32 ("spdy/2") + Reference: + http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2 + + + + + + +Friedl, et al. Standards Track [Page 7] + +RFC 7301 TLS App-Layer Protocol Negotiation Ext July 2014 + + + Protocol: SPDY/3 + Identification Sequence: + 0x73 0x70 0x64 0x79 0x2f 0x33 ("spdy/3") + Reference: + http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3 + +7. Acknowledgements + + This document benefitted specifically from the Next Protocol + Negotiation (NPN) extension document authored by Adam Langley and + from discussions with Tom Wesselman and Cullen Jennings, both of + Cisco. + +8. References + +8.1. Normative References + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RFC3629] Yergeau, F., "UTF-8, a transformation format of ISO + 10646", STD 63, RFC 3629, November 2003. + + [RFC5226] Narten, T. and H. Alvestrand, "Guidelines for Writing an + IANA Considerations Section in RFCs", BCP 26, RFC 5226, + May 2008. + + [RFC5246] Dierks, T. and E. Rescorla, "The Transport Layer Security + (TLS) Protocol Version 1.2", RFC 5246, August 2008. + + [RFC7230] Fielding, R. and J. Reschke, "Hypertext Transfer Protocol + (HTTP/1.1): Message Syntax and Routing", RFC 7230, June + 2014. + +8.2. Informative References + + [HTTP2] Belshe, M., Peon, R., and M. Thomson, "Hypertext Transfer + Protocol version 2", Work in Progress, June 2014. + + [RFC5077] Salowey, J., Zhou, H., Eronen, P., and H. Tschofenig, + "Transport Layer Security (TLS) Session Resumption without + Server-Side State", RFC 5077, January 2008. + + + + + + + + + +Friedl, et al. Standards Track [Page 8] + +RFC 7301 TLS App-Layer Protocol Negotiation Ext July 2014 + + +Authors' Addresses + + Stephan Friedl + Cisco Systems, Inc. + 170 West Tasman Drive + San Jose, CA 95134 + USA + + Phone: (720)562-6785 + EMail: sfriedl@cisco.com + + + Andrei Popov + Microsoft Corp. + One Microsoft Way + Redmond, WA 98052 + USA + + EMail: andreipo@microsoft.com + + + Adam Langley + Google Inc. + USA + + EMail: agl@google.com + + + Emile Stephan + Orange + 2 avenue Pierre Marzin + Lannion F-22307 + France + + EMail: emile.stephan@orange.com + + + + + + + + + + + + + + + + +Friedl, et al. Standards Track [Page 9] +