Skip to content

Commit 7e3d2d8

Browse files
tniessenRafaelGSS
authored andcommittedJun 19, 2023
doc,test: clarify behavior of DH generateKeys
The DiffieHellman class is an old and thin wrapper around certain OpenSSL functions, many of which are deprecated in OpenSSL 3.0. Because the Node.js API mirrors the OpenSSL API, it adopts some of its peculiarities, but the Node.js documentation does not properly reflect these. Most importantly, despite the documentation saying otherwise, diffieHellman.generateKeys() does not generate a new private key when one has already been set or generated. Based on the documentation alone, users may be led to misuse the API in a way that results in key reuse, which can have drastic negative consequences for subsequent operations that consume the shared secret. These design issues in this old API have been around for many years, and we are not currently aware of any misuse in the ecosystem that falls into the above scenario. Changing the behavior of the API would be a significant breaking change and is thus not appropriate for a security release (nor is it a goal.) The reported issue is treated as CWE-1068 (after a vast amount of uncertainty whether to treat it as a vulnerability at all), therefore, this change only updates the documentation to match the actual behavior. Tests are also added that demonstrate this particular oddity. Newer APIs exist that can be used for some, but not all, Diffie-Hellman operations (e.g., crypto.diffieHellman() that was added in 2020). We should keep modernizing crypto APIs, but that is a non-goal for this security release. The ECDH class mirrors the DiffieHellman class in many ways, but it does not appear to be affected by this particular peculiarity. In particular, ecdh.generateKeys() does appear to always generate a new private key. PR-URL: nodejs-private/node-private#426 Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> CVE-ID: CVE-2023-30590
1 parent 925e8f5 commit 7e3d2d8

File tree

2 files changed

+65
-1
lines changed

2 files changed

+65
-1
lines changed
 

‎doc/api/crypto.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -1060,12 +1060,17 @@ added: v0.5.0
10601060
* `encoding` {string} The [encoding][] of the return value.
10611061
* Returns: {Buffer | string}
10621062

1063-
Generates private and public Diffie-Hellman key values, and returns
1063+
Generates private and public Diffie-Hellman key values unless they have been
1064+
generated or computed already, and returns
10641065
the public key in the specified `encoding`. This key should be
10651066
transferred to the other party.
10661067
If `encoding` is provided a string is returned; otherwise a
10671068
[`Buffer`][] is returned.
10681069

1070+
This function is a thin wrapper around [`DH_generate_key()`][]. In particular,
1071+
once a private key has been generated or set, calling this function only updates
1072+
the public key but does not generate a new private key.
1073+
10691074
### `diffieHellman.getGenerator([encoding])`
10701075

10711076
<!-- YAML
@@ -1132,6 +1137,10 @@ Sets the Diffie-Hellman private key. If the `encoding` argument is provided,
11321137
to be a string. If no `encoding` is provided, `privateKey` is expected
11331138
to be a [`Buffer`][], `TypedArray`, or `DataView`.
11341139

1140+
This function does not automatically compute the associated public key. Either
1141+
[`diffieHellman.setPublicKey()`][] or [`diffieHellman.generateKeys()`][] can be
1142+
used to manually provide the public key or to automatically derive it.
1143+
11351144
### `diffieHellman.setPublicKey(publicKey[, encoding])`
11361145

11371146
<!-- YAML
@@ -6056,6 +6065,7 @@ See the [list of SSL OP Flags][] for details.
60566065
[Web Crypto API documentation]: webcrypto.md
60576066
[`BN_is_prime_ex`]: https://www.openssl.org/docs/man1.1.1/man3/BN_is_prime_ex.html
60586067
[`Buffer`]: buffer.md
6068+
[`DH_generate_key()`]: https://www.openssl.org/docs/man3.0/man3/DH_generate_key.html
60596069
[`DiffieHellmanGroup`]: #class-diffiehellmangroup
60606070
[`EVP_BytesToKey`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_BytesToKey.html
60616071
[`KeyObject`]: #class-keyobject
@@ -6092,6 +6102,7 @@ See the [list of SSL OP Flags][] for details.
60926102
[`crypto.webcrypto.subtle`]: webcrypto.md#class-subtlecrypto
60936103
[`decipher.final()`]: #decipherfinaloutputencoding
60946104
[`decipher.update()`]: #decipherupdatedata-inputencoding-outputencoding
6105+
[`diffieHellman.generateKeys()`]: #diffiehellmangeneratekeysencoding
60956106
[`diffieHellman.setPublicKey()`]: #diffiehellmansetpublickeypublickey-encoding
60966107
[`ecdh.generateKeys()`]: #ecdhgeneratekeysencoding-format
60976108
[`ecdh.setPrivateKey()`]: #ecdhsetprivatekeyprivatekey-encoding

‎test/parallel/test-crypto-dh.js

+53
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,56 @@ assert.throws(
202202
() => crypto.createDiffieHellman('', 'base64', generator),
203203
{ code: 'ERR_INVALID_ARG_TYPE' }
204204
));
205+
206+
{
207+
function unlessInvalidState(f) {
208+
try {
209+
return f();
210+
} catch (err) {
211+
if (err.code !== 'ERR_CRYPTO_INVALID_STATE') {
212+
throw err;
213+
}
214+
}
215+
}
216+
217+
function testGenerateKeysChangesKeys(setup, expected) {
218+
const dh = crypto.createDiffieHellman(size);
219+
setup(dh);
220+
const firstPublicKey = unlessInvalidState(() => dh.getPublicKey());
221+
const firstPrivateKey = unlessInvalidState(() => dh.getPrivateKey());
222+
dh.generateKeys();
223+
const secondPublicKey = dh.getPublicKey();
224+
const secondPrivateKey = dh.getPrivateKey();
225+
function changed(shouldChange, first, second) {
226+
if (shouldChange) {
227+
assert.notDeepStrictEqual(first, second);
228+
} else {
229+
assert.deepStrictEqual(first, second);
230+
}
231+
}
232+
changed(expected.includes('public'), firstPublicKey, secondPublicKey);
233+
changed(expected.includes('private'), firstPrivateKey, secondPrivateKey);
234+
}
235+
236+
// Both the private and the public key are missing: generateKeys() generates both.
237+
testGenerateKeysChangesKeys(() => {
238+
// No setup.
239+
}, ['public', 'private']);
240+
241+
// Neither key is missing: generateKeys() does nothing.
242+
testGenerateKeysChangesKeys((dh) => {
243+
dh.generateKeys();
244+
}, []);
245+
246+
// Only the public key is missing: generateKeys() generates only the public key.
247+
testGenerateKeysChangesKeys((dh) => {
248+
dh.setPrivateKey(Buffer.from('01020304', 'hex'));
249+
}, ['public']);
250+
251+
// The public key is outdated: generateKeys() generates only the public key.
252+
testGenerateKeysChangesKeys((dh) => {
253+
const oldPublicKey = dh.generateKeys();
254+
dh.setPrivateKey(Buffer.from('01020304', 'hex'));
255+
assert.deepStrictEqual(dh.getPublicKey(), oldPublicKey);
256+
}, ['public']);
257+
}

0 commit comments

Comments
 (0)
Please sign in to comment.