Despite a statement made in the comments saying PHP's steam_socket_client()
is too low-level to care about certificates, I know it does care because I have observed it returning false
on systems that lack some certificate in the standard location, where providing a stream-context (to tell it where else to look for certs) stops this from happening.
Frustratingly, the $errno
can remain 0 until you've fixed this, so you are better off checking your certificate-authority directory first with another tool.
diagnosis
via curl
To get things working with curl
, you can usually figure out what's going on by adding the --verbose
flag. This should tell you which concatenated PEM-file of certificates (CAfile/cacert) and CA-directory (capath) are being used, if any. Toward the top of the output, you'll get something like:
[...]
* successfully set certificate verify locations:
* CAfile: /etc/pki/tls/certs/ca-bundle.crt
CApath: /home2/wynddorg/private_html/trusted_roots
[...]
Once you know what these are defaulting to, you can either adjust your system configuration or simply add either --cacert <concatenated_cert_path>
flag or --capath <c_rehash'd_directory>
or both.
via openssl s_client
Using s_client
is a bit trickier; you may have access to its -trace
option depending on your version and how it was compiled instead, but try:
strace -e open,stat openssl s_client -connect test.cgps.org:443 [-CApath <dir>]
# this trick provided by the amazing xemdetia in irc://freenode/%23%23openssl
About 5 lines after the CONNECTED(00000003)
message, you should see an open("...", O_RDONLY)
(or several) which should reveal which locations are being used to find certs.
I'm missing one or more certs. Now what?
Once you know which certificates are missing, you can download them as shown here, and then do any:
- concatenate them into a PEM file (preferably in order) and then specify that file when you try to connect to your site
- save them as individual '.crt'-files in the default directory for this and then run
c_rehash
on that directory (likely requires root/sudo)
- save them as individual '.crt'-files in another directory and explicitly specify that as the capath when connecting. You may wish to do this on shared hosting or if you don't want all applications to trust the certs, just certain ones.
Once you have things working with openssl s_client
or curl
or both, you can specify the same options like so:
$context = stream_context_create();
stream_context_set_option($context, 'ssl', 'cafile', <concatenated_cert_file_path>);
stream_context_set_option($context, 'ssl', 'capath', <c_rehashed_cert_dir_path>);
// You only really need to provide one of the two options above. Using capath is
// faster, but this only matters if you'll be making a huge number of
// connections.
$ssc = stream_socket_client(
'ssl://subdomain.example.com:443',
$errno,
$errstr,
30,
STREAM_CLIENT_CONNECT,
$context
);
if ($ssc) { /* ... */ }