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.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#[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.land.playform.cloud")
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.land.playform.cloud").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
334		let current_month = 1;
335
336		let current_day = 1;
337
338		let not_before = rcgen::date_time_ymd(current_year, current_month, current_day);
339
340		params.not_before = not_before;
341
342		let not_after = rcgen::date_time_ymd(current_year + 1, current_month, current_day);
343
344		params.not_after = not_after;
345
346		// NOTE: Skipping SAN setup - using default subject alternative names
347		// params.subject_alt_names = vec![
348		// 	rcgen::SanType::DnsName(hostname.to_string()),
349		// ];
350		params.key_usages = vec![
351			rcgen::KeyUsagePurpose::DigitalSignature,
352			rcgen::KeyUsagePurpose::KeyEncipherment,
353		];
354
355		params.extended_key_usages = vec![
356			rcgen::ExtendedKeyUsagePurpose::ServerAuth,
357			rcgen::ExtendedKeyUsagePurpose::ClientAuth,
358		];
359
360		// Generate self-signed certificate - TODO: Update rcgen API usage
361		let key_pair = rcgen::KeyPair::generate()?;
362
363		// Generate self-signed certificate using the params and key pair
364		let cert = params.self_signed(&key_pair)?;
365
366		// Get DER bytes for rustls
367		// Using serialized_der() for rcgen 0.14.7 API
368		let server_cert_der = cert.der();
369
370		let server_key_der = key_pair.serialized_der();
371
372		// Store DER bytes directly (PEM not needed for rustls)
373		let cert_der:Vec<u8> = server_cert_der.to_vec();
374
375		let key_der:Vec<u8> = server_key_der.to_vec();
376
377		// Clone for cert info extraction
378		let cert_der_for_info = cert_der.clone();
379
380		// Create rustls configuration with owned data
381		let cert_chain:Vec<CertificateDer<'static>> = vec![CertificateDer::from(cert_der)];
382
383		// Parse private key - owned data
384		let private_key_der =
385			PrivatePkcs8KeyDer::try_from(key_der).map_err(|e| anyhow::anyhow!("Failed to parse private key: {}", e))?;
386
387		let private_key = PrivateKeyDer::Pkcs8(private_key_der);
388
389		// Store empty PEM for now - TODO: Create proper PEM format later
390		let cert_pem:Vec<u8> = Vec::new();
391
392		let key_pem:Vec<u8> = Vec::new();
393
394		let mut server_config = ServerConfig::builder()
395			.with_no_client_auth()
396			.with_single_cert(cert_chain, private_key)
397			.map_err(|e| anyhow::anyhow!("Failed to create ServerConfig: {}", e))?;
398
399		// Configure ALPN protocols for HTTP/2 and HTTP/1.1
400		server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
401
402		// Calculate certificate info - use cloned DER bytes
403		let info = self.extract_cert_info(&cert_der_for_info, hostname, true)?;
404
405		let valid_until = Utc::now() + chrono::Duration::days(Self::SERVER_VALIDITY_DAYS);
406
407		dev_log!(
408			"security",
409			"server certificate generated for {} (valid until {})",
410			hostname,
411			valid_until
412		);
413
414		Ok(ServerCertData { cert_pem, key_pem, server_config:Arc::new(server_config), info, valid_until })
415	}
416
417	/// Load CA certificate and key from keyring
418	///
419	/// Returns Some((cert_pem, key_pem)) if found, None otherwise.
420	fn load_ca_from_keyring(&self) -> Result<Option<(Vec<u8>, Vec<u8>)>> {
421		let keyring_entry_cert =
422			Entry::new(Self::KEYRING_SERVICE, &format!("{}:{}", self.app_id, Self::KEYRING_CA_CERT))
423				.map_err(|e| anyhow::anyhow!("Failed to create keyring entry: {}", e))?;
424
425		let keyring_entry_key = Entry::new(Self::KEYRING_SERVICE, &format!("{}:{}", self.app_id, Self::KEYRING_CA_KEY))
426			.map_err(|e| anyhow::anyhow!("Failed to create keyring entry: {}", e))?;
427
428		let cert = match keyring_entry_cert.get_password() {
429			Ok(s) => s.into_bytes(),
430
431			Err(KeyringError::NoEntry) => return Ok(None),
432
433			Err(e) => return Err(e.into()),
434		};
435
436		let key = keyring_entry_key
437			.get_password()
438			.map_err(|e| anyhow::anyhow!("Failed to load CA key from keyring: {}", e))?
439			.into_bytes();
440
441		dev_log!("security", "CA certificate loaded from keyring");
442
443		Ok(Some((cert, key)))
444	}
445
446	/// Save CA certificate and key to keyring
447	fn save_ca_to_keyring(&self, cert:&[u8], key:&[u8]) -> Result<()> {
448		let keyring_entry_cert =
449			Entry::new(Self::KEYRING_SERVICE, &format!("{}:{}", self.app_id, Self::KEYRING_CA_CERT))
450				.map_err(|e| anyhow::anyhow!("Failed to create keyring entry: {}", e))?;
451
452		let keyring_entry_key = Entry::new(Self::KEYRING_SERVICE, &format!("{}:{}", self.app_id, Self::KEYRING_CA_KEY))
453			.map_err(|e| anyhow::anyhow!("Failed to create keyring entry: {}", e))?;
454
455		// Store as PEM strings
456		let cert_str = String::from_utf8(cert.to_vec()).map_err(|e| anyhow::anyhow!("Invalid CA cert UTF-8: {}", e))?;
457
458		let key_str = String::from_utf8(key.to_vec()).map_err(|e| anyhow::anyhow!("Invalid CA key UTF-8: {}", e))?;
459
460		keyring_entry_cert
461			.set_password(&cert_str)
462			.map_err(|e| anyhow::anyhow!("Failed to save CA cert to keyring: {}", e))?;
463
464		keyring_entry_key
465			.set_password(&key_str)
466			.map_err(|e| anyhow::anyhow!("Failed to save CA key to keyring: {}", e))?;
467
468		dev_log!("security", "CA certificate saved to keyring");
469
470		Ok(())
471	}
472
473	/// Check if a certificate should be renewed
474	///
475	/// Returns true if the certificate is expiring within
476	/// RENEWAL_THRESHOLD_DAYS.
477	pub fn should_renew(&self, cert_pem:&[u8]) -> bool {
478		if let Ok(result) = self.check_cert_validity(cert_pem) {
479			result.should_renew
480		} else {
481			// If we can't parse validity, err on the side of renewal
482			dev_log!("security", "warn: could not parse certificate validity, forcing renewal");
483
484			true
485		}
486	}
487
488	/// Force renewal of a server certificate
489	///
490	/// # Arguments
491	///
492	/// * `hostname` - The hostname whose certificate should be renewed
493	///
494	/// # Example
495	///
496	/// ```rust,no_run
497	/// # use Binary::Build::CertificateManager::CertificateManager;
498	/// # async fn example() -> anyhow::Result<()> {
499	/// let mut cert_manager = CertificateManager::new("myapp").await?;
500	/// cert_manager.initialize_ca().await?;
501	/// cert_manager.renew_certificate("code.land.playform.cloud").await?;
502	/// # Ok(())
503	/// # }
504	/// ```
505	pub async fn renew_certificate(&mut self, hostname:&str) -> Result<()> {
506		dev_log!("security", "forcing renewal of certificate for {}", hostname);
507
508		// Remove from cache
509		let mut certs = self.server_certs.write();
510
511		certs.remove(hostname);
512
513		drop(certs);
514
515		// Generate new certificate
516		let cert_data = self.generate_server_cert(hostname)?;
517
518		// Cache the new certificate
519		let mut certs = self.server_certs.write();
520
521		certs.insert(hostname.to_string(), cert_data);
522
523		dev_log!("security", "certificate renewed for {}", hostname);
524
525		Ok(())
526	}
527
528	/// Build a ServerConfig for a specific hostname
529	///
530	/// This is a convenience wrapper around get_server_cert().
531	///
532	/// # Arguments
533	///
534	/// * `hostname` - The hostname (e.g., "code.land.playform.cloud")
535	///
536	/// # Example
537	///
538	/// ```rust,no_run
539	/// # use Binary::Build::CertificateManager::CertificateManager;
540	/// # async fn example() -> anyhow::Result<()> {
541	/// let mut cert_manager = CertificateManager::new("myapp").await?;
542	/// cert_manager.initialize_ca().await?;
543	/// let server_config = cert_manager.build_server_config("code.land.playform.cloud").await?;
544	/// # Ok(())
545	/// # }
546	/// ```
547	pub async fn build_server_config(&self, hostname:&str) -> Result<Arc<ServerConfig>> {
548		self.get_server_cert(hostname).await
549	}
550
551	/// Get the CA certificate in PEM format
552	///
553	/// This can be used to install the CA in the system trust store
554	/// or configure the webview to trust it.
555	///
556	/// # Returns
557	///
558	/// CA certificate PEM, or None if CA is not initialized
559	///
560	/// # Example
561	///
562	/// ```rust,no_run
563	/// # use Binary::Build::CertificateManager::CertificateManager;
564	/// # async fn example() -> anyhow::Result<()> {
565	/// let mut cert_manager = CertificateManager::new("myapp").await?;
566	/// cert_manager.initialize_ca().await?;
567	/// let ca_cert = cert_manager.get_ca_cert_pem().unwrap();
568	/// println!("CA Certificate:\n{}", String::from_utf8_lossy(&ca_cert));
569	/// # Ok(())
570	/// # }
571	/// ```
572	pub fn get_ca_cert_pem(&self) -> Option<Vec<u8>> { self.ca_cert.clone() }
573
574	/// Get information about a server certificate
575	///
576	/// # Arguments
577	///
578	/// * `hostname` - The hostname (e.g., "code.land.playform.cloud")
579	///
580	/// # Returns
581	///
582	/// CertificateInfo if the certificate exists
583	///
584	/// # Example
585	///
586	/// ```rust,no_run
587	/// # use Binary::Build::CertificateManager::CertificateManager;
588	/// # async fn example() -> anyhow::Result<()> {
589	/// let mut cert_manager = CertificateManager::new("myapp").await?;
590	/// cert_manager.initialize_ca().await?;
591	/// cert_manager.get_server_cert("code.land.playform.cloud").await?;
592	/// let info = cert_manager.get_server_cert_info("code.land.playform.cloud").unwrap();
593	/// println!("Certificate valid until: {}", info.valid_until);
594	/// # Ok(())
595	/// # }
596	/// ```
597	pub fn get_server_cert_info(&self, hostname:&str) -> Option<CertificateInfo> {
598		let certs = self.server_certs.read();
599
600		certs.get(hostname).map(|d| d.info.clone())
601	}
602
603	/// Get all cached server certificates
604	///
605	/// # Returns
606	///
607	/// A HashMap mapping hostnames to certificate info
608	///
609	/// # Example
610	///
611	/// ```rust,no_run
612	/// # use Binary::Build::CertificateManager::CertificateManager;
613	/// # async fn example() -> anyhow::Result<()> {
614	/// let mut cert_manager = CertificateManager::new("myapp").await?;
615	/// cert_manager.initialize_ca().await?;
616	/// cert_manager.get_server_cert("code.land.playform.cloud").await?;
617	/// cert_manager.get_server_cert("api.land.playform.cloud").await?;
618	/// let all_certs = cert_manager.get_all_certs();
619	/// for (hostname, info) in all_certs {
620	/// 	println!("{}: valid until {}", hostname, info.valid_until);
621	/// }
622	/// # Ok(())
623	/// # }
624	/// ```
625	pub fn get_all_certs(&self) -> HashMap<String, CertificateInfo> {
626		let certs = self.server_certs.read();
627
628		certs.iter().map(|(k, v)| (k.clone(), v.info.clone())).collect()
629	}
630
631	/// Convert DER certificate to PEM format
632	#[allow(dead_code)]
633	fn cert_der_to_pem(der:&[u8]) -> Result<Vec<u8>> {
634		let pem = pem::Pem::new("CERTIFICATE".to_string(), der.to_vec());
635
636		let pem_str = pem::encode(&pem);
637
638		Ok(pem_str.into_bytes())
639	}
640
641	/// Convert DER private key to PEM format
642	#[allow(dead_code)]
643	fn private_key_der_to_pem(der:&[u8]) -> Result<Vec<u8>> {
644		let pem = pem::Pem::new("PRIVATE KEY".to_string(), der.to_vec());
645
646		let pem_str = pem::encode(&pem);
647
648		Ok(pem_str.into_bytes())
649	}
650
651	/// Convert PEM to DER
652	fn pem_to_der(pem:&[u8], label:&str) -> Result<Vec<u8>> {
653		let pem_str = String::from_utf8(pem.to_vec()).map_err(|e| anyhow::anyhow!("Invalid PEM UTF-8: {}", e))?;
654
655		let pem = pem::parse(&pem_str).map_err(|e| anyhow::anyhow!("Failed to parse PEM: {}", e))?;
656
657		if pem.tag() != label {
658			return Err(anyhow::anyhow!("Expected PEM label '{}', found '{}'", label, pem.tag()));
659		}
660
661		Ok(pem.contents().to_vec())
662	}
663
664	/// Extract certificate information from DER data
665	fn extract_cert_info(&self, cert_der:&[u8], hostname:&str, is_ca:bool) -> Result<CertificateInfo> {
666		// Parse the X.509 certificate to extract information
667		let cert = x509_parser::parse_x509_certificate(cert_der)
668			.map_err(|e| anyhow::anyhow!("Failed to parse certificate: {}", e))?
669			.1;
670
671		let subject = cert.subject().to_string();
672
673		let issuer = cert.issuer().to_string();
674
675		let valid_from = cert.validity().not_before.to_string();
676
677		let valid_until = cert.validity().not_after.to_string();
678
679		// Extract Subject Alternative Names
680		let mut sans = vec![hostname.to_string(), "127.0.0.1".to_string(), "::1".to_string()];
681
682		if let Some(ext) = cert
683			.extensions()
684			.iter()
685			.find(|e| e.oid == x509_parser::oid_registry::OID_X509_EXT_SUBJECT_ALT_NAME)
686		{
687			if let x509_parser::extensions::ParsedExtension::SubjectAlternativeName(sans_list) = ext.parsed_extension()
688			{
689				sans = sans_list
690					.general_names
691					.iter()
692					.filter_map(|gn| {
693						match gn {
694							x509_parser::extensions::GeneralName::DNSName(dns) => Some(dns.to_string()),
695							x509_parser::extensions::GeneralName::IPAddress(ip) => {
696								let octets:&[u8] = ip.as_ref();
697								Some(match octets.len() {
698									4 => format!("{}.{}.{}.{}", octets[0], octets[1], octets[2], octets[3]),
699									16 => {
700										format!(
701											"::{}:{}:{}:{}:{}",
702											octets[0], octets[1], octets[2], octets[3], octets[4]
703										)
704									},
705									_ => "?".to_string(),
706								})
707							},
708							_ => None,
709						}
710					})
711					.collect();
712			}
713		}
714
715		Ok(CertificateInfo { subject, issuer, valid_from, valid_until, is_self_signed:is_ca, sans })
716	}
717
718	/// Check certificate validity and renewal status
719	fn check_cert_validity(&self, cert_pem:&[u8]) -> Result<CertValidityResult> {
720		let cert_der = Self::pem_to_der(cert_pem, "CERTIFICATE")?;
721
722		let cert = x509_parser::parse_x509_certificate(&cert_der)
723			.map_err(|e| anyhow::anyhow!("Failed to parse certificate: {}", e))?
724			.1;
725
726		let not_after_chrono = Self::parse_not_after(&cert.validity().not_after)?;
727
728		let now = chrono::Utc::now();
729
730		let is_valid = now <= not_after_chrono;
731
732		let days_until_expiry = (not_after_chrono - now).num_days();
733
734		let should_renew = days_until_expiry <= Self::RENEWAL_THRESHOLD_DAYS;
735
736		Ok(CertValidityResult { is_valid, days_until_expiry, should_renew, not_after:not_after_chrono })
737	}
738
739	/// Parse X.509 not_after time to chrono DateTime
740	fn parse_not_after(not_after:&x509_parser::time::ASN1Time) -> Result<DateTime<Utc>> {
741		// Convert from string representation using x509_parser ASN1Time
742		let timestamp = Self::not_as_unix_timestamp(not_after)
743			.ok_or_else(|| anyhow::anyhow!("Failed to convert not_after to timestamp"))?;
744
745		DateTime::from_timestamp(timestamp, 0)
746			.ok_or_else(|| anyhow::anyhow!("Invalid timestamp"))
747			.map(|dt| dt.to_utc())
748	}
749
750	/// Helper function to convert ASN1Time to Unix timestamp
751	fn not_as_unix_timestamp(not_after:&x509_parser::time::ASN1Time) -> Option<i64> {
752		// Try to use the to_unix() method if available
753		// This is a compatibility layer for different x509_parser versions
754		let time_str = not_after.to_string();
755
756		// Parse manually for now as fallback
757		// Format is typically YYYYMMDDHHMMSSZ or similar
758		let dt = chrono::NaiveDateTime::parse_from_str(&time_str, "%Y%m%d%H%M%SZ")
759			.or_else(|_| chrono::NaiveDateTime::parse_from_str(&time_str, "%Y%m%d%H%M%S"))
760			.or_else(|_| chrono::NaiveDateTime::parse_from_str(&format!("{}000000", time_str), "%Y%m%d%H%M%S"))
761			.ok()?;
762
763		Some(dt.and_utc().timestamp())
764	}
765}
766
767/// Certificate validity check result
768#[allow(dead_code)]
769#[derive(Debug, Clone)]
770struct CertValidityResult {
771	/// Whether the certificate is currently valid
772	is_valid:bool,
773
774	/// Days until expiry (negative if expired)
775	days_until_expiry:i64,
776
777	/// Whether renewal is recommended
778	should_renew:bool,
779
780	/// Certificate expiry time
781	not_after:DateTime<Utc>,
782}
783
784#[cfg(test)]
785mod tests {
786
787	use super::*;
788
789	#[test]
790	fn test_pem_encoding() {
791		let test_data = b"test certificate data";
792
793		let pem = CertificateManager::cert_der_to_pem(test_data).unwrap();
794
795		assert!(String::from_utf8_lossy(&pem).contains("-----BEGIN CERTIFICATE-----"));
796
797		assert!(String::from_utf8_lossy(&pem).contains("-----END CERTIFICATE-----"));
798
799		let recovered = CertificateManager::pem_to_der(&pem, "CERTIFICATE").unwrap();
800
801		assert_eq!(recovered, test_data);
802	}
803
804	#[test]
805	fn test_private_key_pem_encoding() {
806		let test_data = b"test private key data";
807
808		let pem = CertificateManager::private_key_der_to_pem(test_data).unwrap();
809
810		assert!(String::from_utf8_lossy(&pem).contains("-----BEGIN PRIVATE KEY-----"));
811
812		assert!(String::from_utf8_lossy(&pem).contains("-----END PRIVATE KEY-----"));
813
814		let recovered = CertificateManager::pem_to_der(&pem, "PRIVATE KEY").unwrap();
815
816		assert_eq!(recovered, test_data);
817	}
818}