diff --git a/scripts/sync_vendor.py b/scripts/sync_vendor.py index 8306cf93ec..402d7bbad3 100755 --- a/scripts/sync_vendor.py +++ b/scripts/sync_vendor.py @@ -5,7 +5,7 @@ import os import sys import subprocess -HTTPLIB_VERSION = "refs/tags/v0.46.1" +HTTPLIB_VERSION = "refs/tags/v0.47.0" vendor = { "https://github.com/nlohmann/json/releases/latest/download/json.hpp": "vendor/nlohmann/json.hpp", diff --git a/vendor/cpp-httplib/httplib.cpp b/vendor/cpp-httplib/httplib.cpp index b9d05d9296..370c0e798d 100644 --- a/vendor/cpp-httplib/httplib.cpp +++ b/vendor/cpp-httplib/httplib.cpp @@ -478,7 +478,7 @@ bool set_socket_opt_time(socket_t sock, int level, int optname, } bool is_hex(char c, int &v) { - if (isdigit(c)) { + if (isdigit(static_cast(c))) { v = c - '0'; return true; } else if ('A' <= c && c <= 'F') { @@ -731,7 +731,7 @@ std::string sha1(const std::string &input) { // Pre-processing: adding padding bits std::string msg = input; uint64_t original_bit_len = static_cast(msg.size()) * 8; - msg.push_back(static_cast(0x80)); + msg.push_back(static_cast(0x80u)); while (msg.size() % 64 != 56) { msg.push_back(0); } @@ -4336,7 +4336,7 @@ bool is_multipart_boundary_chars_valid(const std::string &boundary) { auto valid = true; for (size_t i = 0; i < boundary.size(); i++) { auto c = boundary[i]; - if (!std::isalnum(c) && c != '-' && c != '_') { + if (!std::isalnum(static_cast(c)) && c != '-' && c != '_') { valid = false; break; } @@ -4545,6 +4545,14 @@ void coalesce_ranges(Ranges &ranges, size_t content_length) { bool range_error(Request &req, Response &res) { if (!req.ranges.empty() && 200 <= res.status && res.status < 300) { + if (res.body.empty() && res.content_provider_ && res.content_length_ == 0) { + req.ranges.clear(); + if (res.status == StatusCode::PartialContent_206) { + res.status = StatusCode::OK_200; + } + return false; + } + ssize_t content_len = static_cast( res.content_length_ ? res.content_length_ : res.body.size()); @@ -4813,9 +4821,10 @@ private: namespace fields { bool is_token_char(char c) { - return std::isalnum(c) || c == '!' || c == '#' || c == '$' || c == '%' || - c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' || - c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~'; + return std::isalnum(static_cast(c)) || c == '!' || c == '#' || + c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || + c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || + c == '|' || c == '~'; } bool is_token(const std::string &s) { @@ -5306,41 +5315,68 @@ verify_cert_with_windows_schannel(const std::vector &der_cert, } #endif // _WIN32 -bool setup_client_tls_session(const std::string &host, tls::ctx_t &ctx, +// Loads CA file/dir configuration and applies the system CA policy to a +// client TLS context. PEM data and native stores are applied to the context +// directly at set time; has_custom_store reflects them for the Auto policy +// decision. +bool load_client_ca_config(tls::ctx_t ctx, + const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path, + bool has_custom_store, SystemCAMode mode, + uint64_t &backend_error) { + auto ret = true; + + if (!ca_cert_file_path.empty()) { + if (!tls::load_ca_file(ctx, ca_cert_file_path.c_str())) { + backend_error = tls::get_error(); + ret = false; + } + } else if (!ca_cert_dir_path.empty()) { + if (!tls::load_ca_dir(ctx, ca_cert_dir_path.c_str())) { + backend_error = tls::get_error(); + ret = false; + } + } + + auto has_custom_ca = !ca_cert_file_path.empty() || + !ca_cert_dir_path.empty() || has_custom_store; + if (mode == SystemCAMode::Enabled || + (mode == SystemCAMode::Auto && !has_custom_ca)) { + if (!tls::load_system_certs(ctx)) { backend_error = tls::get_error(); } + } + + return ret; +} + +bool setup_client_tls_session(const std::string &host, tls::ctx_t ctx, tls::session_t &session, socket_t sock, bool server_certificate_verification, - const std::string &ca_cert_file_path, - tls::ca_store_t ca_cert_store, time_t timeout_sec, time_t timeout_usec) { using namespace tls; - ctx = create_client_context(); if (!ctx) { return false; } - if (server_certificate_verification) { - if (!ca_cert_file_path.empty()) { - load_ca_file(ctx, ca_cert_file_path.c_str()); - } - if (ca_cert_store) { set_ca_store(ctx, ca_cert_store); } - load_system_certs(ctx); - } - bool is_ip = is_ip_address(host); -#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT - if (is_ip && server_certificate_verification) { - set_verify_client(ctx, false); - } else { - set_verify_client(ctx, server_certificate_verification); - } +#if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT) + // Chain verification happens during the handshake even for IP hosts; the + // certificate identity is verified post-handshake via verify_hostname() + set_verify_client(ctx, server_certificate_verification); #endif session = create_session(ctx, sock); if (!session) { return false; } - // RFC 6066: SNI must not be set for IP addresses - if (!is_ip) { set_sni(session, host.c_str()); } - if (server_certificate_verification) { set_hostname(session, host.c_str()); } + // RFC 6066: SNI must not be set for IP addresses. On Mbed TLS and wolfSSL + // set_hostname also sets SNI, so it must be skipped for IP hosts as well; + // their identity is checked post-handshake below instead. + if (!is_ip) { + if (server_certificate_verification) { + set_hostname(session, host.c_str()); + } else { + set_sni(session, host.c_str()); + } + } if (!connect_nonblocking(session, sock, timeout_sec, timeout_usec, nullptr)) { return false; @@ -5348,6 +5384,14 @@ bool setup_client_tls_session(const std::string &host, tls::ctx_t &ctx, if (server_certificate_verification) { if (get_verify_result(session) != 0) { return false; } + + // Identity check against the peer certificate, post-handshake for all + // backends (same as SSLClient). For IP hosts this is the only identity + // verification since no hostname is bound during the handshake. + auto server_cert = get_peer_cert(session); + if (!server_cert) { return false; } + auto cert_guard = detail::scope_exit([&] { free_cert(server_cert); }); + if (!verify_hostname(server_cert, host.c_str())) { return false; } } return true; @@ -7194,6 +7238,11 @@ Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { return *this; } +Server &Server::set_start_handler(StartHandler handler) { + start_handler_ = std::move(handler); + return *this; +} + Server &Server::set_address_family(int family) { address_family_ = family; return *this; @@ -7889,6 +7938,8 @@ bool Server::listen_internal() { is_running_ = true; auto se = detail::scope_exit([&]() { is_running_ = false; }); + if (start_handler_) { start_handler_(); } + { std::unique_ptr task_queue(new_task_queue()); @@ -8032,26 +8083,26 @@ bool Server::routing(Request &req, Response &res, Stream &strm) { } } - // Read content into `req.body` - if (!read_content(strm, req, res)) { - output_error_log(Error::Read, &req); - return false; - } + // NOTE: `req.body` is not read here. For a regular handler the body is + // read inside dispatch_request(), after the route has matched and the + // pre-request handler has approved the request, so that a rejected + // request (e.g. failed authentication) never forces us to buffer a + // potentially large body. } // Regular handler if (req.method == "GET" || req.method == "HEAD") { - return dispatch_request(req, res, get_handlers_); + return dispatch_request(req, res, get_handlers_, strm); } else if (req.method == "POST") { - return dispatch_request(req, res, post_handlers_); + return dispatch_request(req, res, post_handlers_, strm); } else if (req.method == "PUT") { - return dispatch_request(req, res, put_handlers_); + return dispatch_request(req, res, put_handlers_, strm); } else if (req.method == "DELETE") { - return dispatch_request(req, res, delete_handlers_); + return dispatch_request(req, res, delete_handlers_, strm); } else if (req.method == "OPTIONS") { - return dispatch_request(req, res, options_handlers_); + return dispatch_request(req, res, options_handlers_, strm); } else if (req.method == "PATCH") { - return dispatch_request(req, res, patch_handlers_); + return dispatch_request(req, res, patch_handlers_, strm); } res.status = StatusCode::BadRequest_400; @@ -8059,17 +8110,29 @@ bool Server::routing(Request &req, Response &res, Stream &strm) { } bool Server::dispatch_request(Request &req, Response &res, - const Handlers &handlers) const { + const Handlers &handlers, Stream &strm) { for (const auto &x : handlers) { const auto &matcher = x.first; const auto &handler = x.second; if (matcher->match(req)) { req.matched_route = matcher->pattern(); - if (!pre_request_handler_ || - pre_request_handler_(req, res) != HandlerResponse::Handled) { - handler(req, res); + + // Run the pre-request handler before reading the body so a rejected + // request (e.g. failed authentication) never forces us to buffer a + // potentially large body. `req.matched_route` is available here. + if (pre_request_handler_ && + pre_request_handler_(req, res) == HandlerResponse::Handled) { + return true; } + + // The route matched and the request was approved; read the body now. + if (detail::expect_content(req) && !read_content(strm, req, res)) { + output_error_log(Error::Read, &req); + return false; + } + + handler(req, res); return true; } } @@ -8638,6 +8701,7 @@ void ClientImpl::copy_settings(const ClientImpl &rhs) { ca_cert_dir_path_ = rhs.ca_cert_dir_path_; server_certificate_verification_ = rhs.server_certificate_verification_; server_hostname_verification_ = rhs.server_hostname_verification_; + system_ca_mode_ = rhs.system_ca_mode_; #endif } @@ -9420,6 +9484,7 @@ bool ClientImpl::create_redirect_client( server_certificate_verification_); redirect_client.enable_server_hostname_verification( server_hostname_verification_); + redirect_client.system_ca_mode_ = system_ca_mode_; // Transfer CA certificate to redirect client if (!ca_cert_pem_.empty()) { @@ -11071,6 +11136,10 @@ void ClientImpl::enable_server_certificate_verification(bool enabled) { void ClientImpl::enable_server_hostname_verification(bool enabled) { server_hostname_verification_ = enabled; } + +void ClientImpl::enable_system_ca(bool enabled) { + system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled; +} #endif void ClientImpl::set_logger(Logger logger) { @@ -12156,6 +12225,7 @@ void SSLClient::set_ca_cert_store(tls::ca_store_t ca_cert_store) { if (ca_cert_store && ctx_) { // set_ca_store takes ownership of ca_cert_store tls::set_ca_store(ctx_, ca_cert_store); + ca_cert_store_set_ = true; } else if (ca_cert_store) { tls::free_ca_store(ca_cert_store); } @@ -12192,21 +12262,10 @@ bool SSLClient::load_certs() { std::call_once(initialize_cert_, [&]() { std::lock_guard guard(ctx_mutex_); - if (!ca_cert_file_path_.empty()) { - if (!tls::load_ca_file(ctx_, ca_cert_file_path_.c_str())) { - last_backend_error_ = tls::get_error(); - ret = false; - } - } else if (!ca_cert_dir_path_.empty()) { - if (!tls::load_ca_dir(ctx_, ca_cert_dir_path_.c_str())) { - last_backend_error_ = tls::get_error(); - ret = false; - } - } else if (ca_cert_pem_.empty()) { - if (!tls::load_system_certs(ctx_)) { - last_backend_error_ = tls::get_error(); - } - } + ret = detail::load_client_ca_config( + ctx_, ca_cert_file_path_, ca_cert_dir_path_, + !ca_cert_pem_.empty() || ca_cert_store_set_, system_ca_mode_, + last_backend_error_); }); return ret; @@ -12229,13 +12288,9 @@ bool SSLClient::initialize_ssl(Socket &socket, Error &error) { #if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT) // MbedTLS/wolfSSL need explicit verification mode (OpenSSL uses // SSL_VERIFY_NONE by default and performs all verification post-handshake). - // For IP addresses with verification enabled, use OPTIONAL mode since - // these backends require hostname for strict verification. - if (is_ip && server_certificate_verification_) { - set_verify_client(ctx_, false); - } else { - set_verify_client(ctx_, server_certificate_verification_); - } + // Chain verification happens during the handshake even for IP hosts; the + // certificate identity is verified post-handshake via verify_hostname(). + set_verify_client(ctx_, server_certificate_verification_); #endif // Create TLS session @@ -12335,9 +12390,12 @@ bool SSLClient::initialize_ssl(Socket &socket, Error &error) { // 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()) { + // store would not know about user-provided CA certificates. Also skip + // when system CA trust is explicitly disabled. + if (enable_windows_cert_verification_ && + system_ca_mode_ != SystemCAMode::Disabled && + ca_cert_file_path_.empty() && ca_cert_dir_path_.empty() && + ca_cert_pem_.empty() && !ca_cert_store_set_) { std::vector der; if (get_cert_der(server_cert, der)) { uint64_t wincrypt_error = 0; @@ -12376,6 +12434,10 @@ void Client::enable_server_hostname_verification(bool enabled) { cli_->enable_server_hostname_verification(enabled); } +void Client::enable_system_ca(bool enabled) { + cli_->enable_system_ca(enabled); +} + #ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE void Client::enable_windows_certificate_verification(bool enabled) { if (is_ssl_) { @@ -12399,7 +12461,10 @@ void Client::set_ca_cert_store(tls::ca_store_t ca_cert_store) { } void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) { - set_ca_cert_store(tls::create_ca_store(ca_cert, size)); + if (is_ssl_) { + // Use the PEM-based path so the CA data is retained for redirect transfer + static_cast(*cli_).load_ca_cert_store(ca_cert, size); + } } void @@ -13041,15 +13106,22 @@ bool set_hostname(session_t session, const char *hostname) { auto ssl = static_cast(session); - // Set SNI (Server Name Indication) - if (!set_sni(session, hostname)) { return false; } - // Enable hostname verification auto param = SSL_get0_param(ssl); if (!param) return false; - X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); - if (X509_VERIFY_PARAM_set1_host(param, hostname, 0) != 1) { return false; } + if (detail::is_ip_address(hostname)) { + // RFC 6066: SNI must not be set for IP addresses; verify against the + // certificate's IP SANs instead of its DNS names + if (X509_VERIFY_PARAM_set1_ip_asc(param, hostname) != 1) { return false; } + } else { + // Set SNI (Server Name Indication) + if (!set_sni(session, hostname)) { return false; } + + X509_VERIFY_PARAM_set_hostflags(param, + X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + if (X509_VERIFY_PARAM_set1_host(param, hostname, 0) != 1) { return false; } + } SSL_set_verify(ssl, SSL_VERIFY_PEER, nullptr); return true; @@ -14279,6 +14351,14 @@ session_t create_session(ctx_t ctx, socket_t sock) { return nullptr; } + // Explicitly opt out of in-handshake hostname verification by default; + // since Mbed TLS 3.6.4 a client handshake with certificate verification + // fails outright when no hostname was set. set_sni() installs the real + // hostname for DNS hosts; for IP hosts (where SNI must not be set) the + // caller verifies the certificate identity post-handshake via + // verify_hostname(). + mbedtls_ssl_set_hostname(&session->ssl, nullptr); + // Set BIO callbacks mbedtls_ssl_set_bio(&session->ssl, &session->sock, impl::mbedtls_net_send_cb, impl::mbedtls_net_recv_cb, nullptr); @@ -14838,10 +14918,17 @@ bool set_ca_store(ctx_t ctx, ca_store_t store) { while (src != nullptr) { int ret = mbedtls_x509_crt_parse_der(&mbed_ctx->ca_chain, src->raw.p, src->raw.len); - if (ret != 0) { return false; } + if (ret != 0) { + free_ca_store(store); + return false; + } src = src->next; } + // This function takes ownership of the store; the chain was deep-copied + // above, so release the source + free_ca_store(store); + // Update the SSL config to use the new CA chain mbedtls_ssl_conf_ca_chain(&mbed_ctx->conf, &mbed_ctx->ca_chain, nullptr); return true; @@ -15985,6 +16072,9 @@ bool set_ca_store(ctx_t ctx, ca_store_t store) { wctx->ctx, reinterpret_cast(ca->pem_data.data()), static_cast(ca->pem_data.size()), SSL_FILETYPE_PEM); if (ret == SSL_SUCCESS) { wctx->ca_pem_data_ += ca->pem_data; } + // This function takes ownership of the store; the PEM data was copied into + // the context, so release the source + free_ca_store(store); return ret == SSL_SUCCESS; } @@ -16343,9 +16433,16 @@ WebSocketClient::WebSocketClient( if (!uc.port.empty() && !detail::parse_port(uc.port, port_)) { return; } path_ = std::move(uc.path); + if (!uc.query.empty()) { path_ += uc.query; } #ifdef CPPHTTPLIB_SSL_ENABLED is_ssl_ = is_ssl; + if (is_ssl_) { + // The context lives as long as the client so that CA configuration + // survives reconnects; sessions are created per connection. + tls_ctx_ = tls::create_client_context(); + if (!tls_ctx_) { return; } + } #else if (is_ssl) { return; } #endif @@ -16354,7 +16451,15 @@ WebSocketClient::WebSocketClient( } } -WebSocketClient::~WebSocketClient() { shutdown_and_close(); } +WebSocketClient::~WebSocketClient() { + shutdown_and_close(); +#ifdef CPPHTTPLIB_SSL_ENABLED + if (tls_ctx_) { + tls::free_context(tls_ctx_); + tls_ctx_ = nullptr; + } +#endif +} bool WebSocketClient::is_valid() const { return is_valid_; } @@ -16366,10 +16471,6 @@ void WebSocketClient::shutdown_and_close() { tls::free_session(tls_session_); tls_session_ = nullptr; } - if (tls_ctx_) { - tls::free_context(tls_ctx_); - tls_ctx_ = nullptr; - } } #endif if (ws_ && ws_->is_open()) { ws_->close(); } @@ -16384,10 +16485,18 @@ void WebSocketClient::shutdown_and_close() { bool WebSocketClient::create_stream(std::unique_ptr &strm) { #ifdef CPPHTTPLIB_SSL_ENABLED if (is_ssl_) { - if (!detail::setup_client_tls_session( - host_, tls_ctx_, tls_session_, sock_, - server_certificate_verification_, ca_cert_file_path_, - ca_cert_store_, read_timeout_sec_, read_timeout_usec_)) { + if (server_certificate_verification_ && !certs_loaded_) { + uint64_t backend_error = 0; + detail::load_client_ca_config(tls_ctx_, ca_cert_file_path_, std::string(), + custom_ca_loaded_, system_ca_mode_, + backend_error); + certs_loaded_ = true; + } + + if (!detail::setup_client_tls_session(host_, tls_ctx_, tls_session_, sock_, + server_certificate_verification_, + read_timeout_sec_, + read_timeout_usec_)) { return false; } @@ -16407,9 +16516,14 @@ bool WebSocketClient::connect() { if (!is_valid_) { return false; } shutdown_and_close(); + // Check is custom IP specified for host_ + std::string ip; + auto it = addr_map_.find(host_); + if (it != addr_map_.end()) { ip = it->second; } + Error error; sock_ = detail::create_client_socket( - host_, std::string(), port_, address_family_, tcp_nodelay_, ipv6_v6only_, + host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_, socket_options_, connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_, error); @@ -16504,6 +16618,11 @@ void WebSocketClient::set_interface(const std::string &intf) { interface_ = intf; } +void WebSocketClient::set_hostname_addr_map( + std::map addr_map) { + addr_map_ = std::move(addr_map); +} + #ifdef CPPHTTPLIB_SSL_ENABLED void WebSocketClient::set_ca_cert_path(const std::string &path) { @@ -16511,7 +16630,21 @@ void WebSocketClient::set_ca_cert_path(const std::string &path) { } void WebSocketClient::set_ca_cert_store(tls::ca_store_t store) { - ca_cert_store_ = store; + if (store && tls_ctx_) { + // set_ca_store takes ownership of store + tls::set_ca_store(tls_ctx_, store); + custom_ca_loaded_ = true; + } else if (store) { + tls::free_ca_store(store); + } +} + +void WebSocketClient::load_ca_cert_store(const char *ca_cert, + std::size_t size) { + if (tls_ctx_ && ca_cert && size > 0) { + tls::load_ca_pem(tls_ctx_, ca_cert, size); + custom_ca_loaded_ = true; + } } void @@ -16519,6 +16652,10 @@ WebSocketClient::enable_server_certificate_verification(bool enabled) { server_certificate_verification_ = enabled; } +void WebSocketClient::enable_system_ca(bool enabled) { + system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled; +} + #endif // CPPHTTPLIB_SSL_ENABLED } // namespace ws diff --git a/vendor/cpp-httplib/httplib.h b/vendor/cpp-httplib/httplib.h index cbb549e712..94d93e88a5 100644 --- a/vendor/cpp-httplib/httplib.h +++ b/vendor/cpp-httplib/httplib.h @@ -8,8 +8,8 @@ #ifndef CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_HTTPLIB_H -#define CPPHTTPLIB_VERSION "0.46.1" -#define CPPHTTPLIB_VERSION_NUM "0x002e01" +#define CPPHTTPLIB_VERSION "0.47.0" +#define CPPHTTPLIB_VERSION_NUM "0x002f00" #ifdef _WIN32 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00 @@ -810,6 +810,11 @@ enum class SSLVerifierResponse { CertificateRejected }; +// System CA loading policy for SSL clients. Auto (the default) loads system +// CA certs only when no custom CA is configured; enable_system_ca() switches +// to an explicit policy. +enum class SystemCAMode { Auto, Enabled, Disabled }; + enum StatusCode { // Information responses Continue_100 = 100, @@ -1643,6 +1648,8 @@ public: using Expect100ContinueHandler = std::function; + using StartHandler = std::function; + using WebSocketHandler = std::function; using SubProtocolSelector = @@ -1694,6 +1701,9 @@ public: Server &set_pre_request_handler(HandlerWithResponse handler); Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); + + Server &set_start_handler(StartHandler handler); + Server &set_logger(Logger logger); Server &set_pre_compression_logger(Logger logger); Server &set_error_logger(ErrorLogger error_logger); @@ -1807,8 +1817,8 @@ private: const std::string &etag, time_t mtime) const; bool check_if_range(Request &req, const std::string &etag, time_t mtime) const; - bool dispatch_request(Request &req, Response &res, - const Handlers &handlers) const; + bool dispatch_request(Request &req, Response &res, const Handlers &handlers, + Stream &strm); bool dispatch_request_for_content_reader( Request &req, Response &res, ContentReader content_reader, const HandlersForContentReader &handlers) const; @@ -1883,6 +1893,7 @@ private: Handler post_routing_handler_; HandlerWithResponse pre_request_handler_; Expect100ContinueHandler expect_100_continue_handler_; + StartHandler start_handler_; mutable std::mutex logger_mutex_; Logger logger_; @@ -2445,6 +2456,7 @@ public: const std::string &ca_cert_dir_path = std::string()); void enable_server_certificate_verification(bool enabled); void enable_server_hostname_verification(bool enabled); + void enable_system_ca(bool enabled); protected: std::string digest_auth_username_; @@ -2455,6 +2467,7 @@ protected: std::string ca_cert_dir_path_; bool server_certificate_verification_ = true; bool server_hostname_verification_ = true; + SystemCAMode system_ca_mode_ = SystemCAMode::Auto; std::string ca_cert_pem_; // Store CA cert PEM for redirect transfer int last_ssl_error_ = 0; uint64_t last_backend_error_ = 0; @@ -2661,6 +2674,7 @@ public: const std::string &password); void enable_server_certificate_verification(bool enabled); void enable_server_hostname_verification(bool enabled); + void enable_system_ca(bool enabled); void set_ca_cert_path(const std::string &ca_cert_file_path, const std::string &ca_cert_dir_path = std::string()); @@ -2798,6 +2812,11 @@ private: std::mutex ctx_mutex_; std::once_flag initialize_cert_; + // Tracks whether a custom CA store was applied via set_ca_cert_store(), + // since the store handle itself is owned by ctx_ and leaves no other trace. + // Used to keep custom CA configuration exclusive with system CA loading. + bool ca_cert_store_set_ = false; + long verify_result_ = 0; std::function session_verifier_; @@ -3842,11 +3861,14 @@ public: void set_socket_options(SocketOptions socket_options); void set_connection_timeout(time_t sec, time_t usec = 0); void set_interface(const std::string &intf); + void set_hostname_addr_map(std::map addr_map); #ifdef CPPHTTPLIB_SSL_ENABLED void set_ca_cert_path(const std::string &path); void set_ca_cert_store(tls::ca_store_t store); + void load_ca_cert_store(const char *ca_cert, std::size_t size); void enable_server_certificate_verification(bool enabled); + void enable_system_ca(bool enabled); #endif private: @@ -3876,12 +3898,17 @@ private: time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; std::string interface_; + // Hostname-IP map + std::map addr_map_; + #ifdef CPPHTTPLIB_SSL_ENABLED bool is_ssl_ = false; tls::ctx_t tls_ctx_ = nullptr; tls::session_t tls_session_ = nullptr; std::string ca_cert_file_path_; - tls::ca_store_t ca_cert_store_ = nullptr; + bool custom_ca_loaded_ = false; + bool certs_loaded_ = false; + SystemCAMode system_ca_mode_ = SystemCAMode::Auto; bool server_certificate_verification_ = true; #endif };