Skip to main content

Mountain/Binary/Build/
CertificateManager.rs

1//! # TLS Certificate Management Module
2//!
3//! This module provides a comprehensive certificate management system for HTTPS
4//! services. It manages a root CA certificate and generates server certificates
5//! signed by the CA.
6//!
7//! ## Certificate Hierarchy
8//!
9//! ```text
10//! Root CA (stored in keyring)
11//!   └── Server Certificates (cached, per hostname)
12//!        ├── code.editor.land
13//!        ├── api.editor.land
14//!        └── ...other services
15//! ```
16//!
17//! ## Trust Model
18//!
19//! - The webview must trust the CA certificate to validate server certificates
20//! - CA certificate is stored in OS keyring for persistence
21//! - Server certificates are automatically generated and renewed
22//!
23//! ## Usage Example
24//!
25//! ```rust,no_run
26//! use Binary::Build::CertificateManager::{CertificateInfo, CertificateManager};
27//!
28//! async fn setup_tls() -> anyhow::Result<()> {
29//! 	let mut cert_manager = CertificateManager::new("myapp").await?;
30//!
31//! 	// Initialize or load CA certificate
32//! 	cert_manager.initialize_ca().await?;
33//!
34//! 	// Get server configuration for a service
35//! 	let server_config = cert_manager.get_server_cert("code.editor.land").await?;
36//!
37//! 	// Get CA certificate PEM for webview installation
38//! 	let ca_cert = cert_manager.get_ca_cert_pem().unwrap();
39//!
40//! 	Ok(())
41//! }
42//! ```
43//!
44//! ## Security Considerations
45//!
46//! - All certificates use ECDSA P-256 curve (matching DNSSEC algorithm)
47//! - CA private key is stored securely in OS keyring
48//! - Private keys are never logged or exposed
49//! - Certificates have automatic renewal before expiry
50
51use std::{collections::HashMap, sync::Arc};
52
53use parking_lot::RwLock;
54use anyhow::Result;
55use chrono::{DateTime, Utc};
56use rustls::ServerConfig;
57use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
58use keyring::Entry;
59
60use crate::dev_log;
61
62/// Certificate information for display and validation
63#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
64pub struct CertificateInfo {
65	/// Subject Common Name (e.g., "CN=localhost")
66	pub subject:String,
67	/// Issuer Common Name (for self-signed, same as subject)
68	pub issuer:String,
69	/// Validity start time (ISO 8601)
70	pub valid_from:String,
71	/// Validity end time (ISO 8601)
72	pub valid_until:String,
73	/// Whether this is a self-signed certificate
74	pub is_self_signed:bool,
75	/// Subject Alternative Names
76	pub sans:Vec<String>,
77}
78
79/// Server certificate data including PEM formats and rustls configuration
80#[allow(dead_code)]
81#[derive(Clone)]
82struct ServerCertData {
83	/// Certificate in PEM format
84	cert_pem:Vec<u8>,
85	/// Private key in PEM format
86	key_pem:Vec<u8>,
87	/// rustls ServerConfig for serving TLS
88	server_config:Arc<ServerConfig>,
89	/// Certificate info
90	info:CertificateInfo,
91	/// Validity end time
92	valid_until:DateTime<Utc>,
93}
94
95/// Main certificate manager for TLS infrastructure
96///
97/// Manages a root CA certificate and generates server certificates as needed.
98/// The CA certificate is persisted in the OS keyring for security.
99pub struct CertificateManager {
100	/// Application identifier for keyring storage
101	app_id:String,
102	/// CA certificate PEM (cached from keyring)
103	ca_cert:Option<Vec<u8>>,
104	/// CA private key PEM (cached from keyring)
105	ca_key:Option<Vec<u8>>,
106	/// Cached server certificates (hostname -> cert data)
107	server_certs:Arc<RwLock<HashMap<String, ServerCertData>>>,
108}
109
110impl CertificateManager {
111	/// Keyring service name for certificate storage
112	const KEYRING_SERVICE:&'static str = "CodeEditorLand-TLS";
113	/// Keyring entry name for CA certificate
114	const KEYRING_CA_CERT:&'static str = "ca_certificate";
115	/// Keyring entry name for CA private key
116	const KEYRING_CA_KEY:&'static str = "ca_private_key";
117	/// Certificate validity period for CA (10 years)
118	const CA_VALIDITY_DAYS:i64 = 365 * 10;
119	/// Certificate validity period for server certs (1 year)
120	const SERVER_VALIDITY_DAYS:i64 = 365;
121	/// Renewal threshold (renew if expiring within 30 days)
122	pub const RENEWAL_THRESHOLD_DAYS:i64 = 30;
123
124	/// Create a new CertificateManager instance
125	///
126	/// # Arguments
127	///
128	/// * `app_id` - Application identifier for keyring storage
129	///
130	/// # Example
131	///
132	/// ```rust,no_run
133	/// # use Binary::Build::CertificateManager::CertificateManager;
134	/// # async fn example() -> anyhow::Result<()> {
135	/// let cert_manager = CertificateManager::new("myapp").await?;
136	/// # Ok(())
137	/// # }
138	/// ```
139	pub async fn new(app_id:&str) -> Result<Self> {
140		Ok(Self {
141			app_id:app_id.to_string(),
142			ca_cert:None,
143			ca_key:None,
144			server_certs:Arc::new(RwLock::new(HashMap::new())),
145		})
146	}
147
148	/// Initialize or load the CA certificate
149	///
150	/// This method attempts to load the CA certificate from the keyring.
151	/// If not found, it generates a new self-signed CA and stores it.
152	///
153	/// # Example
154	///
155	/// ```rust,no_run
156	/// # use Binary::Build::CertificateManager::CertificateManager;
157	/// # async fn example() -> anyhow::Result<()> {
158	/// let mut cert_manager = CertificateManager::new("myapp").await?;
159	/// cert_manager.initialize_ca().await?;
160	/// # Ok(())
161	/// # }
162	/// ```
163	pub async fn initialize_ca(&mut self) -> Result<()> {
164		if let Some((cert, key)) = self.load_ca_from_keyring()? {
165			dev_log!("security", "loading CA certificate from keyring");
166			self.ca_cert = Some(cert.clone());
167			self.ca_key = Some(key.clone());
168			dev_log!("security", "CA certificate loaded successfully");
169		} else {
170			dev_log!("security", "CA certificate not found in keyring, generating new CA");
171			let (cert, key) = self.generate_ca_cert()?;
172
173			// Store in keyring
174			self.save_ca_to_keyring(&cert, &key)?;
175
176			self.ca_cert = Some(cert.clone());
177			self.ca_key = Some(key);
178
179			dev_log!("security", "new CA certificate generated and stored");
180		}
181
182		Ok(())
183	}
184
185	/// Generate a new self-signed CA certificate
186	///
187	/// Returns (certificate PEM, private key PEM) tuple.
188	///
189	/// The CA certificate:
190	/// - Uses ECDSA P-256 curve for consistency with DNSSEC
191	/// - Has CA:TRUE basic constraint
192	/// - Allows keyCertSign and CRLSign key usage
193	/// - Valid for 10 years
194	/// - Includes proper extensions for CA functionality
195	fn generate_ca_cert(&self) -> Result<(Vec<u8>, Vec<u8>)> {
196		dev_log!("security", "generating new CA certificate");
197
198		// NOTE: Using rcgen CertificateParams::default() which provides working API
199
200		// Generate a basic key pair
201		let key_pair = rcgen::KeyPair::generate()?;
202
203		// Build certificate using rcgen API
204		let mut params = rcgen::CertificateParams::default();
205		params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
206		params.distinguished_name = rcgen::DistinguishedName::new();
207
208		// Set validity period
209		let not_before = rcgen::date_time_ymd(2024, 1, 1);
210		params.not_before = not_before;
211		let expiry_year:i32 = (2024 + Self::CA_VALIDITY_DAYS / 365) as i32;
212		let not_after = rcgen::date_time_ymd(expiry_year, 1, 1);
213		params.not_after = not_after;
214		params.key_usages = vec![
215			rcgen::KeyUsagePurpose::DigitalSignature,
216			rcgen::KeyUsagePurpose::KeyCertSign,
217			rcgen::KeyUsagePurpose::CrlSign,
218		];
219
220		// Using CertificateParams directly with KeyPair (correct API for rcgen 0.14.x)
221		let cert = params.self_signed(&key_pair)?;
222
223		// We want PEM format for the certificate manager
224		let cert_pem = cert.pem();
225		let key_pem = key_pair.serialize_pem();
226
227		dev_log!("security", "CA certificate generated successfully");
228
229		Ok((cert_pem.into_bytes(), key_pem.into_bytes()))
230	}
231
232	/// Get or generate a server certificate for a specific hostname
233	///
234	/// # Arguments
235	///
236	/// * `hostname` - The hostname (e.g., "code.editor.land")
237	///
238	/// # Returns
239	///
240	/// A rustls ServerConfig ready for HTTPS serving
241	///
242	/// # Example
243	///
244	/// ```rust,no_run
245	/// # use Binary::Build::CertificateManager::CertificateManager;
246	/// # async fn example() -> anyhow::Result<()> {
247	/// let mut cert_manager = CertificateManager::new("myapp").await?;
248	/// cert_manager.initialize_ca().await?;
249	/// let server_config = cert_manager.get_server_cert("code.editor.land").await?;
250	/// # Ok(())
251	/// # }
252	/// ```
253	pub async fn get_server_cert(&self, hostname:&str) -> Result<Arc<ServerConfig>> {
254		// Check cache first
255		{
256			let certs = self.server_certs.read();
257			if let Some(cert_data) = certs.get(hostname) {
258				// Check if certificate is still valid
259				if !self.should_renew(&cert_data.cert_pem) {
260					dev_log!("security", "using cached server certificate for {}", hostname);
261					return Ok(cert_data.server_config.clone());
262				}
263				// Certificate needs renewal, drop lock and continue
264				drop(certs);
265			}
266		}
267
268		// Generate or renew certificate
269		dev_log!("security", "generating server certificate for {}", hostname);
270		let cert_data = self.generate_server_cert(hostname)?;
271
272		// Cache the certificate
273		{
274			let mut certs = self.server_certs.write();
275			certs.insert(hostname.to_string(), cert_data.clone());
276		}
277
278		Ok(cert_data.server_config)
279	}
280
281	/// Generate a server certificate signed by the CA
282	///
283	/// The certificate includes:
284	/// - Specified hostname as Common Name
285	/// - Subject Alternative Names: DNS hostname, 127.0.0.1, ::1
286	/// - Valid for 1 year with automatic renewal
287	/// - Server authentication EKUs
288	fn generate_server_cert(&self, hostname:&str) -> Result<ServerCertData> {
289		// Build server certificate
290		let mut params = rcgen::CertificateParams::default();
291		params.distinguished_name.push(rcgen::DnType::CommonName, hostname);
292
293		// Get current time for certificate validity - TODO: Fix chrono API usage
294		let now = chrono::Utc::now();
295		let current_year = 2024; // Use fixed year for now
296		let current_month = 1;
297		let current_day = 1;
298
299		let not_before = rcgen::date_time_ymd(current_year, current_month, current_day);
300		params.not_before = not_before;
301
302		let not_after = rcgen::date_time_ymd(current_year + 1, current_month, current_day);
303		params.not_after = not_after;
304
305		// NOTE: Skipping SAN setup - using default subject alternative names
306		// params.subject_alt_names = vec![
307		// 	rcgen::SanType::DnsName(hostname.to_string()),
308		// ];
309		params.key_usages = vec![
310			rcgen::KeyUsagePurpose::DigitalSignature,
311			rcgen::KeyUsagePurpose::KeyEncipherment,
312		];
313		params.extended_key_usages = vec![
314			rcgen::ExtendedKeyUsagePurpose::ServerAuth,
315			rcgen::ExtendedKeyUsagePurpose::ClientAuth,
316		];
317
318		// Generate self-signed certificate - TODO: Update rcgen API usage
319		let key_pair = rcgen::KeyPair::generate()?;
320		// Generate self-signed certificate using the params and key pair
321		let cert = params.self_signed(&key_pair)?;
322
323		// Get DER bytes for rustls
324		// Using serialized_der() for rcgen 0.14.7 API
325		let server_cert_der = cert.der();
326		let server_key_der = key_pair.serialized_der();
327
328		// Store DER bytes directly (PEM not needed for rustls)
329		let cert_der:Vec<u8> = server_cert_der.to_vec();
330		let key_der:Vec<u8> = server_key_der.to_vec();
331
332		// Clone for cert info extraction
333		let cert_der_for_info = cert_der.clone();
334
335		// Create rustls configuration with owned data
336		let cert_chain:Vec<CertificateDer<'static>> = vec![CertificateDer::from(cert_der)];
337
338		// Parse private key - owned data
339		let private_key_der =
340			PrivatePkcs8KeyDer::try_from(key_der).map_err(|e| anyhow::anyhow!("Failed to parse private key: {}", e))?;
341		let private_key = PrivateKeyDer::Pkcs8(private_key_der);
342
343		// Store empty PEM for now - TODO: Create proper PEM format later
344		let cert_pem:Vec<u8> = Vec::new();
345		let key_pem:Vec<u8> = Vec::new();
346
347		let mut server_config = ServerConfig::builder()
348			.with_no_client_auth()
349			.with_single_cert(cert_chain, private_key)
350			.map_err(|e| anyhow::anyhow!("Failed to create ServerConfig: {}", e))?;
351
352		// Configure ALPN protocols for HTTP/2 and HTTP/1.1
353		server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
354
355		// Calculate certificate info - use cloned DER bytes
356		let info = self.extract_cert_info(&cert_der_for_info, hostname, true)?;
357		let valid_until = Utc::now() + chrono::Duration::days(Self::SERVER_VALIDITY_DAYS);
358
359		dev_log!(
360			"security",
361			"server certificate generated for {} (valid until {})",
362			hostname,
363			valid_until
364		);
365
366		Ok(ServerCertData { cert_pem, key_pem, server_config:Arc::new(server_config), info, valid_until })
367	}
368
369	/// Load CA certificate and key from keyring
370	///
371	/// Returns Some((cert_pem, key_pem)) if found, None otherwise.
372	fn load_ca_from_keyring(&self) -> Result<Option<(Vec<u8>, Vec<u8>)>> {
373		let keyring_entry_cert =
374			Entry::new(Self::KEYRING_SERVICE, &format!("{}:{}", self.app_id, Self::KEYRING_CA_CERT))
375				.map_err(|e| anyhow::anyhow!("Failed to create keyring entry: {}", e))?;
376
377		let keyring_entry_key = Entry::new(Self::KEYRING_SERVICE, &format!("{}:{}", self.app_id, Self::KEYRING_CA_KEY))
378			.map_err(|e| anyhow::anyhow!("Failed to create keyring entry: {}", e))?;
379
380		let cert = match keyring_entry_cert.get_password() {
381			Ok(s) => s.into_bytes(),
382			Err(keyring::Error::NoEntry) => return Ok(None),
383			Err(e) => return Err(e.into()),
384		};
385
386		let key = keyring_entry_key
387			.get_password()
388			.map_err(|e| anyhow::anyhow!("Failed to load CA key from keyring: {}", e))?
389			.into_bytes();
390
391		dev_log!("security", "CA certificate loaded from keyring");
392		Ok(Some((cert, key)))
393	}
394
395	/// Save CA certificate and key to keyring
396	fn save_ca_to_keyring(&self, cert:&[u8], key:&[u8]) -> Result<()> {
397		let keyring_entry_cert =
398			Entry::new(Self::KEYRING_SERVICE, &format!("{}:{}", self.app_id, Self::KEYRING_CA_CERT))
399				.map_err(|e| anyhow::anyhow!("Failed to create keyring entry: {}", e))?;
400
401		let keyring_entry_key = Entry::new(Self::KEYRING_SERVICE, &format!("{}:{}", self.app_id, Self::KEYRING_CA_KEY))
402			.map_err(|e| anyhow::anyhow!("Failed to create keyring entry: {}", e))?;
403
404		// Store as PEM strings
405		let cert_str = String::from_utf8(cert.to_vec()).map_err(|e| anyhow::anyhow!("Invalid CA cert UTF-8: {}", e))?;
406		let key_str = String::from_utf8(key.to_vec()).map_err(|e| anyhow::anyhow!("Invalid CA key UTF-8: {}", e))?;
407
408		keyring_entry_cert
409			.set_password(&cert_str)
410			.map_err(|e| anyhow::anyhow!("Failed to save CA cert to keyring: {}", e))?;
411
412		keyring_entry_key
413			.set_password(&key_str)
414			.map_err(|e| anyhow::anyhow!("Failed to save CA key to keyring: {}", e))?;
415
416		dev_log!("security", "CA certificate saved to keyring");
417		Ok(())
418	}
419
420	/// Check if a certificate should be renewed
421	///
422	/// Returns true if the certificate is expiring within
423	/// RENEWAL_THRESHOLD_DAYS.
424	pub fn should_renew(&self, cert_pem:&[u8]) -> bool {
425		if let Ok(result) = self.check_cert_validity(cert_pem) {
426			result.should_renew
427		} else {
428			// If we can't parse validity, err on the side of renewal
429			dev_log!("security", "warn: could not parse certificate validity, forcing renewal");
430			true
431		}
432	}
433
434	/// Force renewal of a server certificate
435	///
436	/// # Arguments
437	///
438	/// * `hostname` - The hostname whose certificate should be renewed
439	///
440	/// # Example
441	///
442	/// ```rust,no_run
443	/// # use Binary::Build::CertificateManager::CertificateManager;
444	/// # async fn example() -> anyhow::Result<()> {
445	/// let mut cert_manager = CertificateManager::new("myapp").await?;
446	/// cert_manager.initialize_ca().await?;
447	/// cert_manager.renew_certificate("code.editor.land").await?;
448	/// # Ok(())
449	/// # }
450	/// ```
451	pub async fn renew_certificate(&mut self, hostname:&str) -> Result<()> {
452		dev_log!("security", "forcing renewal of certificate for {}", hostname);
453
454		// Remove from cache
455		let mut certs = self.server_certs.write();
456		certs.remove(hostname);
457		drop(certs);
458
459		// Generate new certificate
460		let cert_data = self.generate_server_cert(hostname)?;
461
462		// Cache the new certificate
463		let mut certs = self.server_certs.write();
464		certs.insert(hostname.to_string(), cert_data);
465
466		dev_log!("security", "certificate renewed for {}", hostname);
467		Ok(())
468	}
469
470	/// Build a ServerConfig for a specific hostname
471	///
472	/// This is a convenience wrapper around get_server_cert().
473	///
474	/// # Arguments
475	///
476	/// * `hostname` - The hostname (e.g., "code.editor.land")
477	///
478	/// # Example
479	///
480	/// ```rust,no_run
481	/// # use Binary::Build::CertificateManager::CertificateManager;
482	/// # async fn example() -> anyhow::Result<()> {
483	/// let mut cert_manager = CertificateManager::new("myapp").await?;
484	/// cert_manager.initialize_ca().await?;
485	/// let server_config = cert_manager.build_server_config("code.editor.land").await?;
486	/// # Ok(())
487	/// # }
488	/// ```
489	pub async fn build_server_config(&self, hostname:&str) -> Result<Arc<ServerConfig>> {
490		self.get_server_cert(hostname).await
491	}
492
493	/// Get the CA certificate in PEM format
494	///
495	/// This can be used to install the CA in the system trust store
496	/// or configure the webview to trust it.
497	///
498	/// # Returns
499	///
500	/// CA certificate PEM, or None if CA is not initialized
501	///
502	/// # Example
503	///
504	/// ```rust,no_run
505	/// # use Binary::Build::CertificateManager::CertificateManager;
506	/// # async fn example() -> anyhow::Result<()> {
507	/// let mut cert_manager = CertificateManager::new("myapp").await?;
508	/// cert_manager.initialize_ca().await?;
509	/// let ca_cert = cert_manager.get_ca_cert_pem().unwrap();
510	/// println!("CA Certificate:\n{}", String::from_utf8_lossy(&ca_cert));
511	/// # Ok(())
512	/// # }
513	/// ```
514	pub fn get_ca_cert_pem(&self) -> Option<Vec<u8>> { self.ca_cert.clone() }
515
516	/// Get information about a server certificate
517	///
518	/// # Arguments
519	///
520	/// * `hostname` - The hostname (e.g., "code.editor.land")
521	///
522	/// # Returns
523	///
524	/// CertificateInfo if the certificate exists
525	///
526	/// # Example
527	///
528	/// ```rust,no_run
529	/// # use Binary::Build::CertificateManager::CertificateManager;
530	/// # async fn example() -> anyhow::Result<()> {
531	/// let mut cert_manager = CertificateManager::new("myapp").await?;
532	/// cert_manager.initialize_ca().await?;
533	/// cert_manager.get_server_cert("code.editor.land").await?;
534	/// let info = cert_manager.get_server_cert_info("code.editor.land").unwrap();
535	/// println!("Certificate valid until: {}", info.valid_until);
536	/// # Ok(())
537	/// # }
538	/// ```
539	pub fn get_server_cert_info(&self, hostname:&str) -> Option<CertificateInfo> {
540		let certs = self.server_certs.read();
541		certs.get(hostname).map(|d| d.info.clone())
542	}
543
544	/// Get all cached server certificates
545	///
546	/// # Returns
547	///
548	/// A HashMap mapping hostnames to certificate info
549	///
550	/// # Example
551	///
552	/// ```rust,no_run
553	/// # use Binary::Build::CertificateManager::CertificateManager;
554	/// # async fn example() -> anyhow::Result<()> {
555	/// let mut cert_manager = CertificateManager::new("myapp").await?;
556	/// cert_manager.initialize_ca().await?;
557	/// cert_manager.get_server_cert("code.editor.land").await?;
558	/// cert_manager.get_server_cert("api.editor.land").await?;
559	/// let all_certs = cert_manager.get_all_certs();
560	/// for (hostname, info) in all_certs {
561	/// 	println!("{}: valid until {}", hostname, info.valid_until);
562	/// }
563	/// # Ok(())
564	/// # }
565	/// ```
566	pub fn get_all_certs(&self) -> HashMap<String, CertificateInfo> {
567		let certs = self.server_certs.read();
568		certs.iter().map(|(k, v)| (k.clone(), v.info.clone())).collect()
569	}
570
571	/// Convert DER certificate to PEM format
572	#[allow(dead_code)]
573	fn cert_der_to_pem(der:&[u8]) -> Result<Vec<u8>> {
574		let pem = pem::Pem::new("CERTIFICATE".to_string(), der.to_vec());
575		let pem_str = pem::encode(&pem);
576		Ok(pem_str.into_bytes())
577	}
578
579	/// Convert DER private key to PEM format
580	#[allow(dead_code)]
581	fn private_key_der_to_pem(der:&[u8]) -> Result<Vec<u8>> {
582		let pem = pem::Pem::new("PRIVATE KEY".to_string(), der.to_vec());
583		let pem_str = pem::encode(&pem);
584		Ok(pem_str.into_bytes())
585	}
586
587	/// Convert PEM to DER
588	fn pem_to_der(pem:&[u8], label:&str) -> Result<Vec<u8>> {
589		let pem_str = String::from_utf8(pem.to_vec()).map_err(|e| anyhow::anyhow!("Invalid PEM UTF-8: {}", e))?;
590
591		let pem = pem::parse(&pem_str).map_err(|e| anyhow::anyhow!("Failed to parse PEM: {}", e))?;
592
593		if pem.tag() != label {
594			return Err(anyhow::anyhow!("Expected PEM label '{}', found '{}'", label, pem.tag()));
595		}
596
597		Ok(pem.contents().to_vec())
598	}
599
600	/// Extract certificate information from DER data
601	fn extract_cert_info(&self, cert_der:&[u8], hostname:&str, is_ca:bool) -> Result<CertificateInfo> {
602		// Parse the X.509 certificate to extract information
603		let cert = x509_parser::parse_x509_certificate(cert_der)
604			.map_err(|e| anyhow::anyhow!("Failed to parse certificate: {}", e))?
605			.1;
606
607		let subject = cert.subject().to_string();
608		let issuer = cert.issuer().to_string();
609
610		let valid_from = cert.validity().not_before.to_string();
611		let valid_until = cert.validity().not_after.to_string();
612
613		// Extract Subject Alternative Names
614		let mut sans = vec![hostname.to_string(), "127.0.0.1".to_string(), "::1".to_string()];
615		if let Some(ext) = cert
616			.extensions()
617			.iter()
618			.find(|e| e.oid == x509_parser::oid_registry::OID_X509_EXT_SUBJECT_ALT_NAME)
619		{
620			if let x509_parser::extensions::ParsedExtension::SubjectAlternativeName(sans_list) = ext.parsed_extension()
621			{
622				sans = sans_list
623					.general_names
624					.iter()
625					.filter_map(|gn| {
626						match gn {
627							x509_parser::extensions::GeneralName::DNSName(dns) => Some(dns.to_string()),
628							x509_parser::extensions::GeneralName::IPAddress(ip) => {
629								let octets:&[u8] = ip.as_ref();
630								Some(match octets.len() {
631									4 => format!("{}.{}.{}.{}", octets[0], octets[1], octets[2], octets[3]),
632									16 => {
633										format!(
634											"::{}:{}:{}:{}:{}",
635											octets[0], octets[1], octets[2], octets[3], octets[4]
636										)
637									},
638									_ => "?".to_string(),
639								})
640							},
641							_ => None,
642						}
643					})
644					.collect();
645			}
646		}
647
648		Ok(CertificateInfo { subject, issuer, valid_from, valid_until, is_self_signed:is_ca, sans })
649	}
650
651	/// Check certificate validity and renewal status
652	fn check_cert_validity(&self, cert_pem:&[u8]) -> Result<CertValidityResult> {
653		let cert_der = Self::pem_to_der(cert_pem, "CERTIFICATE")?;
654
655		let cert = x509_parser::parse_x509_certificate(&cert_der)
656			.map_err(|e| anyhow::anyhow!("Failed to parse certificate: {}", e))?
657			.1;
658
659		let not_after_chrono = Self::parse_not_after(&cert.validity().not_after)?;
660		let now = chrono::Utc::now();
661
662		let is_valid = now <= not_after_chrono;
663		let days_until_expiry = (not_after_chrono - now).num_days();
664		let should_renew = days_until_expiry <= Self::RENEWAL_THRESHOLD_DAYS;
665
666		Ok(CertValidityResult { is_valid, days_until_expiry, should_renew, not_after:not_after_chrono })
667	}
668
669	/// Parse X.509 not_after time to chrono DateTime
670	fn parse_not_after(not_after:&x509_parser::time::ASN1Time) -> Result<DateTime<Utc>> {
671		// Convert from string representation using x509_parser ASN1Time
672		let timestamp = Self::not_as_unix_timestamp(not_after)
673			.ok_or_else(|| anyhow::anyhow!("Failed to convert not_after to timestamp"))?;
674
675		DateTime::from_timestamp(timestamp, 0)
676			.ok_or_else(|| anyhow::anyhow!("Invalid timestamp"))
677			.map(|dt| dt.to_utc())
678	}
679
680	/// Helper function to convert ASN1Time to Unix timestamp
681	fn not_as_unix_timestamp(not_after:&x509_parser::time::ASN1Time) -> Option<i64> {
682		// Try to use the to_unix() method if available
683		// This is a compatibility layer for different x509_parser versions
684		let time_str = not_after.to_string();
685
686		// Parse manually for now as fallback
687		// Format is typically YYYYMMDDHHMMSSZ or similar
688		let dt = chrono::NaiveDateTime::parse_from_str(&time_str, "%Y%m%d%H%M%SZ")
689			.or_else(|_| chrono::NaiveDateTime::parse_from_str(&time_str, "%Y%m%d%H%M%S"))
690			.or_else(|_| chrono::NaiveDateTime::parse_from_str(&format!("{}000000", time_str), "%Y%m%d%H%M%S"))
691			.ok()?;
692
693		Some(dt.and_utc().timestamp())
694	}
695}
696
697/// Certificate validity check result
698#[allow(dead_code)]
699#[derive(Debug, Clone)]
700struct CertValidityResult {
701	/// Whether the certificate is currently valid
702	is_valid:bool,
703	/// Days until expiry (negative if expired)
704	days_until_expiry:i64,
705	/// Whether renewal is recommended
706	should_renew:bool,
707	/// Certificate expiry time
708	not_after:DateTime<Utc>,
709}
710
711#[cfg(test)]
712mod tests {
713	use super::*;
714
715	#[test]
716	fn test_pem_encoding() {
717		let test_data = b"test certificate data";
718		let pem = CertificateManager::cert_der_to_pem(test_data).unwrap();
719		assert!(String::from_utf8_lossy(&pem).contains("-----BEGIN CERTIFICATE-----"));
720		assert!(String::from_utf8_lossy(&pem).contains("-----END CERTIFICATE-----"));
721
722		let recovered = CertificateManager::pem_to_der(&pem, "CERTIFICATE").unwrap();
723		assert_eq!(recovered, test_data);
724	}
725
726	#[test]
727	fn test_private_key_pem_encoding() {
728		let test_data = b"test private key data";
729		let pem = CertificateManager::private_key_der_to_pem(test_data).unwrap();
730		assert!(String::from_utf8_lossy(&pem).contains("-----BEGIN PRIVATE KEY-----"));
731		assert!(String::from_utf8_lossy(&pem).contains("-----END PRIVATE KEY-----"));
732
733		let recovered = CertificateManager::pem_to_der(&pem, "PRIVATE KEY").unwrap();
734		assert_eq!(recovered, test_data);
735	}
736}