Skip to content

Commit 5991efa

Browse files
panvaaduh95
authored andcommitted
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: #67067 Signed-off-by: Filip Skokan <panva.ip@gmail.com> Assisted-by: Codex PR-URL: #67071 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 7b8589b commit 5991efa

3 files changed

Lines changed: 120 additions & 0 deletions

File tree

‎deps/ncrypto/ncrypto.cc‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2503,6 +2503,15 @@ DHPointer::CheckResult DHPointer::check() {
25032503
// through the provider instead of preserving the historical verifyError.
25042504
if (group_name_ != nullptr) return CheckResult::NONE;
25052505

2506+
// Like DH_check(), skip primality checks for groups OpenSSL recognizes from
2507+
// explicitly supplied parameters.
2508+
size_t group_name_len = 0;
2509+
if (dh_ &&
2510+
EVP_PKEY_get_group_name(dh_.get(), nullptr, 0, &group_name_len) == 1 &&
2511+
group_name_len > 0) {
2512+
return CheckResult::NONE;
2513+
}
2514+
25062515
DeleteFnPtr<BIGNUM, BN_free> p;
25072516
DeleteFnPtr<BIGNUM, BN_free> g;
25082517
DeleteFnPtr<BIGNUM, BN_free> q;

‎test/cctest/test_node_crypto.cc‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <openssl/core_names.h>
2323
#include <openssl/params.h>
2424
#include <openssl/provider.h>
25+
#include <openssl/rand.h>
2526
#endif
2627

2728
using ncrypto::Ec;
@@ -120,6 +121,55 @@ TEST(NodeCrypto, KeyAlgorithmNames) {
120121
}
121122

122123
#if NCRYPTO_USE_OPENSSL_PROVIDER
124+
TEST(NodeCrypto, DhNamedGroupCheckDoesNotRequireRandomness) {
125+
ncrypto::ClearErrorOnReturn clear_errors;
126+
ncrypto::DeleteFnPtr<OSSL_LIB_CTX, OSSL_LIB_CTX_free> libctx(
127+
OSSL_LIB_CTX_new());
128+
ASSERT_TRUE(libctx);
129+
ASSERT_EQ(
130+
RAND_set_DRBG_type(
131+
libctx.get(), "node-test-unavailable-rng", nullptr, nullptr, nullptr),
132+
1);
133+
OSSL_LIB_CTX* previous_libctx = OSSL_LIB_CTX_set0_default(libctx.get());
134+
auto restore_libctx = node::OnScopeLeave(
135+
[previous_libctx] { OSSL_LIB_CTX_set0_default(previous_libctx); });
136+
137+
// Primality checks need randomness. Known groups must skip those checks.
138+
unsigned char random_byte;
139+
ASSERT_NE(RAND_bytes_ex(libctx.get(), &random_byte, 1, 0), 1);
140+
ERR_clear_error();
141+
142+
using DH = ncrypto::DHPointer;
143+
for (const char* name :
144+
{"modp5", "modp14", "modp15", "modp16", "modp17", "modp18"}) {
145+
auto prime = DH::FindGroup(name);
146+
auto generator = DH::GetStandardGenerator();
147+
ASSERT_TRUE(prime);
148+
ASSERT_TRUE(generator);
149+
auto dh = DH::New(std::move(prime), std::move(generator));
150+
ASSERT_TRUE(dh);
151+
EXPECT_EQ(dh.check(), DH::CheckResult::NONE) << name;
152+
}
153+
154+
auto prime = DH::FindGroup("modp14");
155+
auto generator = DH::GetStandardGenerator();
156+
ASSERT_TRUE(prime);
157+
ASSERT_TRUE(generator);
158+
ASSERT_EQ(BN_add_word(prime.get(), 1), 1);
159+
auto composite = DH::New(std::move(prime), std::move(generator));
160+
ASSERT_TRUE(composite);
161+
EXPECT_EQ(composite.check(), DH::CheckResult::P_NOT_PRIME);
162+
163+
prime = DH::FindGroup("modp14");
164+
generator = DH::GetStandardGenerator();
165+
ASSERT_TRUE(prime);
166+
ASSERT_TRUE(generator);
167+
ASSERT_EQ(BN_add_word(generator.get(), 1), 1);
168+
auto other_generator = DH::New(std::move(prime), std::move(generator));
169+
ASSERT_TRUE(other_generator);
170+
EXPECT_EQ(other_generator.check(), DH::CheckResult::CHECK_FAILED);
171+
}
172+
123173
TEST(NodeCrypto, ProviderPkcs1PublicKeyImport) {
124174
ncrypto::ClearErrorOnReturn clear_errors;
125175
auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::RSA);
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
if (!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
const assert = require('node:assert');
8+
const crypto = require('node:crypto');
9+
const { hasFIPS, isBoringSSL } = require('../common/crypto');
10+
11+
if (isBoringSSL)
12+
common.skip('OpenSSL named-group validation');
13+
14+
const groups = ['modp14', 'modp15', 'modp16', 'modp17', 'modp18'];
15+
if (!hasFIPS(3))
16+
groups.unshift('modp5');
17+
18+
const privateKey = Buffer.from('01020304', 'hex');
19+
const generator = Buffer.from([2]);
20+
21+
for (const name of groups) {
22+
const peer = crypto.getDiffieHellman(name);
23+
const prime = peer.getPrime();
24+
const peerPublicKey = peer.generateKeys();
25+
26+
// SSH encodes positive integers with a leading zero when necessary.
27+
for (const [p, g] of [
28+
[prime, 2],
29+
[Buffer.concat([Buffer.from([0]), prime]), Buffer.from([0, 2])],
30+
]) {
31+
const dh = crypto.createDiffieHellman(p, g);
32+
assert.strictEqual(dh.verifyError, 0);
33+
assert.deepStrictEqual(dh.getPrime(), prime);
34+
assert.deepStrictEqual(dh.getGenerator(), generator);
35+
36+
dh.setPrivateKey(privateKey);
37+
const publicKey = dh.generateKeys();
38+
assert.deepStrictEqual(dh.generateKeys(), publicKey);
39+
assert.deepStrictEqual(dh.getPrivateKey(), privateKey);
40+
assert.deepStrictEqual(dh.computeSecret(peerPublicKey),
41+
peer.computeSecret(publicKey));
42+
}
43+
}
44+
45+
// A known modulus alone must not bypass validation of different parameters.
46+
{
47+
const prime = crypto.getDiffieHellman('modp14').getPrime();
48+
const notPrime = Buffer.from(prime);
49+
notPrime[notPrime.length - 1] &= 0xfe;
50+
assert.strictEqual(
51+
crypto.createDiffieHellman(notPrime, 2).verifyError &
52+
crypto.constants.DH_CHECK_P_NOT_PRIME,
53+
crypto.constants.DH_CHECK_P_NOT_PRIME);
54+
55+
const notGenerator = Buffer.from(prime);
56+
notGenerator[notGenerator.length - 1]--;
57+
assert.strictEqual(
58+
crypto.createDiffieHellman(prime, notGenerator).verifyError &
59+
crypto.constants.DH_NOT_SUITABLE_GENERATOR,
60+
crypto.constants.DH_NOT_SUITABLE_GENERATOR);
61+
}

0 commit comments

Comments
 (0)