From de062fe1a4e061dc952e8894233e316a36af9308 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Wed, 13 May 2026 14:36:24 -0400 Subject: [PATCH] ext/openssl: Match IPv6 IPADDR SAN when connecting to bracketed URI php_openssl_get_url_name() returns NULL for resourcenames of the form "[::1]:port" because php_url_parse_ex() cannot extract a host from a bracketed hostport when no scheme is present. With url_name unset, the SAN matcher falls back to peer_name which is also unset under default client config, so verify_peer_name silently rejects every IPv6 literal target even when the cert carries the matching IPADDR SAN entry. Handle the bare "[host]:port" form before php_url_parse_ex() and strip surrounding brackets on the parse path for callers that pass a full "ssl://[::1]:port" URL. The SAN matcher's inet_pton(AF_INET6, ...) call now sees "::1" instead of "[::1]" and the 16-byte IPADDR SAN comparison body runs. --- .../tests/peer_verification_ipv6_san.phpt | 64 +++++++++++++++++++ ext/openssl/xp_ssl.c | 12 ++++ 2 files changed, 76 insertions(+) create mode 100644 ext/openssl/tests/peer_verification_ipv6_san.phpt diff --git a/ext/openssl/tests/peer_verification_ipv6_san.phpt b/ext/openssl/tests/peer_verification_ipv6_san.phpt new file mode 100644 index 000000000000..109fec1358da --- /dev/null +++ b/ext/openssl/tests/peer_verification_ipv6_san.phpt @@ -0,0 +1,64 @@ +--TEST-- +Peer verification matches an IPADDR SAN when connecting to a bracketed IPv6 URI +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + [ + 'local_cert' => '%s' + ]]); + + $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx); + phpt_notify_server_start($server); + + $client = @stream_socket_accept($server, 3); + if ($client) { + fwrite($client, "HELLO\n"); + } +CODE; +$serverCode = sprintf($serverCode, $certFile); + +$clientCode = <<<'CODE' + $serverUri = "ssl://{{ ADDR }}"; + $clientFlags = STREAM_CLIENT_CONNECT; + $clientCtx = stream_context_create(['ssl' => [ + 'cafile' => '%s', + ]]); + + $client = @stream_socket_client($serverUri, $errno, $errstr, 3, $clientFlags, $clientCtx); + if (!$client) { + echo "connect failed: $errstr\n"; + return; + } + echo trim(fread($client, 16)), "\n"; +CODE; +$clientCode = sprintf($clientCode, $cacertFile); + +include 'CertificateGenerator.inc'; +$certificateGenerator = new CertificateGenerator(); +$certificateGenerator->saveCaCert($cacertFile); +$certificateGenerator->saveNewCertAsFileWithKey('ipv6-san', $certFile, null, 'IP:::1'); + +include 'ServerClientTestCase.inc'; +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--CLEAN-- + +--EXPECT-- +HELLO diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index ffacd8a107b7..796427681db5 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -2760,6 +2760,13 @@ static char *php_openssl_get_url_name(const char *resourcename, return NULL; } + if (resourcenamelen >= 2 && resourcename[0] == '[') { + const char *end = memchr(resourcename, ']', resourcenamelen); + if (end != NULL && end > resourcename + 1) { + return pestrndup(resourcename + 1, end - resourcename - 1, is_persistent); + } + } + url = php_url_parse_ex(resourcename, resourcenamelen); if (!url) { return NULL; @@ -2775,6 +2782,11 @@ static char *php_openssl_get_url_name(const char *resourcename, --len; } + if (len >= 2 && host[0] == '[' && host[len-1] == ']') { + host += 1; + len -= 2; + } + if (len) { url_name = pestrndup(host, len, is_persistent); }