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:
parent
e078e482c5
commit
0c32ca96c8
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
74
test/parallel/test-tls-set-sigalgs.js
Normal file
74
test/parallel/test-tls-set-sigalgs.js
Normal 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');
|
Loading…
x
Reference in New Issue
Block a user