Commit 6adf7c17225 for nodejs
commit 6adf7c172257a51eade45cca292334de9d2cd27e
Author: Filip Skokan <panva.ip@gmail.com>
Date: Tue Sep 8 00:17:57 2026 +0200
crypto: skip private RSA parameters in key details
Retrieving asymmetricKeyDetails only needs the modulus, public exponent,
and RSA-PSS restrictions. Add a public-only Rsa view so provider-backed
keys do not also extract private components or probe additional primes.
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66108
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
diff --git a/benchmark/crypto/rsa-key-details.js b/benchmark/crypto/rsa-key-details.js
new file mode 100644
index 00000000000..cb114226e30
--- /dev/null
+++ b/benchmark/crypto/rsa-key-details.js
@@ -0,0 +1,26 @@
+'use strict';
+
+const common = require('../common.js');
+const { KeyObject } = require('crypto');
+
+const bench = common.createBenchmark(main, {
+ type: ['public', 'private'],
+ n: [10000],
+});
+
+async function main({ type, n }) {
+ const pair = await crypto.subtle.generateKey({
+ name: 'RSA-PSS',
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([1, 0, 1]),
+ hash: 'SHA-256',
+ }, true, ['sign', 'verify']);
+ const cryptoKey = pair[`${type}Key`];
+ // Use a fresh KeyObject so each iteration retrieves uncached key details.
+ bench.start();
+ for (let index = 0; index < n; index++) {
+ if (KeyObject.from(cryptoKey).asymmetricKeyDetails.modulusLength !== 2048)
+ throw new Error('Unexpected modulus length');
+ }
+ bench.end(n);
+}
diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc
index ca1eb4653df..268875f6b36 100644
--- a/deps/ncrypto/ncrypto.cc
+++ b/deps/ncrypto/ncrypto.cc
@@ -6953,13 +6953,22 @@ ASN1StringPointer EncodeRsaPssParams(const Rsa::PssParams& params) {
Rsa::Rsa() : rsa_(false) {}
-Rsa::Rsa(const EVP_PKEY* pkey) : Rsa() {
+Rsa::Rsa(const EVP_PKEY* pkey, Selection selection) : Rsa() {
rsa_pss_ = EVPKeyPointer::isA(pkey, KeyAlgorithm::RSA_PSS);
if (!EVPKeyPointer::isA(pkey, KeyAlgorithm::RSA) && !rsa_pss_) return;
if (!GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_N, &n_) ||
!GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_E, &e_)) {
return;
}
+ if (rsa_pss_) {
+ MarkPopErrorOnReturn pop_errors;
+ PssParams params;
+ if (ReadRsaPssParams(pkey, ¶ms)) pss_params_ = params;
+ }
+ if (selection == Selection::Public) {
+ rsa_ = true;
+ return;
+ }
if (!GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_D, &d_) ||
!GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_FACTOR1, &p_) ||
!GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_FACTOR2, &q_) ||
@@ -6982,12 +6991,6 @@ Rsa::Rsa(const EVP_PKEY* pkey) : Rsa() {
other_prime_infos_.push_back(std::move(info));
}
- if (rsa_pss_) {
- MarkPopErrorOnReturn pop_errors;
- PssParams params;
- if (ReadRsaPssParams(pkey, ¶ms)) pss_params_ = params;
- }
-
rsa_ = true;
}
#else
@@ -6995,6 +6998,14 @@ Rsa::Rsa() : rsa_(nullptr) {}
Rsa::Rsa(OSSL3_CONST RSA* ptr) : rsa_(ptr) {}
#endif
+Rsa Rsa::PublicOnly(const EVPKeyPointer& key) {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ return Rsa(key.get(), Selection::Public);
+#else
+ return key;
+#endif
+}
+
const Rsa::PublicKey Rsa::getPublicKey() const {
#if NCRYPTO_USE_OPENSSL3_PROVIDER
if (!rsa_) return {};
diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h
index 0ffd664a1ea..13444e226b6 100644
--- a/deps/ncrypto/ncrypto.h
+++ b/deps/ncrypto/ncrypto.h
@@ -650,8 +650,10 @@ class Dsa final {
class Rsa final {
public:
Rsa();
+ enum class Selection { Public, Private };
+ static Rsa PublicOnly(const EVPKeyPointer& key);
#if NCRYPTO_USE_OPENSSL3_PROVIDER
- explicit Rsa(const EVP_PKEY* pkey);
+ explicit Rsa(const EVP_PKEY* pkey, Selection selection = Selection::Private);
#else
Rsa(OSSL3_CONST RSA* rsa);
#endif
diff --git a/src/crypto/crypto_rsa.cc b/src/crypto/crypto_rsa.cc
index 36b53a0dfa6..e3c594bfbc4 100644
--- a/src/crypto/crypto_rsa.cc
+++ b/src/crypto/crypto_rsa.cc
@@ -530,9 +530,7 @@ bool GetRsaKeyDetail(Environment* env,
Mutex::ScopedLock lock(key.mutex());
const auto& m_pkey = key.GetAsymmetricKey();
- // TODO(tniessen): Remove the "else" branch once we drop support for OpenSSL
- // versions older than 1.1.1e via FIPS / dynamic linking.
- const ncrypto::Rsa rsa = m_pkey;
+ const auto rsa = ncrypto::Rsa::PublicOnly(m_pkey);
if (!rsa) return false;
auto pub_key = rsa.getPublicKey();
@@ -566,15 +564,8 @@ bool GetRsaKeyDetail(Environment* env,
}
if (m_pkey.isA(KeyAlgorithm::RSA_PSS)) {
- // Due to the way ASN.1 encoding works, default values are omitted when
- // encoding the data structure. However, there are also RSA-PSS keys for
- // which no parameters are set. In that case, the ASN.1 RSASSA-PSS-params
- // sequence will be missing entirely and RSA_get0_pss_params will return
- // nullptr. If parameters are present but all parameters are set to their
- // default values, an empty sequence will be stored in the ASN.1 structure.
- // In that case, RSA_get0_pss_params does not return nullptr but all fields
- // of the returned RSA_PSS_PARAMS will be set to nullptr.
-
+ // An absent RSASSA-PSS-params sequence means the key is unrestricted.
+ // An empty sequence restricts the key to the default parameter values.
auto maybe_params = rsa.getPssParams();
if (maybe_params.has_value()) {
auto& params = maybe_params.value();