@@ -810,6 +810,11 @@ enum class SSLVerifierResponse {
810810 CertificateRejected
811811};
812812
813+ // System CA loading policy for SSL clients. Auto (the default) loads system
814+ // CA certs only when no custom CA is configured; enable_system_ca() switches
815+ // to an explicit policy.
816+ enum class SystemCAMode { Auto, Enabled, Disabled };
817+
813818enum StatusCode {
814819 // Information responses
815820 Continue_100 = 100,
@@ -2451,6 +2456,7 @@ class ClientImpl {
24512456 const std::string &ca_cert_dir_path = std::string());
24522457 void enable_server_certificate_verification(bool enabled);
24532458 void enable_server_hostname_verification(bool enabled);
2459+ void enable_system_ca(bool enabled);
24542460
24552461protected:
24562462 std::string digest_auth_username_;
@@ -2461,6 +2467,7 @@ class ClientImpl {
24612467 std::string ca_cert_dir_path_;
24622468 bool server_certificate_verification_ = true;
24632469 bool server_hostname_verification_ = true;
2470+ SystemCAMode system_ca_mode_ = SystemCAMode::Auto;
24642471 std::string ca_cert_pem_; // Store CA cert PEM for redirect transfer
24652472 int last_ssl_error_ = 0;
24662473 uint64_t last_backend_error_ = 0;
@@ -2667,6 +2674,7 @@ class Client {
26672674 const std::string &password);
26682675 void enable_server_certificate_verification(bool enabled);
26692676 void enable_server_hostname_verification(bool enabled);
2677+ void enable_system_ca(bool enabled);
26702678 void set_ca_cert_path(const std::string &ca_cert_file_path,
26712679 const std::string &ca_cert_dir_path = std::string());
26722680
@@ -3858,7 +3866,9 @@ class WebSocketClient {
38583866#ifdef CPPHTTPLIB_SSL_ENABLED
38593867 void set_ca_cert_path(const std::string &path);
38603868 void set_ca_cert_store(tls::ca_store_t store);
3869+ void load_ca_cert_store(const char *ca_cert, std::size_t size);
38613870 void enable_server_certificate_verification(bool enabled);
3871+ void enable_system_ca(bool enabled);
38623872#endif
38633873
38643874private:
@@ -3896,7 +3906,9 @@ class WebSocketClient {
38963906 tls::ctx_t tls_ctx_ = nullptr;
38973907 tls::session_t tls_session_ = nullptr;
38983908 std::string ca_cert_file_path_;
3899- tls::ca_store_t ca_cert_store_ = nullptr;
3909+ bool custom_ca_loaded_ = false;
3910+ bool certs_loaded_ = false;
3911+ SystemCAMode system_ca_mode_ = SystemCAMode::Auto;
39003912 bool server_certificate_verification_ = true;
39013913#endif
39023914};
@@ -9228,25 +9240,47 @@ verify_cert_with_windows_schannel(const std::vector<unsigned char> &der_cert,
92289240}
92299241#endif // _WIN32
92309242
9231- inline bool setup_client_tls_session(const std::string &host, tls::ctx_t &ctx,
9243+ // Loads CA file/dir configuration and applies the system CA policy to a
9244+ // client TLS context. PEM data and native stores are applied to the context
9245+ // directly at set time; has_custom_store reflects them for the Auto policy
9246+ // decision.
9247+ inline bool load_client_ca_config(tls::ctx_t ctx,
9248+ const std::string &ca_cert_file_path,
9249+ const std::string &ca_cert_dir_path,
9250+ bool has_custom_store, SystemCAMode mode,
9251+ uint64_t &backend_error) {
9252+ auto ret = true;
9253+
9254+ if (!ca_cert_file_path.empty()) {
9255+ if (!tls::load_ca_file(ctx, ca_cert_file_path.c_str())) {
9256+ backend_error = tls::get_error();
9257+ ret = false;
9258+ }
9259+ } else if (!ca_cert_dir_path.empty()) {
9260+ if (!tls::load_ca_dir(ctx, ca_cert_dir_path.c_str())) {
9261+ backend_error = tls::get_error();
9262+ ret = false;
9263+ }
9264+ }
9265+
9266+ auto has_custom_ca = !ca_cert_file_path.empty() ||
9267+ !ca_cert_dir_path.empty() || has_custom_store;
9268+ if (mode == SystemCAMode::Enabled ||
9269+ (mode == SystemCAMode::Auto && !has_custom_ca)) {
9270+ if (!tls::load_system_certs(ctx)) { backend_error = tls::get_error(); }
9271+ }
9272+
9273+ return ret;
9274+ }
9275+
9276+ inline bool setup_client_tls_session(const std::string &host, tls::ctx_t ctx,
92329277 tls::session_t &session, socket_t sock,
92339278 bool server_certificate_verification,
9234- const std::string &ca_cert_file_path,
9235- tls::ca_store_t ca_cert_store,
92369279 time_t timeout_sec, time_t timeout_usec) {
92379280 using namespace tls;
92389281
9239- ctx = create_client_context();
92409282 if (!ctx) { return false; }
92419283
9242- if (server_certificate_verification) {
9243- if (!ca_cert_file_path.empty()) {
9244- load_ca_file(ctx, ca_cert_file_path.c_str());
9245- }
9246- if (ca_cert_store) { set_ca_store(ctx, ca_cert_store); }
9247- load_system_certs(ctx);
9248- }
9249-
92509284 bool is_ip = is_ip_address(host);
92519285
92529286#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
@@ -12579,6 +12613,7 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
1257912613 ca_cert_dir_path_ = rhs.ca_cert_dir_path_;
1258012614 server_certificate_verification_ = rhs.server_certificate_verification_;
1258112615 server_hostname_verification_ = rhs.server_hostname_verification_;
12616+ system_ca_mode_ = rhs.system_ca_mode_;
1258212617#endif
1258312618}
1258412619
@@ -13361,6 +13396,7 @@ inline bool ClientImpl::create_redirect_client(
1336113396 server_certificate_verification_);
1336213397 redirect_client.enable_server_hostname_verification(
1336313398 server_hostname_verification_);
13399+ redirect_client.system_ca_mode_ = system_ca_mode_;
1336413400
1336513401 // Transfer CA certificate to redirect client
1336613402 if (!ca_cert_pem_.empty()) {
@@ -15012,6 +15048,10 @@ inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
1501215048inline void ClientImpl::enable_server_hostname_verification(bool enabled) {
1501315049 server_hostname_verification_ = enabled;
1501415050}
15051+
15052+ inline void ClientImpl::enable_system_ca(bool enabled) {
15053+ system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled;
15054+ }
1501515055#endif
1501615056
1501715057inline void ClientImpl::set_logger(Logger logger) {
@@ -16134,21 +16174,10 @@ inline bool SSLClient::load_certs() {
1613416174 std::call_once(initialize_cert_, [&]() {
1613516175 std::lock_guard<std::mutex> guard(ctx_mutex_);
1613616176
16137- if (!ca_cert_file_path_.empty()) {
16138- if (!tls::load_ca_file(ctx_, ca_cert_file_path_.c_str())) {
16139- last_backend_error_ = tls::get_error();
16140- ret = false;
16141- }
16142- } else if (!ca_cert_dir_path_.empty()) {
16143- if (!tls::load_ca_dir(ctx_, ca_cert_dir_path_.c_str())) {
16144- last_backend_error_ = tls::get_error();
16145- ret = false;
16146- }
16147- } else if (ca_cert_pem_.empty() && !ca_cert_store_set_) {
16148- if (!tls::load_system_certs(ctx_)) {
16149- last_backend_error_ = tls::get_error();
16150- }
16151- }
16177+ ret = detail::load_client_ca_config(
16178+ ctx_, ca_cert_file_path_, ca_cert_dir_path_,
16179+ !ca_cert_pem_.empty() || ca_cert_store_set_, system_ca_mode_,
16180+ last_backend_error_);
1615216181 });
1615316182
1615416183 return ret;
@@ -16277,10 +16306,12 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
1627716306 // This provides real-time certificate validation with Windows Update
1627816307 // integration, working with both OpenSSL and MbedTLS backends.
1627916308 // Skip when a custom CA cert is specified, as the Windows certificate
16280- // store would not know about user-provided CA certificates.
16281- if (enable_windows_cert_verification_ && ca_cert_file_path_.empty() &&
16282- ca_cert_dir_path_.empty() && ca_cert_pem_.empty() &&
16283- !ca_cert_store_set_) {
16309+ // store would not know about user-provided CA certificates. Also skip
16310+ // when system CA trust is explicitly disabled.
16311+ if (enable_windows_cert_verification_ &&
16312+ system_ca_mode_ != SystemCAMode::Disabled &&
16313+ ca_cert_file_path_.empty() && ca_cert_dir_path_.empty() &&
16314+ ca_cert_pem_.empty() && !ca_cert_store_set_) {
1628416315 std::vector<unsigned char> der;
1628516316 if (get_cert_der(server_cert, der)) {
1628616317 uint64_t wincrypt_error = 0;
@@ -16319,6 +16350,10 @@ inline void Client::enable_server_hostname_verification(bool enabled) {
1631916350 cli_->enable_server_hostname_verification(enabled);
1632016351}
1632116352
16353+ inline void Client::enable_system_ca(bool enabled) {
16354+ cli_->enable_system_ca(enabled);
16355+ }
16356+
1632216357#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
1632316358inline void Client::enable_windows_certificate_verification(bool enabled) {
1632416359 if (is_ssl_) {
@@ -16987,15 +17022,22 @@ inline bool set_hostname(session_t session, const char *hostname) {
1698717022
1698817023 auto ssl = static_cast<SSL *>(session);
1698917024
16990- // Set SNI (Server Name Indication)
16991- if (!set_sni(session, hostname)) { return false; }
16992-
1699317025 // Enable hostname verification
1699417026 auto param = SSL_get0_param(ssl);
1699517027 if (!param) return false;
1699617028
16997- X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
16998- if (X509_VERIFY_PARAM_set1_host(param, hostname, 0) != 1) { return false; }
17029+ if (detail::is_ip_address(hostname)) {
17030+ // RFC 6066: SNI must not be set for IP addresses; verify against the
17031+ // certificate's IP SANs instead of its DNS names
17032+ if (X509_VERIFY_PARAM_set1_ip_asc(param, hostname) != 1) { return false; }
17033+ } else {
17034+ // Set SNI (Server Name Indication)
17035+ if (!set_sni(session, hostname)) { return false; }
17036+
17037+ X509_VERIFY_PARAM_set_hostflags(param,
17038+ X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
17039+ if (X509_VERIFY_PARAM_set1_host(param, hostname, 0) != 1) { return false; }
17040+ }
1699917041
1700017042 SSL_set_verify(ssl, SSL_VERIFY_PEER, nullptr);
1700117043 return true;
@@ -18784,10 +18826,17 @@ inline bool set_ca_store(ctx_t ctx, ca_store_t store) {
1878418826 while (src != nullptr) {
1878518827 int ret = mbedtls_x509_crt_parse_der(&mbed_ctx->ca_chain, src->raw.p,
1878618828 src->raw.len);
18787- if (ret != 0) { return false; }
18829+ if (ret != 0) {
18830+ free_ca_store(store);
18831+ return false;
18832+ }
1878818833 src = src->next;
1878918834 }
1879018835
18836+ // This function takes ownership of the store; the chain was deep-copied
18837+ // above, so release the source
18838+ free_ca_store(store);
18839+
1879118840 // Update the SSL config to use the new CA chain
1879218841 mbedtls_ssl_conf_ca_chain(&mbed_ctx->conf, &mbed_ctx->ca_chain, nullptr);
1879318842 return true;
@@ -19931,6 +19980,9 @@ inline bool set_ca_store(ctx_t ctx, ca_store_t store) {
1993119980 wctx->ctx, reinterpret_cast<const unsigned char *>(ca->pem_data.data()),
1993219981 static_cast<long>(ca->pem_data.size()), SSL_FILETYPE_PEM);
1993319982 if (ret == SSL_SUCCESS) { wctx->ca_pem_data_ += ca->pem_data; }
19983+ // This function takes ownership of the store; the PEM data was copied into
19984+ // the context, so release the source
19985+ free_ca_store(store);
1993419986 return ret == SSL_SUCCESS;
1993519987}
1993619988
@@ -20293,6 +20345,12 @@ inline WebSocketClient::WebSocketClient(
2029320345
2029420346#ifdef CPPHTTPLIB_SSL_ENABLED
2029520347 is_ssl_ = is_ssl;
20348+ if (is_ssl_) {
20349+ // The context lives as long as the client so that CA configuration
20350+ // survives reconnects; sessions are created per connection.
20351+ tls_ctx_ = tls::create_client_context();
20352+ if (!tls_ctx_) { return; }
20353+ }
2029620354#else
2029720355 if (is_ssl) { return; }
2029820356#endif
@@ -20301,7 +20359,15 @@ inline WebSocketClient::WebSocketClient(
2030120359 }
2030220360}
2030320361
20304- inline WebSocketClient::~WebSocketClient() { shutdown_and_close(); }
20362+ inline WebSocketClient::~WebSocketClient() {
20363+ shutdown_and_close();
20364+ #ifdef CPPHTTPLIB_SSL_ENABLED
20365+ if (tls_ctx_) {
20366+ tls::free_context(tls_ctx_);
20367+ tls_ctx_ = nullptr;
20368+ }
20369+ #endif
20370+ }
2030520371
2030620372inline bool WebSocketClient::is_valid() const { return is_valid_; }
2030720373
@@ -20313,10 +20379,6 @@ inline void WebSocketClient::shutdown_and_close() {
2031320379 tls::free_session(tls_session_);
2031420380 tls_session_ = nullptr;
2031520381 }
20316- if (tls_ctx_) {
20317- tls::free_context(tls_ctx_);
20318- tls_ctx_ = nullptr;
20319- }
2032020382 }
2032120383#endif
2032220384 if (ws_ && ws_->is_open()) { ws_->close(); }
@@ -20331,10 +20393,18 @@ inline void WebSocketClient::shutdown_and_close() {
2033120393inline bool WebSocketClient::create_stream(std::unique_ptr<Stream> &strm) {
2033220394#ifdef CPPHTTPLIB_SSL_ENABLED
2033320395 if (is_ssl_) {
20334- if (!detail::setup_client_tls_session(
20335- host_, tls_ctx_, tls_session_, sock_,
20336- server_certificate_verification_, ca_cert_file_path_,
20337- ca_cert_store_, read_timeout_sec_, read_timeout_usec_)) {
20396+ if (server_certificate_verification_ && !certs_loaded_) {
20397+ uint64_t backend_error = 0;
20398+ detail::load_client_ca_config(tls_ctx_, ca_cert_file_path_, std::string(),
20399+ custom_ca_loaded_, system_ca_mode_,
20400+ backend_error);
20401+ certs_loaded_ = true;
20402+ }
20403+
20404+ if (!detail::setup_client_tls_session(host_, tls_ctx_, tls_session_, sock_,
20405+ server_certificate_verification_,
20406+ read_timeout_sec_,
20407+ read_timeout_usec_)) {
2033820408 return false;
2033920409 }
2034020410
@@ -20468,14 +20538,32 @@ inline void WebSocketClient::set_ca_cert_path(const std::string &path) {
2046820538}
2046920539
2047020540inline void WebSocketClient::set_ca_cert_store(tls::ca_store_t store) {
20471- ca_cert_store_ = store;
20541+ if (store && tls_ctx_) {
20542+ // set_ca_store takes ownership of store
20543+ tls::set_ca_store(tls_ctx_, store);
20544+ custom_ca_loaded_ = true;
20545+ } else if (store) {
20546+ tls::free_ca_store(store);
20547+ }
20548+ }
20549+
20550+ inline void WebSocketClient::load_ca_cert_store(const char *ca_cert,
20551+ std::size_t size) {
20552+ if (tls_ctx_ && ca_cert && size > 0) {
20553+ tls::load_ca_pem(tls_ctx_, ca_cert, size);
20554+ custom_ca_loaded_ = true;
20555+ }
2047220556}
2047320557
2047420558inline void
2047520559WebSocketClient::enable_server_certificate_verification(bool enabled) {
2047620560 server_certificate_verification_ = enabled;
2047720561}
2047820562
20563+ inline void WebSocketClient::enable_system_ca(bool enabled) {
20564+ system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled;
20565+ }
20566+
2047920567#endif // CPPHTTPLIB_SSL_ENABLED
2048020568
2048120569} // namespace ws
0 commit comments