Skip to content
ecdsa.com

Verify a certificate chain · PHP

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

To verify a certificate chain against your own trust store in PHP, call openssl_x509_checkpurpose with the leaf, a purpose such as X509_PURPOSE_SSL_SERVER, an array with your root file and a PEM file of intermediates. It returns true for a valid path to a trusted root. This recipe checks a leaf → intermediate → root chain from a test CA and three chains that must fail.

Tested with PHP 8.5.11 (ext/openssl, OpenSSL 3.5.7).

verify.php — chain validation with openssl_x509_checkpurpose
<?php
// true: a valid path to a trusted root exists for this purpose; false: it does not; -1: an error
function 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

  1. The third argument lists trusted roots as files or directories (hashed c_rehash layout); only those are trusted.
  2. The fourth argument is a file of untrusted intermediates — one PEM file that may hold several certificates.
  3. The purpose adds key usage checks: X509_PURPOSE_SSL_SERVER requires a certificate fit for TLS servers, X509_PURPOSE_ANY skips that part.
  4. true means OpenSSL found and verified a path: signatures, validity, CA flags — the same engine as openssl verify.

Gotchas

  • On failure the function returns false and gives no reason: the run's look-alike root, expired leaf and missing intermediate all print false with no message in openssl_error_string(). Run openssl verify on 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 -1 for internal errors; compare with === true, never with a plain truthiness check, because -1 is truthy.
  • Roots and intermediates are file paths, not PEM strings; write PEM data you hold in memory to temporary files first.

Related recipes