From e0dcc1a1414282b8f9038b3881e7d8494cd0ba2c Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 25 Sep 2020 12:55:50 +0200 Subject: [PATCH] SecureConnector: add optional TlsPeer, this... ...allows to capture your peer certificate and/or it's chain --- README.md | 27 ++++++++++ src/Connection.php | 28 ++++++++++ src/SecureConnector.php | 8 ++- src/TlsPeer.php | 111 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 src/TlsPeer.php diff --git a/README.md b/README.md index fafc66f8..5b65becb 100644 --- a/README.md +++ b/README.md @@ -1322,6 +1322,33 @@ $secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop, array( )); ``` +In case you want to retrieve your peers certificate or certificate chain, +you can use the related context options: + +```php +$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop, array( + 'capture_peer_cert' => true, + 'capture_peer_cert_chain' => true, +)); +``` + +To show the peer certificate for every new connection this can be done as +follows: + +```php +$secureConnector->connect('www.google.com:443')->then(function (React\Socket\ConnectionInterface $connection) { + assert($connection instanceof React\Socket\Connection); + if ($connection->hasTlsPeer()) { + $peer = $connection->getTlsPeer(); + if ($peer && $peer->hasPeerCertificate()) { + $peerCert = $peer->getPeerCertificate(); + openssl_x509_export($peerCert, $cert); + echo $cert; + } + } +}); +``` + By default, this connector supports TLSv1.0+ and excludes support for legacy SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you want to negotiate with the remote side: diff --git a/src/Connection.php b/src/Connection.php index 5e3b00d9..fad8f723 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -41,6 +41,9 @@ class Connection extends EventEmitter implements ConnectionInterface private $input; + /** @var TlsPeer|null */ + private $tlsPeer; + public function __construct($resource, LoopInterface $loop) { // PHP < 7.3.3 (and PHP < 7.2.15) suffers from a bug where feof() might @@ -154,6 +157,31 @@ public function getLocalAddress() return $this->parseAddress(\stream_socket_get_name($this->stream, false)); } + /** + * @param TlsPeer $peer + * @internal + */ + public function setTlsPeer(TlsPeer $peer = null) + { + $this->tlsPeer = $peer; + } + + /** + * @return bool + */ + public function hasTlsPeer() + { + return $this->tlsPeer !== null; + } + + /** + * @return TlsPeer|null + */ + public function getTlsPeer() + { + return $this->tlsPeer; + } + private function parseAddress($address) { if ($address === false) { diff --git a/src/SecureConnector.php b/src/SecureConnector.php index e5ebc73e..970a383a 100644 --- a/src/SecureConnector.php +++ b/src/SecureConnector.php @@ -56,7 +56,13 @@ public function connect($uri) } // try to enable encryption - return $promise = $encryption->enable($connection)->then(null, function ($error) use ($connection, $uri) { + return $promise = $encryption->enable($connection)->then(function () use ($connection) { + $connection->setTlsPeer( + TlsPeer::fromContextOptions(\stream_context_get_options($connection->stream)) + ); + + return $connection; + }, function ($error) use ($connection, $uri) { // establishing encryption failed => close invalid connection and return error $connection->close(); diff --git a/src/TlsPeer.php b/src/TlsPeer.php new file mode 100644 index 00000000..38cfad10 --- /dev/null +++ b/src/TlsPeer.php @@ -0,0 +1,111 @@ +peerCertificate = $peerCertificate; + } + if ($peerCertificateChain !== null) { + foreach ($peerCertificateChain as $resource) { + static::assertX509Resource($resource); + } + $this->peerCertificateChain = $peerCertificateChain; + } + } + + public static function fromContextOptions($options) + { + if (isset($options['ssl']['peer_certificate'])) { + $peerCertificate = $options['ssl']['peer_certificate']; + } else { + $peerCertificate = null; + } + if (isset($options['ssl']['peer_certificate_chain'])) { + $peerCertificateChain = $options['ssl']['peer_certificate_chain']; + } else { + $peerCertificateChain = null; + } + + return new static($peerCertificate, $peerCertificateChain); + } + + protected static function assertX509Resource($resource) + { + if (! \is_resource($resource)) { + throw new \InvalidArgumentException(\sprintf( + 'Resource expected, got "%s"', + \gettype($resource) + )); + } + if (\get_resource_type($resource) !== 'OpenSSL X.509') { + throw new \InvalidArgumentException(\sprintf( + 'Resource of type "OpenSSL X.509" expected, got "%s"', + \get_resource_type($resource) + )); + } + } + + /** + * @return bool + */ + public function hasPeerCertificate() + { + return $this->peerCertificate !== null; + } + + /** + * @return null|resource (OpenSSL x509) + */ + public function getPeerCertificate() + { + return $this->peerCertificate; + } + + /** + * @return bool + */ + public function hasPeerCertificateChain() + { + return $this->peerCertificateChain !== null; + } + + /** + * @return null|array of OpenSSL x509 resources + */ + public function getPeerCertificateChain() + { + return $this->peerCertificateChain; + } + + protected function free() + { + if ($this->peerCertificate) { + \openssl_x509_free($this->peerCertificate); + $this->peerCertificate = null; + } + if (\is_array($this->peerCertificateChain)) { + foreach ($this->peerCertificateChain as $cert) { + \openssl_x509_free($cert); + } + $this->peerCertificateChain = null; + } + } + + public function __destruct() + { + $this->free(); + } +}