Re: [PATCH v2] Add ssl_alt_cert_file/ssl_alt_key_file for dual RSA+ECDSA certificate support

From: Renaud Métrich <rmetrich(at)redhat(dot)com>
To: pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: Re: [PATCH v2] Add ssl_alt_cert_file/ssl_alt_key_file for dual RSA+ECDSA certificate support
Date: 2026-06-15 08:53:29
Message-ID: 10e87dcb-c46c-4baf-8b82-39a6ae9e87e9@redhat.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

To: pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: Re: [PATCH v2] Add ssl_alt_cert_file/ssl_alt_key_file for dual
RSA+ECDSA certificate support
In-Reply-To: <0cabf744-6d58-4bc4-817a-413c804cf61d(at)redhat(dot)com>

Hi Zsolt,

Thanks for the thorough review.  Here is v2 addressing all six issues
you raised.  The fixes are in separate commits so each one can be
reviewed independently:

v2-0001: original patch (unchanged from v1)
v2-0002: reject alternate certificate with same key type as primary (#5)
v2-0003: load full certificate chain for alternate certificate (#4)
v2-0004: load alternate certificates only for the default host context (#2)
v2-0005: add compatibility guards for LibreSSL and older OpenSSL (#6)
v2-0006: document ssl_alt_cert_file limitations (#3)
v2-0007: fix TLS 1.3 HelloRetryRequest with multiple certificate types (#1)
v2-0008: expand test coverage (same-type rejection, SIGHUP reload,
         single-cert regression, TLS 1.3 actually exercised)

Specific answers:

1) TLS 1.3 HelloRetryRequest: the root cause was using override=0 for
   the second certificate.  During HRR the ClientHello callback fires
   twice; on the second invocation the key type was already loaded from
   the first, so SSL_use_cert_and_key() refused to replace it. Fixed
   by always using override=1 — since we load a complete set from the
   SSL_CTX, replacing is always correct.  Added a TLS 1.3 connectivity
   test and removed the TLS 1.2 protocol restriction.

2) Global GUCs leaking into SNI contexts: added an is_default parameter
   to init_host_context().  Alt certs are now loaded only for the
   default host context (postgresql.conf), not for per-host entries from
   pg_hosts.conf.

3) Per-host SNI support: documented that ssl_alt_cert_file applies only
   to the default SSL configuration from postgresql.conf.

4) Full chain loading: switched from SSL_CTX_use_certificate_file() to
   SSL_CTX_use_certificate_chain_file(), matching how the primary
   certificate is loaded.

5) Same-type detection: after loading the alt cert, we iterate with
   SSL_CTX_set_current_cert(FIRST/NEXT) and compare
   EVP_PKEY_get_base_id() on both.  Produces a FATAL error if they
   match.

6) LibreSSL/older OpenSSL: guarded SSL_CTX_set_current_cert() and
   SSL_CERT_SET_FIRST/NEXT with #ifdef SSL_CERT_SET_FIRST.  When not
   available, ssl_alt_cert_file produces a clear error, ssl_update_ssl()
   falls back to the original single-cert copy, and cert types caching
   is skipped.  Verified by building with #undef SSL_CERT_SET_FIRST to
   exercise the fallback code paths.

Test results: 452/452 SSL tests pass (29 alt cert subtests including
TLS 1.3, same-type rejection, SIGHUP reload add/remove, single-cert
regression, plus the existing 423), no regressions.  RHEL 9.8 /
OpenSSL 3.5.5.

---

Looking ahead, I want to flag an alternative design direction worth
considering.  The ssl_alt_cert_file approach is inherently limited to
two certificates (primary + one alternate), but OpenSSL supports up to
three key types (RSA, ECDSA, EdDSA).  It also introduces new GUC names
that don't generalize well.

An approach that other projects have converged on is to make
ssl_cert_file and ssl_key_file multi-valued — httpd and nginx already
work this way, and I am doing the same for MariaDB [1] where we went
through a similar evolution: we started with --ssl-alt-cert, then
redesigned to allow repeated --ssl-cert/--ssl-key options and let
OpenSSL handle cert/key matching and type verification internally.

For PostgreSQL, since GUCs are strings, this could take the form of
comma-separated paths:

    ssl_cert_file = 'server-rsa.crt, server-ecdsa.crt'
    ssl_key_file  = 'server-rsa.key, server-ecdsa.key'

The server would load each pair via
SSL_CTX_use_certificate_chain_file() / SSL_CTX_use_PrivateKey_file()
and let OpenSSL sort out the type matching — no same-type detection
needed, no "alt" naming, and natural support for all three key types.
The ssl_update_ssl() fix from this patch (iterating all cert types)
would still be needed regardless.

I'm happy to go either direction — the current v2 is functional and
complete, but if the community prefers the multi-valued approach, I
can rework it.

[1] https://github.com/MariaDB/server/pull/5178

Thanks,
Renaud Métrich
Red Hat

Le 12/06/2026 à 10:39 PM, Zsolt Parragi a écrit :
> Hello!
>
> The problem the patch tries to solve is real, but I see several
> gaps/problems with the current implementation with some testing:
>
> 1. it seems to break TLS 1.3 HelloRetryRequest as it tries to add the
> second certificate with override=0. Connection then fails with "SSL
> error: tlsv1 alert internal error", server log shows "could not update
> certificate chain: not replacing certificate" / "failed to switch to
> SSL configuration for host, terminating connection"
>
> 2. The global ssl_alt_* GUCs are loaded into every pg_hosts context.
> If the SNI cert is a different type, it loads the alternative
> certificates as alternatives, if it's the same type, it replaces the
> hosts entry.
>
> 3. pg_hosts/SNI has no support for the new GUCs, there's no way to
> configure per host versions of the feature. Shouldn't the patch
> include proper support for SNI?
>
> 4. Shouldn't alternative certificates load the entire chain, not just
> the first block?
>
> 5. If both have the same type, the alternate certificate silently
> replaces the primary one. Shouldn't that result in a startup error
> instead?
>
> 6. Won't this cause build failure with LibreSSL, or older OpenSSL?
>
>

Attachment Content-Type Size
v2-0001-Add-ssl_alt_cert_file-ssl_alt_key_file-for-dual-R.patch text/x-patch 29.2 KB
v2-0002-Reject-alternate-certificate-with-same-key-type-a.patch text/x-patch 2.2 KB
v2-0003-Load-full-certificate-chain-for-alternate-certifi.patch text/x-patch 1.4 KB
v2-0004-Load-alternate-certificates-only-for-the-default-.patch text/x-patch 3.8 KB
v2-0005-Add-compatibility-guards-for-LibreSSL-and-older-O.patch text/x-patch 3.7 KB
v2-0006-Document-ssl_alt_cert_file-limitations.patch text/x-patch 1.5 KB
v2-0007-Fix-TLS-1.3-HelloRetryRequest-with-multiple-certi.patch text/x-patch 5.2 KB
v2-0008-Expand-test-coverage-for-dual-certificate-support.patch text/x-patch 6.5 KB

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Akshay Joshi 2026-06-15 08:55:06 Re: [PATCH] Add pg_get_table_ddl() to reconstruct CREATE TABLE statements
Previous Message Amit Kapila 2026-06-15 08:50:26 Re: Proposal: Conflict log history table for Logical Replication