Skip to content

Commit bb7c085

Browse files
committed
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
1 parent 145fc8b commit bb7c085

File tree

2 files changed

+98
-0
lines changed

2 files changed

+98
-0
lines changed

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
* HTTPLIB_REQUIRE_ZSTD (default off)
1212
* HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on)
1313
* HTTPLIB_USE_NON_BLOCKING_GETADDRINFO (default on)
14+
* HTTPLIB_USE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE (default on)
1415
* HTTPLIB_COMPILE (default off)
1516
* HTTPLIB_INSTALL (default on)
1617
* HTTPLIB_TEST (default off)
@@ -109,6 +110,7 @@ option(HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN "Enable feature to load system cer
109110
option(HTTPLIB_USE_NON_BLOCKING_GETADDRINFO "Enables the non-blocking alternatives for getaddrinfo." ON)
110111
option(HTTPLIB_REQUIRE_ZSTD "Requires ZSTD to be found & linked, or fails build." OFF)
111112
option(HTTPLIB_USE_ZSTD_IF_AVAILABLE "Uses ZSTD (if available) to enable zstd support." ON)
113+
option(HTTPLIB_USE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE "Enable automatic root certificates update on Windows." ON)
112114
# Defaults to static library
113115
option(BUILD_SHARED_LIBS "Build the library as a shared library instead of static. Has no effect if using header-only." OFF)
114116
if (BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE)
@@ -274,6 +276,7 @@ target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
274276
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
275277
$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>,$<BOOL:${HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN}>>:CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN>
276278
$<$<BOOL:${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO}>:CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO>
279+
$<$<AND:$<PLATFORM_ID:Windows>,$<NOT:$<BOOL:${HTTPLIB_USE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE}>>>:CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE>
277280
)
278281

279282
# CMake configuration files installation directory

httplib.h

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,10 @@ using ssize_t = long;
197197
#endif // NOMINMAX
198198

199199
#include <io.h>
200+
#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && \
201+
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
202+
#define CERT_CHAIN_PARA_HAS_EXTRA_FIELDS
203+
#endif
200204
#include <winsock2.h>
201205
#include <ws2tcpip.h>
202206

@@ -6040,6 +6044,7 @@ inline bool is_ssl_peer_could_be_closed(SSL *ssl, socket_t sock) {
60406044
}
60416045

60426046
#ifdef _WIN32
6047+
#ifdef CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
60436048
// NOTE: This code came up with the following stackoverflow post:
60446049
// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store
60456050
inline bool load_system_certs_on_windows(X509_STORE *store) {
@@ -6066,6 +6071,7 @@ inline bool load_system_certs_on_windows(X509_STORE *store) {
60666071

60676072
return result;
60686073
}
6074+
#endif // CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
60696075
#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && \
60706076
defined(TARGET_OS_OSX)
60716077
template <typename T>
@@ -10483,8 +10489,10 @@ inline bool SSLClient::load_certs() {
1048310489
} else {
1048410490
auto loaded = false;
1048510491
#ifdef _WIN32
10492+
#ifdef CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
1048610493
loaded =
1048710494
detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
10495+
#endif // CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
1048810496
#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && \
1048910497
defined(TARGET_OS_OSX)
1049010498
loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_));
@@ -10529,13 +10537,17 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
1052910537
}
1053010538

1053110539
if (verification_status == SSLVerifierResponse::NoDecisionMade) {
10540+
#if !defined(_WIN32) || \
10541+
defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
1053210542
verify_result_ = SSL_get_verify_result(ssl2);
1053310543

1053410544
if (verify_result_ != X509_V_OK) {
1053510545
last_openssl_error_ = static_cast<unsigned long>(verify_result_);
1053610546
error = Error::SSLServerVerification;
1053710547
return false;
1053810548
}
10549+
#endif // not _WIN32 ||
10550+
// CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
1053910551

1054010552
auto server_cert = SSL_get1_peer_certificate(ssl2);
1054110553
auto se = detail::scope_exit([&] { X509_free(server_cert); });
@@ -10546,13 +10558,96 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
1054610558
return false;
1054710559
}
1054810560

10561+
#if !defined(_WIN32) || \
10562+
defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
1054910563
if (server_hostname_verification_) {
1055010564
if (!verify_host(server_cert)) {
1055110565
last_openssl_error_ = X509_V_ERR_HOSTNAME_MISMATCH;
1055210566
error = Error::SSLServerHostnameVerification;
1055310567
return false;
1055410568
}
1055510569
}
10570+
#else // _WIN32 && !
10571+
// CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE Convert
10572+
// OpenSSL certificate to DER format
10573+
auto der_cert =
10574+
std::vector<unsigned char>(i2d_X509(server_cert, nullptr));
10575+
auto der_cert_data = der_cert.data();
10576+
if (i2d_X509(server_cert, &der_cert_data) < 0) {
10577+
error = Error::SSLServerVerification;
10578+
return false;
10579+
}
10580+
10581+
// Create a certificate context from the DER-encoded certificate
10582+
auto cert_context = CertCreateCertificateContext(
10583+
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, der_cert.data(),
10584+
static_cast<DWORD>(der_cert.size()));
10585+
10586+
if (cert_context == nullptr) {
10587+
error = Error::SSLServerVerification;
10588+
return false;
10589+
}
10590+
10591+
auto chain_para = CERT_CHAIN_PARA{};
10592+
chain_para.cbSize = sizeof(chain_para);
10593+
chain_para.dwUrlRetrievalTimeout = 10 * 1000;
10594+
10595+
auto chain_context = PCCERT_CHAIN_CONTEXT{};
10596+
auto result = CertGetCertificateChain(
10597+
nullptr, cert_context, nullptr, cert_context->hCertStore,
10598+
&chain_para,
10599+
CERT_CHAIN_CACHE_END_CERT |
10600+
CERT_CHAIN_REVOCATION_CHECK_END_CERT |
10601+
CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT,
10602+
nullptr, &chain_context);
10603+
10604+
CertFreeCertificateContext(cert_context);
10605+
10606+
if (!result || chain_context == nullptr) {
10607+
error = Error::SSLServerVerification;
10608+
return false;
10609+
}
10610+
10611+
// Verify chain policy
10612+
auto extra_policy_para = SSL_EXTRA_CERT_CHAIN_POLICY_PARA{};
10613+
extra_policy_para.cbSize = sizeof(extra_policy_para);
10614+
extra_policy_para.dwAuthType = AUTHTYPE_SERVER;
10615+
auto whost = detail::u8string_to_wstring(host_.c_str());
10616+
if (server_hostname_verification_) {
10617+
extra_policy_para.pwszServerName =
10618+
const_cast<wchar_t *>(whost.c_str());
10619+
}
10620+
10621+
auto policy_para = CERT_CHAIN_POLICY_PARA{};
10622+
policy_para.cbSize = sizeof(policy_para);
10623+
policy_para.dwFlags =
10624+
CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS;
10625+
policy_para.pvExtraPolicyPara = &extra_policy_para;
10626+
10627+
auto policy_status = CERT_CHAIN_POLICY_STATUS{};
10628+
policy_status.cbSize = sizeof(policy_status);
10629+
10630+
result = CertVerifyCertificateChainPolicy(
10631+
CERT_CHAIN_POLICY_SSL, chain_context, &policy_para,
10632+
&policy_status);
10633+
10634+
CertFreeCertificateChain(chain_context);
10635+
10636+
if (!result) {
10637+
error = Error::SSLServerVerification;
10638+
return false;
10639+
}
10640+
10641+
if (policy_status.dwError != 0) {
10642+
if (policy_status.dwError == CERT_E_CN_NO_MATCH) {
10643+
error = Error::SSLServerHostnameVerification;
10644+
} else {
10645+
error = Error::SSLServerVerification;
10646+
}
10647+
return false;
10648+
}
10649+
#endif // not _WIN32 ||
10650+
// CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
1055610651
}
1055710652
}
1055810653

0 commit comments

Comments
 (0)