net_io/tls.rs
1//! The TLS seam: traits a TLS engine implements in its own crate.
2//!
3//! net-io carries no TLS code. The seam is in place and sans-IO: ciphertext is
4//! decrypted inside the connection's own read buffer and plaintext is encrypted
5//! straight into net-io's output buffer. The server integration arrives in
6//! `0.4.0` behind the `tls` feature; the traits exist in every build.
7
8use std::fmt;
9
10use crate::info::ConnectionInfo;
11
12/// Creates one [`TlsSession`] per admitted connection.
13///
14/// # Examples
15///
16/// ```
17/// use net_io::{ConnectionInfo, NoTls, TlsAcceptor};
18/// let info = ConnectionInfo::for_testing("127.0.0.1:1".parse().unwrap());
19/// assert!(NoTls.session(&info).is_err());
20/// ```
21pub trait TlsAcceptor: Send + Sync + 'static {
22 /// The per-connection session type.
23 type Session: TlsSession;
24
25 /// Called once per admitted connection, after PROXY and readmission, before any TLS byte is read.
26 ///
27 /// # Errors
28 ///
29 /// A [`TlsError`] refuses the connection (for example an acceptor at capacity).
30 fn session(&self, info: &ConnectionInfo) -> Result<Self::Session, TlsError>;
31}
32
33/// One connection's TLS state machine. Never blocks, never does IO.
34///
35/// See `docs/API.md` (TLS seam) for how the server drives it.
36pub trait TlsSession: Send + 'static {
37 /// Ciphertext is in `input`; free space for records to send is `output`.
38 ///
39 /// # Errors
40 ///
41 /// A [`TlsError`]; `TlsFailure::HelloRefused` closes the connection with
42 /// `CloseReason::HelloRefused`, anything else with `TlsFailed`.
43 fn step<'i>(
44 &mut self,
45 input: &'i mut [u8],
46 output: &mut [u8],
47 ) -> Result<TlsProgress<'i>, TlsError>;
48
49 /// Encrypts `plaintext` into `output`: all records or none.
50 ///
51 /// # Errors
52 ///
53 /// `TlsFailure::OutputFull` when `output` cannot hold every record.
54 fn encrypt(&mut self, plaintext: &[u8], output: &mut [u8]) -> Result<usize, TlsError>;
55
56 /// Writes `close_notify` into `output`.
57 ///
58 /// # Errors
59 ///
60 /// `TlsFailure::OutputFull` or `TlsFailure::State`.
61 fn close(&mut self, output: &mut [u8]) -> Result<usize, TlsError>;
62
63 /// The peer's transport closed: was the TLS stream ended cleanly?
64 fn on_transport_closed(&mut self) -> TlsCloseKind;
65
66 /// SNI, ALPN, version, and cipher suite; `Some` after the handshake.
67 fn info(&self) -> Option<TlsInfo>;
68
69 /// The smallest input buffer the session needs now.
70 fn input_capacity_required(&self) -> usize;
71}
72
73/// What one [`TlsSession::step`] did.
74#[must_use]
75#[derive(Debug)]
76pub struct TlsProgress<'i> {
77 /// Input bytes the engine consumed.
78 pub consumed: usize,
79 /// Bytes the engine wrote at the start of `output`.
80 pub written: usize,
81 /// What the caller does next.
82 pub event: TlsEvent<'i>,
83}
84
85/// The next thing the driver does after a step.
86#[non_exhaustive]
87#[derive(Debug)]
88pub enum TlsEvent<'i> {
89 /// Read more ciphertext, append it after the unconsumed bytes, call `step` again.
90 NeedInput,
91 /// Send the `written` bytes at the start of `output`, all of them.
92 Transmit,
93 /// The handshake finished; `info()` is now `Some`.
94 HandshakeComplete,
95 /// Plaintext decrypted in place; call `step` again for more.
96 Data(&'i [u8]),
97 /// Not enough room in `output`; flush and call again.
98 OutputFull,
99 /// `close_notify` was received.
100 PeerClosed,
101 /// Both directions are closed.
102 Closed,
103}
104
105/// Whether a TLS stream ended with `close_notify`.
106///
107/// ```
108/// assert_ne!(net_io::TlsCloseKind::Clean, net_io::TlsCloseKind::Unclean);
109/// ```
110#[non_exhaustive]
111#[derive(Clone, Copy, Debug, PartialEq, Eq)]
112pub enum TlsCloseKind {
113 /// `close_notify` was seen.
114 Clean,
115 /// The transport closed without it.
116 Unclean,
117}
118
119/// What failed in a TLS engine.
120///
121/// ```
122/// assert_ne!(net_io::TlsFailure::Handshake, net_io::TlsFailure::HelloRefused);
123/// ```
124#[non_exhaustive]
125#[derive(Clone, Copy, Debug, PartialEq, Eq)]
126pub enum TlsFailure {
127 /// The handshake failed.
128 Handshake,
129 /// The engine's hello hook refused the ClientHello.
130 HelloRefused,
131 /// The acceptor is at capacity.
132 Capacity,
133 /// The peer broke the protocol.
134 Protocol,
135 /// The output buffer is too small.
136 OutputFull,
137 /// The call is not valid in the session's state.
138 State,
139}
140
141/// A TLS engine error: codes only, never secret material.
142///
143/// ```
144/// use net_io::{TlsError, TlsFailure};
145/// let e = TlsError::new(TlsFailure::HelloRefused, 3, None);
146/// assert_eq!(e.kind, TlsFailure::HelloRefused);
147/// ```
148#[non_exhaustive]
149#[derive(Clone, Copy, Debug, PartialEq, Eq)]
150pub struct TlsError {
151 /// What failed.
152 pub kind: TlsFailure,
153 /// The engine's own code.
154 pub code: u16,
155 /// The TLS alert sent or received, if any.
156 pub alert: Option<u8>,
157}
158
159impl TlsError {
160 /// Builds an error.
161 #[must_use]
162 pub const fn new(kind: TlsFailure, code: u16, alert: Option<u8>) -> TlsError {
163 TlsError { kind, code, alert }
164 }
165}
166
167impl fmt::Display for TlsError {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 write!(f, "{:?} (engine code {}", self.kind, self.code)?;
170 if let Some(alert) = self.alert {
171 write!(f, ", alert {alert}")?;
172 }
173 f.write_str(")")
174 }
175}
176
177impl std::error::Error for TlsError {}
178
179/// TLS facts after the handshake, in fixed arrays.
180///
181/// ```
182/// let info = net_io::TlsInfo::new(Some("example.com"), Some(b"h2"), 0x0304, 0x1301).unwrap();
183/// assert_eq!(info.sni(), Some("example.com"));
184/// assert!(net_io::TlsInfo::new(Some(&"a".repeat(256)), None, 0, 0).is_none());
185/// ```
186#[non_exhaustive]
187#[derive(Clone, Copy)]
188pub struct TlsInfo {
189 sni: [u8; 255],
190 sni_len: u8,
191 alpn: [u8; 32],
192 alpn_len: u8,
193 version: u16,
194 cipher_suite: u16,
195}
196
197impl TlsInfo {
198 /// Builds the facts; `None` if the SNI is longer than 255 bytes or the ALPN longer than 32.
199 #[must_use]
200 pub fn new(
201 sni: Option<&str>,
202 alpn: Option<&[u8]>,
203 version: u16,
204 cipher_suite: u16,
205 ) -> Option<TlsInfo> {
206 let sni = sni.unwrap_or("").as_bytes();
207 let alpn = alpn.unwrap_or(&[]);
208 if sni.len() > 255 || alpn.len() > 32 {
209 return None;
210 }
211 let mut info = TlsInfo {
212 sni: [0; 255],
213 sni_len: u8::try_from(sni.len()).ok()?,
214 alpn: [0; 32],
215 alpn_len: u8::try_from(alpn.len()).ok()?,
216 version,
217 cipher_suite,
218 };
219 info.sni[..sni.len()].copy_from_slice(sni);
220 info.alpn[..alpn.len()].copy_from_slice(alpn);
221 Some(info)
222 }
223
224 /// The server name the client asked for.
225 #[must_use]
226 pub fn sni(&self) -> Option<&str> {
227 if self.sni_len == 0 {
228 return None;
229 }
230 std::str::from_utf8(&self.sni[..usize::from(self.sni_len)]).ok()
231 }
232
233 /// The negotiated application protocol.
234 #[must_use]
235 pub fn alpn(&self) -> Option<&[u8]> {
236 (self.alpn_len > 0).then(|| &self.alpn[..usize::from(self.alpn_len)])
237 }
238
239 /// The protocol version code (for example `0x0304` for TLS 1.3).
240 #[must_use]
241 pub fn version(&self) -> u16 {
242 self.version
243 }
244
245 /// The cipher suite code.
246 #[must_use]
247 pub fn cipher_suite(&self) -> u16 {
248 self.cipher_suite
249 }
250}
251
252impl fmt::Debug for TlsInfo {
253 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254 f.debug_struct("TlsInfo")
255 .field("sni", &self.sni())
256 .field("alpn", &self.alpn().map(String::from_utf8_lossy))
257 .field("version", &format_args!("{:#06x}", self.version))
258 .field("cipher_suite", &format_args!("{:#06x}", self.cipher_suite))
259 .finish()
260 }
261}
262
263/// The builder's default acceptor: no listener may enable TLS with it.
264///
265/// ```
266/// use net_io::{ConnectionInfo, NoTls, TlsAcceptor};
267/// let info = ConnectionInfo::for_testing("127.0.0.1:1".parse().unwrap());
268/// assert!(NoTls.session(&info).is_err());
269/// ```
270#[derive(Clone, Copy, Debug, Default)]
271pub struct NoTls;
272
273/// An acceptor behind a pointer: sessions come back boxed.
274#[cfg_attr(not(feature = "tls"), allow(dead_code))]
275pub(crate) trait ErasedAcceptor: Send + Sync {
276 fn session_boxed(&self, info: &ConnectionInfo) -> Result<Box<dyn TlsSession>, TlsError>;
277}
278
279impl<A: TlsAcceptor> ErasedAcceptor for A {
280 fn session_boxed(&self, info: &ConnectionInfo) -> Result<Box<dyn TlsSession>, TlsError> {
281 self.session(info)
282 .map(|s| Box::new(s) as Box<dyn TlsSession>)
283 }
284}
285
286/// The session type of [`NoTls`]; it has no values.
287#[derive(Debug)]
288pub enum NoTlsSession {}
289
290impl TlsAcceptor for NoTls {
291 type Session = NoTlsSession;
292 fn session(&self, _info: &ConnectionInfo) -> Result<NoTlsSession, TlsError> {
293 Err(TlsError::new(TlsFailure::State, 0, None))
294 }
295}
296
297impl TlsSession for NoTlsSession {
298 fn step<'i>(
299 &mut self,
300 _input: &'i mut [u8],
301 _output: &mut [u8],
302 ) -> Result<TlsProgress<'i>, TlsError> {
303 match *self {}
304 }
305 fn encrypt(&mut self, _plaintext: &[u8], _output: &mut [u8]) -> Result<usize, TlsError> {
306 match *self {}
307 }
308 fn close(&mut self, _output: &mut [u8]) -> Result<usize, TlsError> {
309 match *self {}
310 }
311 fn on_transport_closed(&mut self) -> TlsCloseKind {
312 match *self {}
313 }
314 fn info(&self) -> Option<TlsInfo> {
315 match *self {}
316 }
317 fn input_capacity_required(&self) -> usize {
318 match *self {}
319 }
320}