Skip to main content

netget/server/acme/
ca.rs

1//! The test CA behind the ACME server: a P-256 root generated at startup, CSR checks and
2//! issuance with rcgen.
3use anyhow::{ensure, Context, Result};
4use rcgen::{
5    BasicConstraints, CertificateParams, CertificateSigningRequestParams, CertifiedIssuer, DnType,
6    ExtendedKeyUsagePurpose, IsCa, KeyPair, KeyUsagePurpose, SanType, SerialNumber,
7};
8use ring::rand::SecureRandom;
9
10pub struct Ca {
11    issuer: CertifiedIssuer<'static, KeyPair>,
12}
13
14/// What a CSR asks for.
15pub struct CsrSummary {
16    pub dns_names: Vec<String>,
17    pub common_name: Option<String>,
18    pub other_names: usize,
19    params: CertificateSigningRequestParams,
20}
21
22/// An issued certificate.
23pub struct Issued {
24    pub der: Vec<u8>,
25    pub pem: String,
26    pub serial: String,
27    pub not_after: String,
28}
29
30impl Ca {
31    pub fn new(name: &str) -> Result<Self> {
32        let key = KeyPair::generate()?;
33        let mut params = CertificateParams::new(Vec::<String>::new())?;
34        params.distinguished_name.push(DnType::CommonName, name);
35        params.is_ca = IsCa::Ca(BasicConstraints::Constrained(0));
36        params.key_usages = vec![
37            KeyUsagePurpose::KeyCertSign,
38            KeyUsagePurpose::CrlSign,
39            KeyUsagePurpose::DigitalSignature,
40        ];
41        let now = time::OffsetDateTime::now_utc();
42        params.not_before = now - time::Duration::hours(1);
43        params.not_after = now + time::Duration::days(3650);
44        Ok(Self {
45            issuer: CertifiedIssuer::self_signed(params, key)?,
46        })
47    }
48
49    pub fn pem(&self) -> String {
50        self.issuer.pem()
51    }
52
53    /// Parse a DER CSR and verify its self-signature.
54    pub fn read_csr(der: &[u8]) -> Result<CsrSummary> {
55        ensure!(der.len() <= 16 * 1024, "CSR over 16 KiB");
56        let params = CertificateSigningRequestParams::from_der(&der.to_vec().into())
57            .ok()
58            .context("CSR does not parse or its signature does not verify")?;
59        let mut dns_names = Vec::new();
60        let mut other_names = 0;
61        for san in &params.params.subject_alt_names {
62            match san {
63                SanType::DnsName(n) => dns_names.push(n.as_str().to_ascii_lowercase()),
64                _ => other_names += 1,
65            }
66        }
67        let common_name = params
68            .params
69            .distinguished_name
70            .get(&DnType::CommonName)
71            .map(dn_text);
72        Ok(CsrSummary {
73            dns_names,
74            common_name,
75            other_names,
76            params,
77        })
78    }
79
80    pub fn issue(&self, csr: CsrSummary, names: &[String], validity_days: u32) -> Result<Issued> {
81        let mut params = CertificateParams::new(names.to_vec())?;
82        params
83            .distinguished_name
84            .push(DnType::CommonName, names[0].as_str());
85        let mut serial = [0u8; 16];
86        ring::rand::SystemRandom::new()
87            .fill(&mut serial)
88            .ok()
89            .context("no randomness")?;
90        serial[0] &= 0x7f;
91        params.serial_number = Some(SerialNumber::from_slice(&serial));
92        let now = time::OffsetDateTime::now_utc();
93        params.not_before = now - time::Duration::minutes(1);
94        params.not_after = now + time::Duration::days(validity_days as i64);
95        // NoCa omits basicConstraints: an explicit cA FALSE is a DER default and is refused
96        // by strict parsers (python cryptography, so certbot).
97        params.is_ca = IsCa::NoCa;
98        params.key_usages = vec![
99            KeyUsagePurpose::DigitalSignature,
100            KeyUsagePurpose::KeyEncipherment,
101        ];
102        params.extended_key_usages = vec![
103            ExtendedKeyUsagePurpose::ServerAuth,
104            ExtendedKeyUsagePurpose::ClientAuth,
105        ];
106        params.use_authority_key_identifier_extension = true;
107        let not_after = params
108            .not_after
109            .format(&time::format_description::well_known::Rfc3339)?;
110        let request = CertificateSigningRequestParams {
111            params,
112            public_key: csr.params.public_key,
113        };
114        let cert = request.signed_by(&self.issuer)?;
115        Ok(Issued {
116            der: cert.der().to_vec(),
117            pem: format!("{}{}", cert.pem(), self.issuer.pem()),
118            serial: serial.iter().map(|b| format!("{b:02x}")).collect(),
119            not_after,
120        })
121    }
122}
123
124fn dn_text(v: &rcgen::DnValue) -> String {
125    match v {
126        rcgen::DnValue::Utf8String(s) => s.clone(),
127        rcgen::DnValue::PrintableString(s) => s.as_str().to_owned(),
128        rcgen::DnValue::Ia5String(s) => s.as_str().to_owned(),
129        rcgen::DnValue::TeletexString(s) => s.as_str().to_owned(),
130        _ => String::new(),
131    }
132}