UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

335 lines (286 loc) 11.6 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/tls.js import { codes as __codes__ } from "nstdlib/lib/internal/errors"; import * as internalUtil from "nstdlib/lib/internal/util"; import { isArrayBufferView, isUint8Array, } from "nstdlib/lib/internal/util/types"; import * as net from "nstdlib/lib/net"; import { getOptionValue } from "nstdlib/lib/internal/options"; import { getRootCertificates, getSSLCiphers, } from "nstdlib/stub/binding/crypto"; import { Buffer } from "nstdlib/lib/buffer"; import { canonicalizeIP } from "nstdlib/stub/binding/cares_wrap"; import * as _tls_common from "nstdlib/lib/_tls_common"; import * as _tls_wrap from "nstdlib/lib/_tls_wrap"; import { createSecurePair } from "nstdlib/lib/internal/tls/secure-pair"; // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. const { ERR_TLS_CERT_ALTNAME_FORMAT, ERR_TLS_CERT_ALTNAME_INVALID, ERR_OUT_OF_RANGE, } = __codes__; internalUtil.assertCrypto(); // Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations // every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more // renegotiations are seen. The settings are applied to all remote client // connections. const _export_CLIENT_RENEG_LIMIT_ = 3; export { _export_CLIENT_RENEG_LIMIT_ as CLIENT_RENEG_LIMIT }; const _export_CLIENT_RENEG_WINDOW_ = 600; export { _export_CLIENT_RENEG_WINDOW_ as CLIENT_RENEG_WINDOW }; const _export_DEFAULT_CIPHERS_ = getOptionValue("--tls-cipher-list"); export { _export_DEFAULT_CIPHERS_ as DEFAULT_CIPHERS }; const _export_DEFAULT_ECDH_CURVE_ = "auto"; export { _export_DEFAULT_ECDH_CURVE_ as DEFAULT_ECDH_CURVE }; if (getOptionValue("--tls-min-v1.0")) exports.DEFAULT_MIN_VERSION = "TLSv1"; else if (getOptionValue("--tls-min-v1.1")) exports.DEFAULT_MIN_VERSION = "TLSv1.1"; else if (getOptionValue("--tls-min-v1.2")) exports.DEFAULT_MIN_VERSION = "TLSv1.2"; else if (getOptionValue("--tls-min-v1.3")) exports.DEFAULT_MIN_VERSION = "TLSv1.3"; else exports.DEFAULT_MIN_VERSION = "TLSv1.2"; if (getOptionValue("--tls-max-v1.3")) exports.DEFAULT_MAX_VERSION = "TLSv1.3"; else if (getOptionValue("--tls-max-v1.2")) exports.DEFAULT_MAX_VERSION = "TLSv1.2"; else exports.DEFAULT_MAX_VERSION = "TLSv1.3"; // Will depend on node version. const _export_getCiphers_ = internalUtil.cachedResult(() => internalUtil.filterDuplicateStrings(getSSLCiphers(), true), ); export { _export_getCiphers_ as getCiphers }; let rootCertificates; function cacheRootCertificates() { rootCertificates = Object.freeze(getRootCertificates()); } Object.defineProperty(exports, "rootCertificates", { __proto__: null, configurable: false, enumerable: true, get: () => { // Out-of-line caching to promote inlining the getter. if (!rootCertificates) cacheRootCertificates(); return rootCertificates; }, }); // Convert protocols array into valid OpenSSL protocols list // ("\x06spdy/2\x08http/1.1\x08http/1.0") function convertProtocols(protocols) { const lens = new Array(protocols.length); const buff = Buffer.allocUnsafe( protocols.reduce((p, c, i) => { const len = Buffer.byteLength(c); if (len > 255) { throw new ERR_OUT_OF_RANGE( "The byte length of the protocol at index " + `${i} exceeds the maximum length.`, "<= 255", len, true, ); } lens[i] = len; return p + 1 + len; }, 0), ); let offset = 0; for (let i = 0, c = protocols.length; i < c; i++) { buff[offset++] = lens[i]; buff.write(protocols[i], offset); offset += lens[i]; } return buff; } const _export_convertALPNProtocols_ = function convertALPNProtocols( protocols, out, ) { // If protocols is Array - translate it into buffer if (Array.isArray(protocols)) { out.ALPNProtocols = convertProtocols(protocols); } else if (isUint8Array(protocols)) { // Copy new buffer not to be modified by user. out.ALPNProtocols = Buffer.from(protocols); } else if (isArrayBufferView(protocols)) { out.ALPNProtocols = Buffer.from( protocols.buffer.slice( protocols.byteOffset, protocols.byteOffset + protocols.byteLength, ), ); } }; export { _export_convertALPNProtocols_ as convertALPNProtocols }; function unfqdn(host) { return host.replace(/[.]$/, ""); } // String#toLowerCase() is locale-sensitive so we use // a conservative version that only lowercases A-Z. function toLowerCase(c) { return String.fromCharCode(32 + c.charCodeAt(0)); } function splitHost(host) { return unfqdn(host).replace(/[A-Z]/g, toLowerCase).split("."); } function check(hostParts, pattern, wildcards) { // Empty strings, null, undefined, etc. never match. if (!pattern) return false; const patternParts = splitHost(pattern); if (hostParts.length !== patternParts.length) return false; // Pattern has empty components, e.g. "bad..example.com". if (patternParts.includes("")) return false; // RFC 6125 allows IDNA U-labels (Unicode) in names but we have no // good way to detect their encoding or normalize them so we simply // reject them. Control characters and blanks are rejected as well // because nothing good can come from accepting them. const isBad = (s) => /[^\u0021-\u007F]/u.test(s); if (patternParts.some(isBad)) return false; // Check host parts from right to left first. for (let i = hostParts.length - 1; i > 0; i -= 1) { if (hostParts[i] !== patternParts[i]) return false; } const hostSubdomain = hostParts[0]; const patternSubdomain = patternParts[0]; const patternSubdomainParts = patternSubdomain.split("*"); // Short-circuit when the subdomain does not contain a wildcard. // RFC 6125 does not allow wildcard substitution for components // containing IDNA A-labels (Punycode) so match those verbatim. if (patternSubdomainParts.length === 1 || patternSubdomain.includes("xn--")) return hostSubdomain === patternSubdomain; if (!wildcards) return false; // More than one wildcard is always wrong. if (patternSubdomainParts.length > 2) return false; // *.tld wildcards are not allowed. if (patternParts.length <= 2) return false; const { 0: prefix, 1: suffix } = patternSubdomainParts; if (prefix.length + suffix.length > hostSubdomain.length) return false; if (!hostSubdomain.startsWith(prefix)) return false; if (!hostSubdomain.endsWith(suffix)) return false; return true; } // This pattern is used to determine the length of escaped sequences within // the subject alt names string. It allows any valid JSON string literal. // This MUST match the JSON specification (ECMA-404 / RFC8259) exactly. const jsonStringPattern = // eslint-disable-next-line no-control-regex /^"(?:[^"\\\u0000-\u001f]|\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4}))*"/; function splitEscapedAltNames(altNames) { const result = []; let currentToken = ""; let offset = 0; while (offset !== altNames.length) { const nextSep = altNames.indexOf(",", offset); const nextQuote = altNames.indexOf('"', offset); if (nextQuote !== -1 && (nextSep === -1 || nextQuote < nextSep)) { // There is a quote character and there is no separator before the quote. currentToken += altNames.substring(offset, nextQuote); const match = jsonStringPattern.exec(altNames.substring(nextQuote)); if (!match) { throw new ERR_TLS_CERT_ALTNAME_FORMAT(); } currentToken += JSONParse(match[0]); offset = nextQuote + match[0].length; } else if (nextSep !== -1) { // There is a separator and no quote before it. currentToken += altNames.substring(offset, nextSep); result.push(currentToken); currentToken = ""; offset = nextSep + 2; } else { currentToken += altNames.substring(offset); offset = altNames.length; } } result.push(currentToken); return result; } const _export_checkServerIdentity_ = function checkServerIdentity( hostname, cert, ) { const subject = cert.subject; const altNames = cert.subjectaltname; const dnsNames = []; const ips = []; hostname = "" + hostname; if (altNames) { const splitAltNames = altNames.includes('"') ? splitEscapedAltNames(altNames) : altNames.split(", "); splitAltNames.forEach((name) => { if (name.startsWith("DNS:")) { dnsNames.push(name.slice(4)); } else if (name.startsWith("IP Address:")) { ips.push(canonicalizeIP(name.slice(11))); } }); } let valid = false; let reason = "Unknown reason"; hostname = unfqdn(hostname); // Remove trailing dot for error messages. if (net.isIP(hostname)) { valid = ips.includes(canonicalizeIP(hostname)); 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 wildcard = (pattern) => check(hostParts, pattern, true); if (dnsNames.length > 0) { valid = dnsNames.some(wildcard); if (!valid) reason = `Host: ${hostname}. is not in the cert's altnames: ${altNames}`; } else { // Match against Common Name only if no supported identifiers exist. const cn = subject.CN; if (Array.isArray(cn)) valid = cn.some(wildcard); else if (cn) valid = wildcard(cn); if (!valid) reason = `Host: ${hostname}. is not cert's CN: ${cn}`; } } else { reason = "Cert does not contain a DNS name"; } if (!valid) { return new ERR_TLS_CERT_ALTNAME_INVALID(reason, hostname, cert); } }; export { _export_checkServerIdentity_ as checkServerIdentity }; const _export_createSecureContext_ = _tls_common.createSecureContext; export { _export_createSecureContext_ as createSecureContext }; const _export_SecureContext_ = _tls_common.SecureContext; export { _export_SecureContext_ as SecureContext }; const _export_TLSSocket_ = _tls_wrap.TLSSocket; export { _export_TLSSocket_ as TLSSocket }; const _export_Server_ = _tls_wrap.Server; export { _export_Server_ as Server }; const _export_createServer_ = _tls_wrap.createServer; export { _export_createServer_ as createServer }; const _export_connect_ = _tls_wrap.connect; export { _export_connect_ as connect }; const _export_createSecurePair_ = internalUtil.deprecate( createSecurePair, "tls.createSecurePair() is deprecated. Please use " + "tls.TLSSocket instead.", "DEP0064", ); export { _export_createSecurePair_ as createSecurePair };