Summary
build_tls_connector's own doc comment states: "require forces TLS without certificate validation (matches the builtin driver's require behavior — see src-tauri/src/pool_manager.rs)." The code doesn't do this.
needs_cert_validation only matches "verify-ca" | "verify-full" — require is excluded. So for ssl_mode=require, execution falls through the if needs_cert_validation { ... } block entirely and lands on the final fallback:
let builder = rustls::ClientConfig::builder()
.with_platform_verifier()
.map_err(|e| format!("Failed to build platform TLS verifier: {e}"))?;
with_platform_verifier() does validate the server's certificate against the OS trust store — the opposite of "without certificate validation." So require currently behaves like verify-full (full platform-trust validation) instead of like disable/allow/prefer (no validation, just opportunistic/forced TLS).
Builtin's actual behavior (for reference)
src-tauri/src/pool_manager.rs's build_postgres_tls_connector:
"require" => {
// Force TLS, skip cert validation.
let verifier = Arc::new(NoCertVerifier::new());
ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(verifier)
.with_no_client_auth()
}
require uses the same NoCertVerifier (accepts any certificate, no validation at all) as disable/allow/prefer — the only thing that changes between those modes and require is whether TLS is attempted at all (handled by SslMode, see #43), not whether the cert is checked.
Impact / reproduction
Found while manually verifying the #43 fix against a real SSL-enabled PostgreSQL instance with a self-signed certificate:
echo '{"jsonrpc":"2.0","id":1,"method":"test_connection","params":{"params":{"host":"127.0.0.1","port":55432,"username":"postgres","password":"password","database":"testdb","ssl_mode":"require"}}}' \
| ./target/debug/postgresql-plugin
Result: {"error":{"code":-32603,"message":"Connection failed: Error occurred while creating a new object: error performing TLS handshake"}}
Expected (per the doc comment and the builtin's behavior): require should connect successfully — the whole point of require mode (vs. verify-full) is "encrypt the connection, but don't bother validating who's on the other end" — the standard use case being self-signed certs, private CAs the user hasn't configured ssl_ca for, or any server the user trusts by network topology rather than PKI.
Discovery context
Confirmed via git log/git show 9986b1e that this bug has been present since client.rs was first staged (#3) — not a regression from #35/#37/#40/#41/#42, and distinct from #43 (different root cause: cert validation logic, not protocol-level SslMode enforcement), even though both live in build_tls_connector/TLS setup and were found back-to-back while testing #43.
Proposed fix
Add a no-op/accept-all certificate verifier (mirroring the builtin's NoCertVerifier) and route require mode to it instead of falling through to with_platform_verifier(). Something like:
let needs_cert_validation = matches!(
params.ssl_mode.as_deref(),
Some("verify-ca" | "verify-full")
);
if needs_cert_validation {
// ...existing verify-ca / verify-full logic...
}
if params.ssl_mode.as_deref() == Some("require") {
// no validation, matches builtin's NoCertVerifier
...
}
// remaining fallback (disable/allow/prefer/unset) — platform verifier is
// arguably also wrong here per the builtin's NoCertVerifier for these
// modes; worth checking during the fix whether this fallback branch needs
// the same treatment, or whether it's intentionally different since those
// modes don't force TLS to begin with.
Needs a rustls::client::danger::ServerCertVerifier impl that unconditionally accepts (or a Debug-safe wrapper around rustls::client::WebPkiServerVerifier-style dangerous-but-empty checks) — check if rustls's ecosystem already has one exposed (e.g. behind a feature flag) before hand-rolling one.
Add a unit test constructing the verifier directly and asserting it accepts an arbitrary self-signed cert (mirroring the pattern used for VerifyCaCertVerifier's tests in src/client_tests.rs), plus a live-DB test against an SSL-enabled fixture with a self-signed cert (the repo doesn't currently have one in CI — .github/workflows/ci.yml's Postgres fixture runs without SSL at all).
Summary
build_tls_connector's own doc comment states: "requireforces TLS without certificate validation (matches the builtin driver'srequirebehavior — seesrc-tauri/src/pool_manager.rs)." The code doesn't do this.needs_cert_validationonly matches"verify-ca" | "verify-full"—requireis excluded. So forssl_mode=require, execution falls through theif needs_cert_validation { ... }block entirely and lands on the final fallback:with_platform_verifier()does validate the server's certificate against the OS trust store — the opposite of "without certificate validation." Sorequirecurrently behaves likeverify-full(full platform-trust validation) instead of likedisable/allow/prefer(no validation, just opportunistic/forced TLS).Builtin's actual behavior (for reference)
src-tauri/src/pool_manager.rs'sbuild_postgres_tls_connector:requireuses the sameNoCertVerifier(accepts any certificate, no validation at all) asdisable/allow/prefer— the only thing that changes between those modes andrequireis whether TLS is attempted at all (handled bySslMode, see #43), not whether the cert is checked.Impact / reproduction
Found while manually verifying the #43 fix against a real SSL-enabled PostgreSQL instance with a self-signed certificate:
Result:
{"error":{"code":-32603,"message":"Connection failed: Error occurred while creating a new object: error performing TLS handshake"}}Expected (per the doc comment and the builtin's behavior):
requireshould connect successfully — the whole point ofrequiremode (vs.verify-full) is "encrypt the connection, but don't bother validating who's on the other end" — the standard use case being self-signed certs, private CAs the user hasn't configuredssl_cafor, or any server the user trusts by network topology rather than PKI.Discovery context
Confirmed via
git log/git show 9986b1ethat this bug has been present sinceclient.rswas first staged (#3) — not a regression from #35/#37/#40/#41/#42, and distinct from #43 (different root cause: cert validation logic, not protocol-levelSslModeenforcement), even though both live inbuild_tls_connector/TLS setup and were found back-to-back while testing #43.Proposed fix
Add a no-op/accept-all certificate verifier (mirroring the builtin's
NoCertVerifier) and routerequiremode to it instead of falling through towith_platform_verifier(). Something like:Needs a
rustls::client::danger::ServerCertVerifierimpl that unconditionally accepts (or aDebug-safe wrapper aroundrustls::client::WebPkiServerVerifier-style dangerous-but-empty checks) — check ifrustls's ecosystem already has one exposed (e.g. behind a feature flag) before hand-rolling one.Add a unit test constructing the verifier directly and asserting it accepts an arbitrary self-signed cert (mirroring the pattern used for
VerifyCaCertVerifier's tests insrc/client_tests.rs), plus a live-DB test against an SSL-enabled fixture with a self-signed cert (the repo doesn't currently have one in CI —.github/workflows/ci.yml's Postgres fixture runs without SSL at all).