Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tls: refactor to use more primordials #36266

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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\[email protected]
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