Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tls: add option to override signature algorithms #29598

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion doc/api/tls.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
addaleax marked this conversation as resolved.
Show resolved Hide resolved
<!-- 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()
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
sam-github marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
13 changes: 13 additions & 0 deletions lib/_tls_common.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 7 additions & 0 deletions lib/_tls_wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,7 @@ function makeSocketMethodProxy(name) {

[
'getCipher',
'getSharedSigalgs',
'getEphemeralKeyInfo',
'getFinished',
'getPeerFinished',
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
101 changes: 101 additions & 0 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/node_crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
74 changes: 74 additions & 0 deletions test/parallel/test-tls-set-sigalgs.js
Original file line number Diff line number Diff line change
@@ -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');