Skip to content

Commit

Permalink
tls: refactor to use more primordials
Browse files Browse the repository at this point in the history
PR-URL: nodejs#36266
Reviewed-By: Rich Trott <rtrott@gmail.com>
  • Loading branch information
aduh95 authored and cjihrig committed Dec 8, 2020
1 parent 398f29f commit 26e0ffe
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 59 deletions.
15 changes: 9 additions & 6 deletions lib/_tls_common.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ const {
ArrayIsArray,
ArrayPrototypeFilter,
ArrayPrototypeJoin,
ArrayPrototypePush,
ObjectCreate,
StringPrototypeReplace,
StringPrototypeSplit,
StringPrototypeStartsWith,
} = primordials;
Expand Down Expand Up @@ -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;
};
60 changes: 35 additions & 25 deletions lib/_tls_wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,18 @@
'use strict';

const {
ArrayPrototypeForEach,
ArrayPrototypeJoin,
ArrayPrototypePush,
FunctionPrototype,
ObjectAssign,
ObjectDefineProperty,
ObjectSetPrototypeOf,
ReflectApply,
RegExp,
RegExpPrototypeTest,
StringPrototypeReplace,
StringPrototypeSlice,
Symbol,
SymbolFor,
} = primordials;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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',
Expand All @@ -1009,7 +1018,7 @@ function makeSocketMethodProxy(name) {
'getTLSTicket',
'isSessionReused',
'enableTrace',
].forEach((method) => {
], (method) => {
TLSSocket.prototype[method] = makeSocketMethodProxy(method);
});

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand All @@ -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(
Expand All @@ -1429,16 +1439,16 @@ 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]);
}
};

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;
}
Expand Down
14 changes: 9 additions & 5 deletions lib/internal/tls.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,27 @@

const {
ArrayIsArray,
ArrayPrototypePush,
StringPrototypeIndexOf,
StringPrototypeSlice,
StringPrototypeSplit,
ObjectCreate,
} = primordials;

// Example:
// 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;
}
Expand Down
61 changes: 38 additions & 23 deletions lib/tls.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 ' +
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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)
Expand All @@ -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;
Expand All @@ -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)));
}
}
}
Expand All @@ -263,17 +276,19 @@ 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);
const wildcard = (pattern) => check(hostParts, pattern, true);

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}`;
Expand All @@ -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);

Expand Down

0 comments on commit 26e0ffe

Please sign in to comment.