Skip to content

Default using Windows Schannel for SSL/TLS on Windows #2116

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -274,6 +276,7 @@ target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>,$<BOOL:${HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN}>>:CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN>
$<$<BOOL:${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO}>:CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO>
$<$<AND:$<PLATFORM_ID:Windows>,$<NOT:$<BOOL:${HTTPLIB_USE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE}>>>:CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE>
)

# CMake configuration files installation directory
Expand Down
95 changes: 95 additions & 0 deletions httplib.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ using ssize_t = long;
#endif // NOMINMAX

#include <io.h>
#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && \
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
#define CERT_CHAIN_PARA_HAS_EXTRA_FIELDS
#endif
#include <winsock2.h>
#include <ws2tcpip.h>

Expand Down Expand Up @@ -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) {
Expand All @@ -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 <typename T>
Expand Down Expand Up @@ -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_));
Expand Down Expand Up @@ -10529,13 +10537,17 @@ 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) {
last_openssl_error_ = static_cast<unsigned long>(verify_result_);
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); });
Expand All @@ -10546,13 +10558,96 @@ 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;
error = Error::SSLServerHostnameVerification;
return false;
}
}
#else // _WIN32 && !
// CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE Convert
// OpenSSL certificate to DER format
auto der_cert =
std::vector<unsigned char>(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<DWORD>(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<wchar_t *>(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
}
}

Expand Down
Loading