simple_hyper_server_tls/
lib.rs

1// lib.rs - main library
2//
3// simple-hyper-server-tls - Library to simplify initialization TLS for hyper server
4// Copyright (C) 2022  Mateusz Szpakowski
5//
6// This library is free software; you can redistribute it and/or
7// modify it under the terms of the GNU Lesser General Public
8// License as published by the Free Software Foundation; either
9// version 2.1 of the License, or (at your option) any later version.
10//
11// This library is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14// Lesser General Public License for more details.
15//
16// You should have received a copy of the GNU Lesser General Public
17// License along with this library; if not, write to the Free Software
18// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19
20#![cfg_attr(docsrs, feature(doc_cfg))]
21//! The library to simplify TLS configuration for Hyper server including ALPN
22//! (Application-Layer Protocol Negotiation) setup.
23//! This library setup TLS configuration suitable for clients.
24//! The configuration includes the HTTP protocol choice setup (ALPN mechanism setup)
25//! thanks to the almost clients can choose for example HTTP/2 protocol.
26//!
27//! The usage of this library requires choose suitable the TLS implementation, by choosing
28//! feature that one of:
29//! * `tls-rustls` - RusTLS - native for Rust TLS implementation based on tokio-rustls,
30//! * `tls-openssl` - OpenSSL - TLS implementation based native OpenSSL library and openssl.
31//!
32//! The `tls-openssl` is recommended for systems which can not handle rustls
33//! due to some problems, like lacks of some CPU instructions needed by `ring` crate.
34//! For other systems, `tls-rustls` should be preferred.
35//!
36//! By default three versions of HTTP protocol are enabled (HTTP/1.0, HTTP/1.1, HTTP/2).
37//! It is possible to choose only one version by disabling default features and choose
38//! one of features:
39//! * `hyper-h1` - for HTTP/1.0 or HTTP/1.1,
40//! * `hyper-h2` - for HTTP/2.
41//!
42//! ## List of other features
43//! * `hyper-full-server` - enables all features for hyper server.
44//!
45//! ## Examples
46//! The simplest usage is:
47//!
48//! ```no_run
49//! use std::{convert::Infallible, net::SocketAddr};
50//! use simple_hyper_server_tls::*;
51//! use hyper::{Body, Request, Response, Server};
52//! use hyper::service::{make_service_fn, service_fn};
53//!
54//! async fn handle(_: Request<Body>) -> Result<Response<Body>, Infallible> {
55//!     Ok(Response::new("Hello, World!".into()))
56//! }
57//!
58//! #[tokio::main]
59//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
60//!     let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
61//!
62//!     let make_svc = make_service_fn(|_conn| async {
63//!         Ok::<_, Infallible>(service_fn(handle))
64//!     });
65//!     let mut server = hyper_from_pem_files("cert.pem", "key.pem", Protocols::ALL, &addr)?
66//!             .serve(make_svc);
67//!     while let Err(e) = (&mut server).await {
68//!         eprintln!("server error: {}", e);
69//!     }
70//!     Ok(())
71//! }
72//! ```
73//!
74//! Additional functions can be used for customization of the TLS configuration.
75
76use hyper::server::conn::AddrIncoming;
77use hyper::server::{Builder, Server};
78#[cfg(feature = "tls-openssl")]
79use openssl::pkey::PKey;
80#[cfg(feature = "tls-openssl")]
81use openssl::ssl::{SslContext, SslContextBuilder, SslFiletype, SslMethod, SslRef};
82#[cfg(feature = "tls-openssl")]
83use openssl::x509::X509;
84#[cfg(feature = "tls-rustls")]
85use rustls::ServerConfig;
86#[cfg(feature = "tls-rustls")]
87use rustls_pemfile;
88use std::net::SocketAddr;
89use std::path::Path;
90#[cfg(any(feature = "tls-rustls", feature = "tls-openssl"))]
91use tls_listener::hyper::WrappedAccept;
92#[cfg(feature = "tls-rustls")]
93use tokio_rustls::rustls::{Certificate, PrivateKey};
94#[cfg(feature = "tls-rustls")]
95use tokio_rustls::TlsAcceptor;
96
97pub use hyper;
98#[cfg(feature = "tls-openssl")]
99pub use openssl;
100#[cfg(feature = "tls-rustls")]
101pub use rustls;
102#[cfg(any(feature = "tls-rustls", feature = "tls-openssl"))]
103pub use tls_listener;
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106/// Defines protocols that will be used in TLS configuration.
107pub enum Protocols {
108    /// All protocols enabled by features (HTTP/1.0, HTTP/1.1, HTTP/2).
109    ALL,
110    #[cfg(feature = "hyper-h1")]
111    #[cfg_attr(docsrs, doc(cfg(feature = "hyper-h1")))]
112    /// Only HTTP/1.0 or HTTP/1.1 if enabled by hyper-h1.
113    HTTP1,
114    #[cfg(feature = "hyper-h2")]
115    #[cfg_attr(docsrs, doc(cfg(feature = "hyper-h2")))]
116    /// Only HTTP/2 if enabled by hyper-h2.
117    HTTP2,
118}
119
120/// Boxed error type.
121pub type Error = Box<dyn std::error::Error>;
122
123#[cfg(feature = "tls-rustls")]
124fn rustls_server_config_from_readers<R: std::io::Read>(
125    cert: R,
126    key: R,
127    protocols: Protocols,
128) -> Result<ServerConfig, Error> {
129    use std::io::{self, BufReader};
130    // load certificates and keys from Read
131    let certs = rustls_pemfile::certs(&mut BufReader::new(cert))
132        .map(|mut certs| certs.drain(..).map(Certificate).collect())?;
133    let mut keys: Vec<PrivateKey> = rustls_pemfile::pkcs8_private_keys(&mut BufReader::new(key))
134        .map(|mut keys| keys.drain(..).map(PrivateKey).collect())?;
135    let mut config = ServerConfig::builder()
136        .with_safe_defaults()
137        .with_no_client_auth()
138        .with_single_cert(certs, keys.remove(0))
139        .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
140
141    // set up ALPN protocols based on Protocols
142    config.alpn_protocols = match protocols {
143        #[cfg(all(feature = "hyper-h1", feature = "hyper-h2"))]
144        Protocols::ALL => vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()],
145
146        #[cfg(all(feature = "hyper-h1", not(feature = "hyper-h2")))]
147        Protocols::ALL => vec![b"http/1.1".to_vec(), b"http/1.0".to_vec()],
148
149        #[cfg(all(not(feature = "hyper-h1"), feature = "hyper-h2"))]
150        Protocols::ALL => vec![b"h2".to_vec()],
151
152        #[cfg(feature = "hyper-h1")]
153        Protocols::HTTP1 => vec![b"http/1.1".to_vec(), b"http/1.0".to_vec()],
154        #[cfg(feature = "hyper-h2")]
155        Protocols::HTTP2 => vec![b"h2".to_vec()],
156    };
157    Ok(config)
158}
159
160#[cfg(feature = "tls-rustls")]
161#[cfg_attr(docsrs, doc(cfg(feature = "tls-rustls")))]
162/// The low-level function to retrieve configuration to further customization.
163///
164/// Creates the RusTLS server configuration. Certificates and key will be obtained from files.
165/// Protocols determines list of protocols that will be supported.
166pub fn rustls_server_config_from_pem_files<P: AsRef<Path>, Q: AsRef<Path>>(
167    cert_file: P,
168    key_file: Q,
169    protocols: Protocols,
170) -> Result<ServerConfig, Error> {
171    use std::fs::File;
172    rustls_server_config_from_readers(File::open(cert_file)?, File::open(key_file)?, protocols)
173}
174
175#[cfg(feature = "tls-rustls")]
176#[cfg_attr(docsrs, doc(cfg(feature = "tls-rustls")))]
177/// The low-level function to retrieve configuration to further customization.
178///
179/// Creates the RusTLS server configuration. Certificates and key will be obtained from data.
180/// Protocols determines list of protocols that will be supported.
181pub fn rustls_server_config_from_pem_data<'a>(
182    cert: &'a [u8],
183    key: &'a [u8],
184    protocols: Protocols,
185) -> Result<ServerConfig, Error> {
186    rustls_server_config_from_readers(cert, key, protocols)
187}
188
189#[cfg(feature = "tls-openssl")]
190fn ssl_context_set_alpns(
191    builder: &mut SslContextBuilder,
192    protocols: Protocols,
193) -> Result<(), Error> {
194    // set up ALPN protocols based on Protocols
195    let protos = match protocols {
196        #[cfg(all(feature = "hyper-h1", feature = "hyper-h2"))]
197        Protocols::ALL => &b"\x02h2\x08http/1.1\x08http/1.0"[..],
198
199        #[cfg(all(feature = "hyper-h1", not(feature = "hyper-h2")))]
200        Protocols::ALL => &b"\x08http/1.1\x08http/1.0"[..],
201
202        #[cfg(all(not(feature = "hyper-h1"), feature = "hyper-h2"))]
203        Protocols::ALL => &b"\x02h2"[..],
204
205        #[cfg(feature = "hyper-h1")]
206        Protocols::HTTP1 => &b"\x08http/1.1\x08http/1.0"[..],
207        #[cfg(feature = "hyper-h2")]
208        Protocols::HTTP2 => &b"\x02h2"[..],
209    };
210    builder.set_alpn_protos(protos)?;
211    // set uo ALPN selection routine - as select_next_proto
212    builder.set_alpn_select_callback(move |_: &mut SslRef, list: &[u8]| {
213        openssl::ssl::select_next_proto(protos, list).ok_or(openssl::ssl::AlpnError::NOACK)
214    });
215    Ok(())
216}
217
218#[cfg(feature = "tls-openssl")]
219#[cfg_attr(docsrs, doc(cfg(feature = "tls-openssl")))]
220/// The low-level function to retrieve configuration to further customization.
221///
222/// Creates the SSL context builder. Certificates and key will be obtained from files.
223/// Protocols determines list of protocols that will be supported.
224pub fn ssl_context_builder_from_pem_files<P: AsRef<Path>, Q: AsRef<Path>>(
225    cert_file: P,
226    key_file: Q,
227    protocols: Protocols,
228) -> Result<SslContextBuilder, Error> {
229    let mut builder = SslContext::builder(SslMethod::tls_server()).unwrap();
230    builder.set_certificate_chain_file(cert_file)?;
231    builder.set_private_key_file(key_file, SslFiletype::PEM)?;
232    ssl_context_set_alpns(&mut builder, protocols)?;
233    Ok(builder)
234}
235
236#[cfg(feature = "tls-openssl")]
237#[cfg_attr(docsrs, doc(cfg(feature = "tls-openssl")))]
238/// The low-level function to retrieve configuration to further customization.
239///
240/// Creates the SSL context builder. Certificates and key will be obtained from data.
241/// Protocols determines list of protocols that will be supported.
242pub fn ssl_context_builder_from_pem_data<'a>(
243    cert: &'a [u8],
244    key: &'a [u8],
245    protocols: Protocols,
246) -> Result<SslContextBuilder, Error> {
247    let mut builder = SslContext::builder(SslMethod::tls_server()).unwrap();
248    let mut certs = X509::stack_from_pem(cert)?;
249    let mut certs = certs.drain(..);
250    builder.set_certificate(certs.next().ok_or("no leaf certificate")?.as_ref())?;
251    certs.try_for_each(|cert| builder.add_extra_chain_cert(cert))?;
252    builder.set_private_key(PKey::private_key_from_pem(key)?.as_ref())?;
253    ssl_context_set_alpns(&mut builder, protocols)?;
254    Ok(builder)
255}
256
257#[cfg(feature = "tls-rustls")]
258/// TlsListener for hyper server.
259pub type TlsListener = tls_listener::TlsListener<WrappedAccept<AddrIncoming>, TlsAcceptor>;
260#[cfg(all(not(docsrs), feature = "tls-openssl"))]
261/// TlsListener for hyper server.
262pub type TlsListener = tls_listener::TlsListener<WrappedAccept<AddrIncoming>, SslContext>;
263
264/// The higher level function. Creates the TLS listener for Hyper server.
265///
266/// Certificates and key will be obtained from files.
267/// Protocols determines list of protocols that will be supported.
268/// Typical usage is:
269/// ```no run
270/// let listener = listener_from_pem_files("cert.pem", "key.pem", Protocols::ALL, &addr)?;
271/// let server = Server::builder(listener).serve(make_svc);
272/// ```
273pub fn listener_from_pem_files<P: AsRef<Path>, Q: AsRef<Path>>(
274    cert_file: P,
275    key_file: Q,
276    protocols: Protocols,
277    addr: &SocketAddr,
278) -> Result<TlsListener, Error> {
279    #[cfg(feature = "tls-rustls")]
280    let acceptor = {
281        use std::sync::Arc;
282        let config = rustls_server_config_from_pem_files(cert_file, key_file, protocols)?;
283        TlsAcceptor::from(Arc::new(config))
284    };
285    #[cfg(feature = "tls-openssl")]
286    let acceptor = {
287        let builder = ssl_context_builder_from_pem_files(cert_file, key_file, protocols)?;
288        builder.build()
289    };
290    Ok(TlsListener::new_hyper(acceptor, AddrIncoming::bind(addr)?))
291}
292
293/// The higher level function. Creates the TLS listener for Hyper server.
294///
295/// Certificates and key will be obtained from data.
296/// Protocols determines list of protocols that will be supported.
297/// Typical usage is:
298/// ```no run
299/// let listener = listener_from_pem_data(cert_data, key_data, Protocols::ALL, &addr)?;
300/// let server = Server::builder(listener).serve(make_svc);
301/// ```
302pub fn listener_from_pem_data<'a>(
303    cert: &'a [u8],
304    key: &'a [u8],
305    protocols: Protocols,
306    addr: &SocketAddr,
307) -> Result<TlsListener, Error> {
308    #[cfg(feature = "tls-rustls")]
309    let acceptor = {
310        use std::sync::Arc;
311        let config = rustls_server_config_from_pem_data(cert, key, protocols)?;
312        TlsAcceptor::from(Arc::new(config))
313    };
314    #[cfg(feature = "tls-openssl")]
315    let acceptor = {
316        let builder = ssl_context_builder_from_pem_data(cert, key, protocols)?;
317        builder.build()
318    };
319    Ok(TlsListener::new_hyper(acceptor, AddrIncoming::bind(&addr)?))
320}
321
322/// The highest level function. Creates the Hyper server builder.
323///
324/// Certificates and key will be obtained from files.
325/// Protocols determines list of protocols that will be supported.
326/// Typical usage is:
327/// ```no run
328/// let server = hyper_from_pem_files("cert.pem", "key.pem", Protocols::ALL, &addr)?
329///             .serve(make_svc);
330/// ```
331pub fn hyper_from_pem_files<P: AsRef<Path>, Q: AsRef<Path>>(
332    cert_file: P,
333    key_file: Q,
334    protocols: Protocols,
335    addr: &SocketAddr,
336) -> Result<Builder<TlsListener>, Error> {
337    let listener = listener_from_pem_files(cert_file, key_file, protocols, addr)?;
338    let builder = Server::builder(listener);
339    Ok(match protocols {
340        Protocols::ALL => builder,
341        #[cfg(feature = "hyper-h1")]
342        Protocols::HTTP1 => builder.http1_only(true),
343        #[cfg(feature = "hyper-h2")]
344        Protocols::HTTP2 => builder.http2_only(true),
345    })
346}
347
348/// The highest level function. Creates the Hyper server builder.
349///
350/// Certificates and key will be obtained from data.
351/// Protocols determines list of protocols that will be supported.
352/// Typical usage is:
353/// ```no run
354/// let server = hyper_from_pem_data(cert_data, key_data, Protocols::ALL, &addr)?
355///             .serve(make_svc);
356/// ```
357pub fn hyper_from_pem_data<'a>(
358    cert: &'a [u8],
359    key: &'a [u8],
360    protocols: Protocols,
361    addr: &SocketAddr,
362) -> Result<Builder<TlsListener>, Error> {
363    let listener = listener_from_pem_data(cert, key, protocols, addr)?;
364    let builder = Server::builder(listener);
365    Ok(match protocols {
366        Protocols::ALL => builder,
367        #[cfg(feature = "hyper-h1")]
368        Protocols::HTTP1 => builder.http1_only(true),
369        #[cfg(feature = "hyper-h2")]
370        Protocols::HTTP2 => builder.http2_only(true),
371    })
372}
373
374#[cfg(test)]
375mod tests {
376    #[test]
377    #[cfg(feature = "tls-rustls")]
378    fn test_rustls_server_config_from_readers() {
379        use super::*;
380        const CERT: &[u8] = include_bytes!("../data/cert.pem");
381        const KEY: &[u8] = include_bytes!("../data/key.pem");
382        let config = rustls_server_config_from_readers(CERT, KEY, Protocols::ALL).unwrap();
383        #[cfg(all(feature = "hyper-h1", feature = "hyper-h2"))]
384        assert_eq!(
385            config.alpn_protocols,
386            vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()]
387        );
388        #[cfg(all(not(feature = "hyper-h1"), feature = "hyper-h2"))]
389        assert_eq!(config.alpn_protocols, vec![b"h2".to_vec()]);
390        #[cfg(all(feature = "hyper-h1", not(feature = "hyper-h2")))]
391        assert_eq!(
392            config.alpn_protocols,
393            vec![b"http/1.1".to_vec(), b"http/1.0".to_vec()]
394        );
395
396        #[cfg(feature = "hyper-h1")]
397        {
398            let config = rustls_server_config_from_readers(CERT, KEY, Protocols::HTTP1).unwrap();
399            assert_eq!(
400                config.alpn_protocols,
401                vec![b"http/1.1".to_vec(), b"http/1.0".to_vec()]
402            );
403        }
404        #[cfg(feature = "hyper-h2")]
405        {
406            let config = rustls_server_config_from_readers(CERT, KEY, Protocols::HTTP2).unwrap();
407            assert_eq!(config.alpn_protocols, vec![b"h2".to_vec()]);
408        }
409    }
410}