Skip to main content

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