Skip to content

Commit bd07574

Browse files
rogapsjasnell
authored andcommitted
crypto: support multiple ECDH curves and auto
Using SSL_CTX_set1_curves_list() (OpenSSL 1.0.2+), this allows to set colon separated ECDH curve names in SecureContext's ecdhCurve option. The option can also be set to "auto" to select the curve automatically from list built in OpenSSL by enabling SSL_CTX_set_ecdh_auto() (OpenSSL 1.0.2+). PR-URL: #15206 Ref: #15054 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
1 parent b9a9290 commit bd07574

File tree

4 files changed

+159
-19
lines changed

4 files changed

+159
-19
lines changed

doc/api/tls.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ openssl dhparam -outform PEM -out dhparam.pem 2048
101101

102102
If using Perfect Forward Secrecy using `ECDHE`, Diffie-Hellman parameters are
103103
not required and a default ECDHE curve will be used. The `ecdhCurve` property
104-
can be used when creating a TLS Server to specify the name of an alternative
105-
curve to use, see [`tls.createServer()`] for more info.
104+
can be used when creating a TLS Server to specify the list of names of supported
105+
curves to use, see [`tls.createServer()`] for more info.
106106

107107
### ALPN, NPN and SNI
108108

@@ -984,11 +984,13 @@ changes:
984984
preferences instead of the client's. When `true`, causes
985985
`SSL_OP_CIPHER_SERVER_PREFERENCE` to be set in `secureOptions`, see
986986
[OpenSSL Options][] for more information.
987-
* `ecdhCurve` {string} A string describing a named curve to use for ECDH key
988-
agreement or `false` to disable ECDH. Defaults to
989-
[`tls.DEFAULT_ECDH_CURVE`]. Use [`crypto.getCurves()`][] to obtain a list
990-
of available curve names. On recent releases, `openssl ecparam -list_curves`
991-
will also display the name and description of each available elliptic curve.
987+
* `ecdhCurve` {string} A string describing a named curve or a colon separated
988+
list of curve NIDs or names, for example `P-521:P-384:P-256`, to use for
989+
ECDH key agreement, or `false` to disable ECDH. Set to `auto` to select the
990+
curve automatically. Defaults to [`tls.DEFAULT_ECDH_CURVE`]. Use
991+
[`crypto.getCurves()`][] to obtain a list of available curve names. On
992+
recent releases, `openssl ecparam -list_curves` will also display the name
993+
and description of each available elliptic curve.
992994
* `dhparam` {string|Buffer} Diffie Hellman parameters, required for
993995
[Perfect Forward Secrecy][]. Use `openssl dhparam` to create the parameters.
994996
The key length must be greater than or equal to 1024 bits, otherwise an

src/node_crypto.cc

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -923,20 +923,14 @@ void SecureContext::SetECDHCurve(const FunctionCallbackInfo<Value>& args) {
923923

924924
node::Utf8Value curve(env->isolate(), args[0]);
925925

926-
int nid = OBJ_sn2nid(*curve);
927-
928-
if (nid == NID_undef)
929-
return env->ThrowTypeError("First argument should be a valid curve name");
930-
931-
EC_KEY* ecdh = EC_KEY_new_by_curve_name(nid);
932-
933-
if (ecdh == nullptr)
934-
return env->ThrowTypeError("First argument should be a valid curve name");
935-
936926
SSL_CTX_set_options(sc->ctx_, SSL_OP_SINGLE_ECDH_USE);
937-
SSL_CTX_set_tmp_ecdh(sc->ctx_, ecdh);
927+
SSL_CTX_set_ecdh_auto(sc->ctx_, 1);
928+
929+
if (strcmp(*curve, "auto") == 0)
930+
return;
938931

939-
EC_KEY_free(ecdh);
932+
if (!SSL_CTX_set1_curves_list(sc->ctx_, *curve))
933+
return env->ThrowError("Failed to set ECDH curve");
940934
}
941935

942936

test/parallel/test-tls-ecdh-auto.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
'use strict';
2+
const common = require('../common');
3+
4+
// This test ensures that the value "auto" on ecdhCurve option is
5+
// supported to enable automatic curve selection in TLS server.
6+
7+
if (!common.hasCrypto)
8+
common.skip('missing crypto');
9+
10+
if (!common.opensslCli)
11+
common.skip('missing openssl-cli');
12+
13+
const assert = require('assert');
14+
const tls = require('tls');
15+
const spawn = require('child_process').spawn;
16+
const fixtures = require('../common/fixtures');
17+
18+
function loadPEM(n) {
19+
return fixtures.readKey(`${n}.pem`);
20+
}
21+
22+
const options = {
23+
key: loadPEM('agent2-key'),
24+
cert: loadPEM('agent2-cert'),
25+
ciphers: '-ALL:ECDHE-RSA-AES128-SHA256',
26+
ecdhCurve: 'auto'
27+
};
28+
29+
const reply = 'I AM THE WALRUS'; // something recognizable
30+
31+
const server = tls.createServer(options, function(conn) {
32+
conn.end(reply);
33+
});
34+
35+
let gotReply = false;
36+
37+
server.listen(0, function() {
38+
const args = ['s_client',
39+
'-cipher', `${options.ciphers}`,
40+
'-connect', `127.0.0.1:${this.address().port}`];
41+
42+
// for the performance and stability issue in s_client on Windows
43+
if (common.isWindows)
44+
args.push('-no_rand_screen');
45+
46+
const client = spawn(common.opensslCli, args);
47+
48+
client.stdout.on('data', function(data) {
49+
const message = data.toString();
50+
if (message.includes(reply))
51+
gotReply = true;
52+
});
53+
54+
client.on('exit', function(code) {
55+
assert.strictEqual(0, code);
56+
server.close();
57+
});
58+
59+
client.on('error', assert.ifError);
60+
});
61+
62+
process.on('exit', function() {
63+
assert.ok(gotReply);
64+
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
'use strict';
2+
const common = require('../common');
3+
4+
// This test ensures that ecdhCurve option of TLS server supports colon
5+
// separated ECDH curve names as value.
6+
7+
if (!common.hasCrypto)
8+
common.skip('missing crypto');
9+
10+
if (!common.opensslCli)
11+
common.skip('missing openssl-cli');
12+
13+
const assert = require('assert');
14+
const tls = require('tls');
15+
const spawn = require('child_process').spawn;
16+
const fixtures = require('../common/fixtures');
17+
18+
function loadPEM(n) {
19+
return fixtures.readKey(`${n}.pem`);
20+
}
21+
22+
const options = {
23+
key: loadPEM('agent2-key'),
24+
cert: loadPEM('agent2-cert'),
25+
ciphers: '-ALL:ECDHE-RSA-AES128-SHA256',
26+
ecdhCurve: 'secp256k1:prime256v1:secp521r1'
27+
};
28+
29+
const reply = 'I AM THE WALRUS'; // something recognizable
30+
31+
const server = tls.createServer(options, function(conn) {
32+
conn.end(reply);
33+
});
34+
35+
let gotReply = false;
36+
37+
server.listen(0, function() {
38+
const args = ['s_client',
39+
'-cipher', `${options.ciphers}`,
40+
'-connect', `127.0.0.1:${this.address().port}`];
41+
42+
// for the performance and stability issue in s_client on Windows
43+
if (common.isWindows)
44+
args.push('-no_rand_screen');
45+
46+
const client = spawn(common.opensslCli, args);
47+
48+
client.stdout.on('data', function(data) {
49+
const message = data.toString();
50+
if (message.includes(reply))
51+
gotReply = true;
52+
});
53+
54+
client.on('exit', function(code) {
55+
assert.strictEqual(0, code);
56+
server.close();
57+
});
58+
59+
client.on('error', assert.ifError);
60+
});
61+
62+
process.on('exit', function() {
63+
assert.ok(gotReply);
64+
65+
// Some of unsupported curves
66+
const unsupportedCurves = [
67+
'wap-wsg-idm-ecid-wtls1',
68+
'c2pnb163v1',
69+
'prime192v3'
70+
];
71+
72+
// Brainpool is not supported in FIPS mode
73+
if (common.hasFipsCrypto)
74+
unsupportedCurves.push('brainpoolP256r1');
75+
76+
unsupportedCurves.forEach((ecdhCurve) => {
77+
assert.throws(() => tls.createServer({ ecdhCurve: ecdhCurve }),
78+
/Error: Failed to set ECDH curve/);
79+
});
80+
});

0 commit comments

Comments
 (0)