Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
tls: multiple PFX in createSecureContext
Add support for multiple PFX files in tls.createSecureContext.
Also added support for object-style PFX pass.

Fixes: #14756
  • Loading branch information
djphoenix committed Sep 1, 2017
commit 21e2413b2b661ac63e97f029b24fdc5c9a2793e7
10 changes: 7 additions & 3 deletions doc/api/tls.md
Original file line number Diff line number Diff line change
Expand Up @@ -934,10 +934,14 @@ changes:
-->

* `options` {Object}
* `pfx` {string|Buffer} Optional PFX or PKCS12 encoded private key and
certificate chain. `pfx` is an alternative to providing `key` and `cert`
* `pfx` {Buffer|Buffer[]|Object[]} Optional PFX or PKCS12 encoded private key
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How come string is not supported anymore?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

PKCS12 is very rarely stored in PEM format, by default it's DER-encoded. But I forget about binary strings in JS. Will revert it, ACK.

and certificate chain. `pfx` is an alternative to providing `key` and `cert`
individually. PFX is usually encrypted, if it is, `passphrase` will be used
to decrypt it.
to decrypt it. Multiple PFX can be provided either as an array of unencrypted
PFX buffers, or an array of objects in the form `{buffer: <buffer>[,
passphrase: <string>]}`. The object form can only occur in an array.
`object.passphrase` is optional. Encrypted PFX will be decrypted with
`object.passphrase` if provided, or `options.passphrase` if it is not.
* `key` {string|string[]|Buffer|Buffer[]|Object[]} Optional private keys in
PEM format. PEM allows the option of private keys being encrypted. Encrypted
keys will be decrypted with `options.passphrase`. Multiple keys using
Expand Down
35 changes: 25 additions & 10 deletions lib/_tls_common.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,19 +161,34 @@ exports.createSecureContext = function createSecureContext(options, context) {
}

if (options.pfx) {
var pfx = options.pfx;

if (!crypto)
crypto = require('crypto');

pfx = crypto._toBuf(pfx);
if (passphrase)
passphrase = crypto._toBuf(passphrase);

if (passphrase) {
c.context.loadPKCS12(pfx, passphrase);
const Buffer = require('buffer').Buffer;

if (Array.isArray(options.pfx)) {
for (i = 0; i < options.pfx.length; i++) {
const pfx = options.pfx[i];
let buf;
if (pfx.buffer instanceof Buffer) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Buffer.isBuffer()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also, should this be limited to just Buffer instances? We likely should allow any Uint8Array

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ACK

buf = crypto._toBuf(pfx.buffer);
} else {
buf = crypto._toBuf(pfx);
}
const passphrase = pfx.passphrase || options.passphrase;
if (passphrase) {
c.context.loadPKCS12(buf, crypto._toBuf(passphrase));
} else {
c.context.loadPKCS12(buf);
}
}
} else {
c.context.loadPKCS12(pfx);
const buf = crypto._toBuf(options.pfx);
const passphrase = options.passphrase;
if (passphrase) {
c.context.loadPKCS12(buf, crypto._toBuf(passphrase));
} else {
c.context.loadPKCS12(buf);
}
}
}

Expand Down
Binary file added test/fixtures/keys/ec-pfx.pem
Binary file not shown.
50 changes: 50 additions & 0 deletions test/parallel/test-tls-multi-pfx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

const assert = require('assert');
const tls = require('tls');
const fs = require('fs');

const options = {
pfx: [
{
buffer: fs.readFileSync(`${common.fixturesDir}/keys/agent1-pfx.pem`),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please use the new ../common/fixtures stuff... e.g.

const fixtures = require('../common/fixtures');
/*... */
{
  buffer: fixtures.readKey('agent1-pfx.pem')
}

passphrase: 'sample'
},
fs.readFileSync(`${common.fixturesDir}/keys/ec-pfx.pem`)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we should add a third case for when an object is supplied with an encrypted key and the passphrase from options is used.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ACK (partially). Maybe add separate case out of "multi-pfx" test?

]
};

const ciphers = [];

const server = tls.createServer(options, function(conn) {
conn.end('ok');
}).listen(0, function() {
const ecdsa = tls.connect(this.address().port, {
ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384',
rejectUnauthorized: false
}, function() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

common.mustCall() here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@jasnell in listen (L24) also? Or not necessary?

ciphers.push(ecdsa.getCipher());
const rsa = tls.connect(server.address().port, {
ciphers: 'ECDHE-RSA-AES256-GCM-SHA384',
rejectUnauthorized: false
}, function() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

common.mustCall() here also

ciphers.push(rsa.getCipher());
ecdsa.end();
rsa.end();
server.close();
});
});
});

process.on('exit', function() {
assert.deepStrictEqual(ciphers, [{
name: 'ECDHE-ECDSA-AES256-GCM-SHA384',
version: 'TLSv1/SSLv3'
}, {
name: 'ECDHE-RSA-AES256-GCM-SHA384',
version: 'TLSv1/SSLv3'
}]);
});