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.
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).
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.
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 - english finance
Chat2026 part 10: continuing application design - english chat2026-devblog
If you want privacy, please use a desktop PC - english privacy
YouTube is now practically unsearchable - english rants
Chat2026 part 9: using CRL in the server and client examples - english chat2026-devblog