use std::sync::OnceLock;
use rcgen::{
BasicConstraints, CertificateParams, DistinguishedName, DnType, IsCa, KeyPair, KeyUsagePurpose,
PKCS_ECDSA_P256_SHA256,
};
use rustls_pki_types::CertificateDer;
use time::{Duration, OffsetDateTime};
use crate::error::{DigTlsError, Result};
pub const DIG_CA_CERT_PEM: &str = include_str!("ca/dig_ca.crt");
pub const DIG_CA_KEY_PEM: &str = include_str!("ca/dig_ca.key");
pub const CA_ORGANIZATION: &str = "DIG Network";
pub const CA_COMMON_NAME: &str = "DIG Network CA";
pub const CA_LIFETIME: Duration = Duration::days(365 * 100);
pub(crate) const CLOCK_SKEW_BACKDATE: Duration = Duration::hours(1);
#[derive(Clone)]
pub struct CaMaterial {
pub cert_pem: String,
pub key_pem: String,
}
pub fn generate_dig_ca(now: OffsetDateTime) -> Result<CaMaterial> {
let key = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256)
.map_err(|e| DigTlsError::CertGen(format!("generate CA key: {e}")))?;
let mut params = CertificateParams::new(Vec::<String>::new())
.map_err(|e| DigTlsError::CertGen(format!("CA params: {e}")))?;
params.not_before = now - CLOCK_SKEW_BACKDATE;
params.not_after = now + CA_LIFETIME;
let mut dn = DistinguishedName::new();
dn.push(DnType::CommonName, CA_COMMON_NAME);
dn.push(DnType::OrganizationName, CA_ORGANIZATION);
params.distinguished_name = dn;
params.is_ca = IsCa::Ca(BasicConstraints::Constrained(0));
params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign];
params.use_authority_key_identifier_extension = true;
let cert = params
.self_signed(&key)
.map_err(|e| DigTlsError::CertGen(format!("self-sign CA: {e}")))?;
Ok(CaMaterial {
cert_pem: cert.pem(),
key_pem: key.serialize_pem(),
})
}
pub struct DigCa {
pub(crate) cert: rcgen::Certificate,
pub(crate) key: KeyPair,
}
impl DigCa {
pub fn embedded() -> Result<Self> {
Self::from_pem(DIG_CA_CERT_PEM, DIG_CA_KEY_PEM)
}
pub fn from_pem(cert_pem: &str, key_pem: &str) -> Result<Self> {
let key = KeyPair::from_pem(key_pem)
.map_err(|e| DigTlsError::Ca(format!("parse CA key: {e}")))?;
let params = CertificateParams::from_ca_cert_pem(cert_pem)
.map_err(|e| DigTlsError::Ca(format!("parse CA cert: {e}")))?;
let cert = params
.self_signed(&key)
.map_err(|e| DigTlsError::Ca(format!("rematerialize CA issuer: {e}")))?;
Ok(Self { cert, key })
}
}
pub fn embedded_ca_cert_der() -> Result<CertificateDer<'static>> {
static DER: OnceLock<Vec<u8>> = OnceLock::new();
let der = DER.get_or_init(|| {
rustls_pemfile::certs(&mut DIG_CA_CERT_PEM.as_bytes())
.next()
.and_then(|r| r.ok())
.map(|c| c.to_vec())
.unwrap_or_default()
});
if der.is_empty() {
return Err(DigTlsError::Ca(
"embedded DigNetwork CA certificate is missing or unparseable".into(),
));
}
Ok(CertificateDer::from(der.clone()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generated_ca_round_trips_through_pem() {
let ca = generate_dig_ca(OffsetDateTime::now_utc()).expect("mint CA");
assert!(ca.cert_pem.contains("BEGIN CERTIFICATE"));
assert!(ca.key_pem.contains("PRIVATE KEY"));
DigCa::from_pem(&ca.cert_pem, &ca.key_pem).expect("load generated CA");
}
#[test]
fn embedded_ca_loads_and_parses_to_der() {
DigCa::embedded().expect("the shipped CA loads");
let der = embedded_ca_cert_der().expect("shipped CA parses to DER");
assert!(!der.as_ref().is_empty());
}
}