[dependencies]x509-parser = "=0.18.1"sha2 = "=0.11.1"hex = "=0.4.3" use sha2::{Digest, Sha256};use x509_parser::der_parser::oid::Oid;use x509_parser::extensions::GeneralName;use x509_parser::objects::{oid_registry, oid2sn};use x509_parser::pem::Pem; /// Short name of an OID ("ecdsa-with-SHA256"), or its dotted form when the registry does not know it.fn oid_name(oid: &Oid) -> String { oid2sn(oid, oid_registry()).map(str::to_owned).unwrap_or_else(|_| oid.to_id_string())} fn main() -> Result<(), Box<dyn std::error::Error>> { let data = std::fs::read("chain.pem")?; // every "BEGIN CERTIFICATE" block in the file, leaf first for pem in Pem::iter_from_buffer(&data) { let pem = pem?; let cert = pem.parse_x509()?; // borrows pem.contents, the DER bytes println!("subject: {}", cert.subject()); println!("issuer: {}", cert.issuer()); println!("serial: {}", cert.raw_serial_as_string()); println!("valid: {} -> {}", cert.validity().not_before, cert.validity().not_after); println!("signed with: {}", oid_name(&cert.signature_algorithm.algorithm)); if let Some(san) = cert.subject_alternative_name()? { for name in &san.value.general_names { if let GeneralName::DNSName(dns) = name { println!("DNS name: {dns}"); } } } let curve = cert.public_key().algorithm.parameters.as_ref().map(|p| p.as_oid()).transpose()?; if let Some(curve) = curve { println!("key: EC on {}", oid_name(&curve)); } println!("SHA-256 fingerprint: {}", hex::encode(Sha256::digest(&pem.contents))); println!("CA: {}, currently valid: {}", cert.is_ca(), cert.validity().is_valid()); } Ok(())} How it works
Pem::iter_from_bufferyields onePemper block, so bundle files need no splitting;pem.contentsholds the DER bytes andparse_x509borrows from it without copying.subject()andissuer()display the attributes in the order the certificate stores them (CN=…, O=…, C=…), as OpenSSL does — not in the reversed RFC 4514 string order (C=…,O=…,CN=…) that Java and .NET print.- Extension getters return
Result<Option<…>>: an error for a malformed extension,Nonewhen it is absent.subject_alternative_name()listsGeneralNamevalues such asDNSName. oid2snwith the bundledoid_registry()turns OIDs into OpenSSL-style short names (ecdsa-with-SHA256,prime256v1);oid_namefalls back to the dotted form for OIDs the registry does not know.- The fingerprint is SHA-256 over
pem.contents, the DER of the whole certificate;is_ca()andvalidity().is_valid()answer the two most common questions about a certificate in a chain.
Gotchas
oid2snreturnsResult<&str, NidError>, andNidErrordoes not implementstd::error::Error—?intoBox<dyn Error>does not compile. Map the error, asoid_namedoes.validity().is_valid()compares with the current time: the example certificate stops being valid after 15 January 2027, so code that tests it must not hard-codetrue.- Parsing checks structure only. Signature verification needs the
verifyfeature (verify_signaturewith the issuer's public key), and chain trust needs a validator such asrustls-webpki. parse_x509_pemfails withMissingHeaderon DER input; for binary certificates callx509_parser::parse_x509_certificate(&der)directly.