18
18
use React \Socket \ConnectionInterface ;
19
19
use React \Socket \Connector ;
20
20
use RuntimeException ;
21
+ use function explode ;
22
+ use function inet_pton ;
23
+ use function openssl_x509_fingerprint ;
24
+ use function openssl_x509_parse ;
21
25
use function parse_url ;
26
+ use function str_replace ;
27
+ use function stream_context_get_params ;
22
28
use function strlen ;
23
29
use function strtolower ;
24
30
use function substr_count ;
31
+ use function trim ;
25
32
26
33
class DohExecutor implements ExecutorInterface {
27
34
@@ -41,6 +48,9 @@ class DohExecutor implements ExecutorInterface {
41
48
42
49
const FINGERPRINT_HASH_METHOD = 'sha256 ' ;
43
50
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
+
44
54
/**
45
55
* @param string $nameserver
46
56
* @param string $method
@@ -137,60 +147,8 @@ private function getBrowser() : Promise\PromiseInterface {
137
147
if (!isset ($ this ->browserResolution )) {
138
148
$ deferred = new Deferred ();
139
149
$ 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 );
194
152
} else {
195
153
$ browser = (new Browser (new Connector ([
196
154
'tcp ' => [
@@ -203,4 +161,60 @@ private function getBrowser() : Promise\PromiseInterface {
203
161
}
204
162
return $ this ->browserResolution ;
205
163
}
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
+ }
206
220
}
0 commit comments