From bb7c0856bdf59a692cfc8e99ce8bace9157143b2 Mon Sep 17 00:00:00 2001 From: "Sung, Po Han" Date: Mon, 24 Mar 2025 10:17:10 +0800 Subject: [PATCH] Default using Windows Schannel for SSL/TLS on Windows Follow https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certgetcertificatechain for related flags. Closes #1978 --- CMakeLists.txt | 3 ++ httplib.h | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 99d9bc4738..a531f302c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ * HTTPLIB_REQUIRE_ZSTD (default off) * HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on) * HTTPLIB_USE_NON_BLOCKING_GETADDRINFO (default on) + * HTTPLIB_USE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE (default on) * HTTPLIB_COMPILE (default off) * HTTPLIB_INSTALL (default on) * HTTPLIB_TEST (default off) @@ -109,6 +110,7 @@ option(HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN "Enable feature to load system cer option(HTTPLIB_USE_NON_BLOCKING_GETADDRINFO "Enables the non-blocking alternatives for getaddrinfo." ON) option(HTTPLIB_REQUIRE_ZSTD "Requires ZSTD to be found & linked, or fails build." OFF) option(HTTPLIB_USE_ZSTD_IF_AVAILABLE "Uses ZSTD (if available) to enable zstd support." ON) +option(HTTPLIB_USE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE "Enable automatic root certificates update on Windows." ON) # Defaults to static library option(BUILD_SHARED_LIBS "Build the library as a shared library instead of static. Has no effect if using header-only." OFF) if (BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE) @@ -274,6 +276,7 @@ target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} $<$:CPPHTTPLIB_OPENSSL_SUPPORT> $<$,$,$>:CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN> $<$:CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO> + $<$,$>>:CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE> ) # CMake configuration files installation directory diff --git a/httplib.h b/httplib.h index ec8349e109..14784f42c5 100644 --- a/httplib.h +++ b/httplib.h @@ -197,6 +197,10 @@ using ssize_t = long; #endif // NOMINMAX #include +#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && \ + !defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE) +#define CERT_CHAIN_PARA_HAS_EXTRA_FIELDS +#endif #include #include @@ -6040,6 +6044,7 @@ inline bool is_ssl_peer_could_be_closed(SSL *ssl, socket_t sock) { } #ifdef _WIN32 +#ifdef CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE // NOTE: This code came up with the following stackoverflow post: // https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store inline bool load_system_certs_on_windows(X509_STORE *store) { @@ -6066,6 +6071,7 @@ inline bool load_system_certs_on_windows(X509_STORE *store) { return result; } +#endif // CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE #elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && \ defined(TARGET_OS_OSX) template @@ -10483,8 +10489,10 @@ inline bool SSLClient::load_certs() { } else { auto loaded = false; #ifdef _WIN32 +#ifdef CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE loaded = detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#endif // CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE #elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && \ defined(TARGET_OS_OSX) loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); @@ -10529,6 +10537,8 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { } if (verification_status == SSLVerifierResponse::NoDecisionMade) { +#if !defined(_WIN32) || \ + defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE) verify_result_ = SSL_get_verify_result(ssl2); if (verify_result_ != X509_V_OK) { @@ -10536,6 +10546,8 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { error = Error::SSLServerVerification; return false; } +#endif // not _WIN32 || + // CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE auto server_cert = SSL_get1_peer_certificate(ssl2); auto se = detail::scope_exit([&] { X509_free(server_cert); }); @@ -10546,6 +10558,8 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { return false; } +#if !defined(_WIN32) || \ + defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE) if (server_hostname_verification_) { if (!verify_host(server_cert)) { last_openssl_error_ = X509_V_ERR_HOSTNAME_MISMATCH; @@ -10553,6 +10567,87 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { return false; } } +#else // _WIN32 && ! + // CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE Convert + // OpenSSL certificate to DER format + auto der_cert = + std::vector(i2d_X509(server_cert, nullptr)); + auto der_cert_data = der_cert.data(); + if (i2d_X509(server_cert, &der_cert_data) < 0) { + error = Error::SSLServerVerification; + return false; + } + + // Create a certificate context from the DER-encoded certificate + auto cert_context = CertCreateCertificateContext( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, der_cert.data(), + static_cast(der_cert.size())); + + if (cert_context == nullptr) { + error = Error::SSLServerVerification; + return false; + } + + auto chain_para = CERT_CHAIN_PARA{}; + chain_para.cbSize = sizeof(chain_para); + chain_para.dwUrlRetrievalTimeout = 10 * 1000; + + auto chain_context = PCCERT_CHAIN_CONTEXT{}; + auto result = CertGetCertificateChain( + nullptr, cert_context, nullptr, cert_context->hCertStore, + &chain_para, + CERT_CHAIN_CACHE_END_CERT | + CERT_CHAIN_REVOCATION_CHECK_END_CERT | + CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT, + nullptr, &chain_context); + + CertFreeCertificateContext(cert_context); + + if (!result || chain_context == nullptr) { + error = Error::SSLServerVerification; + return false; + } + + // Verify chain policy + auto extra_policy_para = SSL_EXTRA_CERT_CHAIN_POLICY_PARA{}; + extra_policy_para.cbSize = sizeof(extra_policy_para); + extra_policy_para.dwAuthType = AUTHTYPE_SERVER; + auto whost = detail::u8string_to_wstring(host_.c_str()); + if (server_hostname_verification_) { + extra_policy_para.pwszServerName = + const_cast(whost.c_str()); + } + + auto policy_para = CERT_CHAIN_POLICY_PARA{}; + policy_para.cbSize = sizeof(policy_para); + policy_para.dwFlags = + CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS; + policy_para.pvExtraPolicyPara = &extra_policy_para; + + auto policy_status = CERT_CHAIN_POLICY_STATUS{}; + policy_status.cbSize = sizeof(policy_status); + + result = CertVerifyCertificateChainPolicy( + CERT_CHAIN_POLICY_SSL, chain_context, &policy_para, + &policy_status); + + CertFreeCertificateChain(chain_context); + + if (!result) { + error = Error::SSLServerVerification; + return false; + } + + if (policy_status.dwError != 0) { + if (policy_status.dwError == CERT_E_CN_NO_MATCH) { + error = Error::SSLServerHostnameVerification; + } else { + error = Error::SSLServerVerification; + } + return false; + } +#endif // not _WIN32 || + // CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE } }