tls: allow obvious key/passphrase combinations

Passphrase is now used whether keys are provided singly, in an array of
string/buffer, or an array of object, where it used to be ignored in
some argument combinations. Specifically, these now work as expected:

  key: [encryptedPem],
  passphrase: 'passphrase'

and

  key: [{pem: encryptedPem}]
  passphrase: 'passphrase'

and

  key: [{pem: unencryptedPem}]

PR-URL: https://github.com/nodejs/node/pull/10294
Reviewed-By: Fedor Indutny <fedor.indutny@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
This commit is contained in:
Sam Roberts 2016-12-15 12:47:36 -08:00
parent 793d8719eb
commit 0b44384561
4 changed files with 97 additions and 30 deletions

View File

@ -893,12 +893,13 @@ added: v0.11.13
individually. PFX is usually encrypted, if it is, `passphrase` will be used individually. PFX is usually encrypted, if it is, `passphrase` will be used
to decrypt it. to decrypt it.
* `key` {string|string[]|Buffer|Buffer[]|Object[]} Optional private keys in * `key` {string|string[]|Buffer|Buffer[]|Object[]} Optional private keys in
PEM format. Single keys will be decrypted with `passphrase` if necessary. PEM format. PEM allows the option of private keys being encrypted. Encrypted
Multiple keys, probably using different algorithms, can be provided either keys will be decrypted with `options.passphrase`. Multiple keys using
as an array of unencrypted key strings or buffers, or an array of objects in different algorithms can be provided either as an array of unencrypted key
the form `{pem: <string|buffer>, passphrase: <string>}`. The object form can strings or buffers, or an array of objects in the form `{pem:
only occur in an array, and it _must_ include a passphrase, even if key is <string|buffer>[, passphrase: <string>]}`. The object form can only occur in
not encrypted. an array. `object.passphrase` is optional. Encrypted keys will be decrypted
with `object.passphrase` if provided, or `options.passphrase` if it is not.
* `passphrase` {string} Optional shared passphrase used for a single private * `passphrase` {string} Optional shared passphrase used for a single private
key and/or a PFX. key and/or a PFX.
* `cert` {string|string[]|Buffer|Buffer[]} Optional cert chains in PEM format. * `cert` {string|string[]|Buffer|Buffer[]} Optional cert chains in PEM format.

View File

@ -78,17 +78,11 @@ exports.createSecureContext = function createSecureContext(options, context) {
if (Array.isArray(options.key)) { if (Array.isArray(options.key)) {
for (i = 0; i < options.key.length; i++) { for (i = 0; i < options.key.length; i++) {
const key = options.key[i]; const key = options.key[i];
if (key.passphrase) const passphrase = key.passphrase || options.passphrase;
c.context.setKey(key.pem, key.passphrase); c.context.setKey(key.pem || key, passphrase);
else
c.context.setKey(key);
} }
} else { } else {
if (options.passphrase) { c.context.setKey(options.key, options.passphrase);
c.context.setKey(options.key, options.passphrase);
} else {
c.context.setKey(options.key);
}
} }
} }

View File

@ -442,7 +442,10 @@ void SecureContext::SetKey(const FunctionCallbackInfo<Value>& args) {
} }
if (len == 2) { if (len == 2) {
THROW_AND_RETURN_IF_NOT_STRING(args[1], "Pass phrase"); if (args[1]->IsUndefined() || args[1]->IsNull())
len = 1;
else
THROW_AND_RETURN_IF_NOT_STRING(args[1], "Pass phrase");
} }
BIO *bio = LoadBIO(env, args[0]); BIO *bio = LoadBIO(env, args[0]);

View File

@ -51,13 +51,12 @@ server.listen(0, common.mustCall(function() {
tls.connect({ tls.connect({
port: this.address().port, port: this.address().port,
key: rawKey, key: rawKey,
passphrase: 'passphrase', // Ignored. passphrase: 'ignored',
cert: cert, cert: cert,
rejectUnauthorized: false rejectUnauthorized: false
}, common.mustCall(function() {})); }, common.mustCall(function() {}));
// Buffer[] // Buffer[]
/* XXX(sam) Should work, but its unimplemented ATM.
tls.connect({ tls.connect({
port: this.address().port, port: this.address().port,
key: [passKey], key: [passKey],
@ -65,7 +64,6 @@ server.listen(0, common.mustCall(function() {
cert: [cert], cert: [cert],
rejectUnauthorized: false rejectUnauthorized: false
}, common.mustCall(function() {})); }, common.mustCall(function() {}));
*/
tls.connect({ tls.connect({
port: this.address().port, port: this.address().port,
@ -77,7 +75,7 @@ server.listen(0, common.mustCall(function() {
tls.connect({ tls.connect({
port: this.address().port, port: this.address().port,
key: [rawKey], key: [rawKey],
passphrase: 'passphrase', // Ignored. passphrase: 'ignored',
cert: [cert], cert: [cert],
rejectUnauthorized: false rejectUnauthorized: false
}, common.mustCall(function() {})); }, common.mustCall(function() {}));
@ -101,13 +99,12 @@ server.listen(0, common.mustCall(function() {
tls.connect({ tls.connect({
port: this.address().port, port: this.address().port,
key: rawKey.toString(), key: rawKey.toString(),
passphrase: 'passphrase', // Ignored. passphrase: 'ignored',
cert: cert.toString(), cert: cert.toString(),
rejectUnauthorized: false rejectUnauthorized: false
}, common.mustCall(function() {})); }, common.mustCall(function() {}));
// String[] // String[]
/* XXX(sam) Should work, but its unimplemented ATM.
tls.connect({ tls.connect({
port: this.address().port, port: this.address().port,
key: [passKey.toString()], key: [passKey.toString()],
@ -115,7 +112,6 @@ server.listen(0, common.mustCall(function() {
cert: [cert.toString()], cert: [cert.toString()],
rejectUnauthorized: false rejectUnauthorized: false
}, common.mustCall(function() {})); }, common.mustCall(function() {}));
*/
tls.connect({ tls.connect({
port: this.address().port, port: this.address().port,
@ -127,7 +123,7 @@ server.listen(0, common.mustCall(function() {
tls.connect({ tls.connect({
port: this.address().port, port: this.address().port,
key: [rawKey.toString()], key: [rawKey.toString()],
passphrase: 'passphrase', // Ignored. passphrase: 'ignored',
cert: [cert.toString()], cert: [cert.toString()],
rejectUnauthorized: false rejectUnauthorized: false
}, common.mustCall(function() {})); }, common.mustCall(function() {}));
@ -140,6 +136,22 @@ server.listen(0, common.mustCall(function() {
rejectUnauthorized: false rejectUnauthorized: false
}, common.mustCall(function() {})); }, common.mustCall(function() {}));
tls.connect({
port: this.address().port,
key: [{pem: passKey, passphrase: 'passphrase'}],
passphrase: 'ignored',
cert: cert,
rejectUnauthorized: false
}, common.mustCall(function() {}));
tls.connect({
port: this.address().port,
key: [{pem: passKey}],
passphrase: 'passphrase',
cert: cert,
rejectUnauthorized: false
}, common.mustCall(function() {}));
tls.connect({ tls.connect({
port: this.address().port, port: this.address().port,
key: [{pem: passKey.toString(), passphrase: 'passphrase'}], key: [{pem: passKey.toString(), passphrase: 'passphrase'}],
@ -149,23 +161,22 @@ server.listen(0, common.mustCall(function() {
tls.connect({ tls.connect({
port: this.address().port, port: this.address().port,
key: [{pem: rawKey, passphrase: 'passphrase'}], key: [{pem: rawKey, passphrase: 'ignored'}],
cert: cert, cert: cert,
rejectUnauthorized: false rejectUnauthorized: false
}, common.mustCall(function() {})); }, common.mustCall(function() {}));
tls.connect({ tls.connect({
port: this.address().port, port: this.address().port,
key: [{pem: rawKey.toString(), passphrase: 'passphrase'}], key: [{pem: rawKey.toString(), passphrase: 'ignored'}],
cert: cert, cert: cert,
rejectUnauthorized: false rejectUnauthorized: false
}, common.mustCall(function() {})); }, common.mustCall(function() {}));
/* XXX(sam) Should work, but unimplemented ATM
tls.connect({ tls.connect({
port: this.address().port, port: this.address().port,
key: [{pem: rawKey}], key: [{pem: rawKey}],
passphrase: 'passphrase', passphrase: 'ignored',
cert: cert, cert: cert,
rejectUnauthorized: false rejectUnauthorized: false
}, common.mustCall(function() {})); }, common.mustCall(function() {}));
@ -173,7 +184,7 @@ server.listen(0, common.mustCall(function() {
tls.connect({ tls.connect({
port: this.address().port, port: this.address().port,
key: [{pem: rawKey.toString()}], key: [{pem: rawKey.toString()}],
passphrase: 'passphrase', passphrase: 'ignored',
cert: cert, cert: cert,
rejectUnauthorized: false rejectUnauthorized: false
}, common.mustCall(function() {})); }, common.mustCall(function() {}));
@ -191,9 +202,37 @@ server.listen(0, common.mustCall(function() {
cert: cert, cert: cert,
rejectUnauthorized: false rejectUnauthorized: false
}, common.mustCall(function() {})); }, common.mustCall(function() {}));
*/
})).unref(); })).unref();
// Missing passphrase
assert.throws(function() {
tls.connect({
port: server.address().port,
key: passKey,
cert: cert,
rejectUnauthorized: false
});
}, /bad password read/);
assert.throws(function() {
tls.connect({
port: server.address().port,
key: [passKey],
cert: cert,
rejectUnauthorized: false
});
}, /bad password read/);
assert.throws(function() {
tls.connect({
port: server.address().port,
key: [{pem: passKey}],
cert: cert,
rejectUnauthorized: false
});
}, /bad password read/);
// Invalid passphrase
assert.throws(function() { assert.throws(function() {
tls.connect({ tls.connect({
port: server.address().port, port: server.address().port,
@ -203,3 +242,33 @@ assert.throws(function() {
rejectUnauthorized: false rejectUnauthorized: false
}); });
}, /bad decrypt/); }, /bad decrypt/);
assert.throws(function() {
tls.connect({
port: server.address().port,
key: [passKey],
passphrase: 'invalid',
cert: cert,
rejectUnauthorized: false
});
}, /bad decrypt/);
assert.throws(function() {
tls.connect({
port: server.address().port,
key: [{pem: passKey}],
passphrase: 'invalid',
cert: cert,
rejectUnauthorized: false
});
}, /bad decrypt/);
assert.throws(function() {
tls.connect({
port: server.address().port,
key: [{pem: passKey, passphrase: 'invalid'}],
passphrase: 'passphrase', // Valid but unused
cert: cert,
rejectUnauthorized: false
});
}, /bad decrypt/);