1use crate::cli::GenerateArgs;
4use rcgen::{CertificateParams, KeyPair, PKCS_ECDSA_P256_SHA256, SanType};
5use std::fs;
6use thiserror::Error;
7
8#[derive(Error, Debug)]
10pub enum CertError {
11 #[error("Key generation failed: {0}")]
12 KeyGeneration(String),
13
14 #[error("Certificate generation failed: {0}")]
15 CertGeneration(String),
16
17 #[error("IO error: {0}")]
18 Io(#[from] std::io::Error),
19
20 #[error("Invalid domain name: {0}")]
21 InvalidDomain(String),
22}
23
24pub fn generate(args: &GenerateArgs) -> Result<(), CertError> {
26 let key_pair = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256)
28 .map_err(|e| CertError::KeyGeneration(e.to_string()))?;
29
30 let mut params = CertificateParams::default();
32
33 for domain in &args.domain {
35 let san = SanType::DnsName(
36 domain
37 .clone()
38 .try_into()
39 .map_err(|_| CertError::InvalidDomain(domain.clone()))?,
40 );
41 params.subject_alt_names.push(san);
42 }
43
44 for ip in &args.ip {
46 params.subject_alt_names.push(SanType::IpAddress(*ip));
47 }
48
49 let now = time::OffsetDateTime::now_utc();
51 params.not_before = now;
52 params.not_after = now + time::Duration::days(args.days as i64);
53
54 let cert = params
56 .self_signed(&key_pair)
57 .map_err(|e| CertError::CertGeneration(e.to_string()))?;
58
59 fs::create_dir_all(&args.output)?;
61
62 let cert_path = args.output.join(format!("{}.pem", args.cert_name));
64 let key_path = args.output.join(format!("{}.pem", args.key_name));
65
66 fs::write(&cert_path, cert.pem())?;
67 fs::write(&key_path, key_pair.serialize_pem())?;
68
69 println!("Certificate generated successfully:");
71 println!(" Certificate: {}", cert_path.display());
72 println!(" Private key: {}", key_path.display());
73 println!(" Valid for: {} days", args.days);
74 println!(" Domains: {}", args.domain.join(", "));
75 if !args.ip.is_empty() {
76 println!(
77 " IPs: {}",
78 args.ip
79 .iter()
80 .map(|ip| ip.to_string())
81 .collect::<Vec<_>>()
82 .join(", ")
83 );
84 }
85
86 Ok(())
87}