Skip to main content

trojan_cert/
generate.rs

1//! Self-signed certificate generation.
2
3use crate::cli::GenerateArgs;
4use rcgen::{CertificateParams, KeyPair, PKCS_ECDSA_P256_SHA256, SanType};
5use std::fs;
6use thiserror::Error;
7
8/// Errors that can occur during certificate generation.
9#[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
24/// Generate a self-signed certificate with the given parameters.
25pub fn generate(args: &GenerateArgs) -> Result<(), CertError> {
26    // 1. Generate key pair using ECDSA P-256 (compatible with aws-lc-rs)
27    let key_pair = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256)
28        .map_err(|e| CertError::KeyGeneration(e.to_string()))?;
29
30    // 2. Configure certificate parameters
31    let mut params = CertificateParams::default();
32
33    // Add domain names to Subject Alternative Names
34    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    // Add IP addresses to Subject Alternative Names
45    for ip in &args.ip {
46        params.subject_alt_names.push(SanType::IpAddress(*ip));
47    }
48
49    // Set validity period
50    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    // 3. Generate self-signed certificate
55    let cert = params
56        .self_signed(&key_pair)
57        .map_err(|e| CertError::CertGeneration(e.to_string()))?;
58
59    // 4. Ensure output directory exists
60    fs::create_dir_all(&args.output)?;
61
62    // 5. Write certificate and private key to files
63    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    // 6. Print summary
70    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}