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..70a8d426 100644 --- a/tests/FunctionalSecureServerTest.php +++ b/tests/FunctionalSecureServerTest.php @@ -48,6 +48,95 @@ 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']['cipher_version'])); + $this->assertEquals('TLSv1.3', $meta['crypto']['cipher_version']); + } + + 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']['cipher_version'])); + $this->assertEquals('TLSv1.2', $meta['crypto']['cipher_version']); + } + + 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']['cipher_version'])); + $this->assertEquals('TLSv1.2', $meta['crypto']['cipher_version']); + } + public function testServerEmitsConnectionForClientConnection() { $loop = Factory::create(); @@ -621,4 +710,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; + } }