proofmode 0.9.0

Capture, share, and preserve verifiable photos and videos
Documentation
use rcgen::{
    generate_simple_self_signed, CertificateParams, CertificateSigningRequestParams, KeyPair,
};

use super::error::{Result, SignError};

/// Generate a Certificate Signing Request (CSR) with the given Subject Alternative Names.
pub fn generate_csr(subject_alt_names: &[String]) -> Result<(String, String)> {
    let names: Vec<String> = if subject_alt_names.is_empty() {
        vec!["localhost".to_string()]
    } else {
        subject_alt_names.to_vec()
    };

    let key_pair = KeyPair::generate()
        .map_err(|e| SignError::CertificateError(format!("Failed to generate key pair: {}", e)))?;

    let private_key_pem = key_pair.serialize_pem();

    let cert_params = CertificateParams::new(names).map_err(|e| {
        SignError::CertificateError(format!("Failed to create certificate params: {}", e))
    })?;

    let csr = cert_params
        .serialize_request(&key_pair)
        .map_err(|e| SignError::CertificateError(format!("Failed to serialize CSR: {}", e)))?;

    let csr_pem = csr
        .pem()
        .map_err(|e| SignError::CertificateError(format!("Failed to get CSR PEM: {}", e)))?
        .to_string();

    Ok((csr_pem, private_key_pem))
}

/// Sign a CSR with a self-signed CA certificate for the given domain.
pub fn sign_csr(csr_pem: &str, domain: &str) -> Result<String> {
    let ca_cert = generate_simple_self_signed(vec![domain.to_string()]).map_err(|e| {
        SignError::CertificateError(format!("Failed to generate CA certificate: {}", e))
    })?;

    let csr_params = CertificateSigningRequestParams::from_pem(csr_pem)
        .map_err(|e| SignError::CertificateError(format!("Failed to parse CSR PEM: {}", e)))?;

    let signed_cert = csr_params
        .signed_by(&ca_cert.cert, &ca_cert.key_pair)
        .map_err(|e| SignError::CertificateError(format!("Failed to sign CSR: {}", e)))?;

    Ok(signed_cert.pem())
}

/// Generate a self-signed certificate for the given Subject Alternative Names.
pub fn create_self_signed_certificate(subject_alt_names: &[String]) -> Result<(String, String)> {
    let names: Vec<String> = if subject_alt_names.is_empty() {
        vec!["localhost".to_string()]
    } else {
        subject_alt_names.to_vec()
    };

    let cert = generate_simple_self_signed(names).map_err(|e| {
        SignError::CertificateError(format!("Failed to generate self-signed certificate: {}", e))
    })?;

    Ok((cert.cert.pem(), cert.key_pair.serialize_pem()))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_generate_csr() {
        let sans = vec!["example.com".to_string(), "localhost".to_string()];
        let (csr_pem, key_pem) = generate_csr(&sans).unwrap();
        assert!(csr_pem.contains("BEGIN CERTIFICATE REQUEST"));
        assert!(key_pem.contains("BEGIN PRIVATE KEY"));
    }

    #[test]
    fn test_sign_csr() {
        let sans = vec!["example.com".to_string()];
        let (csr_pem, _key_pem) = generate_csr(&sans).unwrap();
        let signed = sign_csr(&csr_pem, "example.com").unwrap();
        assert!(signed.contains("BEGIN CERTIFICATE"));
    }

    #[test]
    fn test_create_self_signed_certificate() {
        let sans = vec!["localhost".to_string()];
        let (cert_pem, key_pem) = create_self_signed_certificate(&sans).unwrap();
        assert!(cert_pem.contains("BEGIN CERTIFICATE"));
        assert!(key_pem.contains("BEGIN PRIVATE KEY"));
    }

    #[test]
    fn test_generate_csr_empty_sans() {
        let (csr_pem, _) = generate_csr(&[]).unwrap();
        assert!(csr_pem.contains("BEGIN CERTIFICATE REQUEST"));
    }
}