Skip to content

Commit e0dcc1a

Browse files
committed
SecureConnector: add optional TlsPeer, this...
...allows to capture your peer certificate and/or it's chain
1 parent 28fac70 commit e0dcc1a

File tree

4 files changed

+173
-1
lines changed

4 files changed

+173
-1
lines changed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,33 @@ $secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop, array(
13221322
));
13231323
```
13241324

1325+
In case you want to retrieve your peers certificate or certificate chain,
1326+
you can use the related context options:
1327+
1328+
```php
1329+
$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop, array(
1330+
'capture_peer_cert' => true,
1331+
'capture_peer_cert_chain' => true,
1332+
));
1333+
```
1334+
1335+
To show the peer certificate for every new connection this can be done as
1336+
follows:
1337+
1338+
```php
1339+
$secureConnector->connect('www.google.com:443')->then(function (React\Socket\ConnectionInterface $connection) {
1340+
assert($connection instanceof React\Socket\Connection);
1341+
if ($connection->hasTlsPeer()) {
1342+
$peer = $connection->getTlsPeer();
1343+
if ($peer && $peer->hasPeerCertificate()) {
1344+
$peerCert = $peer->getPeerCertificate();
1345+
openssl_x509_export($peerCert, $cert);
1346+
echo $cert;
1347+
}
1348+
}
1349+
});
1350+
```
1351+
13251352
By default, this connector supports TLSv1.0+ and excludes support for legacy
13261353
SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
13271354
want to negotiate with the remote side:

src/Connection.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ class Connection extends EventEmitter implements ConnectionInterface
4141

4242
private $input;
4343

44+
/** @var TlsPeer|null */
45+
private $tlsPeer;
46+
4447
public function __construct($resource, LoopInterface $loop)
4548
{
4649
// PHP < 7.3.3 (and PHP < 7.2.15) suffers from a bug where feof() might
@@ -154,6 +157,31 @@ public function getLocalAddress()
154157
return $this->parseAddress(\stream_socket_get_name($this->stream, false));
155158
}
156159

160+
/**
161+
* @param TlsPeer $peer
162+
* @internal
163+
*/
164+
public function setTlsPeer(TlsPeer $peer = null)
165+
{
166+
$this->tlsPeer = $peer;
167+
}
168+
169+
/**
170+
* @return bool
171+
*/
172+
public function hasTlsPeer()
173+
{
174+
return $this->tlsPeer !== null;
175+
}
176+
177+
/**
178+
* @return TlsPeer|null
179+
*/
180+
public function getTlsPeer()
181+
{
182+
return $this->tlsPeer;
183+
}
184+
157185
private function parseAddress($address)
158186
{
159187
if ($address === false) {

src/SecureConnector.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,13 @@ public function connect($uri)
5656
}
5757

5858
// try to enable encryption
59-
return $promise = $encryption->enable($connection)->then(null, function ($error) use ($connection, $uri) {
59+
return $promise = $encryption->enable($connection)->then(function () use ($connection) {
60+
$connection->setTlsPeer(
61+
TlsPeer::fromContextOptions(\stream_context_get_options($connection->stream))
62+
);
63+
64+
return $connection;
65+
}, function ($error) use ($connection, $uri) {
6066
// establishing encryption failed => close invalid connection and return error
6167
$connection->close();
6268

src/TlsPeer.php

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
namespace React\Socket;
4+
5+
use InvalidArgumentException;
6+
7+
class TlsPeer
8+
{
9+
/** @var resource of type OpenSSL X.509 */
10+
private $peerCertificate;
11+
12+
/** @var resource[] of type OpenSSL X.509 */
13+
private $peerCertificateChain;
14+
15+
public function __construct($peerCertificate = null, array $peerCertificateChain = null)
16+
{
17+
if ($peerCertificate !== null) {
18+
static::assertX509Resource($peerCertificate);
19+
$this->peerCertificate = $peerCertificate;
20+
}
21+
if ($peerCertificateChain !== null) {
22+
foreach ($peerCertificateChain as $resource) {
23+
static::assertX509Resource($resource);
24+
}
25+
$this->peerCertificateChain = $peerCertificateChain;
26+
}
27+
}
28+
29+
public static function fromContextOptions($options)
30+
{
31+
if (isset($options['ssl']['peer_certificate'])) {
32+
$peerCertificate = $options['ssl']['peer_certificate'];
33+
} else {
34+
$peerCertificate = null;
35+
}
36+
if (isset($options['ssl']['peer_certificate_chain'])) {
37+
$peerCertificateChain = $options['ssl']['peer_certificate_chain'];
38+
} else {
39+
$peerCertificateChain = null;
40+
}
41+
42+
return new static($peerCertificate, $peerCertificateChain);
43+
}
44+
45+
protected static function assertX509Resource($resource)
46+
{
47+
if (! \is_resource($resource)) {
48+
throw new \InvalidArgumentException(\sprintf(
49+
'Resource expected, got "%s"',
50+
\gettype($resource)
51+
));
52+
}
53+
if (\get_resource_type($resource) !== 'OpenSSL X.509') {
54+
throw new \InvalidArgumentException(\sprintf(
55+
'Resource of type "OpenSSL X.509" expected, got "%s"',
56+
\get_resource_type($resource)
57+
));
58+
}
59+
}
60+
61+
/**
62+
* @return bool
63+
*/
64+
public function hasPeerCertificate()
65+
{
66+
return $this->peerCertificate !== null;
67+
}
68+
69+
/**
70+
* @return null|resource (OpenSSL x509)
71+
*/
72+
public function getPeerCertificate()
73+
{
74+
return $this->peerCertificate;
75+
}
76+
77+
/**
78+
* @return bool
79+
*/
80+
public function hasPeerCertificateChain()
81+
{
82+
return $this->peerCertificateChain !== null;
83+
}
84+
85+
/**
86+
* @return null|array of OpenSSL x509 resources
87+
*/
88+
public function getPeerCertificateChain()
89+
{
90+
return $this->peerCertificateChain;
91+
}
92+
93+
protected function free()
94+
{
95+
if ($this->peerCertificate) {
96+
\openssl_x509_free($this->peerCertificate);
97+
$this->peerCertificate = null;
98+
}
99+
if (\is_array($this->peerCertificateChain)) {
100+
foreach ($this->peerCertificateChain as $cert) {
101+
\openssl_x509_free($cert);
102+
}
103+
$this->peerCertificateChain = null;
104+
}
105+
}
106+
107+
public function __destruct()
108+
{
109+
$this->free();
110+
}
111+
}

0 commit comments

Comments
 (0)