siphon_common/
tls.rs

1use rustls::pki_types::{CertificateDer, PrivateKeyDer};
2use rustls::server::WebPkiClientVerifier;
3use rustls::{ClientConfig, RootCertStore, ServerConfig};
4use rustls_pemfile::{certs, private_key};
5use std::fs::File;
6use std::io::{BufReader, Cursor};
7use std::path::Path;
8use std::sync::Arc;
9
10use crate::TunnelError;
11
12/// Load certificates from a PEM file
13fn load_certs(path: &Path) -> Result<Vec<CertificateDer<'static>>, TunnelError> {
14    let file = File::open(path).map_err(|e| {
15        TunnelError::Certificate(format!("Failed to open cert file {:?}: {}", path, e))
16    })?;
17    let mut reader = BufReader::new(file);
18    certs(&mut reader)
19        .collect::<Result<Vec<_>, _>>()
20        .map_err(|e| TunnelError::Certificate(format!("Failed to parse certificates: {}", e)))
21}
22
23/// Load a private key from a PEM file
24fn load_private_key(path: &Path) -> Result<PrivateKeyDer<'static>, TunnelError> {
25    let file = File::open(path).map_err(|e| {
26        TunnelError::Certificate(format!("Failed to open key file {:?}: {}", path, e))
27    })?;
28    let mut reader = BufReader::new(file);
29    private_key(&mut reader)
30        .map_err(|e| TunnelError::Certificate(format!("Failed to parse private key: {}", e)))?
31        .ok_or_else(|| TunnelError::Certificate("No private key found in file".to_string()))
32}
33
34/// Load a root certificate store from a CA file
35fn load_root_store(ca_path: &Path) -> Result<RootCertStore, TunnelError> {
36    let ca_certs = load_certs(ca_path)?;
37    let mut root_store = RootCertStore::empty();
38    for cert in ca_certs {
39        root_store.add(cert).map_err(|e| {
40            TunnelError::Certificate(format!("Failed to add CA certificate: {}", e))
41        })?;
42    }
43    Ok(root_store)
44}
45
46// ============================================================================
47// PEM content loading functions (for secret-resolved content)
48// ============================================================================
49
50/// Load certificates from PEM content string
51pub fn load_certs_from_pem(pem_content: &str) -> Result<Vec<CertificateDer<'static>>, TunnelError> {
52    let mut cursor = Cursor::new(pem_content.as_bytes());
53    certs(&mut cursor)
54        .collect::<Result<Vec<_>, _>>()
55        .map_err(|e| TunnelError::Certificate(format!("Failed to parse certificates: {}", e)))
56}
57
58/// Load a private key from PEM content string
59pub fn load_private_key_from_pem(pem_content: &str) -> Result<PrivateKeyDer<'static>, TunnelError> {
60    let mut cursor = Cursor::new(pem_content.as_bytes());
61    private_key(&mut cursor)
62        .map_err(|e| TunnelError::Certificate(format!("Failed to parse private key: {}", e)))?
63        .ok_or_else(|| TunnelError::Certificate("No private key found in PEM content".to_string()))
64}
65
66/// Load a root certificate store from PEM content string
67fn load_root_store_from_pem(pem_content: &str) -> Result<RootCertStore, TunnelError> {
68    let ca_certs = load_certs_from_pem(pem_content)?;
69    let mut root_store = RootCertStore::empty();
70    for cert in ca_certs {
71        root_store.add(cert).map_err(|e| {
72            TunnelError::Certificate(format!("Failed to add CA certificate: {}", e))
73        })?;
74    }
75    Ok(root_store)
76}
77
78/// Load server TLS config with mTLS (client certificate verification)
79///
80/// # Arguments
81/// * `cert_path` - Path to server certificate PEM file
82/// * `key_path` - Path to server private key PEM file
83/// * `ca_path` - Path to CA certificate for verifying client certificates
84pub fn load_server_config(
85    cert_path: &Path,
86    key_path: &Path,
87    ca_path: &Path,
88) -> Result<ServerConfig, TunnelError> {
89    let certs = load_certs(cert_path)?;
90    let key = load_private_key(key_path)?;
91    let root_store = load_root_store(ca_path)?;
92
93    // Require client certificates
94    let client_verifier = WebPkiClientVerifier::builder(Arc::new(root_store))
95        .build()
96        .map_err(|e| TunnelError::Tls(format!("Failed to build client verifier: {}", e)))?;
97
98    let config = ServerConfig::builder()
99        .with_client_cert_verifier(client_verifier)
100        .with_single_cert(certs, key)
101        .map_err(|e| TunnelError::Tls(format!("Failed to build server config: {}", e)))?;
102
103    Ok(config)
104}
105
106/// Load client TLS config with mTLS (present client certificate)
107///
108/// # Arguments
109/// * `cert_path` - Path to client certificate PEM file
110/// * `key_path` - Path to client private key PEM file
111/// * `ca_path` - Path to CA certificate for verifying server certificate
112pub fn load_client_config(
113    cert_path: &Path,
114    key_path: &Path,
115    ca_path: &Path,
116) -> Result<ClientConfig, TunnelError> {
117    let certs = load_certs(cert_path)?;
118    let key = load_private_key(key_path)?;
119    let root_store = load_root_store(ca_path)?;
120
121    let config = ClientConfig::builder()
122        .with_root_certificates(root_store)
123        .with_client_auth_cert(certs, key)
124        .map_err(|e| TunnelError::Tls(format!("Failed to build client config: {}", e)))?;
125
126    Ok(config)
127}
128
129/// Load server TLS config from PEM content strings with mTLS
130///
131/// # Arguments
132/// * `cert_pem` - Server certificate PEM content
133/// * `key_pem` - Server private key PEM content
134/// * `ca_pem` - CA certificate PEM content for verifying client certificates
135pub fn load_server_config_from_pem(
136    cert_pem: &str,
137    key_pem: &str,
138    ca_pem: &str,
139) -> Result<ServerConfig, TunnelError> {
140    let certs = load_certs_from_pem(cert_pem)?;
141    let key = load_private_key_from_pem(key_pem)?;
142    let root_store = load_root_store_from_pem(ca_pem)?;
143
144    // Require client certificates
145    let client_verifier = WebPkiClientVerifier::builder(Arc::new(root_store))
146        .build()
147        .map_err(|e| TunnelError::Tls(format!("Failed to build client verifier: {}", e)))?;
148
149    let config = ServerConfig::builder()
150        .with_client_cert_verifier(client_verifier)
151        .with_single_cert(certs, key)
152        .map_err(|e| TunnelError::Tls(format!("Failed to build server config: {}", e)))?;
153
154    Ok(config)
155}
156
157/// Load server TLS config from PEM content WITHOUT client certificate verification
158///
159/// Use this for HTTPS endpoints that don't need mTLS (e.g., HTTP data plane for Cloudflare)
160///
161/// # Arguments
162/// * `cert_pem` - Server certificate PEM content
163/// * `key_pem` - Server private key PEM content
164pub fn load_server_config_no_client_auth(
165    cert_pem: &str,
166    key_pem: &str,
167) -> Result<ServerConfig, TunnelError> {
168    let certs = load_certs_from_pem(cert_pem)?;
169    let key = load_private_key_from_pem(key_pem)?;
170
171    let config = ServerConfig::builder()
172        .with_no_client_auth()
173        .with_single_cert(certs, key)
174        .map_err(|e| TunnelError::Tls(format!("Failed to build server config: {}", e)))?;
175
176    Ok(config)
177}
178
179/// Load client TLS config from PEM content strings with mTLS
180///
181/// # Arguments
182/// * `cert_pem` - Client certificate PEM content
183/// * `key_pem` - Client private key PEM content
184/// * `ca_pem` - CA certificate PEM content for verifying server certificate
185pub fn load_client_config_from_pem(
186    cert_pem: &str,
187    key_pem: &str,
188    ca_pem: &str,
189) -> Result<ClientConfig, TunnelError> {
190    let certs = load_certs_from_pem(cert_pem)?;
191    let key = load_private_key_from_pem(key_pem)?;
192    let root_store = load_root_store_from_pem(ca_pem)?;
193
194    let config = ClientConfig::builder()
195        .with_root_certificates(root_store)
196        .with_client_auth_cert(certs, key)
197        .map_err(|e| TunnelError::Tls(format!("Failed to build client config: {}", e)))?;
198
199    Ok(config)
200}
201
202/// Extract the Common Name (CN) from a certificate
203#[allow(dead_code)]
204pub fn extract_cn(cert: &rustls::pki_types::CertificateDer<'_>) -> Option<String> {
205    // Parse the certificate using x509-parser would be ideal here,
206    // but for simplicity we'll just note this is where CN extraction would go
207    // In a real implementation, add x509-parser to dependencies
208
209    // For now, return a placeholder - this should be implemented properly
210    // when we add certificate parsing
211    let _ = cert;
212    None
213}