uselesskey-x509 0.9.1

X.509 self-signed and chain certificate fixtures with negative variants for tests.
Documentation
use rand_core::RngCore;
use rcgen::{
    BasicConstraints, CertificateParams, DnType, ExtendedKeyUsagePurpose, IsCa, KeyUsagePurpose,
    SerialNumber,
};
use time::Duration as TimeDuration;
use time::OffsetDateTime;

use crate::srp::derive::{
    deterministic_base_time_from_parts, deterministic_serial_number_with_rng,
};
use crate::srp::spec::{ChainSpec, KeyUsage, NotBeforeOffset};

pub(super) struct LeafParams {
    pub(super) params: CertificateParams,
    pub(super) serial_number: SerialNumber,
}

pub(super) fn deterministic_base_time(label: &str, spec: &ChainSpec) -> OffsetDateTime {
    let rsa_bits = (spec.rsa_bits as u32).to_be_bytes();
    deterministic_base_time_from_parts(&[
        label.as_bytes(),
        spec.leaf_cn.as_bytes(),
        spec.root_cn.as_bytes(),
        &rsa_bits,
    ])
}

pub(super) fn root_ca_params(
    spec: &ChainSpec,
    base_time: OffsetDateTime,
    rng: &mut impl RngCore,
) -> CertificateParams {
    let mut params = CertificateParams::default();
    params
        .distinguished_name
        .push(DnType::CommonName, spec.root_cn.clone());
    params.is_ca = IsCa::Ca(BasicConstraints::Constrained(1));
    params.key_usages = vec![
        KeyUsagePurpose::KeyCertSign,
        KeyUsagePurpose::CrlSign,
        KeyUsagePurpose::DigitalSignature,
    ];
    params.not_before = base_time - TimeDuration::days(1);
    params.not_after = params.not_before + TimeDuration::days(spec.root_validity_days as i64);
    params.serial_number = Some(next_serial_number(rng));
    params
}

pub(super) fn intermediate_ca_params(
    spec: &ChainSpec,
    base_time: OffsetDateTime,
    rng: &mut impl RngCore,
) -> CertificateParams {
    let mut params = CertificateParams::default();
    params
        .distinguished_name
        .push(DnType::CommonName, spec.intermediate_cn.clone());
    params.is_ca = if spec.intermediate_is_ca.unwrap_or(true) {
        IsCa::Ca(BasicConstraints::Constrained(0))
    } else {
        IsCa::NoCa
    };
    params.key_usages =
        key_usage_purposes(spec.intermediate_key_usage.unwrap_or_else(KeyUsage::ca));
    params.not_before = apply_not_before(base_time, spec.intermediate_not_before);
    params.not_after =
        params.not_before + TimeDuration::days(spec.intermediate_validity_days as i64);
    params.serial_number = Some(next_serial_number(rng));
    params
}

pub(super) fn leaf_params(
    spec: &ChainSpec,
    base_time: OffsetDateTime,
    rng: &mut impl RngCore,
) -> LeafParams {
    let mut params = CertificateParams::default();
    params
        .distinguished_name
        .push(DnType::CommonName, spec.leaf_cn.clone());
    params.is_ca = IsCa::NoCa;
    params.key_usages = vec![
        KeyUsagePurpose::DigitalSignature,
        KeyUsagePurpose::KeyEncipherment,
    ];
    params.extended_key_usages = vec![
        ExtendedKeyUsagePurpose::ServerAuth,
        ExtendedKeyUsagePurpose::ClientAuth,
    ];
    add_sorted_dns_sans(&mut params, &spec.leaf_sans);
    params.not_before = apply_not_before(base_time, spec.leaf_not_before);
    params.not_after = params.not_before + TimeDuration::days(spec.leaf_validity_days as i64);

    let serial_number = next_serial_number(rng);
    params.serial_number = Some(serial_number.clone());

    LeafParams {
        params,
        serial_number,
    }
}

fn add_sorted_dns_sans(params: &mut CertificateParams, sans: &[String]) {
    let mut sorted_sans = sans.to_vec();
    sorted_sans.sort();
    sorted_sans.dedup();

    for san in &sorted_sans {
        params.subject_alt_names.push(rcgen::SanType::DnsName(
            san.clone().try_into().expect("valid DNS name"),
        ));
    }
}

fn apply_not_before(base_time: OffsetDateTime, offset: Option<NotBeforeOffset>) -> OffsetDateTime {
    match offset.unwrap_or(NotBeforeOffset::DaysAgo(1)) {
        NotBeforeOffset::DaysAgo(days) => base_time - TimeDuration::days(days as i64),
        NotBeforeOffset::DaysFromNow(days) => base_time + TimeDuration::days(days as i64),
    }
}

fn key_usage_purposes(key_usage: KeyUsage) -> Vec<KeyUsagePurpose> {
    let mut purposes = Vec::new();
    if key_usage.key_cert_sign {
        purposes.push(KeyUsagePurpose::KeyCertSign);
    }
    if key_usage.crl_sign {
        purposes.push(KeyUsagePurpose::CrlSign);
    }
    if key_usage.digital_signature {
        purposes.push(KeyUsagePurpose::DigitalSignature);
    }
    if key_usage.key_encipherment {
        purposes.push(KeyUsagePurpose::KeyEncipherment);
    }
    purposes
}

fn next_serial_number(rng: &mut impl RngCore) -> SerialNumber {
    deterministic_serial_number_with_rng(|bytes| rng.fill_bytes(bytes))
}