X.509 Certificates
Overview
X509 wraps OpenSSL’s X509* structure, which is reference-counted. It can be
cloned cheaply (via X509_up_ref) and shared across threads.
This module also provides:
X509Store— a trust store for chain verificationX509StoreCtx— the verification context that consumes store + certificateX509Crl— a certificate revocation list (CRL)
For PKCS#12 bundles (combining key, certificate, and CA chain), see the PKCS#12 guide. For OCSP response handling, see the OCSP guide.
X509 — Certificate Wrapper
#![allow(unused)]
fn main() {
pub struct X509 { /* X509* */ }
impl X509 {
// ── Construction ──────────────────────────────────────────────────────────
/// Create a new, empty X509 bound to an explicit library context.
/// Use when FIPS-awareness is required. propq is always NULL (default properties).
pub fn new_in(ctx: &Arc<LibCtx>) -> Result<Self, ErrorStack>;
// ── Loading ───────────────────────────────────────────────────────────────
/// Parse from PEM bytes. Zero-copy: uses MemBioBuf (BIO_new_mem_buf).
pub fn from_pem(pem: &[u8]) -> Result<Self, ErrorStack>;
/// Parse from PEM within a specific library context.
/// Note: delegates to from_pem (PEM_read_bio_X509_ex does not exist in OpenSSL 3.5).
pub fn from_pem_in(_ctx: &Arc<LibCtx>, pem: &[u8]) -> Result<Self, ErrorStack>;
/// Parse from a DER byte slice. The full slice is consumed.
pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack>;
// ── Serialization ─────────────────────────────────────────────────────────
/// Encode as PEM into a freshly allocated Vec<u8>.
pub fn to_pem(&self) -> Result<Vec<u8>, ErrorStack>;
/// Encode as DER into a freshly allocated Vec<u8>.
pub fn to_der(&self) -> Result<Vec<u8>, ErrorStack>;
// ── Field Accessors ───────────────────────────────────────────────────────
/// Subject distinguished name. Borrowed, valid while self is alive.
pub fn subject_name(&self) -> X509Name<'_>;
/// Issuer distinguished name. Borrowed.
pub fn issuer_name(&self) -> X509Name<'_>;
/// Serial number as i64. Returns None if the serial number exceeds i64::MAX.
pub fn serial_number(&self) -> Option<i64>;
/// Serial number as raw big-endian bytes (content octets only, no DER tag/length).
/// Handles serials that exceed i64::MAX (up to 20 bytes per RFC 5280).
/// Returns None if the serial field is absent.
pub fn serial_number_bytes(&self) -> Option<Vec<u8>>;
/// notBefore validity as a human-readable string ("Mmm DD HH:MM:SS YYYY GMT").
pub fn not_before_str(&self) -> Option<String>;
/// notAfter validity as a human-readable string.
pub fn not_after_str(&self) -> Option<String>;
/// notBefore as a structured BrokenDownTime (year/month/day/hour/minute/second in UTC).
pub fn not_before_tm(&self) -> Option<BrokenDownTime>;
/// notAfter as a structured BrokenDownTime.
pub fn not_after_tm(&self) -> Option<BrokenDownTime>;
/// true if the current time is within [notBefore, notAfter].
pub fn is_valid_now(&self) -> bool;
// ── Public Key ────────────────────────────────────────────────────────────
/// Extract the public key (owned Pkey<Public>).
/// Calls X509_get_pubkey; the returned key is independently reference-counted.
pub fn public_key(&self) -> Result<Pkey<Public>, ErrorStack>;
/// Check whether the certificate's public key uses the named algorithm.
/// Uses X509_get0_pubkey — no refcount increment. Returns false if absent.
pub fn public_key_is_a(&self, alg: &CStr) -> bool;
/// Bit size of the certificate's public key. Uses X509_get0_pubkey — no refcount
/// increment. Returns None if the certificate has no public key.
pub fn public_key_bits(&self) -> Option<u32>;
// ── Extensions ────────────────────────────────────────────────────────────
/// Number of extensions.
pub fn extension_count(&self) -> usize;
/// Extension at zero-based index. Returns None if out of range.
pub fn extension(&self, idx: usize) -> Option<X509Extension<'_>>;
/// First extension with the given NID. Returns None if absent.
pub fn extension_by_nid(&self, nid: i32) -> Option<X509Extension<'_>>;
/// DER-encoded value bytes of the first extension with the given NID.
/// Zero-copy: borrows from the certificate's internal storage.
/// Returns None if the extension is absent.
pub fn extension_der(&self, nid: i32) -> Option<&[u8]>;
// ── Verification ──────────────────────────────────────────────────────────
/// Verify that this certificate was signed by key.
/// Returns Ok(true) if valid, Ok(false) if not, Err on fatal error.
pub fn verify(&self, key: &Pkey<Public>) -> Result<bool, ErrorStack>;
/// true if subject name == issuer name (name match only; no signature check).
pub fn is_self_signed(&self) -> bool;
/// Inspect the signature algorithm fields of this certificate.
/// Returns md_nid (digest), pk_nid (public-key algorithm), and security_bits.
/// md_nid is 0 (NID_undef) for algorithms with no separate pre-hash step
/// (e.g. Ed25519, ML-DSA).
pub fn signature_info(&self) -> Result<SignatureInfo, ErrorStack>;
}
impl Clone for X509 { /* X509_up_ref */ }
impl Drop for X509 { /* X509_free */ }
unsafe impl Send for X509 {}
unsafe impl Sync for X509 {}
}
X509Name<'cert> — Distinguished Name
#![allow(unused)]
fn main() {
/// A borrowed distinguished name, lifetime-tied to the owning X509.
pub struct X509Name<'cert> { /* X509_NAME* */ }
impl X509Name<'_> {
/// Number of RDN entries.
pub fn entry_count(&self) -> usize;
/// Entry at zero-based index. Returns None if out of range.
pub fn entry(&self, idx: usize) -> Option<X509NameEntry<'_>>;
/// Format as a string using X509_NAME_print_ex with XN_FLAG_COMPAT.
/// Returns None if the BIO write fails.
pub fn to_string(&self) -> Option<String>;
/// Return the legacy one-line `/CN=.../O=.../C=...` representation
/// produced by X509_NAME_oneline. Useful for logging and debugging.
/// Prefer to_string() or entry() for structured access.
/// Returns None if OpenSSL cannot allocate the string.
pub fn to_oneline(&self) -> Option<String>;
}
}
X509NameEntry<'name> — One RDN Component
#![allow(unused)]
fn main() {
pub struct X509NameEntry<'name> { /* X509_NAME_ENTRY* */ }
impl X509NameEntry<'_> {
/// NID of this field (e.g. NID_commonName = 13).
pub fn nid(&self) -> i32;
/// Raw DER-encoded value bytes.
pub fn data(&self) -> &[u8];
}
}
X509Extension<'cert> — Certificate Extension
#![allow(unused)]
fn main() {
pub struct X509Extension<'cert> { /* X509_EXTENSION* */ }
impl X509Extension<'_> {
/// NID of this extension.
pub fn nid(&self) -> i32;
/// true if this extension is marked critical.
pub fn is_critical(&self) -> bool;
/// Raw DER-encoded extension value bytes.
pub fn data(&self) -> &[u8];
}
}
KeyUsage — Key Usage Bitmask
The KeyUsage newtype wraps a u32 bitmask decoded from the certificate’s
keyUsage extension (RFC 5280 § 4.2.1.3). Returned by [X509::key_usage].
#![allow(unused)]
fn main() {
pub struct KeyUsage(pub u32);
impl KeyUsage {
/// `digitalSignature` — signing data and authentication.
pub const DIGITAL_SIGNATURE: Self = Self(0x0080);
/// `nonRepudiation` (`contentCommitment` in RFC 5280).
pub const NON_REPUDIATION: Self = Self(0x0040);
/// `keyEncipherment` — encrypting symmetric keys.
pub const KEY_ENCIPHERMENT: Self = Self(0x0020);
/// `dataEncipherment` — directly encrypting data.
pub const DATA_ENCIPHERMENT: Self = Self(0x0010);
/// `keyAgreement` — Diffie-Hellman key exchange.
pub const KEY_AGREEMENT: Self = Self(0x0008);
/// `keyCertSign` — signing certificates.
pub const KEY_CERT_SIGN: Self = Self(0x0004);
/// `cRLSign` — signing CRLs.
pub const CRL_SIGN: Self = Self(0x0002);
/// `encipherOnly` — only encrypt during key agreement.
pub const ENCIPHER_ONLY: Self = Self(0x0001);
/// `decipherOnly` — only decrypt during key agreement.
pub const DECIPHER_ONLY: Self = Self(0x8000);
/// Return `true` if all bits in `flag` are set in `self`.
pub fn contains(self, flag: Self) -> bool;
/// Return the raw bitmask value.
pub fn bits(self) -> u32;
}
// Implements BitOr and BitAnd for combining / intersecting flags.
}
KeyUsage derives Clone, Copy, PartialEq, Eq, and Debug.
Usage example:
#![allow(unused)]
fn main() {
if let Some(ku) = cert.key_usage() {
if ku.contains(KeyUsage::DIGITAL_SIGNATURE) {
println!("certificate can sign data");
}
if ku.contains(KeyUsage::KEY_CERT_SIGN) {
println!("certificate can sign other certificates");
}
}
}
GeneralName — Subject Alternative Name Entry
GeneralName is returned as elements of the Vec from [X509::subject_alt_names].
It corresponds to the GeneralName choice type in RFC 5280 § 4.2.1.6.
#![allow(unused)]
fn main() {
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GeneralName {
/// `dNSName` — a DNS hostname (e.g. `"example.com"`).
Dns(String),
/// `rfc822Name` — an email address.
Email(String),
/// `uniformResourceIdentifier` — a URI.
Uri(String),
/// `iPAddress` — 4 bytes for IPv4, 16 bytes for IPv6.
IpAddress(Vec<u8>),
/// `registeredID` — an OID.
RegisteredId(Oid),
/// `otherName` — application-specific name with a type OID and DER value.
OtherName {
/// The type OID identifying the value schema.
type_id: Oid,
/// DER-encoded value bytes (the full `ASN1_TYPE` encoding).
value: Vec<u8>,
},
}
}
DirectoryName, X400Address, and EdiPartyName entries are silently skipped
(they are uncommon in practice).
New X509 extension methods
These three methods decode specific standard extensions and are available on every
X509 value:
#![allow(unused)]
fn main() {
impl X509 {
/// Key usage bits from the certificate's `keyUsage` extension.
///
/// Returns `None` if the extension is absent. Backed by
/// `X509_get_key_usage`; the extension is decoded once and cached by OpenSSL.
pub fn key_usage(&self) -> Option<KeyUsage>;
/// OIDs from the certificate's `extendedKeyUsage` extension.
///
/// Returns an empty `Vec` if the extension is absent. Each `Oid`
/// identifies one extended key purpose (e.g. id-kp-serverAuth,
/// id-kp-clientAuth, or a vendor-specific OID not in the NID table).
pub fn extended_key_usage_oids(&self) -> Result<Vec<Oid>, ErrorStack>;
/// Decoded Subject Alternative Name entries.
///
/// Returns an empty `Vec` if the `subjectAltName` extension is absent.
pub fn subject_alt_names(&self) -> Result<Vec<GeneralName>, ErrorStack>;
}
}
For vendor-specific EKU OIDs that have no standard NID, compare using
Oid::from_text (see the Object Identifiers guide):
#![allow(unused)]
fn main() {
use native_ossl::obj::Oid;
let target = Oid::from_text("1.3.6.1.4.1.311.20.2.2").unwrap(); // MS Smart Card Logon
for oid in cert.extended_key_usage_oids()? {
if oid == target {
println!("certificate has Smart Card Logon EKU");
}
}
}
Building Certificates
X509NameOwned — Mutable Distinguished Name
#![allow(unused)]
fn main() {
/// An owned, mutable distinguished name for use with X509Builder.
pub struct X509NameOwned { /* X509_NAME* */ }
impl X509NameOwned {
pub fn new() -> Result<Self, ErrorStack>;
/// Append a field. `field` is a short name (c"CN", c"O", c"C").
/// `value` is a UTF-8 byte slice.
pub fn add_entry_by_txt(&mut self, field: &CStr, value: &[u8])
-> Result<(), ErrorStack>;
}
}
X509Builder
#![allow(unused)]
fn main() {
pub struct X509Builder { /* X509* under construction */ }
impl X509Builder {
pub fn new() -> Result<Self, ErrorStack>;
/// Set version: 0=v1, 1=v2, 2=v3.
pub fn set_version(self, version: i64) -> Result<Self, ErrorStack>;
pub fn set_serial_number(self, n: i64) -> Result<Self, ErrorStack>;
/// Set notBefore to now + offset_secs.
pub fn set_not_before_offset(self, offset_secs: i64) -> Result<Self, ErrorStack>;
/// Set notAfter to now + offset_secs.
pub fn set_not_after_offset(self, offset_secs: i64) -> Result<Self, ErrorStack>;
pub fn set_subject_name(self, name: &X509NameOwned) -> Result<Self, ErrorStack>;
pub fn set_issuer_name(self, name: &X509NameOwned) -> Result<Self, ErrorStack>;
pub fn set_public_key<T: HasPublic>(self, key: &Pkey<T>) -> Result<Self, ErrorStack>;
/// Sign the certificate.
/// Pass digest = None for EdDSA (Ed25519, Ed448).
pub fn sign(self, key: &Pkey<Private>, digest: Option<&DigestAlg>)
-> Result<Self, ErrorStack>;
/// Finalise and return the X509.
pub fn build(self) -> X509;
}
}
X509Store — Trust Store
An X509Store holds trusted CA certificates and CRLs. Pass it to
X509StoreCtx::init to verify a certificate chain, or to
OcspResponse::basic_verify to verify an OCSP response signature.
#![allow(unused)]
fn main() {
pub struct X509Store { /* X509_STORE* */ }
impl X509Store {
/// Create an empty trust store.
pub fn new() -> Result<Self, ErrorStack>;
/// Add a trusted certificate.
/// The certificate's reference count is incremented internally.
pub fn add_cert(&mut self, cert: &X509) -> Result<(), ErrorStack>;
/// Add a CRL to the store.
pub fn add_crl(&mut self, crl: &X509Crl) -> Result<(), ErrorStack>;
/// Set verification flags (e.g. X509_V_FLAG_CRL_CHECK).
pub fn set_flags(&mut self, flags: u64) -> Result<(), ErrorStack>;
}
impl Clone for X509Store { /* X509_STORE_up_ref */ }
}
X509Store is Clone (via X509_STORE_up_ref) and Send + Sync.
X509StoreCtx — Chain Verification Context
X509StoreCtx drives X509_verify_cert. It is single-use: create, initialise,
verify, inspect, then drop.
#![allow(unused)]
fn main() {
pub struct X509StoreCtx { /* X509_STORE_CTX* */ }
impl X509StoreCtx {
/// Allocate a new, uninitialised verification context.
pub fn new() -> Result<Self, ErrorStack>;
/// Initialise for verifying `cert` against `store`.
/// Call before `verify`.
pub fn init(&mut self, store: &X509Store, cert: &X509) -> Result<(), ErrorStack>;
/// Verify the certificate chain.
///
/// Returns Ok(true) if valid, Ok(false) if not (call `error()` for the
/// error code), or Err on a fatal OpenSSL error.
pub fn verify(&mut self) -> Result<bool, ErrorStack>;
/// OpenSSL verification error code after a failed `verify`.
/// Returns 0 (X509_V_OK) if no error occurred.
/// See <openssl/x509_vfy.h> for X509_V_ERR_* constants.
pub fn error(&self) -> i32;
/// Collect the verified chain as owned X509 values.
/// Only meaningful after a successful `verify`.
pub fn chain(&self) -> Vec<X509>;
}
}
X509Crl — Certificate Revocation List
#![allow(unused)]
fn main() {
pub struct X509Crl { /* X509_CRL* */ }
impl X509Crl {
/// Allocate a new, empty CRL structure (X509_CRL_new).
pub fn new() -> Result<Self, ErrorStack>;
/// Allocate a new, empty CRL bound to a library context (X509_CRL_new_ex).
pub fn new_in(ctx: &Arc<LibCtx>) -> Result<Self, ErrorStack>;
/// Parse from PEM bytes.
pub fn from_pem(pem: &[u8]) -> Result<Self, ErrorStack>;
/// Parse from DER bytes.
pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack>;
/// Serialise to PEM.
pub fn to_pem(&self) -> Result<Vec<u8>, ErrorStack>;
/// Serialise to DER.
pub fn to_der(&self) -> Result<Vec<u8>, ErrorStack>;
/// Issuer distinguished name (borrowed).
pub fn issuer_name(&self) -> X509Name<'_>;
/// `thisUpdate` field as a human-readable string.
pub fn last_update_str(&self) -> Option<String>;
/// `nextUpdate` field as a human-readable string.
pub fn next_update_str(&self) -> Option<String>;
/// Verify this CRL was signed by `key`.
/// Returns Ok(true) if valid, Ok(false) if not.
pub fn verify(&self, key: &Pkey<Public>) -> Result<bool, ErrorStack>;
}
impl Clone for X509Crl { /* X509_CRL_up_ref */ }
}
X509Crl is Clone (via X509_CRL_up_ref) and Send + Sync.
NID Lookup Free Functions
OpenSSL uses numeric identifiers (NIDs) to represent OIDs internally. Two free
functions in native_ossl::x509 convert human-readable names to NIDs:
#![allow(unused)]
fn main() {
/// Look up a NID by short name (e.g. c"sha256", c"CN", c"rsaEncryption").
/// Returns None if the name is not in OpenSSL's object table.
pub fn nid_from_short_name(sn: &CStr) -> Option<i32>;
/// Look up a NID by OID text or short name.
/// Accepts dotted decimal ("2.5.4.3") or short name ("CN").
/// Returns None if the string is not recognised.
pub fn nid_from_text(s: &CStr) -> Option<i32>;
}
These wrap OBJ_sn2nid and OBJ_txt2nid respectively. Use them when you have a
NID-based API (e.g. X509::extension_by_nid) but only know the algorithm by name:
#![allow(unused)]
fn main() {
use native_ossl::x509::nid_from_short_name;
// Find Subject Key Identifier extension by NID.
let nid = nid_from_short_name(c"subjectKeyIdentifier").unwrap();
let ext = cert.extension_by_nid(nid);
}
Examples
Create an X509 Bound to a Library Context
#![allow(unused)]
fn main() {
use std::sync::Arc;
use native_ossl::lib_ctx::LibCtx;
use native_ossl::x509::X509;
let ctx = Arc::new(LibCtx::new()?);
let _prov = ctx.load_provider(c"default")?;
let cert = X509::new_in(&ctx)?;
// cert is now associated with ctx for any provider-dispatched operations.
}
Load and Inspect a Certificate
#![allow(unused)]
fn main() {
use native_ossl::x509::X509;
let pem = std::fs::read("cert.pem")?;
let cert = X509::from_pem(&pem)?;
// Print subject
if let Some(subject) = cert.subject_name().to_string() {
println!("Subject: {subject}");
}
// Print validity
println!("Not before: {:?}", cert.not_before_str());
println!("Not after: {:?}", cert.not_after_str());
println!("Valid now: {}", cert.is_valid_now());
// Serial number
if let Some(serial) = cert.serial_number() {
println!("Serial: {serial}");
}
}
Inspect Name Entries Manually
#![allow(unused)]
fn main() {
let name = cert.subject_name();
for i in 0..name.entry_count() {
if let Some(entry) = name.entry(i) {
println!(" NID {}: {:?}", entry.nid(), entry.data());
}
}
}
Build a Self-Signed Certificate (Ed25519)
#![allow(unused)]
fn main() {
use native_ossl::pkey::KeygenCtx;
use native_ossl::x509::{X509Builder, X509NameOwned};
let key = KeygenCtx::new(c"ED25519")?.generate()?;
let mut name = X509NameOwned::new()?;
name.add_entry_by_txt(c"CN", b"example.com")?;
name.add_entry_by_txt(c"O", b"Example Corp")?;
let cert = X509Builder::new()?
.set_version(2)? // X.509v3
.set_serial_number(1)?
.set_not_before_offset(0)? // valid from now
.set_not_after_offset(365 * 86400)? // valid for one year
.set_subject_name(&name)?
.set_issuer_name(&name)? // self-signed
.set_public_key(&key)?
.sign(&key, None)? // None = no separate digest (EdDSA)
.build();
let pem = cert.to_pem()?;
}
Build a Self-Signed Certificate (RSA)
#![allow(unused)]
fn main() {
use native_ossl::pkey::KeygenCtx;
use native_ossl::digest::DigestAlg;
use native_ossl::x509::{X509Builder, X509NameOwned};
let key = KeygenCtx::new(c"RSA")?.generate()?;
let sha256 = DigestAlg::fetch(c"SHA2-256", None)?;
let mut name = X509NameOwned::new()?;
name.add_entry_by_txt(c"CN", b"example.com")?;
let cert = X509Builder::new()?
.set_version(2)?
.set_serial_number(1)?
.set_not_before_offset(0)?
.set_not_after_offset(365 * 86400)?
.set_subject_name(&name)?
.set_issuer_name(&name)?
.set_public_key(&key)?
.sign(&key, Some(&sha256))? // RSA requires an explicit digest
.build();
}
Verify a Certificate Signature
#![allow(unused)]
fn main() {
let cert = X509::from_pem(&pem)?;
let ca_cert = X509::from_pem(&ca_pem)?;
let ca_key = ca_cert.public_key()?;
match cert.verify(&ca_key)? {
true => println!("Signature valid"),
false => println!("Signature INVALID"),
}
}
Chain Verification with X509Store
use native_ossl::x509::{X509, X509Store, X509StoreCtx};
// Build a trust store from PEM-encoded CA certificates.
let mut store = X509Store::new()?;
for ca_pem in &ca_pem_list {
let ca = X509::from_pem(ca_pem)?;
store.add_cert(&ca)?;
}
// Verify an end-entity certificate against the store.
let cert = X509::from_pem(&ee_pem)?;
let mut ctx = X509StoreCtx::new()?;
ctx.init(&store, &cert)?;
match ctx.verify()? {
true => {
println!("Chain valid");
// Inspect the verified chain (leaf first).
for c in ctx.chain() {
println!(" {}", c.subject_name().to_string().unwrap_or_default());
}
}
false => {
println!("Chain INVALID: error code {}", ctx.error());
}
}
Load and Verify a CRL
use native_ossl::x509::{X509, X509Crl};
let crl_pem = std::fs::read("crl.pem")?;
let crl = X509Crl::from_pem(&crl_pem)?;
println!("Issuer: {}", crl.issuer_name().to_string().unwrap_or_default());
println!("This update: {:?}", crl.last_update_str());
println!("Next update: {:?}", crl.next_update_str());
// Verify the CRL signature with the issuer's public key.
let ca_cert = X509::from_pem(&ca_pem)?;
let ca_key = ca_cert.public_key()?;
match crl.verify(&ca_key)? {
true => println!("CRL signature valid"),
false => println!("CRL signature INVALID"),
}
CRL-Checked Chain Verification
To enable CRL checking in X509StoreCtx, add the CRL to the store and set the
appropriate flag:
use native_ossl::x509::{X509, X509Crl, X509Store, X509StoreCtx};
const X509_V_FLAG_CRL_CHECK: u64 = 0x4;
let mut store = X509Store::new()?;
store.add_cert(&ca_cert)?;
store.add_crl(&crl)?;
store.set_flags(X509_V_FLAG_CRL_CHECK)?;
let mut ctx = X509StoreCtx::new()?;
ctx.init(&store, &end_entity_cert)?;
let ok = ctx.verify()?;
NID Lookup Free Functions
OpenSSL uses numeric identifiers (NIDs) to identify algorithms and object types. These free functions convert between NIDs and human-readable names.
#![allow(unused)]
fn main() {
/// Short name for a NID (e.g. 13 → "CN", 672 → "SHA256"). Returns None if unknown.
/// Result points to OpenSSL's static table; lifetime is 'static, no allocation.
pub fn nid_to_short_name(nid: i32) -> Option<&'static CStr>;
/// Long name for a NID (e.g. 13 → "commonName"). Returns None if unknown.
pub fn nid_to_long_name(nid: i32) -> Option<&'static CStr>;
}
Provider-aware key-type comparison
When inspecting certificate signature algorithms with X509_get_signature_info,
you get a pknid (signer’s key NID). Comparing it against a public key using
EVP_PKEY_get_base_id is broken for provider-based keys (ML-DSA, ML-KEM,
SLH-DSA) — that function returns NID_undef (0) for all of them. The correct
pattern uses EVP_PKEY_is_a with the NID’s short name:
#![allow(unused)]
fn main() {
// WRONG for provider-based keys:
// pkey.get_base_id() == pknid ← returns 0 for all provider keys
// Correct — works for both legacy and provider-based keys:
use native_ossl::x509::nid_to_short_name;
if let Some(sn) = nid_to_short_name(pknid) {
if pkey.is_a(sn) {
// subject key matches the signing algorithm
}
}
}
Design Notes
from_dertakes&[u8]— the full slice is consumed. A local pointer is passed tod2i_X509; the external slice reference is not advanced. Use for parsing a single, complete DER buffer.- Two serial number accessors —
serial_number()returnsOption<i64>(convenient for small serials);serial_number_bytes()returns the raw big-endian bytes viaX509_get_serialNumber(handles serials up to 20 bytes per RFC 5280). Useserial_number_bytes()when interoperating with CAs that issue large random serials. - Two public key paths —
public_key()returns an ownedPkey<Public>with a refcount increment (X509_get_pubkey).public_key_is_a()/public_key_bits()useX509_get0_pubkey(no refcount increment) for cheap introspection. Prefer theget0path when you only need to check the key type or size. public_key()is always owned —X509_get_pubkeyperformsEVP_PKEY_up_refinternally. There is no borrowed&Pkey<Public>getter.- No
Asn1Timetype — validity dates are accessed as formatted strings vianot_before_str()/not_after_str(), which returnOption<String>usingASN1_TIME_print. No structured timestamp type is exposed. X509NameOwnedvsX509Name— the owned type is for the builder (mutable). The borrowed type (X509Name<'cert>) is for reading from a parsed certificate. They are not interchangeable.is_self_signedchecks name equality only — it does not verify the signature. To check the signature usecert.verify(&cert.public_key()?).- Extensions — raw DER bytes are accessible via
X509Extension::data()or the convenience accessorX509::extension_der(nid). For the most common extensions, use the structured accessors instead:key_usage(),extended_key_usage_oids(), andsubject_alt_names(). For any other extension, identify it by NID and parse the raw bytes yourself or vianative-ossl-sysdirectly. X509StoreisClone—X509_STORE_up_refallows sharing the same trust store across threads or verification contexts.X509StoreCtxis single-use — callinginitagain afterverifyis not supported. Allocate a freshX509StoreCtxfor each certificate to verify.X509StoreCtx::chain— eachX509in the returnedVecis independently reference-counted viaX509_up_ref. The vector may outlive theX509StoreCtx.X509CrlisClone—X509_CRL_up_refallows sharing CRL objects across multiple stores without copying.not_before_tm/not_after_tmreturnBrokenDownTime— a simple struct withyear(full Gregorian year),month(1–12),day,hour,minute,second. Backed byASN1_TIME_to_tm; suitable for timestamp arithmetic without a datetime crate dependency. For comparisons against the current time, preferis_valid_now()which uses OpenSSL’s own time comparison.nid_to_short_name/nid_to_long_namereturn&'static CStr— the pointers come from OpenSSL’s static internal OBJ table (not heap-allocated per call). The'staticlifetime is correct and no deallocation is needed. These are pure table lookups backed byOBJ_nid2sn/OBJ_nid2ln.X509Name::to_oneline— produces the legacy/CN=.../O=.../C=...format viaX509_NAME_oneline. Allocates a C string that is immediately freed after conversion to a RustString(usingCRYPTO_free, the function underlying theOPENSSL_freemacro). Preferto_string()orentry()for structured access; useto_oneline()only for quick log output.X509::new_in— creates an emptyX509bound to an explicitLibCtx, useful for FIPS-isolated certificate construction. WrapsX509_new_exwith aNULLproperty query string (default provider properties).signature_infomd_nidis 0 for pre-hash-free algorithms — Ed25519 and ML-DSA (FIPS 204) sign the message directly without a separate digest step.X509_get_signature_inforeturnsNID_undef(0) formdnidin these cases. Always check formd_nid == 0before using it as a digest identifier. For traditional algorithms (RSA, ECDSA),md_nidis a valid non-zero NID such as the one for SHA-256 or SHA-384.key_usagereturnsOption<KeyUsage>—Nonewhen thekeyUsageextension is absent. When present, theKeyUsagebitmask can be tested withku.contains(KeyUsage::DIGITAL_SIGNATURE)etc. Backed byX509_get_key_usage; the extension is decoded once and cached by OpenSSL.extended_key_usage_oidsreturnsVec<Oid>— each entry is a decoded OID from theextendedKeyUsageextension. Returns an emptyVecwhen the extension is absent. UsesX509_get_ext_d2i+OBJ_dupto give eachOidan independent allocation. For vendor-specific EKU OIDs that have no standard NID, compare withOid::from_text("1.3.6.1…")rather than with NID integers.subject_alt_namesreturnsVec<GeneralName>— decodes thesubjectAltNameextension into typed variants:Dns,Email,Uri,IpAddress(4 or 16 bytes),RegisteredId(OID), andOtherName { type_id, value }(application-specific OID + raw DER bytes).DirectoryName,X400Address, andEdiPartyNameentries are silently skipped. Returns an emptyVecwhen the extension is absent.