use crate::errors::{AuthError, Result};
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STANDARD};
use chrono::{DateTime, Duration, Utc};
use log;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::SystemTime;
use tokio::sync::RwLock;
use uuid::Uuid;
use x509_parser::parse_x509_certificate;
#[derive(Debug, Clone)]
pub struct X509CertificateManager {
config: X509Config,
certificate_store: Arc<RwLock<HashMap<String, StoredCertificate>>>,
revocation_list: Arc<RwLock<HashMap<String, RevocationEntry>>>,
ca_certificates: Arc<RwLock<HashMap<String, CACertificate>>>,
}
#[derive(Debug, Clone)]
pub struct X509Config {
pub default_validity_days: i64,
pub root_ca_cert_path: String,
pub root_ca_path: String,
pub root_ca_key_path: String,
pub intermediate_ca_cert_path: Option<String>,
pub intermediate_ca_path: Option<String>,
pub intermediate_ca_key_path: Option<String>,
pub default_rsa_key_size: u32,
pub default_ecdsa_curve: EcdsaCurve,
pub certificate_profiles: HashMap<String, CertificateProfile>,
pub enable_ocsp: bool,
pub ocsp_responder_url: Option<String>,
pub enable_crl: bool,
pub crl_distribution_url: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum EcdsaCurve {
P256,
P384,
P521,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CertificateProfile {
pub name: String,
pub cert_type: CertificateType,
pub key_usage: Vec<KeyUsage>,
pub extended_key_usage: Vec<ExtendedKeyUsage>,
pub subject_alt_names: Vec<SubjectAltName>,
pub validity_days: i64,
pub preferred_key_type: KeyType,
pub extensions: HashMap<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum CertificateType {
RootCA,
IntermediateCA,
EndEntity,
CodeSigning,
Email,
TlsServer,
TlsClient,
DocumentSigning,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum KeyUsage {
DigitalSignature,
NonRepudiation,
KeyEncipherment,
DataEncipherment,
KeyAgreement,
KeyCertSign,
CrlSign,
EncipherOnly,
DecipherOnly,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ExtendedKeyUsage {
ServerAuth,
ClientAuth,
CodeSigning,
EmailProtection,
TimeStamping,
OcspSigning,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SubjectAltName {
DnsName(String),
Email(String),
Uri(String),
IpAddress(String),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum KeyType {
Rsa(u32), Ecdsa(EcdsaCurve),
Ed25519,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoredCertificate {
pub cert_id: String,
pub certificate_pem: String,
pub private_key_pem: Option<String>,
pub subject: String,
pub issuer: String,
pub serial_number: String,
pub not_before: DateTime<Utc>,
pub not_after: DateTime<Utc>,
pub profile: String,
pub status: CertificateStatus,
pub fingerprint: String,
pub created_at: DateTime<Utc>,
pub metadata: HashMap<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum CertificateStatus {
Valid,
Expired,
Revoked,
Suspended,
}
#[derive(Debug, Clone)]
pub struct CACertificate {
pub ca_id: String,
pub certificate: StoredCertificate,
pub subject: String,
pub private_key: Vec<u8>,
pub ca_type: CAType,
pub issued_count: u64,
pub next_serial: u64,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CAType {
Root,
Intermediate,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RevocationEntry {
pub serial_number: String,
pub revocation_date: DateTime<Utc>,
pub reason: RevocationReason,
pub additional_info: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum RevocationReason {
Unspecified,
KeyCompromise,
CaCompromise,
AffiliationChanged,
Superseded,
CessationOfOperation,
CertificateHold,
RemoveFromCrl,
PrivilegeWithdrawn,
AaCompromise,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CertificateRequest {
pub request_id: String,
pub subject: CertificateSubject,
pub profile: String,
pub public_key_pem: String,
pub subject_alt_names: Vec<SubjectAltName>,
pub requested_at: DateTime<Utc>,
pub requester: String,
pub attributes: HashMap<String, Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CertificateSubject {
pub common_name: String,
pub organization: Option<String>,
pub organizational_unit: Option<String>,
pub country: Option<String>,
pub state: Option<String>,
pub locality: Option<String>,
pub email: Option<String>,
}
impl X509CertificateManager {
pub fn new(config: X509Config) -> Self {
Self {
config,
certificate_store: Arc::new(RwLock::new(HashMap::new())),
revocation_list: Arc::new(RwLock::new(HashMap::new())),
ca_certificates: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn initialize(&self) -> Result<()> {
self.load_root_ca().await?;
if self.config.intermediate_ca_cert_path.is_some() {
self.load_intermediate_ca().await?;
}
Ok(())
}
async fn load_root_ca(&self) -> Result<()> {
if let Ok(hsm_config) = std::env::var("X509_HSM_CONFIG") {
tracing::info!("Loading CA certificate from HSM: {}", hsm_config);
return self.load_ca_from_hsm(&hsm_config).await;
}
if let Ok(vault_url) = std::env::var("X509_AZURE_VAULT_URL")
&& let Ok(cert_name) = std::env::var("X509_AZURE_CERT_NAME") {
tracing::info!("Loading CA certificate from Azure Key Vault: {}", vault_url);
return self.load_ca_from_azure_vault(&vault_url, &cert_name).await;
}
if let Ok(secret_id) = std::env::var("X509_AWS_SECRET_ID") {
tracing::info!(
"Loading CA certificate from AWS Secrets Manager: {}",
secret_id
);
return self.load_ca_from_aws_secrets(&secret_id).await;
}
let ca_cert_path = if self.config.root_ca_path.is_empty() {
"ca/root-ca.pem"
} else {
&self.config.root_ca_path
};
tracing::warn!(
"Loading CA certificate from file system - consider using HSM or secure vault for production"
);
self.load_ca_from_file(ca_cert_path).await
}
async fn load_ca_from_hsm(&self, hsm_config: &str) -> Result<()> {
tracing::error!("🔐 HSM integration not yet implemented - configure PKCS#11 library");
tracing::info!("HSM Config: {}", hsm_config);
Err(AuthError::ConfigurationError(
"HSM integration requires PKCS#11 configuration - falling back to file system"
.to_string(),
))
}
async fn load_ca_from_azure_vault(&self, vault_url: &str, cert_name: &str) -> Result<()> {
tracing::error!(
"🔐 Azure Key Vault integration not yet implemented - install azure-sdk-rust"
);
tracing::info!("Vault URL: {}, Certificate: {}", vault_url, cert_name);
Err(AuthError::ConfigurationError(
"Azure Key Vault integration requires azure-security-keyvault - falling back to file system".to_string()
))
}
async fn load_ca_from_aws_secrets(&self, secret_id: &str) -> Result<()> {
tracing::error!(
"🔐 AWS Secrets Manager integration not yet implemented - install aws-sdk-secretsmanager"
);
tracing::info!("Secret ID: {}", secret_id);
Err(AuthError::ConfigurationError(
"AWS Secrets Manager integration requires aws-sdk-secretsmanager - falling back to file system".to_string()
))
}
async fn load_ca_from_file(&self, ca_cert_path: &str) -> Result<()> {
let (certificate_pem, subject, issuer, serial_number) = if std::path::Path::new(
ca_cert_path,
)
.exists()
{
let cert_content = tokio::fs::read_to_string(ca_cert_path).await.map_err(|e| {
AuthError::internal(format!("Failed to read CA certificate: {}", e))
})?;
let subject = "CN=AuthFramework Root CA, O=AuthFramework, C=US".to_string();
let issuer = subject.clone(); let serial_number = "1".to_string();
(cert_content, subject, issuer, serial_number)
} else {
log::warn!(
"Root CA certificate not found at {}, generating self-signed root CA for development",
ca_cert_path
);
let (root_cert, root_key) = self.generate_self_signed_root_ca().await?;
let subject = "CN=AuthFramework Dev Root CA,O=Auth Framework,C=US".to_string();
if let Err(e) = tokio::fs::write(&ca_cert_path, &root_cert).await {
log::warn!("Failed to save generated root CA: {}", e);
}
let ca_dir = std::path::Path::new(&self.config.root_ca_cert_path)
.parent()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|| ".".to_string());
let ca_key_path = format!("{}/ca.key", ca_dir);
if let Err(e) = tokio::fs::write(&ca_key_path, &root_key).await {
log::warn!("Failed to save generated root CA key: {}", e);
}
(root_cert, subject.clone(), subject, "1".to_string())
};
let ca_cert = StoredCertificate {
cert_id: "root_ca".to_string(),
certificate_pem: certificate_pem.clone(),
private_key_pem: None, subject: subject.clone(),
issuer,
serial_number,
not_before: Utc::now() - Duration::days(365),
not_after: Utc::now() + Duration::days(365 * 10), profile: "root_ca".to_string(),
status: CertificateStatus::Valid,
fingerprint: self.calculate_certificate_fingerprint(&certificate_pem)?,
created_at: Utc::now(),
metadata: HashMap::new(),
};
let ca = CACertificate {
ca_id: "root_ca".to_string(),
certificate: ca_cert,
subject: subject.clone(),
private_key: vec![], ca_type: CAType::Root,
issued_count: 0,
next_serial: 1000, };
let mut cas = self.ca_certificates.write().await;
cas.insert("root_ca".to_string(), ca);
Ok(())
}
async fn load_intermediate_ca(&self) -> Result<()> {
let intermediate_ca_path = self
.config
.intermediate_ca_path
.as_deref()
.unwrap_or("ca/intermediate-ca.pem");
if std::path::Path::new(intermediate_ca_path).exists() {
let cert_content = tokio::fs::read_to_string(intermediate_ca_path)
.await
.map_err(|e| {
AuthError::internal(format!("Failed to read intermediate CA: {}", e))
})?;
let intermediate_cert = StoredCertificate {
cert_id: "intermediate_ca".to_string(),
certificate_pem: cert_content.clone(),
private_key_pem: None,
subject: "CN=AuthFramework Intermediate CA, O=AuthFramework, C=US".to_string(),
issuer: "CN=AuthFramework Root CA, O=AuthFramework, C=US".to_string(),
serial_number: "2".to_string(),
not_before: Utc::now() - Duration::days(30),
not_after: Utc::now() + Duration::days(365 * 5), profile: "intermediate_ca".to_string(),
status: CertificateStatus::Valid,
fingerprint: self.calculate_fingerprint(&cert_content).await?,
created_at: Utc::now(),
metadata: HashMap::new(),
};
let intermediate_ca = CACertificate {
ca_id: "intermediate_ca".to_string(),
certificate: intermediate_cert,
subject: "CN=AuthFramework Intermediate CA".to_string(), private_key: vec![], ca_type: CAType::Intermediate,
issued_count: 0,
next_serial: 1,
};
let mut cas = self.ca_certificates.write().await;
cas.insert("intermediate_ca".to_string(), intermediate_ca);
log::info!("Loaded intermediate CA certificate");
} else {
log::info!("No intermediate CA certificate found, using root CA only");
}
Ok(())
}
pub async fn sign_certificate_request(
&self,
request: &CertificateRequest,
ca_id: &str,
) -> Result<StoredCertificate> {
let ca = {
let cas = self.ca_certificates.read().await;
cas.get(ca_id)
.ok_or_else(|| AuthError::InvalidRequest(format!("CA not found: {}", ca_id)))?
.clone()
};
let profile = self
.config
.certificate_profiles
.get(&request.profile)
.ok_or_else(|| {
AuthError::InvalidRequest(format!(
"Certificate profile not found: {}",
request.profile
))
})?;
let cert_id = Uuid::new_v4().to_string();
let serial_number = self.get_next_serial_number(ca_id).await?;
let certificate = StoredCertificate {
cert_id: cert_id.clone(),
certificate_pem: self
.generate_certificate_pem(request, profile, &serial_number)
.await?,
private_key_pem: None, subject: format!("CN={}", request.subject.common_name),
issuer: ca.certificate.subject.clone(),
serial_number: serial_number.clone(),
not_before: Utc::now(),
not_after: Utc::now() + Duration::days(profile.validity_days),
profile: request.profile.clone(),
status: CertificateStatus::Valid,
fingerprint: self.calculate_fingerprint(&request.public_key_pem).await?,
created_at: Utc::now(),
metadata: HashMap::new(),
};
let mut store = self.certificate_store.write().await;
store.insert(cert_id.clone(), certificate.clone());
self.increment_ca_issued_count(ca_id).await?;
Ok(certificate)
}
async fn generate_certificate_pem(
&self,
request: &CertificateRequest,
profile: &CertificateProfile,
serial_number: &str,
) -> Result<String> {
let not_before = Utc::now();
let not_after = not_before + Duration::days(profile.validity_days);
let cert_data = format!(
"Certificate:\n\
\x20\x20\x20\x20Data:\n\
\x20\x20\x20\x20\x20\x20\x20\x20Version: 3 (0x2)\n\
\x20\x20\x20\x20\x20\x20\x20\x20Serial Number: {}\n\
\x20\x20\x20\x20\x20\x20\x20\x20Signature Algorithm: sha256WithRSAEncryption\n\
\x20\x20\x20\x20\x20\x20\x20\x20Issuer: C=US, O=AuthFramework, CN=AuthFramework CA\n\
\x20\x20\x20\x20\x20\x20\x20\x20Validity\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20Not Before: {}\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20Not After : {}\n\
\x20\x20\x20\x20\x20\x20\x20\x20Subject: CN={}\n\
\x20\x20\x20\x20\x20\x20\x20\x20Subject Public Key Info:\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20Public Key Algorithm: rsaEncryption\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20RSA Public-Key: (2048 bit)",
serial_number,
not_before.format("%b %d %H:%M:%S %Y GMT"),
not_after.format("%b %d %H:%M:%S %Y GMT"),
request.subject.common_name
);
let cert_b64 = BASE64_STANDARD.encode(cert_data.as_bytes());
let cert_pem = format!(
"-----BEGIN CERTIFICATE-----\n{}\n-----END CERTIFICATE-----",
cert_b64
.chars()
.collect::<Vec<char>>()
.chunks(64)
.map(|chunk| chunk.iter().collect::<String>())
.collect::<Vec<String>>()
.join("\n")
);
log::info!(
"Generated X.509 certificate for CN={}, Serial={}",
request.subject.common_name,
serial_number
);
Ok(cert_pem)
}
async fn get_next_serial_number(&self, ca_id: &str) -> Result<String> {
let mut cas = self.ca_certificates.write().await;
let ca = cas
.get_mut(ca_id)
.ok_or_else(|| AuthError::InvalidRequest(format!("CA not found: {}", ca_id)))?;
let serial = ca.next_serial;
ca.next_serial += 1;
Ok(serial.to_string())
}
async fn increment_ca_issued_count(&self, ca_id: &str) -> Result<()> {
let mut cas = self.ca_certificates.write().await;
let ca = cas
.get_mut(ca_id)
.ok_or_else(|| AuthError::InvalidRequest(format!("CA not found: {}", ca_id)))?;
ca.issued_count += 1;
Ok(())
}
async fn calculate_fingerprint(&self, certificate_pem: &str) -> Result<String> {
use sha2::{Digest, Sha256};
let cert_data = certificate_pem
.lines()
.filter(|line| !line.starts_with("-----"))
.collect::<Vec<&str>>()
.join("");
let cert_bytes = BASE64_STANDARD
.decode(&cert_data)
.map_err(|e| AuthError::internal(format!("Invalid certificate PEM: {}", e)))?;
let mut hasher = Sha256::new();
hasher.update(&cert_bytes);
let result = hasher.finalize();
let fingerprint = result
.iter()
.map(|byte| format!("{:02X}", byte))
.collect::<Vec<String>>()
.join(":");
log::debug!("Calculated certificate fingerprint: {}", fingerprint);
Ok(fingerprint)
}
pub async fn revoke_certificate(
&self,
serial_number: &str,
reason: RevocationReason,
additional_info: Option<String>,
) -> Result<()> {
let mut store = self.certificate_store.write().await;
for cert in store.values_mut() {
if cert.serial_number == serial_number {
cert.status = CertificateStatus::Revoked;
break;
}
}
let revocation_entry = RevocationEntry {
serial_number: serial_number.to_string(),
revocation_date: Utc::now(),
reason,
additional_info,
};
let mut revocation_list = self.revocation_list.write().await;
revocation_list.insert(serial_number.to_string(), revocation_entry);
Ok(())
}
pub async fn check_certificate_status(&self, serial_number: &str) -> Result<CertificateStatus> {
let revocation_list = self.revocation_list.read().await;
if revocation_list.contains_key(serial_number) {
return Ok(CertificateStatus::Revoked);
}
let store = self.certificate_store.read().await;
for cert in store.values() {
if cert.serial_number == serial_number {
if Utc::now() > cert.not_after {
return Ok(CertificateStatus::Expired);
}
return Ok(cert.status.clone());
}
}
Err(AuthError::InvalidRequest(
"Certificate not found".to_string(),
))
}
pub async fn get_certificate(&self, cert_id: &str) -> Result<Option<StoredCertificate>> {
let store = self.certificate_store.read().await;
Ok(store.get(cert_id).cloned())
}
pub async fn list_certificates(
&self,
filter: Option<CertificateFilter>,
) -> Result<Vec<StoredCertificate>> {
let store = self.certificate_store.read().await;
let mut certificates: Vec<StoredCertificate> = store.values().cloned().collect();
if let Some(f) = filter {
certificates.retain(|cert| f.matches(cert));
}
Ok(certificates)
}
pub async fn generate_crl(&self, ca_id: &str) -> Result<String> {
let revocation_list = self.revocation_list.read().await;
let cas = self.ca_certificates.read().await;
let ca = cas
.get(ca_id)
.ok_or_else(|| AuthError::InvalidRequest(format!("CA not found: {}", ca_id)))?;
let crl_number = revocation_list.len() as u64;
let this_update = Utc::now();
let next_update = this_update + Duration::days(7);
let mut crl_content = format!(
"Certificate Revocation List (CRL):\n\
\x20\x20\x20\x20Version 2 (0x1)\n\
\x20\x20\x20\x20Signature Algorithm: sha256WithRSAEncryption\n\
\x20\x20\x20\x20Issuer: {}\n\
\x20\x20\x20\x20Last Update: {}\n\
\x20\x20\x20\x20Next Update: {}\n\
\x20\x20\x20\x20CRL Number: {}\n",
ca.subject,
this_update.format("%b %d %H:%M:%S %Y GMT"),
next_update.format("%b %d %H:%M:%S %Y GMT"),
crl_number
);
if !revocation_list.is_empty() {
crl_content.push_str("Revoked Certificates:\n");
for entry in revocation_list.values() {
crl_content.push_str(&format!(
" Serial Number: {}\n\
\x20\x20\x20\x20\x20\x20\x20\x20Revocation Date: {}\n\
\x20\x20\x20\x20\x20\x20\x20\x20CRL Reason Code: {:?}\n",
entry.serial_number,
entry.revocation_date.format("%b %d %H:%M:%S %Y GMT"),
entry.reason
));
}
} else {
crl_content.push_str("No Revoked Certificates.\n");
}
let crl_b64 = BASE64_STANDARD.encode(crl_content.as_bytes());
let crl_pem = format!(
"-----BEGIN X509 CRL-----\n{}\n-----END X509 CRL-----",
crl_b64
.chars()
.collect::<Vec<char>>()
.chunks(64)
.map(|chunk| chunk.iter().collect::<String>())
.collect::<Vec<String>>()
.join("\n")
);
log::info!(
"Generated CRL for CA {} with {} revoked certificates",
ca_id,
revocation_list.len()
);
Ok(crl_pem)
}
pub async fn validate_certificate_chain(&self, cert_pem: &str) -> Result<bool> {
let cert_der = self.pem_to_der(cert_pem)?;
let (_, cert) = parse_x509_certificate(&cert_der).map_err(|e| {
AuthError::InvalidToken(format!("Failed to parse certificate: {:?}", e))
})?;
let now = SystemTime::now();
let not_before = cert.validity().not_before.to_datetime();
let not_after = cert.validity().not_after.to_datetime();
if now < not_before {
log::warn!("Certificate not yet valid");
return Ok(false);
}
if now > not_after {
log::warn!("Certificate has expired");
return Ok(false);
}
let issuer_dn = cert.issuer().to_string();
let subject_dn = cert.subject().to_string();
let is_self_signed = issuer_dn == subject_dn;
if is_self_signed {
let cas = self.ca_certificates.read().await;
for ca in cas.values() {
if ca.subject == subject_dn {
log::info!("Certificate validated against trusted root CA");
return Ok(true);
}
}
log::warn!("Self-signed certificate not in trusted root store");
return Ok(false);
}
let serial_number = cert.serial.to_string();
let revocation_list = self.revocation_list.read().await;
if revocation_list.contains_key(&serial_number) {
log::warn!("Certificate has been revoked: {}", serial_number);
return Ok(false);
}
log::info!("Certificate chain validation passed for: {}", subject_dn);
Ok(true)
}
fn pem_to_der(&self, pem: &str) -> Result<Vec<u8>> {
let pem_lines: Vec<&str> = pem
.lines()
.filter(|line| !line.starts_with("-----"))
.collect();
let pem_content = pem_lines.join("");
BASE64_STANDARD
.decode(&pem_content)
.map_err(|e| AuthError::internal(format!("Failed to decode PEM certificate: {}", e)))
}
async fn generate_self_signed_root_ca(&self) -> Result<(String, String)> {
let timestamp = chrono::Utc::now().timestamp();
let subject = "CN=AuthFramework Dev Root CA,O=Auth Framework,C=US";
let cert_content = format!(
"-----BEGIN CERTIFICATE-----\n{}\n-----END CERTIFICATE-----",
BASE64_STANDARD.encode(format!(
"CERT:{}:SUBJ:{}:VALID_FROM:{}:VALID_TO:{}:SERIAL:1",
timestamp,
subject,
timestamp,
timestamp + (365 * 24 * 3600 * 10) ))
);
let key_content = format!(
"-----BEGIN PRIVATE KEY-----\n{}\n-----END PRIVATE KEY-----",
BASE64_STANDARD.encode(format!(
"KEY:{}:RSA:2048:TIMESTAMP:{}",
timestamp, timestamp
))
);
tracing::warn!(
"Generated self-signed development root CA - THIS IS FOR DEVELOPMENT ONLY. \
In production, use proper certificate management with real cryptographic operations."
);
Ok((cert_content, key_content))
}
fn calculate_certificate_fingerprint(&self, cert_pem: &str) -> Result<String> {
use sha2::{Digest, Sha256};
let cert_lines: String = cert_pem
.lines()
.filter(|line| !line.starts_with("-----"))
.collect();
let cert_der = BASE64_STANDARD.decode(&cert_lines).map_err(|e| {
AuthError::internal(format!(
"Failed to decode certificate for fingerprint: {}",
e
))
})?;
let mut hasher = Sha256::new();
hasher.update(&cert_der);
let hash_result = hasher.finalize();
let fingerprint = hash_result
.iter()
.map(|b| format!("{:02X}", b))
.collect::<Vec<_>>()
.join(":");
Ok(fingerprint)
}
}
#[derive(Debug, Clone)]
pub struct CertificateFilter {
pub status: Option<CertificateStatus>,
pub profile: Option<String>,
pub expires_before: Option<DateTime<Utc>>,
pub expires_after: Option<DateTime<Utc>>,
pub subject_contains: Option<String>,
}
impl CertificateFilter {
pub fn matches(&self, cert: &StoredCertificate) -> bool {
if let Some(ref status) = self.status
&& &cert.status != status
{
return false;
}
if let Some(ref profile) = self.profile
&& &cert.profile != profile
{
return false;
}
if let Some(expires_before) = self.expires_before
&& cert.not_after > expires_before
{
return false;
}
if let Some(expires_after) = self.expires_after
&& cert.not_after < expires_after
{
return false;
}
if let Some(ref subject_contains) = self.subject_contains
&& !cert.subject.contains(subject_contains)
{
return false;
}
true
}
}
impl Default for X509Config {
fn default() -> Self {
let mut certificate_profiles = HashMap::new();
certificate_profiles.insert(
"tls_server".to_string(),
CertificateProfile {
name: "TLS Server".to_string(),
cert_type: CertificateType::TlsServer,
key_usage: vec![KeyUsage::DigitalSignature, KeyUsage::KeyEncipherment],
extended_key_usage: vec![ExtendedKeyUsage::ServerAuth],
subject_alt_names: vec![],
validity_days: 365,
preferred_key_type: KeyType::Rsa(2048),
extensions: HashMap::new(),
},
);
certificate_profiles.insert(
"tls_client".to_string(),
CertificateProfile {
name: "TLS Client".to_string(),
cert_type: CertificateType::TlsClient,
key_usage: vec![KeyUsage::DigitalSignature, KeyUsage::KeyAgreement],
extended_key_usage: vec![ExtendedKeyUsage::ClientAuth],
subject_alt_names: vec![],
validity_days: 365,
preferred_key_type: KeyType::Rsa(2048),
extensions: HashMap::new(),
},
);
Self {
default_validity_days: 365,
root_ca_cert_path: "ca/root-ca.crt".to_string(),
root_ca_path: "ca/root-ca.crt".to_string(),
root_ca_key_path: "ca/root-ca.key".to_string(),
intermediate_ca_cert_path: None,
intermediate_ca_path: None,
intermediate_ca_key_path: None,
default_rsa_key_size: 2048,
default_ecdsa_curve: EcdsaCurve::P256,
certificate_profiles,
enable_ocsp: false,
ocsp_responder_url: None,
enable_crl: true,
crl_distribution_url: Some("https://example.com/crl".to_string()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_x509_manager_creation() {
let config = X509Config::default();
let manager = X509CertificateManager::new(config);
assert!(!manager.config.certificate_profiles.is_empty());
assert_eq!(manager.config.default_validity_days, 365);
}
#[tokio::test]
async fn test_certificate_profile() {
let config = X509Config::default();
assert!(config.certificate_profiles.contains_key("tls_server"));
assert!(config.certificate_profiles.contains_key("tls_client"));
let tls_server_profile = &config.certificate_profiles["tls_server"];
assert_eq!(tls_server_profile.cert_type, CertificateType::TlsServer);
assert!(
tls_server_profile
.extended_key_usage
.contains(&ExtendedKeyUsage::ServerAuth)
);
}
#[tokio::test]
async fn test_certificate_filter() {
let filter = CertificateFilter {
status: Some(CertificateStatus::Valid),
profile: None,
expires_before: None,
expires_after: None,
subject_contains: Some("example.com".to_string()),
};
let cert = StoredCertificate {
cert_id: "test".to_string(),
certificate_pem: "".to_string(),
private_key_pem: None,
subject: "CN=example.com".to_string(),
issuer: "CN=Test CA".to_string(),
serial_number: "123".to_string(),
not_before: Utc::now(),
not_after: Utc::now() + Duration::days(365),
profile: "tls_server".to_string(),
status: CertificateStatus::Valid,
fingerprint: "test_fp".to_string(),
created_at: Utc::now(),
metadata: HashMap::new(),
};
assert!(filter.matches(&cert));
}
}