PHP sockets: Fix “Unable to complete TLS handshake” with mkcert local development certificates

I use DDEV-Local for my local development stack. DDEV leverages mkcert for trusted local development certificates. The mkcert tool has been a missing component in my local development stack for a long time. And, the best part, it has worked without any problems. Until this week. My coworker said that a script I wrote was not working — it kept failing saying that the remote certificate could not be validated. However, cURL had no complaints, nor did any web browser. I chalked it up as “works on my machine 🤷‍♂️.” Until today 😬.

I was working on the follow up to my blog about using ReactPHP to consume an HTTP API. I was taking the resulting data and creating entities on a Drupal site. I point the react/http client to https://drupex.ddev.site... and got an unexpected error.

Connection to drupex.ddev.site:443 failed during TLS handshake: Unable to complete TLS handshake: SSL operation failed with code 1. OpenSSL Error messages: error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed

Now, why did this work before on my other machine? I was using PHP’s built-in webserver. I wasn’t using a secure connection locally. My coworker was. Just like him, I had no issues connecting via cURL or wget or a browser.

To be safe, I ran the mkcert installer again

~ » mkcert -install                                                                                                                                                                                                 
Using the local CA at "/Users/mglaman/Library/Application Support/mkcert" ✨
The local CA is already installed in the system trust store! 👍
The local CA is already installed in the Firefox trust store! 👍

🙌 All right, it is already installed in the system trust store. Maybe I just needed to rerun it? Nope. Still nothing. I flexed my search-fu on Google and DuckDuckGo to no avail. So I asked on the Twitterverse and began digging.

I looked into the react/socket code and saw it was using OpenSSL functions. This must have meant that the problem was OpenSSL was not aware of the mkcert certificate authority installed on my machine. So I checked the OpenSSL configuration for my PHP install.

~ » php -i | grep openssl

openssl
Openssl default config => /usr/local/etc/openssl@1.1/openssl.cnf
openssl.cafile => /usr/local/etc/openssl@1.1/cert.pem => /usr/local/etc/openssl@1.1/cert.pem
openssl.capath => /usr/local/etc/openssl@1.1/certs => /usr/local/etc/openssl@1.1/certs

I’m on macOS with Homebrew installs of OpenSSL and PHP. That’s why you see openssl@1.1. From the PHP documentation, the capath configuration operation is for:

If cafile is not specified or if the certificate is not found there, the directory pointed to by capath is searched for a suitable certificate. capath must be a correctly hashed certificate directory.

So, I need to symlink my mkcert CA into the capath for OpenSSL!

mkdir /usr/local/etc/openssl@1.1/certs
ln -s "$(mkcert -CAROOT)/rootCA.pem" /usr/local/etc/openssl@1.1/certs

The quotes are important, as for macOS they install to /Users/{user}/Library/Application Support/mkcert and the space must be escaped.

Filippo Valsorda, creator of mkcert, managed to find my tweet, and it sounds like he will be adding a fix to mkcert. I opened an issue for good measure, if you would like to follow along.

Installation of root CA for OpenSSL · Issue #295 · FiloSottile/mkcert

👋 This is a follow up to a tweet I made earlier: https://twitter.com/nmdmatt/status/1305206184094445571 Basically, PHP sockets fail to connect to a site using certificates with mkcert. That is beca…

Open source developer, working with Drupal and building Drupal Commerce.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store