diff options
Diffstat (limited to 'vendor/rustls/src/conn/kernel.rs')
| -rw-r--r-- | vendor/rustls/src/conn/kernel.rs | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/vendor/rustls/src/conn/kernel.rs b/vendor/rustls/src/conn/kernel.rs new file mode 100644 index 00000000..b90fad39 --- /dev/null +++ b/vendor/rustls/src/conn/kernel.rs @@ -0,0 +1,268 @@ +//! Kernel connection API. +//! +//! This module gives you the bare minimum you need to implement a TLS connection +//! that does its own encryption and decryption while still using rustls to manage +//! connection secrets and session tickets. It is intended for use cases like kTLS +//! where you want to use rustls to establish the connection but want to use +//! something else to do the encryption/decryption after that. +//! +//! There are only two things that [`KernelConnection`] is able to do: +//! 1. Compute new traffic secrets when a key update occurs. +//! 2. Save received session tickets sent by a server peer. +//! +//! That's it. Everything else you will need to implement yourself. +//! +//! # Entry Point +//! The entry points into this API are +//! [`UnbufferedClientConnection::dangerous_into_kernel_connection`][client-into] +//! and +//! [`UnbufferedServerConnection::dangerous_into_kernel_connection`][server-into]. +//! +//! In order to actually create an [`KernelConnection`] all of the following +//! must be true: +//! - the connection must have completed its handshake, +//! - the connection must have no buffered TLS data waiting to be sent, and, +//! - the config used to create the connection must have `enable_extract_secrets` +//! set to true. +//! +//! This sounds fairly complicated to achieve at first glance. However, if you +//! drive an unbuffered connection through the handshake until it returns +//! [`WriteTraffic`] then it will end up in an appropriate state to convert +//! into an external connection. +//! +//! [client-into]: crate::client::UnbufferedClientConnection::dangerous_into_kernel_connection +//! [server-into]: crate::server::UnbufferedServerConnection::dangerous_into_kernel_connection +//! [`WriteTraffic`]: crate::unbuffered::ConnectionState::WriteTraffic +//! +//! # Cipher Suite Confidentiality Limits +//! Some cipher suites (notably AES-GCM) have vulnerabilities where they are no +//! longer secure once a certain number of messages have been sent. Normally, +//! rustls tracks how many messages have been written or read and will +//! automatically either refresh keys or emit an error when approaching the +//! confidentiality limit of the cipher suite. +//! +//! [`KernelConnection`] has no way to track this. It is the responsibility +//! of the user of the API to track approximately how many messages have been +//! sent and either refresh the traffic keys or abort the connection before the +//! confidentiality limit is reached. +//! +//! You can find the current confidentiality limit by looking at +//! [`CipherSuiteCommon::confidentiality_limit`] for the cipher suite selected +//! by the connection. +//! +//! [`CipherSuiteCommon::confidentiality_limit`]: crate::CipherSuiteCommon::confidentiality_limit +//! [`KernelConnection`]: crate::kernel::KernelConnection + +use alloc::boxed::Box; +use core::marker::PhantomData; + +use crate::client::ClientConnectionData; +use crate::common_state::Protocol; +use crate::msgs::codec::Codec; +use crate::msgs::handshake::{CertificateChain, NewSessionTicketPayloadTls13}; +use crate::quic::Quic; +use crate::{CommonState, ConnectionTrafficSecrets, Error, ProtocolVersion, SupportedCipherSuite}; + +/// A kernel connection. +/// +/// This does not directly wrap a kernel connection, rather it gives you the +/// minimal interfaces you need to implement a well-behaved TLS connection on +/// top of kTLS. +/// +/// See the [`crate::kernel`] module docs for more details. +pub struct KernelConnection<Data> { + state: Box<dyn KernelState>, + + peer_certificates: Option<CertificateChain<'static>>, + quic: Quic, + + negotiated_version: ProtocolVersion, + protocol: Protocol, + suite: SupportedCipherSuite, + + _data: PhantomData<Data>, +} + +impl<Data> KernelConnection<Data> { + pub(crate) fn new(state: Box<dyn KernelState>, common: CommonState) -> Result<Self, Error> { + Ok(Self { + state, + + peer_certificates: common.peer_certificates, + quic: common.quic, + negotiated_version: common + .negotiated_version + .ok_or(Error::HandshakeNotComplete)?, + protocol: common.protocol, + suite: common + .suite + .ok_or(Error::HandshakeNotComplete)?, + + _data: PhantomData, + }) + } + + /// Retrieves the ciphersuite agreed with the peer. + pub fn negotiated_cipher_suite(&self) -> SupportedCipherSuite { + self.suite + } + + /// Retrieves the protocol version agreed with the peer. + pub fn protocol_version(&self) -> ProtocolVersion { + self.negotiated_version + } + + /// Update the traffic secret used for encrypting messages sent to the peer. + /// + /// Returns the new traffic secret and initial sequence number to use. + /// + /// In order to use the new secret you should send a TLS 1.3 key update to + /// the peer and then use the new traffic secrets to encrypt any future + /// messages. + /// + /// Note that it is only possible to update the traffic secrets on a TLS 1.3 + /// connection. Attempting to do so on a non-TLS 1.3 connection will result + /// in an error. + pub fn update_tx_secret(&mut self) -> Result<(u64, ConnectionTrafficSecrets), Error> { + // The sequence number always starts at 0 after a key update. + self.state + .update_secrets(Direction::Transmit) + .map(|secret| (0, secret)) + } + + /// Update the traffic secret used for decrypting messages received from the + /// peer. + /// + /// Returns the new traffic secret and initial sequence number to use. + /// + /// You should call this method once you receive a TLS 1.3 key update message + /// from the peer. + /// + /// Note that it is only possible to update the traffic secrets on a TLS 1.3 + /// connection. Attempting to do so on a non-TLS 1.3 connection will result + /// in an error. + pub fn update_rx_secret(&mut self) -> Result<(u64, ConnectionTrafficSecrets), Error> { + // The sequence number always starts at 0 after a key update. + self.state + .update_secrets(Direction::Receive) + .map(|secret| (0, secret)) + } +} + +impl KernelConnection<ClientConnectionData> { + /// Handle a `new_session_ticket` message from the peer. + /// + /// This will register the session ticket within with rustls so that it can + /// be used to establish future TLS connections. + /// + /// # Getting the right payload + /// + /// This method expects to be passed the inner payload of the handshake + /// message. This means that you will need to parse the header of the + /// handshake message in order to determine the correct payload to pass in. + /// The message format is described in [RFC 8446 section 4][0]. `payload` + /// should not include the `msg_type` or `length` fields. + /// + /// Code to parse out the payload should look something like this + /// ```no_run + /// use rustls::{ContentType, HandshakeType}; + /// use rustls::kernel::KernelConnection; + /// use rustls::client::ClientConnectionData; + /// + /// # fn doctest(conn: &mut KernelConnection<ClientConnectionData>, typ: ContentType, message: &[u8]) -> Result<(), rustls::Error> { + /// let conn: &mut KernelConnection<ClientConnectionData> = // ... + /// # conn; + /// let typ: ContentType = // ... + /// # typ; + /// let mut message: &[u8] = // ... + /// # message; + /// + /// // Processing for other messages not included in this example + /// assert_eq!(typ, ContentType::Handshake); + /// + /// // There may be multiple handshake payloads within a single handshake message. + /// while !message.is_empty() { + /// let (typ, len, rest) = match message { + /// &[typ, a, b, c, ref rest @ ..] => ( + /// HandshakeType::from(typ), + /// u32::from_be_bytes([0, a, b, c]) as usize, + /// rest + /// ), + /// _ => panic!("error handling not included in this example") + /// }; + /// + /// // Processing for other messages not included in this example. + /// assert_eq!(typ, HandshakeType::NewSessionTicket); + /// assert!(rest.len() >= len, "invalid handshake message"); + /// + /// let (payload, rest) = rest.split_at(len); + /// message = rest; + /// + /// conn.handle_new_session_ticket(payload)?; + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// This method will return an error if: + /// - This connection is not a TLS 1.3 connection (in TLS 1.2 session tickets + /// are sent as part of the handshake). + /// - The provided payload is not a valid `new_session_ticket` payload or has + /// extra unparsed trailing data. + /// - An error occurs while the connection updates the session ticket store. + /// + /// [0]: https://datatracker.ietf.org/doc/html/rfc8446#section-4 + pub fn handle_new_session_ticket(&mut self, payload: &[u8]) -> Result<(), Error> { + // We want to return a more specific error here first if this is called + // on a non-TLS 1.3 connection since a parsing error isn't the real issue + // here. + if self.protocol_version() != ProtocolVersion::TLSv1_3 { + return Err(Error::General( + "TLS 1.2 session tickets may not be sent once the handshake has completed".into(), + )); + } + + let nst = NewSessionTicketPayloadTls13::read_bytes(payload)?; + let mut cx = KernelContext { + peer_certificates: self.peer_certificates.as_ref(), + protocol: self.protocol, + quic: &self.quic, + }; + self.state + .handle_new_session_ticket(&mut cx, &nst) + } +} + +pub(crate) trait KernelState: Send + Sync { + /// Update the traffic secret for the specified direction on the connection. + fn update_secrets(&mut self, dir: Direction) -> Result<ConnectionTrafficSecrets, Error>; + + /// Handle a new session ticket. + /// + /// This will only ever be called for client connections, as [`KernelConnection`] + /// only exposes the relevant API for client connections. + fn handle_new_session_ticket( + &mut self, + cx: &mut KernelContext<'_>, + message: &NewSessionTicketPayloadTls13, + ) -> Result<(), Error>; +} + +pub(crate) struct KernelContext<'a> { + pub(crate) peer_certificates: Option<&'a CertificateChain<'static>>, + pub(crate) protocol: Protocol, + pub(crate) quic: &'a Quic, +} + +impl KernelContext<'_> { + pub(crate) fn is_quic(&self) -> bool { + self.protocol == Protocol::Quic + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) enum Direction { + Transmit, + Receive, +} |
