[dependencies]rcgen = "=0.14.10" # default features: ring backend and PEM outputuse rcgen::{CertificateParams, DistinguishedName, DnType, KeyPair, PKCS_ECDSA_P256_SHA256}; fn main() -> Result<(), Box<dyn std::error::Error>> { let key = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256)?; // new() takes the SANs (DNS names or IP addresses) let mut params = CertificateParams::new(vec!["api.example.com".to_owned(), "www.example.com".to_owned()])?; // Replace the default subject ("CN=rcgen self signed cert") let mut subject = DistinguishedName::new(); subject.push(DnType::CountryName, "US"); subject.push(DnType::OrganizationName, "Example Corp"); subject.push(DnType::CommonName, "api.example.com"); params.distinguished_name = subject; let csr = params.serialize_request(&key)?; std::fs::write("csr.pem", csr.pem()?)?; std::fs::write("key.pem", key.serialize_pem())?; // PKCS#8 Ok(())}How it works
KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256)creates the key; the algorithm constant also fixes the signature algorithm.CertificateParams::new(vec![…])turns each string into a DNS SAN, or an IP SAN if it parses as an IP address.DistinguishedName::new()pluspushbuilds the subject in the order given.serialize_request(&key)returns the CSR;csr.pem()andkey.serialize_pem()write the PEM files.
Gotchas
CertificateParamsstarts with the subjectCN=rcgen self signed cert. Forgetting to replacedistinguished_namesends that name to the CA.CertificateParamsalso carries validity, key usages and CA flags that a CSR does not transmit; for a CSR only the subject, the SANs and the extensions requested matter.- rcgen's default features pull in
ring; on platforms where ring does not build, usedefault-features = falsewith theaws_lc_rsandpemfeatures. serialize_requestis not deterministic — two calls give different signatures for the same input — so do not compare CSR bytes in tests.