Skip to main content

go_http/
tls.rs

1// SPDX-License-Identifier: Apache-2.0
2
3/// TLS configuration helpers.
4///
5/// Thin wrappers around `rustls` and `rustls-pemfile` that load server
6/// certificates / keys and build client root stores.
7use std::fs;
8use std::io::BufReader;
9use std::sync::Arc;
10
11use rustls::pki_types::{CertificateDer, PrivateKeyDer};
12
13use crate::error::HttpError;
14
15// ---------------------------------------------------------------------------
16// Server TLS config
17// ---------------------------------------------------------------------------
18
19/// Load a `rustls::ServerConfig` from PEM certificate and key files.
20///
21/// `cert_file` may contain a certificate chain (leaf first).
22/// `key_file` must contain a single RSA, ECDSA, or PKCS#8 private key.
23pub fn server_config(
24    cert_file: &str,
25    key_file:  &str,
26) -> Result<Arc<rustls::ServerConfig>, HttpError> {
27    let certs = load_certs(cert_file)?;
28    let key   = load_private_key(key_file)?;
29
30    let cfg = rustls::ServerConfig::builder()
31        .with_no_client_auth()
32        .with_single_cert(certs, key)
33        .map_err(|e| HttpError::Tls(e.to_string()))?;
34
35    Ok(Arc::new(cfg))
36}
37
38// ---------------------------------------------------------------------------
39// Client TLS config
40// ---------------------------------------------------------------------------
41
42/// Build a `rustls::ClientConfig` that trusts the Mozilla root CA bundle
43/// (via `webpki-roots`).
44///
45/// This is the equivalent of Go's default `http.Transport` which uses the
46/// system root store.  For custom CA certificates call `client_config_with_roots`.
47pub fn default_client_config() -> Arc<rustls::ClientConfig> {
48    use std::sync::OnceLock;
49    static CFG: OnceLock<Arc<rustls::ClientConfig>> = OnceLock::new();
50    Arc::clone(CFG.get_or_init(|| {
51        let mut roots = rustls::RootCertStore::empty();
52        roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
53        Arc::new(
54            rustls::ClientConfig::builder()
55                .with_root_certificates(roots)
56                .with_no_client_auth(),
57        )
58    }))
59}
60
61/// Build a `rustls::ClientConfig` that trusts a custom PEM CA certificate file
62/// in addition to the Mozilla roots.
63pub fn client_config_with_ca(ca_file: &str) -> Result<Arc<rustls::ClientConfig>, HttpError> {
64    let mut roots = rustls::RootCertStore::empty();
65    roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
66
67    let ca_certs = load_certs(ca_file)?;
68    for cert in ca_certs {
69        roots.add(cert).map_err(|e| HttpError::Tls(e.to_string()))?;
70    }
71
72    Ok(Arc::new(
73        rustls::ClientConfig::builder()
74            .with_root_certificates(roots)
75            .with_no_client_auth(),
76    ))
77}
78
79// ---------------------------------------------------------------------------
80// PEM loading helpers
81// ---------------------------------------------------------------------------
82
83fn load_certs(path: &str) -> Result<Vec<CertificateDer<'static>>, HttpError> {
84    let f   = fs::File::open(path).map_err(|e| HttpError::Io(e))?;
85    let mut r = BufReader::new(f);
86    rustls_pemfile::certs(&mut r)
87        .collect::<Result<Vec<_>, _>>()
88        .map_err(|e| HttpError::Tls(e.to_string()))
89}
90
91fn load_private_key(path: &str) -> Result<PrivateKeyDer<'static>, HttpError> {
92    let f   = fs::File::open(path).map_err(HttpError::Io)?;
93    let mut r = BufReader::new(f);
94    rustls_pemfile::private_key(&mut r)
95        .map_err(|e| HttpError::Tls(e.to_string()))?
96        .ok_or_else(|| HttpError::Tls(format!("no private key found in {path}")))
97}