use super::error::{MitmError, Result};
use super::cert_cache::CertCache;
use rcgen::{Certificate, CertificateParams, DistinguishedName, DnType, KeyPair};
use std::fs;
use std::path::Path;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{info, warn, error};
pub struct CertificateAuthority {
ca_cert: Certificate,
ca_key: KeyPair,
cert_cache: Arc<RwLock<CertCache>>,
config: CaConfig,
}
#[derive(Debug, Clone)]
pub struct CaConfig {
pub cert_path: String,
pub key_path: String,
pub cache_size: usize,
pub validity_days: u32,
pub organization: String,
pub country: String,
}
impl Default for CaConfig {
fn default() -> Self {
Self {
cert_path: "ca.crt".to_string(),
key_path: "ca.key".to_string(),
cache_size: 10_000,
validity_days: 365,
organization: "Pinaka Derusted Proxy".to_string(),
country: "US".to_string(),
}
}
}
impl CertificateAuthority {
pub async fn new(config: CaConfig) -> Result<Self> {
info!("Initializing Certificate Authority");
let (ca_cert, ca_key) = if Path::new(&config.cert_path).exists()
&& Path::new(&config.key_path).exists()
{
info!("Loading existing CA from disk");
Self::load_ca(&config).await?
} else {
info!("Generating new CA certificate");
let (cert, key) = Self::generate_ca(&config).await?;
Self::save_ca(&config, &cert, &key).await?;
(cert, key)
};
let cert_cache = Arc::new(RwLock::new(CertCache::new(config.cache_size)));
info!("Certificate Authority initialized successfully");
info!("Cache size: {} certificates", config.cache_size);
info!("Certificate validity: {} days", config.validity_days);
Ok(Self {
ca_cert,
ca_key,
cert_cache,
config,
})
}
async fn generate_ca(config: &CaConfig) -> Result<(Certificate, KeyPair)> {
info!("Generating CA certificate and private key");
let mut params = CertificateParams::default();
params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
let mut distinguished_name = DistinguishedName::new();
distinguished_name.push(DnType::OrganizationName, &config.organization);
distinguished_name.push(DnType::CommonName, "Pinaka Derusted Proxy CA");
distinguished_name.push(DnType::CountryName, &config.country);
params.distinguished_name = distinguished_name;
params.not_before = rcgen::date_time_ymd(2024, 1, 1);
params.not_after = rcgen::date_time_ymd(2034, 1, 1);
params.key_usages = vec![
rcgen::KeyUsagePurpose::KeyCertSign,
rcgen::KeyUsagePurpose::CrlSign,
rcgen::KeyUsagePurpose::DigitalSignature,
];
let key_pair = KeyPair::generate()
.map_err(|e| MitmError::ca_generation(format!("Failed to generate key pair: {}", e)))?;
let cert = params
.self_signed(&key_pair)
.map_err(|e| MitmError::ca_generation(format!("Failed to self-sign certificate: {}", e)))?;
info!("CA certificate generated successfully");
Ok((cert, key_pair))
}
async fn load_ca(config: &CaConfig) -> Result<(Certificate, KeyPair)> {
info!("Loading CA certificate from: {}", config.cert_path);
info!("Loading CA private key from: {}", config.key_path);
let cert_pem = fs::read_to_string(&config.cert_path)
.map_err(|e| MitmError::ca_load(format!("Failed to read cert file: {}", e)))?;
let key_pem = fs::read_to_string(&config.key_path)
.map_err(|e| MitmError::ca_load(format!("Failed to read key file: {}", e)))?;
let key_pair_for_cert = KeyPair::from_pem(&key_pem)
.map_err(|e| MitmError::ca_load(format!("Failed to parse private key: {}", e)))?;
let key_pair = KeyPair::from_pem(&key_pem)
.map_err(|e| MitmError::ca_load(format!("Failed to parse private key: {}", e)))?;
let params = CertificateParams::from_ca_cert_pem(&cert_pem, key_pair_for_cert)
.map_err(|e| MitmError::ca_load(format!("Failed to parse certificate: {}", e)))?;
let cert = Certificate::from_params(params)
.map_err(|e| MitmError::ca_load(format!("Failed to reconstruct certificate: {}", e)))?;
info!("CA certificate and key loaded successfully");
Ok((cert, key_pair))
}
async fn save_ca(config: &CaConfig, cert: &Certificate, key: &KeyPair) -> Result<()> {
info!("Saving CA certificate to: {}", config.cert_path);
info!("Saving CA private key to: {}", config.key_path);
fs::write(&config.cert_path, cert.pem())
.map_err(|e| MitmError::ca_generation(format!("Failed to save certificate: {}", e)))?;
fs::write(&config.key_path, key.serialize_pem())
.map_err(|e| MitmError::ca_generation(format!("Failed to save private key: {}", e)))?;
info!("CA certificate and key saved successfully");
Ok(())
}
pub async fn generate_cert_for_domain(&self, domain: &str) -> Result<Certificate> {
{
let cache = self.cert_cache.read().await;
if let Some(cert) = cache.get(domain) {
info!("📦 Certificate cache hit for: {}", domain);
return Ok(cert.clone());
}
}
info!("🔧 Generating certificate for: {}", domain);
let mut params = CertificateParams::default();
params.subject_alt_names = vec![
rcgen::SanType::DnsName(domain.to_string()),
rcgen::SanType::DnsName(format!("*.{}", domain)),
];
let mut distinguished_name = DistinguishedName::new();
distinguished_name.push(DnType::OrganizationName, &self.config.organization);
distinguished_name.push(DnType::CommonName, domain);
distinguished_name.push(DnType::CountryName, &self.config.country);
params.distinguished_name = distinguished_name;
let now = chrono::Utc::now();
let not_before = now - chrono::Duration::days(1); let not_after = now + chrono::Duration::days(self.config.validity_days as i64);
params.not_before = rcgen::date_time_ymd(
not_before.year(),
not_before.month() as u8,
not_before.day() as u8,
);
params.not_after = rcgen::date_time_ymd(
not_after.year(),
not_after.month() as u8,
not_after.day() as u8,
);
params.key_usages = vec![
rcgen::KeyUsagePurpose::DigitalSignature,
rcgen::KeyUsagePurpose::KeyEncipherment,
];
let cert = params
.signed_by(&self.ca_key, &self.ca_cert, &self.ca_key)
.map_err(|e| {
MitmError::cert_generation(
domain,
format!("Failed to sign certificate: {}", e),
)
})?;
{
let mut cache = self.cert_cache.write().await;
cache.insert(domain.to_string(), cert.clone());
}
info!("✅ Certificate generated and cached for: {}", domain);
Ok(cert)
}
pub async fn get_or_generate(&self, domain: &str) -> Result<Certificate> {
self.generate_cert_for_domain(domain).await
}
pub fn get_ca_cert_pem(&self) -> String {
self.ca_cert.pem()
}
pub fn get_ca_key_pem(&self) -> String {
self.ca_key.serialize_pem()
}
pub async fn get_cache_stats(&self) -> CacheStats {
let cache = self.cert_cache.read().await;
CacheStats {
size: cache.len(),
capacity: cache.capacity(),
hit_rate: cache.hit_rate(),
}
}
pub async fn reload(&mut self) -> Result<()> {
info!("Hot-reloading CA certificate");
let (ca_cert, ca_key) = Self::load_ca(&self.config).await?;
self.ca_cert = ca_cert;
self.ca_key = ca_key;
{
let mut cache = self.cert_cache.write().await;
cache.clear();
}
info!("CA certificate reloaded successfully");
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct CacheStats {
pub size: usize,
pub capacity: usize,
pub hit_rate: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_ca_generation() {
let config = CaConfig {
cert_path: "/tmp/test_ca.crt".to_string(),
key_path: "/tmp/test_ca.key".to_string(),
..Default::default()
};
let ca = CertificateAuthority::new(config).await.unwrap();
assert!(!ca.get_ca_cert_pem().is_empty());
assert!(!ca.get_ca_key_pem().is_empty());
}
#[tokio::test]
async fn test_domain_cert_generation() {
let config = CaConfig {
cert_path: "/tmp/test_ca2.crt".to_string(),
key_path: "/tmp/test_ca2.key".to_string(),
cache_size: 100,
..Default::default()
};
let ca = CertificateAuthority::new(config).await.unwrap();
let cert = ca.generate_cert_for_domain("example.com").await.unwrap();
assert!(!cert.pem().is_empty());
let cert2 = ca.generate_cert_for_domain("example.com").await.unwrap();
assert_eq!(cert.pem(), cert2.pem());
let stats = ca.get_cache_stats().await;
assert_eq!(stats.size, 1);
}
#[tokio::test]
async fn test_ca_reload() {
let config = CaConfig {
cert_path: "/tmp/test_ca3.crt".to_string(),
key_path: "/tmp/test_ca3.key".to_string(),
..Default::default()
};
let mut ca = CertificateAuthority::new(config).await.unwrap();
let original_pem = ca.get_ca_cert_pem();
ca.reload().await.unwrap();
assert_eq!(original_pem, ca.get_ca_cert_pem());
}
}