Zend Http Client SSL certificate validation failure with custom CA bundles
0 reputation · 28 Jun 2024, 06:04 UTC
0 reputation · 28 Jun 2024, 06:04 UTC
The Zend\Http\Client (Laminas) relies on PHP stream wrappers to handle SSL/TLS handshakes. When configuring the client for secure communication, the sslcafile and sslcapath options are used to define the trusted Certificate Authority bundles for peer verification.
There is uncertainty regarding the precedence of these client-level configurations when the php.ini setting openssl.cafile is also defined. Specifically, it is unclear if the client-specific options completely override the global PHP configuration or if the underlying OpenSSL extension merges these paths during the validation process.
sslcafile option in the client configuration take absolute precedence over the openssl.cafile directive in php.ini?verify_peer is enabled, how does the client behave if the sslcafile is provided but the system-level CA bundle is missing?32775 reputation · 28 Jun 2024, 13:23 UTC
Does the sslcafile option in the client configuration take absolute precedence over the openssl.cafile directive in php.ini?
Yes. When the Zend Http Client is configured with sslcafile (or sslcapath for the stream adapter) the underlying PHP stream wrapper or cURL receives that path directly. PHP never falls back to the openssl.cafile value for that request. The openssl.cafile setting only applies when the client does not supply its own CA file.
In environments where verify_peer is enabled, how does the client behave if the sslcafile is provided but the system‑level CA bundle is missing?
With verify_peer (and verify_host) set to true, the client will attempt to validate the server’s certificate chain against the certificates in the supplied sslcafile only. If that file does not contain a complete chain (root + intermediates) or is unreadable by the web‑server user, the handshake fails with "certificate verify failed". The absence of a system CA bundle is irrelevant because the client never consults it when a custom bundle is present.
The Zend Http Client does not merge the two sources; it simply uses whichever path was supplied last in the request context. This behavior matches the design of PHP’s stream wrapper and cURL: options passed to the wrapper override global defaults.
Verify OpenSSL support in PHP:
php -i | grep -i openssl
Make sure OpenSSL support is enabled and that openssl.cafile is either empty or points to a valid bundle.
Prepare a single PEM bundle that includes the root and any intermediate certificates in the correct order.
Set the client adapter explicitly and provide the CA file path:
// Using the stream adapter
$client = new Zend\Http\Client();
$client->setAdapter('Zend\Http\Client\Adapter\Stream');
$client->setConfig([
'sslcapath' => '/etc/ssl/certs/ca-bundle.pem', // or 'sslcafile'
'sslverifypeer' => true,
'sslverifyhost' => true,
]);
// Using the curl adapter
$client->setAdapter('Zend\Http\Client\Adapter\Curl');
$client->setConfig([
'curloptions' => [CURLOPT_CAINFO => '/etc/ssl/certs/ca-bundle.pem'],
'sslverifypeer' => true,
'sslverifyhost' => true,
]);
Check file permissions: the web‑server user (e.g., www‑data, apache, www‑www) must have read access to the bundle file.
Test the bundle directly with cURL:
curl -v --cacert /etc/ssl/certs/ca-bundle.pem https://your.server
A successful handshake confirms the bundle is valid.
Enable Zend Http Client logging to see the exact error if validation still fails:
$client->setConfig(['log' => true]);
Review the log for "SSL: certificate verify failed" messages.
Do not depend on openssl.cafile when you provide sslcafile. If you need the system bundle for other parts of the application, keep it in php.ini and simply leave the client configuration empty; the client will then use the global setting.
Could you confirm which adapter you are using (Stream or Curl)? The exact option names differ between adapters, and a mismatch can silently ignore the CA file you set.
Use comments to ask for clarification. Post a solution as an answer.
32,775 reputation · 28 Jun 2024, 07:45 UTC
The Zend Http Client passes the CA bundle path it receives (via sslcafile or sslcapath) directly to the underlying PHP stream context or cURL. When that value is set, the client never consults the openssl.cafile directive from php.ini. Only if the client omits a custom bundle will PHP fall back to the global setting.
For the bundle to be accepted:
If the server presents a self‑signed certificate that you trust, set allowSelfSigned to true and keep sslVerifyPeerName enabled to avoid hostname spoofing.
$client = new Zend\Http\Client('https://example.com');
$client->setOptions([
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'allow_self_signed' => false,
'cafile' => '/path/to/ca-bundle.pem',
],
]);
$response = $client->send();
php -i | grep openssl to confirm the OpenSSL extension is enabled.phpinfo() for the openssl.cafile value to see the global fallback.openssl s_client -connect example.com:443 -CAfile /path/to/ca-bundle.pem to verify the chain outside PHP.