Skip to main content

postrust_proxy/tls/
server.rs

1//! TLS server configuration for the HTTPS listener.
2//!
3//! Kept out of `acme.rs` deliberately: serving TLS from a certificate on disk
4//! should not require the `acme` feature, which pulls in `rustls-acme` and is
5//! compiled out of some builds.
6
7use std::io::BufReader;
8use std::sync::Arc;
9
10use tokio_rustls::rustls::ServerConfig;
11
12use crate::error::{ProxyError, ProxyResult};
13
14/// ALPN protocols offered, in preference order.
15///
16/// This is what makes HTTP/2 reachable the way browsers actually use it. Without
17/// it a TLS listener negotiates nothing and every client falls back to
18/// HTTP/1.1, leaving h2 available only as cleartext h2c.
19pub const ALPN_PROTOCOLS: [&[u8]; 2] = [b"h2", b"http/1.1"];
20
21/// Select the rustls crypto provider for this process.
22///
23/// Both `aws-lc-rs` and `ring` end up in the dependency tree -- rustls is
24/// configured for the former, reqwest pulls the latter -- so rustls refuses to
25/// guess and panics on first use unless one is installed explicitly. Idempotent:
26/// a second call returns Err and is ignored.
27pub(crate) fn install_crypto_provider() {
28    static ONCE: std::sync::Once = std::sync::Once::new();
29    ONCE.call_once(|| {
30        let _ = tokio_rustls::rustls::crypto::aws_lc_rs::default_provider().install_default();
31    });
32}
33
34/// Build a rustls `ServerConfig` from PEM bytes, with ALPN configured.
35pub fn build_server_config(cert_pem: &[u8], key_pem: &[u8]) -> ProxyResult<Arc<ServerConfig>> {
36    install_crypto_provider();
37
38    let certs: Vec<_> = rustls_pemfile::certs(&mut BufReader::new(cert_pem))
39        .filter_map(|r| r.ok())
40        .collect();
41
42    if certs.is_empty() {
43        return Err(ProxyError::Tls("no certificates found in PEM".into()));
44    }
45
46    let key = rustls_pemfile::private_key(&mut BufReader::new(key_pem))
47        .map_err(|e| ProxyError::Tls(format!("could not parse private key: {}", e)))?
48        .ok_or_else(|| ProxyError::Tls("no private key found in PEM".into()))?;
49
50    let mut config = ServerConfig::builder()
51        .with_no_client_auth()
52        .with_single_cert(certs, key)
53        .map_err(|e| ProxyError::Tls(format!("could not build TLS config: {}", e)))?;
54
55    config.alpn_protocols = ALPN_PROTOCOLS.iter().map(|p| p.to_vec()).collect();
56
57    Ok(Arc::new(config))
58}
59
60/// Build a `ServerConfig` that picks its certificate per handshake.
61///
62/// The single-certificate form above cannot serve a multi-tenant proxy: it
63/// answers every handshake with the same chain whatever name was asked for.
64/// See [`crate::tls::SniCertResolver`].
65pub fn build_server_config_with_resolver(
66    resolver: Arc<dyn tokio_rustls::rustls::server::ResolvesServerCert>,
67) -> Arc<ServerConfig> {
68    install_crypto_provider();
69
70    let mut config = ServerConfig::builder()
71        .with_no_client_auth()
72        .with_cert_resolver(resolver);
73
74    config.alpn_protocols = ALPN_PROTOCOLS.iter().map(|p| p.to_vec()).collect();
75
76    Arc::new(config)
77}
78
79/// Load a certificate and key from disk and build a `ServerConfig`.
80pub async fn load_server_config(cert_file: &str, key_file: &str) -> ProxyResult<Arc<ServerConfig>> {
81    let cert_pem = tokio::fs::read(cert_file)
82        .await
83        .map_err(|e| ProxyError::Tls(format!("could not read {}: {}", cert_file, e)))?;
84    let key_pem = tokio::fs::read(key_file)
85        .await
86        .map_err(|e| ProxyError::Tls(format!("could not read {}: {}", key_file, e)))?;
87    build_server_config(&cert_pem, &key_pem)
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_alpn_offers_h2_first() {
96        // Order is preference order. If h2 is dropped or demoted, HTTP/2 stops
97        // being reachable over TLS and every client silently falls back to
98        // HTTP/1.1 -- which is exactly the state this module was added to fix.
99        assert_eq!(ALPN_PROTOCOLS[0], b"h2");
100        assert_eq!(ALPN_PROTOCOLS[1], b"http/1.1");
101    }
102
103    #[test]
104    fn test_rejects_pem_without_certificate() {
105        let err = build_server_config(b"not a certificate", b"not a key").unwrap_err();
106        assert!(
107            err.to_string().contains("no certificates"),
108            "unexpected error: {err}"
109        );
110    }
111
112    #[test]
113    fn test_rejects_certificate_without_key() {
114        // A well-formed but empty cert block, and no key at all.
115        let cert = b"-----BEGIN CERTIFICATE-----\nMA==\n-----END CERTIFICATE-----\n";
116        let err = build_server_config(cert, b"").unwrap_err();
117        assert!(
118            err.to_string().contains("no private key"),
119            "unexpected error: {err}"
120        );
121    }
122}