1
0
mirror of synced 2025-12-04 23:02:38 +03:00

Default using Windows Schannel for SSL/TLS certificate verification on Windows

This commit adds support for using Windows Schannel (Windows certificate store)
for SSL/TLS certificate verification instead of OpenSSL's verification on Windows.
This provides automatic root certificate updates from Windows Update.

Changes:
- Add CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE macro
  to optionally disable this feature and use OpenSSL verification
- Add HTTPLIB_USE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE CMake option (default ON)
- Add wincrypt_error() and wincrypt_chain_error() methods to Result class
  for Windows-specific certificate error information
- Modify SSLClient::initialize_ssl() to use Windows CertGetCertificateChain()
  and CertVerifyCertificateChainPolicy() APIs for certificate verification
- Update tests to handle both Windows Schannel and OpenSSL verification paths
- Update README.md with documentation for Windows certificate verification

Closes #1978
Based on PR #2116 by @solarispika
This commit is contained in:
yhirose
2025-11-25 21:53:54 -05:00
parent adf58bf474
commit 104d033ffd
4 changed files with 259 additions and 10 deletions

View File

@@ -11,6 +11,7 @@
* HTTPLIB_REQUIRE_ZSTD (default off) * HTTPLIB_REQUIRE_ZSTD (default off)
* HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on) * HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on)
* HTTPLIB_USE_NON_BLOCKING_GETADDRINFO (default on) * HTTPLIB_USE_NON_BLOCKING_GETADDRINFO (default on)
* HTTPLIB_USE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE (default on)
* HTTPLIB_COMPILE (default off) * HTTPLIB_COMPILE (default off)
* HTTPLIB_INSTALL (default on) * HTTPLIB_INSTALL (default on)
* HTTPLIB_SHARED (default off) builds as a shared library (if HTTPLIB_COMPILE is ON) * HTTPLIB_SHARED (default off) builds as a shared library (if HTTPLIB_COMPILE is ON)
@@ -110,6 +111,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_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_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_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 but respects standard BUILD_SHARED_LIBS if set # Defaults to static library but respects standard BUILD_SHARED_LIBS if set
include(CMakeDependentOption) include(CMakeDependentOption)
cmake_dependent_option(HTTPLIB_SHARED "Build the library as a shared library instead of static. Has no effect if using header-only." cmake_dependent_option(HTTPLIB_SHARED "Build the library as a shared library instead of static. Has no effect if using header-only."
@@ -296,6 +298,7 @@ target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT> $<$<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> $<$<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> $<$<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 # CMake configuration files installation directory

View File

@@ -113,13 +113,26 @@ if (!res) {
break; break;
case httplib::Error::SSLServerVerification: case httplib::Error::SSLServerVerification:
std::cout << "SSL verification failed, X509 error: " std::cout << "SSL verification failed" << std::endl;
<< res.ssl_openssl_error() << std::endl; #if defined(_WIN32) && !defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
// On Windows with Schannel, check Windows certificate errors
std::cout << " Windows error: 0x" << std::hex << res.wincrypt_error()
<< ", chain status: 0x" << res.wincrypt_chain_error() << std::endl;
#else
// On other platforms, check OpenSSL errors
std::cout << " X509 error: " << res.ssl_openssl_error() << std::endl;
#endif
break; break;
case httplib::Error::SSLServerHostnameVerification: case httplib::Error::SSLServerHostnameVerification:
std::cout << "SSL hostname verification failed, X509 error: " std::cout << "SSL hostname verification failed" << std::endl;
<< res.ssl_openssl_error() << std::endl; #if defined(_WIN32) && !defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
// On Windows with Schannel, check Windows certificate errors
std::cout << " Windows error: 0x" << std::hex << res.wincrypt_error() << std::endl;
#else
// On other platforms, check OpenSSL errors
std::cout << " X509 error: " << res.ssl_openssl_error() << std::endl;
#endif
break; break;
default: default:
@@ -128,6 +141,62 @@ if (!res) {
} }
``` ```
For a simpler platform-agnostic approach, you can check which error field is non-zero:
```c++
auto res = cli.Get("/");
if (!res) {
if (res.error() == httplib::Error::SSLServerVerification) {
std::cout << "Certificate verification failed!" << std::endl;
// Check which backend reported the error
if (res.ssl_openssl_error() != 0) {
// OpenSSL reported the error (Linux, macOS, or Windows with Schannel disabled)
std::cout << "OpenSSL error: " << res.ssl_openssl_error() << std::endl;
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#if defined(_WIN32) && !defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
else if (res.wincrypt_error() != 0) {
// Windows Schannel reported the error
std::cout << "Windows error: 0x" << std::hex << res.wincrypt_error() << std::endl;
std::cout << "Chain status: 0x" << std::hex << res.wincrypt_chain_error() << std::endl;
}
#endif
#endif
}
}
```
### Windows Certificate Verification
On Windows, by default, cpp-httplib uses Windows Schannel for certificate verification instead of OpenSSL's verification. This provides automatic root certificate updates from Windows Update.
**When certificate verification fails on Windows:**
- OpenSSL still handles the TLS handshake
- Windows Schannel performs certificate verification
- Check `wincrypt_error()` and `wincrypt_chain_error()` for diagnostic information
- The `ssl_openssl_error()` field will be 0 for certificate verification errors
**Windows-specific error codes** (`wincrypt_error()`) include:
- `CERT_E_EXPIRED` (0x800B0101) - Certificate has expired
- `CERT_E_UNTRUSTEDROOT` (0x800B0109) - Certificate chain to untrusted root
- `CERT_E_CN_NO_MATCH` (0x800B010F) - Certificate CN doesn't match hostname
- `CERT_E_REVOKED` (0x800B010C) - Certificate has been revoked
- `CERT_E_CHAINING` (0x800B010A) - Error building certificate chain
**Chain trust status** (`wincrypt_chain_error()`) provides additional details:
- `CERT_TRUST_IS_NOT_TIME_VALID` (0x00000001) - Certificate expired
- `CERT_TRUST_IS_REVOKED` (0x00000004) - Certificate revoked
- `CERT_TRUST_IS_NOT_SIGNATURE_VALID` (0x00000008) - Invalid signature
- `CERT_TRUST_IS_UNTRUSTED_ROOT` (0x00000020) - Untrusted root
To disable Windows automatic certificate updates and use OpenSSL verification:
```cmake
set(HTTPLIB_USE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE OFF)
```
Or define `CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE` before including httplib.h.
Server Server
------ ------

162
httplib.h
View File

@@ -221,6 +221,10 @@ using ssize_t = __int64;
#endif // NOMINMAX #endif // NOMINMAX
#include <io.h> #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 <winsock2.h>
#include <ws2tcpip.h> #include <ws2tcpip.h>
@@ -1349,6 +1353,17 @@ public:
: res_(std::move(res)), err_(err), : res_(std::move(res)), err_(err),
request_headers_(std::move(request_headers)), ssl_error_(ssl_error), request_headers_(std::move(request_headers)), ssl_error_(ssl_error),
ssl_openssl_error_(ssl_openssl_error) {} ssl_openssl_error_(ssl_openssl_error) {}
#if defined(_WIN32) && \
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers,
int ssl_error, unsigned long ssl_openssl_error,
unsigned long wincrypt_error, unsigned long wincrypt_chain_error)
: res_(std::move(res)), err_(err),
request_headers_(std::move(request_headers)), ssl_error_(ssl_error),
ssl_openssl_error_(ssl_openssl_error), wincrypt_error_(wincrypt_error),
wincrypt_chain_error_(wincrypt_chain_error) {}
#endif
#endif #endif
// Response // Response
operator bool() const { return res_ != nullptr; } operator bool() const { return res_ != nullptr; }
@@ -1369,6 +1384,14 @@ public:
int ssl_error() const { return ssl_error_; } int ssl_error() const { return ssl_error_; }
// OpenSSL Error // OpenSSL Error
unsigned long ssl_openssl_error() const { return ssl_openssl_error_; } unsigned long ssl_openssl_error() const { return ssl_openssl_error_; }
#if defined(_WIN32) && \
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
// Windows Certificate Error (from GetLastError or policy_status.dwError)
unsigned long wincrypt_error() const { return wincrypt_error_; }
// Windows Certificate Chain Trust Status (from TrustStatus.dwErrorStatus)
unsigned long wincrypt_chain_error() const { return wincrypt_chain_error_; }
#endif
#endif #endif
// Request Headers // Request Headers
@@ -1387,6 +1410,12 @@ private:
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
int ssl_error_ = 0; int ssl_error_ = 0;
unsigned long ssl_openssl_error_ = 0; unsigned long ssl_openssl_error_ = 0;
#if defined(_WIN32) && \
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
unsigned long wincrypt_error_ = 0;
unsigned long wincrypt_chain_error_ = 0;
#endif
#endif #endif
}; };
@@ -1706,6 +1735,12 @@ protected:
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
int last_ssl_error_ = 0; int last_ssl_error_ = 0;
unsigned long last_openssl_error_ = 0; unsigned long last_openssl_error_ = 0;
#if defined(_WIN32) && \
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
unsigned long last_wincrypt_error_ = 0;
unsigned long last_wincrypt_chain_error_ = 0;
#endif
#endif #endif
private: private:
@@ -6310,6 +6345,7 @@ inline bool is_ssl_peer_could_be_closed(SSL *ssl, socket_t sock) {
} }
#ifdef _WIN32 #ifdef _WIN32
#ifdef CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
// NOTE: This code came up with the following stackoverflow post: // NOTE: This code came up with the following stackoverflow post:
// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store // https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store
inline bool load_system_certs_on_windows(X509_STORE *store) { inline bool load_system_certs_on_windows(X509_STORE *store) {
@@ -6336,6 +6372,7 @@ inline bool load_system_certs_on_windows(X509_STORE *store) {
return result; return result;
} }
#endif // CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && TARGET_OS_MAC #elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && TARGET_OS_MAC
template <typename T> template <typename T>
using CFObjectPtr = using CFObjectPtr =
@@ -8954,8 +8991,19 @@ inline Result ClientImpl::send_(Request &&req) {
auto error = Error::Success; auto error = Error::Success;
auto ret = send(req, *res, error); auto ret = send(req, *res, error);
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#if defined(_WIN32) && \
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
return Result{ret ? std::move(res) : nullptr,
error,
std::move(req.headers),
last_ssl_error_,
last_openssl_error_,
last_wincrypt_error_,
last_wincrypt_chain_error_};
#else
return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers), return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers),
last_ssl_error_, last_openssl_error_}; last_ssl_error_, last_openssl_error_};
#endif
#else #else
return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)};
#endif #endif
@@ -9541,8 +9589,19 @@ inline Result ClientImpl::send_with_content_provider_and_receiver(
std::move(content_receiver), error); std::move(content_receiver), error);
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#if defined(_WIN32) && \
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
return Result{std::move(res),
error,
std::move(req.headers),
last_ssl_error_,
last_openssl_error_,
last_wincrypt_error_,
last_wincrypt_chain_error_};
#else
return Result{std::move(res), error, std::move(req.headers), last_ssl_error_, return Result{std::move(res), error, std::move(req.headers), last_ssl_error_,
last_openssl_error_}; last_openssl_error_};
#endif
#else #else
return Result{std::move(res), error, std::move(req.headers)}; return Result{std::move(res), error, std::move(req.headers)};
#endif #endif
@@ -11348,8 +11407,10 @@ inline bool SSLClient::load_certs() {
} else { } else {
auto loaded = false; auto loaded = false;
#ifdef _WIN32 #ifdef _WIN32
#ifdef CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
loaded = loaded =
detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); 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) && TARGET_OS_MAC #elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && TARGET_OS_MAC
loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_));
#endif // _WIN32 #endif // _WIN32
@@ -11396,6 +11457,8 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
} }
if (verification_status == SSLVerifierResponse::NoDecisionMade) { if (verification_status == SSLVerifierResponse::NoDecisionMade) {
#if !defined(_WIN32) || \
defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
verify_result_ = SSL_get_verify_result(ssl2); verify_result_ = SSL_get_verify_result(ssl2);
if (verify_result_ != X509_V_OK) { if (verify_result_ != X509_V_OK) {
@@ -11404,6 +11467,8 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
output_error_log(error, nullptr); output_error_log(error, nullptr);
return false; return false;
} }
#endif // !_WIN32 ||
// CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
auto server_cert = SSL_get1_peer_certificate(ssl2); auto server_cert = SSL_get1_peer_certificate(ssl2);
auto se = detail::scope_exit([&] { X509_free(server_cert); }); auto se = detail::scope_exit([&] { X509_free(server_cert); });
@@ -11415,6 +11480,8 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
return false; return false;
} }
#if !defined(_WIN32) || \
defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
if (server_hostname_verification_) { if (server_hostname_verification_) {
if (!verify_host(server_cert)) { if (!verify_host(server_cert)) {
last_openssl_error_ = X509_V_ERR_HOSTNAME_MISMATCH; last_openssl_error_ = X509_V_ERR_HOSTNAME_MISMATCH;
@@ -11423,6 +11490,101 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
return false; return false;
} }
} }
#else // _WIN32 &&
// !CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
// Windows Schannel verification path - clear OpenSSL errors since
// we're not using OpenSSL for certificate verification
last_openssl_error_ = 0;
// 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) {
last_wincrypt_error_ = GetLastError();
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) {
if (!result) { last_wincrypt_error_ = GetLastError(); }
error = Error::SSLServerVerification;
return false;
}
// Capture detailed chain trust status before using the chain
last_wincrypt_chain_error_ =
chain_context->TrustStatus.dwErrorStatus;
// 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) {
last_wincrypt_error_ = GetLastError();
error = Error::SSLServerVerification;
return false;
}
if (policy_status.dwError != 0) {
// Store the specific Windows certificate error code
last_wincrypt_error_ = policy_status.dwError;
if (policy_status.dwError == CERT_E_CN_NO_MATCH) {
error = Error::SSLServerHostnameVerification;
} else {
error = Error::SSLServerVerification;
}
return false;
}
#endif // !_WIN32 ||
// CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
} }
} }

View File

@@ -8394,14 +8394,19 @@ TEST(SSLClientTest, ServerCertificateVerificationError_Online) {
ASSERT_TRUE(!res); ASSERT_TRUE(!res);
EXPECT_EQ(Error::SSLServerVerification, res.error()); EXPECT_EQ(Error::SSLServerVerification, res.error());
// For SSL server verification errors, ssl_error should be 0, only // For SSL server verification errors, ssl_error should be 0
// ssl_openssl_error should be set
EXPECT_EQ(0, res.ssl_error()); EXPECT_EQ(0, res.ssl_error());
#if defined(_WIN32) && \
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
// On Windows with Schannel, check wincrypt_error instead of ssl_openssl_error
EXPECT_NE(0UL, res.wincrypt_error());
#else
// Verify OpenSSL error is captured for SSLServerVerification // Verify OpenSSL error is captured for SSLServerVerification
// This occurs when SSL_get_verify_result() returns a verification failure // This occurs when SSL_get_verify_result() returns a verification failure
EXPECT_EQ(static_cast<unsigned long>(X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT), EXPECT_EQ(static_cast<unsigned long>(X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT),
res.ssl_openssl_error()); res.ssl_openssl_error());
#endif
} }
TEST(SSLClientTest, ServerHostnameVerificationError_Online) { TEST(SSLClientTest, ServerHostnameVerificationError_Online) {
@@ -8416,14 +8421,19 @@ TEST(SSLClientTest, ServerHostnameVerificationError_Online) {
EXPECT_EQ(Error::SSLServerHostnameVerification, res.error()); EXPECT_EQ(Error::SSLServerHostnameVerification, res.error());
// For SSL hostname verification errors, ssl_error should be 0, only // For SSL hostname verification errors, ssl_error should be 0
// ssl_openssl_error should be set
EXPECT_EQ(0, res.ssl_error()); EXPECT_EQ(0, res.ssl_error());
#if defined(_WIN32) && \
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
// On Windows with Schannel, check wincrypt_error instead of ssl_openssl_error
EXPECT_NE(0UL, res.wincrypt_error());
#else
// Verify OpenSSL error is captured for SSLServerHostnameVerification // Verify OpenSSL error is captured for SSLServerHostnameVerification
// This occurs when verify_host() fails due to hostname mismatch // This occurs when verify_host() fails due to hostname mismatch
EXPECT_EQ(static_cast<unsigned long>(X509_V_ERR_HOSTNAME_MISMATCH), EXPECT_EQ(static_cast<unsigned long>(X509_V_ERR_HOSTNAME_MISMATCH),
res.ssl_openssl_error()); res.ssl_openssl_error());
#endif
} }
TEST(SSLClientTest, ServerCertificateVerification1_Online) { TEST(SSLClientTest, ServerCertificateVerification1_Online) {
@@ -8799,14 +8809,19 @@ TEST(SSLClientServerTest, ClientCertMissing) {
ASSERT_TRUE(!res); ASSERT_TRUE(!res);
EXPECT_EQ(Error::SSLServerVerification, res.error()); EXPECT_EQ(Error::SSLServerVerification, res.error());
// For SSL server verification errors, ssl_error should be 0, only // For SSL server verification errors, ssl_error should be 0
// ssl_openssl_error should be set
EXPECT_EQ(0, res.ssl_error()); EXPECT_EQ(0, res.ssl_error());
#if defined(_WIN32) && \
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
// On Windows with Schannel, check wincrypt_error instead of ssl_openssl_error
EXPECT_NE(0UL, res.wincrypt_error());
#else
// Verify OpenSSL error is captured for SSLServerVerification // Verify OpenSSL error is captured for SSLServerVerification
// Note: This test may have different error codes depending on the exact // Note: This test may have different error codes depending on the exact
// verification failure // verification failure
EXPECT_NE(0UL, res.ssl_openssl_error()); EXPECT_NE(0UL, res.ssl_openssl_error());
#endif
} }
TEST(SSLClientServerTest, TrustDirOptional) { TEST(SSLClientServerTest, TrustDirOptional) {