Skip to main content

static_web_server/
tls.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// This file is part of Static Web Server.
3// See https://static-web-server.net/ for more information
4// Copyright (C) 2019-present Jose Quintana <joseluisq.net>
5
6//! The module handles requests over TLS via [Rustls](tokio_rustls::rustls).
7//!
8
9// Ensure exactly one TLS crypto provider is enabled.
10#[cfg(all(feature = "tls-ring", feature = "tls-fips"))]
11compile_error!("Only one TLS crypto provider may be enabled. Choose one of: tls-ring, tls-fips");
12
13#[cfg(not(any(feature = "tls-ring", feature = "tls-fips")))]
14compile_error!("tls requires a crypto provider: enable tls-ring or tls-fips");
15
16// Most of the file is borrowed from https://github.com/seanmonstar/warp/blob/master/src/tls.rs
17
18use futures_util::ready;
19use rustls_pki_types::pem::PemObject;
20use rustls_pki_types::{CertificateDer, PrivateKeyDer};
21use std::fs::File;
22use std::future::Future;
23use std::io::{self, BufReader, Cursor, Read};
24use std::path::{Path, PathBuf};
25use std::pin::Pin;
26use std::sync::Arc;
27use std::task::{Context, Poll};
28use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
29use tokio::net::TcpStream;
30use tokio_rustls::rustls::{Error as TlsError, ServerConfig};
31
32/// Represents errors that can occur building the TlsConfig
33#[derive(Debug)]
34pub enum TlsConfigError {
35    /// Error type for I/O operations
36    Io(io::Error),
37    /// An Error parsing the Certificate
38    CertParseError,
39    /// Identity PEM is invalid
40    InvalidIdentityPem,
41    /// An error from an empty key
42    EmptyKey,
43    /// Unknown private key format
44    UnknownPrivateKeyFormat,
45    /// An error from an invalid key
46    InvalidKey(TlsError),
47    /// Illegal section start in PEM
48    IllegalSectionStart(Vec<u8>),
49    /// Illegal section end in PEM
50    IllegalSectionEnd(Vec<u8>),
51}
52
53impl std::fmt::Display for TlsConfigError {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        match self {
56            TlsConfigError::Io(err) => err.fmt(f),
57            TlsConfigError::CertParseError => write!(f, "failed to parse certificate"),
58            TlsConfigError::InvalidIdentityPem => write!(f, "the identity PEM provided is invalid"),
59            TlsConfigError::UnknownPrivateKeyFormat => {
60                write!(f, "the private key format is unknown")
61            }
62            TlsConfigError::EmptyKey => write!(f, "the key provided is probably missing or empty"),
63            TlsConfigError::InvalidKey(err) => write!(f, "the key provided is invalid, {err}"),
64            TlsConfigError::IllegalSectionStart(line) => {
65                let line = String::from_utf8(line.clone()).unwrap_or_default();
66                write!(f, "illegal section start in PEM at '{line}'")
67            }
68            TlsConfigError::IllegalSectionEnd(end_marker) => {
69                let end_marker = String::from_utf8(end_marker.clone()).unwrap_or_default();
70                write!(f, "illegal section end in PEM at '{end_marker}'")
71            }
72        }
73    }
74}
75
76impl std::error::Error for TlsConfigError {}
77
78/// Builder to set the configuration for the Tls server.
79pub struct TlsConfigBuilder {
80    cert: Box<dyn Read + Send + Sync>,
81    key: Box<dyn Read + Send + Sync>,
82}
83
84impl std::fmt::Debug for TlsConfigBuilder {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> ::std::fmt::Result {
86        f.debug_struct("TlsConfigBuilder").finish()
87    }
88}
89
90impl TlsConfigBuilder {
91    /// Create a new TlsConfigBuilder
92    pub fn new() -> TlsConfigBuilder {
93        TlsConfigBuilder {
94            key: Box::new(io::empty()),
95            cert: Box::new(io::empty()),
96        }
97    }
98
99    /// sets the Tls key via File Path, returns `TlsConfigError::IoError` if the file cannot be open
100    pub fn key_path(mut self, path: impl AsRef<Path>) -> Self {
101        self.key = Box::new(LazyFile {
102            path: path.as_ref().into(),
103            file: None,
104        });
105        self
106    }
107
108    /// sets the Tls key via bytes slice
109    pub fn key(mut self, key: &[u8]) -> Self {
110        self.key = Box::new(Cursor::new(Vec::from(key)));
111        self
112    }
113
114    /// Specify the file path for the TLS certificate to use.
115    pub fn cert_path(mut self, path: impl AsRef<Path>) -> Self {
116        self.cert = Box::new(LazyFile {
117            path: path.as_ref().into(),
118            file: None,
119        });
120        self
121    }
122
123    /// sets the Tls certificate via bytes slice
124    pub fn cert(mut self, cert: &[u8]) -> Self {
125        self.cert = Box::new(Cursor::new(Vec::from(cert)));
126        self
127    }
128
129    /// Builds TLS configuration.
130    pub fn build(mut self) -> Result<ServerConfig, TlsConfigError> {
131        let mut cert_rdr = BufReader::new(self.cert);
132        let cert = CertificateDer::pem_reader_iter(&mut cert_rdr)
133            .collect::<Result<Vec<_>, _>>()
134            .map_err(|_e| TlsConfigError::CertParseError)?;
135
136        // convert it to Vec<u8> to allow reading it again if key is RSA
137        let mut key_buf = Vec::new();
138        self.key
139            .read_to_end(&mut key_buf)
140            .map_err(TlsConfigError::Io)?;
141
142        if key_buf.is_empty() {
143            return Err(TlsConfigError::EmptyKey);
144        }
145
146        let reader = Cursor::new(key_buf);
147        let key = PrivateKeyDer::from_pem_reader(reader).map_err(|err| match err {
148            rustls_pki_types::pem::Error::Base64Decode(_) => TlsConfigError::InvalidIdentityPem,
149            rustls_pki_types::pem::Error::NoItemsFound => TlsConfigError::EmptyKey,
150            rustls_pki_types::pem::Error::IllegalSectionStart { line } => {
151                TlsConfigError::IllegalSectionStart(line)
152            }
153            rustls_pki_types::pem::Error::MissingSectionEnd { end_marker } => {
154                TlsConfigError::IllegalSectionEnd(end_marker)
155            }
156            rustls_pki_types::pem::Error::Io(err) => {
157                TlsConfigError::Io(io::Error::new(io::ErrorKind::InvalidData, err))
158            }
159            _ => TlsConfigError::InvalidIdentityPem,
160        })?;
161
162        let mut config = ServerConfig::builder()
163            .with_no_client_auth()
164            .with_single_cert(cert, key)
165            .map_err(TlsConfigError::InvalidKey)?;
166        config.alpn_protocols = vec!["h2".into(), "http/1.1".into()];
167        Ok(config)
168    }
169}
170
171impl Default for TlsConfigBuilder {
172    fn default() -> Self {
173        Self::new()
174    }
175}
176
177struct LazyFile {
178    path: PathBuf,
179    file: Option<File>,
180}
181
182impl LazyFile {
183    fn lazy_read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
184        if self.file.is_none() {
185            self.file = Some(File::open(&self.path)?);
186        }
187
188        match self.file.as_mut() {
189            Some(file) => file.read(buf),
190            None => Err(io::Error::other("file handle unavailable after open")),
191        }
192    }
193}
194
195impl Read for LazyFile {
196    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
197        self.lazy_read(buf).map_err(|err| {
198            let kind = err.kind();
199            io::Error::new(
200                kind,
201                format!("error reading file ({:?}): {}", self.path.display(), err),
202            )
203        })
204    }
205}
206
207/// State of the TLS stream, either handshaking or streaming.
208enum State {
209    Handshaking(tokio_rustls::Accept<TcpStream>),
210    Streaming(tokio_rustls::server::TlsStream<TcpStream>),
211}
212
213/// TlsStream implements AsyncRead/AsyncWrite handshaking tokio_rustls::Accept first.
214///
215/// tokio_rustls::server::TlsStream doesn't expose constructor methods,
216/// so we have to TlsAcceptor::accept and handshake to have access to it.
217pub struct TlsStream {
218    state: State,
219}
220
221impl TlsStream {
222    fn new(stream: TcpStream, config: Arc<ServerConfig>) -> TlsStream {
223        let accept = tokio_rustls::TlsAcceptor::from(config).accept(stream);
224        TlsStream {
225            state: State::Handshaking(accept),
226        }
227    }
228}
229
230impl AsyncRead for TlsStream {
231    fn poll_read(
232        self: Pin<&mut Self>,
233        cx: &mut Context<'_>,
234        buf: &mut ReadBuf<'_>,
235    ) -> Poll<io::Result<()>> {
236        let pin = self.get_mut();
237        match pin.state {
238            State::Handshaking(ref mut accept) => match ready!(Pin::new(accept).poll(cx)) {
239                Ok(mut stream) => {
240                    let result = Pin::new(&mut stream).poll_read(cx, buf);
241                    pin.state = State::Streaming(stream);
242                    result
243                }
244                Err(err) => Poll::Ready(Err(err)),
245            },
246            State::Streaming(ref mut stream) => Pin::new(stream).poll_read(cx, buf),
247        }
248    }
249}
250
251impl AsyncWrite for TlsStream {
252    fn poll_write(
253        self: Pin<&mut Self>,
254        cx: &mut Context<'_>,
255        buf: &[u8],
256    ) -> Poll<io::Result<usize>> {
257        let pin = self.get_mut();
258        match pin.state {
259            State::Handshaking(ref mut accept) => match ready!(Pin::new(accept).poll(cx)) {
260                Ok(mut stream) => {
261                    let result = Pin::new(&mut stream).poll_write(cx, buf);
262                    pin.state = State::Streaming(stream);
263                    result
264                }
265                Err(err) => Poll::Ready(Err(err)),
266            },
267            State::Streaming(ref mut stream) => Pin::new(stream).poll_write(cx, buf),
268        }
269    }
270
271    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
272        match self.state {
273            State::Handshaking(_) => Poll::Ready(Ok(())),
274            State::Streaming(ref mut stream) => Pin::new(stream).poll_flush(cx),
275        }
276    }
277
278    fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
279        match self.state {
280            State::Handshaking(_) => Poll::Ready(Ok(())),
281            State::Streaming(ref mut stream) => Pin::new(stream).poll_shutdown(cx),
282        }
283    }
284}
285
286/// Type to intercept Tls incoming connections.
287#[derive(Clone)]
288pub struct TlsAcceptor {
289    config: Arc<ServerConfig>,
290}
291
292impl TlsAcceptor {
293    /// Creates a new Tls interceptor from a built [`ServerConfig`].
294    pub fn new(config: ServerConfig) -> TlsAcceptor {
295        TlsAcceptor {
296            config: Arc::new(config),
297        }
298    }
299
300    /// Wraps an accepted [`TcpStream`] in a [`TlsStream`].
301    ///
302    /// The TLS handshake is driven lazily inside the returned stream's
303    /// [`AsyncRead`] / [`AsyncWrite`] implementations, so this method
304    /// returns immediately.
305    pub async fn accept(&self, stream: TcpStream) -> io::Result<TlsStream> {
306        Ok(TlsStream::new(stream, self.config.clone()))
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313
314    #[test]
315    fn file_cert_key_rsa_pkcs1() {
316        TlsConfigBuilder::new()
317            .cert_path("tests/tls/local.dev_cert.rsa_pkcs1.pem")
318            .key_path("tests/tls/local.dev_key.rsa_pkcs1.pem")
319            .build()
320            .unwrap();
321    }
322
323    #[test]
324    fn bytes_cert_key_rsa_pkcs1() {
325        let cert = include_str!("../tests/tls/local.dev_cert.rsa_pkcs1.pem");
326        let key = include_str!("../tests/tls/local.dev_key.rsa_pkcs1.pem");
327
328        TlsConfigBuilder::new()
329            .key(key.as_bytes())
330            .cert(cert.as_bytes())
331            .build()
332            .unwrap();
333    }
334
335    #[test]
336    fn file_cert_key_pkcs8() {
337        TlsConfigBuilder::new()
338            .cert_path("tests/tls/local.dev_cert.pkcs8.pem")
339            .key_path("tests/tls/local.dev_key.pkcs8.pem")
340            .build()
341            .unwrap();
342    }
343
344    #[test]
345    fn bytes_cert_key_pkcs8() {
346        let cert = include_str!("../tests/tls/local.dev_cert.pkcs8.pem");
347        let key = include_str!("../tests/tls/local.dev_key.pkcs8.pem");
348
349        TlsConfigBuilder::new()
350            .key(key.as_bytes())
351            .cert(cert.as_bytes())
352            .build()
353            .unwrap();
354    }
355
356    #[test]
357    fn file_cert_key_sec1_ec() {
358        TlsConfigBuilder::new()
359            .cert_path("tests/tls/local.dev_cert.sec1_ec.pem")
360            .key_path("tests/tls/local.dev_key.sec1_ec.pem")
361            .build()
362            .unwrap();
363    }
364
365    #[test]
366    fn bytes_cert_key_sec1_ec() {
367        let cert = include_str!("../tests/tls/local.dev_cert.sec1_ec.pem");
368        let key = include_str!("../tests/tls/local.dev_key.sec1_ec.pem");
369
370        TlsConfigBuilder::new()
371            .key(key.as_bytes())
372            .cert(cert.as_bytes())
373            .build()
374            .unwrap();
375    }
376
377    #[test]
378    fn missing_cert_path_returns_io_error() {
379        let err = TlsConfigBuilder::new()
380            .cert_path("tests/tls/nonexistent_cert.pem")
381            .key_path("tests/tls/local.dev_key.pkcs8.pem")
382            .build()
383            .unwrap_err();
384        assert!(
385            matches!(err, TlsConfigError::CertParseError | TlsConfigError::Io(_)),
386            "expected Io or CertParseError, got: {err}"
387        );
388    }
389
390    #[test]
391    fn missing_key_path_returns_io_error() {
392        let err = TlsConfigBuilder::new()
393            .cert_path("tests/tls/local.dev_cert.pkcs8.pem")
394            .key_path("tests/tls/nonexistent_key.pem")
395            .build()
396            .unwrap_err();
397        assert!(
398            matches!(err, TlsConfigError::Io(_)),
399            "expected Io error, got: {err}"
400        );
401    }
402
403    #[test]
404    fn empty_key_bytes_returns_empty_key_error() {
405        let cert = include_str!("../tests/tls/local.dev_cert.pkcs8.pem");
406        let err = TlsConfigBuilder::new()
407            .cert(cert.as_bytes())
408            .key(b"")
409            .build()
410            .unwrap_err();
411        assert!(
412            matches!(err, TlsConfigError::EmptyKey),
413            "expected EmptyKey error, got: {err}"
414        );
415    }
416
417    #[test]
418    fn mismatched_cert_key_returns_invalid_key_error() {
419        // Use rsa_pkcs1 cert with ec key — should fail at the ServerConfig builder stage
420        let cert = include_str!("../tests/tls/local.dev_cert.rsa_pkcs1.pem");
421        let key = include_str!("../tests/tls/local.dev_key.sec1_ec.pem");
422        let err = TlsConfigBuilder::new()
423            .cert(cert.as_bytes())
424            .key(key.as_bytes())
425            .build()
426            .unwrap_err();
427        assert!(
428            matches!(err, TlsConfigError::InvalidKey(_)),
429            "expected InvalidKey error for mismatched cert/key, got: {err}"
430        );
431    }
432
433    #[cfg(feature = "tls-fips")]
434    #[test]
435    fn fips_mode_is_active() {
436        aws_lc_rs::try_fips_mode()
437            .expect("FIPS mode should be active when tls-fips feature is enabled");
438    }
439}