Skip to content

Commit 6272f82

Browse files
OYTISBridgeAR
authored andcommitted
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 <[email protected]> PR-URL: #29598 Reviewed-By: Sam Roberts <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent f016823 commit 6272f82

File tree

6 files changed

+220
-1
lines changed

6 files changed

+220
-1
lines changed

doc/api/tls.md

+23-1
Original file line numberDiff line numberDiff line change
@@ -839,7 +839,19 @@ Returns an object containing information on the negotiated cipher suite.
839839
For example: `{ name: 'AES256-SHA', version: 'TLSv1.2' }`.
840840

841841
See
842-
[OpenSSL](https://www.openssl.org/docs/man1.1.1/man3/SSL_CIPHER_get_name.html)
842+
[SSL_CIPHER_get_name](https://www.openssl.org/docs/man1.1.1/man3/SSL_CIPHER_get_name.html)
843+
for more information.
844+
845+
### tlsSocket.getSharedSigalgs()
846+
<!-- YAML
847+
added: REPLACEME
848+
-->
849+
850+
* Returns: {Array} List of signature algorithms shared between the server and
851+
the client in the order of decreasing preference.
852+
853+
See
854+
[SSL_get_shared_sigalgs](https://www.openssl.org/docs/man1.1.1/man3/SSL_get_shared_sigalgs.html)
843855
for more information.
844856

845857
### tlsSocket.getEphemeralKeyInfo()
@@ -1346,6 +1358,10 @@ argument.
13461358
<!-- YAML
13471359
added: v0.11.13
13481360
changes:
1361+
- version: REPLACEME
1362+
pr-url: https://github.com/nodejs/node/pull/29598
1363+
description: Added `sigalgs` option to override supported signature
1364+
algorithms.
13491365
- version: v12.0.0
13501366
pr-url: https://github.com/nodejs/node/pull/26209
13511367
description: TLSv1.3 support added.
@@ -1406,6 +1422,12 @@ changes:
14061422
order as their private keys in `key`. If the intermediate certificates are
14071423
not provided, the peer will not be able to validate the certificate, and the
14081424
handshake will fail.
1425+
* `sigalgs` {string}` Colon-separated list of supported signature algorithms.
1426+
The list can contain digest algorithms (`SHA256`, `MD5` etc.), public key
1427+
algorithms (`RSA-PSS`, `ECDSA` etc.), combination of both (e.g
1428+
'RSA+SHA384') or TLS v1.3 scheme names (e.g. `rsa_pss_pss_sha512`).
1429+
See [OpenSSL man pages](https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set1_sigalgs_list.html)
1430+
for more info.
14091431
* `ciphers` {string} Cipher suite specification, replacing the default. For
14101432
more information, see [modifying the default cipher suite][]. Permitted
14111433
ciphers can be obtained via [`tls.getCiphers()`][]. Cipher names must be

lib/_tls_common.js

+13
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,19 @@ exports.createSecureContext = function createSecureContext(options) {
153153
}
154154
}
155155

156+
const sigalgs = options.sigalgs;
157+
if (sigalgs !== undefined) {
158+
if (typeof sigalgs !== 'string') {
159+
throw new ERR_INVALID_ARG_TYPE('options.sigalgs', 'string', sigalgs);
160+
}
161+
162+
if (sigalgs === '') {
163+
throw new ERR_INVALID_OPT_VALUE('sigalgs', sigalgs);
164+
}
165+
166+
c.context.setSigalgs(sigalgs);
167+
}
168+
156169
if (options.ciphers && typeof options.ciphers !== 'string') {
157170
throw new ERR_INVALID_ARG_TYPE(
158171
'options.ciphers', 'string', options.ciphers);

lib/_tls_wrap.js

+7
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,7 @@ function makeSocketMethodProxy(name) {
859859

860860
[
861861
'getCipher',
862+
'getSharedSigalgs',
862863
'getEphemeralKeyInfo',
863864
'getFinished',
864865
'getPeerFinished',
@@ -1113,6 +1114,11 @@ Server.prototype.setSecureContext = function(options) {
11131114
else
11141115
this.crl = undefined;
11151116

1117+
if (options.sigalgs !== undefined)
1118+
this.sigalgs = options.sigalgs;
1119+
else
1120+
this.sigalgs = undefined;
1121+
11161122
if (options.ciphers)
11171123
this.ciphers = options.ciphers;
11181124
else
@@ -1157,6 +1163,7 @@ Server.prototype.setSecureContext = function(options) {
11571163
clientCertEngine: this.clientCertEngine,
11581164
ca: this.ca,
11591165
ciphers: this.ciphers,
1166+
sigalgs: this.sigalgs,
11601167
ecdhCurve: this.ecdhCurve,
11611168
dhparam: this.dhparam,
11621169
minVersion: this.minVersion,

src/node_crypto.cc

+101
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,7 @@ void SecureContext::Initialize(Environment* env, Local<Object> target) {
477477
env->SetProtoMethod(t, "addRootCerts", AddRootCerts);
478478
env->SetProtoMethod(t, "setCipherSuites", SetCipherSuites);
479479
env->SetProtoMethod(t, "setCiphers", SetCiphers);
480+
env->SetProtoMethod(t, "setSigalgs", SetSigalgs);
480481
env->SetProtoMethod(t, "setECDHCurve", SetECDHCurve);
481482
env->SetProtoMethod(t, "setDHParam", SetDHParam);
482483
env->SetProtoMethod(t, "setMaxProto", SetMaxProto);
@@ -745,6 +746,23 @@ void SecureContext::SetKey(const FunctionCallbackInfo<Value>& args) {
745746
}
746747
}
747748

749+
void SecureContext::SetSigalgs(const FunctionCallbackInfo<Value>& args) {
750+
SecureContext* sc;
751+
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
752+
Environment* env = sc->env();
753+
ClearErrorOnReturn clear_error_on_return;
754+
755+
CHECK_EQ(args.Length(), 1);
756+
CHECK(args[0]->IsString());
757+
758+
const node::Utf8Value sigalgs(env->isolate(), args[0]);
759+
760+
int rv = SSL_CTX_set1_sigalgs_list(sc->ctx_.get(), *sigalgs);
761+
762+
if (rv == 0) {
763+
return ThrowCryptoError(env, ERR_get_error());
764+
}
765+
}
748766

749767
int SSL_CTX_get_issuer(SSL_CTX* ctx, X509* cert, X509** issuer) {
750768
X509_STORE* store = SSL_CTX_get_cert_store(ctx);
@@ -1690,6 +1708,7 @@ void SSLWrap<Base>::AddMethods(Environment* env, Local<FunctionTemplate> t) {
16901708
env->SetProtoMethodNoSideEffect(t, "isSessionReused", IsSessionReused);
16911709
env->SetProtoMethodNoSideEffect(t, "verifyError", VerifyError);
16921710
env->SetProtoMethodNoSideEffect(t, "getCipher", GetCipher);
1711+
env->SetProtoMethodNoSideEffect(t, "getSharedSigalgs", GetSharedSigalgs);
16931712
env->SetProtoMethod(t, "endParser", EndParser);
16941713
env->SetProtoMethod(t, "certCbDone", CertCbDone);
16951714
env->SetProtoMethod(t, "renegotiate", Renegotiate);
@@ -2623,6 +2642,88 @@ void SSLWrap<Base>::GetCipher(const FunctionCallbackInfo<Value>& args) {
26232642
}
26242643

26252644

2645+
template <class Base>
2646+
void SSLWrap<Base>::GetSharedSigalgs(const FunctionCallbackInfo<Value>& args) {
2647+
Base* w;
2648+
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
2649+
Environment* env = w->ssl_env();
2650+
std::vector<Local<Value>> ret_arr;
2651+
2652+
SSL* ssl = w->ssl_.get();
2653+
int nsig = SSL_get_shared_sigalgs(ssl, 0, nullptr, nullptr, nullptr, nullptr,
2654+
nullptr);
2655+
2656+
for (int i = 0; i < nsig; i++) {
2657+
int hash_nid;
2658+
int sign_nid;
2659+
std::string sig_with_md;
2660+
2661+
SSL_get_shared_sigalgs(ssl, i, &sign_nid, &hash_nid, nullptr, nullptr,
2662+
nullptr);
2663+
2664+
switch (sign_nid) {
2665+
case EVP_PKEY_RSA:
2666+
sig_with_md = "RSA+";
2667+
break;
2668+
2669+
case EVP_PKEY_RSA_PSS:
2670+
sig_with_md = "RSA-PSS+";
2671+
break;
2672+
2673+
case EVP_PKEY_DSA:
2674+
sig_with_md = "DSA+";
2675+
break;
2676+
2677+
case EVP_PKEY_EC:
2678+
sig_with_md = "ECDSA+";
2679+
break;
2680+
2681+
case NID_ED25519:
2682+
sig_with_md = "Ed25519+";
2683+
break;
2684+
2685+
case NID_ED448:
2686+
sig_with_md = "Ed448+";
2687+
break;
2688+
2689+
case NID_id_GostR3410_2001:
2690+
sig_with_md = "gost2001+";
2691+
break;
2692+
2693+
case NID_id_GostR3410_2012_256:
2694+
sig_with_md = "gost2012_256+";
2695+
break;
2696+
2697+
case NID_id_GostR3410_2012_512:
2698+
sig_with_md = "gost2012_512+";
2699+
break;
2700+
2701+
default:
2702+
const char* sn = OBJ_nid2sn(sign_nid);
2703+
2704+
if (sn != nullptr) {
2705+
sig_with_md = std::string(sn) + "+";
2706+
} else {
2707+
sig_with_md = "UNDEF+";
2708+
}
2709+
break;
2710+
}
2711+
2712+
const char* sn_hash = OBJ_nid2sn(hash_nid);
2713+
if (sn_hash != nullptr) {
2714+
sig_with_md += std::string(sn_hash);
2715+
} else {
2716+
sig_with_md += "UNDEF";
2717+
}
2718+
2719+
ret_arr.push_back(OneByteString(env->isolate(), sig_with_md.c_str()));
2720+
}
2721+
2722+
args.GetReturnValue().Set(
2723+
Array::New(env->isolate(), ret_arr.data(), ret_arr.size()));
2724+
}
2725+
2726+
26262727
template <class Base>
26272728
void SSLWrap<Base>::GetProtocol(const FunctionCallbackInfo<Value>& args) {
26282729
Base* w;

src/node_crypto.h

+2
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ class SecureContext : public BaseObject {
125125
static void AddRootCerts(const v8::FunctionCallbackInfo<v8::Value>& args);
126126
static void SetCipherSuites(const v8::FunctionCallbackInfo<v8::Value>& args);
127127
static void SetCiphers(const v8::FunctionCallbackInfo<v8::Value>& args);
128+
static void SetSigalgs(const v8::FunctionCallbackInfo<v8::Value>& args);
128129
static void SetECDHCurve(const v8::FunctionCallbackInfo<v8::Value>& args);
129130
static void SetDHParam(const v8::FunctionCallbackInfo<v8::Value>& args);
130131
static void SetOptions(const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -250,6 +251,7 @@ class SSLWrap {
250251
static void IsSessionReused(const v8::FunctionCallbackInfo<v8::Value>& args);
251252
static void VerifyError(const v8::FunctionCallbackInfo<v8::Value>& args);
252253
static void GetCipher(const v8::FunctionCallbackInfo<v8::Value>& args);
254+
static void GetSharedSigalgs(const v8::FunctionCallbackInfo<v8::Value>& args);
253255
static void EndParser(const v8::FunctionCallbackInfo<v8::Value>& args);
254256
static void CertCbDone(const v8::FunctionCallbackInfo<v8::Value>& args);
255257
static void Renegotiate(const v8::FunctionCallbackInfo<v8::Value>& args);

test/parallel/test-tls-set-sigalgs.js

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
'use strict';
2+
const common = require('../common');
3+
if (!common.hasCrypto) common.skip('missing crypto');
4+
const fixtures = require('../common/fixtures');
5+
6+
// Test sigalgs: option for TLS.
7+
8+
const {
9+
assert, connect, keys
10+
} = require(fixtures.path('tls-connect'));
11+
12+
function assert_arrays_equal(left, right) {
13+
assert.strictEqual(left.length, right.length);
14+
for (let i = 0; i < left.length; i++) {
15+
assert.strictEqual(left[i], right[i]);
16+
}
17+
}
18+
19+
function test(csigalgs, ssigalgs, shared_sigalgs, cerr, serr) {
20+
assert(shared_sigalgs || serr || cerr, 'test missing any expectations');
21+
connect({
22+
client: {
23+
checkServerIdentity: (servername, cert) => { },
24+
ca: `${keys.agent1.cert}\n${keys.agent6.ca}`,
25+
cert: keys.agent2.cert,
26+
key: keys.agent2.key,
27+
sigalgs: csigalgs
28+
},
29+
server: {
30+
cert: keys.agent6.cert,
31+
key: keys.agent6.key,
32+
ca: keys.agent2.ca,
33+
context: {
34+
requestCert: true,
35+
rejectUnauthorized: true
36+
},
37+
sigalgs: ssigalgs
38+
},
39+
}, common.mustCall((err, pair, cleanup) => {
40+
if (shared_sigalgs) {
41+
assert.ifError(err);
42+
assert.ifError(pair.server.err);
43+
assert.ifError(pair.client.err);
44+
assert(pair.server.conn);
45+
assert(pair.client.conn);
46+
assert_arrays_equal(pair.server.conn.getSharedSigalgs(), shared_sigalgs);
47+
} else {
48+
if (serr) {
49+
assert(pair.server.err);
50+
assert(pair.server.err.code, serr);
51+
}
52+
53+
if (cerr) {
54+
assert(pair.client.err);
55+
assert(pair.client.err.code, cerr);
56+
}
57+
}
58+
59+
return cleanup();
60+
}));
61+
}
62+
63+
// Have shared sigalgs
64+
test('RSA-PSS+SHA384', 'RSA-PSS+SHA384', ['RSA-PSS+SHA384']);
65+
test('RSA-PSS+SHA256:RSA-PSS+SHA512:ECDSA+SHA256',
66+
'RSA-PSS+SHA256:ECDSA+SHA256',
67+
['RSA-PSS+SHA256', 'ECDSA+SHA256']);
68+
69+
// Do not have shared sigalgs.
70+
test('RSA-PSS+SHA384', 'ECDSA+SHA256',
71+
undefined, 'ECONNRESET', 'ERR_SSL_NO_SHARED_SIGNATURE_ALGORITMS');
72+
73+
test('RSA-PSS+SHA384:ECDSA+SHA256', 'ECDSA+SHA384:RSA-PSS+SHA256',
74+
undefined, 'ECONNRESET', 'ERR_SSL_NO_SHARED_SIGNATURE_ALGORITMS');

0 commit comments

Comments
 (0)