diff --git a/httplib.h b/httplib.h index 82a8a196..eccfb2bc 100644 --- a/httplib.h +++ b/httplib.h @@ -10736,9 +10736,12 @@ inline bool verify_cert_with_windows_schannel( auto store = cert_context->hCertStore; #endif - // Setup chain parameters + // Setup chain parameters. The SSL policy does not check the key usage. + LPSTR server_auth = const_cast(szOID_PKIX_KP_SERVER_AUTH); CERT_CHAIN_PARA chain_para = {}; chain_para.cbSize = sizeof(chain_para); + chain_para.RequestedUsage.Usage.cUsageIdentifier = 1; + chain_para.RequestedUsage.Usage.rgpszUsageIdentifier = &server_auth; // Build certificate chain with revocation checking PCCERT_CHAIN_CONTEXT chain_context = nullptr; @@ -10936,6 +10939,15 @@ inline bool setup_client_tls_session( if (verification_status == SSLVerifierResponse::NoDecisionMade && server_certificate_verification) { auto verify_result = get_verify_result(session); +#if defined(CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE) && \ + defined(CPPHTTPLIB_OPENSSL_SUPPORT) + // Windows adds a root it trusts to its store only when CryptoAPI needs it, + // so leave a root missing from the store to the CryptoAPI check below + if (options.windows_cert_verification && + verify_result == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) { + verify_result = X509_V_OK; + } +#endif if (verify_result != 0) { return fail(Error::SSLServerVerification, 0, static_cast(verify_result)); @@ -10970,6 +10982,8 @@ inline bool setup_client_tls_session( session)) { return fail(Error::SSLServerVerification, 0, wincrypt_error); } + } else { + return fail(Error::SSLServerVerification, 0, get_error()); } } #endif diff --git a/test/test.cc b/test/test.cc index 9679e988..5fa11ca1 100644 --- a/test/test.cc +++ b/test/test.cc @@ -13967,6 +13967,34 @@ TEST(SSLClientTest, WindowsCertificateVerification_ServerIntermediates_Online) { ASSERT_TRUE(res) << "Error: " << to_string(res.error()) << " ssl_backend_error=" << res.ssl_backend_error(); } + +// A root missing from the trust store is left to CryptoAPI, which must still +// reject a chain whose root Windows does not trust either. +TEST(SSLClientTest, WindowsCertificateVerification_UnknownIssuerRejected) { + // Issued by the test root CA, which is not in the Windows root store + SSLServer svr(CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE); + ASSERT_TRUE(svr.is_valid()); + svr.Get("/", [](const Request &, Response &res) { + res.set_content("ok", "text/plain"); + }); + + thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); }); + auto se = detail::scope_exit([&] { + svr.stop(); + t.join(); + ASSERT_FALSE(svr.is_running()); + }); + + svr.wait_until_ready(); + + SSLClient cli(HOST, PORT); + // Keep the hostname check from failing first + cli.enable_server_hostname_verification(false); + + auto res = cli.Get("/"); + ASSERT_FALSE(res); + EXPECT_EQ(Error::SSLServerVerification, res.error()); +} #endif TEST(SSLClientTest, ServerCertificateVerification1_Online) {