Skip to content
ecdsa.com

Verify a certificate chain · Python

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

To verify a certificate chain against your own trust store in Python, use cryptography.x509.verification: put your root in a Store, build a server verifier with PolicyBuilder for the expected host name, and call verify(leaf, intermediates). It returns the validated chain or raises VerificationError. This recipe checks a leaf → intermediate → root chain from a test CA and three chains that must fail.

Tested with cryptography 50.0.2 (Python 3.14.8, cryptography.x509.verification).

verify.py — path validation with cryptography's X.509 verifier
from cryptography import x509
from cryptography.x509.verification import PolicyBuilder, Store, VerificationError
def load(path: str) -> x509.Certificate:
with open(path, "rb") as f:
return x509.load_pem_x509_certificate(f.read())
def verify_chain(leaf_path, intermediate_paths, root_path, host):
store = Store([load(root_path)]) # only this root is trusted
verifier = PolicyBuilder().store(store).build_server_verifier(x509.DNSName(host))
# verify() builds the path, checks signatures, validity, key usage and the host name
return verifier.verify(load(leaf_path), [load(p) for p in intermediate_paths])
chain = verify_chain("leaf.pem", ["intermediate.pem"], "root.pem", "api.example.com")
print("valid:", " -> ".join(cert.subject.rfc4514_string() for cert in chain))
for label, leaf, intermediates, root in [
("impostor root", "leaf.pem", ["intermediate.pem"], "impostor-root.pem"),
("expired leaf", "expired.pem", ["intermediate.pem"], "root.pem"),
("missing intermediate", "leaf.pem", [], "root.pem"),
]:
try:
verify_chain(leaf, intermediates, root, "api.example.com" if leaf == "leaf.pem" else "expired.example.com")
except VerificationError as error:
print(f"{label}: {error}")

How it works

  1. Store([root]) holds the trust anchors; only these roots are trusted.
  2. PolicyBuilder().store(store).build_server_verifier(x509.DNSName(host)) creates a verifier for TLS server certificates: it checks the signatures, validity periods, basic constraints, key usage and extended key usage along the path, and the host name.
  3. verifier.verify(leaf, intermediates) builds the path through the intermediates and returns it, leaf first; the run prints api.example.com → Issuing CA 1 → Root CA.
  4. PolicyBuilder().time(...) validates at a given moment instead of now, and build_client_verifier() checks client certificates (clientAuth) instead.

Gotchas

  • A look-alike root with the same name fails with validation failed: candidates exhausted: signature does not match — matching names is not enough, the key must have signed the intermediate.
  • The expired leaf fails with cert is not valid at validation time, and the missing intermediate with candidates exhausted: all candidates exhausted with no interior errors — the verifier does not download missing issuers.
  • VerificationError messages are for logs, not for logic: the look-alike root and the missing intermediate both start with candidates exhausted. Treat any exception as "not trusted".

Related recipes