Skip to content

Commit 4da6fda

Browse files
committed
Simplify assigning crypto method to include all TLS versions
This only simplifies some of unneeded assignments for legacy PHP versions and should not affect usage otherwise. TLS 1.3 is implicitly available despite being omitted in this assignment. The required crypto flag is likely going to be added in PHP 7.2.x in the future via php/php-src#3700 and should thus be covered by the main crypto method constant in the future already. Due to the way how PHP interfaces with OpenSSL, this means that TLS 1.3 is in fact already enabled by default when using a recent OpenSSL version for all client and server connections even for older PHP versions.
1 parent e02071c commit 4da6fda

File tree

2 files changed

+122
-22
lines changed

2 files changed

+122
-22
lines changed

src/StreamEncryption.php

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,35 +25,21 @@ public function __construct(LoopInterface $loop, $server = true)
2525
$this->server = $server;
2626

2727
// support TLSv1.0+ by default and exclude legacy SSLv2/SSLv3.
28-
// PHP 5.6+ supports bitmasks, legacy PHP only supports predefined
29-
// constants, so apply accordingly below.
30-
// Also, since PHP 5.6.7 up until before PHP 7.2.0 the main constant did
31-
// only support TLSv1.0, so we explicitly apply all versions.
32-
// @link http://php.net/manual/en/migration56.openssl.php#migration56.openssl.crypto-method
33-
// @link https://3v4l.org/plbFn
28+
// As of PHP 7.2+ the main crypto method constant includes all TLS versions.
29+
// As of PHP 5.6+ the crypto method is a bitmask, so we explicitly include all TLS versions.
30+
// For legacy PHP < 5.6 the crypto method is a single value only and this constant includes all TLS versions.
31+
// @link https://3v4l.org/9PSST
3432
if ($server) {
3533
$this->method = \STREAM_CRYPTO_METHOD_TLS_SERVER;
3634

37-
if (\defined('STREAM_CRYPTO_METHOD_TLSv1_0_SERVER')) {
38-
$this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_SERVER;
39-
}
40-
if (\defined('STREAM_CRYPTO_METHOD_TLSv1_1_SERVER')) {
41-
$this->method |= \STREAM_CRYPTO_METHOD_TLSv1_1_SERVER;
42-
}
43-
if (\defined('STREAM_CRYPTO_METHOD_TLSv1_2_SERVER')) {
44-
$this->method |= \STREAM_CRYPTO_METHOD_TLSv1_2_SERVER;
35+
if (\PHP_VERSION_ID < 70200 && \PHP_VERSION_ID >= 50600) {
36+
$this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_SERVER | \STREAM_CRYPTO_METHOD_TLSv1_1_SERVER | \STREAM_CRYPTO_METHOD_TLSv1_2_SERVER;
4537
}
4638
} else {
4739
$this->method = \STREAM_CRYPTO_METHOD_TLS_CLIENT;
4840

49-
if (\defined('STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT')) {
50-
$this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT;
51-
}
52-
if (\defined('STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT')) {
53-
$this->method |= \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
54-
}
55-
if (\defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
56-
$this->method |= \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
41+
if (\PHP_VERSION_ID < 70200 && \PHP_VERSION_ID >= 50600) {
42+
$this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
5743
}
5844
}
5945
}

tests/FunctionalSecureServerTest.php

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,103 @@ public function testClientCanConnectToServer()
4848
$server->close();
4949
}
5050

51+
public function testClientUsesTls13ByDefaultWhenSupportedByOpenSSL()
52+
{
53+
if (PHP_VERSION_ID < 70000 || !$this->supportsTls13()) {
54+
$this->markTestSkipped('Test requires PHP 7+ for crypto meta data and OpenSSL 1.1.1+ for TLS 1.3');
55+
}
56+
57+
$loop = Factory::create();
58+
59+
$server = new TcpServer(0, $loop);
60+
$server = new SecureServer($server, $loop, array(
61+
'local_cert' => __DIR__ . '/../examples/localhost.pem'
62+
));
63+
64+
$connector = new SecureConnector(new TcpConnector($loop), $loop, array(
65+
'verify_peer' => false
66+
));
67+
$promise = $connector->connect($server->getAddress());
68+
69+
/* @var ConnectionInterface $client */
70+
$client = Block\await($promise, $loop, self::TIMEOUT);
71+
72+
$this->assertInstanceOf('React\Socket\Connection', $client);
73+
$this->assertTrue(isset($client->stream));
74+
75+
$meta = stream_get_meta_data($client->stream);
76+
$this->assertTrue(isset($meta['crypto']['protocol']));
77+
78+
if ($meta['crypto']['protocol'] === 'UNKNOWN') {
79+
// TLSv1.3 protocol will only be added via https://github.com/php/php-src/pull/3700
80+
// prior to merging that PR, this info is still available in the cipher version by OpenSSL
81+
$this->assertTrue(isset($meta['crypto']['cipher_version']));
82+
$this->assertEquals('TLSv1.3', $meta['crypto']['cipher_version']);
83+
} else {
84+
$this->assertEquals('TLSv1.3', $meta['crypto']['protocol']);
85+
}
86+
}
87+
88+
public function testClientUsesTls12WhenCryptoMethodIsExplicitlyConfiguredByClient()
89+
{
90+
if (PHP_VERSION_ID < 70000) {
91+
$this->markTestSkipped('Test requires PHP 7+ for crypto meta data');
92+
}
93+
94+
$loop = Factory::create();
95+
96+
$server = new TcpServer(0, $loop);
97+
$server = new SecureServer($server, $loop, array(
98+
'local_cert' => __DIR__ . '/../examples/localhost.pem'
99+
));
100+
101+
$connector = new SecureConnector(new TcpConnector($loop), $loop, array(
102+
'verify_peer' => false,
103+
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
104+
));
105+
$promise = $connector->connect($server->getAddress());
106+
107+
/* @var ConnectionInterface $client */
108+
$client = Block\await($promise, $loop, self::TIMEOUT);
109+
110+
$this->assertInstanceOf('React\Socket\Connection', $client);
111+
$this->assertTrue(isset($client->stream));
112+
113+
$meta = stream_get_meta_data($client->stream);
114+
$this->assertTrue(isset($meta['crypto']['protocol']));
115+
$this->assertEquals('TLSv1.2', $meta['crypto']['protocol']);
116+
}
117+
118+
public function testClientUsesTls12WhenCryptoMethodIsExplicitlyConfiguredByServer()
119+
{
120+
if (PHP_VERSION_ID < 70000) {
121+
$this->markTestSkipped('Test requires PHP 7+ for crypto meta data');
122+
}
123+
124+
$loop = Factory::create();
125+
126+
$server = new TcpServer(0, $loop);
127+
$server = new SecureServer($server, $loop, array(
128+
'local_cert' => __DIR__ . '/../examples/localhost.pem',
129+
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
130+
));
131+
132+
$connector = new SecureConnector(new TcpConnector($loop), $loop, array(
133+
'verify_peer' => false
134+
));
135+
$promise = $connector->connect($server->getAddress());
136+
137+
/* @var ConnectionInterface $client */
138+
$client = Block\await($promise, $loop, self::TIMEOUT);
139+
140+
$this->assertInstanceOf('React\Socket\Connection', $client);
141+
$this->assertTrue(isset($client->stream));
142+
143+
$meta = stream_get_meta_data($client->stream);
144+
$this->assertTrue(isset($meta['crypto']['protocol']));
145+
$this->assertEquals('TLSv1.2', $meta['crypto']['protocol']);
146+
}
147+
51148
public function testServerEmitsConnectionForClientConnection()
52149
{
53150
$loop = Factory::create();
@@ -621,4 +718,21 @@ private function createPromiseForEvent(EventEmitterInterface $emitter, $event, $
621718
});
622719
});
623720
}
721+
722+
private function supportsTls13()
723+
{
724+
// TLS 1.3 is supported as of OpenSSL 1.1.1 (https://www.openssl.org/blog/blog/2018/09/11/release111/)
725+
// The OpenSSL library version can only be obtained by parsing output from phpinfo().
726+
// OPENSSL_VERSION_TEXT refers to header version which does not necessarily match actual library version
727+
// see php -i | grep OpenSSL
728+
// OpenSSL Library Version => OpenSSL 1.1.1 11 Sep 2018
729+
ob_start();
730+
phpinfo(INFO_MODULES);
731+
$info = ob_get_clean();
732+
733+
if (preg_match('/OpenSSL Library Version => OpenSSL (\S+)/', $info, $match)) {
734+
return version_compare($match[1], '1.1.1', '>=');
735+
}
736+
return false;
737+
}
624738
}

0 commit comments

Comments
 (0)