Skip to content
ecdsa.com

Verify a certificate chain · Rust

How to verify a certificate chain against your own trust store in Rust

To verify a certificate chain against your own trust store in Rust, use rustls-webpki — the validator inside rustls: turn your root into a TrustAnchor, parse the leaf as an EndEntityCert and call verify_for_usage with the intermediates, then verify_is_valid_for_subject_name for the host. This recipe checks a leaf → intermediate → root chain from a test CA and the chains that must fail, each with a typed error.

Tested with rustls-webpki 0.103.16 (Rust 1.99.1, ring provider, rustls-pki-types 1.15.1).

Cargo.toml — rustls-webpki with the ring provider, rustls-pki-types
[dependencies]
rustls-webpki = { version = "=0.103.16", features = ["ring"] }
rustls-pki-types = "=1.15.1"
src/main.rs — path validation, then the host name
use rustls_pki_types::pem::PemObject;
use rustls_pki_types::{CertificateDer, ServerName, UnixTime};
use webpki::{EndEntityCert, KeyUsage, anchor_from_trusted_cert};
fn cert(path: &str) -> CertificateDer<'static> {
CertificateDer::from_pem_file(path).expect(path)
}
/// Path building and validation as rustls does it: signatures, validity, CA and
/// key usage constraints, then the host name. Returns the error, if any.
fn verify(leaf: &str, intermediates: &[&str], root: &str, host: &str) -> Result<(), webpki::Error> {
let root = cert(root);
let anchors = [anchor_from_trusted_cert(&root)?];
let intermediates: Vec<_> = intermediates.iter().map(|p| cert(p)).collect();
let leaf_der = cert(leaf);
let leaf = EndEntityCert::try_from(&leaf_der)?;
leaf.verify_for_usage(
webpki::ALL_VERIFICATION_ALGS, // ring-backed algorithms (feature "ring")
&anchors,
&intermediates,
UnixTime::now(),
KeyUsage::server_auth(),
None, // revocation: no CRLs for a test CA
None,
)?;
leaf.verify_is_valid_for_subject_name(&ServerName::try_from(host).expect("host name"))
}
fn main() {
println!("good chain: {:?}", verify("leaf.pem", &["intermediate.pem"], "root.pem", "api.example.com"));
println!("impostor root: {:?}", verify("leaf.pem", &["intermediate.pem"], "impostor-root.pem", "api.example.com"));
println!("expired leaf: {:?}", verify("expired.pem", &["intermediate.pem"], "root.pem", "expired.example.com"));
println!("missing intermediate: {:?}", verify("leaf.pem", &[], "root.pem", "api.example.com"));
println!("wrong host: {:?}", verify("leaf.pem", &["intermediate.pem"], "root.pem", "example.org"));
}

How it works

  1. anchor_from_trusted_cert(&root) turns a DER certificate into a TrustAnchor; the slice of anchors is your trust store.
  2. EndEntityCert::try_from(&leaf) parses the leaf; verify_for_usage(algorithms, anchors, intermediates, time, KeyUsage::server_auth(), None, None) builds and checks the path.
  3. webpki::ALL_VERIFICATION_ALGS (feature ring) lists the signature algorithms accepted along the path.
  4. Path validation does not look at names: verify_is_valid_for_subject_name checks the host separately, and the run shows it rejecting example.org.

Gotchas

  • Errors are precise: the look-alike root gives InvalidSignatureForPublicKey, the missing intermediate UnknownIssuer, the expired leaf CertExpired { time, not_after } with both Unix times.
  • Skipping verify_is_valid_for_subject_name accepts any valid certificate from your CA for any host: CertNotValidForName lists the names the certificate does have.

Related recipes