fix(ssl): NoCnVerifier catches all InvalidCertificate variants for verify-ca#738
fix(ssl): NoCnVerifier catches all InvalidCertificate variants for verify-ca#738
Conversation
…rify-ca
rustls 0.23 introduced NotValidForNameContext as a richer variant of
NotValidForName that includes the expected server name and presented SANs.
webpki 0.103+ always returns this context variant when hostname check fails.
The verify_chain() method only caught the legacy NotValidForName variant,
so sslmode=verify-ca connections failed with:
certificate not valid for name "dummy.invalid"; certificate is only
valid for DnsName("localhost") or IpAddress(127.0.0.1)
Fix: catch both NotValidForName and NotValidForNameContext in the match
arm for the intentional dummy-hostname mismatch.
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #738 +/- ##
=======================================
Coverage 69.10% 69.10%
=======================================
Files 47 47
Lines 31990 31990
=======================================
Hits 22105 22105
Misses 9885 9885 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
REV Code Review — PR #738PR: #738 — BLOCKING ISSUESNone. NON-BLOCKINGINFO The comment says webpki 0.103+ always returns POTENTIAL ISSUESNone. Detailed Analysis1. Or-pattern correctnessErr(RustlsError::InvalidCertificate(
rustls::CertificateError::NotValidForName
| rustls::CertificateError::NotValidForNameContext { .. },
)) => Ok(ServerCertVerified::assertion()),Correct. Rust's or-patterns in 2. Exhaustiveness — other CertificateError variantsReviewed the full Only 3. Impact on verify-full (FullVerifier)
4. Scope isolation — NoCnVerifier only
5. Test coverageGap identified (non-blocking): There is no unit test for The regression risk is real: if someone replaces the or-pattern with only A targeted unit test injecting #[test]
fn test_no_cn_verifier_accepts_not_valid_for_name_context() {
use rustls::CertificateError;
use rustls::Error as RustlsError;
// Simulate what NoCnVerifier::verify_chain() would receive from webpki 0.103+
let err = RustlsError::InvalidCertificate(CertificateError::NotValidForNameContext {
expected: rustls::ServerName::try_from("dummy.invalid").unwrap(),
presented: vec!["localhost".to_owned()],
});
// The match arm must treat this as success (Ok), not propagate as Err
let result: Result<(), _> = match err {
RustlsError::InvalidCertificate(
CertificateError::NotValidForName
| CertificateError::NotValidForNameContext { .. },
) => Ok(()),
e => Err(e),
};
assert!(result.is_ok(), "NotValidForNameContext must be swallowed by NoCnVerifier");
}Summary
Verdict: APPROVE. The fix is correct, minimal, and well-scoped. REV-assisted review (AI analysis by postgres-ai/rev) |
Testing evidenceBuild: Build clean. ✓ Unit tests: 1809 passed, 0 failed ✓ verify-ca (correct CA): connects, query succeeds ✓ verify-full (correct CA, hostname=localhost): connects, query succeeds ✓ — verify-full unaffected by this change verify-ca with wrong CA (must FAIL): ✓ — CA mismatch correctly rejected; NoCnVerifier is NOT swallowing non-hostname errors |
Problem
sslmode=verify-caconnections fail with:Root Cause
NoCnVerifier::verify_chain()callsWebPkiServerVerifier::verify_server_cert()with a dummy hostname (dummy.invalid) specifically to trigger a hostname-mismatch error that can be safely ignored — because forverify-cawe only care about the certificate chain, not the hostname.The match arm caught
CertificateError::NotValidForName(the legacy rustls variant). However, rustls 0.23 introduced a second variantCertificateError::NotValidForNameContext { expected, presented }that includes the expected name and presented SANs for richer error messages. webpki 0.103+ always returns the context variant — so the legacyNotValidForNamearm never fires, and the error propagates as a real failure.Fix
Catch both variants in the same match arm:
Testing
verify-ca: connects successfully ✓verify-full: still works correctly ✓cargo clippywithRUSTFLAGS=-D warningsclean ✓