Skip to content

[HttpClient] Add option crypto_method to set the minimum TLS version and make it default to v1.2 #50274

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions UPGRADE-6.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ FrameworkBundle
HttpClient
----------

* The minimum TLS version now defaults to v1.2; use the `crypto_method`
option if you need to connect to servers that don't support it
* The default user agents have been renamed from `Symfony HttpClient/Amp`, `Symfony HttpClient/Curl`
and `Symfony HttpClient/Native` to `Symfony HttpClient (Amp)`, `Symfony HttpClient (Curl)`
and `Symfony HttpClient (Native)` respectively to comply with the RFC 9110 specification
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1817,7 +1817,7 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e
->info('A network interface name, IP address, a host name or a UNIX socket to bind to.')
->end()
->booleanNode('verify_peer')
->info('Indicates if the peer should be verified in an SSL/TLS context.')
->info('Indicates if the peer should be verified in a TLS context.')
->end()
->booleanNode('verify_host')
->info('Indicates if the host should exist as a certificate common name.')
Expand All @@ -1838,7 +1838,7 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e
->info('The passphrase used to encrypt the "local_pk" file.')
->end()
->scalarNode('ciphers')
->info('A list of SSL/TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...)')
->info('A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...)')
->end()
->arrayNode('peer_fingerprint')
->info('Associative array: hashing algorithm => hash(es).')
Expand All @@ -1849,6 +1849,9 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e
->variableNode('md5')->end()
->end()
->end()
->scalarNode('crypto_method')
->info('The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants.')
->end()
->arrayNode('extra')
->info('Extra options for specific HTTP client')
->normalizeKeys(false)
Expand Down Expand Up @@ -1965,7 +1968,7 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e
->info('A network interface name, IP address, a host name or a UNIX socket to bind to.')
->end()
->booleanNode('verify_peer')
->info('Indicates if the peer should be verified in an SSL/TLS context.')
->info('Indicates if the peer should be verified in a TLS context.')
->end()
->booleanNode('verify_host')
->info('Indicates if the host should exist as a certificate common name.')
Expand All @@ -1986,7 +1989,7 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e
->info('The passphrase used to encrypt the "local_pk" file.')
->end()
->scalarNode('ciphers')
->info('A list of SSL/TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...)')
->info('A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...)')
->end()
->arrayNode('peer_fingerprint')
->info('Associative array: hashing algorithm => hash(es).')
Expand Down
4 changes: 4 additions & 0 deletions src/Symfony/Component/HttpClient/AmpHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ final class AmpHttpClient implements HttpClientInterface, LoggerAwareInterface,
use HttpClientTrait;
use LoggerAwareTrait;

public const OPTIONS_DEFAULTS = HttpClientInterface::OPTIONS_DEFAULTS + [
'crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT,
];

private array $defaultOptions = self::OPTIONS_DEFAULTS;
private static array $emptyDefaults = self::OPTIONS_DEFAULTS;
private AmpClientState $multi;
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/HttpClient/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ CHANGELOG
6.3
---

* Add option `crypto_method` to set the minimum TLS version and make it default to v1.2
* Add `UriTemplateHttpClient` to use URI templates as specified in the RFC 6570
* Add `ServerSentEvent::getArrayData()` to get the Server-Sent Event's data decoded as an array when it's a JSON payload
* Allow array of urls as `base_uri` option value in `RetryableHttpClient` to retry on a new url each time
Expand Down
10 changes: 10 additions & 0 deletions src/Symfony/Component/HttpClient/CurlHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
{
use HttpClientTrait;

public const OPTIONS_DEFAULTS = HttpClientInterface::OPTIONS_DEFAULTS + [
'crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT,
];

private array $defaultOptions = self::OPTIONS_DEFAULTS + [
'auth_ntlm' => null, // array|string - an array containing the username as first value, and optionally the
// password as the second one; or string like username:password - enabling NTLM auth
Expand Down Expand Up @@ -116,6 +120,12 @@ public function request(string $method, string $url, array $options = []): Respo
\CURLOPT_SSLKEY => $options['local_pk'],
\CURLOPT_KEYPASSWD => $options['passphrase'],
\CURLOPT_CERTINFO => $options['capture_peer_cert_chain'],
\CURLOPT_SSLVERSION => match ($options['crypto_method']) {
\STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT => \CURL_SSLVERSION_TLSv1_3,
\STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT => \CURL_SSLVERSION_TLSv1_2,
\STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT => \CURL_SSLVERSION_TLSv1_1,
\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT => \CURL_SSLVERSION_TLSv1_0,
},
];

if (1.0 === (float) $options['http_version']) {
Expand Down
10 changes: 10 additions & 0 deletions src/Symfony/Component/HttpClient/HttpClientTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Symfony\Component\HttpClient\Response\StreamableInterface;
use Symfony\Component\HttpClient\Response\StreamWrapper;
use Symfony\Component\Mime\MimeTypes;
use Symfony\Contracts\HttpClient\HttpClientInterface;

/**
* Provides the common logic from writing HttpClientInterface implementations.
Expand Down Expand Up @@ -116,6 +117,15 @@ private static function prepareRequest(?string $method, ?string $url, array $opt
$options['peer_fingerprint'] = self::normalizePeerFingerprint($options['peer_fingerprint']);
}

if (isset($options['crypto_method']) && !\in_array($options['crypto_method'], [
\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT,
\STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT,
\STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT,
\STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT,
], true)) {
throw new InvalidArgumentException('Option "crypto_method" must be one of "STREAM_CRYPTO_METHOD_TLSv1_*_CLIENT".');
}

// Validate on_progress
if (isset($options['on_progress']) && !\is_callable($onProgress = $options['on_progress'])) {
throw new InvalidArgumentException(sprintf('Option "on_progress" must be callable, "%s" given.', get_debug_type($onProgress)));
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Component/HttpClient/Internal/AmpClientState.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ private function getClient(array $options): array
'ciphers' => $options['ciphers'],
'capture_peer_cert_chain' => $options['capture_peer_cert_chain'] || $options['peer_fingerprint'],
'proxy' => $options['proxy'],
'crypto_method' => $options['crypto_method'],
];

$key = hash('xxh128', serialize($options));
Expand All @@ -141,6 +142,7 @@ private function getClient(array $options): array
$options['local_cert'] && $context = $context->withCertificate(new Certificate($options['local_cert'], $options['local_pk']));
$options['ciphers'] && $context = $context->withCiphers($options['ciphers']);
$options['capture_peer_cert_chain'] && $context = $context->withPeerCapturing();
$options['crypto_method'] && $context = $context->withMinimumVersion($options['crypto_method']);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stof this is the line you're looking for


$connector = $handleConnector = new class() implements Connector {
public $connector;
Expand Down
11 changes: 11 additions & 0 deletions src/Symfony/Component/HttpClient/NativeHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
use HttpClientTrait;
use LoggerAwareTrait;

public const OPTIONS_DEFAULTS = HttpClientInterface::OPTIONS_DEFAULTS + [
'crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT,
];

private array $defaultOptions = self::OPTIONS_DEFAULTS;
private static array $emptyDefaults = self::OPTIONS_DEFAULTS;

Expand Down Expand Up @@ -198,6 +202,12 @@ public function request(string $method, string $url, array $options = []): Respo
$options['timeout'] = min($options['max_duration'], $options['timeout']);
}

switch ($cryptoMethod = $options['crypto_method']) {
case \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT: $cryptoMethod |= \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
case \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT: $cryptoMethod |= \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
case \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT: $cryptoMethod |= \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
}

$context = [
'http' => [
'protocol_version' => min($options['http_version'] ?: '1.1', '1.1'),
Expand All @@ -224,6 +234,7 @@ public function request(string $method, string $url, array $options = []): Respo
'allow_self_signed' => (bool) $options['peer_fingerprint'],
'SNI_enabled' => true,
'disable_compression' => true,
'crypto_method' => $cryptoMethod,
], static fn ($v) => null !== $v),
'socket' => [
'bindto' => $options['bindto'],
Expand Down
5 changes: 5 additions & 0 deletions src/Symfony/Contracts/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

3.3
---

* Add option `crypto_method` to `HttpClientInterface` to define the minimum TLS version to accept

3.2
---

Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Contracts/HttpClient/HttpClientInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ interface HttpClientInterface
'ciphers' => null,
'peer_fingerprint' => null,
'capture_peer_cert_chain' => false,
'crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT, // STREAM_CRYPTO_METHOD_TLSv*_CLIENT - minimum TLS version
'extra' => [], // array - additional options that can be ignored if unsupported, unlike regular options
];

Expand Down