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' }`.
|
||||
|
||||
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
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
@ -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>
|
||||
void SSLWrap<Base>::GetProtocol(const FunctionCallbackInfo<Value>& args) {
|
||||
Base* w;
|
||||
|
@ -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);
|
||||
|
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