summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Gerasimov <agerasimov@twilio.com>2019-09-18 16:48:44 +0200
committerRuben Bridgewater <ruben@bridgewater.de>2019-09-25 18:21:06 +0200
commit6272f82c07e913a76a316a786c9aadbc09f953ff (patch)
tree7fb7a288ad7b1830b285fefcac82a15b26bda07c
parentf01682392910b0e9dabfa5016f9ed2771a9e0e50 (diff)
downloadnode-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.md24
-rw-r--r--lib/_tls_common.js13
-rw-r--r--lib/_tls_wrap.js7
-rw-r--r--src/node_crypto.cc101
-rw-r--r--src/node_crypto.h2
-rw-r--r--test/parallel/test-tls-set-sigalgs.js74
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');