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, &params)) 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, &params)) 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();