<?php// true: a valid path to a trusted root exists for this purpose; false: it does not; -1: an errorfunction verifyChain(string $leafFile, ?string $intermediatesFile, string $rootFile): bool|int{ return openssl_x509_checkpurpose( file_get_contents($leafFile), X509_PURPOSE_SSL_SERVER, // also checks the key usage and extended key usage [$rootFile], // trusted: files (or directories) with root certificates $intermediatesFile, // untrusted: a PEM file with intermediates, or null );} foreach ([ 'good chain' => ['leaf.pem', 'intermediate.pem', 'root.pem'], 'impostor root' => ['leaf.pem', 'intermediate.pem', 'impostor-root.pem'], 'expired leaf' => ['expired.pem', 'intermediate.pem', 'root.pem'], 'missing intermediate' => ['leaf.pem', null, 'root.pem'],] as $label => [$leaf, $intermediates, $root]) { $result = verifyChain($leaf, $intermediates, $root); echo $label, ': ', var_export($result, true); while (($error = openssl_error_string()) !== false) { echo ' | ', $error; } echo "\n";} How it works
- The third argument lists trusted roots as files or directories (hashed
c_rehashlayout); only those are trusted. - The fourth argument is a file of untrusted intermediates — one PEM file that may hold several certificates.
- The purpose adds key usage checks:
X509_PURPOSE_SSL_SERVERrequires a certificate fit for TLS servers,X509_PURPOSE_ANYskips that part. truemeans OpenSSL found and verified a path: signatures, validity, CA flags — the same engine asopenssl verify.
Gotchas
- On failure the function returns
falseand gives no reason: the run's look-alike root, expired leaf and missing intermediate all printfalsewith no message inopenssl_error_string(). Runopenssl verifyon the same files to see why. - A CA file that does not exist also yields
false— indistinguishable from an untrusted chain — so check that your trust files are readable before blaming the certificate. - The function can also return
-1for internal errors; compare with=== true, never with a plain truthiness check, because-1is truthy. - Roots and intermediates are file paths, not PEM strings; write PEM data you hold in memory to temporary files first.
Related recipes
- How to verify a certificate chain against your own trust store in Ruby
- How to verify a certificate chain against your own trust store in Python
- How to verify a certificate chain against your own trust store with OpenSSL
- How to check SSL certificate expiration date in PHP
- How to decode an X.509 certificate in PHP