Skip to content

Commit 06debee

Browse files
solarispikaclaude
andcommitted
Default using Windows Schannel for SSL/TLS on Windows
This commit enables Windows Schannel for certificate verification on Windows platforms, providing automatic root certificate updates from Windows Update. Key changes: - Added Windows Schannel certificate verification using CertGetCertificateChain and CertVerifyCertificateChainPolicy APIs - Implemented wincrypt_error() and wincrypt_chain_error() to expose Windows certificate errors alongside existing OpenSSL error fields - Clear ssl_openssl_error before Windows verification to provide unambiguous error source indication (0 means Windows error, non-zero means OpenSSL error) - Updated tests with platform-specific assertions for Windows vs OpenSSL errors - Added comprehensive Windows certificate error documentation to README Certificate verification flow on Windows: 1. OpenSSL performs TLS handshake (can set ssl_error) 2. Windows Schannel verifies certificate chain (sets wincrypt_error and wincrypt_chain_error) 3. Users check wincrypt_error() for Windows-specific error codes like CERT_E_EXPIRED, CERT_E_UNTRUSTEDROOT, CERT_E_REVOKED, etc. Feature can be disabled with CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE or by setting HTTPLIB_USE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE=OFF in CMake. Fixes #1978 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 145fc8b commit 06debee

File tree

4 files changed

+269
-17
lines changed

4 files changed

+269
-17
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

README.md

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,26 @@ if (!res) {
112112
break;
113113

114114
case httplib::Error::SSLServerVerification:
115-
std::cout << "SSL verification failed, X509 error: "
116-
<< res->ssl_openssl_error() << std::endl;
115+
std::cout << "SSL verification failed" << std::endl;
116+
#if defined(_WIN32) && !defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
117+
// On Windows with Schannel, check Windows certificate errors
118+
std::cout << " Windows error: 0x" << std::hex << res->wincrypt_error()
119+
<< ", chain status: 0x" << res->wincrypt_chain_error() << std::endl;
120+
#else
121+
// On other platforms, check OpenSSL errors
122+
std::cout << " X509 error: " << res->ssl_openssl_error() << std::endl;
123+
#endif
117124
break;
118125

119126
case httplib::Error::SSLServerHostnameVerification:
120-
std::cout << "SSL hostname verification failed, X509 error: "
121-
<< res->ssl_openssl_error() << std::endl;
127+
std::cout << "SSL hostname verification failed" << std::endl;
128+
#if defined(_WIN32) && !defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
129+
// On Windows with Schannel, check Windows certificate errors
130+
std::cout << " Windows error: 0x" << std::hex << res->wincrypt_error() << std::endl;
131+
#else
132+
// On other platforms, check OpenSSL errors
133+
std::cout << " X509 error: " << res->ssl_openssl_error() << std::endl;
134+
#endif
122135
break;
123136

124137
default:
@@ -128,6 +141,62 @@ if (!res) {
128141
}
129142
```
130143
144+
For a simpler platform-agnostic approach, you can check which error field is non-zero:
145+
146+
```c++
147+
auto res = cli.Get("/");
148+
if (!res) {
149+
if (res.error() == httplib::Error::SSLServerVerification) {
150+
std::cout << "Certificate verification failed!" << std::endl;
151+
152+
// Check which backend reported the error
153+
if (res->ssl_openssl_error() != 0) {
154+
// OpenSSL reported the error (Linux, macOS, or Windows with Schannel disabled)
155+
std::cout << "OpenSSL error: " << res->ssl_openssl_error() << std::endl;
156+
}
157+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
158+
#if defined(_WIN32) && !defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
159+
else if (res->wincrypt_error() != 0) {
160+
// Windows Schannel reported the error
161+
std::cout << "Windows error: 0x" << std::hex << res->wincrypt_error() << std::endl;
162+
std::cout << "Chain status: 0x" << std::hex << res->wincrypt_chain_error() << std::endl;
163+
}
164+
#endif
165+
#endif
166+
}
167+
}
168+
```
169+
170+
### Windows Certificate Verification
171+
172+
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.
173+
174+
**When certificate verification fails on Windows:**
175+
- OpenSSL still handles the TLS handshake
176+
- Windows Schannel performs certificate verification
177+
- Check `wincrypt_error()` and `wincrypt_chain_error()` for diagnostic information
178+
- The `ssl_openssl_error()` field will be 0 for certificate verification errors
179+
180+
**Windows-specific error codes** (`wincrypt_error()`) include:
181+
- `CERT_E_EXPIRED` (0x800B0101) - Certificate has expired
182+
- `CERT_E_UNTRUSTEDROOT` (0x800B0109) - Certificate chain to untrusted root
183+
- `CERT_E_CN_NO_MATCH` (0x800B010F) - Certificate CN doesn't match hostname
184+
- `CERT_E_REVOKED` (0x800B010C) - Certificate has been revoked
185+
- `CERT_E_CHAINING` (0x800B010A) - Error building certificate chain
186+
187+
**Chain trust status** (`wincrypt_chain_error()`) provides additional details:
188+
- `CERT_TRUST_IS_NOT_TIME_VALID` (0x00000001) - Certificate expired
189+
- `CERT_TRUST_IS_REVOKED` (0x00000004) - Certificate revoked
190+
- `CERT_TRUST_IS_NOT_SIGNATURE_VALID` (0x00000008) - Invalid signature
191+
- `CERT_TRUST_IS_UNTRUSTED_ROOT` (0x00000020) - Untrusted root
192+
193+
To disable Windows automatic certificate updates and use OpenSSL verification:
194+
```cmake
195+
set(HTTPLIB_USE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE OFF)
196+
```
197+
198+
Or define `CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE` before including httplib.h.
199+
131200
Server
132201
------
133202

0 commit comments

Comments
 (0)