From e02071c25abd7f65f2040cc19d9f205e376e6e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 1 Jan 2019 17:57:13 +0100 Subject: [PATCH 1/2] Improve TLS 1.3 support by always reading complete receive buffer Construct underlying stream to always consume complete receive buffer. This avoids stale data in TLS buffers and also works around possible buffering issues in legacy PHP versions. The buffer size is limited due to TCP/IP buffers anyway, so this should not affect usage otherwise. This builds on top of https://github.com/reactphp/stream/pull/139 to work around a bug in PHP where reading from a TLS 1.3 stream resource would hang with 100% CPU usage due to the changed TLS 1.3 handshake. --- README.md | 13 ------------- composer.json | 2 +- src/Connection.php | 15 +++++---------- 3 files changed, 6 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 6ab9b3f6..8c71e19e 100644 --- a/README.md +++ b/README.md @@ -1372,19 +1372,6 @@ This library does not take responsibility over these context options, so it's up to consumers of this library to take care of setting appropriate context options as described above. -All versions of PHP prior to 5.6.8 suffered from a buffering issue where reading -from a streaming TLS connection could be one `data` event behind. -This library implements a work-around to try to flush the complete incoming -data buffers on these legacy PHP versions, which has a penalty of around 10% of -throughput on all connections. -With this work-around, we have not been able to reproduce this issue anymore, -but we have seen reports of people saying this could still affect some of the -older PHP versions (`5.5.23`, `5.6.7`, and `5.6.8`). -Note that this only affects *some* higher-level streaming protocols, such as -IRC over TLS, but should not affect HTTP over TLS (HTTPS). -Further investigation of this issue is needed. -For more insights, this issue is also covered by our test suite. - PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big chunks of data over TLS streams at once. We try to work around this by limiting the write chunk size to 8192 diff --git a/composer.json b/composer.json index cad0aef0..70643316 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "evenement/evenement": "^3.0 || ^2.0 || ^1.0", "react/dns": "^0.4.13", "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", - "react/stream": "^1.0 || ^0.7.1", + "react/stream": "^1.1", "react/promise": "^2.6.0 || ^1.2.1", "react/promise-timer": "^1.4.0" }, diff --git a/src/Connection.php b/src/Connection.php index afe3184b..a27784d5 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -43,15 +43,6 @@ class Connection extends EventEmitter implements ConnectionInterface public function __construct($resource, LoopInterface $loop) { - // PHP < 5.6.8 suffers from a buffer indicator bug on secure TLS connections - // as a work-around we always read the complete buffer until its end. - // The buffer size is limited due to TCP/IP buffers anyway, so this - // should not affect usage otherwise. - // See https://bugs.php.net/bug.php?id=65137 - // https://bugs.php.net/bug.php?id=41631 - // https://github.com/reactphp/socket-client/issues/24 - $clearCompleteBuffer = \PHP_VERSION_ID < 50608; - // PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big // chunks of data over TLS streams at once. // We try to work around this by limiting the write chunk size to 8192 @@ -62,10 +53,14 @@ public function __construct($resource, LoopInterface $loop) // See https://github.com/reactphp/socket/issues/105 $limitWriteChunks = (\PHP_VERSION_ID < 70018 || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70104)); + // Construct underlying stream to always consume complete receive buffer. + // This avoids stale data in TLS buffers and also works around possible + // buffering issues in legacy PHP versions. The buffer size is limited + // due to TCP/IP buffers anyway, so this should not affect usage otherwise. $this->input = new DuplexResourceStream( $resource, $loop, - $clearCompleteBuffer ? -1 : null, + -1, new WritableResourceStream($resource, $loop, null, $limitWriteChunks ? 8192 : null) ); From 4da6fdaa9e31385b51cd4299d601907a97307759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 31 Dec 2018 17:15:49 +0100 Subject: [PATCH 2/2] 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 https://github.com/php/php-src/pull/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. --- src/StreamEncryption.php | 30 ++----- tests/FunctionalSecureServerTest.php | 114 +++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 22 deletions(-) diff --git a/src/StreamEncryption.php b/src/StreamEncryption.php index ad9afce1..0e0f3d6e 100644 --- a/src/StreamEncryption.php +++ b/src/StreamEncryption.php @@ -25,35 +25,21 @@ public function __construct(LoopInterface $loop, $server = true) $this->server = $server; // support TLSv1.0+ by default and exclude legacy SSLv2/SSLv3. - // PHP 5.6+ supports bitmasks, legacy PHP only supports predefined - // constants, so apply accordingly below. - // Also, since PHP 5.6.7 up until before PHP 7.2.0 the main constant did - // only support TLSv1.0, so we explicitly apply all versions. - // @link http://php.net/manual/en/migration56.openssl.php#migration56.openssl.crypto-method - // @link https://3v4l.org/plbFn + // As of PHP 7.2+ the main crypto method constant includes all TLS versions. + // As of PHP 5.6+ the crypto method is a bitmask, so we explicitly include all TLS versions. + // For legacy PHP < 5.6 the crypto method is a single value only and this constant includes all TLS versions. + // @link https://3v4l.org/9PSST if ($server) { $this->method = \STREAM_CRYPTO_METHOD_TLS_SERVER; - if (\defined('STREAM_CRYPTO_METHOD_TLSv1_0_SERVER')) { - $this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_SERVER; - } - if (\defined('STREAM_CRYPTO_METHOD_TLSv1_1_SERVER')) { - $this->method |= \STREAM_CRYPTO_METHOD_TLSv1_1_SERVER; - } - if (\defined('STREAM_CRYPTO_METHOD_TLSv1_2_SERVER')) { - $this->method |= \STREAM_CRYPTO_METHOD_TLSv1_2_SERVER; + if (\PHP_VERSION_ID < 70200 && \PHP_VERSION_ID >= 50600) { + $this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_SERVER | \STREAM_CRYPTO_METHOD_TLSv1_1_SERVER | \STREAM_CRYPTO_METHOD_TLSv1_2_SERVER; } } else { $this->method = \STREAM_CRYPTO_METHOD_TLS_CLIENT; - if (\defined('STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT')) { - $this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT; - } - if (\defined('STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT')) { - $this->method |= \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; - } - if (\defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { - $this->method |= \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; + if (\PHP_VERSION_ID < 70200 && \PHP_VERSION_ID >= 50600) { + $this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; } } } diff --git a/tests/FunctionalSecureServerTest.php b/tests/FunctionalSecureServerTest.php index ea3dcfdd..4caee701 100644 --- a/tests/FunctionalSecureServerTest.php +++ b/tests/FunctionalSecureServerTest.php @@ -48,6 +48,103 @@ public function testClientCanConnectToServer() $server->close(); } + public function testClientUsesTls13ByDefaultWhenSupportedByOpenSSL() + { + if (PHP_VERSION_ID < 70000 || !$this->supportsTls13()) { + $this->markTestSkipped('Test requires PHP 7+ for crypto meta data and OpenSSL 1.1.1+ for TLS 1.3'); + } + + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost.pem' + )); + + $connector = new SecureConnector(new TcpConnector($loop), $loop, array( + 'verify_peer' => false + )); + $promise = $connector->connect($server->getAddress()); + + /* @var ConnectionInterface $client */ + $client = Block\await($promise, $loop, self::TIMEOUT); + + $this->assertInstanceOf('React\Socket\Connection', $client); + $this->assertTrue(isset($client->stream)); + + $meta = stream_get_meta_data($client->stream); + $this->assertTrue(isset($meta['crypto']['protocol'])); + + if ($meta['crypto']['protocol'] === 'UNKNOWN') { + // TLSv1.3 protocol will only be added via https://github.com/php/php-src/pull/3700 + // prior to merging that PR, this info is still available in the cipher version by OpenSSL + $this->assertTrue(isset($meta['crypto']['cipher_version'])); + $this->assertEquals('TLSv1.3', $meta['crypto']['cipher_version']); + } else { + $this->assertEquals('TLSv1.3', $meta['crypto']['protocol']); + } + } + + public function testClientUsesTls12WhenCryptoMethodIsExplicitlyConfiguredByClient() + { + if (PHP_VERSION_ID < 70000) { + $this->markTestSkipped('Test requires PHP 7+ for crypto meta data'); + } + + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost.pem' + )); + + $connector = new SecureConnector(new TcpConnector($loop), $loop, array( + 'verify_peer' => false, + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT + )); + $promise = $connector->connect($server->getAddress()); + + /* @var ConnectionInterface $client */ + $client = Block\await($promise, $loop, self::TIMEOUT); + + $this->assertInstanceOf('React\Socket\Connection', $client); + $this->assertTrue(isset($client->stream)); + + $meta = stream_get_meta_data($client->stream); + $this->assertTrue(isset($meta['crypto']['protocol'])); + $this->assertEquals('TLSv1.2', $meta['crypto']['protocol']); + } + + public function testClientUsesTls12WhenCryptoMethodIsExplicitlyConfiguredByServer() + { + if (PHP_VERSION_ID < 70000) { + $this->markTestSkipped('Test requires PHP 7+ for crypto meta data'); + } + + $loop = Factory::create(); + + $server = new TcpServer(0, $loop); + $server = new SecureServer($server, $loop, array( + 'local_cert' => __DIR__ . '/../examples/localhost.pem', + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER + )); + + $connector = new SecureConnector(new TcpConnector($loop), $loop, array( + 'verify_peer' => false + )); + $promise = $connector->connect($server->getAddress()); + + /* @var ConnectionInterface $client */ + $client = Block\await($promise, $loop, self::TIMEOUT); + + $this->assertInstanceOf('React\Socket\Connection', $client); + $this->assertTrue(isset($client->stream)); + + $meta = stream_get_meta_data($client->stream); + $this->assertTrue(isset($meta['crypto']['protocol'])); + $this->assertEquals('TLSv1.2', $meta['crypto']['protocol']); + } + public function testServerEmitsConnectionForClientConnection() { $loop = Factory::create(); @@ -621,4 +718,21 @@ private function createPromiseForEvent(EventEmitterInterface $emitter, $event, $ }); }); } + + private function supportsTls13() + { + // TLS 1.3 is supported as of OpenSSL 1.1.1 (https://www.openssl.org/blog/blog/2018/09/11/release111/) + // The OpenSSL library version can only be obtained by parsing output from phpinfo(). + // OPENSSL_VERSION_TEXT refers to header version which does not necessarily match actual library version + // see php -i | grep OpenSSL + // OpenSSL Library Version => OpenSSL 1.1.1 11 Sep 2018 + ob_start(); + phpinfo(INFO_MODULES); + $info = ob_get_clean(); + + if (preg_match('/OpenSSL Library Version => OpenSSL (\S+)/', $info, $match)) { + return version_compare($match[1], '1.1.1', '>='); + } + return false; + } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy