Skip to content
ecdsa.com

Self-signed certificate · Rust

How to create a self-signed EC P-256 certificate with SANs in Rust

rcgen creates certificates in Rust: CertificateParams::new takes the SANs, the subject, validity, CA flag and key usages are fields of the params, and self_signed signs the certificate with a P-256 KeyPair. Several rcgen defaults do not suit a server certificate, so the recipe sets them explicitly. The run decodes cert.pem with OpenSSL and verifies it for localhost and 127.0.0.1.

Tested with rcgen 0.14.10 (Rust 1.99.1, default features (ring), time 0.3.55).

Cargo.toml — rcgen and time
[dependencies]
rcgen = "=0.14.10"
time = "=0.3.55" # OffsetDateTime for the validity period
src/main.rs — P-256 key and self-signed server certificate
use rcgen::{
CertificateParams, DistinguishedName, DnType, ExtendedKeyUsagePurpose, IsCa, KeyPair, KeyUsagePurpose,
PKCS_ECDSA_P256_SHA256,
};
use time::{Duration, OffsetDateTime};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let key = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256)?;
// Strings that parse as IP addresses become IP SANs, the rest DNS SANs
let mut params = CertificateParams::new(vec!["localhost".to_owned(), "127.0.0.1".to_owned()])?;
let mut subject = DistinguishedName::new();
subject.push(DnType::CommonName, "localhost");
params.distinguished_name = subject;
// The defaults are a 1975-to-4096 validity and no basicConstraints: set both
let now = OffsetDateTime::now_utc();
params.not_before = now;
params.not_after = now + Duration::days(365);
params.is_ca = IsCa::ExplicitNoCa; // writes CA:FALSE
params.key_usages = vec![KeyUsagePurpose::DigitalSignature];
params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ServerAuth];
let cert = params.self_signed(&key)?;
std::fs::write("cert.pem", cert.pem())?;
std::fs::write("key.pem", key.serialize_pem())?;
Ok(())
}

How it works

  1. KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256) creates the key and fixes the signature algorithm.
  2. CertificateParams::new makes an IP SAN of every string that parses as an IP address and a DNS SAN of the rest.
  3. not_before and not_after take a time::OffsetDateTime; IsCa::ExplicitNoCa writes a critical basicConstraints with CA:FALSE, and rcgen marks keyUsage critical as well.
  4. params.self_signed(&key) signs; cert.pem() returns the certificate and key.serialize_pem() the key as PKCS#8.

Gotchas

  • CertificateParams starts with defaults meant for tests: the subject CN=rcgen self signed cert and a validity from 1975 to 4096. rcgen::generate_simple_self_signed keeps all of them, plus no basicConstraints and no key usages.
  • With the default IsCa::NoCa, rcgen writes no basicConstraints extension at all; IsCa::ExplicitNoCa is what adds CA:FALSE.
  • Without serial_number, rcgen derives the serial from a SHA-256 hash of the public key, so two certificates for the same key get the same serial. Set params.serial_number when you re-issue for a kept key.
  • rcgen does not re-export the time crate: OffsetDateTime::now_utc() needs time as a direct dependency (rcgen's own date_time_ymd only builds fixed dates).

Related recipes