1769719620 english chat2026-devblog

Chat2026 part 8: certificate signing requests and certificate revocation lists

So in this post I'll modify the certificate making part of the program so it works using CSRs to issue more certificates. Then I make another program that can create CRLs to revoke them.

Handling certificate signing requests

So while I'm trying to write this, I've realized the rcgen has some cross-dependencies with rustls-pki-types. Another issue is that the documentation generated by cargo doc doesn't generate documentation for features that are disabled, so I have to use docs.rs always, this pretty much renders the offline generated documentation useless. Anyway it turns out I don't need to drag in the rustls-pki-types, but I only needed to enable x509-parser feature of the rcgen crate. So here is the program that uses rcgen to generate a CSR then sign it:

use rcgen::{
    CertificateParams, CertificateSigningRequestParams, DnType, ExtendedKeyUsagePurpose, Issuer,
    KeyPair,
};

pub type BoxedError = Box<dyn std::error::Error + Send + Sync>;
pub type GenResult<T> = Result<T, BoxedError>;

fn main() -> GenResult<()> {
    let mut args = std::env::args();
    args.next();
    let existing_ca_key_file = args.next().expect("Expected existing CA key name");
    let existing_ca_cert_file = args.next().expect("Expected existing cert file");
    let dev_key_name = args.next().expect("Expected device key name");
    let server_name = args.next().expect("Expected server name");

    // Create CSR
    let dev_key = KeyPair::generate()?;
    let mut device_cert_params = CertificateParams::new([server_name])?;
    device_cert_params
        .distinguished_name
        .push(DnType::CommonName, format!("{} device key.", dev_key_name));
    device_cert_params
        .extended_key_usages
        .push(ExtendedKeyUsagePurpose::ClientAuth);
    device_cert_params
        .extended_key_usages
        .push(ExtendedKeyUsagePurpose::ServerAuth);
    std::fs::write(
        format!("{}.pem", dev_key_name),
        dev_key.serialize_pem().as_bytes(),
    )?;
    let csr = device_cert_params.serialize_request(&dev_key)?;
    let csr_pem = csr.pem()?;

    // On the CA side sign the request.

    let csr_params = CertificateSigningRequestParams::from_pem(csr_pem.as_str())?;

    let ca_key_pem = std::fs::read_to_string(existing_ca_key_file)?;
    let ca_key = KeyPair::from_pem(ca_key_pem.as_str())?;

    let ca_cert_pem = std::fs::read_to_string(existing_ca_cert_file)?;
    let issuer = Issuer::from_ca_cert_pem(&ca_cert_pem, ca_key)?;

    let cert = csr_params.signed_by(&issuer)?;

    std::fs::write(format!("{}.crt", dev_key_name), cert.pem())?;

    println!("All is OK");

    Ok(())
}

The program takes the CA key, CA certificate, the name of the new certificate and the corresponding server name. Then it creates a new key, and a new CSR. On the CA side, this CSR is read, then an Issuer is created using the CA cert and key, which is then used on the CertificateSigningRequestParams to create a certificate.

This is committed and is at the commit f8df548d2c85909537bb6700b9e0f32994c9110a in the repo (https://git.jxzqj.com/chat2026.git).

Revoking certificates

The next program is used to revoke certificates, that is creating a CRL. The rcgen::CertificateRevocationListParams seems to be more straightforward. Just fill the struct and send it. But now I need to pull in the time create to create an OffsetDateTIme type as required by the struct... Then it seems the rcgen crate itself cannot load the certificates themselves or read details from them. So I cannot simply fill the RevokedCertParams structure, goddammit. So I added the x509_parser crate in order to read the certificate. Perhaps I shouldn't have enabled the x509_parser feature of the rcgen crate, if I'm using the parser crate anyway. So this is the program I've come up with:

use rcgen::{CertificateRevocationListParams, Issuer, KeyPair, RevokedCertParams, SerialNumber};
use time::{Duration, OffsetDateTime};
use x509_parser::pem::parse_x509_pem;

pub type BoxedError = Box<dyn std::error::Error + Send + Sync>;
pub type GenResult<T> = Result<T, BoxedError>;

fn main() -> GenResult<()> {
    let mut args = std::env::args();
    args.next();
    let existing_ca_key_file = args.next().expect("CA key file needed");
    let existing_ca_cert_file = args.next().expect("CA certificate file needed");
    let cert_file_to_revoke = args.next().expect("certificate file to revoke needed");
    let crl_file_to_make = args.next().expect("CRL file name needed");

    let ca_key_pem = std::fs::read_to_string(existing_ca_key_file)?;
    let ca_key = KeyPair::from_pem(ca_key_pem.as_str())?;
    let ca_cert_pem = std::fs::read_to_string(existing_ca_cert_file)?;
    let issuer = Issuer::from_ca_cert_pem(&ca_cert_pem, ca_key)?;

    let cert_pem = std::fs::read_to_string(cert_file_to_revoke)?;
    let (_, pem) = parse_x509_pem(cert_pem.as_bytes())?;
    let x509 = pem.parse_x509()?;
    let serial_bytes = x509.tbs_certificate.raw_serial();

    let revoked_cert_params = RevokedCertParams {
        serial_number: SerialNumber::from_slice(&serial_bytes),
        revocation_time: OffsetDateTime::now_utc(),
        reason_code: None,
        invalidity_date: None,
    };

    let crl_params = CertificateRevocationListParams {
        this_update: OffsetDateTime::now_utc(),
        next_update: OffsetDateTime::now_utc().saturating_add(Duration::days(100000)),
        crl_number: SerialNumber::from(12345),
        issuing_distribution_point: None,
        revoked_certs: vec![revoked_cert_params],
        key_identifier_method: rcgen::KeyIdMethod::Sha256,
    };

    let crl = crl_params.signed_by(&issuer)?;
    std::fs::write(crl_file_to_make, crl.pem()?)?;

    println!("Done!");
    Ok(())
}

It takes the CA key, CA certificate, the certificate file to revoke and the file to dump the PEM formatted .crl into. And it works and makes a CRL that can be parsed by OpenSSL. I've committed it, so we are at the commit e405f7791386510fd432c6949756d5ac54ffbfd8 in the repo. In the next post, I will try to modify the server and client example programs to check if they can properly handle the CRL and reject the revoked certificate.

Feedback

Posts

See the latest posts below, click the "..." to see them all. Click the tags to filter by tag. You can also subscribe to RSS in those lists.

Double entry bookkeeping explained - 1771536720 english finance

Chat2026 part 10: continuing application design - 1770850620 english chat2026-devblog

If you want privacy, please use a desktop PC - 1770575340 english privacy

YouTube is now practically unsearchable - 1770565140 english rants

Chat2026 part 9: using CRL in the server and client examples - 1770031740 english chat2026-devblog

...