Skip to content

Commit f74669a

Browse files
authored
Merge pull request #149 from clue-labs/tls
Support explicitly choosing TLS version to negotiate with remote side
2 parents 0cf2798 + 63d050a commit f74669a

File tree

3 files changed

+114
-5
lines changed

3 files changed

+114
-5
lines changed

README.md

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,19 @@ $server = new Server('tls://127.0.0.1:8000', $loop, array(
460460
));
461461
```
462462

463+
By default, this server supports TLSv1.0+ and excludes support for legacy
464+
SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
465+
want to negotiate with the remote side:
466+
467+
```php
468+
$server = new Server('tls://127.0.0.1:8000', $loop, array(
469+
'tls' => array(
470+
'local_cert' => 'server.pem',
471+
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
472+
)
473+
));
474+
```
475+
463476
> Note that available [TLS context options](http://php.net/manual/en/context.ssl.php),
464477
their defaults and effects of changing these may vary depending on your system
465478
and/or PHP version.
@@ -612,6 +625,18 @@ $server = new SecureServer($server, $loop, array(
612625
));
613626
```
614627

628+
By default, this server supports TLSv1.0+ and excludes support for legacy
629+
SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
630+
want to negotiate with the remote side:
631+
632+
```php
633+
$server = new TcpServer(8000, $loop);
634+
$server = new SecureServer($server, $loop, array(
635+
'local_cert' => 'server.pem',
636+
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
637+
));
638+
```
639+
615640
> Note that available [TLS context options](http://php.net/manual/en/context.ssl.php),
616641
their defaults and effects of changing these may vary depending on your system
617642
and/or PHP version.
@@ -1000,6 +1025,18 @@ $connector->connect('tls://localhost:443')->then(function (ConnectionInterface $
10001025
});
10011026
```
10021027

1028+
By default, this connector supports TLSv1.0+ and excludes support for legacy
1029+
SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
1030+
want to negotiate with the remote side:
1031+
1032+
```php
1033+
$connector = new Connector($loop, array(
1034+
'tls' => array(
1035+
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
1036+
)
1037+
));
1038+
```
1039+
10031040
> For more details about context options, please refer to the PHP documentation
10041041
about [socket context options](http://php.net/manual/en/context.socket.php)
10051042
and [SSL context options](http://php.net/manual/en/context.ssl.php).
@@ -1189,7 +1226,7 @@ $promise->cancel();
11891226
```
11901227

11911228
Calling `cancel()` on a pending promise will cancel the underlying TCP/IP
1192-
connection and/or the SSL/TLS negonation and reject the resulting promise.
1229+
connection and/or the SSL/TLS negotiation and reject the resulting promise.
11931230

11941231
You can optionally pass additional
11951232
[SSL context options](http://php.net/manual/en/context.ssl.php)
@@ -1202,6 +1239,16 @@ $secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop, array(
12021239
));
12031240
```
12041241

1242+
By default, this connector supports TLSv1.0+ and excludes support for legacy
1243+
SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
1244+
want to negotiate with the remote side:
1245+
1246+
```php
1247+
$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop, array(
1248+
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
1249+
));
1250+
```
1251+
12051252
> Advanced usage: Internally, the `SecureConnector` relies on setting up the
12061253
required *context options* on the underlying stream resource.
12071254
It should therefor be used with a `TcpConnector` somewhere in the connector

src/StreamEncryption.php

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ public function __construct(LoopInterface $loop, $server = true)
2727
$this->loop = $loop;
2828
$this->server = $server;
2929

30+
// support TLSv1.0+ by default and exclude legacy SSLv2/SSLv3.
31+
// PHP 5.6+ supports bitmasks, legacy PHP only supports predefined
32+
// constants, so apply accordingly below.
33+
// Also, since PHP 5.6.7 up until before PHP 7.2.0 the main constant did
34+
// only support TLSv1.0, so we explicitly apply all versions.
35+
// @link http://php.net/manual/en/migration56.openssl.php#migration56.openssl.crypto-method
36+
// @link https://3v4l.org/plbFn
3037
if ($server) {
3138
$this->method = STREAM_CRYPTO_METHOD_TLS_SERVER;
3239

@@ -79,9 +86,16 @@ public function toggle(Connection $stream, $toggle)
7986
// get actual stream socket from stream instance
8087
$socket = $stream->stream;
8188

89+
// get crypto method from context options or use global setting from constructor
90+
$method = $this->method;
91+
$context = stream_context_get_options($socket);
92+
if (isset($context['ssl']['crypto_method'])) {
93+
$method = $context['ssl']['crypto_method'];
94+
}
95+
8296
$that = $this;
83-
$toggleCrypto = function () use ($socket, $deferred, $toggle, $that) {
84-
$that->toggleCrypto($socket, $deferred, $toggle);
97+
$toggleCrypto = function () use ($socket, $deferred, $toggle, $method, $that) {
98+
$that->toggleCrypto($socket, $deferred, $toggle, $method);
8599
};
86100

87101
$this->loop->addReadStream($socket, $toggleCrypto);
@@ -106,10 +120,10 @@ public function toggle(Connection $stream, $toggle)
106120
});
107121
}
108122

109-
public function toggleCrypto($socket, Deferred $deferred, $toggle)
123+
public function toggleCrypto($socket, Deferred $deferred, $toggle, $method)
110124
{
111125
set_error_handler(array($this, 'handleError'));
112-
$result = stream_socket_enable_crypto($socket, $toggle, $this->method);
126+
$result = stream_socket_enable_crypto($socket, $toggle, $method);
113127
restore_error_handler();
114128

115129
if (true === $result) {

tests/FunctionalSecureServerTest.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,54 @@ public function testPipesDataBackInMultipleChunksFromConnection()
224224
$this->assertEquals(400000, $received);
225225
}
226226

227+
/**
228+
* @requires PHP 5.6
229+
*/
230+
public function testEmitsConnectionForNewTlsv11Connection()
231+
{
232+
$loop = Factory::create();
233+
234+
$server = new TcpServer(0, $loop);
235+
$server = new SecureServer($server, $loop, array(
236+
'local_cert' => __DIR__ . '/../examples/localhost.pem',
237+
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_SERVER
238+
));
239+
$server->on('connection', $this->expectCallableOnce());
240+
241+
$connector = new SecureConnector(new TcpConnector($loop), $loop, array(
242+
'verify_peer' => false,
243+
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT
244+
));
245+
$promise = $connector->connect($server->getAddress());
246+
247+
Block\await($promise, $loop, self::TIMEOUT);
248+
}
249+
250+
/**
251+
* @requires PHP 5.6
252+
*/
253+
public function testEmitsErrorForClientWithTlsVersionMismatch()
254+
{
255+
$loop = Factory::create();
256+
257+
$server = new TcpServer(0, $loop);
258+
$server = new SecureServer($server, $loop, array(
259+
'local_cert' => __DIR__ . '/../examples/localhost.pem',
260+
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_SERVER|STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
261+
));
262+
$server->on('connection', $this->expectCallableNever());
263+
$server->on('error', $this->expectCallableOnce());
264+
265+
$connector = new SecureConnector(new TcpConnector($loop), $loop, array(
266+
'verify_peer' => false,
267+
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT
268+
));
269+
$promise = $connector->connect($server->getAddress());
270+
271+
$this->setExpectedException('RuntimeException', 'handshake');
272+
Block\await($promise, $loop, self::TIMEOUT);
273+
}
274+
227275
public function testEmitsConnectionForNewConnectionWithEncryptedCertificate()
228276
{
229277
$loop = Factory::create();

0 commit comments

Comments
 (0)