Skip to main content

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