Fetch digests explicitly for cryptohash with OpenSSL 3.0 and later

Started by Mark Atwood18 days ago5 messageshackers
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

won't retrysuccessCI history

This thread has been committed, so CI has stopped here. Anything below is the last result it produced.

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t253327
psql -h localhost -U postgres

Built from patchset v2 (message #2), August 08, 2026 at 08:02 AM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t253327_2 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t253327_2 && git checkout t253327_2

Patchset v2 (message #2) is on t253327_2

Jump to latest
#1Mark Atwood
mark@reviewcommit.com

The attached patch makes src/common/cryptohash_openssl.c select its digest with
EVP_MD_fetch() when building against OpenSSL 3.0 or newer.

cryptohash_openssl.c initializes the EVP_MD_CTX with the implicit static digest
objects (EVP_sha256() and friends), which do not deterministically dispatch
through a loaded provider. The patch fetches the digest by name, caches it in
the context, and frees it on teardown, so hashing is served by the active
provider. The digest type is fixed for the lifetime of the context, so the
fetch is done once. The implicit path is kept 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.

All of PostgreSQL's authentication hashing (SCRAM, md5) rides pg_cryptohash, so
it follows the active provider automatically. As a consequence md5
authentication depends on MD5 being offered by the provider, and is therefore
unavailable under FIPS. That is the intended behavior, and it matches the
reasoning already given for moving MD5 to EVP: otherwise PostgreSQL would
"cheat if FIPS is enabled because MD5 should not be authorized", whereas EVP
"allows us to rely on OpenSSL to control such restrictions" [1]/messages/by-id/20201106073434.GA4961@paquier.xyz. Every md5
hashing call site already fails closed, so a FIPS provider rejects md5 auth
rather than silently bypassing it. The patch documents this in
client-auth.sgml and points to scram-sha-256.

I checked what these paths resolve to at run time rather than assuming. The
probe program is attached as provider_probe.c; it makes no assertions, it
prints what OpenSSL reports via EVP_MD_get0_provider(). Build it with
"cc -o provider_probe provider_probe.c -lcrypto". On OpenSSL 3.0.13:

Q1 EVP_sha256() provider : LEGACY BUILT-IN (no provider)
Q2 ctx MD after DigestInit_ex(EVP_sha256()) : LEGACY BUILT-IN (no provider)
Q3 EVP_MD_fetch(NULL,"SHA256",NULL) : default
Q4a EVP_MD_fetch(legacy-only ctx,"SHA256") : FETCH FAILED
Q4b EVP_MD_fetch(legacy-only ctx,"MD5") : FETCH FAILED

Q1/Q2 are what the backend does today; Q3 is what the patch switches to. Q4 is
the control: with a library context holding only the legacy provider, the fetch
fails rather than silently falling back to a built-in. That is the behavior
that makes a FIPS provider's restrictions actually take effect.

Details:

* Against master, tested at 8b73ceb78f. It touches
src/common/cryptohash_openssl.c and doc/src/sgml/client-auth.sgml, 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. The
digests were also checked against the NIST known-answer vectors.

* No new regression tests. This replaces the implementation behind
pg_cryptohash without changing its behavior or API, and the existing SCRAM
and md5 coverage in src/test/authentication exercises it.

* Documentation: client-auth.sgml gains a note that md5 authentication is
unavailable when the provider does not offer MD5, as under FIPS, with a
pointer to scram-sha-256.

* No performance impact expected. The fetch is a provider lookup done once
per context, and these paths run at connection time and at hash-seed setup,
not in any tight loop.

* pgcrypto is deliberately out of scope. It exposes legacy ciphers (DES,
Blowfish, CAST5) that depend on OpenSSL's legacy provider and warrant a
separate discussion.

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/20201106073434.GA4961@paquier.xyz
[2]: /messages/by-id/20260805004805.1174492-1-mark@reviewcommit.com

--
Mark

Attachments:

v1-0002-Fetch-digests-explicitly-for-cryptohash-with-Open.patchtext/x-diff; charset=utf-8Download+68-1
provider_probe.ctext/x-csrc; charset=utf-8Download
#2Michael Paquier
michael@paquier.xyz
In reply to: Mark Atwood (#1)
Re: Fetch digests explicitly for cryptohash with OpenSSL 3.0 and later

On Wed, Aug 05, 2026 at 01:09:13PM -0700, Mark Atwood wrote:

cryptohash_openssl.c initializes the EVP_MD_CTX with the implicit static digest
objects (EVP_sha256() and friends), which do not deterministically dispatch
through a loaded provider. The patch fetches the digest by name, caches it in
the context, and frees it on teardown, so hashing is served by the active
provider. The digest type is fixed for the lifetime of the context, so the
fetch is done once. The implicit path is kept 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.

I was looking at this patch, and the switch to EVP_MD_fetch() makes
sense, based on this:
https://docs.openssl.org/3.0/man7/migration_guide/#programming

With this quote:
"If a library context is needed then all EVP_* digest functions that
return a const EVP_MD * such as EVP_sha256() should be replaced with a
call to EVP_MD_fetch(3). See "ALGORITHM FETCHING" in crypto(7)."

The code your agent has generated (because it's AI-generated) is not
really in line with the usual PostgreSQL format. The doc addition
does not bring much: the stance about MD5 and FIPS was already true:
we blocked MD5 computations in cryptohash_openssl.c before this
change, it's still the case after this change.

I have tested the attached with 3.5 (version installed on my system),
and some custom 3.0 and 1.1.1 (not interesting, legacy API) builds,
and that seems to work fine.
--
Michael

Attachments:

t253327_2
v2-0001-Fetch-digests-explicitly-for-cryptohash-with-Open.patchtext/plain; charset=us-asciiDownload+46-1
#3Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#2)
Re: Fetch digests explicitly for cryptohash with OpenSSL 3.0 and later

On Fri, Aug 07, 2026 at 04:50:59PM +0900, Michael Paquier wrote:

The code your agent has generated (because it's AI-generated) is not
really in line with the usual PostgreSQL format. The doc addition
does not bring much: the stance about MD5 and FIPS was already true:
we blocked MD5 computations in cryptohash_openssl.c before this
change, it's still the case after this change.

I have tested the attached with 3.5 (version installed on my system),
and some custom 3.0 and 1.1.1 (not interesting, legacy API) builds,
and that seems to work fine.

And applied this one on HEAD. I am looking at your two other
submissions, while on it. These also need a few edits..
--
Michael

#4Mark Atwood
mark@reviewcommit.com
In reply to: Mark Atwood (#1)
Re: Fetch digests explicitly for cryptohash with OpenSSL 3.0 and later

Correction: my rationale for this patch was wrong, and so was the probe I
attached.

Implicit static digests DO reach providers. evp_md_init_internal() sees
type->prov == NULL and re-fetches the MD by name, so EVP_sha256() passed to
EVP_DigestInit_ex ends up provider-backed. My probe missed that because
EVP_MD_CTX_get0_md() returns ctx->reqdigest, the MD passed in, not the one
used. Please disregard provider_probe.c.

The corrected probe is attached. With the default property query set to
provider=legacy, which has no SHA256, EVP_DigestInit_ex(ctx, EVP_sha256())
fails. It could not if that path were served by a built-in.

So this patch is not a bypass fix. What survives: the internal fetch
hardcodes libctx=NULL and propq="", so a non-default OSSL_LIB_CTX gets no
provider control and no property query is expressible, and a registered ENGINE
takes the legacy branch and bypasses providers outright. Control and clarity,
not bypass.

Patch 1 (HMAC via EVP_MAC) is unaffected. HMAC_CTX does the ipad/opad
construction in OpenSSL's own code and delegates only the digest, so a
provider's HMAC is genuinely never consulted.

Repost on the narrower basis, or withdraw? The same correction applies to the
channel binding patch [1]/messages/by-id/178596055550.1584328.5465570319340175126@reviewcommit.com.

[1]: /messages/by-id/178596055550.1584328.5465570319340175126@reviewcommit.com

--
Mark

Attachments:

corrected_provider_probe.ctext/x-csrc; charset=utf-8Download
#5Michael Paquier
michael@paquier.xyz
In reply to: Mark Atwood (#4)
Re: Fetch digests explicitly for cryptohash with OpenSSL 3.0 and later

On Tue, Aug 11, 2026 at 01:33:26PM -0700, Mark Atwood wrote:

Implicit static digests DO reach providers. evp_md_init_internal() sees
type->prov == NULL and re-fetches the MD by name, so EVP_sha256() passed to
EVP_DigestInit_ex ends up provider-backed. My probe missed that because
EVP_MD_CTX_get0_md() returns ctx->reqdigest, the MD passed in, not the one
used. Please disregard provider_probe.c.

The corrected probe is attached. With the default property query set to
provider=legacy, which has no SHA256, EVP_DigestInit_ex(ctx, EVP_sha256())
fails. It could not if that path were served by a built-in.

As b91f79cd08ab and its code stand, I don't see a need for a change:
it does not change the fact that EVP_MD_fetch() is the recommended API
over the static routines in terms of 3.0. If something is wrong, we
still have a full release cycle to find defects. You are right that
the commit message is wrong regarding the loaded providers, though..
Now there is nothing that we can do to edit the commit message.

Something that bugs me a bit is if we should do something for
pgcrypto. We use EVP_get_digestbyname(), which is not marked as
deprecated, but it's still just a OBJ_name_get(), that maps to the
EVP_sha*() deprecated in 3.0. Perhaps there is little justification
to update this code, or perhaps something will be interested in that..

Repost on the narrower basis, or withdraw? The same correction applies to the
channel binding patch [1].

You're sounding like an agent to me here overall in all these
threads.. Sorry if I'm wrong, but writing messages with one's own
words is still a good practice overall, because one still needs to
understand the code he/she submits to be able to argue about it. An
agent is a tool that should not do that for you, even if it can be of
some help regarding some of its aspects.
--
Michael