← Week 2: Revocation: CRL and OCSP

Day 9: CRL Generation with rcgen

Phase 2 · June 25, 2026

← Week 2: Revocation: CRL and OCSP

Agenda (2–3 hours)

  • Read (20 min): rcgen CertificateRevocationListParams and RevokedCertParams
  • Build (120 min): Implement CrlStore::generate_crl() in revoke.rs
  • Verify (30 min): Parse the generated CRL with openssl crl and x509-parser
← Week 2: Revocation: CRL and OCSP

rcgen CRL API

use rcgen::{
    CertificateRevocationListParams, RevokedCertParams,
    RevocationReason, SerialNumber,
};

pub fn generate_crl(
    store: &CrlStore,
    issuer_ca: &Ca,
) -> anyhow::Result<Vec<u8>> {   // returns DER bytes
    let revoked: Vec<RevokedCertParams> = store.revoked
        .iter()
        .map(|entry| RevokedCertParams {
            serial_number: SerialNumber::from_slice(
                &hex::decode(&entry.serial_hex)?
            ),
            revocation_time: entry.revocation_time,
            reason_code: Some(entry.reason.to_rcgen()),
            invalidity_date: None,
        })
        .collect::<anyhow::Result<Vec<_>>>()?;

    let params = CertificateRevocationListParams {
        this_update: OffsetDateTime::now_utc(),
        next_update: OffsetDateTime::now_utc() + Duration::hours(24),
        crl_number:  SerialNumber::from_slice(&store.crl_number.to_be_bytes()),
        issuing_distribution_point: None,
        revoked_certs: revoked,
        alg: &rcgen::PKCS_ECDSA_P256_SHA256,
    };

    let crl = params.signed_by(&issuer_ca.cert, &issuer_ca.key)?;
    Ok(crl.der().to_vec())
}
← Week 2: Revocation: CRL and OCSP

Writing the CRL File

impl CrlStore {
    pub fn generate_and_save(&mut self, issuer: &Ca) -> anyhow::Result<()> {
        let crl_der = generate_crl(self, issuer)?;

        // Write DER
        let crl_path = self.issuer_path.join("intermediate.crl");
        std::fs::write(&crl_path, &crl_der)?;

        // Also write PEM for human inspection
        let pem = pem::encode(&pem::Pem::new("X509 CRL", crl_der.clone()));
        std::fs::write(crl_path.with_extension("pem"), pem)?;

        Ok(())
    }
}
← Week 2: Revocation: CRL and OCSP

Verifying the CRL with openssl

# Inspect the CRL
openssl crl -in intermediate.crl -inform DER -noout -text

# Verify the CRL signature against the intermediate CA
openssl crl -in intermediate.crl -inform DER \
  -CAfile intermediate/ca.crt -noout

# Verify a cert against the CRL
openssl verify -crl_check \
  -CAfile root/ca.crt \
  -untrusted intermediate/ca.crt \
  -CRLfile intermediate.crl \
  issued/0001.pem
← Week 2: Revocation: CRL and OCSP

Parsing with x509-parser

use x509_parser::prelude::*;

let (_, crl) = CertificateRevocationList::from_der(&crl_der)?;

println!("Issuer: {}", crl.issuer());
println!("This Update: {}", crl.last_update());
println!("Next Update: {:?}", crl.next_update());

for revoked in crl.iter_revoked_certificates() {
    println!("Revoked serial: {}", hex::encode(revoked.serial.to_bytes_be()));
    println!("Reason: {:?}", revoked.reason_code());
}
← Week 2: Revocation: CRL and OCSP

Challenge Assignment

Implement CrlStore::generate_and_save() in revoke.rs.

Write a test test_crl_generation:

  1. Create root → intermediate → 3 leaf certs
  2. Revoke leaf certs 1 and 3 with different reasons
  3. Generate the CRL
  4. Parse it with x509-parser and assert:
    • Serials 1 and 3 are present with correct reason codes
    • Serial 2 is NOT present
    • nextUpdate is approximately 24 hours from now
  5. Verify the CRL signature with openssl crl -CAfile via std::process::Command
← Week 2: Revocation: CRL and OCSP

Resources

  • rcgen CertificateRevocationListParams: docs.rs/rcgen
  • x509-parser CertificateRevocationList: docs.rs/x509-parser
  • pem crate for PEM encoding DER bytes
  • hex crate for serial number conversion