fix: verify-ca incorrectly enforces hostname verification - #40
Conversation
Version suggestionBased on this PR's title (
This is informational only — no tag or release is created automatically yet. |
bd389df to
b66d733
Compare
build_tls_connector wrapped rustls::client::WebPkiServerVerifier for verify-ca, whose verify_server_cert unconditionally checks the hostname via verify_server_name with no way to opt out -- making verify-ca behave identically to verify-full. verify-ca is supposed to validate the certificate chain but skip hostname verification -- that's the entire distinction from verify-full, matching libpq sslmode=verify-ca semantics. Ported VerifyCaCertVerifier from the builtin driver's src-tauri/src/pool_manager.rs, which deliberately avoids WebPkiServerVerifier for this exact reason and instead calls rustls::client::verify_server_cert_signed_by_trust_anchor directly (never reads server_name at all). Proved the bug and the fix with a chain-valid cert whose hostname deliberately doesn't match the connection target -- rejected before this fix, accepted after -- while confirming a CA-untrusted cert is still correctly rejected, and verify-full still correctly rejects the same hostname mismatch. Rebasing onto main (after #35 and #37 merged) surfaced a real merge hazard: naively combining #38's verify-ca branch with #35's client-auth logic would have dropped client_auth entirely on the verify-ca path, always calling .with_no_client_auth() regardless of ssl_cert/ssl_key. Fixed during this rebase so verify-ca now attaches a configured client cert the same way verify-full and the default branch already do. Added build_tls_connector_verify_ca_attaches_a_configured_client_cert, checking ClientConfig::client_auth_cert_resolver.has_certs() (not just "builds successfully", which the buggy version also did) -- confirmed it fails against the naive merge and passes against this fix. Fixes #38.
b66d733 to
c381b3e
Compare
Rebase note: found and fixed an additional bug while resolving conflicts with #35/#37After #35 and #37 merged, rebasing this branch surfaced a real merge hazard beyond the textual conflicts: combining #35's client-cert (`client_auth`) logic with this PR's new `VerifyCaCertVerifier` branch would have silently dropped client-cert auth whenever `verify-ca` was configured together with `ssl_cert`/`ssl_key` — that branch called `.with_no_client_auth()` unconditionally, ignoring `client_auth` entirely. Fixed as part of this rebase (folded into the same commit, since it's fixing a bug my own conflict resolution would otherwise have introduced, not new scope): `verify-ca` now attaches a configured client cert the same way `verify-full` and the default branch already do. Added a regression test, `build_tls_connector_verify_ca_attaches_a_configured_client_cert`, which checks `ClientConfig::client_auth_cert_resolver.has_certs()` directly — deliberately not just "the connector builds successfully," since the buggy merge also built successfully (it just silently omitted the client cert). Confirmed via a scratch revert that the test fails against the naive merge and passes against the fix. PR description updated with the same details. |
Summary
ssl_mode=verify-cais supposed to validate the server's certificate chain against a CA but skip hostname verification — that's the entire distinction betweenverify-caandverify-full(matches libpq semantics).build_tls_connector'sverify-cabranch instead wrappedrustls::client::WebPkiServerVerifier, whoseverify_server_certunconditionally callsverify_server_nameafter chain validation, with no way to opt out. Soverify-cahere behaved identically toverify-full: a chain-valid cert with a mismatched hostname was incorrectly rejected.Fixes #38.
Proof
Added
verify_ca_cert_verifier_accepts_a_chain_valid_cert_with_mismatched_hostname— constructs the newVerifyCaCertVerifierfrom a real CA fixture and runs it against a real leaf cert issued by that CA, checked against a hostname deliberately different from the leaf's CN/SAN. Before this fix (probed manually against the oldWebPkiServerVerifier-based code), this scenario was rejected; after the fix, it's accepted.Companion tests confirm the fix doesn't weaken anything else:
verify_ca_cert_verifier_still_rejects_a_cert_from_an_untrusted_ca— chain validation is still enforced (a cert signed by a CA not in the root store is still rejected).verify-full's existingWebPkiServerVerifierpath is untouched — still correctly rejects hostname mismatches (not re-tested here since that behavior didn't change, but confirmed unaffected by code review).Fix
Ported
VerifyCaCertVerifierfrom the builtin driver'ssrc-tauri/src/pool_manager.rs, which deliberately avoidsWebPkiServerVerifierforverify-cafor exactly this reason (its own doc comment: "makes the 'skip hostname check' intent explicit, avoids double-verifying the chain, and prevents the fragile.or(Ok(...))error-recovery pattern"). It callsrustls::client::verify_server_cert_signed_by_trust_anchordirectly and never readsserver_nameat all.build_tls_connector'sverify-cabranch now constructs this verifier instead ofWebPkiServerVerifier;verify-fullis unchanged (it should check hostname, and does).Merge-time bug found and fixed while rebasing onto #35 + #37
Rebasing this branch onto
mainafter #35 (client-cert auth) and #37 (TLS-param pool key) merged surfaced a real hazard: naively combining #35'sclient_authhandling with this PR's newVerifyCaCertVerifierbranch would have silently dropped client-cert auth wheneververify-cawas combined withssl_cert/ssl_key— theverify-cabranch called.with_no_client_auth()unconditionally, regardless of whether a client cert was configured.Fixed so
verify-canow attaches a configured client cert the same wayverify-fulland the default branch already do. Addedbuild_tls_connector_verify_ca_attaches_a_configured_client_cert, which checksClientConfig::client_auth_cert_resolver.has_certs()— not just "the connector builds successfully," which the buggy merge also satisfied. Confirmed this test fails against the naive merge and passes against the fix.Test plan
cargo test --lib --bins— 104/104 passcargo clippy --all-targets -- -D warnings— cleancargo fmt --all -- --check— cleannpx markdownlint CHANGELOG.md— clean