The attached patch switches src/common/hmac_openssl.c to the EVP_MAC API when
building against OpenSSL 3.0 or newer.
The legacy HMAC_CTX interface (HMAC_CTX_new, HMAC_Init_ex, HMAC_Update,
HMAC_Final) has been deprecated since 3.0, and it does not dispatch through the
provider framework, so a loaded provider's HMAC implementation is bypassed.
The patch fetches "HMAC" with EVP_MAC_fetch(), creates an EVP_MAC_CTX, and
selects the digest through an OSSL_PARAM. The HMAC_CTX path is retained for
older OpenSSL and for LibreSSL, guarded by OPENSSL_VERSION_NUMBER >=
0x30000000L.
The fetch uses the default library context and a NULL property query, so no
dependency is added and no particular provider is required; the provider is
selected by the usual OpenSSL configuration (openssl.cnf).
When the pg_hmac abstraction was built, EVP_MAC was set aside deliberately --
"a bit too new to use though, as we need to support OpenSSL down to 1.0.1 on
HEAD ... So instead I have decided to rely on the older interface based on
HMAC_Init_ex()" [1]. That constraint no longer holds: the minimum supported
OpenSSL is now 1.1.1, and the version guard confines this change to 3.0+.
Dropping use of an interface deprecated in 3.0 also stands on its own,
independent of provider routing.
SCRAM authentication does no crypto of its own -- it rides pg_hmac -- so it
follows the active provider once the backend does.
I checked what this path resolves to at run time using
EVP_MAC_get0_provider(). On OpenSSL 3.0.13:
legacy HMAC_CTX / HMAC_Init_ex : legacy low-level API, not
provider-dispatched
EVP_MAC_fetch(NULL,"HMAC",NULL) : default
Details:
* Against master, tested at 8b73ceb78f. It touches only
src/common/hmac_openssl.c and applies on its own; there is no dependency on
the two related patches I am posting in separate threads.
* Built and tested with OpenSSL 3.0.13 on Ubuntu 24.04 (x86-64): clean build,
src/test/regress, src/test/ssl and src/test/authentication all pass. HMAC
output was also checked against the RFC 4231 test vectors.
* No new regression tests. This replaces the implementation behind pg_hmac
without changing its behavior or API, and the existing SCRAM coverage in
src/test/authentication exercises it.
* No documentation change.
* No performance impact expected. EVP_MAC_fetch() does a provider lookup,
but the fetched object is cached in the context, and this path runs at
connection time rather than in any tight loop.
This was previously posted as a three-patch series in a single thread [2].
Reposting as separate threads with the patch attached, per review request.
Intended for the next commitfest.
[1] https://postgr.es/m/[email protected]
[2] https://postgr.es/m/20260805004805.1174492-1-mark%40reviewcommit.com
--
Mark
>From 8e1ca4424ec71aa54fc37e494fcea1cb14e631a1 Mon Sep 17 00:00:00 2001
From: Mark Atwood <[email protected]>
Date: Fri, 24 Jul 2026 11:21:52 -0700
Subject: [PATCH v1 1/3] Use EVP_MAC for HMAC with OpenSSL 3.0 and later
The legacy HMAC_CTX interface (HMAC_CTX_new, HMAC_Init_ex, HMAC_Update,
HMAC_Final) has been deprecated since OpenSSL 3.0, and it does not
dispatch through the provider framework, so a loaded provider's HMAC
implementation is bypassed.
Switch hmac_openssl.c to the EVP_MAC API when building against OpenSSL
3.0 or newer: fetch "HMAC" with EVP_MAC_fetch(), create an EVP_MAC_CTX,
and select the digest through an OSSL_PARAM. The HMAC_CTX path is
retained for older OpenSSL and for LibreSSL.
This lets a third-party or FIPS provider service PostgreSQL's HMAC
(notably SCRAM authentication, which is built on pg_hmac), and removes
use of a deprecated interface.
---
src/common/hmac_openssl.c | 92 +++++++++++++++++++++++++++++++++++++++
1 file changed, 92 insertions(+)
diff --git a/src/common/hmac_openssl.c b/src/common/hmac_openssl.c
index 7990822854..6dec73fd9b 100644
--- a/src/common/hmac_openssl.c
+++ b/src/common/hmac_openssl.c
@@ -22,7 +22,13 @@
#include <openssl/err.h>
+#include <openssl/evp.h>
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+#include <openssl/core_names.h>
+#include <openssl/params.h>
+#else
#include <openssl/hmac.h>
+#endif
#include "common/hmac.h"
#include "common/md5.h"
@@ -58,7 +64,12 @@ typedef enum pg_hmac_errno
/* Internal pg_hmac_ctx structure */
struct pg_hmac_ctx
{
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ EVP_MAC *mac;
+ EVP_MAC_CTX *hmacctx;
+#else
HMAC_CTX *hmacctx;
+#endif
pg_cryptohash_type type;
pg_hmac_errno error;
const char *errreason;
@@ -139,6 +150,32 @@ pg_hmac_create(pg_cryptohash_type type)
ResourceOwnerEnlarge(CurrentResourceOwner);
#endif
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+
+ /*
+ * On OpenSSL 3.0 and newer, use the EVP_MAC API so that the HMAC
+ * implementation is served by the loaded provider, rather than the
+ * deprecated HMAC_CTX interface which bypasses provider dispatch. This
+ * lets a third-party or FIPS provider service PostgreSQL's HMAC. The
+ * digest is selected later, in pg_hmac_init().
+ */
+ ctx->mac = EVP_MAC_fetch(NULL, "HMAC", NULL);
+ if (ctx->mac != NULL)
+ ctx->hmacctx = EVP_MAC_CTX_new(ctx->mac);
+
+ if (ctx->hmacctx == NULL)
+ {
+ EVP_MAC_free(ctx->mac);
+ explicit_bzero(ctx, sizeof(pg_hmac_ctx));
+ FREE(ctx);
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+ }
+#else
ctx->hmacctx = HMAC_CTX_new();
if (ctx->hmacctx == NULL)
@@ -152,6 +189,7 @@ pg_hmac_create(pg_cryptohash_type type)
#endif
return NULL;
}
+#endif
#ifdef USE_RESOWNER_FOR_HMAC
@@ -175,6 +213,40 @@ pg_hmac_init(pg_hmac_ctx *ctx, const uint8 *key, size_t len)
if (ctx == NULL)
return -1;
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ {
+ const char *digest = NULL;
+ OSSL_PARAM params[2];
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ digest = "MD5";
+ break;
+ case PG_SHA1:
+ digest = "SHA1";
+ break;
+ case PG_SHA224:
+ digest = "SHA224";
+ break;
+ case PG_SHA256:
+ digest = "SHA256";
+ break;
+ case PG_SHA384:
+ digest = "SHA384";
+ break;
+ case PG_SHA512:
+ digest = "SHA512";
+ break;
+ }
+
+ params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST,
+ unconstify(char *, digest), 0);
+ params[1] = OSSL_PARAM_construct_end();
+
+ status = EVP_MAC_init(ctx->hmacctx, key, len, params);
+ }
+#else
switch (ctx->type)
{
case PG_MD5:
@@ -196,6 +268,7 @@ pg_hmac_init(pg_hmac_ctx *ctx, const uint8 *key, size_t len)
status = HMAC_Init_ex(ctx->hmacctx, key, len, EVP_sha512(), NULL);
break;
}
+#endif
/* OpenSSL internals return 1 on success, 0 on failure */
if (status <= 0)
@@ -221,7 +294,11 @@ pg_hmac_update(pg_hmac_ctx *ctx, const uint8 *data, size_t len)
if (ctx == NULL)
return -1;
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ status = EVP_MAC_update(ctx->hmacctx, data, len);
+#else
status = HMAC_Update(ctx->hmacctx, data, len);
+#endif
/* OpenSSL internals return 1 on success, 0 on failure */
if (status <= 0)
@@ -242,7 +319,9 @@ int
pg_hmac_final(pg_hmac_ctx *ctx, uint8 *dest, size_t len)
{
int status = 0;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
uint32 outlen;
+#endif
if (ctx == NULL)
return -1;
@@ -293,7 +372,15 @@ pg_hmac_final(pg_hmac_ctx *ctx, uint8 *dest, size_t len)
break;
}
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ {
+ size_t outlen;
+
+ status = EVP_MAC_final(ctx->hmacctx, dest, &outlen, len);
+ }
+#else
status = HMAC_Final(ctx->hmacctx, dest, &outlen);
+#endif
/* OpenSSL internals return 1 on success, 0 on failure */
if (status <= 0)
@@ -316,7 +403,12 @@ pg_hmac_free(pg_hmac_ctx *ctx)
if (ctx == NULL)
return;
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ EVP_MAC_CTX_free(ctx->hmacctx);
+ EVP_MAC_free(ctx->mac);
+#else
HMAC_CTX_free(ctx->hmacctx);
+#endif
#ifdef USE_RESOWNER_FOR_HMAC
if (ctx->resowner)
ResourceOwnerForgetHMAC(ctx->resowner, ctx);
--
2.43.0