Skip to main content

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/// The session type of [`NoTls`]; it has no values.
274#[derive(Debug)]
275pub enum NoTlsSession {}
276
277impl TlsAcceptor for NoTls {
278    type Session = NoTlsSession;
279    fn session(&self, _info: &ConnectionInfo) -> Result<NoTlsSession, TlsError> {
280        Err(TlsError::new(TlsFailure::State, 0, None))
281    }
282}
283
284impl TlsSession for NoTlsSession {
285    fn step<'i>(
286        &mut self,
287        _input: &'i mut [u8],
288        _output: &mut [u8],
289    ) -> Result<TlsProgress<'i>, TlsError> {
290        match *self {}
291    }
292    fn encrypt(&mut self, _plaintext: &[u8], _output: &mut [u8]) -> Result<usize, TlsError> {
293        match *self {}
294    }
295    fn close(&mut self, _output: &mut [u8]) -> Result<usize, TlsError> {
296        match *self {}
297    }
298    fn on_transport_closed(&mut self) -> TlsCloseKind {
299        match *self {}
300    }
301    fn info(&self) -> Option<TlsInfo> {
302        match *self {}
303    }
304    fn input_capacity_required(&self) -> usize {
305        match *self {}
306    }
307}