Skip to content

Commit 71d129b

Browse files
committed
Future versions of PHP have a bug fix for GH-1
Closes: #1
1 parent b50bc5f commit 71d129b

File tree

1 file changed

+68
-54
lines changed

1 file changed

+68
-54
lines changed

src/DohExecutor.php

Lines changed: 68 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,17 @@
1818
use React\Socket\ConnectionInterface;
1919
use React\Socket\Connector;
2020
use RuntimeException;
21+
use function explode;
22+
use function inet_pton;
23+
use function openssl_x509_fingerprint;
24+
use function openssl_x509_parse;
2125
use function parse_url;
26+
use function str_replace;
27+
use function stream_context_get_params;
2228
use function strlen;
2329
use function strtolower;
2430
use function substr_count;
31+
use function trim;
2532

2633
class DohExecutor implements ExecutorInterface {
2734

@@ -41,6 +48,9 @@ class DohExecutor implements ExecutorInterface {
4148

4249
const FINGERPRINT_HASH_METHOD = 'sha256';
4350

51+
/** @var bool Flag for identifying when we need to workaround php-src issue GH-9356 */
52+
const NEED_GH9356_IPV6_WORKAROUND = (\PHP_VERSION_ID < 80121 || (\PHP_VERSION_ID >= 80200 && \PHP_VERSION_ID < 80208));
53+
4454
/**
4555
* @param string $nameserver
4656
* @param string $method
@@ -137,60 +147,8 @@ private function getBrowser() : Promise\PromiseInterface {
137147
if (!isset($this->browserResolution)) {
138148
$deferred = new Deferred();
139149
$this->browserResolution = $deferred->promise();
140-
if ($this->ipv6address) {
141-
// PHP does not validate IPv6 addresses contained in the SAN fields of a certificate
142-
// To support IPv6 we download the certificate on the first connect and manually verify our nameserver IPv6 IP
143-
// is listed in the SAN fields. We then construct a Browser instance with verify_peer_name set to false but with the peer_fingerprint set to our verified certificate.
144-
// This doesn't always work because the server may use different front end certificates (SIGH!)
145-
$address = str_replace('https://', 'tls://', $this->nameserver);
146-
$connector = new Connector([
147-
'tcp' => [
148-
'tcp_nodelay' => true,
149-
],
150-
'tls' => [
151-
'verify_peer_name' => false,
152-
'capture_peer_cert' => true
153-
],
154-
'dns' => false,
155-
], $this->loop);
156-
$connector->connect($address)->then(function (ConnectionInterface $connection) use ($deferred) {
157-
$response = stream_context_get_params($connection->stream); //Using @internal stream
158-
$connection->end();
159-
$certificatePem = $response['options']['ssl']['peer_certificate'];
160-
161-
$certificateFields = openssl_x509_parse($certificatePem);
162-
$additionalDomains = explode(', ', $certificateFields['extensions']['subjectAltName'] ?? '');
163-
164-
$ip = inet_pton(trim(parse_url($this->nameserver, PHP_URL_HOST), '[]'));
165-
if ($ip !== false) {
166-
foreach ($additionalDomains as $subAltName) {
167-
$subAltName = trim(strtolower($subAltName));
168-
if (str_starts_with($subAltName, 'ip address:')) {
169-
$compare = inet_pton(str_replace('ip address:', '', $subAltName));
170-
if ($compare === $ip) {
171-
$fingerprint = openssl_x509_fingerprint($certificatePem, self::FINGERPRINT_HASH_METHOD);
172-
$browser = (new Browser(new Connector([
173-
'tcp' => [
174-
'tcp_nodelay' => true,
175-
],
176-
'tls' => [
177-
'verify_peer_name' => false,
178-
'peer_fingerprint'=>[
179-
self::FINGERPRINT_HASH_METHOD => $fingerprint,
180-
],
181-
],
182-
], $this->loop), $this->loop));
183-
$deferred->resolve($browser);
184-
return;
185-
}
186-
}
187-
}
188-
}
189-
$deferred->reject(new RuntimeException('IPv6 IP Address Connection Failed. Unable to Validate Peer Certificate'));
190-
191-
}, function($ex) use ($deferred) {
192-
$deferred->reject(new RuntimeException('IPv6 IP Address Connection Failed. ' . $ex->getMessage()));
193-
});
150+
if ($this->ipv6address && self::NEED_GH9356_IPV6_WORKAROUND) {
151+
$this->initialiseIPv6Workaround($deferred);
194152
} else {
195153
$browser = (new Browser(new Connector([
196154
'tcp' => [
@@ -203,4 +161,60 @@ private function getBrowser() : Promise\PromiseInterface {
203161
}
204162
return $this->browserResolution;
205163
}
164+
165+
protected function initialiseIPv6Workaround(Deferred $deferred) : void {
166+
// Some versions of PHP do not validate IPv6 addresses contained in the SAN fields of a certificate
167+
// To support IPv6 we download the certificate on the first connect and manually verify our nameserver IPv6 IP
168+
// is listed in the SAN fields. We then construct a Browser instance with verify_peer_name set to false but with the peer_fingerprint set to our verified certificate.
169+
// This doesn't always work because the server may use different front end certificates (SIGH!)
170+
$address = str_replace('https://', 'tls://', $this->nameserver);
171+
$connector = new Connector([
172+
'tcp' => [
173+
'tcp_nodelay' => true,
174+
],
175+
'tls' => [
176+
'verify_peer_name' => false,
177+
'capture_peer_cert' => true
178+
],
179+
'dns' => false,
180+
], $this->loop);
181+
$connector->connect($address)->then(function (ConnectionInterface $connection) use ($deferred) {
182+
$response = stream_context_get_params($connection->stream); //Using @internal stream
183+
$connection->end();
184+
$certificatePem = $response['options']['ssl']['peer_certificate'];
185+
186+
$certificateFields = openssl_x509_parse($certificatePem);
187+
$additionalDomains = explode(', ', $certificateFields['extensions']['subjectAltName'] ?? '');
188+
189+
$ip = inet_pton(trim(parse_url($this->nameserver, PHP_URL_HOST), '[]'));
190+
if ($ip !== false) {
191+
foreach ($additionalDomains as $subAltName) {
192+
$subAltName = trim(strtolower($subAltName));
193+
if (str_starts_with($subAltName, 'ip address:')) {
194+
$compare = inet_pton(str_replace('ip address:', '', $subAltName));
195+
if ($compare === $ip) {
196+
$fingerprint = openssl_x509_fingerprint($certificatePem, self::FINGERPRINT_HASH_METHOD);
197+
$browser = (new Browser(new Connector([
198+
'tcp' => [
199+
'tcp_nodelay' => true,
200+
],
201+
'tls' => [
202+
'verify_peer_name' => false,
203+
'peer_fingerprint'=>[
204+
self::FINGERPRINT_HASH_METHOD => $fingerprint,
205+
],
206+
],
207+
], $this->loop), $this->loop));
208+
$deferred->resolve($browser);
209+
return;
210+
}
211+
}
212+
}
213+
}
214+
$deferred->reject(new RuntimeException('IPv6 IP Address Connection Failed. Unable to Validate Peer Certificate'));
215+
216+
}, function($ex) use ($deferred) {
217+
$deferred->reject(new RuntimeException('IPv6 IP Address Connection Failed. ' . $ex->getMessage()));
218+
});
219+
}
206220
}

0 commit comments

Comments
 (0)