Commit 1eaaeee72c2 for nodejs

commit 1eaaeee72c288766d5e52c7a90a757f397dfdb5d
Author: Yagiz Nizipli <yagiz@nizipli.com>
Date:   Thu Oct 29 11:21:04 2026 -0400

    crypto: reuse HMAC contexts in createHmac()

    Every HMAC context was created with EVP_MAC_fetch() and keyed with the
    digest passed by name, so the provider fetched the digest again on
    each init. Both lookups go through OpenSSL's method store.

    Keep an HMAC context without a key for each digest in the
    per-Environment digest cache, and duplicate it in createHmac(). A
    duplicated context already has its digest, so keying it with the same
    digest no longer fetches anything.

    Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
    Assisted-by: Grok
    PR-URL: https://github.com/nodejs/node/pull/67109
    Reviewed-By: Filip Skokan <panva.ip@gmail.com>

diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc
index ba0e1f2e09d..f0940bb38f5 100644
--- a/deps/ncrypto/ncrypto.cc
+++ b/deps/ncrypto/ncrypto.cc
@@ -5506,16 +5506,39 @@ DigestCache::Result DigestCache::insert(const char* name,
 #endif
 }

+DigestCache::~DigestCache() = default;
+
 void DigestCache::reset(uint64_t generation) {
 #if NCRYPTO_USE_OPENSSL_PROVIDER
   if (generation_ == generation) return;
   aliases_.clear();
+  hmac_templates_.clear();
   digests_.clear();
   first_id_ = next_id_;
 #endif
   generation_ = generation;
 }

+#if NCRYPTO_USE_OPENSSL_PROVIDER
+const HMACCtxPointer* DigestCache::getHmacTemplate(const EVP_MD* digest,
+                                                   uint64_t generation) {
+  if (generation_ != generation || digest == nullptr) return nullptr;
+  for (size_t index = 0; index < digests_.size(); index++) {
+    if (digests_[index].get() != digest) continue;
+    if (hmac_templates_.size() <= index) {
+      hmac_templates_.resize(digests_.size());
+    }
+    if (!hmac_templates_[index]) {
+      auto ctx = HMACCtxPointer::New(Digest::Ref(digest));
+      if (!ctx) return nullptr;
+      hmac_templates_[index] = std::make_unique<HMACCtxPointer>(std::move(ctx));
+    }
+    return hmac_templates_[index].get();
+  }
+  return nullptr;
+}
+#endif
+
 const DigestCache::AliasMap& DigestCache::aliases() const {
 #if NCRYPTO_USE_OPENSSL_PROVIDER
   return aliases_;
@@ -8584,7 +8607,9 @@ HMACCtxPointer::HMACCtxPointer(EVPMacPointer&& mac, EVPMacCtxPointer&& ctx)
 HMACCtxPointer::HMACCtxPointer(HMACCtxPointer&& other) noexcept
     : mac_(std::move(other.mac_)),
       ctx_(std::move(other.ctx_)),
+      md_(other.md_),
       md_size_(other.md_size_) {
+  other.md_ = nullptr;
   other.md_size_ = 0;
 }

@@ -8592,7 +8617,9 @@ HMACCtxPointer& HMACCtxPointer::operator=(HMACCtxPointer&& other) noexcept {
   if (this == &other) return *this;
   mac_ = std::move(other.mac_);
   ctx_ = std::move(other.ctx_);
+  md_ = other.md_;
   md_size_ = other.md_size_;
+  other.md_ = nullptr;
   other.md_size_ = 0;
   return *this;
 }
@@ -8604,11 +8631,13 @@ HMACCtxPointer::~HMACCtxPointer() {
 void HMACCtxPointer::reset() {
   ctx_.reset();
   mac_.reset();
+  md_ = nullptr;
   md_size_ = 0;
 }

 bool HMACCtxPointer::init(const Buffer<const void>& buf, const Digest& md) {
   if (!ctx_ || !md) return false;
+  if (md.get() == md_) return ctx_.init(buf);

   const char* md_name = EVP_MD_get0_name(md);
   if (md_name == nullptr) return false;
@@ -8620,6 +8649,7 @@ bool HMACCtxPointer::init(const Buffer<const void>& buf, const Digest& md) {
   };

   if (!ctx_.init(buf, params)) return false;
+  md_ = md.get();
   md_size_ = md.size();
   return true;
 }
@@ -8651,6 +8681,16 @@ bool HMACCtxPointer::digestInto(Buffer<void>* buf) {
   return true;
 }

+HMACCtxPointer HMACCtxPointer::clone() const {
+  if (!ctx_) return {};
+  EVPMacCtxPointer ctx(EVP_MAC_CTX_dup(ctx_.get()));
+  if (!ctx) return {};
+  HMACCtxPointer result(EVPMacPointer(), std::move(ctx));
+  result.md_ = md_;
+  result.md_size_ = md_size_;
+  return result;
+}
+
 HMACCtxPointer HMACCtxPointer::New() {
   auto mac = EVPMacPointer::Fetch(OSSL_MAC_NAME_HMAC);
   if (!mac) return {};
@@ -8660,6 +8700,25 @@ HMACCtxPointer HMACCtxPointer::New() {

   return HMACCtxPointer(std::move(mac), std::move(ctx));
 }
+
+HMACCtxPointer HMACCtxPointer::New(const Digest& md) {
+  if (!md) return {};
+  const char* md_name = EVP_MD_get0_name(md);
+  if (md_name == nullptr) return {};
+
+  auto result = New();
+  if (!result) return {};
+
+  OSSL_PARAM params[] = {
+      OSSL_PARAM_construct_utf8_string(
+          OSSL_MAC_PARAM_DIGEST, const_cast<char*>(md_name), 0),
+      OSSL_PARAM_construct_end(),
+  };
+  if (EVP_MAC_CTX_set_params(result.ctx_.get(), params) != 1) return {};
+  result.md_ = md.get();
+  result.md_size_ = md.size();
+  return result;
+}
 #endif  // OPENSSL_WITH_EVP_MAC

 DataPointer hashDigest(const Buffer<const unsigned char>& buf,
diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h
index adafe98b307..7b972258ad7 100644
--- a/deps/ncrypto/ncrypto.h
+++ b/deps/ncrypto/ncrypto.h
@@ -307,6 +307,7 @@ class DataPointer;
 class DHPointer;
 class ECKeyPointer;
 class EVPKeyPointer;
+class HMACCtxPointer;
 class KeyAlgorithm;
 class MacCache;
 class EVPMacCtxPointer;
@@ -403,6 +404,7 @@ class DigestCache final {
                                       CaseInsensitiveNameEqual>;

   DigestCache() = default;
+  ~DigestCache();
   NCRYPTO_DISALLOW_COPY_AND_MOVE(DigestCache)

   Result lookup(const char* name, uint64_t generation) const;
@@ -425,6 +427,12 @@ class DigestCache final {
   Result insert(const char* name, const EVP_MD* digest, uint64_t generation);
   void reset(uint64_t generation);
   const AliasMap& aliases() const;
+#if NCRYPTO_USE_OPENSSL_PROVIDER
+  // Returns an HMAC context for the cached digest that has no key yet.
+  // Duplicating it avoids fetching HMAC and the digest for every context.
+  const HMACCtxPointer* getHmacTemplate(const EVP_MD* digest,
+                                        uint64_t generation);
+#endif

  private:
   uint64_t generation_ = 0;
@@ -436,6 +444,7 @@ class DigestCache final {
   uint32_t first_id_ = 0;
   uint32_t next_id_ = 0;
   std::vector<EVPMDPointer> digests_;
+  std::vector<std::unique_ptr<HMACCtxPointer>> hmac_templates_;
   AliasMap aliases_;
 #endif
 };
@@ -2151,14 +2160,19 @@ class HMACCtxPointer final {
   bool update(const Buffer<const void>& buf);
   DataPointer digest();
   bool digestInto(Buffer<void>* buf);
+  HMACCtxPointer clone() const;

   static HMACCtxPointer New();
+  // Creates a context that already uses md, so that init() does not have to
+  // fetch md again when it is called with the same digest.
+  static HMACCtxPointer New(const Digest& md);

  private:
   HMACCtxPointer(EVPMacPointer&& mac, EVPMacCtxPointer&& ctx);

   EVPMacPointer mac_;
   EVPMacCtxPointer ctx_;
+  const EVP_MD* md_ = nullptr;
   size_t md_size_ = 0;
 };
 #endif  // OPENSSL_WITH_EVP_MAC
diff --git a/src/crypto/crypto_hash.cc b/src/crypto/crypto_hash.cc
index 492abc7c722..b0ea75e88b2 100644
--- a/src/crypto/crypto_hash.cc
+++ b/src/crypto/crypto_hash.cc
@@ -382,6 +382,18 @@ const ncrypto::Digest GetDigestForJob(Environment* env,
   return ncrypto::Digest::FromName(name);
 }

+ncrypto::HMACCtxPointer NewHmacContext(Environment* env,
+                                       const ncrypto::Digest& md) {
+#if NCRYPTO_USE_OPENSSL_PROVIDER
+  SynchronizeHashCache(env);
+  if (const auto* hmac_template = env->provider_digest_cache->getHmacTemplate(
+          md, env->hash_cache_generation)) {
+    if (auto ctx = hmac_template->clone()) return ctx;
+  }
+#endif
+  return ncrypto::HMACCtxPointer::New();
+}
+
 void MarkInvalidXofLength() {
 #if NCRYPTO_USE_OPENSSL_PROVIDER
   ERR_raise(ERR_LIB_EVP, EVP_R_NOT_XOF_OR_INVALID_LENGTH);
diff --git a/src/crypto/crypto_hash.h b/src/crypto/crypto_hash.h
index 7aaac944786..322ff83da69 100644
--- a/src/crypto/crypto_hash.h
+++ b/src/crypto/crypto_hash.h
@@ -24,6 +24,12 @@ const ncrypto::Digest GetDigestForJob(Environment* env,
                                       CryptoJobMode mode,
                                       const char* name);

+// Returns an HMAC context to be keyed with md. Contexts for digests from
+// GetDigest() are duplicated from a cached template, so keying them does not
+// fetch HMAC or md again.
+ncrypto::HMACCtxPointer NewHmacContext(Environment* env,
+                                       const ncrypto::Digest& md);
+
 class Hash final : public BaseObject {
  public:
   static void Initialize(Environment* env, v8::Local<v8::Object> target);
diff --git a/src/crypto/crypto_hmac.cc b/src/crypto/crypto_hmac.cc
index 36f07c7d98d..8345b68bc90 100644
--- a/src/crypto/crypto_hmac.cc
+++ b/src/crypto/crypto_hmac.cc
@@ -79,7 +79,7 @@ void Hmac::HmacInit(const char* hash_type, const char* key, int key_len) {
     key = "";
   }

-  ctx_ = HMACCtxPointer::New();
+  ctx_ = NewHmacContext(env(), md);
   ncrypto::Buffer<const void> key_buf{
       .data = key,
       .len = static_cast<size_t>(key_len),