1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23 24const { 25 Array, 26 ArrayIsArray, 27 ObjectDefineProperty, 28 ObjectFreeze, 29} = primordials; 30 31const { 32 ERR_TLS_CERT_ALTNAME_INVALID, 33 ERR_OUT_OF_RANGE 34} = require('internal/errors').codes; 35const internalUtil = require('internal/util'); 36const internalTLS = require('internal/tls'); 37internalUtil.assertCrypto(); 38const { isArrayBufferView } = require('internal/util/types'); 39 40const net = require('net'); 41const { getOptionValue } = require('internal/options'); 42const url = require('url'); 43const { getRootCertificates, getSSLCiphers } = internalBinding('crypto'); 44const { Buffer } = require('buffer'); 45const EventEmitter = require('events'); 46const { URL } = require('internal/url'); 47const DuplexPair = require('internal/streams/duplexpair'); 48const { canonicalizeIP } = internalBinding('cares_wrap'); 49const _tls_common = require('_tls_common'); 50const _tls_wrap = require('_tls_wrap'); 51 52// Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations 53// every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more 54// renegotiations are seen. The settings are applied to all remote client 55// connections. 56exports.CLIENT_RENEG_LIMIT = 3; 57exports.CLIENT_RENEG_WINDOW = 600; 58 59exports.DEFAULT_CIPHERS = getOptionValue('--tls-cipher-list'); 60 61exports.DEFAULT_ECDH_CURVE = 'auto'; 62 63if (getOptionValue('--tls-min-v1.0')) 64 exports.DEFAULT_MIN_VERSION = 'TLSv1'; 65else if (getOptionValue('--tls-min-v1.1')) 66 exports.DEFAULT_MIN_VERSION = 'TLSv1.1'; 67else if (getOptionValue('--tls-min-v1.2')) 68 exports.DEFAULT_MIN_VERSION = 'TLSv1.2'; 69else if (getOptionValue('--tls-min-v1.3')) 70 exports.DEFAULT_MIN_VERSION = 'TLSv1.3'; 71else 72 exports.DEFAULT_MIN_VERSION = 'TLSv1.2'; 73 74if (getOptionValue('--tls-max-v1.3')) 75 exports.DEFAULT_MAX_VERSION = 'TLSv1.3'; 76else if (getOptionValue('--tls-max-v1.2')) 77 exports.DEFAULT_MAX_VERSION = 'TLSv1.2'; 78else 79 exports.DEFAULT_MAX_VERSION = 'TLSv1.3'; // Will depend on node version. 80 81 82exports.getCiphers = internalUtil.cachedResult( 83 () => internalUtil.filterDuplicateStrings(getSSLCiphers(), true) 84); 85 86let rootCertificates; 87 88function cacheRootCertificates() { 89 rootCertificates = ObjectFreeze(getRootCertificates()); 90} 91 92ObjectDefineProperty(exports, 'rootCertificates', { 93 configurable: false, 94 enumerable: true, 95 get: () => { 96 // Out-of-line caching to promote inlining the getter. 97 if (!rootCertificates) cacheRootCertificates(); 98 return rootCertificates; 99 }, 100}); 101 102// Convert protocols array into valid OpenSSL protocols list 103// ("\x06spdy/2\x08http/1.1\x08http/1.0") 104function convertProtocols(protocols) { 105 const lens = new Array(protocols.length); 106 const buff = Buffer.allocUnsafe(protocols.reduce((p, c, i) => { 107 const len = Buffer.byteLength(c); 108 if (len > 255) { 109 throw new ERR_OUT_OF_RANGE('The byte length of the protocol at index ' + 110 `${i} exceeds the maximum length.`, '<= 255', len, true); 111 } 112 lens[i] = len; 113 return p + 1 + len; 114 }, 0)); 115 116 let offset = 0; 117 for (let i = 0, c = protocols.length; i < c; i++) { 118 buff[offset++] = lens[i]; 119 buff.write(protocols[i], offset); 120 offset += lens[i]; 121 } 122 123 return buff; 124} 125 126exports.convertALPNProtocols = function convertALPNProtocols(protocols, out) { 127 // If protocols is Array - translate it into buffer 128 if (ArrayIsArray(protocols)) { 129 out.ALPNProtocols = convertProtocols(protocols); 130 } else if (isArrayBufferView(protocols)) { 131 // Copy new buffer not to be modified by user. 132 out.ALPNProtocols = Buffer.from(protocols); 133 } 134}; 135 136function unfqdn(host) { 137 return host.replace(/[.]$/, ''); 138} 139 140function splitHost(host) { 141 // String#toLowerCase() is locale-sensitive so we use 142 // a conservative version that only lowercases A-Z. 143 const replacer = (c) => String.fromCharCode(32 + c.charCodeAt(0)); 144 return unfqdn(host).replace(/[A-Z]/g, replacer).split('.'); 145} 146 147function check(hostParts, pattern, wildcards) { 148 // Empty strings, null, undefined, etc. never match. 149 if (!pattern) 150 return false; 151 152 const patternParts = splitHost(pattern); 153 154 if (hostParts.length !== patternParts.length) 155 return false; 156 157 // Pattern has empty components, e.g. "bad..example.com". 158 if (patternParts.includes('')) 159 return false; 160 161 // RFC 6125 allows IDNA U-labels (Unicode) in names but we have no 162 // good way to detect their encoding or normalize them so we simply 163 // reject them. Control characters and blanks are rejected as well 164 // because nothing good can come from accepting them. 165 const isBad = (s) => /[^\u0021-\u007F]/u.test(s); 166 if (patternParts.some(isBad)) 167 return false; 168 169 // Check host parts from right to left first. 170 for (let i = hostParts.length - 1; i > 0; i -= 1) { 171 if (hostParts[i] !== patternParts[i]) 172 return false; 173 } 174 175 const hostSubdomain = hostParts[0]; 176 const patternSubdomain = patternParts[0]; 177 const patternSubdomainParts = patternSubdomain.split('*'); 178 179 // Short-circuit when the subdomain does not contain a wildcard. 180 // RFC 6125 does not allow wildcard substitution for components 181 // containing IDNA A-labels (Punycode) so match those verbatim. 182 if (patternSubdomainParts.length === 1 || patternSubdomain.includes('xn--')) 183 return hostSubdomain === patternSubdomain; 184 185 if (!wildcards) 186 return false; 187 188 // More than one wildcard is always wrong. 189 if (patternSubdomainParts.length > 2) 190 return false; 191 192 // *.tld wildcards are not allowed. 193 if (patternParts.length <= 2) 194 return false; 195 196 const [prefix, suffix] = patternSubdomainParts; 197 198 if (prefix.length + suffix.length > hostSubdomain.length) 199 return false; 200 201 if (!hostSubdomain.startsWith(prefix)) 202 return false; 203 204 if (!hostSubdomain.endsWith(suffix)) 205 return false; 206 207 return true; 208} 209 210let urlWarningEmitted = false; 211exports.checkServerIdentity = function checkServerIdentity(hostname, cert) { 212 const subject = cert.subject; 213 const altNames = cert.subjectaltname; 214 const dnsNames = []; 215 const uriNames = []; 216 const ips = []; 217 218 hostname = '' + hostname; 219 220 if (altNames) { 221 for (const name of altNames.split(', ')) { 222 if (name.startsWith('DNS:')) { 223 dnsNames.push(name.slice(4)); 224 } else if (name.startsWith('URI:')) { 225 let uri; 226 try { 227 uri = new URL(name.slice(4)); 228 } catch { 229 uri = url.parse(name.slice(4)); 230 if (!urlWarningEmitted && !process.noDeprecation) { 231 urlWarningEmitted = true; 232 process.emitWarning( 233 `The URI ${name.slice(4)} found in cert.subjectaltname ` + 234 'is not a valid URI, and is supported in the tls module ' + 235 'solely for compatibility.', 236 'DeprecationWarning', 'DEP0109'); 237 } 238 } 239 240 uriNames.push(uri.hostname); // TODO(bnoordhuis) Also use scheme. 241 } else if (name.startsWith('IP Address:')) { 242 ips.push(canonicalizeIP(name.slice(11))); 243 } 244 } 245 } 246 247 let valid = false; 248 let reason = 'Unknown reason'; 249 250 const hasAltNames = 251 dnsNames.length > 0 || ips.length > 0 || uriNames.length > 0; 252 253 hostname = unfqdn(hostname); // Remove trailing dot for error messages. 254 255 if (net.isIP(hostname)) { 256 valid = ips.includes(canonicalizeIP(hostname)); 257 if (!valid) 258 reason = `IP: ${hostname} is not in the cert's list: ${ips.join(', ')}`; 259 // TODO(bnoordhuis) Also check URI SANs that are IP addresses. 260 } else if (hasAltNames || subject) { 261 const hostParts = splitHost(hostname); 262 const wildcard = (pattern) => check(hostParts, pattern, true); 263 264 if (hasAltNames) { 265 const noWildcard = (pattern) => check(hostParts, pattern, false); 266 valid = dnsNames.some(wildcard) || uriNames.some(noWildcard); 267 if (!valid) 268 reason = 269 `Host: ${hostname}. is not in the cert's altnames: ${altNames}`; 270 } else { 271 // Match against Common Name only if no supported identifiers exist. 272 const cn = subject.CN; 273 274 if (ArrayIsArray(cn)) 275 valid = cn.some(wildcard); 276 else if (cn) 277 valid = wildcard(cn); 278 279 if (!valid) 280 reason = `Host: ${hostname}. is not cert's CN: ${cn}`; 281 } 282 } else { 283 reason = 'Cert is empty'; 284 } 285 286 if (!valid) { 287 return new ERR_TLS_CERT_ALTNAME_INVALID(reason, hostname, cert); 288 } 289}; 290 291 292class SecurePair extends EventEmitter { 293 constructor(secureContext = exports.createSecureContext(), 294 isServer = false, 295 requestCert = !isServer, 296 rejectUnauthorized = false, 297 options = {}) { 298 super(); 299 const { socket1, socket2 } = new DuplexPair(); 300 301 this.server = options.server; 302 this.credentials = secureContext; 303 304 this.encrypted = socket1; 305 this.cleartext = new exports.TLSSocket(socket2, { 306 secureContext, 307 isServer, 308 requestCert, 309 rejectUnauthorized, 310 ...options 311 }); 312 this.cleartext.once('secure', () => this.emit('secure')); 313 } 314 315 destroy() { 316 this.cleartext.destroy(); 317 this.encrypted.destroy(); 318 } 319} 320 321 322exports.parseCertString = internalUtil.deprecate( 323 internalTLS.parseCertString, 324 'tls.parseCertString() is deprecated. ' + 325 'Please use querystring.parse() instead.', 326 'DEP0076'); 327 328exports.createSecureContext = _tls_common.createSecureContext; 329exports.SecureContext = _tls_common.SecureContext; 330exports.TLSSocket = _tls_wrap.TLSSocket; 331exports.Server = _tls_wrap.Server; 332exports.createServer = _tls_wrap.createServer; 333exports.connect = _tls_wrap.connect; 334 335exports.createSecurePair = internalUtil.deprecate( 336 function createSecurePair(...args) { 337 return new SecurePair(...args); 338 }, 339 'tls.createSecurePair() is deprecated. Please use ' + 340 'tls.TLSSocket instead.', 'DEP0064'); 341