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::enums::ProtocolVersion;
61use crate::msgs::{Codec, NewSessionTicketPayloadTls13};
62use crate::{CommonState, ConnectionTrafficSecrets, Error, SupportedCipherSuite};
63
64/// A kernel connection.
65///
66/// This does not directly wrap a kernel connection, rather it gives you the
67/// minimal interfaces you need to implement a well-behaved TLS connection on
68/// top of kTLS.
69///
70/// See the [`crate::kernel`] module docs for more details.
71pub struct KernelConnection<Side> {
72 state: Box<dyn KernelState>,
73
74 negotiated_version: ProtocolVersion,
75 suite: SupportedCipherSuite,
76
77 _side: PhantomData<Side>,
78}
79
80impl<Side> KernelConnection<Side> {
81 pub(crate) fn new(state: Box<dyn KernelState>, common: CommonState) -> Result<Self, Error> {
82 let (negotiated_version, suite) = common
83 .outputs
84 .into_kernel_parts()
85 .ok_or(Error::HandshakeNotComplete)?;
86 Ok(Self {
87 state,
88
89 negotiated_version,
90 suite,
91
92 _side: PhantomData,
93 })
94 }
95
96 /// Retrieves the cipher suite agreed with the peer.
97 pub fn negotiated_cipher_suite(&self) -> SupportedCipherSuite {
98 self.suite
99 }
100
101 /// Retrieves the protocol version agreed with the peer.
102 pub fn protocol_version(&self) -> ProtocolVersion {
103 self.negotiated_version
104 }
105
106 /// Update the traffic secret used for encrypting messages sent to the peer.
107 ///
108 /// Returns the new traffic secret and initial sequence number to use.
109 ///
110 /// In order to use the new secret you should send a TLS 1.3 key update to
111 /// the peer and then use the new traffic secrets to encrypt any future
112 /// messages.
113 ///
114 /// Note that it is only possible to update the traffic secrets on a TLS 1.3
115 /// connection. Attempting to do so on a non-TLS 1.3 connection will result
116 /// in an error.
117 pub fn update_tx_secret(&mut self) -> Result<(u64, ConnectionTrafficSecrets), Error> {
118 // The sequence number always starts at 0 after a key update.
119 self.state
120 .update_secrets(Direction::Transmit)
121 .map(|secret| (0, secret))
122 }
123
124 /// Update the traffic secret used for decrypting messages received from the
125 /// peer.
126 ///
127 /// Returns the new traffic secret and initial sequence number to use.
128 ///
129 /// You should call this method once you receive a TLS 1.3 key update message
130 /// from the peer.
131 ///
132 /// Note that it is only possible to update the traffic secrets on a TLS 1.3
133 /// connection. Attempting to do so on a non-TLS 1.3 connection will result
134 /// in an error.
135 pub fn update_rx_secret(&mut self) -> Result<(u64, ConnectionTrafficSecrets), Error> {
136 // The sequence number always starts at 0 after a key update.
137 self.state
138 .update_secrets(Direction::Receive)
139 .map(|secret| (0, secret))
140 }
141}
142
143impl KernelConnection<ClientConnectionData> {
144 /// Handle a `new_session_ticket` message from the peer.
145 ///
146 /// This will register the session ticket within with rustls so that it can
147 /// be used to establish future TLS connections.
148 ///
149 /// # Getting the right payload
150 ///
151 /// This method expects to be passed the inner payload of the handshake
152 /// message. This means that you will need to parse the header of the
153 /// handshake message in order to determine the correct payload to pass in.
154 /// The message format is described in [RFC 8446 section 4][0]. `payload`
155 /// should not include the `msg_type` or `length` fields.
156 ///
157 /// Code to parse out the payload should look something like this
158 /// ```no_run
159 /// use rustls::enums::{ContentType, HandshakeType};
160 /// use rustls::kernel::KernelConnection;
161 /// use rustls::client::ClientConnectionData;
162 ///
163 /// # fn doctest(conn: &mut KernelConnection<ClientConnectionData>, typ: ContentType, message: &[u8]) -> Result<(), rustls::Error> {
164 /// let conn: &mut KernelConnection<ClientConnectionData> = // ...
165 /// # conn;
166 /// let typ: ContentType = // ...
167 /// # typ;
168 /// let mut message: &[u8] = // ...
169 /// # message;
170 ///
171 /// // Processing for other messages not included in this example
172 /// assert_eq!(typ, ContentType::Handshake);
173 ///
174 /// // There may be multiple handshake payloads within a single handshake message.
175 /// while !message.is_empty() {
176 /// let (typ, len, rest) = match message {
177 /// &[typ, a, b, c, ref rest @ ..] => (
178 /// HandshakeType::from(typ),
179 /// u32::from_be_bytes([0, a, b, c]) as usize,
180 /// rest
181 /// ),
182 /// _ => panic!("error handling not included in this example")
183 /// };
184 ///
185 /// // Processing for other messages not included in this example.
186 /// assert_eq!(typ, HandshakeType::NewSessionTicket);
187 /// assert!(rest.len() >= len, "invalid handshake message");
188 ///
189 /// let (payload, rest) = rest.split_at(len);
190 /// message = rest;
191 ///
192 /// conn.handle_new_session_ticket(payload)?;
193 /// }
194 /// # Ok(())
195 /// # }
196 /// ```
197 ///
198 /// # Errors
199 /// This method will return an error if:
200 /// - This connection is not a TLS 1.3 connection (in TLS 1.2 session tickets
201 /// are sent as part of the handshake).
202 /// - The provided payload is not a valid `new_session_ticket` payload or has
203 /// extra unparsed trailing data.
204 /// - An error occurs while the connection updates the session ticket store.
205 ///
206 /// [0]: https://datatracker.ietf.org/doc/html/rfc8446#section-4
207 pub fn handle_new_session_ticket(&mut self, payload: &[u8]) -> Result<(), Error> {
208 // We want to return a more specific error here first if this is called
209 // on a non-TLS 1.3 connection since a parsing error isn't the real issue
210 // here.
211 if self.protocol_version() != ProtocolVersion::TLSv1_3 {
212 return Err(Error::General(
213 "TLS 1.2 session tickets may not be sent once the handshake has completed".into(),
214 ));
215 }
216
217 let nst = NewSessionTicketPayloadTls13::read_bytes(payload)?;
218 self.state
219 .handle_new_session_ticket(&nst)
220 }
221}
222
223pub(crate) trait KernelState: Send + Sync {
224 /// Update the traffic secret for the specified direction on the connection.
225 fn update_secrets(&mut self, dir: Direction) -> Result<ConnectionTrafficSecrets, Error>;
226
227 /// Handle a new session ticket.
228 ///
229 /// This will only ever be called for client connections, as [`KernelConnection`]
230 /// only exposes the relevant API for client connections.
231 fn handle_new_session_ticket(
232 &self,
233 message: &NewSessionTicketPayloadTls13,
234 ) -> Result<(), Error>;
235}
236
237#[derive(Copy, Clone, Debug, Eq, PartialEq)]
238pub(crate) enum Direction {
239 Transmit,
240 Receive,
241}