use time::{Duration, OffsetDateTime};
use rcgen::{
BasicConstraints, CertificateParams, DnType, IsCa, KeyPair, KeyUsagePurpose, SerialNumber,
};
use ring::rand::{SecureRandom, SystemRandom};
use crate::config::LocalCaSubjectConfig;
use super::{CA_VALIDITY_DAYS, CLOCK_SKEW_ALLOWANCE};
pub(super) const DEFAULT_CA_COMMON_NAME: &str = "acme-proxy local CA";
pub(super) fn build_ca_distinguished_name(
subject: &LocalCaSubjectConfig,
) -> rcgen::DistinguishedName {
fn present(value: &Option<String>) -> Option<&str> {
match value.as_deref() {
None | Some("") => None,
Some(s) => Some(s),
}
}
let mut dn = rcgen::DistinguishedName::new();
dn.push(
DnType::CommonName,
present(&subject.common_name).unwrap_or(DEFAULT_CA_COMMON_NAME),
);
if let Some(v) = present(&subject.organization) {
dn.push(DnType::OrganizationName, v);
}
if let Some(v) = present(&subject.organizational_unit) {
dn.push(DnType::OrganizationalUnitName, v);
}
if let Some(v) = present(&subject.country) {
dn.push(DnType::CountryName, v);
}
if let Some(v) = present(&subject.state) {
dn.push(DnType::StateOrProvinceName, v);
}
if let Some(v) = present(&subject.locality) {
dn.push(DnType::LocalityName, v);
}
dn
}
pub(super) fn generate_ca(
key_type: &str,
subject: &LocalCaSubjectConfig,
) -> anyhow::Result<(KeyPair, String)> {
let key_pair = match key_type {
"ecdsa-p256" => KeyPair::generate()?,
other => anyhow::bail!("unsupported local_ca key_type: {other}"),
};
let mut params = CertificateParams::new(Vec::<String>::new())?;
params.is_ca = IsCa::Ca(BasicConstraints::Constrained(0));
params.distinguished_name = build_ca_distinguished_name(subject);
params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign];
let now = OffsetDateTime::now_utc();
params.not_before = now - CLOCK_SKEW_ALLOWANCE;
params.not_after = now + Duration::days(CA_VALIDITY_DAYS);
params.serial_number = Some(random_serial()?);
let ca_cert = params.self_signed(&key_pair)?;
Ok((key_pair, ca_cert.pem()))
}
pub(super) fn random_serial() -> anyhow::Result<SerialNumber> {
let mut bytes = [0u8; 16];
SystemRandom::new()
.fill(&mut bytes)
.map_err(|_| anyhow::anyhow!("system RNG unavailable"))?;
bytes[0] &= 0x7f;
Ok(SerialNumber::from_slice(&bytes))
}