Commit 3b209b2cd7f for nodejs
commit 3b209b2cd7f43a23f45fde746ce01215e82a3d75
Author: Filip Skokan <panva.ip@gmail.com>
Date: Wed Nov 4 10:29:22 2026 +0100
crypto: skip DH checks for recognized groups
Explicit DH parameters are recognized as named groups during OpenSSL
provider import, but DHPointer::check() only skips primality checks for
groups constructed by name. This makes createDiffieHellman() block for
seconds on standard MODP groups.
Query the imported key's group name to restore the legacy DH_check()
shortcut without changing key generation. Add a deterministic regression
test with randomness disabled and cover explicit and padded parameters
through the JavaScript API.
Fixes: https://github.com/nodejs/node/issues/67067
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/67071
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc
index 3991235fb29..42f5a68768f 100644
--- a/deps/ncrypto/ncrypto.cc
+++ b/deps/ncrypto/ncrypto.cc
@@ -2503,6 +2503,15 @@ DHPointer::CheckResult DHPointer::check() {
// through the provider instead of preserving the historical verifyError.
if (group_name_ != nullptr) return CheckResult::NONE;
+ // Like DH_check(), skip primality checks for groups OpenSSL recognizes from
+ // explicitly supplied parameters.
+ size_t group_name_len = 0;
+ if (dh_ &&
+ EVP_PKEY_get_group_name(dh_.get(), nullptr, 0, &group_name_len) == 1 &&
+ group_name_len > 0) {
+ return CheckResult::NONE;
+ }
+
DeleteFnPtr<BIGNUM, BN_free> p;
DeleteFnPtr<BIGNUM, BN_free> g;
DeleteFnPtr<BIGNUM, BN_free> q;
diff --git a/test/cctest/test_node_crypto.cc b/test/cctest/test_node_crypto.cc
index 447f4f93896..bbf06673431 100644
--- a/test/cctest/test_node_crypto.cc
+++ b/test/cctest/test_node_crypto.cc
@@ -22,6 +22,7 @@
#include <openssl/core_names.h>
#include <openssl/params.h>
#include <openssl/provider.h>
+#include <openssl/rand.h>
#endif
using ncrypto::Ec;
@@ -120,6 +121,55 @@ TEST(NodeCrypto, KeyAlgorithmNames) {
}
#if NCRYPTO_USE_OPENSSL_PROVIDER
+TEST(NodeCrypto, DhNamedGroupCheckDoesNotRequireRandomness) {
+ ncrypto::ClearErrorOnReturn clear_errors;
+ ncrypto::DeleteFnPtr<OSSL_LIB_CTX, OSSL_LIB_CTX_free> libctx(
+ OSSL_LIB_CTX_new());
+ ASSERT_TRUE(libctx);
+ ASSERT_EQ(
+ RAND_set_DRBG_type(
+ libctx.get(), "node-test-unavailable-rng", nullptr, nullptr, nullptr),
+ 1);
+ OSSL_LIB_CTX* previous_libctx = OSSL_LIB_CTX_set0_default(libctx.get());
+ auto restore_libctx = node::OnScopeLeave(
+ [previous_libctx] { OSSL_LIB_CTX_set0_default(previous_libctx); });
+
+ // Primality checks need randomness. Known groups must skip those checks.
+ unsigned char random_byte;
+ ASSERT_NE(RAND_bytes_ex(libctx.get(), &random_byte, 1, 0), 1);
+ ERR_clear_error();
+
+ using DH = ncrypto::DHPointer;
+ for (const char* name :
+ {"modp5", "modp14", "modp15", "modp16", "modp17", "modp18"}) {
+ auto prime = DH::FindGroup(name);
+ auto generator = DH::GetStandardGenerator();
+ ASSERT_TRUE(prime);
+ ASSERT_TRUE(generator);
+ auto dh = DH::New(std::move(prime), std::move(generator));
+ ASSERT_TRUE(dh);
+ EXPECT_EQ(dh.check(), DH::CheckResult::NONE) << name;
+ }
+
+ auto prime = DH::FindGroup("modp14");
+ auto generator = DH::GetStandardGenerator();
+ ASSERT_TRUE(prime);
+ ASSERT_TRUE(generator);
+ ASSERT_EQ(BN_add_word(prime.get(), 1), 1);
+ auto composite = DH::New(std::move(prime), std::move(generator));
+ ASSERT_TRUE(composite);
+ EXPECT_EQ(composite.check(), DH::CheckResult::P_NOT_PRIME);
+
+ prime = DH::FindGroup("modp14");
+ generator = DH::GetStandardGenerator();
+ ASSERT_TRUE(prime);
+ ASSERT_TRUE(generator);
+ ASSERT_EQ(BN_add_word(generator.get(), 1), 1);
+ auto other_generator = DH::New(std::move(prime), std::move(generator));
+ ASSERT_TRUE(other_generator);
+ EXPECT_EQ(other_generator.check(), DH::CheckResult::CHECK_FAILED);
+}
+
TEST(NodeCrypto, ProviderPkcs1PublicKeyImport) {
ncrypto::ClearErrorOnReturn clear_errors;
auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::RSA);
diff --git a/test/parallel/test-crypto-dh-known-groups.js b/test/parallel/test-crypto-dh-known-groups.js
new file mode 100644
index 00000000000..0ff0ba957a5
--- /dev/null
+++ b/test/parallel/test-crypto-dh-known-groups.js
@@ -0,0 +1,61 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const assert = require('node:assert');
+const crypto = require('node:crypto');
+const { hasFIPS, isBoringSSL } = require('../common/crypto');
+
+if (isBoringSSL)
+ common.skip('OpenSSL named-group validation');
+
+const groups = ['modp14', 'modp15', 'modp16', 'modp17', 'modp18'];
+if (!hasFIPS(3))
+ groups.unshift('modp5');
+
+const privateKey = Buffer.from('01020304', 'hex');
+const generator = Buffer.from([2]);
+
+for (const name of groups) {
+ const peer = crypto.getDiffieHellman(name);
+ const prime = peer.getPrime();
+ const peerPublicKey = peer.generateKeys();
+
+ // SSH encodes positive integers with a leading zero when necessary.
+ for (const [p, g] of [
+ [prime, 2],
+ [Buffer.concat([Buffer.from([0]), prime]), Buffer.from([0, 2])],
+ ]) {
+ const dh = crypto.createDiffieHellman(p, g);
+ assert.strictEqual(dh.verifyError, 0);
+ assert.deepStrictEqual(dh.getPrime(), prime);
+ assert.deepStrictEqual(dh.getGenerator(), generator);
+
+ dh.setPrivateKey(privateKey);
+ const publicKey = dh.generateKeys();
+ assert.deepStrictEqual(dh.generateKeys(), publicKey);
+ assert.deepStrictEqual(dh.getPrivateKey(), privateKey);
+ assert.deepStrictEqual(dh.computeSecret(peerPublicKey),
+ peer.computeSecret(publicKey));
+ }
+}
+
+// A known modulus alone must not bypass validation of different parameters.
+{
+ const prime = crypto.getDiffieHellman('modp14').getPrime();
+ const notPrime = Buffer.from(prime);
+ notPrime[notPrime.length - 1] &= 0xfe;
+ assert.strictEqual(
+ crypto.createDiffieHellman(notPrime, 2).verifyError &
+ crypto.constants.DH_CHECK_P_NOT_PRIME,
+ crypto.constants.DH_CHECK_P_NOT_PRIME);
+
+ const notGenerator = Buffer.from(prime);
+ notGenerator[notGenerator.length - 1]--;
+ assert.strictEqual(
+ crypto.createDiffieHellman(prime, notGenerator).verifyError &
+ crypto.constants.DH_NOT_SUITABLE_GENERATOR,
+ crypto.constants.DH_NOT_SUITABLE_GENERATOR);
+}