Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SecureConnector: add optional TlsPeer, this... #252

Open
wants to merge 1 commit into
base: 1.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
28 changes: 28 additions & 0 deletions src/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
8 changes: 7 additions & 1 deletion src/SecureConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
111 changes: 111 additions & 0 deletions src/TlsPeer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

namespace React\Socket;

use InvalidArgumentException;

class TlsPeer
{
/** @var resource of type OpenSSL X.509 */
private $peerCertificate;

/** @var resource[] of type OpenSSL X.509 */
private $peerCertificateChain;

public function __construct($peerCertificate = null, array $peerCertificateChain = null)
{
if ($peerCertificate !== null) {
static::assertX509Resource($peerCertificate);
$this->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();
}
}