diff --git a/doc/api/tls.md b/doc/api/tls.md index e18bbb62b1908f..ebcf85438fdeb5 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -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` - individually. PFX is usually encrypted, if it is, `passphrase` will be used - to decrypt it. + * `pfx` {string|string[]|Buffer|Buffer[]|Object[]} Optional PFX or PKCS12 + encoded private key 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. Multiple PFX can be provided either + as an array of unencrypted PFX buffers, or an array of objects in the form + `{buf: [, passphrase: ]}`. 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 diff --git a/lib/_tls_common.js b/lib/_tls_common.js index 36b2ebdad68d0b..3d191b8244871e 100644 --- a/lib/_tls_common.js +++ b/lib/_tls_common.js @@ -137,20 +137,29 @@ exports.createSecureContext = function createSecureContext(options, context) { } if (options.pfx) { - var pfx = options.pfx; - var passphrase = options.passphrase; - if (!crypto) crypto = require('crypto'); - pfx = crypto._toBuf(pfx); - if (passphrase) - passphrase = crypto._toBuf(passphrase); - - if (passphrase) { - c.context.loadPKCS12(pfx, passphrase); + if (Array.isArray(options.pfx)) { + for (i = 0; i < options.pfx.length; i++) { + const pfx = options.pfx[i]; + const raw = pfx.buf ? pfx.buf : pfx; + const buf = crypto._toBuf(raw); + 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); + } } } diff --git a/test/fixtures/keys/Makefile b/test/fixtures/keys/Makefile index c7390eda0eefc4..27fda1eef27c87 100644 --- a/test/fixtures/keys/Makefile +++ b/test/fixtures/keys/Makefile @@ -335,6 +335,14 @@ ec-cert.pem: ec-csr.pem ec-key.pem -signkey ec-key.pem \ -out ec-cert.pem +ec-pfx.pem: ec-cert.pem ec-key.pem + openssl pkcs12 -export \ + -descert \ + -in ec-cert.pem \ + -inkey ec-key.pem \ + -out ec-pfx.pem \ + -password pass: + dh512.pem: openssl dhparam -out dh512.pem 512 diff --git a/test/fixtures/keys/ec-pfx.pem b/test/fixtures/keys/ec-pfx.pem new file mode 100644 index 00000000000000..3a4aa7dd696085 Binary files /dev/null and b/test/fixtures/keys/ec-pfx.pem differ diff --git a/test/parallel/test-tls-multi-pfx.js b/test/parallel/test-tls-multi-pfx.js new file mode 100644 index 00000000000000..f750aec325c957 --- /dev/null +++ b/test/parallel/test-tls-multi-pfx.js @@ -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 fixtures = require('../common/fixtures'); + +const options = { + pfx: [ + { + buf: fixtures.readKey('agent1-pfx.pem'), + passphrase: 'sample' + }, + fixtures.readKey('ec-pfx.pem') + ] +}; + +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 + }, common.mustCall(function() { + ciphers.push(ecdsa.getCipher()); + const rsa = tls.connect(server.address().port, { + ciphers: 'ECDHE-RSA-AES256-GCM-SHA384', + rejectUnauthorized: false + }, common.mustCall(function() { + 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' + }]); +});