]> dgit.raspbian.org Git - nodejs.git/commitdiff
tls: normalize hostname for server identity checks
authorMatteo Collina <hello@matteocollina.com>
Mon, 11 May 2026 11:02:28 +0000 (13:02 +0200)
committerBastien Roucariès <rouca@debian.org>
Sun, 16 Aug 2026 17:34:06 +0000 (19:34 +0200)
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs-private/node-private/pull/869
Backport-PR-URL: https://github.com/nodejs-private/node-private/pull/893
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
CVE-ID: CVE-2026-48618
Refs: https://hackerone.com/reports/3688064
origin: backport, https://github.com/nodejs/node/commit/2197a47144f3356ab451c5dcd858a49eb5957a70

Gbp-Pq: Topic sec
Gbp-Pq: Name CVE-2026-48618.patch

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

index b78f56d49a1c254de2b85a3ee52b87f539c80638..ac8b8cc0142415497882ca1dacd030d3ed051a94 100644 (file)
@@ -66,6 +66,8 @@ const { canonicalizeIP } = internalBinding('cares_wrap');
 const _tls_common = require('_tls_common');
 const _tls_wrap = require('_tls_wrap');
 const { createSecurePair } = require('internal/tls/secure-pair');
+const { domainToASCII } = require('internal/url');
+const { validateString } = require('internal/validators');
 
 // Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations
 // every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more
@@ -284,6 +286,11 @@ exports.checkServerIdentity = function checkServerIdentity(hostname, cert) {
   const ips = [];
 
   hostname = '' + hostname;
+  const hostnameASCII = domainToASCII(hostname);
+
+  // Remove trailing dots for error messages and matching.
+  hostname = unfqdn(hostname);
+  const hostnameASCIIWithoutFQDN = unfqdn(hostnameASCII);
 
   if (altNames) {
     const splitAltNames = StringPrototypeIncludes(altNames, '"') ?
@@ -301,15 +308,14 @@ exports.checkServerIdentity = function checkServerIdentity(hostname, cert) {
   let valid = false;
   let reason = 'Unknown reason';
 
-  hostname = unfqdn(hostname);  // Remove trailing dot for error messages.
-
-  if (net.isIP(hostname)) {
-    valid = ArrayPrototypeIncludes(ips, canonicalizeIP(hostname));
-    if (!valid)
-      reason = `IP: ${hostname} is not in the cert's list: ` +
-               ArrayPrototypeJoin(ips, ', ');
+  if (net.isIP(hostnameASCIIWithoutFQDN)) {
+    valid = ArrayPrototypeIncludes(ips, canonicalizeIP(hostnameASCIIWithoutFQDN));
+    if (!valid) {
+      reason =
+        `IP: ${hostname} is not in the cert's list: ` + ips.join(', ');
+    }
   } else if (dnsNames.length > 0 || subject?.CN) {
-    const hostParts = splitHost(hostname);
+    const hostParts = splitHost(hostnameASCIIWithoutFQDN);
     const wildcard = (pattern) => check(hostParts, pattern, true);
 
     if (dnsNames.length > 0) {
index fe81fc5285a3ce2f83a680c3ed097496a3027fb0..3dc0275ec607ac678e87b128c165e594cedee1df 100644 (file)
@@ -313,6 +313,15 @@ const tests = [
     error: 'Host: localhost. is not in the cert\'s altnames: ' +
            'DNS:a.com'
   },
+  {
+    host: 'foo。bar.example.com',
+    cert: {
+      subjectaltname: 'DNS:*.example.com',
+      subject: {}
+    },
+    error: 'Host: foo。bar.example.com. is not in the cert\'s altnames: ' +
+           'DNS:*.example.com'
+  },
   // IDNA
   {
     host: 'xn--bcher-kva.example.com',