reactphp / socket

Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP.
https://reactphp.org/socket/
MIT License
1.21k stars 157 forks source link

Certificates using mkcet fail handshake #250

Closed mglaman closed 4 years ago

mglaman commented 4 years ago

I'm using DDEV to run a local API, which leverages mkcert (https://mkcert.org/) to allow valid localhost HTTPS certificates. cURL is happy with it

curl -I https://drupex.ddev.site/                                                                                                                                                  
HTTP/2 200
server: nginx/1.17.10
date: Sun, 13 Sep 2020 17:47:39 GMT
content-type: text/html; charset=UTF-8
vary: Accept-Encoding
cache-control: must-revalidate, no-cache, private
x-drupal-dynamic-cache: MISS
x-ua-compatible: IE=edge
content-language: en
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
expires: Sun, 19 Nov 1978 05:00:00 GMT
x-generator: Drupal 8 (https://www.drupal.org)
x-drupal-cache: HIT

But I'm getting the following error when trying to connect using react/http, and I tracked the error to StreamEncryption (or so)

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

It looks like the promise fails here

            if (\feof($socket) || $error === null) {
                // EOF or failed without error => connection closed during handshake
                $d->reject(new \UnexpectedValueException(
                    'Connection lost during TLS handshake',
                    \defined('SOCKET_ECONNRESET') ? \SOCKET_ECONNRESET : 0
                ));
            } else {
                // handshake failed with error message
                $d->reject(new \UnexpectedValueException(
                    'Unable to complete TLS handshake: ' . $error
                ));
            }

mkcert installs certificate so that they're valid in the system trust store

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! 👍
mglaman commented 4 years ago

Okay here's the root of the error: https://github.com/reactphp/socket/blob/284d72d11a40bb3e3b8092f6dd2732caa59b13dd/src/SecureConnector.php#L59

            // try to enable encryption
            return $promise = $encryption->enable($connection)->then(null, function ($error) use ($connection, $uri) {
                // establishing encryption failed => close invalid connection and return error
                $connection->close();

                throw new \RuntimeException(
                    'Connection to ' . $uri . ' failed during TLS handshake: ' . $error->getMessage(),
                    $error->getCode()
                );
            });
mglaman commented 4 years ago

I had to update openssl.capath to include the mkcert CAROOT

clue commented 4 years ago

@mglaman Happy to hear you've got this solved already!

In case somebody else stumbles upon the same problem, can you share a short snippet of how you've updated the "openssl.capath to include the mkcert CAROOT"?

mglaman commented 4 years ago

Ah, yes! Forgot to post the fix here.

Here's the command I used for macOS, because Homebrew installs a second version of OpenSSL it's downloaded packages use

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

You just need to symlink the rootCA.pem to the capath.

I did a more lengthy write up here: https://mglaman.dev/blog/php-sockets-fix-unable-complete-tls-handshake-mkcert-local-development-certificates