rustls/conn/
kernel.rs

1//! Kernel connection API.
2//!
3//! This module gives you the bare minimum you need to implement a TLS connection
4//! that does its own encryption and decryption while still using rustls to manage
5//! connection secrets and session tickets. It is intended for use cases like kTLS
6//! where you want to use rustls to establish the connection but want to use
7//! something else to do the encryption/decryption after that.
8//!
9//! There are only two things that [`KernelConnection`] is able to do:
10//! 1. Compute new traffic secrets when a key update occurs.
11//! 2. Save received session tickets sent by a server peer.
12//!
13//! That's it. Everything else you will need to implement yourself.
14//!
15//! # Entry Point
16//! The entry points into this API are
17//! [`UnbufferedClientConnection::dangerous_into_kernel_connection`][client-into]
18//! and
19//! [`UnbufferedServerConnection::dangerous_into_kernel_connection`][server-into].
20//!
21//! In order to actually create an [`KernelConnection`] all of the following
22//! must be true:
23//! - the connection must have completed its handshake,
24//! - the connection must have no buffered TLS data waiting to be sent, and,
25//! - the config used to create the connection must have `enable_extract_secrets`
26//!   set to true.
27//!
28//! This sounds fairly complicated to achieve at first glance. However, if you
29//! drive an unbuffered connection through the handshake until it returns
30//! [`WriteTraffic`] then it will end up in an appropriate state to convert
31//! into an external connection.
32//!
33//! [client-into]: crate::client::UnbufferedClientConnection::dangerous_into_kernel_connection
34//! [server-into]: crate::server::UnbufferedServerConnection::dangerous_into_kernel_connection
35//! [`WriteTraffic`]: crate::unbuffered::ConnectionState::WriteTraffic
36//!
37//! # Cipher Suite Confidentiality Limits
38//! Some cipher suites (notably AES-GCM) have vulnerabilities where they are no
39//! longer secure once a certain number of messages have been sent. Normally,
40//! rustls tracks how many messages have been written or read and will
41//! automatically either refresh keys or emit an error when approaching the
42//! confidentiality limit of the cipher suite.
43//!
44//! [`KernelConnection`] has no way to track this. It is the responsibility
45//! of the user of the API to track approximately how many messages have been
46//! sent and either refresh the traffic keys or abort the connection before the
47//! confidentiality limit is reached.
48//!
49//! You can find the current confidentiality limit by looking at
50//! [`CipherSuiteCommon::confidentiality_limit`] for the cipher suite selected
51//! by the connection.
52//!
53//! [`CipherSuiteCommon::confidentiality_limit`]: crate::CipherSuiteCommon::confidentiality_limit
54//! [`KernelConnection`]: crate::kernel::KernelConnection
55
56use alloc::boxed::Box;
57use core::marker::PhantomData;
58
59use crate::client::ClientConnectionData;
60use crate::common_state::Protocol;
61use crate::crypto::Identity;
62use crate::enums::ProtocolVersion;
63use crate::msgs::codec::Codec;
64use crate::msgs::handshake::NewSessionTicketPayloadTls13;
65use crate::quic::Quic;
66use crate::{CommonState, ConnectionTrafficSecrets, Error, SupportedCipherSuite};
67
68/// A kernel connection.
69///
70/// This does not directly wrap a kernel connection, rather it gives you the
71/// minimal interfaces you need to implement a well-behaved TLS connection on
72/// top of kTLS.
73///
74/// See the [`crate::kernel`] module docs for more details.
75pub struct KernelConnection<Side> {
76    state: Box<dyn KernelState>,
77
78    peer_identity: Option<Identity<'static>>,
79    quic: Quic,
80
81    negotiated_version: ProtocolVersion,
82    protocol: Protocol,
83    suite: SupportedCipherSuite,
84
85    _side: PhantomData<Side>,
86}
87
88impl<Side> KernelConnection<Side> {
89    pub(crate) fn new(state: Box<dyn KernelState>, common: CommonState) -> Result<Self, Error> {
90        Ok(Self {
91            state,
92
93            peer_identity: common.peer_identity,
94            quic: common.quic,
95            negotiated_version: common
96                .negotiated_version
97                .ok_or(Error::HandshakeNotComplete)?,
98            protocol: common.protocol,
99            suite: common
100                .suite
101                .ok_or(Error::HandshakeNotComplete)?,
102
103            _side: PhantomData,
104        })
105    }
106
107    /// Retrieves the cipher suite agreed with the peer.
108    pub fn negotiated_cipher_suite(&self) -> SupportedCipherSuite {
109        self.suite
110    }
111
112    /// Retrieves the protocol version agreed with the peer.
113    pub fn protocol_version(&self) -> ProtocolVersion {
114        self.negotiated_version
115    }
116
117    /// Update the traffic secret used for encrypting messages sent to the peer.
118    ///
119    /// Returns the new traffic secret and initial sequence number to use.
120    ///
121    /// In order to use the new secret you should send a TLS 1.3 key update to
122    /// the peer and then use the new traffic secrets to encrypt any future
123    /// messages.
124    ///
125    /// Note that it is only possible to update the traffic secrets on a TLS 1.3
126    /// connection. Attempting to do so on a non-TLS 1.3 connection will result
127    /// in an error.
128    pub fn update_tx_secret(&mut self) -> Result<(u64, ConnectionTrafficSecrets), Error> {
129        // The sequence number always starts at 0 after a key update.
130        self.state
131            .update_secrets(Direction::Transmit)
132            .map(|secret| (0, secret))
133    }
134
135    /// Update the traffic secret used for decrypting messages received from the
136    /// peer.
137    ///
138    /// Returns the new traffic secret and initial sequence number to use.
139    ///
140    /// You should call this method once you receive a TLS 1.3 key update message
141    /// from the peer.
142    ///
143    /// Note that it is only possible to update the traffic secrets on a TLS 1.3
144    /// connection. Attempting to do so on a non-TLS 1.3 connection will result
145    /// in an error.
146    pub fn update_rx_secret(&mut self) -> Result<(u64, ConnectionTrafficSecrets), Error> {
147        // The sequence number always starts at 0 after a key update.
148        self.state
149            .update_secrets(Direction::Receive)
150            .map(|secret| (0, secret))
151    }
152}
153
154impl KernelConnection<ClientConnectionData> {
155    /// Handle a `new_session_ticket` message from the peer.
156    ///
157    /// This will register the session ticket within with rustls so that it can
158    /// be used to establish future TLS connections.
159    ///
160    /// # Getting the right payload
161    ///
162    /// This method expects to be passed the inner payload of the handshake
163    /// message. This means that you will need to parse the header of the
164    /// handshake message in order to determine the correct payload to pass in.
165    /// The message format is described in [RFC 8446 section 4][0]. `payload`
166    /// should not include the `msg_type` or `length` fields.
167    ///
168    /// Code to parse out the payload should look something like this
169    /// ```no_run
170    /// use rustls::enums::{ContentType, HandshakeType};
171    /// use rustls::kernel::KernelConnection;
172    /// use rustls::client::ClientConnectionData;
173    ///
174    /// # fn doctest(conn: &mut KernelConnection<ClientConnectionData>, typ: ContentType, message: &[u8]) -> Result<(), rustls::Error> {
175    /// let conn: &mut KernelConnection<ClientConnectionData> = // ...
176    /// #   conn;
177    /// let typ: ContentType = // ...
178    /// #   typ;
179    /// let mut message: &[u8] = // ...
180    /// #   message;
181    ///
182    /// // Processing for other messages not included in this example
183    /// assert_eq!(typ, ContentType::Handshake);
184    ///
185    /// // There may be multiple handshake payloads within a single handshake message.
186    /// while !message.is_empty() {
187    ///     let (typ, len, rest) = match message {
188    ///         &[typ, a, b, c, ref rest @ ..] => (
189    ///             HandshakeType::from(typ),
190    ///             u32::from_be_bytes([0, a, b, c]) as usize,
191    ///             rest
192    ///         ),
193    ///         _ => panic!("error handling not included in this example")
194    ///     };
195    ///
196    ///     // Processing for other messages not included in this example.
197    ///     assert_eq!(typ, HandshakeType::NewSessionTicket);
198    ///     assert!(rest.len() >= len, "invalid handshake message");
199    ///
200    ///     let (payload, rest) = rest.split_at(len);
201    ///     message = rest;
202    ///
203    ///     conn.handle_new_session_ticket(payload)?;
204    /// }
205    /// # Ok(())
206    /// # }
207    /// ```
208    ///
209    /// # Errors
210    /// This method will return an error if:
211    /// - This connection is not a TLS 1.3 connection (in TLS 1.2 session tickets
212    ///   are sent as part of the handshake).
213    /// - The provided payload is not a valid `new_session_ticket` payload or has
214    ///   extra unparsed trailing data.
215    /// - An error occurs while the connection updates the session ticket store.
216    ///
217    /// [0]: https://datatracker.ietf.org/doc/html/rfc8446#section-4
218    pub fn handle_new_session_ticket(&mut self, payload: &[u8]) -> Result<(), Error> {
219        // We want to return a more specific error here first if this is called
220        // on a non-TLS 1.3 connection since a parsing error isn't the real issue
221        // here.
222        if self.protocol_version() != ProtocolVersion::TLSv1_3 {
223            return Err(Error::General(
224                "TLS 1.2 session tickets may not be sent once the handshake has completed".into(),
225            ));
226        }
227
228        let nst = NewSessionTicketPayloadTls13::read_bytes(payload)?;
229        let mut cx = KernelContext {
230            peer_identity: self.peer_identity.as_ref(),
231            protocol: self.protocol,
232            quic: &self.quic,
233        };
234        self.state
235            .handle_new_session_ticket(&mut cx, &nst)
236    }
237}
238
239pub(crate) trait KernelState: Send + Sync {
240    /// Update the traffic secret for the specified direction on the connection.
241    fn update_secrets(&mut self, dir: Direction) -> Result<ConnectionTrafficSecrets, Error>;
242
243    /// Handle a new session ticket.
244    ///
245    /// This will only ever be called for client connections, as [`KernelConnection`]
246    /// only exposes the relevant API for client connections.
247    fn handle_new_session_ticket(
248        &mut self,
249        cx: &mut KernelContext<'_>,
250        message: &NewSessionTicketPayloadTls13,
251    ) -> Result<(), Error>;
252}
253
254pub(crate) struct KernelContext<'a> {
255    pub(crate) peer_identity: Option<&'a Identity<'static>>,
256    pub(crate) protocol: Protocol,
257    pub(crate) quic: &'a Quic,
258}
259
260impl KernelContext<'_> {
261    pub(crate) fn is_quic(&self) -> bool {
262        self.protocol == Protocol::Quic
263    }
264}
265
266#[derive(Copy, Clone, Debug, Eq, PartialEq)]
267pub(crate) enum Direction {
268    Transmit,
269    Receive,
270}