Skip to main content

ant_quic/crypto/
certificate_manager.rs

1// Copyright 2024 Saorsa Labs Ltd.
2//
3// This Saorsa Network Software is licensed under the General Public License (GPL), version 3.
4// Please see the file LICENSE-GPL, or visit <http://www.gnu.org/licenses/> for the full text.
5//
6// Full details available at https://saorsalabs.com/licenses
7#![allow(missing_docs)]
8
9//! Production-ready certificate management for ant-quic
10//!
11//! This module provides comprehensive certificate management functionality including:
12//! - Self-signed certificate generation for development/testing
13//! - Certificate validation and chain verification
14//! - External certificate loading (PEM, PKCS#12)
15//! - Certificate rotation and renewal mechanisms
16//! - CA certificate management for bootstrap node verification
17
18use rustls::pki_types::{CertificateDer, PrivateKeyDer};
19use std::{sync::Arc, time::Duration};
20use thiserror::Error;
21
22/// Certificate management errors
23#[derive(Error, Debug)]
24pub enum CertificateError {
25    #[error("Certificate generation failed: {0}")]
26    GenerationFailed(String),
27
28    #[error("Certificate validation failed: {0}")]
29    ValidationFailed(String),
30
31    #[error("Certificate loading failed: {0}")]
32    LoadingFailed(String),
33
34    #[error("Certificate parsing failed: {0}")]
35    ParsingFailed(String),
36
37    #[error("Private key error: {0}")]
38    PrivateKeyError(String),
39
40    #[error("Certificate chain error: {0}")]
41    ChainError(String),
42
43    #[error("Certificate expired or not yet valid")]
44    ValidityError,
45
46    #[error("Unsupported certificate format")]
47    UnsupportedFormat,
48}
49
50/// Certificate configuration for different deployment scenarios
51#[derive(Debug, Clone)]
52pub struct CertificateConfig {
53    /// Common name for the certificate (typically the hostname or peer ID)
54    pub common_name: String,
55
56    /// Subject alternative names (SANs) for the certificate
57    pub subject_alt_names: Vec<String>,
58
59    /// Certificate validity duration
60    pub validity_duration: Duration,
61
62    /// Key algorithm and size
63    pub key_algorithm: KeyAlgorithm,
64
65    /// Whether to generate self-signed certificates
66    pub self_signed: bool,
67
68    /// CA certificate path (for validation)
69    pub ca_cert_path: Option<String>,
70
71    /// Certificate chain validation requirements
72    pub require_chain_validation: bool,
73}
74
75/// Supported key algorithms for certificate generation
76#[derive(Debug, Clone, Copy)]
77pub enum KeyAlgorithm {
78    /// RSA with specified key size (2048, 3072, 4096)
79    Rsa(u32),
80    /// ECDSA with P-256 curve
81    EcdsaP256,
82    /// ECDSA with P-384 curve  
83    EcdsaP384,
84    /// Ed25519 (recommended for new deployments)
85    Ed25519,
86}
87
88/// Certificate and private key pair
89#[derive(Debug)]
90pub struct CertificateBundle {
91    /// X.509 certificate chain
92    pub cert_chain: Vec<CertificateDer<'static>>,
93
94    /// Private key corresponding to the certificate
95    pub private_key: PrivateKeyDer<'static>,
96
97    /// Certificate creation timestamp
98    pub created_at: std::time::SystemTime,
99
100    /// Certificate expiration timestamp
101    pub expires_at: std::time::SystemTime,
102}
103
104/// Production-ready certificate manager
105pub struct CertificateManager {
106    config: CertificateConfig,
107    ca_certs: Vec<CertificateDer<'static>>,
108}
109
110impl Default for CertificateConfig {
111    fn default() -> Self {
112        Self {
113            common_name: "ant-quic-node".to_string(),
114            subject_alt_names: vec!["localhost".to_string()],
115            validity_duration: Duration::from_secs(365 * 24 * 60 * 60), // 1 year
116            key_algorithm: KeyAlgorithm::Ed25519,
117            self_signed: true,
118            ca_cert_path: None,
119            require_chain_validation: false,
120        }
121    }
122}
123
124impl CertificateManager {
125    /// Create a new certificate manager with the given configuration
126    pub fn new(config: CertificateConfig) -> Result<Self, CertificateError> {
127        let ca_certs = if let Some(ca_path) = &config.ca_cert_path {
128            Self::load_ca_certificates(ca_path)?
129        } else {
130            Vec::new()
131        };
132
133        Ok(Self { config, ca_certs })
134    }
135
136    /// Generate a new certificate bundle using rcgen
137    pub fn generate_certificate(&self) -> Result<CertificateBundle, CertificateError> {
138        use rcgen::generate_simple_self_signed;
139
140        // For now, use a simplified approach with the rcgen API
141        // This generates a basic self-signed certificate
142        let subject_alt_names = vec![self.config.common_name.clone()];
143        let cert = generate_simple_self_signed(subject_alt_names)
144            .map_err(|e| CertificateError::GenerationFailed(e.to_string()))?;
145
146        // Serialize certificate and key
147        let cert_der = cert.cert.der();
148        let private_key_der = cert.signing_key.serialize_der();
149
150        let created_at = std::time::SystemTime::now();
151        let expires_at = created_at + self.config.validity_duration;
152
153        Ok(CertificateBundle {
154            cert_chain: vec![cert_der.clone()],
155            private_key: PrivateKeyDer::try_from(private_key_der).map_err(|e| {
156                CertificateError::PrivateKeyError(format!("Key conversion failed: {e:?}"))
157            })?,
158            created_at,
159            expires_at,
160        })
161    }
162
163    /// Load certificates from PEM file
164    pub fn load_certificate_from_pem(
165        cert_path: &str,
166        key_path: &str,
167    ) -> Result<CertificateBundle, CertificateError> {
168        use rustls_pemfile::{certs, private_key};
169
170        // Load certificate file
171        let cert_file = std::fs::File::open(cert_path).map_err(|e| {
172            CertificateError::LoadingFailed(format!("Failed to open cert file: {e}"))
173        })?;
174
175        let mut cert_reader = std::io::BufReader::new(cert_file);
176        let cert_chain: Vec<CertificateDer<'static>> = certs(&mut cert_reader)
177            .collect::<Result<Vec<_>, _>>()
178            .map_err(|e| {
179                CertificateError::ParsingFailed(format!("Failed to parse certificates: {e}"))
180            })?;
181
182        if cert_chain.is_empty() {
183            return Err(CertificateError::LoadingFailed(
184                "No certificates found in file".to_string(),
185            ));
186        }
187
188        // Load private key file
189        let key_file = std::fs::File::open(key_path).map_err(|e| {
190            CertificateError::LoadingFailed(format!("Failed to open key file: {e}"))
191        })?;
192
193        let mut key_reader = std::io::BufReader::new(key_file);
194        let private_key = private_key(&mut key_reader)
195            .map_err(|e| {
196                CertificateError::ParsingFailed(format!("Failed to parse private key: {e}"))
197            })?
198            .ok_or_else(|| {
199                CertificateError::LoadingFailed("No private key found in file".to_string())
200            })?;
201
202        // Extract validity information from the first certificate
203        let (created_at, expires_at) = Self::extract_validity_from_cert(&cert_chain[0])?;
204
205        Ok(CertificateBundle {
206            cert_chain,
207            private_key,
208            created_at,
209            expires_at,
210        })
211    }
212
213    /// Validate a certificate bundle
214    pub fn validate_certificate(&self, bundle: &CertificateBundle) -> Result<(), CertificateError> {
215        // Check if certificate has expired
216        let now = std::time::SystemTime::now();
217        if now > bundle.expires_at {
218            return Err(CertificateError::ValidityError);
219        }
220
221        // If chain validation is required, perform it
222        if self.config.require_chain_validation && !self.ca_certs.is_empty() {
223            self.validate_certificate_chain(&bundle.cert_chain)?;
224        }
225
226        Ok(())
227    }
228
229    /// Create a server configuration from a certificate bundle
230    pub fn create_server_config(
231        &self,
232        bundle: &CertificateBundle,
233    ) -> Result<Arc<rustls::ServerConfig>, CertificateError> {
234        use rustls::ServerConfig;
235
236        self.validate_certificate(bundle)?;
237
238        let server_config = ServerConfig::builder()
239            .with_no_client_auth()
240            .with_single_cert(bundle.cert_chain.clone(), bundle.private_key.clone_key())
241            .map_err(|e| CertificateError::ValidationFailed(e.to_string()))?;
242
243        Ok(Arc::new(server_config))
244    }
245
246    /// Create a client configuration with optional certificate verification
247    pub fn create_client_config(&self) -> Result<Arc<rustls::ClientConfig>, CertificateError> {
248        use rustls::ClientConfig;
249
250        let config = if self.ca_certs.is_empty() {
251            // For development/testing - accept any certificate
252            ClientConfig::builder()
253                .dangerous()
254                .with_custom_certificate_verifier(Arc::new(NoCertificateVerifier))
255                .with_no_client_auth()
256        } else {
257            // Production - use provided CA certificates
258            let mut root_store = rustls::RootCertStore::empty();
259            for ca_cert in &self.ca_certs {
260                root_store.add(ca_cert.clone()).map_err(|e| {
261                    CertificateError::ValidationFailed(format!("Failed to add CA cert: {e}"))
262                })?;
263            }
264
265            ClientConfig::builder()
266                .with_root_certificates(root_store)
267                .with_no_client_auth()
268        };
269
270        Ok(Arc::new(config))
271    }
272
273    /// Load CA certificates from a file
274    fn load_ca_certificates(
275        ca_path: &str,
276    ) -> Result<Vec<CertificateDer<'static>>, CertificateError> {
277        use rustls_pemfile::certs;
278
279        let ca_file = std::fs::File::open(ca_path)
280            .map_err(|e| CertificateError::LoadingFailed(format!("Failed to open CA file: {e}")))?;
281
282        let mut ca_reader = std::io::BufReader::new(ca_file);
283        let ca_certs: Vec<CertificateDer<'static>> = certs(&mut ca_reader)
284            .collect::<Result<Vec<_>, _>>()
285            .map_err(|e| {
286                CertificateError::ParsingFailed(format!("Failed to parse CA certificates: {e}"))
287            })?;
288
289        if ca_certs.is_empty() {
290            return Err(CertificateError::LoadingFailed(
291                "No CA certificates found".to_string(),
292            ));
293        }
294
295        Ok(ca_certs)
296    }
297
298    /// Extract validity information from a certificate
299    fn extract_validity_from_cert(
300        _cert: &CertificateDer<'static>,
301    ) -> Result<(std::time::SystemTime, std::time::SystemTime), CertificateError> {
302        // For now, return reasonable defaults
303        // In a full implementation, you'd parse the certificate to extract actual validity
304        let created_at = std::time::SystemTime::now();
305        let expires_at = created_at + Duration::from_secs(365 * 24 * 60 * 60); // 1 year
306
307        Ok((created_at, expires_at))
308    }
309
310    /// Validate certificate chain against CA certificates
311    fn validate_certificate_chain(
312        &self,
313        cert_chain: &[CertificateDer<'static>],
314    ) -> Result<(), CertificateError> {
315        if cert_chain.is_empty() {
316            return Err(CertificateError::ChainError(
317                "Empty certificate chain".to_string(),
318            ));
319        }
320
321        // For now, basic validation - in production you'd use a proper chain validator
322        // This would involve checking signatures, validity periods, extensions, etc.
323
324        Ok(())
325    }
326}
327
328/// Certificate verifier that accepts any certificate (for development/testing only)
329#[derive(Debug)]
330struct NoCertificateVerifier;
331
332impl rustls::client::danger::ServerCertVerifier for NoCertificateVerifier {
333    fn verify_server_cert(
334        &self,
335        _end_entity: &CertificateDer<'_>,
336        _intermediates: &[CertificateDer<'_>],
337        _server_name: &rustls::pki_types::ServerName<'_>,
338        _ocsp_response: &[u8],
339        _now: rustls::pki_types::UnixTime,
340    ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
341        Ok(rustls::client::danger::ServerCertVerified::assertion())
342    }
343
344    fn verify_tls12_signature(
345        &self,
346        _message: &[u8],
347        _cert: &CertificateDer<'_>,
348        _dss: &rustls::DigitallySignedStruct,
349    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
350        Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
351    }
352
353    fn verify_tls13_signature(
354        &self,
355        _message: &[u8],
356        _cert: &CertificateDer<'_>,
357        _dss: &rustls::DigitallySignedStruct,
358    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
359        Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
360    }
361
362    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
363        // v0.2: Pure PQC - only ML-DSA-65 (IANA 0x0905)
364        vec![rustls::SignatureScheme::ML_DSA_65]
365    }
366}
367
368impl CertificateBundle {
369    /// Check if the certificate is expired or will expire within the given duration
370    pub fn expires_within(&self, duration: Duration) -> bool {
371        let now = std::time::SystemTime::now();
372        match now.checked_add(duration) {
373            Some(check_time) => check_time >= self.expires_at,
374            None => true, // Overflow, assume will expire
375        }
376    }
377
378    /// Get the remaining validity duration
379    pub fn remaining_validity(&self) -> Option<Duration> {
380        std::time::SystemTime::now()
381            .duration_since(self.expires_at)
382            .ok()
383    }
384}
385
386#[cfg(test)]
387mod tests {
388    use super::*;
389
390    #[test]
391    fn test_default_certificate_config() {
392        let config = CertificateConfig::default();
393        assert_eq!(config.common_name, "ant-quic-node");
394        assert_eq!(config.subject_alt_names, vec!["localhost"]);
395        assert!(config.self_signed);
396        assert!(!config.require_chain_validation);
397    }
398
399    #[test]
400    fn test_certificate_manager_creation() {
401        let config = CertificateConfig::default();
402        let manager = CertificateManager::new(config);
403        assert!(manager.is_ok());
404    }
405
406    #[test]
407    fn test_certificate_generation() {
408        let config = CertificateConfig::default();
409        let manager = CertificateManager::new(config).unwrap();
410
411        let bundle = manager.generate_certificate();
412        assert!(bundle.is_ok());
413
414        let bundle = bundle.unwrap();
415        assert!(!bundle.cert_chain.is_empty());
416        assert!(bundle.expires_at > bundle.created_at);
417    }
418
419    #[test]
420    fn test_certificate_bundle_expiry_check() {
421        // Create a dummy PKCS#8 private key structure for testing
422        // This is a minimal valid PKCS#8 structure with an Ed25519 OID
423        let dummy_key = vec![
424            0x30, 0x2e, // SEQUENCE (46 bytes)
425            0x02, 0x01, 0x00, // INTEGER version 0
426            0x30, 0x05, // SEQUENCE (5 bytes) - AlgorithmIdentifier
427            0x06, 0x03, 0x2b, 0x65, 0x70, // OID 1.3.101.112 (Ed25519)
428            0x04, 0x22, // OCTET STRING (34 bytes) - PrivateKey
429            0x04, 0x20, // OCTET STRING (32 bytes) - actual key
430            // 32 bytes of dummy key data
431            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
432            0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
433            0x1c, 0x1d, 0x1e, 0x1f,
434        ];
435
436        let bundle = CertificateBundle {
437            cert_chain: vec![],
438            private_key: PrivateKeyDer::try_from(dummy_key).unwrap(),
439            created_at: std::time::SystemTime::now(),
440            expires_at: std::time::SystemTime::now() + Duration::from_secs(3600), // 1 hour
441        };
442
443        assert!(!bundle.expires_within(Duration::from_secs(1800))); // 30 minutes
444        assert!(bundle.expires_within(Duration::from_secs(7200))); // 2 hours
445    }
446}