Skip to content

Commit 4e0236d

Browse files
panvarichardlau
authored andcommitted
tls: report negotiated TLS groups
When OpenSSL reports no peer temporary key object for TLS key agreement, fall back to the negotiated TLS Supported Group name. This lets getEphemeralKeyInfo() identify PQ, hybrid, and other group-based key agreement as TLSGroup without synthesizing a size. Keep existing DH and ECDH results unchanged when OpenSSL provides a recognizable temporary key. Fixes: #59452 Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: #64119 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent bdf3262 commit 4e0236d

6 files changed

Lines changed: 106 additions & 20 deletions

File tree

deps/ncrypto/ncrypto.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3079,6 +3079,17 @@ EVPKeyPointer SSLPointer::getPeerTempKey() const {
30793079
return EVPKeyPointer(raw_key);
30803080
}
30813081

3082+
std::optional<std::string_view> SSLPointer::getNegotiatedGroup() const {
3083+
#if OPENSSL_VERSION_PREREQ(3, 5)
3084+
if (!ssl_) return std::nullopt;
3085+
const char* group = SSL_get0_group_name(get());
3086+
if (group == nullptr) return std::nullopt;
3087+
return group;
3088+
#else
3089+
return std::nullopt;
3090+
#endif
3091+
}
3092+
30823093
std::optional<std::string_view> SSLPointer::getCipherName() const {
30833094
auto cipher = getCipher();
30843095
if (cipher == nullptr) return std::nullopt;

deps/ncrypto/ncrypto.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,7 @@ class SSLPointer final {
11971197
std::optional<const std::string_view> getServerName() const;
11981198
X509View getCertificate() const;
11991199
EVPKeyPointer getPeerTempKey() const;
1200+
std::optional<std::string_view> getNegotiatedGroup() const;
12001201
const SSL_CIPHER* getCipher() const;
12011202
bool isServer() const;
12021203

doc/api/tls.md

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ the character "E" appended to the traditional abbreviations):
131131

132132
Perfect forward secrecy using ECDHE is enabled by default. The `ecdhCurve`
133133
option can be used when creating a TLS server to customize the list of supported
134-
ECDH curves to use. See [`tls.createServer()`][] for more info.
134+
ECDH curves for TLSv1.2 and below, and the list of supported TLS groups for
135+
TLSv1.3. See [`tls.createServer()`][] for more info.
135136

136137
DHE is disabled by default but can be enabled alongside ECDHE by setting the
137138
`dhparam` option to `'auto'`. Custom DHE parameters are also supported but
@@ -1196,12 +1197,19 @@ added: v5.0.0
11961197

11971198
* Returns: {Object}
11981199

1199-
Returns an object representing the type, name, and size of parameter of
1200-
an ephemeral key exchange in [perfect forward secrecy][] on a client
1201-
connection. It returns an empty object when the key exchange is not
1202-
ephemeral. As this is only supported on a client socket; `null` is returned
1203-
if called on a server socket. The supported types are `'DH'` and `'ECDH'`. The
1204-
`name` property is available only when type is `'ECDH'`.
1200+
Returns an object describing ephemeral key agreement in [perfect forward
1201+
secrecy][] on a client connection. It returns an empty object when the key
1202+
agreement is not ephemeral. As this is only supported on a client socket;
1203+
`null` is returned if called on a server socket. The supported types are `'DH'`,
1204+
`'ECDH'`, and `'TLSGroup'`. For `'DH'` and `'ECDH'`, the object describes peer
1205+
temporary key parameters. For `'TLSGroup'`, the object identifies the negotiated
1206+
TLS Supported Group used for key agreement when a peer temporary key object is
1207+
not available.
1208+
1209+
The `name` property is available only when type is `'ECDH'` or `'TLSGroup'`. The
1210+
`size` property is not available when type is `'TLSGroup'`. For `'TLSGroup'`,
1211+
`name` is the negotiated TLS Supported Group name. Standardized TLS group names
1212+
and code points are listed in the [IANA TLS Supported Groups registry][].
12051213

12061214
For example: `{ type: 'ECDH', name: 'prime256v1', size: 256 }`.
12071215

@@ -2015,12 +2023,16 @@ changes:
20152023
required for non-ECDHE [perfect forward secrecy][]. If omitted or invalid,
20162024
the parameters are silently discarded and DHE ciphers will not be available.
20172025
[ECDHE][]-based [perfect forward secrecy][] will still be available.
2018-
* `ecdhCurve` {string} A string describing a named curve or a colon separated
2019-
list of curve NIDs or names, for example `P-521:P-384:P-256`, to use for
2020-
ECDH key agreement. Set to `auto` to select the
2021-
curve automatically. Use [`crypto.getCurves()`][] to obtain a list of
2022-
available curve names. On recent releases, `openssl ecparam -list_curves`
2023-
will also display the name and description of each available elliptic curve.
2026+
* `ecdhCurve` {string} A string describing a named curve, TLS group, or
2027+
colon-separated list of named curves or TLS groups to use for key agreement,
2028+
for example `P-521:P-384:P-256`, `X25519`, or `X25519MLKEM768`. The
2029+
historical name of this option refers to ECDH key agreement in TLSv1.2 and
2030+
below. In TLSv1.3, this option configures the TLS Supported Groups and
2031+
key share groups offered or accepted by the TLS stack. Set to `auto` to
2032+
select the group automatically. Use [`crypto.getCurves()`][] to obtain a
2033+
list of available elliptic curve names. For TLS group names, use
2034+
`openssl list -tls-groups` or consult the [IANA TLS Supported Groups
2035+
registry][].
20242036
**Default:** [`tls.DEFAULT_ECDH_CURVE`][].
20252037
* `honorCipherOrder` {boolean} Attempt to use the server's cipher suite
20262038
preferences instead of the client's. When `true`, causes
@@ -2435,9 +2447,9 @@ changes:
24352447
description: Default value changed to `'auto'`.
24362448
-->
24372449

2438-
The default curve name to use for ECDH key agreement in a tls server. The
2439-
default value is `'auto'`. See [`tls.createSecureContext()`][] for further
2440-
information.
2450+
The default named curve or TLS group list to use for key agreement in a TLS
2451+
server. The default value is `'auto'`. See [`tls.createSecureContext()`][] for
2452+
further information.
24412453

24422454
## `tls.DEFAULT_MAX_VERSION`
24432455

@@ -2485,6 +2497,7 @@ added: v0.11.3
24852497
[Chrome's 'modern cryptography' setting]: https://www.chromium.org/Home/chromium-security/education/tls#TOC-Cipher-Suites
24862498
[DHE]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
24872499
[ECDHE]: https://en.wikipedia.org/wiki/Elliptic_curve_Diffie%E2%80%93Hellman
2500+
[IANA TLS Supported Groups registry]: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8
24882501
[Modifying the default TLS cipher suite]: #modifying-the-default-tls-cipher-suite
24892502
[Mozilla's publicly trusted list of CAs]: https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt
24902503
[OCSP request]: https://en.wikipedia.org/wiki/OCSP_stapling

src/crypto/crypto_common.cc

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,13 +215,15 @@ MaybeLocal<Object> GetEphemeralKey(Environment* env, const SSLPointer& ssl) {
215215
Undefined(env->isolate()), // name
216216
Undefined(env->isolate()), // size
217217
};
218-
EVPKeyPointer key = ssl.getPeerTempKey();
218+
219+
bool found = false;
219220
if (EVPKeyPointer key = ssl.getPeerTempKey()) {
220221
int kid = key.id();
221222
switch (kid) {
222223
case EVP_PKEY_DH: {
223224
values[0] = env->dh_string();
224225
values[2] = Integer::New(env->isolate(), key.bits());
226+
found = true;
225227
break;
226228
}
227229
case EVP_PKEY_EC:
@@ -237,10 +239,17 @@ MaybeLocal<Object> GetEphemeralKey(Environment* env, const SSLPointer& ssl) {
237239
values[0] = env->ecdh_string();
238240
values[1] = OneByteString(env->isolate(), curve_name);
239241
values[2] = Integer::New(env->isolate(), key.bits());
242+
found = true;
240243
break;
241244
}
242245
}
243246
}
247+
if (!found) {
248+
if (auto name = ssl.getNegotiatedGroup()) {
249+
values[0] = env->tls_group_string();
250+
values[1] = OneByteString(env->isolate(), name.value());
251+
}
252+
}
244253

245254
return scope.EscapeMaybe(NewDictionaryInstance(env->context(), tmpl, values));
246255
}

src/env_properties.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@
363363
V(target_string, "target") \
364364
V(thread_id_string, "threadId") \
365365
V(thread_name_string, "threadName") \
366+
V(tls_group_string, "TLSGroup") \
366367
V(ticketkeycallback_string, "onticketkeycallback") \
367368
V(timeout_string, "timeout") \
368369
V(time_to_first_byte_string, "timeToFirstByte") \

test/parallel/test-tls-client-getephemeralkeyinfo.js

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ const tls = require('tls');
1818
const key = fixtures.readKey('agent2-key.pem');
1919
const cert = fixtures.readKey('agent2-cert.pem');
2020

21-
// TODO(@sam-github) test works with TLS1.3, rework test to add
22-
// 'ECDH' with 'TLS_AES_128_GCM_SHA256',
23-
2421
function loadDHParam(n) {
2522
return fixtures.readKey(`dh${n}.pem`);
2623
}
@@ -89,3 +86,57 @@ test(256, 'ECDH', 'prime256v1', 'ECDHE-RSA-AES256-GCM-SHA384');
8986
test(521, 'ECDH', 'secp521r1', 'ECDHE-RSA-AES256-GCM-SHA384');
9087
test(253, 'ECDH', 'X25519', 'ECDHE-RSA-AES256-GCM-SHA384');
9188
test(448, 'ECDH', 'X448', 'ECDHE-RSA-AES256-GCM-SHA384');
89+
90+
function testTLS13Group(size, type, name) {
91+
const options = {
92+
key,
93+
cert,
94+
ecdhCurve: name,
95+
minVersion: 'TLSv1.3',
96+
maxVersion: 'TLSv1.3',
97+
};
98+
99+
const server = tls.createServer(options, common.mustCall((conn) => {
100+
assert.strictEqual(conn.getEphemeralKeyInfo(), null);
101+
conn.end();
102+
}));
103+
104+
server.on('close', common.mustSucceed());
105+
106+
server.listen(0, common.mustCall(() => {
107+
const client = tls.connect({
108+
port: server.address().port,
109+
rejectUnauthorized: false,
110+
ecdhCurve: name,
111+
minVersion: 'TLSv1.3',
112+
maxVersion: 'TLSv1.3',
113+
}, common.mustCall(() => {
114+
const ekeyinfo = client.getEphemeralKeyInfo();
115+
assert.strictEqual(ekeyinfo.type, type);
116+
assert.strictEqual(ekeyinfo.size, size);
117+
assert.strictEqual(ekeyinfo.name, name);
118+
server.close();
119+
}));
120+
client.on('secureConnect', common.mustCall());
121+
}));
122+
}
123+
124+
testTLS13Group(253, 'ECDH', 'X25519');
125+
126+
if (hasOpenSSL(3, 5)) {
127+
const tls13Groups = [
128+
'MLKEM512',
129+
'MLKEM768',
130+
'MLKEM1024',
131+
'SecP256r1MLKEM768',
132+
'X25519MLKEM768',
133+
'SecP384r1MLKEM1024',
134+
];
135+
136+
if (hasOpenSSL(4, 0)) {
137+
tls13Groups.push('curveSM2');
138+
tls13Groups.push('curveSM2MLKEM768');
139+
}
140+
141+
tls13Groups.forEach((name) => testTLS13Group(undefined, 'TLSGroup', name));
142+
}

0 commit comments

Comments
 (0)