tls: add option to override signature algorithms

Passes the list down to SSL_CTX_set1_sigalgs_list.

Option to get the list of shared signature algorithms
from a TLS socket added as well for testing.

Signed-off-by: Anton Gerasimov <agerasimov@twilio.com>

PR-URL: https://github.com/nodejs/node/pull/29598
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
Anton Gerasimov 2019-09-18 16:48:44 +02:00 committed by Rich Trott
parent e078e482c5
commit 0c32ca96c8
6 changed files with 220 additions and 1 deletions

View File

@ -839,7 +839,19 @@ Returns an object containing information on the negotiated cipher suite.
For example: `{ name: 'AES256-SHA', version: 'TLSv1.2' }`. For example: `{ name: 'AES256-SHA', version: 'TLSv1.2' }`.
See See
[OpenSSL](https://www.openssl.org/docs/man1.1.1/man3/SSL_CIPHER_get_name.html) [SSL_CIPHER_get_name](https://www.openssl.org/docs/man1.1.1/man3/SSL_CIPHER_get_name.html)
for more information.
### tlsSocket.getSharedSigalgs()
<!-- YAML
added: REPLACEME
-->
* Returns: {Array} List of signature algorithms shared between the server and
the client in the order of decreasing preference.
See
[SSL_get_shared_sigalgs](https://www.openssl.org/docs/man1.1.1/man3/SSL_get_shared_sigalgs.html)
for more information. for more information.
### tlsSocket.getEphemeralKeyInfo() ### tlsSocket.getEphemeralKeyInfo()
@ -1346,6 +1358,10 @@ argument.
<!-- YAML <!-- YAML
added: v0.11.13 added: v0.11.13
changes: changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/29598
description: Added `sigalgs` option to override supported signature
algorithms.
- version: v12.0.0 - version: v12.0.0
pr-url: https://github.com/nodejs/node/pull/26209 pr-url: https://github.com/nodejs/node/pull/26209
description: TLSv1.3 support added. description: TLSv1.3 support added.
@ -1406,6 +1422,12 @@ changes:
order as their private keys in `key`. If the intermediate certificates are order as their private keys in `key`. If the intermediate certificates are
not provided, the peer will not be able to validate the certificate, and the not provided, the peer will not be able to validate the certificate, and the
handshake will fail. handshake will fail.
* `sigalgs` {string}` Colon-separated list of supported signature algorithms.
The list can contain digest algorithms (`SHA256`, `MD5` etc.), public key
algorithms (`RSA-PSS`, `ECDSA` etc.), combination of both (e.g
'RSA+SHA384') or TLS v1.3 scheme names (e.g. `rsa_pss_pss_sha512`).
See [OpenSSL man pages](https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set1_sigalgs_list.html)
for more info.
* `ciphers` {string} Cipher suite specification, replacing the default. For * `ciphers` {string} Cipher suite specification, replacing the default. For
more information, see [modifying the default cipher suite][]. Permitted more information, see [modifying the default cipher suite][]. Permitted
ciphers can be obtained via [`tls.getCiphers()`][]. Cipher names must be ciphers can be obtained via [`tls.getCiphers()`][]. Cipher names must be

View File

@ -153,6 +153,19 @@ exports.createSecureContext = function createSecureContext(options) {
} }
} }
const sigalgs = options.sigalgs;
if (sigalgs !== undefined) {
if (typeof sigalgs !== 'string') {
throw new ERR_INVALID_ARG_TYPE('options.sigalgs', 'string', sigalgs);
}
if (sigalgs === '') {
throw new ERR_INVALID_OPT_VALUE('sigalgs', sigalgs);
}
c.context.setSigalgs(sigalgs);
}
if (options.ciphers && typeof options.ciphers !== 'string') { if (options.ciphers && typeof options.ciphers !== 'string') {
throw new ERR_INVALID_ARG_TYPE( throw new ERR_INVALID_ARG_TYPE(
'options.ciphers', 'string', options.ciphers); 'options.ciphers', 'string', options.ciphers);

View File

@ -859,6 +859,7 @@ function makeSocketMethodProxy(name) {
[ [
'getCipher', 'getCipher',
'getSharedSigalgs',
'getEphemeralKeyInfo', 'getEphemeralKeyInfo',
'getFinished', 'getFinished',
'getPeerFinished', 'getPeerFinished',
@ -1113,6 +1114,11 @@ Server.prototype.setSecureContext = function(options) {
else else
this.crl = undefined; this.crl = undefined;
if (options.sigalgs !== undefined)
this.sigalgs = options.sigalgs;
else
this.sigalgs = undefined;
if (options.ciphers) if (options.ciphers)
this.ciphers = options.ciphers; this.ciphers = options.ciphers;
else else
@ -1157,6 +1163,7 @@ Server.prototype.setSecureContext = function(options) {
clientCertEngine: this.clientCertEngine, clientCertEngine: this.clientCertEngine,
ca: this.ca, ca: this.ca,
ciphers: this.ciphers, ciphers: this.ciphers,
sigalgs: this.sigalgs,
ecdhCurve: this.ecdhCurve, ecdhCurve: this.ecdhCurve,
dhparam: this.dhparam, dhparam: this.dhparam,
minVersion: this.minVersion, minVersion: this.minVersion,

View File

@ -477,6 +477,7 @@ void SecureContext::Initialize(Environment* env, Local<Object> target) {
env->SetProtoMethod(t, "addRootCerts", AddRootCerts); env->SetProtoMethod(t, "addRootCerts", AddRootCerts);
env->SetProtoMethod(t, "setCipherSuites", SetCipherSuites); env->SetProtoMethod(t, "setCipherSuites", SetCipherSuites);
env->SetProtoMethod(t, "setCiphers", SetCiphers); env->SetProtoMethod(t, "setCiphers", SetCiphers);
env->SetProtoMethod(t, "setSigalgs", SetSigalgs);
env->SetProtoMethod(t, "setECDHCurve", SetECDHCurve); env->SetProtoMethod(t, "setECDHCurve", SetECDHCurve);
env->SetProtoMethod(t, "setDHParam", SetDHParam); env->SetProtoMethod(t, "setDHParam", SetDHParam);
env->SetProtoMethod(t, "setMaxProto", SetMaxProto); env->SetProtoMethod(t, "setMaxProto", SetMaxProto);
@ -745,6 +746,23 @@ void SecureContext::SetKey(const FunctionCallbackInfo<Value>& args) {
} }
} }
void SecureContext::SetSigalgs(const FunctionCallbackInfo<Value>& args) {
SecureContext* sc;
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
Environment* env = sc->env();
ClearErrorOnReturn clear_error_on_return;
CHECK_EQ(args.Length(), 1);
CHECK(args[0]->IsString());
const node::Utf8Value sigalgs(env->isolate(), args[0]);
int rv = SSL_CTX_set1_sigalgs_list(sc->ctx_.get(), *sigalgs);
if (rv == 0) {
return ThrowCryptoError(env, ERR_get_error());
}
}
int SSL_CTX_get_issuer(SSL_CTX* ctx, X509* cert, X509** issuer) { int SSL_CTX_get_issuer(SSL_CTX* ctx, X509* cert, X509** issuer) {
X509_STORE* store = SSL_CTX_get_cert_store(ctx); X509_STORE* store = SSL_CTX_get_cert_store(ctx);
@ -1690,6 +1708,7 @@ void SSLWrap<Base>::AddMethods(Environment* env, Local<FunctionTemplate> t) {
env->SetProtoMethodNoSideEffect(t, "isSessionReused", IsSessionReused); env->SetProtoMethodNoSideEffect(t, "isSessionReused", IsSessionReused);
env->SetProtoMethodNoSideEffect(t, "verifyError", VerifyError); env->SetProtoMethodNoSideEffect(t, "verifyError", VerifyError);
env->SetProtoMethodNoSideEffect(t, "getCipher", GetCipher); env->SetProtoMethodNoSideEffect(t, "getCipher", GetCipher);
env->SetProtoMethodNoSideEffect(t, "getSharedSigalgs", GetSharedSigalgs);
env->SetProtoMethod(t, "endParser", EndParser); env->SetProtoMethod(t, "endParser", EndParser);
env->SetProtoMethod(t, "certCbDone", CertCbDone); env->SetProtoMethod(t, "certCbDone", CertCbDone);
env->SetProtoMethod(t, "renegotiate", Renegotiate); env->SetProtoMethod(t, "renegotiate", Renegotiate);
@ -2623,6 +2642,88 @@ void SSLWrap<Base>::GetCipher(const FunctionCallbackInfo<Value>& args) {
} }
template <class Base>
void SSLWrap<Base>::GetSharedSigalgs(const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
Environment* env = w->ssl_env();
std::vector<Local<Value>> ret_arr;
SSL* ssl = w->ssl_.get();
int nsig = SSL_get_shared_sigalgs(ssl, 0, nullptr, nullptr, nullptr, nullptr,
nullptr);
for (int i = 0; i < nsig; i++) {
int hash_nid;
int sign_nid;
std::string sig_with_md;
SSL_get_shared_sigalgs(ssl, i, &sign_nid, &hash_nid, nullptr, nullptr,
nullptr);
switch (sign_nid) {
case EVP_PKEY_RSA:
sig_with_md = "RSA+";
break;
case EVP_PKEY_RSA_PSS:
sig_with_md = "RSA-PSS+";
break;
case EVP_PKEY_DSA:
sig_with_md = "DSA+";
break;
case EVP_PKEY_EC:
sig_with_md = "ECDSA+";
break;
case NID_ED25519:
sig_with_md = "Ed25519+";
break;
case NID_ED448:
sig_with_md = "Ed448+";
break;
case NID_id_GostR3410_2001:
sig_with_md = "gost2001+";
break;
case NID_id_GostR3410_2012_256:
sig_with_md = "gost2012_256+";
break;
case NID_id_GostR3410_2012_512:
sig_with_md = "gost2012_512+";
break;
default:
const char* sn = OBJ_nid2sn(sign_nid);
if (sn != nullptr) {
sig_with_md = std::string(sn) + "+";
} else {
sig_with_md = "UNDEF+";
}
break;
}
const char* sn_hash = OBJ_nid2sn(hash_nid);
if (sn_hash != nullptr) {
sig_with_md += std::string(sn_hash);
} else {
sig_with_md += "UNDEF";
}
ret_arr.push_back(OneByteString(env->isolate(), sig_with_md.c_str()));
}
args.GetReturnValue().Set(
Array::New(env->isolate(), ret_arr.data(), ret_arr.size()));
}
template <class Base> template <class Base>
void SSLWrap<Base>::GetProtocol(const FunctionCallbackInfo<Value>& args) { void SSLWrap<Base>::GetProtocol(const FunctionCallbackInfo<Value>& args) {
Base* w; Base* w;

View File

@ -125,6 +125,7 @@ class SecureContext : public BaseObject {
static void AddRootCerts(const v8::FunctionCallbackInfo<v8::Value>& args); static void AddRootCerts(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetCipherSuites(const v8::FunctionCallbackInfo<v8::Value>& args); static void SetCipherSuites(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetCiphers(const v8::FunctionCallbackInfo<v8::Value>& args); static void SetCiphers(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetSigalgs(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetECDHCurve(const v8::FunctionCallbackInfo<v8::Value>& args); static void SetECDHCurve(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetDHParam(const v8::FunctionCallbackInfo<v8::Value>& args); static void SetDHParam(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetOptions(const v8::FunctionCallbackInfo<v8::Value>& args); static void SetOptions(const v8::FunctionCallbackInfo<v8::Value>& args);
@ -250,6 +251,7 @@ class SSLWrap {
static void IsSessionReused(const v8::FunctionCallbackInfo<v8::Value>& args); static void IsSessionReused(const v8::FunctionCallbackInfo<v8::Value>& args);
static void VerifyError(const v8::FunctionCallbackInfo<v8::Value>& args); static void VerifyError(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetCipher(const v8::FunctionCallbackInfo<v8::Value>& args); static void GetCipher(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetSharedSigalgs(const v8::FunctionCallbackInfo<v8::Value>& args);
static void EndParser(const v8::FunctionCallbackInfo<v8::Value>& args); static void EndParser(const v8::FunctionCallbackInfo<v8::Value>& args);
static void CertCbDone(const v8::FunctionCallbackInfo<v8::Value>& args); static void CertCbDone(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Renegotiate(const v8::FunctionCallbackInfo<v8::Value>& args); static void Renegotiate(const v8::FunctionCallbackInfo<v8::Value>& args);

View File

@ -0,0 +1,74 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
const fixtures = require('../common/fixtures');
// Test sigalgs: option for TLS.
const {
assert, connect, keys
} = require(fixtures.path('tls-connect'));
function assert_arrays_equal(left, right) {
assert.strictEqual(left.length, right.length);
for (let i = 0; i < left.length; i++) {
assert.strictEqual(left[i], right[i]);
}
}
function test(csigalgs, ssigalgs, shared_sigalgs, cerr, serr) {
assert(shared_sigalgs || serr || cerr, 'test missing any expectations');
connect({
client: {
checkServerIdentity: (servername, cert) => { },
ca: `${keys.agent1.cert}\n${keys.agent6.ca}`,
cert: keys.agent2.cert,
key: keys.agent2.key,
sigalgs: csigalgs
},
server: {
cert: keys.agent6.cert,
key: keys.agent6.key,
ca: keys.agent2.ca,
context: {
requestCert: true,
rejectUnauthorized: true
},
sigalgs: ssigalgs
},
}, common.mustCall((err, pair, cleanup) => {
if (shared_sigalgs) {
assert.ifError(err);
assert.ifError(pair.server.err);
assert.ifError(pair.client.err);
assert(pair.server.conn);
assert(pair.client.conn);
assert_arrays_equal(pair.server.conn.getSharedSigalgs(), shared_sigalgs);
} else {
if (serr) {
assert(pair.server.err);
assert(pair.server.err.code, serr);
}
if (cerr) {
assert(pair.client.err);
assert(pair.client.err.code, cerr);
}
}
return cleanup();
}));
}
// Have shared sigalgs
test('RSA-PSS+SHA384', 'RSA-PSS+SHA384', ['RSA-PSS+SHA384']);
test('RSA-PSS+SHA256:RSA-PSS+SHA512:ECDSA+SHA256',
'RSA-PSS+SHA256:ECDSA+SHA256',
['RSA-PSS+SHA256', 'ECDSA+SHA256']);
// Do not have shared sigalgs.
test('RSA-PSS+SHA384', 'ECDSA+SHA256',
undefined, 'ECONNRESET', 'ERR_SSL_NO_SHARED_SIGNATURE_ALGORITMS');
test('RSA-PSS+SHA384:ECDSA+SHA256', 'ECDSA+SHA384:RSA-PSS+SHA256',
undefined, 'ECONNRESET', 'ERR_SSL_NO_SHARED_SIGNATURE_ALGORITMS');