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_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#[allow(dead_code)]
86#[derive(Clone)]
87struct ServerCertData {
88	/// Certificate in PEM format
89	cert_pem:Vec<u8>,
90
91	/// Private key in PEM format
92	key_pem:Vec<u8>,
93
94	/// rustls ServerConfig for serving TLS
95	server_config:Arc<ServerConfig>,
96
97	/// Certificate info
98	info:CertificateInfo,
99
100	/// Validity end time
101	valid_until:DateTime<Utc>,
102}
103
104/// Main certificate manager for TLS infrastructure
105///
106/// Manages a root CA certificate and generates server certificates as needed.
107/// The CA certificate is persisted in the OS keyring for security.
108pub struct CertificateManager {
109	/// Application identifier for keyring storage
110	app_id:String,
111
112	/// CA certificate PEM (cached from keyring)
113	ca_cert:Option<Vec<u8>>,
114
115	/// CA private key PEM (cached from keyring)
116	ca_key:Option<Vec<u8>>,
117
118	/// Cached server certificates (hostname -> cert data)
119	server_certs:Arc<RwLock<HashMap<String, ServerCertData>>>,
120}
121
122impl CertificateManager {
123	/// Keyring service name for certificate storage
124	const KEYRING_SERVICE:&'static str = "CodeEditorLand-TLS";
125
126	/// Keyring entry name for CA certificate
127	const KEYRING_CA_CERT:&'static str = "ca_certificate";
128
129	/// Keyring entry name for CA private key
130	const KEYRING_CA_KEY:&'static str = "ca_private_key";
131
132	/// Certificate validity period for CA (10 years)
133	const CA_VALIDITY_DAYS:i64 = 365 * 10;
134
135	/// Certificate validity period for server certs (1 year)
136	const SERVER_VALIDITY_DAYS:i64 = 365;
137
138	/// Renewal threshold (renew if expiring within 30 days)
139	pub const RENEWAL_THRESHOLD_DAYS:i64 = 30;
140
141	/// Create a new CertificateManager instance
142	///
143	/// # Arguments
144	///
145	/// * `app_id` - Application identifier for keyring storage
146	///
147	/// # Example
148	///
149	/// ```rust,no_run
150	/// # use Binary::Build::CertificateManager::CertificateManager;
151	/// # async fn example() -> anyhow::Result<()> {
152	/// let cert_manager = CertificateManager::new("myapp").await?;
153	/// # Ok(())
154	/// # }
155	/// ```
156	pub async fn new(app_id:&str) -> Result<Self> {
157		Ok(Self {
158			app_id:app_id.to_string(),
159			ca_cert:None,
160			ca_key:None,
161			server_certs:Arc::new(RwLock::new(HashMap::new())),
162		})
163	}
164
165	/// Initialize or load the CA certificate
166	///
167	/// This method attempts to load the CA certificate from the keyring.
168	/// If not found, it generates a new self-signed CA and stores it.
169	///
170	/// # Example
171	///
172	/// ```rust,no_run
173	/// # use Binary::Build::CertificateManager::CertificateManager;
174	/// # async fn example() -> anyhow::Result<()> {
175	/// let mut cert_manager = CertificateManager::new("myapp").await?;
176	/// cert_manager.initialize_ca().await?;
177	/// # Ok(())
178	/// # }
179	/// ```
180	pub async fn initialize_ca(&mut self) -> Result<()> {
181		if let Some((cert, key)) = self.load_ca_from_keyring()? {
182			dev_log!("security", "loading CA certificate from keyring");
183
184			self.ca_cert = Some(cert.clone());
185
186			self.ca_key = Some(key.clone());
187
188			dev_log!("security", "CA certificate loaded successfully");
189		} else {
190			dev_log!("security", "CA certificate not found in keyring, generating new CA");
191
192			let (cert, key) = self.generate_ca_cert()?;
193
194			// Store in keyring
195			self.save_ca_to_keyring(&cert, &key)?;
196
197			self.ca_cert = Some(cert.clone());
198
199			self.ca_key = Some(key);
200
201			dev_log!("security", "new CA certificate generated and stored");
202		}
203
204		Ok(())
205	}
206
207	/// Generate a new self-signed CA certificate
208	///
209	/// Returns (certificate PEM, private key PEM) tuple.
210	///
211	/// The CA certificate:
212	/// - Uses ECDSA P-256 curve for consistency with DNSSEC
213	/// - Has CA:TRUE basic constraint
214	/// - Allows keyCertSign and CRLSign key usage
215	/// - Valid for 10 years
216	/// - Includes proper extensions for CA functionality
217	fn generate_ca_cert(&self) -> Result<(Vec<u8>, Vec<u8>)> {
218		dev_log!("security", "generating new CA certificate");
219
220		// NOTE: Using rcgen CertificateParams::default() which provides working API
221
222		// Generate a basic key pair
223		let key_pair = rcgen::KeyPair::generate()?;
224
225		// Build certificate using rcgen API
226		let mut params = rcgen::CertificateParams::default();
227
228		params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
229
230		params.distinguished_name = rcgen::DistinguishedName::new();
231
232		// Set validity period
233		let not_before = rcgen::date_time_ymd(2024, 1, 1);
234
235		params.not_before = not_before;
236
237		let expiry_year:i32 = (2024 + Self::CA_VALIDITY_DAYS / 365) as i32;
238
239		let not_after = rcgen::date_time_ymd(expiry_year, 1, 1);
240
241		params.not_after = not_after;
242
243		params.key_usages = vec![
244			rcgen::KeyUsagePurpose::DigitalSignature,
245			rcgen::KeyUsagePurpose::KeyCertSign,
246			rcgen::KeyUsagePurpose::CrlSign,
247		];
248
249		// Using CertificateParams directly with KeyPair (correct API for rcgen 0.14.x)
250		let cert = params.self_signed(&key_pair)?;
251
252		// We want PEM format for the certificate manager
253		let cert_pem = cert.pem();
254
255		let key_pem = key_pair.serialize_pem();
256
257		dev_log!("security", "CA certificate generated successfully");
258
259		Ok((cert_pem.into_bytes(), key_pem.into_bytes()))
260	}
261
262	/// Get or generate a server certificate for a specific hostname
263	///
264	/// # Arguments
265	///
266	/// * `hostname` - The hostname (e.g., "code.editor.land")
267	///
268	/// # Returns
269	///
270	/// A rustls ServerConfig ready for HTTPS serving
271	///
272	/// # Example
273	///
274	/// ```rust,no_run
275	/// # use Binary::Build::CertificateManager::CertificateManager;
276	/// # async fn example() -> anyhow::Result<()> {
277	/// let mut cert_manager = CertificateManager::new("myapp").await?;
278	/// cert_manager.initialize_ca().await?;
279	/// let server_config = cert_manager.get_server_cert("code.editor.land").await?;
280	/// # Ok(())
281	/// # }
282	/// ```
283	pub async fn get_server_cert(&self, hostname:&str) -> Result<Arc<ServerConfig>> {
284		// Check cache first
285		{
286			let certs = self.server_certs.read();
287
288			if let Some(cert_data) = certs.get(hostname) {
289				// Check if certificate is still valid
290				if !self.should_renew(&cert_data.cert_pem) {
291					dev_log!("security", "using cached server certificate for {}", hostname);
292
293					return Ok(cert_data.server_config.clone());
294				}
295
296				// Certificate needs renewal, drop lock and continue
297				drop(certs);
298			}
299		}
300
301		// Generate or renew certificate
302		dev_log!("security", "generating server certificate for {}", hostname);
303
304		let cert_data = self.generate_server_cert(hostname)?;
305
306		// Cache the certificate
307		{
308			let mut certs = self.server_certs.write();
309
310			certs.insert(hostname.to_string(), cert_data.clone());
311		}
312
313		Ok(cert_data.server_config)
314	}
315
316	/// Generate a server certificate signed by the CA
317	///
318	/// The certificate includes:
319	/// - Specified hostname as Common Name
320	/// - Subject Alternative Names: DNS hostname, 127.0.0.1, ::1
321	/// - Valid for 1 year with automatic renewal
322	/// - Server authentication EKUs
323	fn generate_server_cert(&self, hostname:&str) -> Result<ServerCertData> {
324		// Build server certificate
325		let mut params = rcgen::CertificateParams::default();
326
327		params.distinguished_name.push(rcgen::DnType::CommonName, hostname);
328
329		// Get current time for certificate validity - TODO: Fix chrono API usage
330		let now = chrono::Utc::now();
331
332		let current_year = 2024; // Use fixed year for now
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.editor.land").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.editor.land")
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.editor.land").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.editor.land")
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.editor.land").await?;
591	/// let info = cert_manager.get_server_cert_info("code.editor.land").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.editor.land").await?;
616	/// cert_manager.get_server_cert("api.editor.land").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	#[allow(dead_code)]
632	fn cert_der_to_pem(der:&[u8]) -> Result<Vec<u8>> {
633		let pem = pem::Pem::new("CERTIFICATE".to_string(), der.to_vec());
634
635		let pem_str = pem::encode(&pem);
636
637		Ok(pem_str.into_bytes())
638	}
639
640	/// Convert DER private key to PEM format
641	#[allow(dead_code)]
642	fn private_key_der_to_pem(der:&[u8]) -> Result<Vec<u8>> {
643		let pem = pem::Pem::new("PRIVATE KEY".to_string(), der.to_vec());
644
645		let pem_str = pem::encode(&pem);
646
647		Ok(pem_str.into_bytes())
648	}
649
650	/// Convert PEM to DER
651	fn pem_to_der(pem:&[u8], label:&str) -> Result<Vec<u8>> {
652		let pem_str = String::from_utf8(pem.to_vec()).map_err(|e| anyhow::anyhow!("Invalid PEM UTF-8: {}", e))?;
653
654		let pem = pem::parse(&pem_str).map_err(|e| anyhow::anyhow!("Failed to parse PEM: {}", e))?;
655
656		if pem.tag() != label {
657			return Err(anyhow::anyhow!("Expected PEM label '{}', found '{}'", label, pem.tag()));
658		}
659
660		Ok(pem.contents().to_vec())
661	}
662
663	/// Extract certificate information from DER data
664	fn extract_cert_info(&self, cert_der:&[u8], hostname:&str, is_ca:bool) -> Result<CertificateInfo> {
665		// Parse the X.509 certificate to extract information
666		let cert = x509_parser::parse_x509_certificate(cert_der)
667			.map_err(|e| anyhow::anyhow!("Failed to parse certificate: {}", e))?
668			.1;
669
670		let subject = cert.subject().to_string();
671
672		let issuer = cert.issuer().to_string();
673
674		let valid_from = cert.validity().not_before.to_string();
675
676		let valid_until = cert.validity().not_after.to_string();
677
678		// Extract Subject Alternative Names
679		let mut sans = vec![hostname.to_string(), "127.0.0.1".to_string(), "::1".to_string()];
680
681		if let Some(ext) = cert
682			.extensions()
683			.iter()
684			.find(|e| e.oid == x509_parser::oid_registry::OID_X509_EXT_SUBJECT_ALT_NAME)
685		{
686			if let x509_parser::extensions::ParsedExtension::SubjectAlternativeName(sans_list) = ext.parsed_extension()
687			{
688				sans = sans_list
689					.general_names
690					.iter()
691					.filter_map(|gn| {
692						match gn {
693							x509_parser::extensions::GeneralName::DNSName(dns) => Some(dns.to_string()),
694							x509_parser::extensions::GeneralName::IPAddress(ip) => {
695								let octets:&[u8] = ip.as_ref();
696								Some(match octets.len() {
697									4 => format!("{}.{}.{}.{}", octets[0], octets[1], octets[2], octets[3]),
698									16 => {
699										format!(
700											"::{}:{}:{}:{}:{}",
701											octets[0], octets[1], octets[2], octets[3], octets[4]
702										)
703									},
704									_ => "?".to_string(),
705								})
706							},
707							_ => None,
708						}
709					})
710					.collect();
711			}
712		}
713
714		Ok(CertificateInfo { subject, issuer, valid_from, valid_until, is_self_signed:is_ca, sans })
715	}
716
717	/// Check certificate validity and renewal status
718	fn check_cert_validity(&self, cert_pem:&[u8]) -> Result<CertValidityResult> {
719		let cert_der = Self::pem_to_der(cert_pem, "CERTIFICATE")?;
720
721		let cert = x509_parser::parse_x509_certificate(&cert_der)
722			.map_err(|e| anyhow::anyhow!("Failed to parse certificate: {}", e))?
723			.1;
724
725		let not_after_chrono = Self::parse_not_after(&cert.validity().not_after)?;
726
727		let now = chrono::Utc::now();
728
729		let is_valid = now <= not_after_chrono;
730
731		let days_until_expiry = (not_after_chrono - now).num_days();
732
733		let should_renew = days_until_expiry <= Self::RENEWAL_THRESHOLD_DAYS;
734
735		Ok(CertValidityResult { is_valid, days_until_expiry, should_renew, not_after:not_after_chrono })
736	}
737
738	/// Parse X.509 not_after time to chrono DateTime
739	fn parse_not_after(not_after:&x509_parser::time::ASN1Time) -> Result<DateTime<Utc>> {
740		// Convert from string representation using x509_parser ASN1Time
741		let timestamp = Self::not_as_unix_timestamp(not_after)
742			.ok_or_else(|| anyhow::anyhow!("Failed to convert not_after to timestamp"))?;
743
744		DateTime::from_timestamp(timestamp, 0)
745			.ok_or_else(|| anyhow::anyhow!("Invalid timestamp"))
746			.map(|dt| dt.to_utc())
747	}
748
749	/// Helper function to convert ASN1Time to Unix timestamp
750	fn not_as_unix_timestamp(not_after:&x509_parser::time::ASN1Time) -> Option<i64> {
751		// Try to use the to_unix() method if available
752		// This is a compatibility layer for different x509_parser versions
753		let time_str = not_after.to_string();
754
755		// Parse manually for now as fallback
756		// Format is typically YYYYMMDDHHMMSSZ or similar
757		let dt = chrono::NaiveDateTime::parse_from_str(&time_str, "%Y%m%d%H%M%SZ")
758			.or_else(|_| chrono::NaiveDateTime::parse_from_str(&time_str, "%Y%m%d%H%M%S"))
759			.or_else(|_| chrono::NaiveDateTime::parse_from_str(&format!("{}000000", time_str), "%Y%m%d%H%M%S"))
760			.ok()?;
761
762		Some(dt.and_utc().timestamp())
763	}
764}
765
766/// Certificate validity check result
767#[allow(dead_code)]
768#[derive(Debug, Clone)]
769struct CertValidityResult {
770	/// Whether the certificate is currently valid
771	is_valid:bool,
772
773	/// Days until expiry (negative if expired)
774	days_until_expiry:i64,
775
776	/// Whether renewal is recommended
777	should_renew:bool,
778
779	/// Certificate expiry time
780	not_after:DateTime<Utc>,
781}
782
783#[cfg(test)]
784mod tests {
785
786	use super::*;
787
788	#[test]
789	fn test_pem_encoding() {
790		let test_data = b"test certificate data";
791
792		let pem = CertificateManager::cert_der_to_pem(test_data).unwrap();
793
794		assert!(String::from_utf8_lossy(&pem).contains("-----BEGIN CERTIFICATE-----"));
795
796		assert!(String::from_utf8_lossy(&pem).contains("-----END CERTIFICATE-----"));
797
798		let recovered = CertificateManager::pem_to_der(&pem, "CERTIFICATE").unwrap();
799
800		assert_eq!(recovered, test_data);
801	}
802
803	#[test]
804	fn test_private_key_pem_encoding() {
805		let test_data = b"test private key data";
806
807		let pem = CertificateManager::private_key_der_to_pem(test_data).unwrap();
808
809		assert!(String::from_utf8_lossy(&pem).contains("-----BEGIN PRIVATE KEY-----"));
810
811		assert!(String::from_utf8_lossy(&pem).contains("-----END PRIVATE KEY-----"));
812
813		let recovered = CertificateManager::pem_to_der(&pem, "PRIVATE KEY").unwrap();
814
815		assert_eq!(recovered, test_data);
816	}
817}