Skip to content

Commit

Permalink
tls: add option to override signature algorithms
Browse files Browse the repository at this point in the history
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: #29598
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
  • Loading branch information
OYTIS authored and Trott committed Sep 24, 2019
1 parent e078e48 commit 0c32ca9
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 1 deletion.
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()
<!-- 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.
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');

0 comments on commit 0c32ca9

Please sign in to comment.