UNPKG

@whatwg-node/node-fetch

Version:

Fetch API implementation for Node

79 lines (78 loc) 2.7 kB
import { isIP } from 'node:net'; import tls from 'node:tls'; /** * Canonicalize an IP for equality checks (`::1` vs `0:0:0:0:0:0:0:1`). */ export function normalizeIpAddress(ip) { const bare = ip.replace(/^\[|\]$/g, ''); const family = isIP(bare); if (family === 4) { return bare; } if (family === 6) { // WHATWG URL hostname normalizes IPv6 to a canonical form. return new URL(`http://[${bare}]`).hostname.replace(/^\[|\]$/g, '').toLowerCase(); } return bare.toLowerCase(); } function collectCertIpAddresses(cert) { const alt = cert.subjectaltname; if (!alt) { return []; } const ips = []; for (const part of alt.split(', ')) { if (part.startsWith('IP Address:')) { ips.push(part.slice('IP Address:'.length)); } } return ips; } let probedIpv6SanWorkaround = false; let needsIpv6SanWorkaroundValue = false; /** * Detect Node.js IPv6 IP-SAN regression in `tls.checkServerIdentity` * (https://github.com/nodejs/node/issues/64032). Probes once, on first use. */ export function needsIpv6SanWorkaround() { if (!probedIpv6SanWorkaround) { probedIpv6SanWorkaround = true; try { needsIpv6SanWorkaroundValue = tls.checkServerIdentity('::1', { subject: {}, subjectaltname: 'IP Address:::1', }) != null; } catch { // If the probe itself throws, prefer the workaround. needsIpv6SanWorkaroundValue = true; } } return needsIpv6SanWorkaroundValue; } /** * Custom verifier that correctly matches IPv6 literals against `IP Address` SANs. */ export function checkServerIdentityIpv6San(hostname, cert) { const bareHost = hostname.replace(/^\[|\]$/g, '').replace(/\.$/, ''); if (isIP(bareHost)) { const certIps = collectCertIpAddresses(cert); const want = normalizeIpAddress(bareHost); if (certIps.some(ip => normalizeIpAddress(ip) === want)) { return undefined; } const reason = `Hostname/IP does not match certificate's altnames: IP: ${bareHost} is not in the cert's list: ${certIps.join(', ')}`; const error = new Error(reason); error.reason = reason; error.host = bareHost; error.cert = cert; error.code = 'ERR_TLS_CERT_ALTNAME_INVALID'; return error; } return tls.checkServerIdentity(hostname, cert); } /** Lazy: only set when the first https request probes an affected Node build. */ export function getHttpsCheckServerIdentity() { return needsIpv6SanWorkaround() ? checkServerIdentityIpv6San : undefined; }