slinger_mitm/
ca.rs

1//! Certificate Authority (CA) management for MITM proxy
2//!
3//! This module handles automatic generation and management of CA certificates
4//! for intercepting HTTPS traffic.
5//!
6
7use crate::error::{Error, Result};
8use moka::future::Cache;
9use rand::Rng;
10use rcgen::{
11  BasicConstraints, CertificateParams, DistinguishedName, DnType, IsCa, Issuer, KeyPair,
12  KeyUsagePurpose, SanType,
13};
14use std::net::IpAddr;
15use std::path::{Path, PathBuf};
16use std::sync::Arc;
17use time::{Duration, OffsetDateTime};
18use tokio::fs;
19use tokio::io::AsyncWriteExt;
20use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer};
21
22/// Certificate validity period in seconds (1 year)
23const TTL_SECS: i64 = 365 * 24 * 60 * 60;
24/// Cache time-to-live in seconds (6 months)
25const CACHE_TTL: u64 = (TTL_SECS / 2) as u64;
26/// Offset for not_before timestamp to handle clock skew (60 seconds)
27const NOT_BEFORE_OFFSET: i64 = 60;
28
29/// Certificate Authority for generating certificates
30pub struct CertificateAuthority {
31  /// Root CA issuer
32  issuer: Issuer<'static, KeyPair>,
33  /// Root CA certificate in DER format
34  ca_cert_der: CertificateDer<'static>,
35  /// Root CA private key
36  #[allow(dead_code)]
37  ca_key_der: PrivateKeyDer<'static>,
38  /// Storage path for certificates
39  storage_path: PathBuf,
40}
41
42impl CertificateAuthority {
43  /// Create a new Certificate Authority
44  ///
45  /// If a CA already exists at the storage path, it will be loaded.
46  /// Otherwise, a new CA will be generated.
47  pub async fn new(storage_path: impl AsRef<Path>) -> Result<Self> {
48    let storage_path = storage_path.as_ref().to_path_buf();
49
50    // Create storage directory if it doesn't exist
51    if !storage_path.exists() {
52      fs::create_dir_all(&storage_path).await?;
53    }
54
55    let ca_cert_path = storage_path.join("ca_cert.pem");
56    let ca_key_path = storage_path.join("ca_key.pem");
57
58    // Check if CA already exists
59    let (issuer, ca_cert_der, ca_key_der) = if ca_cert_path.exists() && ca_key_path.exists() {
60      Self::load_ca(&ca_cert_path, &ca_key_path).await?
61    } else {
62      Self::generate_ca(&ca_cert_path, &ca_key_path).await?
63    };
64
65    Ok(Self {
66      issuer,
67      ca_cert_der,
68      ca_key_der,
69      storage_path,
70    })
71  }
72
73  /// Load existing CA certificate and key
74  async fn load_ca(
75    cert_path: &Path,
76    key_path: &Path,
77  ) -> Result<(
78    Issuer<'static, KeyPair>,
79    CertificateDer<'static>,
80    PrivateKeyDer<'static>,
81  )> {
82    let cert_pem = fs::read_to_string(cert_path).await?;
83    let key_pem = fs::read_to_string(key_path).await?;
84
85    let key_pair = KeyPair::from_pem(&key_pem)
86      .map_err(|e| Error::certificate_error(format!("Failed to parse CA key: {}", e)))?;
87
88    let issuer = Issuer::from_ca_cert_pem(&cert_pem, key_pair).map_err(|e| {
89      Error::certificate_error(format!("Failed to create issuer from CA cert: {}", e))
90    })?;
91
92    // Parse PEM to DER for rustls
93    let cert_der = rustls_pemfile::certs(&mut cert_pem.as_bytes())
94      .next()
95      .ok_or_else(|| Error::certificate_error("No certificate found in PEM"))?
96      .map_err(|e| Error::certificate_error(format!("Failed to parse PEM: {}", e)))?;
97
98    let key_der = PrivateKeyDer::try_from(issuer.key().serialize_der())
99      .map_err(|_| Error::certificate_error("Failed to serialize CA key"))?;
100
101    Ok((issuer, cert_der, key_der))
102  }
103
104  /// Generate a new CA certificate and key
105  async fn generate_ca(
106    cert_path: &Path,
107    key_path: &Path,
108  ) -> Result<(
109    Issuer<'static, KeyPair>,
110    CertificateDer<'static>,
111    PrivateKeyDer<'static>,
112  )> {
113    let mut params = CertificateParams::default();
114
115    // Set up distinguished name
116    let mut dn = DistinguishedName::new();
117    dn.push(DnType::CommonName, "Slinger MITM Proxy CA");
118    dn.push(DnType::OrganizationName, "Emo-Crab");
119    dn.push(DnType::CountryName, "CN");
120    dn.push(DnType::LocalityName, "Internet");
121    dn.push(DnType::StateOrProvinceName, "World");
122    params.distinguished_name = dn;
123    // Configure as CA
124    params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
125    params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign];
126
127    // Set validity period (10 years)
128    let now = OffsetDateTime::now_utc();
129    params.not_before = now;
130    params.not_after = now + Duration::days(3650);
131
132    let key_pair = KeyPair::generate()
133      .map_err(|e| Error::certificate_error(format!("Failed to generate key pair: {}", e)))?;
134
135    let cert = params
136      .self_signed(&key_pair)
137      .map_err(|e| Error::certificate_error(format!("Failed to generate CA: {}", e)))?;
138
139    // Serialize and save
140    let cert_pem = cert.pem();
141    let key_pem = key_pair.serialize_pem();
142
143    let mut cert_file = fs::File::create(cert_path).await?;
144    cert_file.write_all(cert_pem.as_bytes()).await?;
145
146    let mut key_file = fs::File::create(key_path).await?;
147    key_file.write_all(key_pem.as_bytes()).await?;
148
149    let cert_der = CertificateDer::from(cert.der().to_vec());
150    let key_der = PrivateKeyDer::try_from(key_pair.serialize_der())
151      .map_err(|_| Error::certificate_error("Failed to serialize CA key DER"))?;
152
153    // Create issuer from the certificate and key pair
154    let issuer = Issuer::from_ca_cert_pem(&cert_pem, key_pair)
155      .map_err(|e| Error::certificate_error(format!("Failed to create issuer: {}", e)))?;
156
157    Ok((issuer, cert_der, key_der))
158  }
159
160  /// Generate a server certificate signed by this CA
161  pub fn generate_server_cert(
162    &self,
163    domain: &str,
164  ) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
165    let mut params = CertificateParams::default();
166
167    // Generate random serial number for uniqueness
168    params.serial_number = Some(rand::rng().random::<u64>().into());
169
170    // Set up distinguished name
171    let mut dn = DistinguishedName::new();
172    dn.push(DnType::CommonName, domain);
173    params.distinguished_name = dn;
174
175    // Add subject alternative names
176    // If domain parses as an IP literal, include both an IP SAN and a DNS SAN.
177    // Some clients strictly check iPAddress in SAN for IP targets while others
178    // may check dNSName; including both increases compatibility for local IPs.
179    params.subject_alt_names = if let Ok(ip) = domain.parse::<IpAddr>() {
180      let mut sans = Vec::new();
181      sans.push(SanType::IpAddress(ip));
182      // Also add a DNS SAN with the textual IP as fallback (if rcgen accepts it).
183      if let Ok(dns_name) = domain.try_into() {
184        sans.push(SanType::DnsName(dns_name));
185      }
186      sans
187    } else {
188      vec![SanType::DnsName(domain.try_into().map_err(|_| {
189        Error::certificate_error(format!("Invalid domain name: {}", domain))
190      })?)]
191    };
192
193    // Set validity period with clock skew handling
194    let now = OffsetDateTime::now_utc();
195    params.not_before = now - Duration::seconds(NOT_BEFORE_OFFSET);
196    params.not_after = now + Duration::seconds(TTL_SECS);
197
198    let key_pair = KeyPair::generate()
199      .map_err(|e| Error::certificate_error(format!("Failed to generate key pair: {}", e)))?;
200
201    let cert = params
202      .signed_by(&key_pair, &self.issuer)
203      .map_err(|e| Error::certificate_error(format!("Failed to sign server cert: {}", e)))?;
204
205    let cert_der = CertificateDer::from(cert.der().to_vec());
206
207    let key_der = PrivateKeyDer::try_from(key_pair.serialize_der())
208      .map_err(|_| Error::certificate_error("Failed to serialize server key"))?;
209
210    // Return chain: [server_cert, ca_cert]
211    Ok((vec![cert_der, self.ca_cert_der.clone()], key_der))
212  }
213
214  /// Get CA certificate in PEM format for client installation
215  pub fn ca_cert_pem(&self) -> Result<String> {
216    // Read the saved certificate file
217    let ca_cert_path = self.storage_path.join("ca_cert.pem");
218    std::fs::read_to_string(&ca_cert_path)
219      .map_err(|e| Error::certificate_error(format!("Failed to read CA cert: {}", e)))
220  }
221
222  /// Get CA certificate path
223  pub fn ca_cert_path(&self) -> PathBuf {
224    self.storage_path.join("ca_cert.pem")
225  }
226}
227
228/// Manager for caching generated server certificates
229pub struct CertificateManager {
230  ca: CertificateAuthority,
231  /// Cache for generated server certificates
232  cert_cache: Cache<String, Arc<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)>>,
233}
234
235impl CertificateManager {
236  /// Create a new certificate manager
237  pub async fn new(storage_path: impl AsRef<Path>) -> Result<Self> {
238    let ca = CertificateAuthority::new(storage_path).await?;
239
240    // Create cache with TTL matching certificate validity
241    let cert_cache = Cache::builder()
242      .max_capacity(1000)
243      .time_to_live(std::time::Duration::from_secs(CACHE_TTL))
244      .build();
245
246    Ok(Self { ca, cert_cache })
247  }
248
249  /// Get or generate a server certificate for the given domain
250  pub async fn get_server_cert(
251    &self,
252    domain: &str,
253  ) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
254    // If domain is an IP literal, avoid returning a potentially stale cached
255    // certificate that might lack an iPAddress SAN; always generate a fresh
256    // certificate containing the IP SAN. For hostnames, use the cache for
257    // performance.
258    if domain.parse::<std::net::IpAddr>().is_ok() {
259      let (cert_chain, key) = self.ca.generate_server_cert(domain)?;
260      // Cache the generated cert for future use
261      let cached_cert = (cert_chain.clone(), key.clone_key());
262      self
263        .cert_cache
264        .insert(domain.to_string(), Arc::new(cached_cert))
265        .await;
266      return Ok((cert_chain, key));
267    }
268
269    // Try to get from cache for non-IP hostnames
270    if let Some(cached) = self.cert_cache.get(domain).await {
271      // Clone the certificate chain and key from cache
272      let (cert_chain, key) = cached.as_ref();
273      return Ok((cert_chain.clone(), key.clone_key()));
274    }
275
276    // Generate new certificate
277    let (cert_chain, key) = self.ca.generate_server_cert(domain)?;
278
279    // Clone before caching since we need to return the original
280    let cached_cert = (cert_chain.clone(), key.clone_key());
281
282    // Store in cache
283    self
284      .cert_cache
285      .insert(domain.to_string(), Arc::new(cached_cert))
286      .await;
287
288    Ok((cert_chain, key))
289  }
290
291  /// Get the CA certificate in PEM format
292  pub fn ca_cert_pem(&self) -> Result<String> {
293    self.ca.ca_cert_pem()
294  }
295
296  /// Get the CA certificate path
297  pub fn ca_cert_path(&self) -> PathBuf {
298    self.ca.ca_cert_path()
299  }
300}