Skip to content

Commit 3d10ba0

Browse files
committed
Fixed bug #9356 Incomplete validation of IP Address fields in subjectAltNames
IPv6 addresses are valid entries in subjectAltNames. Certificate Authorities may issue certificates including IPv6 addresses except if they fall within addresses in the RFC 4193 range. Google and CloudFlare provide IPv6 addresses in their DNS over HTTPS services. Internal CAs do not have those restrictions and can issue Unique local addresses in certificates.
1 parent 78ec64a commit 3d10ba0

File tree

2 files changed

+97
-5
lines changed

2 files changed

+97
-5
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
--TEST--
2+
IPv6 Peer verification matches SAN names
3+
--EXTENSIONS--
4+
openssl
5+
--SKIPIF--
6+
<?php
7+
if (!function_exists("proc_open")) die("skip no proc_open");
8+
?>
9+
--FILE--
10+
<?php
11+
$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'san_ipv6_peer_matching.pem.tmp';
12+
$san = 'IP:2001:db8:85a3:8d3:1319:8a2e:370:7348';
13+
14+
$serverCode = <<<'CODE'
15+
$serverUri = "ssl://[::1]:64321";
16+
$serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
17+
$serverCtx = stream_context_create(['ssl' => [
18+
'local_cert' => '%s',
19+
]]);
20+
21+
$server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
22+
phpt_notify();
23+
24+
@stream_socket_accept($server, 1);
25+
@stream_socket_accept($server, 1);
26+
CODE;
27+
$serverCode = sprintf($serverCode, $certFile);
28+
29+
$clientCode = <<<'CODE'
30+
$serverUri = "ssl://[::1]:64321";
31+
$clientFlags = STREAM_CLIENT_CONNECT;
32+
$clientCtx = stream_context_create(['ssl' => [
33+
'verify_peer' => false,
34+
]]);
35+
36+
phpt_wait();
37+
38+
stream_context_set_option($clientCtx, 'ssl', 'peer_name', '2001:db8:85a3:8d3:1319:8a2e:370:7348');
39+
var_dump(stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx));
40+
41+
stream_context_set_option($clientCtx, 'ssl', 'peer_name', '2001:db8:85a3:8d3:1319:8a2e:370:7349');
42+
var_dump(stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx));
43+
CODE;
44+
45+
include 'CertificateGenerator.inc';
46+
$certificateGenerator = new CertificateGenerator();
47+
$certificateGenerator->saveNewCertAsFileWithKey(null, $certFile, null, $san);
48+
49+
include 'ServerClientTestCase.inc';
50+
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
51+
?>
52+
--CLEAN--
53+
<?php
54+
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'san_ipv6_peer_matching.pem.tmp');
55+
?>
56+
--EXPECTF--
57+
resource(%d) of type (stream)
58+
59+
Warning: stream_socket_client(): Unable to locate peer certificate CN in %s on line %d
60+
61+
Warning: stream_socket_client(): Failed to enable crypto in %s on line %d
62+
63+
Warning: stream_socket_client(): Unable to connect to ssl://[::1]:64321 (Unknown error) in %s on line %d
64+
bool(false)

ext/openssl/xp_ssl.c

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
#undef X509_NAME
4545
#undef X509_CERT_PAIR
4646
#undef X509_EXTENSIONS
47+
#else
48+
#include <arpa/inet.h>
4749
#endif
4850

4951
/* Flags for determining allowed stream crypto methods */
@@ -110,6 +112,21 @@
110112
#define PHP_X509_NAME_ENTRY_TO_UTF8(ne, i, out) \
111113
ASN1_STRING_to_UTF8(&out, X509_NAME_ENTRY_get_data(X509_NAME_get_entry(ne, i)))
112114

115+
/* Used for IPv6 Address peer verification */
116+
#define EXPAND_IPV6_ADDRESS(_str, _bytes) \
117+
do { \
118+
snprintf(_str, 39, "%X:%X:%X:%X:%X:%X:%X:%X", \
119+
_bytes[0] << 8 | _bytes[1], \
120+
_bytes[2] << 8 | _bytes[3], \
121+
_bytes[4] << 8 | _bytes[5], \
122+
_bytes[6] << 8 | _bytes[7], \
123+
_bytes[8] << 8 | _bytes[9], \
124+
_bytes[10] << 8 | _bytes[11], \
125+
_bytes[12] << 8 | _bytes[13], \
126+
_bytes[14] << 8 | _bytes[15] \
127+
); \
128+
} while(0)
129+
113130
#if PHP_OPENSSL_API_VERSION < 0x10100
114131
static RSA *php_openssl_tmp_rsa_cb(SSL *s, int is_export, int keylength);
115132
#endif
@@ -416,11 +433,18 @@ static bool php_openssl_matches_san_list(X509 *peer, const char *subject_name) /
416433
{
417434
int i, len;
418435
unsigned char *cert_name = NULL;
419-
char ipbuffer[64];
436+
char ipbuffer[64], ipv6_expanded[40];
437+
unsigned char ipv6[16];
420438

421439
GENERAL_NAMES *alt_names = X509_get_ext_d2i(peer, NID_subject_alt_name, 0, 0);
422440
int alt_name_count = sk_GENERAL_NAME_num(alt_names);
423441

442+
/* detect if subject name is an IPv6 address and expand once if required */
443+
ipv6_expanded[0] = 0;
444+
if (inet_pton(AF_INET6,subject_name,&ipv6)) {
445+
EXPAND_IPV6_ADDRESS(ipv6_expanded, ipv6);
446+
}
447+
424448
for (i = 0; i < alt_name_count; i++) {
425449
GENERAL_NAME *san = sk_GENERAL_NAME_value(alt_names, i);
426450

@@ -456,13 +480,17 @@ static bool php_openssl_matches_san_list(X509 *peer, const char *subject_name) /
456480
if (strcasecmp(subject_name, (const char*)ipbuffer) == 0) {
457481
sk_GENERAL_NAME_pop_free(alt_names, GENERAL_NAME_free);
458482

483+
return 1;
484+
}
485+
} else if (san->d.ip->length == 16 && strlen(ipv6_expanded) >= 15) { /* shortest expanded IPv6 address is 0:0:0:0:0:0:0:0 */
486+
ipbuffer[0] = 0;
487+
EXPAND_IPV6_ADDRESS(ipbuffer, san->d.iPAddress->data);
488+
if (strcasecmp((const char*)ipv6_expanded, (const char*)ipbuffer) == 0) {
489+
sk_GENERAL_NAME_pop_free(alt_names, GENERAL_NAME_free);
490+
459491
return 1;
460492
}
461493
}
462-
/* No, we aren't bothering to check IPv6 addresses. Why?
463-
* Because IP SAN names are officially deprecated and are
464-
* not allowed by CAs starting in 2015. Deal with it.
465-
*/
466494
}
467495
}
468496

0 commit comments

Comments
 (0)