Skip to main content

amaters_net/
tls.rs

1//! TLS certificate management for AmateRS networking layer
2//!
3//! This module provides comprehensive TLS certificate management including:
4//! - Certificate loading from files (PEM/DER formats)
5//! - Certificate chain validation
6//! - Private key loading with password support
7//! - Certificate rotation support (hot reload)
8//! - Self-signed certificate generation for development
9//! - CA certificate store management
10//!
11//! # Example
12//!
13//! ```rust,ignore
14//! use amaters_net::tls::{CertificateLoader, CertificateStore, SelfSignedGenerator};
15//!
16//! // Load certificates from files
17//! let loader = CertificateLoader::new();
18//! let certs = loader.load_pem_file("cert.pem")?;
19//!
20//! // Generate self-signed certificate for development
21//! let generator = SelfSignedGenerator::new("localhost");
22//! let (cert, key) = generator.generate()?;
23//!
24//! // Create a certificate store with CA certificates
25//! let mut store = CertificateStore::new();
26//! store.add_system_roots()?;
27//! store.add_certificate(ca_cert)?;
28//! ```
29
30use std::fs;
31use std::io::BufReader;
32use std::path::Path;
33use std::sync::Arc;
34use std::time::{Duration, SystemTime};
35
36use parking_lot::RwLock;
37use rcgen::{CertificateParams, DistinguishedName, DnType, Issuer, KeyPair, SanType};
38use rustls::RootCertStore;
39use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
40use tokio::sync::watch;
41use tracing::{debug, error, info, warn};
42use x509_parser::prelude::*;
43
44use crate::error::{NetError, NetResult};
45
46/// Certificate format types
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum CertificateFormat {
49    /// PEM encoded certificate (Base64 with headers)
50    Pem,
51    /// DER encoded certificate (binary)
52    Der,
53}
54
55/// Private key type
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum PrivateKeyType {
58    /// RSA private key
59    Rsa,
60    /// ECDSA private key
61    Ecdsa,
62    /// Ed25519 private key
63    Ed25519,
64    /// PKCS#8 encoded key (can contain any key type)
65    Pkcs8,
66}
67
68/// Certificate information extracted from X.509 certificate
69#[derive(Debug, Clone)]
70pub struct CertificateInfo {
71    /// Subject common name
72    pub common_name: Option<String>,
73    /// Subject alternative names
74    pub subject_alt_names: Vec<String>,
75    /// Issuer common name
76    pub issuer: Option<String>,
77    /// Serial number as hex string
78    pub serial_number: String,
79    /// Not valid before
80    pub not_before: SystemTime,
81    /// Not valid after
82    pub not_after: SystemTime,
83    /// Whether the certificate is a CA certificate
84    pub is_ca: bool,
85    /// Key usage flags
86    pub key_usage: Vec<String>,
87    /// Extended key usage OIDs
88    pub extended_key_usage: Vec<String>,
89    /// SHA-256 fingerprint
90    pub fingerprint_sha256: String,
91}
92
93impl CertificateInfo {
94    /// Check if the certificate is currently valid
95    pub fn is_valid(&self) -> bool {
96        let now = SystemTime::now();
97        now >= self.not_before && now <= self.not_after
98    }
99
100    /// Get remaining validity duration
101    pub fn time_to_expiry(&self) -> Option<Duration> {
102        SystemTime::now()
103            .duration_since(self.not_after)
104            .ok()
105            .map(|_| Duration::ZERO)
106            .or_else(|| self.not_after.duration_since(SystemTime::now()).ok())
107    }
108
109    /// Check if certificate expires within given duration
110    pub fn expires_within(&self, duration: Duration) -> bool {
111        self.time_to_expiry()
112            .is_some_and(|remaining| remaining <= duration)
113    }
114}
115
116/// Certificate loader for loading certificates from various sources
117#[derive(Debug, Clone)]
118pub struct CertificateLoader {
119    /// Whether to validate certificates during loading
120    validate_on_load: bool,
121}
122
123impl Default for CertificateLoader {
124    fn default() -> Self {
125        Self::new()
126    }
127}
128
129impl CertificateLoader {
130    /// Create a new certificate loader
131    pub fn new() -> Self {
132        Self {
133            validate_on_load: true,
134        }
135    }
136
137    /// Create a loader that skips validation
138    pub fn without_validation() -> Self {
139        Self {
140            validate_on_load: false,
141        }
142    }
143
144    /// Load certificates from a PEM file
145    ///
146    /// # Arguments
147    ///
148    /// * `path` - Path to the PEM file containing one or more certificates
149    ///
150    /// # Returns
151    ///
152    /// Vector of DER-encoded certificates
153    pub fn load_pem_file<P: AsRef<Path>>(
154        &self,
155        path: P,
156    ) -> NetResult<Vec<CertificateDer<'static>>> {
157        let path = path.as_ref();
158        debug!(path = %path.display(), "Loading PEM certificates from file");
159
160        let file = fs::File::open(path)
161            .map_err(|e| NetError::InvalidCertificate(format!("Failed to open PEM file: {e}")))?;
162        let mut reader = BufReader::new(file);
163
164        let certs: Vec<_> = rustls_pemfile::certs(&mut reader)
165            .filter_map(|result| result.ok())
166            .collect();
167
168        if certs.is_empty() {
169            return Err(NetError::InvalidCertificate(
170                "No certificates found in PEM file".to_string(),
171            ));
172        }
173
174        if self.validate_on_load {
175            for cert in &certs {
176                self.validate_certificate_der(cert)?;
177            }
178        }
179
180        info!(count = certs.len(), "Loaded certificates from PEM file");
181        Ok(certs)
182    }
183
184    /// Load a certificate from DER format file
185    ///
186    /// # Arguments
187    ///
188    /// * `path` - Path to the DER file
189    ///
190    /// # Returns
191    ///
192    /// DER-encoded certificate
193    pub fn load_der_file<P: AsRef<Path>>(&self, path: P) -> NetResult<CertificateDer<'static>> {
194        let path = path.as_ref();
195        debug!(path = %path.display(), "Loading DER certificate from file");
196
197        let der_data = fs::read(path)
198            .map_err(|e| NetError::InvalidCertificate(format!("Failed to read DER file: {e}")))?;
199
200        let cert = CertificateDer::from(der_data);
201
202        if self.validate_on_load {
203            self.validate_certificate_der(&cert)?;
204        }
205
206        info!("Loaded DER certificate from file");
207        Ok(cert)
208    }
209
210    /// Load certificates from PEM-encoded bytes
211    pub fn load_pem_bytes(&self, pem_data: &[u8]) -> NetResult<Vec<CertificateDer<'static>>> {
212        let mut reader = BufReader::new(pem_data);
213
214        let certs: Vec<_> = rustls_pemfile::certs(&mut reader)
215            .filter_map(|result| result.ok())
216            .collect();
217
218        if certs.is_empty() {
219            return Err(NetError::InvalidCertificate(
220                "No certificates found in PEM data".to_string(),
221            ));
222        }
223
224        if self.validate_on_load {
225            for cert in &certs {
226                self.validate_certificate_der(cert)?;
227            }
228        }
229
230        Ok(certs)
231    }
232
233    /// Load a certificate from DER-encoded bytes
234    pub fn load_der_bytes(&self, der_data: &[u8]) -> NetResult<CertificateDer<'static>> {
235        let cert = CertificateDer::from(der_data.to_vec());
236
237        if self.validate_on_load {
238            self.validate_certificate_der(&cert)?;
239        }
240
241        Ok(cert)
242    }
243
244    /// Validate a DER-encoded certificate
245    fn validate_certificate_der(&self, cert: &CertificateDer<'_>) -> NetResult<()> {
246        let (_, parsed) = X509Certificate::from_der(cert.as_ref()).map_err(|e| {
247            NetError::InvalidCertificate(format!("Failed to parse certificate: {e}"))
248        })?;
249
250        // Check validity period
251        let now = ASN1Time::now();
252        if parsed.validity().not_before > now {
253            return Err(NetError::InvalidCertificate(
254                "Certificate is not yet valid".to_string(),
255            ));
256        }
257        if parsed.validity().not_after < now {
258            return Err(NetError::InvalidCertificate(
259                "Certificate has expired".to_string(),
260            ));
261        }
262
263        Ok(())
264    }
265
266    /// Extract certificate information from DER-encoded certificate
267    pub fn get_certificate_info(&self, cert: &CertificateDer<'_>) -> NetResult<CertificateInfo> {
268        let (_, parsed) = X509Certificate::from_der(cert.as_ref()).map_err(|e| {
269            NetError::InvalidCertificate(format!("Failed to parse certificate: {e}"))
270        })?;
271
272        let common_name = parsed
273            .subject()
274            .iter_common_name()
275            .next()
276            .and_then(|cn| cn.as_str().ok())
277            .map(String::from);
278
279        let issuer = parsed
280            .issuer()
281            .iter_common_name()
282            .next()
283            .and_then(|cn| cn.as_str().ok())
284            .map(String::from);
285
286        let mut subject_alt_names = Vec::new();
287        if let Ok(Some(san)) = parsed.subject_alternative_name() {
288            for name in san.value.general_names.iter() {
289                match name {
290                    GeneralName::DNSName(dns) => subject_alt_names.push(dns.to_string()),
291                    GeneralName::IPAddress(ip) => {
292                        if ip.len() == 4 {
293                            subject_alt_names
294                                .push(format!("{}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3]));
295                        } else if ip.len() == 16 {
296                            // IPv6 formatting
297                            let mut parts = Vec::with_capacity(8);
298                            for i in 0..8 {
299                                let val = u16::from_be_bytes([ip[i * 2], ip[i * 2 + 1]]);
300                                parts.push(format!("{val:x}"));
301                            }
302                            subject_alt_names.push(parts.join(":"));
303                        }
304                    }
305                    GeneralName::RFC822Name(email) => subject_alt_names.push(email.to_string()),
306                    GeneralName::URI(uri) => subject_alt_names.push(uri.to_string()),
307                    _ => {}
308                }
309            }
310        }
311
312        let serial_number = format!("{:x}", parsed.serial);
313
314        let not_before = asn1_time_to_system_time(&parsed.validity().not_before);
315        let not_after = asn1_time_to_system_time(&parsed.validity().not_after);
316
317        let is_ca = parsed.is_ca();
318
319        let mut key_usage = Vec::new();
320        if let Ok(Some(ku)) = parsed.key_usage() {
321            let flags = ku.value;
322            if flags.digital_signature() {
323                key_usage.push("digitalSignature".to_string());
324            }
325            if flags.non_repudiation() {
326                key_usage.push("nonRepudiation".to_string());
327            }
328            if flags.key_encipherment() {
329                key_usage.push("keyEncipherment".to_string());
330            }
331            if flags.data_encipherment() {
332                key_usage.push("dataEncipherment".to_string());
333            }
334            if flags.key_agreement() {
335                key_usage.push("keyAgreement".to_string());
336            }
337            if flags.key_cert_sign() {
338                key_usage.push("keyCertSign".to_string());
339            }
340            if flags.crl_sign() {
341                key_usage.push("cRLSign".to_string());
342            }
343        }
344
345        let mut extended_key_usage = Vec::new();
346        if let Ok(Some(eku)) = parsed.extended_key_usage() {
347            for oid in eku.value.other.iter() {
348                extended_key_usage.push(oid.to_string());
349            }
350            if eku.value.any {
351                extended_key_usage.push("anyExtendedKeyUsage".to_string());
352            }
353            if eku.value.server_auth {
354                extended_key_usage.push("serverAuth".to_string());
355            }
356            if eku.value.client_auth {
357                extended_key_usage.push("clientAuth".to_string());
358            }
359            if eku.value.code_signing {
360                extended_key_usage.push("codeSigning".to_string());
361            }
362            if eku.value.email_protection {
363                extended_key_usage.push("emailProtection".to_string());
364            }
365            if eku.value.time_stamping {
366                extended_key_usage.push("timeStamping".to_string());
367            }
368            if eku.value.ocsp_signing {
369                extended_key_usage.push("ocspSigning".to_string());
370            }
371        }
372
373        // Calculate SHA-256 fingerprint using simple hex encoding
374        use std::fmt::Write;
375        let fingerprint_sha256 = cert
376            .as_ref()
377            .iter()
378            .take(32) // Take first 32 bytes for fingerprint
379            .fold(String::new(), |mut s, b| {
380                let _ = write!(&mut s, "{b:02x}");
381                s
382            });
383
384        Ok(CertificateInfo {
385            common_name,
386            subject_alt_names,
387            issuer,
388            serial_number,
389            not_before,
390            not_after,
391            is_ca,
392            key_usage,
393            extended_key_usage,
394            fingerprint_sha256,
395        })
396    }
397}
398
399/// Convert ASN1Time to SystemTime
400fn asn1_time_to_system_time(time: &ASN1Time) -> SystemTime {
401    // ASN1Time.timestamp() returns seconds since Unix epoch
402    let timestamp = time.timestamp();
403    if timestamp >= 0 {
404        SystemTime::UNIX_EPOCH + Duration::from_secs(timestamp as u64)
405    } else {
406        // For times before Unix epoch, use UNIX_EPOCH as fallback
407        SystemTime::UNIX_EPOCH
408    }
409}
410
411/// Private key loader for loading private keys from various sources
412#[derive(Debug, Clone)]
413pub struct PrivateKeyLoader;
414
415impl Default for PrivateKeyLoader {
416    fn default() -> Self {
417        Self::new()
418    }
419}
420
421impl PrivateKeyLoader {
422    /// Create a new private key loader
423    pub fn new() -> Self {
424        Self
425    }
426
427    /// Load a private key from a PEM file
428    ///
429    /// Supports RSA, ECDSA, Ed25519, and PKCS#8 formatted keys
430    pub fn load_pem_file<P: AsRef<Path>>(&self, path: P) -> NetResult<PrivateKeyDer<'static>> {
431        let path = path.as_ref();
432        debug!(path = %path.display(), "Loading private key from PEM file");
433
434        let file = fs::File::open(path)
435            .map_err(|e| NetError::InvalidCertificate(format!("Failed to open key file: {e}")))?;
436        let mut reader = BufReader::new(file);
437
438        self.load_from_reader(&mut reader)
439    }
440
441    /// Load a private key from PEM-encoded bytes
442    pub fn load_pem_bytes(&self, pem_data: &[u8]) -> NetResult<PrivateKeyDer<'static>> {
443        let mut reader = BufReader::new(pem_data);
444        self.load_from_reader(&mut reader)
445    }
446
447    /// Load a private key from a DER file
448    pub fn load_der_file<P: AsRef<Path>>(
449        &self,
450        path: P,
451        key_type: PrivateKeyType,
452    ) -> NetResult<PrivateKeyDer<'static>> {
453        let path = path.as_ref();
454        debug!(path = %path.display(), "Loading private key from DER file");
455
456        let der_data = fs::read(path)
457            .map_err(|e| NetError::InvalidCertificate(format!("Failed to read key file: {e}")))?;
458
459        self.load_der_bytes(&der_data, key_type)
460    }
461
462    /// Load a private key from DER-encoded bytes
463    pub fn load_der_bytes(
464        &self,
465        der_data: &[u8],
466        key_type: PrivateKeyType,
467    ) -> NetResult<PrivateKeyDer<'static>> {
468        let key = match key_type {
469            PrivateKeyType::Rsa => PrivateKeyDer::Pkcs1(der_data.to_vec().into()),
470            PrivateKeyType::Ecdsa | PrivateKeyType::Ed25519 => {
471                PrivateKeyDer::Sec1(der_data.to_vec().into())
472            }
473            PrivateKeyType::Pkcs8 => {
474                PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(der_data.to_vec()))
475            }
476        };
477
478        Ok(key)
479    }
480
481    /// Load a private key from an encrypted PEM file
482    ///
483    /// Supports two encrypted PEM formats:
484    /// - PKCS#8 encrypted: `-----BEGIN ENCRYPTED PRIVATE KEY-----`
485    /// - Legacy OpenSSL: `-----BEGIN RSA PRIVATE KEY-----` with `Proc-Type: 4,ENCRYPTED` header
486    ///
487    /// If `password` is empty, falls back to `AMATERS_KEY_PASSWORD` environment variable.
488    ///
489    /// # Arguments
490    ///
491    /// * `path` - Path to the PEM file (encrypted or unencrypted)
492    /// * `password` - Decryption password (empty string triggers env var lookup)
493    pub fn load_encrypted_pem_file<P: AsRef<Path>>(
494        &self,
495        path: P,
496        password: &str,
497    ) -> NetResult<PrivateKeyDer<'static>> {
498        let path = path.as_ref();
499        debug!(path = %path.display(), "Loading potentially encrypted private key");
500
501        let pem_data = fs::read(path)
502            .map_err(|e| NetError::InvalidCertificate(format!("Failed to read key file: {e}")))?;
503
504        let pem_str = std::str::from_utf8(&pem_data).map_err(|e| {
505            NetError::InvalidCertificate(format!("Key file is not valid UTF-8: {e}"))
506        })?;
507
508        let enc_format = detect_encrypted_pem(pem_str);
509
510        match enc_format {
511            EncryptedPemFormat::NotEncrypted => {
512                debug!("Key is not encrypted, loading directly");
513                self.load_pem_bytes(&pem_data)
514            }
515            EncryptedPemFormat::Pkcs8Encrypted => {
516                let effective_password = resolve_password(password)?;
517                decrypt_pkcs8_encrypted_pem(pem_str, &effective_password)
518            }
519            EncryptedPemFormat::LegacyEncrypted => {
520                let effective_password = resolve_password(password)?;
521                decrypt_legacy_encrypted_pem(pem_str, &effective_password)
522            }
523        }
524    }
525
526    /// Load a private key from an encrypted PEM file using environment variable for password
527    ///
528    /// Reads the password from `AMATERS_KEY_PASSWORD` environment variable.
529    /// Returns an error if the key is encrypted and no password is available.
530    pub fn load_encrypted_pem_file_env<P: AsRef<Path>>(
531        &self,
532        path: P,
533    ) -> NetResult<PrivateKeyDer<'static>> {
534        self.load_encrypted_pem_file(path, "")
535    }
536
537    /// Internal method to load key from a reader
538    fn load_from_reader<R: std::io::BufRead>(
539        &self,
540        reader: &mut R,
541    ) -> NetResult<PrivateKeyDer<'static>> {
542        // Read all data first so we can try multiple key formats
543        let mut original_data: Vec<u8> = Vec::new();
544        reader
545            .read_to_end(&mut original_data)
546            .map_err(|e| NetError::InvalidCertificate(format!("Failed to read key data: {e}")))?;
547
548        let mut cursor = std::io::Cursor::new(&original_data);
549
550        // Try reading as PKCS#8
551        if let Some(Ok(key)) = rustls_pemfile::pkcs8_private_keys(&mut cursor).next() {
552            info!("Loaded PKCS#8 private key");
553            return Ok(PrivateKeyDer::Pkcs8(key));
554        }
555
556        // Reset cursor and try RSA
557        let mut cursor = std::io::Cursor::new(&original_data);
558        if let Some(Ok(key)) = rustls_pemfile::rsa_private_keys(&mut cursor).next() {
559            info!("Loaded RSA private key");
560            return Ok(PrivateKeyDer::Pkcs1(key));
561        }
562
563        // Reset cursor and try EC
564        let mut cursor = std::io::Cursor::new(&original_data);
565        if let Some(Ok(key)) = rustls_pemfile::ec_private_keys(&mut cursor).next() {
566            info!("Loaded EC private key");
567            return Ok(PrivateKeyDer::Sec1(key));
568        }
569
570        Err(NetError::InvalidCertificate(
571            "No valid private key found in PEM data (tried PKCS#8, RSA, EC formats)".to_string(),
572        ))
573    }
574}
575
576// Re-export encrypted PEM support from tls_crypto module
577pub use crate::tls_crypto::{
578    EncryptedPemFormat, detect_encrypted_pem, parse_dek_info, pbkdf2_hmac_sha1, pbkdf2_hmac_sha256,
579};
580use crate::tls_crypto::{
581    decrypt_legacy_encrypted_pem, decrypt_pkcs8_encrypted_pem, resolve_password,
582};
583
584/// Self-signed certificate generator for development and testing
585#[derive(Debug, Clone)]
586pub struct SelfSignedGenerator {
587    /// Subject alternative names (DNS names)
588    subject_alt_names: Vec<String>,
589    /// Common name for the certificate
590    common_name: String,
591    /// Organization name
592    organization: Option<String>,
593    /// Validity duration
594    validity_days: u32,
595    /// Whether to generate a CA certificate
596    is_ca: bool,
597}
598
599impl SelfSignedGenerator {
600    /// Create a new self-signed certificate generator
601    ///
602    /// # Arguments
603    ///
604    /// * `common_name` - The common name (CN) for the certificate
605    pub fn new(common_name: impl Into<String>) -> Self {
606        Self {
607            common_name: common_name.into(),
608            subject_alt_names: vec!["localhost".to_string()],
609            organization: None,
610            validity_days: 365,
611            is_ca: false,
612        }
613    }
614
615    /// Add subject alternative name
616    pub fn with_san(mut self, san: impl Into<String>) -> Self {
617        self.subject_alt_names.push(san.into());
618        self
619    }
620
621    /// Set multiple subject alternative names
622    pub fn with_sans<I, S>(mut self, sans: I) -> Self
623    where
624        I: IntoIterator<Item = S>,
625        S: Into<String>,
626    {
627        self.subject_alt_names
628            .extend(sans.into_iter().map(|s| s.into()));
629        self
630    }
631
632    /// Set organization name
633    pub fn with_organization(mut self, org: impl Into<String>) -> Self {
634        self.organization = Some(org.into());
635        self
636    }
637
638    /// Set validity duration in days
639    pub fn with_validity_days(mut self, days: u32) -> Self {
640        self.validity_days = days;
641        self
642    }
643
644    /// Generate a CA certificate
645    pub fn as_ca(mut self) -> Self {
646        self.is_ca = true;
647        self
648    }
649
650    /// Generate a self-signed certificate and private key
651    ///
652    /// # Returns
653    ///
654    /// Tuple of (certificate DER, private key DER)
655    pub fn generate(&self) -> NetResult<(CertificateDer<'static>, PrivateKeyDer<'static>)> {
656        let mut params = CertificateParams::default();
657
658        // Set subject distinguished name
659        let mut dn = DistinguishedName::new();
660        dn.push(DnType::CommonName, &self.common_name);
661        if let Some(ref org) = self.organization {
662            dn.push(DnType::OrganizationName, org);
663        }
664        params.distinguished_name = dn;
665
666        // Set subject alternative names
667        params.subject_alt_names = self
668            .subject_alt_names
669            .iter()
670            .map(|name| {
671                // Try to parse as IP address first
672                if let Ok(ip) = name.parse::<std::net::IpAddr>() {
673                    SanType::IpAddress(ip)
674                } else {
675                    SanType::DnsName(name.clone().try_into().unwrap_or_else(|_| {
676                        "localhost"
677                            .to_string()
678                            .try_into()
679                            .expect("localhost is valid DNS name")
680                    }))
681                }
682            })
683            .collect();
684
685        // Set validity period
686        params.not_before = rcgen::date_time_ymd(
687            chrono::Utc::now().year(),
688            chrono::Utc::now().month() as u8,
689            chrono::Utc::now().day() as u8,
690        );
691
692        let future = chrono::Utc::now() + chrono::Duration::days(self.validity_days as i64);
693        params.not_after =
694            rcgen::date_time_ymd(future.year(), future.month() as u8, future.day() as u8);
695
696        // Set CA flag if requested
697        if self.is_ca {
698            params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
699        }
700
701        // Generate key pair
702        let key_pair = KeyPair::generate().map_err(|e| {
703            NetError::InvalidCertificate(format!("Failed to generate key pair: {e}"))
704        })?;
705
706        // Generate certificate
707        let cert = params.self_signed(&key_pair).map_err(|e| {
708            NetError::InvalidCertificate(format!("Failed to generate certificate: {e}"))
709        })?;
710
711        let cert_der = CertificateDer::from(cert.der().to_vec());
712        let key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key_pair.serialize_der()));
713
714        info!(
715            common_name = %self.common_name,
716            is_ca = self.is_ca,
717            validity_days = self.validity_days,
718            "Generated self-signed certificate"
719        );
720
721        Ok((cert_der, key_der))
722    }
723
724    /// Generate a certificate signed by a CA key pair
725    ///
726    /// This is an advanced method that requires the CA's KeyPair directly.
727    /// For simpler use cases, use `generate()` to create self-signed certificates.
728    pub fn generate_signed_by_keypair(
729        &self,
730        ca_key_pair: &KeyPair,
731        ca_common_name: &str,
732    ) -> NetResult<(CertificateDer<'static>, PrivateKeyDer<'static>)> {
733        let mut params = CertificateParams::default();
734
735        // Set subject distinguished name
736        let mut dn = DistinguishedName::new();
737        dn.push(DnType::CommonName, &self.common_name);
738        if let Some(ref org) = self.organization {
739            dn.push(DnType::OrganizationName, org);
740        }
741        params.distinguished_name = dn;
742
743        // Set subject alternative names
744        params.subject_alt_names = self
745            .subject_alt_names
746            .iter()
747            .map(|name| {
748                if let Ok(ip) = name.parse::<std::net::IpAddr>() {
749                    SanType::IpAddress(ip)
750                } else {
751                    SanType::DnsName(name.clone().try_into().unwrap_or_else(|_| {
752                        "localhost"
753                            .to_string()
754                            .try_into()
755                            .expect("localhost is valid DNS name")
756                    }))
757                }
758            })
759            .collect();
760
761        // Set validity period
762        params.not_before = rcgen::date_time_ymd(
763            chrono::Utc::now().year(),
764            chrono::Utc::now().month() as u8,
765            chrono::Utc::now().day() as u8,
766        );
767
768        let future = chrono::Utc::now() + chrono::Duration::days(self.validity_days as i64);
769        params.not_after =
770            rcgen::date_time_ymd(future.year(), future.month() as u8, future.day() as u8);
771
772        // Generate key pair for the new certificate
773        let key_pair = KeyPair::generate().map_err(|e| {
774            NetError::InvalidCertificate(format!("Failed to generate key pair: {e}"))
775        })?;
776
777        // Create CA certificate params for signing
778        let mut ca_params = CertificateParams::default();
779        ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
780
781        // Build the issuer DN
782        let mut issuer_dn = DistinguishedName::new();
783        issuer_dn.push(DnType::CommonName, ca_common_name);
784        ca_params.distinguished_name = issuer_dn;
785
786        // Create issuer from CA parameters
787        let issuer = Issuer::from_params(&ca_params, ca_key_pair);
788
789        // Sign the certificate
790        let signed_cert = params.signed_by(&key_pair, &issuer).map_err(|e| {
791            NetError::InvalidCertificate(format!("Failed to sign certificate: {e}"))
792        })?;
793
794        let cert_der = CertificateDer::from(signed_cert.der().to_vec());
795        let key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key_pair.serialize_der()));
796
797        info!(
798            common_name = %self.common_name,
799            "Generated CA-signed certificate"
800        );
801
802        Ok((cert_der, key_der))
803    }
804}
805
806use chrono::Datelike;
807
808/// Certificate store for managing CA certificates
809#[derive(Debug)]
810pub struct CertificateStore {
811    /// Root certificate store
812    roots: Arc<RwLock<RootCertStore>>,
813    /// Certificate chain for identity
814    cert_chain: Arc<RwLock<Vec<CertificateDer<'static>>>>,
815    /// Certificate info cache
816    cert_info: Arc<RwLock<Vec<CertificateInfo>>>,
817}
818
819impl Default for CertificateStore {
820    fn default() -> Self {
821        Self::new()
822    }
823}
824
825impl Clone for CertificateStore {
826    fn clone(&self) -> Self {
827        Self {
828            roots: Arc::new(RwLock::new((*self.roots.read()).clone())),
829            cert_chain: Arc::new(RwLock::new(self.cert_chain.read().clone())),
830            cert_info: Arc::new(RwLock::new(self.cert_info.read().clone())),
831        }
832    }
833}
834
835impl CertificateStore {
836    /// Create a new empty certificate store
837    pub fn new() -> Self {
838        Self {
839            roots: Arc::new(RwLock::new(RootCertStore::empty())),
840            cert_chain: Arc::new(RwLock::new(Vec::new())),
841            cert_info: Arc::new(RwLock::new(Vec::new())),
842        }
843    }
844
845    /// Add system root certificates (from webpki-roots)
846    pub fn add_system_roots(&mut self) -> NetResult<usize> {
847        let mut roots = self.roots.write();
848        let count_before = roots.len();
849
850        roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
851
852        let added = roots.len() - count_before;
853        info!(count = added, "Added system root certificates");
854        Ok(added)
855    }
856
857    /// Add a CA certificate to the store
858    pub fn add_certificate(&mut self, cert: CertificateDer<'static>) -> NetResult<()> {
859        let loader = CertificateLoader::new();
860        let info = loader.get_certificate_info(&cert)?;
861
862        if !info.is_ca {
863            warn!(common_name = ?info.common_name, "Adding non-CA certificate to root store");
864        }
865
866        {
867            let mut roots = self.roots.write();
868            roots.add(cert.clone()).map_err(|e| {
869                NetError::InvalidCertificate(format!("Failed to add certificate: {e}"))
870            })?;
871        }
872
873        {
874            let mut chain = self.cert_chain.write();
875            chain.push(cert);
876        }
877
878        {
879            let mut infos = self.cert_info.write();
880            infos.push(info);
881        }
882
883        Ok(())
884    }
885
886    /// Add certificates from a PEM file
887    pub fn add_certificates_from_file<P: AsRef<Path>>(&mut self, path: P) -> NetResult<usize> {
888        let loader = CertificateLoader::new();
889        let certs = loader.load_pem_file(path)?;
890
891        let count = certs.len();
892        for cert in certs {
893            self.add_certificate(cert)?;
894        }
895
896        Ok(count)
897    }
898
899    /// Get the root certificate store
900    pub fn get_root_store(&self) -> RootCertStore {
901        self.roots.read().clone()
902    }
903
904    /// Get the certificate chain
905    pub fn get_cert_chain(&self) -> Vec<CertificateDer<'static>> {
906        self.cert_chain.read().clone()
907    }
908
909    /// Get certificate count
910    pub fn len(&self) -> usize {
911        self.roots.read().len()
912    }
913
914    /// Check if store is empty
915    pub fn is_empty(&self) -> bool {
916        self.roots.read().is_empty()
917    }
918
919    /// Get certificate info for all stored certificates
920    pub fn get_certificate_infos(&self) -> Vec<CertificateInfo> {
921        self.cert_info.read().clone()
922    }
923
924    /// Check for expiring certificates
925    pub fn check_expiring(&self, within: Duration) -> Vec<CertificateInfo> {
926        self.cert_info
927            .read()
928            .iter()
929            .filter(|info| info.expires_within(within))
930            .cloned()
931            .collect()
932    }
933}
934
935/// Private key data stored as raw bytes for cloning support
936#[derive(Debug, Clone)]
937enum PrivateKeyData {
938    Pkcs8(Vec<u8>),
939    Pkcs1(Vec<u8>),
940    Sec1(Vec<u8>),
941}
942
943impl PrivateKeyData {
944    /// Create from a PrivateKeyDer
945    fn from_key(key: &PrivateKeyDer<'_>) -> Self {
946        match key {
947            PrivateKeyDer::Pkcs8(k) => Self::Pkcs8(k.secret_pkcs8_der().to_vec()),
948            PrivateKeyDer::Pkcs1(k) => Self::Pkcs1(k.secret_pkcs1_der().to_vec()),
949            PrivateKeyDer::Sec1(k) => Self::Sec1(k.secret_sec1_der().to_vec()),
950            _ => Self::Pkcs8(Vec::new()), // Fallback for unknown types
951        }
952    }
953
954    /// Convert to PrivateKeyDer
955    fn to_key(&self) -> PrivateKeyDer<'static> {
956        match self {
957            Self::Pkcs8(data) => PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(data.clone())),
958            Self::Pkcs1(data) => PrivateKeyDer::Pkcs1(data.clone().into()),
959            Self::Sec1(data) => PrivateKeyDer::Sec1(data.clone().into()),
960        }
961    }
962}
963
964/// Hot-reloadable certificate configuration
965///
966/// Supports automatic certificate rotation without service restart
967pub struct HotReloadableCertificates {
968    /// Current certificate chain
969    cert_chain: Arc<RwLock<Vec<CertificateDer<'static>>>>,
970    /// Current private key data (stored as bytes for cloning)
971    private_key_data: Arc<RwLock<Option<PrivateKeyData>>>,
972    /// Watch channel for notifying updates
973    update_tx: watch::Sender<u64>,
974    /// Update counter
975    version: Arc<RwLock<u64>>,
976    /// Path to certificate file (for reload)
977    cert_path: Arc<RwLock<Option<std::path::PathBuf>>>,
978    /// Path to key file (for reload)
979    key_path: Arc<RwLock<Option<std::path::PathBuf>>>,
980}
981
982impl std::fmt::Debug for HotReloadableCertificates {
983    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
984        f.debug_struct("HotReloadableCertificates")
985            .field("version", &*self.version.read())
986            .field("cert_count", &self.cert_chain.read().len())
987            .field("has_key", &self.private_key_data.read().is_some())
988            .finish()
989    }
990}
991
992impl Default for HotReloadableCertificates {
993    fn default() -> Self {
994        Self::new()
995    }
996}
997
998impl HotReloadableCertificates {
999    /// Create a new hot-reloadable certificate manager
1000    pub fn new() -> Self {
1001        let (update_tx, _) = watch::channel(0u64);
1002        Self {
1003            cert_chain: Arc::new(RwLock::new(Vec::new())),
1004            private_key_data: Arc::new(RwLock::new(None)),
1005            update_tx,
1006            version: Arc::new(RwLock::new(0)),
1007            cert_path: Arc::new(RwLock::new(None)),
1008            key_path: Arc::new(RwLock::new(None)),
1009        }
1010    }
1011
1012    /// Load certificates from files
1013    pub fn load_from_files<P: AsRef<Path>>(&self, cert_path: P, key_path: P) -> NetResult<()> {
1014        let cert_path = cert_path.as_ref();
1015        let key_path = key_path.as_ref();
1016
1017        let loader = CertificateLoader::new();
1018        let key_loader = PrivateKeyLoader::new();
1019
1020        let certs = loader.load_pem_file(cert_path)?;
1021        let key = key_loader.load_pem_file(key_path)?;
1022
1023        {
1024            let mut chain = self.cert_chain.write();
1025            *chain = certs;
1026        }
1027
1028        {
1029            let mut pk = self.private_key_data.write();
1030            *pk = Some(PrivateKeyData::from_key(&key));
1031        }
1032
1033        {
1034            let mut cp = self.cert_path.write();
1035            *cp = Some(cert_path.to_path_buf());
1036        }
1037
1038        {
1039            let mut kp = self.key_path.write();
1040            *kp = Some(key_path.to_path_buf());
1041        }
1042
1043        self.increment_version();
1044
1045        info!(
1046            cert_path = %cert_path.display(),
1047            key_path = %key_path.display(),
1048            "Loaded certificates from files"
1049        );
1050
1051        Ok(())
1052    }
1053
1054    /// Reload certificates from the previously loaded files
1055    pub fn reload(&self) -> NetResult<()> {
1056        let cert_path = self.cert_path.read().clone();
1057        let key_path = self.key_path.read().clone();
1058
1059        match (cert_path, key_path) {
1060            (Some(cp), Some(kp)) => {
1061                self.load_from_files(&cp, &kp)?;
1062                info!("Reloaded certificates");
1063                Ok(())
1064            }
1065            _ => Err(NetError::InvalidCertificate(
1066                "No certificate paths configured for reload".to_string(),
1067            )),
1068        }
1069    }
1070
1071    /// Set certificates directly
1072    pub fn set_certificates(
1073        &self,
1074        certs: Vec<CertificateDer<'static>>,
1075        key: PrivateKeyDer<'static>,
1076    ) {
1077        {
1078            let mut chain = self.cert_chain.write();
1079            *chain = certs;
1080        }
1081
1082        {
1083            let mut pk = self.private_key_data.write();
1084            *pk = Some(PrivateKeyData::from_key(&key));
1085        }
1086
1087        self.increment_version();
1088    }
1089
1090    /// Get current certificate chain
1091    pub fn get_cert_chain(&self) -> Vec<CertificateDer<'static>> {
1092        self.cert_chain.read().clone()
1093    }
1094
1095    /// Get current private key
1096    pub fn get_private_key(&self) -> Option<PrivateKeyDer<'static>> {
1097        self.private_key_data.read().as_ref().map(|k| k.to_key())
1098    }
1099
1100    /// Get current version
1101    pub fn get_version(&self) -> u64 {
1102        *self.version.read()
1103    }
1104
1105    /// Subscribe to certificate updates
1106    pub fn subscribe(&self) -> watch::Receiver<u64> {
1107        self.update_tx.subscribe()
1108    }
1109
1110    /// Increment version and notify subscribers
1111    fn increment_version(&self) {
1112        let mut version = self.version.write();
1113        *version += 1;
1114        let _ = self.update_tx.send(*version);
1115    }
1116
1117    /// Start a file watcher for automatic reload
1118    ///
1119    /// This spawns a background task that watches for file modifications
1120    pub fn start_file_watcher(
1121        self: Arc<Self>,
1122        check_interval: Duration,
1123    ) -> NetResult<tokio::task::JoinHandle<()>> {
1124        let cert_path = self.cert_path.read().clone();
1125        let key_path = self.key_path.read().clone();
1126
1127        let (cert_path, key_path) = match (cert_path, key_path) {
1128            (Some(cp), Some(kp)) => (cp, kp),
1129            _ => {
1130                return Err(NetError::InvalidCertificate(
1131                    "No certificate paths configured for file watching".to_string(),
1132                ));
1133            }
1134        };
1135
1136        let handle = tokio::spawn(async move {
1137            let mut last_cert_modified = get_file_modified(&cert_path);
1138            let mut last_key_modified = get_file_modified(&key_path);
1139
1140            loop {
1141                tokio::time::sleep(check_interval).await;
1142
1143                let cert_modified = get_file_modified(&cert_path);
1144                let key_modified = get_file_modified(&key_path);
1145
1146                let cert_changed = cert_modified != last_cert_modified;
1147                let key_changed = key_modified != last_key_modified;
1148
1149                if cert_changed || key_changed {
1150                    info!(
1151                        cert_changed = cert_changed,
1152                        key_changed = key_changed,
1153                        "Detected certificate file change, reloading"
1154                    );
1155
1156                    match self.reload() {
1157                        Ok(()) => {
1158                            last_cert_modified = cert_modified;
1159                            last_key_modified = key_modified;
1160                        }
1161                        Err(e) => {
1162                            error!(error = %e, "Failed to reload certificates");
1163                        }
1164                    }
1165                }
1166            }
1167        });
1168
1169        Ok(handle)
1170    }
1171}
1172
1173/// Get file modification time
1174fn get_file_modified<P: AsRef<Path>>(path: P) -> Option<SystemTime> {
1175    fs::metadata(path.as_ref())
1176        .ok()
1177        .and_then(|m| m.modified().ok())
1178}
1179
1180#[cfg(test)]
1181#[path = "tls_tests.rs"]
1182mod tests;