tls: reject non-canonical IP addresses · nodejs/node@935918e · GitHub
Skip to content

Commit 935918e

Browse files
mcollinaaduh95
authored andcommitted
tls: reject non-canonical IP addresses
TLS identity checks normalized non-canonical numeric hosts before matching certificates. Platform resolvers can interpret the original host as a different address. Reject hosts that become IP addresses only after domainToASCII() normalization. Assisted-by: Pi Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: nodejs-private/node-private#982 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> CVE-ID: CVE-2026-106069
1 parent de49c91 commit 935918e

6 files changed

Lines changed: 131 additions & 55 deletions

File tree

‎lib/tls.js‎

Lines changed: 18 additions & 20 deletions

‎test/fixtures/keys/Makefile‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ all: \
105105
incorrect_san_correct_subject-key.pem \
106106
irrelevant_san_correct_subject-cert.pem \
107107
irrelevant_san_correct_subject-key.pem \
108+
non-canonical-ip-dns-cert.pem \
109+
non-canonical-ip-dns-key.pem \
108110
ml_dsa_44_private.pem \
109111
ml_dsa_44_private_seed_only.pem \
110112
ml_dsa_44_private_priv_only.pem \
@@ -1258,6 +1260,28 @@ irrelevant_san_correct_subject-cert.pem: irrelevant_san_correct_subject-key.pem
12581260
irrelevant_san_correct_subject-key.pem:
12591261
openssl ecparam -name prime256v1 -genkey -noout -out irrelevant_san_correct_subject-key.pem
12601262

1263+
non-canonical-ip-dns-cert.pem: non-canonical-ip-dns-key.pem ca1-cert.pem ca1-key.pem
1264+
openssl req -new \
1265+
-key non-canonical-ip-dns-key.pem \
1266+
-out non-canonical-ip-dns-cert.csr \
1267+
-sha256 \
1268+
-subj "/CN=Example Operator" \
1269+
-addext "subjectAltName = DNS:87.0.0.1"
1270+
openssl x509 -req \
1271+
-in non-canonical-ip-dns-cert.csr \
1272+
-CA ca1-cert.pem \
1273+
-CAkey ca1-key.pem \
1274+
-passin "pass:password" \
1275+
-set_serial 0x1001 \
1276+
-out non-canonical-ip-dns-cert.pem \
1277+
-days 99999 \
1278+
-copy_extensions copy \
1279+
-sha256
1280+
rm non-canonical-ip-dns-cert.csr
1281+
1282+
non-canonical-ip-dns-key.pem:
1283+
openssl ecparam -name prime256v1 -genkey -noout -out non-canonical-ip-dns-key.pem
1284+
12611285
clean:
12621286
rm -f *.pfx *.pem *.srl ca2-database.txt ca2-crl-agent3-database.txt* ca2-crl-agent3.cnf ca2-serial fake-startcom-root-serial *.print *.old fake-startcom-root-issued-certs/*.pem
12631287
@> fake-startcom-root-database.txt
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDJDCCAgygAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwejELMAkGA1UEBhMCVVMx
3+
CzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEPMA0GA1UECgwGSm95ZW50MRAwDgYD
4+
VQQLDAdOb2RlLmpzMQwwCgYDVQQDDANjYTExIDAeBgkqhkiG9w0BCQEWEXJ5QHRp
5+
bnljbG91ZHMub3JnMCAXDTI2MDkxNTA4MjM0MloYDzIzMDAwNjMwMDgyMzQyWjAb
6+
MRkwFwYDVQQDDBBFeGFtcGxlIE9wZXJhdG9yMFkwEwYHKoZIzj0CAQYIKoZIzj0D
7+
AQcDQgAEtZoLwubUMsMLAtfUgQc7W5WzBW9Y1nq9DTma/F5tUnS7E9nZdnaFd5bj
8+
hofRf5qjd5FyduDgJU0ABKYHVph8/KOB2zCB2DATBgNVHREEDDAKggg4Ny4wLjAu
9+
MTAdBgNVHQ4EFgQUUlN/+oxGG+tfBUkKQX1RBmAl0DwwgaEGA1UdIwSBmTCBlqF+
10+
pHwwejELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEPMA0G
11+
A1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQDDANjYTExIDAe
12+
BgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnghRKsWyN/Wp9DS38q9+cSw6S
13+
xq0CKTANBgkqhkiG9w0BAQsFAAOCAQEApYHYIBczeRxLk1RO1uQICIXtiO1stPu+
14+
Q7PuFAJsv3n8+jp93kBXQn/p7HsstEC0xZfgJ5AFepkBL+r4Nf6rj7+owhDCDpSI
15+
2zV62t8Rmh7gbSj2C6O7vUMowXK0RrLlAl9kWJBGAlQ9huLDdtPrbcv6LBEnuTZ2
16+
B/hJ02I2dVALwuei41R1Omd/mAn1dWozXqlRgF39EUUXWXK/Fd/d44J2cpokLxfs
17+
3OcMrXWY0dXUdPLCvmOB97egcnpy7APOVDXCRmYEzJktiaESkCgeoXMxXPE6oexz
18+
OFzMLtMAAPCh3orw06x9kfaZ/BXecWSbtsza8odbQlaQx9ZBBn8eew==
19+
-----END CERTIFICATE-----
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
MHcCAQEEICA0HmHFMqxNucO30myyMnWxrGy6kXnfP2hNyOhOvylJoAoGCCqGSM49
3+
AwEHoUQDQgAEtZoLwubUMsMLAtfUgQc7W5WzBW9Y1nq9DTma/F5tUnS7E9nZdnaF
4+
d5bjhofRf5qjd5FyduDgJU0ABKYHVph8/A==
5+
-----END EC PRIVATE KEY-----

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

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -394,46 +394,43 @@ const tests = [
394394
},
395395
error: 'IP: 127.0.0.1 is not in the cert\'s list: '
396396
},
397-
// A host that net.isIP() rejects but that normalizes to an IPv4 address is
398-
// an IP address, not a DNS name: it is matched against "IP Address:" SANs,
399-
// never against DNS names or the CN.
400-
{
401-
host: '\uFF11\uFF12\uFF17.0.0.1', // "127.0.0.1" with fullwidth digits.
402-
cert: { subjectaltname: 'DNS:127.0.0.1', subject: {} },
403-
error: 'IP: \uFF11\uFF12\uFF17.0.0.1 is not in the cert\'s list: '
404-
},
405-
{
406-
host: '\uFF11\uFF12\uFF17.0.0.1',
407-
cert: { subjectaltname: 'DNS:*.0.0.1', subject: {} },
408-
error: 'IP: \uFF11\uFF12\uFF17.0.0.1 is not in the cert\'s list: '
409-
},
410-
{
411-
host: '\uFF11\uFF12\uFF17.0.0.1',
412-
cert: { subject: { CN: '127.0.0.1' } },
413-
error: 'IP: \uFF11\uFF12\uFF17.0.0.1 is not in the cert\'s list: '
414-
},
415-
{
416-
host: '\uFF11\uFF12\uFF17.0.0.1',
417-
cert: { subjectaltname: 'IP Address:127.0.0.1', subject: {} }
418-
},
419-
{
420-
host: '\uFF11\uFF12\uFF17.0.0.1',
421-
cert: { subjectaltname: 'IP Address:127.0.0.2', subject: {} },
422-
error: 'IP: \uFF11\uFF12\uFF17.0.0.1 is not in the cert\'s list: 127.0.0.2'
423-
},
397+
// Non-canonical IP addresses are rejected instead of being matched against
398+
// an address produced by domainToASCII().
399+
...[
400+
['0127.0.0.1', '87.0.0.1'],
401+
['0177.0.0.1', '127.0.0.1'],
402+
['0x7f.0.0.1', '127.0.0.1'],
403+
['2130706433', '127.0.0.1'],
404+
['127.1', '127.0.0.1'],
405+
['\uFF11\uFF12\uFF17.0.0.1', '127.0.0.1'], // Fullwidth digits.
406+
['127\u30020.0.1', '127.0.0.1'], // Ideographic full stop.
407+
].map(([host, normalized]) => ({
408+
host,
409+
cert: {
410+
subjectaltname: `DNS:${normalized}`,
411+
subject: {},
412+
},
413+
error: 'Host is a non-canonical IP address',
414+
})),
424415
{
425-
host: '127\u30020.0.1', // "127.0.0.1" with an ideographic full stop.
426-
cert: { subjectaltname: 'DNS:127.0.0.1', subject: {} },
427-
error: 'IP: 127\u30020.0.1 is not in the cert\'s list: '
416+
host: '0127.0.0.1', cert: {
417+
subjectaltname: 'IP Address:87.0.0.1',
418+
subject: { CN: '87.0.0.1' },
419+
},
420+
error: 'Host is a non-canonical IP address',
428421
},
429422
{
430-
host: '127.1',
431-
cert: { subjectaltname: 'DNS:127.0.0.1', subject: {} },
432-
error: 'IP: 127.1 is not in the cert\'s list: '
423+
host: '0127.0.0.1', cert: {
424+
subjectaltname: 'IP Address:87.0.0.1',
425+
subject: { CN: 'Example Operator' },
426+
},
427+
error: 'Host is a non-canonical IP address',
433428
},
434429
{
435-
host: '127.1',
436-
cert: { subjectaltname: 'IP Address:127.0.0.1', subject: {} }
430+
host: '1.2.3.example', cert: {
431+
subjectaltname: 'DNS:1.2.3.example',
432+
subject: {},
433+
},
437434
},
438435
{
439436
host: 'localhost', cert: {
Lines changed: 33 additions & 0 deletions

0 commit comments

Comments
 (0)