Skip to content

Commit 1d87a24

Browse files
authored
tls: match IPv6 hosts against IP-Address SANs
checkServerIdentity() stopped matching an IPv6 host against a matching IP-Address SAN. The hostname is now run through domainToASCII() before the net.isIP() gate, and domainToASCII('::1') === '' (an IPv6 literal is not a domain), so net.isIP('') is 0, the IP-SAN branch is skipped, and verification fails with "Cert does not contain a DNS name". IPv4 is unaffected because dotted-decimal survives domainToASCII(). Match IP hosts against the original hostname instead of the IDNA- normalized one. net.isIP() rejects non-ASCII input, so there is no IDNA confusion to guard against for an IP literal; the normalized form is still used for the DNS-name path. Fixes: #64144 Signed-off-by: Pascal Garber <[email protected]> PR-URL: #64145 Reviewed-By: Tim Perry <[email protected]>
1 parent 91b65a4 commit 1d87a24

2 files changed

Lines changed: 29 additions & 2 deletions

File tree

lib/tls.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -431,8 +431,14 @@ exports.checkServerIdentity = function checkServerIdentity(hostname, cert) {
431431
let valid = false;
432432
let reason = 'Unknown reason';
433433

434-
if (net.isIP(hostnameASCIIWithoutFQDN)) {
435-
valid = ips.includes(canonicalizeIP(hostnameASCIIWithoutFQDN));
434+
// An IP literal must not be IDNA-normalized: domainToASCII() returns '' for
435+
// an IPv6 literal (it is not a domain), so matching against the normalized
436+
// host would skip IP-SAN matching for IPv6 entirely. Match IP hosts against
437+
// the original hostname (net.isIP() rejects non-ASCII, so there is no IDNA
438+
// confusion to guard against here); the normalized form is kept for the
439+
// DNS-name path below.
440+
if (net.isIP(hostname)) {
441+
valid = ips.includes(canonicalizeIP(hostname));
436442
if (!valid) {
437443
reason =
438444
`IP: ${hostname} is not in the cert's list: ` + ips.join(', ');

test/parallel/test-tls-check-server-identity.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,27 @@ const tests = [
100100
cert: { subject: { CN: '8.8.8.8' }, subjectaltname: 'IP Address:8.8.8.8' }
101101
},
102102

103+
// An "IP Address:" SAN also matches an IPv6 host. Regression test for the
104+
// IDNA-normalization change: domainToASCII('::1') === '' (an IPv6 literal is
105+
// not a domain), which made the normalized host skip IPv6 IP-SAN matching.
106+
{
107+
host: '::1',
108+
cert: { subject: {}, subjectaltname: 'IP Address:::1' }
109+
},
110+
111+
// IPv6 hosts and SANs are matched canonically.
112+
{
113+
host: '2001:db8::1',
114+
cert: { subject: {}, subjectaltname: 'IP Address:2001:DB8:0:0:0:0:0:1' }
115+
},
116+
117+
// A non-matching IPv6 "IP Address:" SAN is rejected.
118+
{
119+
host: '::1',
120+
cert: { subject: {}, subjectaltname: 'IP Address:::2' },
121+
error: 'IP: ::1 is not in the cert\'s list: ::2'
122+
},
123+
103124
// But not when it's a CIDR.
104125
{
105126
host: '8.8.8.8',

0 commit comments

Comments
 (0)