* Fix #2116

* Fix problem
This commit is contained in:
yhirose authored and GitHub committed 2026-02-07 19:26:11 -10:00
1 parent 94b5038eb3
commit 8b4146324f
3 files changed
+247 -25

No files matched your search

+194 -23
View File
@@ -2267,6 +2267,11 @@ public:
tls::ctx_t tls_context() const;
#if defined(_WIN32) && \
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
void enable_windows_certificate_verification(bool enabled);
#endif
private:
bool is_ssl_ = false;
#endif
@@ -2384,6 +2389,11 @@ public:
tls::ctx_t tls_context() const { return ctx_; }
#if defined(_WIN32) && \
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
void enable_windows_certificate_verification(bool enabled);
#endif
private:
bool create_and_connect_socket(Socket &socket, Error &error) override;
bool ensure_socket_connection(Socket &socket, Error &error) override;
@@ -2412,6 +2422,11 @@ private:
std::function<SSLVerifierResponse(tls::session_t)> session_verifier_;
#if defined(_WIN32) && \
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
bool enable_windows_cert_verification_ = true;
#endif
friend class ClientImpl;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -3680,6 +3695,7 @@ std::string get_cert_issuer_name(cert_t cert);
bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans);
bool get_cert_validity(cert_t cert, time_t &not_before, time_t &not_after);
std::string get_cert_serial(cert_t cert);
bool get_cert_der(cert_t cert, std::vector<unsigned char> &der);
const char *get_sni(const_session_t session);
// CA store management
@@ -7736,6 +7752,96 @@ inline bool match_hostname(const std::string &pattern,
return true;
}
#ifdef _WIN32
// Verify certificate using Windows CertGetCertificateChain API.
// This provides real-time certificate validation with Windows Update
// integration, independent of the TLS backend (OpenSSL or MbedTLS).
inline bool verify_cert_with_windows_schannel(
const std::vector<unsigned char> &der_cert, const std::string &hostname,
bool verify_hostname, unsigned long &out_error) {
if (der_cert.empty()) { return false; }
out_error = 0;
// Create Windows certificate context from DER data
auto cert_context = CertCreateCertificateContext(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, der_cert.data(),
static_cast<DWORD>(der_cert.size()));
if (!cert_context) {
out_error = GetLastError();
return false;
}
auto cert_guard =
scope_exit([&] { CertFreeCertificateContext(cert_context); });
// Setup chain parameters
CERT_CHAIN_PARA chain_para = {};
chain_para.cbSize = sizeof(chain_para);
// Build certificate chain with revocation checking
PCCERT_CHAIN_CONTEXT chain_context = nullptr;
auto chain_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);
if (!chain_result || !chain_context) {
out_error = GetLastError();
return false;
}
auto chain_guard =
scope_exit([&] { CertFreeCertificateChain(chain_context); });
// Check if chain has errors
if (chain_context->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) {
out_error = chain_context->TrustStatus.dwErrorStatus;
return false;
}
// Verify SSL policy
SSL_EXTRA_CERT_CHAIN_POLICY_PARA extra_policy_para = {};
extra_policy_para.cbSize = sizeof(extra_policy_para);
#ifdef AUTHTYPE_SERVER
extra_policy_para.dwAuthType = AUTHTYPE_SERVER;
#endif
std::wstring whost;
if (verify_hostname) {
whost = u8string_to_wstring(hostname.c_str());
extra_policy_para.pwszServerName = const_cast<wchar_t *>(whost.c_str());
}
CERT_CHAIN_POLICY_PARA policy_para = {};
policy_para.cbSize = sizeof(policy_para);
#ifdef CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS
policy_para.dwFlags = CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS;
#else
policy_para.dwFlags = 0;
#endif
policy_para.pvExtraPolicyPara = &extra_policy_para;
CERT_CHAIN_POLICY_STATUS policy_status = {};
policy_status.cbSize = sizeof(policy_status);
if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, chain_context,
&policy_para, &policy_status)) {
out_error = GetLastError();
return false;
}
if (policy_status.dwError != 0) {
out_error = policy_status.dwError;
return false;
}
return true;
}
#endif // _WIN32
} // namespace detail
#endif // CPPHTTPLIB_SSL_ENABLED
@@ -14100,6 +14206,13 @@ inline void SSLClient::set_session_verifier(
session_verifier_ = std::move(verifier);
}
#if defined(_WIN32) && \
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
inline void SSLClient::enable_windows_certificate_verification(bool enabled) {
enable_windows_cert_verification_ = enabled;
}
#endif
inline void SSLClient::load_ca_cert_store(const char *ca_cert,
std::size_t size) {
if (ctx_ && ca_cert && size > 0) {
@@ -14251,6 +14364,29 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
return false;
}
}
#if defined(_WIN32) && \
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
// Additional Windows Schannel verification.
// This provides real-time certificate validation with Windows Update
// integration, working with both OpenSSL and MbedTLS backends.
// Skip when a custom CA cert is specified, as the Windows certificate
// store would not know about user-provided CA certificates.
if (enable_windows_cert_verification_ && ca_cert_file_path_.empty() &&
ca_cert_dir_path_.empty() && ca_cert_pem_.empty()) {
std::vector<unsigned char> der;
if (get_cert_der(server_cert, der)) {
unsigned long wincrypt_error = 0;
if (!detail::verify_cert_with_windows_schannel(
der, host_, server_hostname_verification_, wincrypt_error)) {
last_backend_error_ = wincrypt_error;
error = Error::SSLServerVerification;
output_error_log(error, nullptr);
return false;
}
}
}
#endif
}
success = true;
@@ -14276,6 +14412,16 @@ inline void Client::enable_server_hostname_verification(bool enabled) {
cli_->enable_server_hostname_verification(enabled);
}
#if defined(_WIN32) && \
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
inline void Client::enable_windows_certificate_verification(bool enabled) {
if (is_ssl_) {
static_cast<SSLClient &>(*cli_).enable_windows_certificate_verification(
enabled);
}
}
#endif
inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path,
const std::string &ca_cert_dir_path) {
cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path);
@@ -14729,25 +14875,28 @@ inline bool load_system_certs(ctx_t ctx) {
auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
#ifdef _WIN32
// Windows: Load from system certificate store
// Windows: Load from system certificate store (ROOT and CA)
auto store = SSL_CTX_get_cert_store(ssl_ctx);
if (!store) return false;
auto hStore = CertOpenSystemStoreW(NULL, L"ROOT");
if (!hStore) return false;
bool loaded_any = false;
PCCERT_CONTEXT pContext = nullptr;
while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
nullptr) {
const unsigned char *data = pContext->pbCertEncoded;
auto x509 = d2i_X509(nullptr, &data, pContext->cbCertEncoded);
if (x509) {
if (X509_STORE_add_cert(store, x509) == 1) { loaded_any = true; }
X509_free(x509);
static const wchar_t *store_names[] = {L"ROOT", L"CA"};
for (auto store_name : store_names) {
auto hStore = CertOpenSystemStoreW(NULL, store_name);
if (!hStore) continue;
PCCERT_CONTEXT pContext = nullptr;
while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
nullptr) {
const unsigned char *data = pContext->pbCertEncoded;
auto x509 = d2i_X509(nullptr, &data, pContext->cbCertEncoded);
if (x509) {
if (X509_STORE_add_cert(store, x509) == 1) { loaded_any = true; }
X509_free(x509);
}
}
CertCloseStore(hStore, 0);
}
CertCloseStore(hStore, 0);
return loaded_any;
#elif defined(__APPLE__)
@@ -15288,6 +15437,17 @@ inline std::string get_cert_serial(cert_t cert) {
return result;
}
inline bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
if (!cert) return false;
auto x509 = static_cast<X509 *>(cert);
auto len = i2d_X509(x509, nullptr);
if (len < 0) return false;
der.resize(static_cast<size_t>(len));
auto p = der.data();
i2d_X509(x509, &p);
return true;
}
inline const char *get_sni(const_session_t session) {
if (!session) return nullptr;
auto ssl = static_cast<SSL *>(const_cast<void *>(session));
@@ -16136,17 +16296,20 @@ inline bool load_system_certs(ctx_t ctx) {
bool loaded = false;
#ifdef _WIN32
// Load from Windows certificate store
HCERTSTORE hStore = CertOpenSystemStoreW(0, L"ROOT");
if (hStore) {
PCCERT_CONTEXT pContext = nullptr;
while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
nullptr) {
int ret = mbedtls_x509_crt_parse_der(
&mctx->ca_chain, pContext->pbCertEncoded, pContext->cbCertEncoded);
if (ret == 0) { loaded = true; }
// Load from Windows certificate store (ROOT and CA)
static const wchar_t *store_names[] = {L"ROOT", L"CA"};
for (auto store_name : store_names) {
HCERTSTORE hStore = CertOpenSystemStoreW(0, store_name);
if (hStore) {
PCCERT_CONTEXT pContext = nullptr;
while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
nullptr) {
int ret = mbedtls_x509_crt_parse_der(
&mctx->ca_chain, pContext->pbCertEncoded, pContext->cbCertEncoded);
if (ret == 0) { loaded = true; }
}
CertCloseStore(hStore, 0);
}
CertCloseStore(hStore, 0);
}
#elif defined(__APPLE__) && defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
// Load from macOS Keychain
@@ -16786,6 +16949,14 @@ inline std::string get_cert_serial(cert_t cert) {
return result;
}
inline bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
if (!cert) return false;
auto crt = static_cast<mbedtls_x509_crt *>(cert);
if (!crt->raw.p || crt->raw.len == 0) return false;
der.assign(crt->raw.p, crt->raw.p + crt->raw.len);
return true;
}
inline const char *get_sni(const_session_t session) {
if (!session) return nullptr;
auto msession = static_cast<const impl::MbedTlsSession *>(session);