From 26e0ffeb6f1834c32e360c3b53d86c0324c2ccfe Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Tue, 17 Nov 2020 23:09:52 +0100 Subject: [PATCH] tls: refactor to use more primordials PR-URL: https://github.com/nodejs/node/pull/36266 Reviewed-By: Rich Trott --- lib/_tls_common.js | 15 ++++++----- lib/_tls_wrap.js | 60 +++++++++++++++++++++++++------------------- lib/internal/tls.js | 14 +++++++---- lib/tls.js | 61 ++++++++++++++++++++++++++++----------------- 4 files changed, 91 insertions(+), 59 deletions(-) diff --git a/lib/_tls_common.js b/lib/_tls_common.js index b1d28b9c49aad4..616b7b47f46dac 100644 --- a/lib/_tls_common.js +++ b/lib/_tls_common.js @@ -25,7 +25,9 @@ const { ArrayIsArray, ArrayPrototypeFilter, ArrayPrototypeJoin, + ArrayPrototypePush, ObjectCreate, + StringPrototypeReplace, StringPrototypeSplit, StringPrototypeStartsWith, } = primordials; @@ -392,12 +394,13 @@ exports.translatePeerCertificate = function translatePeerCertificate(c) { c.infoAccess = ObjectCreate(null); // XXX: More key validation? - info.replace(/([^\n:]*):([^\n]*)(?:\n|$)/g, (all, key, val) => { - if (key in c.infoAccess) - c.infoAccess[key].push(val); - else - c.infoAccess[key] = [val]; - }); + StringPrototypeReplace(info, /([^\n:]*):([^\n]*)(?:\n|$)/g, + (all, key, val) => { + if (key in c.infoAccess) + ArrayPrototypePush(c.infoAccess[key], val); + else + c.infoAccess[key] = [val]; + }); } return c; }; diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index 929114d74ad6b6..bce7d7679131b2 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -22,10 +22,18 @@ 'use strict'; const { + ArrayPrototypeForEach, + ArrayPrototypeJoin, + ArrayPrototypePush, + FunctionPrototype, ObjectAssign, ObjectDefineProperty, ObjectSetPrototypeOf, + ReflectApply, RegExp, + RegExpPrototypeTest, + StringPrototypeReplace, + StringPrototypeSlice, Symbol, SymbolFor, } = primordials; @@ -96,7 +104,7 @@ const kPskIdentityHint = Symbol('pskidentityhint'); const kPendingSession = Symbol('pendingSession'); const kIsVerified = Symbol('verified'); -const noop = () => {}; +const noop = FunctionPrototype; let ipServernameWarned = false; let tlsTracingWarned = false; @@ -408,7 +416,8 @@ function onerror(err) { owner.destroy(err); } else if (owner._tlsOptions.isServer && owner._rejectUnauthorized && - /peer did not return a certificate/.test(err.message)) { + RegExpPrototypeTest(/peer did not return a certificate/, + err.message)) { // Ignore server's authorization errors owner.destroy(); } else { @@ -496,14 +505,14 @@ function TLSSocket(socket, opts) { // distinguishable from regular ones. this.encrypted = true; - net.Socket.call(this, { + ReflectApply(net.Socket, this, [{ handle: this._wrapHandle(wrap), allowHalfOpen: socket ? socket.allowHalfOpen : tlsOptions.allowHalfOpen, pauseOnCreate: tlsOptions.pauseOnConnect, manualStart: true, highWaterMark: tlsOptions.highWaterMark, onread: !socket ? tlsOptions.onread : null, - }); + }]); // Proxy for API compatibility this.ssl = this._handle; // C++ TLSWrap object @@ -535,7 +544,7 @@ const proxiedMethods = [ function makeMethodProxy(name) { return function methodProxy(...args) { if (this._parent[name]) - return this._parent[name].apply(this._parent, args); + return ReflectApply(this._parent[name], this._parent, args); }; } for (const proxiedMethod of proxiedMethods) { @@ -993,12 +1002,12 @@ TLSSocket.prototype.getCertificate = function() { function makeSocketMethodProxy(name) { return function socketMethodProxy(...args) { if (this._handle) - return this._handle[name].apply(this._handle, args); + return ReflectApply(this._handle[name], this._handle, args); return null; }; } -[ +ArrayPrototypeForEach([ 'getCipher', 'getSharedSigalgs', 'getEphemeralKeyInfo', @@ -1009,7 +1018,7 @@ function makeSocketMethodProxy(name) { 'getTLSTicket', 'isSessionReused', 'enableTrace', -].forEach((method) => { +], (method) => { TLSSocket.prototype[method] = makeSocketMethodProxy(method); }); @@ -1209,7 +1218,7 @@ function Server(options, listener) { } // constructor call - net.Server.call(this, options, tlsConnectionListener); + ReflectApply(net.Server, this, [options, tlsConnectionListener]); if (listener) { this.on('secureConnection', listener); @@ -1309,10 +1318,10 @@ Server.prototype.setSecureContext = function(options) { if (options.sessionIdContext) { this.sessionIdContext = options.sessionIdContext; } else { - this.sessionIdContext = crypto.createHash('sha1') - .update(process.argv.join(' ')) - .digest('hex') - .slice(0, 32); + this.sessionIdContext = StringPrototypeSlice( + crypto.createHash('sha1') + .update(ArrayPrototypeJoin(process.argv, ' ')) + .digest('hex'), 0, 32); } if (options.sessionTimeout) @@ -1399,10 +1408,10 @@ Server.prototype.setOptions = deprecate(function(options) { if (options.sessionIdContext) { this.sessionIdContext = options.sessionIdContext; } else { - this.sessionIdContext = crypto.createHash('sha1') - .update(process.argv.join(' ')) - .digest('hex') - .slice(0, 32); + this.sessionIdContext = StringPrototypeSlice( + crypto.createHash('sha1') + .update(ArrayPrototypeJoin(process.argv, ' ')) + .digest('hex'), 0, 32); } if (options.pskCallback) this[kPskCallback] = options.pskCallback; if (options.pskIdentityHint) this[kPskIdentityHint] = options.pskIdentityHint; @@ -1414,11 +1423,12 @@ Server.prototype.addContext = function(servername, context) { throw new ERR_TLS_REQUIRED_SERVER_NAME(); } - const re = new RegExp('^' + - servername.replace(/([.^$+?\-\\[\]{}])/g, '\\$1') - .replace(/\*/g, '[^.]*') + - '$'); - this._contexts.push([re, tls.createSecureContext(context).context]); + const re = new RegExp('^' + StringPrototypeReplace( + StringPrototypeReplace(servername, /([.^$+?\-\\[\]{}])/g, '\\$1'), + /\*/g, '[^.]*' + ) + '$'); + ArrayPrototypePush(this._contexts, + [re, tls.createSecureContext(context).context]); }; Server.prototype[EE.captureRejectionSymbol] = function( @@ -1429,8 +1439,8 @@ Server.prototype[EE.captureRejectionSymbol] = function( sock.destroy(err); break; default: - net.Server.prototype[SymbolFor('nodejs.rejection')] - .call(this, err, event, sock); + ReflectApply(net.Server.prototype[SymbolFor('nodejs.rejection')], this, + [err, event, sock]); } }; @@ -1438,7 +1448,7 @@ function SNICallback(servername, callback) { const contexts = this.server._contexts; for (const elem of contexts) { - if (elem[0].test(servername)) { + if (RegExpPrototypeTest(elem[0], servername)) { callback(null, elem[1]); return; } diff --git a/lib/internal/tls.js b/lib/internal/tls.js index 8d538b27574402..da40f635286bfe 100644 --- a/lib/internal/tls.js +++ b/lib/internal/tls.js @@ -2,6 +2,10 @@ const { ArrayIsArray, + ArrayPrototypePush, + StringPrototypeIndexOf, + StringPrototypeSlice, + StringPrototypeSplit, ObjectCreate, } = primordials; @@ -9,16 +13,16 @@ const { // C=US\nST=CA\nL=SF\nO=Joyent\nOU=Node.js\nCN=ca1\nemailAddress=ry@clouds.org function parseCertString(s) { const out = ObjectCreate(null); - for (const part of s.split('\n')) { - const sepIndex = part.indexOf('='); + for (const part of StringPrototypeSplit(s, '\n')) { + const sepIndex = StringPrototypeIndexOf(part, '='); if (sepIndex > 0) { - const key = part.slice(0, sepIndex); - const value = part.slice(sepIndex + 1); + const key = StringPrototypeSlice(part, 0, sepIndex); + const value = StringPrototypeSlice(part, sepIndex + 1); if (key in out) { if (!ArrayIsArray(out[key])) { out[key] = [out[key]]; } - out[key].push(value); + ArrayPrototypePush(out[key], value); } else { out[key] = value; } diff --git a/lib/tls.js b/lib/tls.js index a46031ad7da90e..7ee9ae49f03a68 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -24,12 +24,22 @@ const { Array, ArrayIsArray, + ArrayPrototypeIncludes, + ArrayPrototypeJoin, + ArrayPrototypePush, + ArrayPrototypeReduce, + ArrayPrototypeSome, ObjectDefineProperty, ObjectFreeze, + RegExpPrototypeTest, StringFromCharCode, StringPrototypeCharCodeAt, + StringPrototypeEndsWith, + StringPrototypeIncludes, StringPrototypeReplace, + StringPrototypeSlice, StringPrototypeSplit, + StringPrototypeStartsWith, } = primordials; const { @@ -107,7 +117,7 @@ ObjectDefineProperty(exports, 'rootCertificates', { // ("\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 buff = Buffer.allocUnsafe(ArrayPrototypeReduce(protocols, (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 ' + @@ -138,7 +148,7 @@ exports.convertALPNProtocols = function convertALPNProtocols(protocols, out) { }; function unfqdn(host) { - return host.replace(/[.]$/, ''); + return StringPrototypeReplace(host, /[.]$/, ''); } // String#toLowerCase() is locale-sensitive so we use @@ -165,15 +175,15 @@ function check(hostParts, pattern, wildcards) { return false; // Pattern has empty components, e.g. "bad..example.com". - if (patternParts.includes('')) + if (ArrayPrototypeIncludes(patternParts, '')) 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)) + const isBad = (s) => RegExpPrototypeTest(/[^\u0021-\u007F]/u, s); + if (ArrayPrototypeSome(patternParts, isBad)) return false; // Check host parts from right to left first. @@ -184,12 +194,13 @@ function check(hostParts, pattern, wildcards) { const hostSubdomain = hostParts[0]; const patternSubdomain = patternParts[0]; - const patternSubdomainParts = patternSubdomain.split('*'); + const patternSubdomainParts = StringPrototypeSplit(patternSubdomain, '*'); // 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--')) + if (patternSubdomainParts.length === 1 || + StringPrototypeIncludes(patternSubdomain, 'xn--')) return hostSubdomain === patternSubdomain; if (!wildcards) @@ -208,10 +219,10 @@ function check(hostParts, pattern, wildcards) { if (prefix.length + suffix.length > hostSubdomain.length) return false; - if (!hostSubdomain.startsWith(prefix)) + if (!StringPrototypeStartsWith(hostSubdomain, prefix)) return false; - if (!hostSubdomain.endsWith(suffix)) + if (!StringPrototypeEndsWith(hostSubdomain, suffix)) return false; return true; @@ -228,28 +239,30 @@ exports.checkServerIdentity = function checkServerIdentity(hostname, cert) { hostname = '' + hostname; if (altNames) { - for (const name of altNames.split(', ')) { - if (name.startsWith('DNS:')) { - dnsNames.push(name.slice(4)); - } else if (name.startsWith('URI:')) { + for (const name of StringPrototypeSplit(altNames, ', ')) { + if (StringPrototypeStartsWith(name, 'DNS:')) { + ArrayPrototypePush(dnsNames, StringPrototypeSlice(name, 4)); + } else if (StringPrototypeStartsWith(name, 'URI:')) { let uri; try { - uri = new URL(name.slice(4)); + uri = new URL(StringPrototypeSlice(name, 4)); } catch { - uri = url.parse(name.slice(4)); + const slicedName = StringPrototypeSlice(name, 4); + uri = url.parse(slicedName); if (!urlWarningEmitted && !process.noDeprecation) { urlWarningEmitted = true; process.emitWarning( - `The URI ${name.slice(4)} found in cert.subjectaltname ` + + `The URI ${slicedName} 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))); + // TODO(bnoordhuis) Also use scheme. + ArrayPrototypePush(uriNames, uri.hostname); + } else if (StringPrototypeStartsWith(name, 'IP Address:')) { + ArrayPrototypePush(ips, canonicalizeIP(StringPrototypeSlice(name, 11))); } } } @@ -263,9 +276,10 @@ exports.checkServerIdentity = function checkServerIdentity(hostname, cert) { hostname = unfqdn(hostname); // Remove trailing dot for error messages. if (net.isIP(hostname)) { - valid = ips.includes(canonicalizeIP(hostname)); + valid = ArrayPrototypeIncludes(ips, canonicalizeIP(hostname)); if (!valid) - reason = `IP: ${hostname} is not in the cert's list: ${ips.join(', ')}`; + reason = `IP: ${hostname} is not in the cert's list: ` + + ArrayPrototypeJoin(ips, ', '); // TODO(bnoordhuis) Also check URI SANs that are IP addresses. } else if (hasAltNames || subject) { const hostParts = splitHost(hostname); @@ -273,7 +287,8 @@ exports.checkServerIdentity = function checkServerIdentity(hostname, cert) { if (hasAltNames) { const noWildcard = (pattern) => check(hostParts, pattern, false); - valid = dnsNames.some(wildcard) || uriNames.some(noWildcard); + valid = ArrayPrototypeSome(dnsNames, wildcard) || + ArrayPrototypeSome(uriNames, noWildcard); if (!valid) reason = `Host: ${hostname}. is not in the cert's altnames: ${altNames}`; @@ -282,7 +297,7 @@ exports.checkServerIdentity = function checkServerIdentity(hostname, cert) { const cn = subject.CN; if (ArrayIsArray(cn)) - valid = cn.some(wildcard); + valid = ArrayPrototypeSome(cn, wildcard); else if (cn) valid = wildcard(cn);