You've already forked cpp-httplib
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:
162
httplib.h
162
httplib.h
@@ -221,6 +221,10 @@ using ssize_t = __int64;
|
||||
#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>
|
||||
|
||||
@@ -1349,6 +1353,17 @@ public:
|
||||
: res_(std::move(res)), err_(err),
|
||||
request_headers_(std::move(request_headers)), ssl_error_(ssl_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
|
||||
// Response
|
||||
operator bool() const { return res_ != nullptr; }
|
||||
@@ -1369,6 +1384,14 @@ public:
|
||||
int ssl_error() const { return ssl_error_; }
|
||||
// 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
|
||||
|
||||
// Request Headers
|
||||
@@ -1387,6 +1410,12 @@ private:
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
int ssl_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
|
||||
};
|
||||
|
||||
@@ -1706,6 +1735,12 @@ protected:
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
int last_ssl_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
|
||||
|
||||
private:
|
||||
@@ -6310,6 +6345,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) {
|
||||
@@ -6336,6 +6372,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) && TARGET_OS_MAC
|
||||
template <typename T>
|
||||
using CFObjectPtr =
|
||||
@@ -8954,8 +8991,19 @@ inline Result ClientImpl::send_(Request &&req) {
|
||||
auto error = Error::Success;
|
||||
auto ret = send(req, *res, error);
|
||||
#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),
|
||||
last_ssl_error_, last_openssl_error_};
|
||||
#endif
|
||||
#else
|
||||
return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)};
|
||||
#endif
|
||||
@@ -9541,8 +9589,19 @@ inline Result ClientImpl::send_with_content_provider_and_receiver(
|
||||
std::move(content_receiver), error);
|
||||
|
||||
#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_,
|
||||
last_openssl_error_};
|
||||
#endif
|
||||
#else
|
||||
return Result{std::move(res), error, std::move(req.headers)};
|
||||
#endif
|
||||
@@ -11348,8 +11407,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) && TARGET_OS_MAC
|
||||
loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_));
|
||||
#endif // _WIN32
|
||||
@@ -11396,6 +11457,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) {
|
||||
@@ -11404,6 +11467,8 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
||||
output_error_log(error, nullptr);
|
||||
return false;
|
||||
}
|
||||
#endif // !_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); });
|
||||
@@ -11415,6 +11480,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;
|
||||
@@ -11423,6 +11490,101 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user