1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//! Provides a couple of functions that assist in getting a `TlsAcceptor` from certificate and key data.
//!
//! These functions use safe defaults from rustls to generate the `TlsAcceptor`, but it is not necessary to use them.

use std::fs::File;
use std::io::{BufRead, BufReader, Cursor};
use std::path::Path;
use std::sync::Arc;
use thiserror::Error;
use tokio_rustls::rustls;

/// Error when creating a `TlsAcceptor`
#[derive(Error, Debug)]
pub enum TlsAcceptorError {
    /// No valid PEM data was provided
    #[error("no valid pem data")]
    NoValidPem,
    /// There were no private keys in the provided PEM data
    #[error("no valid private keys in pem data")]
    NoValidKey,
    /// Rustls failed to create the `ServerConfig`
    #[error("failed to create ServerConfig")]
    ServerConfig(#[from] rustls::Error),
    /// Failed to open a PEM file
    #[error("failed to open pem file")]
    FileOpen(#[source] std::io::Error),
    /// General IO errors
    #[error(transparent)]
    Io(#[from] std::io::Error),
}

/// The HTTP protocol to use when clients are connecting.
///
/// This should match the version(s) of HTTP used to serve your application in Hyper.
/// Using `Both` will prefer HTTP/2 over HTTP/1.1
#[derive(Debug, Clone, Copy)]
pub enum HttpProtocol {
    Http1,
    Http2,
    Both,
}

/// Get a `TlsAcceptor` from PEM certificate and key data
///
/// # Errors
/// Errors if there is no valid certificate/key data given, or if rustls fails to create
/// the server config
pub fn get_tlsacceptor_from_pem_data(
    cert_data: &str,
    key_data: &str,
    protocol: HttpProtocol,
) -> Result<tokio_rustls::TlsAcceptor, TlsAcceptorError> {
    let mut cert_reader = BufReader::new(Cursor::new(cert_data));
    let mut key_reader = BufReader::new(Cursor::new(key_data));
    get_tlsacceptor_from_readers(&mut cert_reader, &mut key_reader, protocol)
}

/// Get a `TlsAcceptor` from PEM-encoded certificate and key files
///
/// # Errors
/// Errors if the files cannot be read, if there is no valid certificate/key data given, or if rustls fails to create
/// the server config
pub fn get_tlsacceptor_from_files(
    cert_path: impl AsRef<Path>,
    key_path: impl AsRef<Path>,
    protocol: HttpProtocol,
) -> Result<tokio_rustls::TlsAcceptor, TlsAcceptorError> {
    let cert_file = File::open(cert_path).map_err(TlsAcceptorError::FileOpen)?;
    let key_file = File::open(key_path).map_err(TlsAcceptorError::FileOpen)?;

    let mut cert_reader = BufReader::new(cert_file);
    let mut key_reader = BufReader::new(key_file);

    get_tlsacceptor_from_readers(&mut cert_reader, &mut key_reader, protocol)
}

fn get_tlsacceptor_from_readers(
    cert_reader: &mut dyn BufRead,
    key_reader: &mut dyn BufRead,
    protocol: HttpProtocol,
) -> Result<tokio_rustls::TlsAcceptor, TlsAcceptorError> {
    let certs: Vec<_> = rustls_pemfile::certs(cert_reader)
        .filter_map(Result::ok)
        .collect();

    let key = rustls_pemfile::read_one(key_reader)?.ok_or(TlsAcceptorError::NoValidPem)?;
    let key = match key {
        rustls_pemfile::Item::Sec1Key(data) => data.into(),
        rustls_pemfile::Item::Pkcs1Key(data) => data.into(),
        rustls_pemfile::Item::Pkcs8Key(data) => data.into(),
        _ => return Err(TlsAcceptorError::NoValidKey),
    };

    let mut cfg = rustls::server::ServerConfig::builder()
        .with_no_client_auth()
        .with_single_cert(certs, key)?;

    cfg.alpn_protocols = match protocol {
        HttpProtocol::Http1 => vec![b"http/1.1".to_vec(), b"http/1.0".to_vec()],
        HttpProtocol::Http2 => vec![b"h2".to_vec()],
        HttpProtocol::Both => vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()],
    };

    let acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(cfg));

    Ok(acceptor)
}