Skip to content

Commit

Permalink
Add support for setting crypto method in a unified way (#3132)
Browse files Browse the repository at this point in the history
  • Loading branch information
GrahamCampbell committed May 13, 2023
1 parent 255d715 commit a8154fc
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 0 deletions.
22 changes: 22 additions & 0 deletions docs/request-options.rst
Expand Up @@ -299,6 +299,28 @@ connect_timeout
handler.


.. _crypto_method-option:

crypto_method
---------------

:Summary: A value describing the minimum TLS protocol version to use.
:Types: int
:Default: None
:Constant: ``GuzzleHttp\RequestOptions::CRYPTO_METHOD``

.. code-block:: php
$client->request('GET', '/foo', ['crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT]);
.. note::

This setting must be set to one of the ``STREAM_CRYPTO_METHOD_TLS*_CLIENT``
constants. PHP 7.4 or higher is required in order to use TLS 1.3, and cURL
7.34.0 or higher is required in order to specify a crypto method, with cURL
7.52.0 or higher being required to use TLS 1.3.


.. _debug-option:

debug
Expand Down
26 changes: 26 additions & 0 deletions src/Handler/CurlFactory.php
Expand Up @@ -452,6 +452,32 @@ private function applyHandlerOptions(EasyHandle $easy, array &$conf): void
}
}

if (isset($options['crypto_method'])) {
if (\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method']) {
if (!defined('CURL_SSLVERSION_TLSv1_0')) {
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.0 not supported by your version of cURL');
}
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_0;
} elseif (\STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT === $options['crypto_method']) {
if (!defined('CURL_SSLVERSION_TLSv1_1')) {
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.1 not supported by your version of cURL');
}
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_1;
} elseif (\STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT === $options['crypto_method']) {
if (!defined('CURL_SSLVERSION_TLSv1_2')) {
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.2 not supported by your version of cURL');
}
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_2;
} elseif (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT === $options['crypto_method']) {
if (!defined('CURL_SSLVERSION_TLSv1_3')) {
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.3 not supported by your version of cURL');
}
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_3;
} else {
throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided');
}
}

if (isset($options['cert'])) {
$cert = $options['cert'];
if (\is_array($cert)) {
Expand Down
19 changes: 19 additions & 0 deletions src/Handler/StreamHandler.php
Expand Up @@ -472,6 +472,25 @@ private function add_timeout(RequestInterface $request, array &$options, $value,
}
}

/**
* @param mixed $value as passed via Request transfer options.
*/
private function add_crypto_method(RequestInterface $request, array &$options, $value, array &$params): void
{
if (
$value === \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT
|| $value === \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT
|| $value === \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
|| (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && $value === \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT)
) {
$options['http']['crypto_method'] = $value;

return;
}

throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided');
}

/**
* @param mixed $value as passed via Request transfer options.
*/
Expand Down
12 changes: 12 additions & 0 deletions src/RequestOptions.php
Expand Up @@ -74,6 +74,18 @@ final class RequestOptions
*/
public const CONNECT_TIMEOUT = 'connect_timeout';

/**
* crypto_method: (int) A value describing the minimum TLS protocol
* version to use.
*
* This setting must be set to one of the
* ``STREAM_CRYPTO_METHOD_TLS*_CLIENT`` constants. PHP 7.4 or higher is
* required in order to use TLS 1.3, and cURL 7.34.0 or higher is required
* in order to specify a crypto method, with cURL 7.52.0 or higher being
* required to use TLS 1.3.
*/
public const CRYPTO_METHOD = 'crypto_method';

/**
* debug: (bool|resource) Set to true or set to a PHP stream returned by
* fopen() enable debug output with the HTTP handler used to send a
Expand Down
40 changes: 40 additions & 0 deletions tests/Handler/CurlFactoryTest.php
Expand Up @@ -221,6 +221,46 @@ public function testUsesProxy()
self::assertSame('hi', (string) $response->getBody());
}

public function testValidatesCryptoMethodInvalidMethod()
{
$f = new Handler\CurlFactory(3);

$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid crypto_method request option: unknown version provided');
$f->create(new Psr7\Request('GET', Server::$url), ['crypto_method' => 123]);
}

public function testAddsCryptoMethodTls10()
{
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', Server::$url), ['crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT]);
self::assertEquals(\CURL_SSLVERSION_TLSv1_0, $_SERVER['_curl'][\CURLOPT_SSLVERSION]);
}

public function testAddsCryptoMethodTls11()
{
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', Server::$url), ['crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT]);
self::assertEquals(\CURL_SSLVERSION_TLSv1_1, $_SERVER['_curl'][\CURLOPT_SSLVERSION]);
}

public function testAddsCryptoMethodTls12()
{
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', Server::$url), ['crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT]);
self::assertEquals(\CURL_SSLVERSION_TLSv1_2, $_SERVER['_curl'][\CURLOPT_SSLVERSION]);
}

/**
* @requires PHP >= 7.4
*/
public function testAddsCryptoMethodTls13()
{
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', Server::$url), ['crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT]);
self::assertEquals(\CURL_SSLVERSION_TLSv1_3, $_SERVER['_curl'][\CURLOPT_SSLVERSION]);
}

public function testValidatesSslKey()
{
$f = new Handler\CurlFactory(3);
Expand Down
39 changes: 39 additions & 0 deletions tests/Handler/StreamHandlerTest.php
Expand Up @@ -379,6 +379,45 @@ public function testEnsuresVerifyOptionIsValid()
$this->getSendResult(['verify' => 10]);
}

public function testEnsuresCryptoMethodOptionIsValid()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid crypto_method request option: unknown version provided');

$this->getSendResult(['crypto_method' => 123]);
}

public function testSetsCryptoMethodTls10()
{
$res = $this->getSendResult(['crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT]);
$opts = \stream_context_get_options($res->getBody()->detach());
self::assertSame(\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT, $opts['http']['crypto_method']);
}

public function testSetsCryptoMethodTls11()
{
$res = $this->getSendResult(['crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT]);
$opts = \stream_context_get_options($res->getBody()->detach());
self::assertSame(\STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT, $opts['http']['crypto_method']);
}

public function testSetsCryptoMethodTls12()
{
$res = $this->getSendResult(['crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT]);
$opts = \stream_context_get_options($res->getBody()->detach());
self::assertSame(\STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT, $opts['http']['crypto_method']);
}

/**
* @requires PHP >=7.4
*/
public function testSetsCryptoMethodTls13()
{
$res = $this->getSendResult(['crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT]);
$opts = \stream_context_get_options($res->getBody()->detach());
self::assertSame(\STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT, $opts['http']['crypto_method']);
}

public function testCanSetPasswordWhenSettingCert()
{
$path = __FILE__;
Expand Down

0 comments on commit a8154fc

Please sign in to comment.