diff options
author | Anton Gerasimov <agerasimov@twilio.com> | 2019-09-18 16:48:44 +0200 |
---|---|---|
committer | Ruben Bridgewater <ruben@bridgewater.de> | 2019-09-25 18:21:06 +0200 |
commit | 6272f82c07e913a76a316a786c9aadbc09f953ff (patch) | |
tree | 7fb7a288ad7b1830b285fefcac82a15b26bda07c | |
parent | f01682392910b0e9dabfa5016f9ed2771a9e0e50 (diff) | |
download | node-new-6272f82c07e913a76a316a786c9aadbc09f953ff.tar.gz |
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>
-rw-r--r-- | doc/api/tls.md | 24 | ||||
-rw-r--r-- | lib/_tls_common.js | 13 | ||||
-rw-r--r-- | lib/_tls_wrap.js | 7 | ||||
-rw-r--r-- | src/node_crypto.cc | 101 | ||||
-rw-r--r-- | src/node_crypto.h | 2 | ||||
-rw-r--r-- | test/parallel/test-tls-set-sigalgs.js | 74 |
6 files changed, 220 insertions, 1 deletions
diff --git a/doc/api/tls.md b/doc/api/tls.md index 34f7aecca8..c9a30d97d7 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -839,7 +839,19 @@ Returns an object containing information on the negotiated cipher suite. For example: `{ name: 'AES256-SHA', version: 'TLSv1.2' }`. 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. ### tlsSocket.getEphemeralKeyInfo() @@ -1346,6 +1358,10 @@ argument. <!-- YAML added: v0.11.13 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 pr-url: https://github.com/nodejs/node/pull/26209 description: TLSv1.3 support added. @@ -1406,6 +1422,12 @@ changes: 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 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 more information, see [modifying the default cipher suite][]. Permitted ciphers can be obtained via [`tls.getCiphers()`][]. Cipher names must be diff --git a/lib/_tls_common.js b/lib/_tls_common.js index efe9040956..f24cfcbca6 100644 --- a/lib/_tls_common.js +++ b/lib/_tls_common.js @@ -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') { throw new ERR_INVALID_ARG_TYPE( 'options.ciphers', 'string', options.ciphers); diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index 2921a446cd..63115d59b8 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -859,6 +859,7 @@ function makeSocketMethodProxy(name) { [ 'getCipher', + 'getSharedSigalgs', 'getEphemeralKeyInfo', 'getFinished', 'getPeerFinished', @@ -1113,6 +1114,11 @@ Server.prototype.setSecureContext = function(options) { else this.crl = undefined; + if (options.sigalgs !== undefined) + this.sigalgs = options.sigalgs; + else + this.sigalgs = undefined; + if (options.ciphers) this.ciphers = options.ciphers; else @@ -1157,6 +1163,7 @@ Server.prototype.setSecureContext = function(options) { clientCertEngine: this.clientCertEngine, ca: this.ca, ciphers: this.ciphers, + sigalgs: this.sigalgs, ecdhCurve: this.ecdhCurve, dhparam: this.dhparam, minVersion: this.minVersion, diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 346853365a..d8bc794dcb 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -477,6 +477,7 @@ void SecureContext::Initialize(Environment* env, Local<Object> target) { env->SetProtoMethod(t, "addRootCerts", AddRootCerts); env->SetProtoMethod(t, "setCipherSuites", SetCipherSuites); env->SetProtoMethod(t, "setCiphers", SetCiphers); + env->SetProtoMethod(t, "setSigalgs", SetSigalgs); env->SetProtoMethod(t, "setECDHCurve", SetECDHCurve); env->SetProtoMethod(t, "setDHParam", SetDHParam); 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) { 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, "verifyError", VerifyError); env->SetProtoMethodNoSideEffect(t, "getCipher", GetCipher); + env->SetProtoMethodNoSideEffect(t, "getSharedSigalgs", GetSharedSigalgs); env->SetProtoMethod(t, "endParser", EndParser); env->SetProtoMethod(t, "certCbDone", CertCbDone); env->SetProtoMethod(t, "renegotiate", Renegotiate); @@ -2624,6 +2643,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> void SSLWrap<Base>::GetProtocol(const FunctionCallbackInfo<Value>& args) { Base* w; ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); diff --git a/src/node_crypto.h b/src/node_crypto.h index e335491612..91586563f4 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -125,6 +125,7 @@ class SecureContext : public BaseObject { static void AddRootCerts(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 SetSigalgs(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 SetOptions(const v8::FunctionCallbackInfo<v8::Value>& args); @@ -250,6 +251,7 @@ class SSLWrap { static void IsSessionReused(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 GetSharedSigalgs(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 Renegotiate(const v8::FunctionCallbackInfo<v8::Value>& args); diff --git a/test/parallel/test-tls-set-sigalgs.js b/test/parallel/test-tls-set-sigalgs.js new file mode 100644 index 0000000000..59dc2ca0c7 --- /dev/null +++ b/test/parallel/test-tls-set-sigalgs.js @@ -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'); |