From e803f5e4135839ff4f8210ba28ca52a7bb87bdf7 Mon Sep 17 00:00:00 2001 From: yhirose Date: Wed, 7 Oct 2026 23:19:36 -0400 Subject: [PATCH] Let the SSL chain policy alone judge the Windows chain (#2618) verify_cert_with_windows_schannel() rejected a chain whenever TrustStatus.dwErrorStatus was non-zero, before CertVerifyCertificateChainPolicy() ran. The CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS flag passed to that policy check was therefore dead code: a certificate without revocation information, or one whose CRL could not be fetched, failed with CERT_TRUST_REVOCATION_STATUS_UNKNOWN. Drop the pre-check so the SSL chain policy is the only judge. Revocation checking becomes best-effort: a revoked certificate and every other chain error are still rejected, while an undetermined revocation status is accepted. On a rejected chain, ssl_backend_error() now holds the policy status, such as CERT_E_UNTRUSTEDROOT, instead of the trust status bit mask. --- README.md | 2 +- httplib.h | 6 ------ test/test.cc | 8 +++----- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 8a76510..7e07e30 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,7 @@ cpp-httplib automatically integrates with the OS certificate store on macOS and | Platform | Behavior | Disable (compile time) | | :------- | :------- | :--------------------- | | macOS | Loads system certs from Keychain (link `CoreFoundation` and `Security` with `-framework`). Requires Apple Clang; GCC is not supported for this feature. | `CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES` | -| Windows | Verifies the certificate chain with CryptoAPI (`CertGetCertificateChain` / `CertVerifyCertificateChainPolicy`) instead of the TLS backend, with revocation checking. Windows fetches missing roots and intermediates on demand. With a custom CA, the TLS backend verifies the chain instead; with `set_server_certificate_verifier()`, both do. | `CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE` | +| Windows | Verifies the certificate chain with CryptoAPI (`CertGetCertificateChain` / `CertVerifyCertificateChainPolicy`) instead of the TLS backend. Revocation checking is best-effort: a revoked server certificate is rejected, while one whose revocation status cannot be determined is accepted. Windows fetches missing roots and intermediates on demand. With a custom CA, the TLS backend verifies the chain instead; with `set_server_certificate_verifier()`, both do. | `CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE` | On Windows, verification can also be disabled at runtime: diff --git a/httplib.h b/httplib.h index c80afc4..34c0b2b 100644 --- a/httplib.h +++ b/httplib.h @@ -10858,12 +10858,6 @@ inline bool verify_cert_with_windows_schannel( auto chain_guard = scope_exit([&] { CertFreeCertificateChain(chain_context); }); - // Check if chain has errors - if (chain_context->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) { - out_error = chain_context->TrustStatus.dwErrorStatus; - return false; - } - // Verify SSL policy SSL_EXTRA_CERT_CHAIN_POLICY_PARA extra_policy_para = {}; extra_policy_para.cbSize = sizeof(extra_policy_para); diff --git a/test/test.cc b/test/test.cc index 3694941..a3e0ada 100644 --- a/test/test.cc +++ b/test/test.cc @@ -14189,9 +14189,8 @@ TEST(SSLClientTest, WindowsCertificateVerification_ServerIntermediates_Online) { << " ssl_backend_error=" << res.ssl_backend_error(); } -// Windows, not the backend, decides on the chain: the error carries a -// CryptoAPI trust status, which no backend error for a self-signed -// certificate has. +// Windows, not the backend, decides on the chain: the error is a CryptoAPI +// policy status, which no backend reports for a self-signed certificate. TEST(SSLClientTest, WindowsCertificateVerification_RejectsUntrustedRoot) { SSLServer svr(SERVER_CERT2_FILE, SERVER_PRIVATE_KEY_FILE); ASSERT_TRUE(svr.is_valid()); @@ -14211,8 +14210,7 @@ TEST(SSLClientTest, WindowsCertificateVerification_RejectsUntrustedRoot) { auto res = cli.Get("/"); ASSERT_FALSE(res); EXPECT_EQ(Error::SSLServerVerification, res.error()); - EXPECT_NE(0u, res.ssl_backend_error() & CERT_TRUST_IS_UNTRUSTED_ROOT) - << "ssl_backend_error=" << res.ssl_backend_error(); + EXPECT_EQ(static_cast(CERT_E_UNTRUSTEDROOT), res.ssl_backend_error()); } #endif