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