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: fix object prototype type confusion #14447

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
7 changes: 2 additions & 5 deletions lib/_tls_common.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,11 @@ exports.translatePeerCertificate = function translatePeerCertificate(c) {
if (c.subject != null) c.subject = tls.parseCertString(c.subject);
if (c.infoAccess != null) {
var info = c.infoAccess;
c.infoAccess = {};
c.infoAccess = Object.create(null);

// XXX: More key validation?
info.replace(/([^\n:]*):([^\n]*)(?:\n|$)/g, function(all, key, val) {
if (key === '__proto__')
return;

if (c.infoAccess.hasOwnProperty(key))
if (key in c.infoAccess)
Copy link
Contributor

@mscdex mscdex Jul 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps a c.infoAccess[key] === undefined would be more efficient?

Or just save the value to a variable and use that value here and use it to skip the lookups below.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlikely to matter because it's only a handful of keys (if it's set at all.) This is shorter so that's what I picked.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a micro benchmark for that on TF&I. Small benefit for in

> var o = {a:1, b:2, c:3, d:4, e:5}
> var j = 0
> console.time('in'); for (let i = 0; i < 1e8; ++i) {const key = String.fromCharCode(97 + (i % 10)); if (key in o) ++j};console.timeEnd('in')
in: 2614.723ms
> console.time('!=='); for (let i = 0; i < 1e8; ++i) {const key = String.fromCharCode(97 + (i % 10)); if (o[key] !== undefined) ++j};console.timeEnd('!==')
!==: 3160.657ms
> console.time('!in'); for (let i = 0; i < 1e8; ++i) {const key = String.fromCharCode(97 + (i % 10)); if (!(key in o)) ++j};console.timeEnd('!in')
!in: 2842.438ms
> console.time('==='); for (let i = 0; i < 1e8; ++i) {const key = String.fromCharCode(97 + (i % 10)); if (o[key] === undefined) ++j};console.timeEnd('===')
===: 2884.768ms
> console.time('!=='); for (let i = 0; i < 1e8; ++i) {const key = String.fromCharCode(97 + (i % 10)); if (o[key] !== undefined) ++j};console.timeEnd('!==')
!==: 2999.367ms
> console.time('==='); for (let i = 0; i < 1e8; ++i) {const key = String.fromCharCode(97 + (i % 10)); if (o[key] === undefined) ++j};console.timeEnd('===')
===: 3113.552ms
> console.time('!in'); for (let i = 0; i < 1e8; ++i) {const key = String.fromCharCode(97 + (i % 10)); if (!(key in o)) ++j};console.timeEnd('!in')
!in: 2679.466ms
>

c.infoAccess[key].push(val);
else
c.infoAccess[key] = [val];
Expand Down
2 changes: 1 addition & 1 deletion lib/tls.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ exports.checkServerIdentity = function checkServerIdentity(host, cert) {
// Example:
// C=US\nST=CA\nL=SF\nO=Joyent\nOU=Node.js\nCN=ca1\nemailAddress=ry@clouds.org
exports.parseCertString = function parseCertString(s) {
var out = {};
var out = Object.create(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have treated other changes like this as semver-major. Think this will cause breakage?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might, hard to say. It would break conn.getPeerCertificate().hasOwnProperty(k) but then again, such code is vulnerable to object manipulation now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but it's still technically semver-major. @nodejs/ctc ... thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be semver-major, as currently parseCertString is documented. I would prefer otherwise.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mcollina parseCertString() isn't documented. You might be thinking of #14193.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then it's not semver-major for me. I'd go for semver-minor, or even patch.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are too many PRs interacting with this change.

var parts = s.split('\n');
for (var i = 0, len = parts.length; i < len; i++) {
var sepIndex = parts[i].indexOf('=');
Expand Down
13 changes: 12 additions & 1 deletion test/parallel/test-tls-parse-cert-string.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-proto */
'use strict';
const common = require('../common');
if (!common.hasCrypto)
Expand All @@ -11,6 +12,7 @@ const tls = require('tls');
'CN=ca1\nemailAddress=ry@clouds.org';
const singlesOut = tls.parseCertString(singles);
assert.deepStrictEqual(singlesOut, {
__proto__: null,
C: 'US',
ST: 'CA',
L: 'SF',
Expand All @@ -26,6 +28,7 @@ const tls = require('tls');
'CN=*.nodejs.org';
const doublesOut = tls.parseCertString(doubles);
assert.deepStrictEqual(doublesOut, {
__proto__: null,
OU: [ 'Domain Control Validated', 'PositiveSSL Wildcard' ],
CN: '*.nodejs.org'
});
Expand All @@ -34,5 +37,13 @@ const tls = require('tls');
{
const invalid = 'fhqwhgads';
const invalidOut = tls.parseCertString(invalid);
assert.deepStrictEqual(invalidOut, {});
assert.deepStrictEqual(invalidOut, { __proto__: null });
}

{
const input = '__proto__=mostly harmless\nhasOwnProperty=not a function';
const expected = Object.create(null);
expected.__proto__ = 'mostly harmless';
expected.hasOwnProperty = 'not a function';
assert.deepStrictEqual(tls.parseCertString(input), expected);
}
30 changes: 22 additions & 8 deletions test/parallel/test-tls-translate-peer-certificate.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-proto */
'use strict';
const common = require('../common');

Expand All @@ -7,8 +8,12 @@ if (!common.hasCrypto)
const { strictEqual, deepStrictEqual } = require('assert');
const { translatePeerCertificate } = require('_tls_common');

const certString = 'A=1\nB=2\nC=3';
const certObject = { A: '1', B: '2', C: '3' };
const certString = '__proto__=42\nA=1\nB=2\nC=3';
const certObject = Object.create(null);
certObject.__proto__ = '42';
certObject.A = '1';
certObject.B = '2';
certObject.C = '3';

strictEqual(translatePeerCertificate(null), null);
strictEqual(translatePeerCertificate(undefined), null);
Expand All @@ -19,14 +24,14 @@ strictEqual(translatePeerCertificate(1), 1);
deepStrictEqual(translatePeerCertificate({}), {});

deepStrictEqual(translatePeerCertificate({ issuer: '' }),
{ issuer: {} });
{ issuer: Object.create(null) });
deepStrictEqual(translatePeerCertificate({ issuer: null }),
{ issuer: null });
deepStrictEqual(translatePeerCertificate({ issuer: certString }),
{ issuer: certObject });

deepStrictEqual(translatePeerCertificate({ subject: '' }),
{ subject: {} });
{ subject: Object.create(null) });
deepStrictEqual(translatePeerCertificate({ subject: null }),
{ subject: null });
deepStrictEqual(translatePeerCertificate({ subject: certString }),
Expand All @@ -47,9 +52,18 @@ deepStrictEqual(
}

deepStrictEqual(translatePeerCertificate({ infoAccess: '' }),
{ infoAccess: {} });
{ infoAccess: Object.create(null) });
deepStrictEqual(translatePeerCertificate({ infoAccess: null }),
{ infoAccess: null });
deepStrictEqual(
translatePeerCertificate({ infoAccess: 'OCSP - URI:file:///etc/passwd' }),
{ infoAccess: { 'OCSP - URI': ['file:///etc/passwd'] } });
{
const input =
'__proto__:mostly harmless\n' +
'hasOwnProperty:not a function\n' +
'OCSP - URI:file:///etc/passwd\n';
const expected = Object.create(null);
expected.__proto__ = ['mostly harmless'];
expected.hasOwnProperty = ['not a function'];
expected['OCSP - URI'] = ['file:///etc/passwd'];
deepStrictEqual(translatePeerCertificate({ infoAccess: input }),
{ infoAccess: expected });
}