| // 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. |
| |
| 'use strict'; |
| |
| const { |
| Array, |
| ArrayIsArray, |
| ObjectDefineProperty, |
| ObjectFreeze, |
| } = primordials; |
| |
| const { |
| ERR_TLS_CERT_ALTNAME_INVALID, |
| ERR_OUT_OF_RANGE |
| } = require('internal/errors').codes; |
| const internalUtil = require('internal/util'); |
| const internalTLS = require('internal/tls'); |
| internalUtil.assertCrypto(); |
| const { isArrayBufferView } = require('internal/util/types'); |
| |
| const net = require('net'); |
| const { getOptionValue } = require('internal/options'); |
| const url = require('url'); |
| const { getRootCertificates, getSSLCiphers } = internalBinding('crypto'); |
| const { Buffer } = require('buffer'); |
| const EventEmitter = require('events'); |
| const { URL } = require('internal/url'); |
| const DuplexPair = require('internal/streams/duplexpair'); |
| const { canonicalizeIP } = internalBinding('cares_wrap'); |
| const _tls_common = require('_tls_common'); |
| const _tls_wrap = require('_tls_wrap'); |
| |
| // 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. |
| exports.CLIENT_RENEG_LIMIT = 3; |
| exports.CLIENT_RENEG_WINDOW = 600; |
| |
| exports.DEFAULT_CIPHERS = getOptionValue('--tls-cipher-list'); |
| |
| exports.DEFAULT_ECDH_CURVE = 'auto'; |
| |
| 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. |
| |
| |
| exports.getCiphers = internalUtil.cachedResult( |
| () => internalUtil.filterDuplicateStrings(getSSLCiphers(), true) |
| ); |
| |
| let rootCertificates; |
| |
| function cacheRootCertificates() { |
| rootCertificates = ObjectFreeze(getRootCertificates()); |
| } |
| |
| ObjectDefineProperty(exports, 'rootCertificates', { |
| 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; |
| } |
| |
| exports.convertALPNProtocols = function convertALPNProtocols(protocols, out) { |
| // If protocols is Array - translate it into buffer |
| if (ArrayIsArray(protocols)) { |
| out.ALPNProtocols = convertProtocols(protocols); |
| } else if (isArrayBufferView(protocols)) { |
| // Copy new buffer not to be modified by user. |
| out.ALPNProtocols = Buffer.from(protocols); |
| } |
| }; |
| |
| function unfqdn(host) { |
| return host.replace(/[.]$/, ''); |
| } |
| |
| function splitHost(host) { |
| // String#toLowerCase() is locale-sensitive so we use |
| // a conservative version that only lowercases A-Z. |
| const replacer = (c) => String.fromCharCode(32 + c.charCodeAt(0)); |
| return unfqdn(host).replace(/[A-Z]/g, replacer).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 [prefix, 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; |
| } |
| |
| let urlWarningEmitted = false; |
| exports.checkServerIdentity = function checkServerIdentity(hostname, cert) { |
| const subject = cert.subject; |
| const altNames = cert.subjectaltname; |
| const dnsNames = []; |
| const uriNames = []; |
| const ips = []; |
| |
| hostname = '' + hostname; |
| |
| if (altNames) { |
| for (const name of altNames.split(', ')) { |
| if (name.startsWith('DNS:')) { |
| dnsNames.push(name.slice(4)); |
| } else if (name.startsWith('URI:')) { |
| let uri; |
| try { |
| uri = new URL(name.slice(4)); |
| } catch { |
| uri = url.parse(name.slice(4)); |
| if (!urlWarningEmitted && !process.noDeprecation) { |
| urlWarningEmitted = true; |
| process.emitWarning( |
| `The URI ${name.slice(4)} found in cert.subjectaltname ` + |
| 'is not a valid URI, and is supported in the tls module ' + |
| 'solely for compatibility.', |
| 'DeprecationWarning', 'DEP0109'); |
| } |
| } |
| |
| uriNames.push(uri.hostname); // TODO(bnoordhuis) Also use scheme. |
| } else if (name.startsWith('IP Address:')) { |
| ips.push(canonicalizeIP(name.slice(11))); |
| } |
| } |
| } |
| |
| let valid = false; |
| let reason = 'Unknown reason'; |
| |
| const hasAltNames = |
| dnsNames.length > 0 || ips.length > 0 || uriNames.length > 0; |
| |
| 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(', ')}`; |
| // TODO(bnoordhuis) Also check URI SANs that are IP addresses. |
| } else if (hasAltNames || subject) { |
| const hostParts = splitHost(hostname); |
| const wildcard = (pattern) => check(hostParts, pattern, true); |
| |
| if (hasAltNames) { |
| const noWildcard = (pattern) => check(hostParts, pattern, false); |
| valid = dnsNames.some(wildcard) || uriNames.some(noWildcard); |
| 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 (ArrayIsArray(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 is empty'; |
| } |
| |
| if (!valid) { |
| return new ERR_TLS_CERT_ALTNAME_INVALID(reason, hostname, cert); |
| } |
| }; |
| |
| |
| class SecurePair extends EventEmitter { |
| constructor(secureContext = exports.createSecureContext(), |
| isServer = false, |
| requestCert = !isServer, |
| rejectUnauthorized = false, |
| options = {}) { |
| super(); |
| const { socket1, socket2 } = new DuplexPair(); |
| |
| this.server = options.server; |
| this.credentials = secureContext; |
| |
| this.encrypted = socket1; |
| this.cleartext = new exports.TLSSocket(socket2, { |
| secureContext, |
| isServer, |
| requestCert, |
| rejectUnauthorized, |
| ...options |
| }); |
| this.cleartext.once('secure', () => this.emit('secure')); |
| } |
| |
| destroy() { |
| this.cleartext.destroy(); |
| this.encrypted.destroy(); |
| } |
| } |
| |
| |
| exports.parseCertString = internalUtil.deprecate( |
| internalTLS.parseCertString, |
| 'tls.parseCertString() is deprecated. ' + |
| 'Please use querystring.parse() instead.', |
| 'DEP0076'); |
| |
| exports.createSecureContext = _tls_common.createSecureContext; |
| exports.SecureContext = _tls_common.SecureContext; |
| exports.TLSSocket = _tls_wrap.TLSSocket; |
| exports.Server = _tls_wrap.Server; |
| exports.createServer = _tls_wrap.createServer; |
| exports.connect = _tls_wrap.connect; |
| |
| exports.createSecurePair = internalUtil.deprecate( |
| function createSecurePair(...args) { |
| return new SecurePair(...args); |
| }, |
| 'tls.createSecurePair() is deprecated. Please use ' + |
| 'tls.TLSSocket instead.', 'DEP0064'); |