Use EVP_MAC for HMAC with OpenSSL 3.0 and later
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]/messages/by-id/X9m0nkEJEzIPXjeZ@paquier.xyz. 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]/messages/by-id/20260805004805.1174492-1-mark@reviewcommit.com.
Reposting as separate threads with the patch attached, per review request.
Intended for the next commitfest.
[1]: /messages/by-id/X9m0nkEJEzIPXjeZ@paquier.xyz
[2]: /messages/by-id/20260805004805.1174492-1-mark@reviewcommit.com
--
Mark
Attachments:
v1-0001-Use-EVP_MAC-for-HMAC-with-OpenSSL-3.0-and-later.patchtext/x-diff; charset=utf-8Download+92-1
On Wed, Aug 05, 2026 at 01:09:11PM -0700, Mark Atwood wrote:
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).
This patch was not that bad. The amount of code duplicated in
pg_hmac_create() for the error handling was just there for
EVP_MAC_free(), so I have added an extra #ifdef for it before throwing
an error on OOM.
OSSL_PARAM_construct_utf8_string() has a couple of alternatives, like
using a pointer. Now, looking at the code and some demo code, mainly
hmac-sha512.c in upstream, that's the right alternative.
The change in pg_hmac_final() was shaped weirdly, tweaked it to make
it more consistent.
And after testing that across various OpenSSL versions down to 1.1.1,
the result was OK, so applied on HEAD. Now that's two. Still need to
look at the channel binding piece.
--
Michael