fix(ssl): verify-ca/verify-full chain completion + sslmode=prefer timeout fix#734
fix(ssl): verify-ca/verify-full chain completion + sslmode=prefer timeout fix#734
Conversation
REV Code Review — PR #734Reviewing PRAISE — Two-attempt chain completion algorithm
Security analysis: augmenting the intermediate list does not lower the trust bar — the chain must still terminate at a trust anchor in PRAISE —
|
REV Code Review — PR #734Reviewing PRAISE — Two-attempt chain completion algorithm
Security analysis: augmenting the intermediate list does not lower the trust bar — the chain must still terminate at a trust anchor in PRAISE —
|
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #734 +/- ##
==========================================
- Coverage 68.65% 68.38% -0.27%
==========================================
Files 46 46
Lines 31132 31265 +133
==========================================
+ Hits 21372 21379 +7
- Misses 9760 9886 +126 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
#712) rustls requires the full certificate chain in the TLS handshake. PostgreSQL by default sends only the leaf certificate; OpenSSL (psql) can complete the chain from the local trust store but rustls cannot, causing UnknownIssuer errors even when the correct CA cert is supplied via sslrootcert. Fix: NoCnVerifier (verify-ca) and new FullVerifier (verify-full) now carry the certs parsed from sslrootcert as extra_intermediates. When the initial verification fails with UnknownIssuer, a second attempt is made with those certs prepended to the intermediates slice. This covers the common case where sslrootcert is a bundle containing the CA and any intermediate certs, without requiring changes to the Postgres server configuration. FullVerifier replaces the previous make_tls_config_verify_full approach (which used ClientConfig::with_root_certificates and had no chain completion) with a custom ServerCertVerifier that performs full chain + hostname validation using WebPkiServerVerifier. fix(connection): sslmode=prefer timeout 2x configured value (#723) When connect_timeout is set and sslmode=prefer, tokio-postgres applies the timeout per attempt. The TLS probe and the plaintext fallback each consumed the full budget, making the total elapsed time ~2x the configured value. Fix: in the Prefer match arm, detect timeout errors from the TLS probe (ConnectionFailed with 'timeout' in the reason string) and propagate immediately without falling back. A timeout means the host is unreachable; falling back to plain would only time out again. Non-timeout TLS failures (server does not support SSL, handshake refused) still trigger the plain fallback as before. Adds is_timeout_error() helper for testability. Adds load_certs_as_intermediates() helper to load raw DER certs from a PEM file for use as intermediates.
0da6f6c to
b0d655c
Compare
Fixes
Closes #712 and #723.
#712 — verify-ca / verify-full fail with UnknownIssuer
Root cause
rustls's
WebPkiServerVerifierrequires the full certificate chain in the TLS handshake. PostgreSQL by default sends only the leaf cert (ssl_cert_filecontains only the server cert). OpenSSL/psql can complete the chain from the local trust store; rustls cannot.Fix
NoCnVerifier(verify-ca) and newFullVerifier(verify-full) now carry the certs parsed fromsslrootcertasextra_intermediates. When initial verification fails withUnknownIssuer, a second attempt prepends those certs to the intermediates slice. This covers the common case —sslrootcertis a bundle with the CA root and any intermediates — without requiring Postgres server reconfiguration.FullVerifierreplaces the previousClientConfig::with_root_certificatesapproach (which had no chain completion) with a customServerCertVerifierthat does full chain + hostname validation viaWebPkiServerVerifier.Workaround that still works: Configure Postgres with
ssl_cert_file = server-chain.crt(leaf + CA concatenated). Now the fix also works when you can't change the server.#723 — sslmode=prefer timeout ~2× configured value
Root cause
connect_timeoutis applied per attempt by tokio-postgres. Forsslmode=prefer, rpg tries TLS (full timeout), then falls back to plain (full timeout again). Host unreachable = 2× elapsed.Fix
In the
Prefermatch arm, detect timeout errors from the TLS probe (ConnectionFailedwith "timeout" in the reason) and propagate immediately. Timeout = host unreachable = no point in falling back. Non-timeout TLS failures (server doesn't support SSL, handshake error) still trigger the plain fallback.Changes
src/connection.rs:load_certs_as_intermediates()— load raw DER certs from PEM file for use as intermediatesis_timeout_error()— classify timeout errors (testable helper)NoCnVerifier— carriesextra_intermediates, two-attempt chain verificationFullVerifier— new struct, same chain completion but WITH hostname check (verify-full)make_tls_config_verify_full— usesFullVerifierinstead ofwith_root_certificatesSslMode::Prefermatch arm — propagates timeout immediatelyTest note
CI's connection-tests job SKIPs D5/D6 (verify-ca/verify-full) pending #712. Once this PR merges, those SKIPs can be removed and the tests should pass with the self-signed cert bundle at
/tmp/pg-tls-certs/srv-chain.crt.