# -CAfile: what you trust; -untrusted: intermediates that must chain up to it;# -no-CApath -no-CAstore: do not also trust the system's CA directory and storeopenssl verify -no-CApath -no-CAstore -CAfile root.pem -untrusted intermediate.pem leaf.pem # -show_chain prints the path OpenSSL built; -verify_hostname checks the name tooopenssl verify -show_chain -verify_hostname api.example.com -no-CApath -no-CAstore \ -CAfile root.pem -untrusted intermediate.pem leaf.pem# Each of these must fail: another root with the same name, an expired leaf, a missing intermediateopenssl verify -no-CApath -no-CAstore -CAfile impostor-root.pem -untrusted intermediate.pem leaf.pemopenssl verify -no-CApath -no-CAstore -CAfile root.pem -untrusted intermediate.pem expired.pemopenssl verify -no-CApath -no-CAstore -CAfile root.pem leaf.pemecho "exit code of the last check: $?"How it works
-CAfilelists your trust anchors, and-no-CApath -no-CAstoreturn off the system's CA directory and store, so nothing else is trusted.-untrustedsupplies intermediates. OpenSSL builds the path through them to an anchor, checks every signature, validity period, CA flag and path length, and printsleaf.pem: OK.-show_chainprints the path it built, marking certificates that came from-untrusted;-verify_hostnameadds the host name check a TLS client would make.- Each failure names the depth and the reason:
error 20 at 1 depth lookup: unable to get local issuer certificatefor the look-alike root,error 10 at 0 depth lookup: certificate has expiredfor the expired leaf. The exit code is 2.
Gotchas
-CAfilealone does not mean "only this file": OpenSSL still loads the default CA directory. In the run, a public root from/etc/ssl/certsverifiesOKagainst-CAfile root.pem, and fails once-no-CApath -no-CAstoreare added.- A root with the same name but another key is not a trust anchor for this chain: the intermediate's signature does not verify with it, and OpenSSL reports error 20 at depth 1 — the issuer of the intermediate was not found.
- An intermediate given as
-CAfileis not trusted on its own: verification still fails unless you add-partial_chain, which turns it into an anchor. Use that switch only when you really mean to trust the intermediate. openssl verifychecks the first certificate of each file: a bundle of leaf + intermediate passed as the certificate fails, because the intermediate in the same file is ignored. Pass intermediates with-untrusted.- Without the intermediate the leaf fails with error 20 at depth 0 — the classic "works in the browser, fails in curl" case: browsers often have the intermediate cached, other clients only see what the server sends.
Related recipes
- How to verify a certificate chain against your own trust store in Python
- How to verify a certificate chain against your own trust store in Go
- How to verify a certificate chain against your own trust store in Java
- How to check SSL certificate expiration date with OpenSSL
- How to decode an X.509 certificate with OpenSSL