Skip to main content

dig_rpc/
tls.rs

1//! TLS / mTLS configuration loading.
2//!
3//! Handles loading PEM-encoded server cert chains + private keys from disk,
4//! and optionally configuring client-certificate verification for the
5//! internal server.
6//!
7//! # Modes
8//!
9//! - **Internal** — mTLS. Server cert + private key, plus a client-CA PEM
10//!   used to verify inbound client certs.
11//! - **Public** — standard HTTPS. Server cert + private key only; clients
12//!   don't present certs.
13//!
14//! # Format notes
15//!
16//! We accept both PKCS#8 and RFC-5958 encoded private keys. The parser uses
17//! [`rustls_pemfile`] which handles `BEGIN PRIVATE KEY`, `BEGIN RSA PRIVATE
18//! KEY`, and `BEGIN EC PRIVATE KEY` blocks.
19
20use std::fs::File;
21use std::io::BufReader;
22use std::path::PathBuf;
23use std::sync::Arc;
24
25use rustls::pki_types::{CertificateDer, PrivateKeyDer};
26use rustls::server::{ServerConfig, WebPkiClientVerifier};
27use rustls::RootCertStore;
28
29/// On-disk paths for an **internal** (mTLS) server.
30#[derive(Debug, Clone)]
31pub struct InternalCertPaths {
32    /// Server certificate chain (PEM).
33    pub server_crt: PathBuf,
34    /// Server private key (PEM, PKCS#8 or SEC1).
35    pub server_key: PathBuf,
36    /// Certificate authority bundle for verifying client certs (PEM).
37    pub client_ca_crt: PathBuf,
38}
39
40/// On-disk paths for a **public** HTTPS server (no client cert verification).
41#[derive(Debug, Clone)]
42pub struct PublicCertPaths {
43    /// Server certificate chain (PEM).
44    pub server_crt: PathBuf,
45    /// Server private key (PEM, PKCS#8 or SEC1).
46    pub server_key: PathBuf,
47}
48
49/// Parsed TLS configuration ready for `axum-server`.
50///
51/// Wraps a `rustls::ServerConfig`.
52#[derive(Clone)]
53pub struct TlsConfig {
54    /// The underlying rustls server config, wrapped in `Arc` so multiple
55    /// listeners can share it.
56    pub server_config: Arc<ServerConfig>,
57}
58
59impl TlsConfig {
60    /// Load + build a TLS config for an internal (mTLS) server.
61    pub fn load_internal(paths: &InternalCertPaths) -> Result<Self, anyhow::Error> {
62        let chain = load_certs(&paths.server_crt)?;
63        let key = load_private_key(&paths.server_key)?;
64
65        let mut roots = RootCertStore::empty();
66        for c in load_certs(&paths.client_ca_crt)? {
67            roots.add(c)?;
68        }
69        let client_verifier = WebPkiClientVerifier::builder(Arc::new(roots)).build()?;
70
71        let config = ServerConfig::builder()
72            .with_client_cert_verifier(client_verifier)
73            .with_single_cert(chain, key)?;
74        Ok(Self {
75            server_config: Arc::new(config),
76        })
77    }
78
79    /// Load + build a TLS config for a public HTTPS server (no mTLS).
80    pub fn load_public(paths: &PublicCertPaths) -> Result<Self, anyhow::Error> {
81        let chain = load_certs(&paths.server_crt)?;
82        let key = load_private_key(&paths.server_key)?;
83
84        let config = ServerConfig::builder()
85            .with_no_client_auth()
86            .with_single_cert(chain, key)?;
87        Ok(Self {
88            server_config: Arc::new(config),
89        })
90    }
91}
92
93impl std::fmt::Debug for TlsConfig {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        f.debug_struct("TlsConfig").finish_non_exhaustive()
96    }
97}
98
99fn load_certs(path: &PathBuf) -> Result<Vec<CertificateDer<'static>>, anyhow::Error> {
100    let f = File::open(path).map_err(|e| anyhow::anyhow!("open {}: {e}", path.display()))?;
101    let mut r = BufReader::new(f);
102    let certs: Vec<_> = rustls_pemfile::certs(&mut r)
103        .collect::<Result<_, _>>()
104        .map_err(|e| anyhow::anyhow!("parse {}: {e}", path.display()))?;
105    if certs.is_empty() {
106        anyhow::bail!("no certificates found in {}", path.display());
107    }
108    Ok(certs)
109}
110
111fn load_private_key(path: &PathBuf) -> Result<PrivateKeyDer<'static>, anyhow::Error> {
112    let f = File::open(path).map_err(|e| anyhow::anyhow!("open {}: {e}", path.display()))?;
113    let mut r = BufReader::new(f);
114    let key = rustls_pemfile::private_key(&mut r)
115        .map_err(|e| anyhow::anyhow!("parse {}: {e}", path.display()))?
116        .ok_or_else(|| anyhow::anyhow!("no private key found in {}", path.display()))?;
117    Ok(key)
118}