mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-14 10:29:35 +00:00
Add enable_system_ca() and unify WebSocketClient CA handling (#2471)
Add an explicit opt-in for loading system CA certs alongside a custom CA, addressing the request in #2471. The default behavior is unchanged: a custom CA remains exclusive. - Add Client/SSLClient/WebSocketClient::enable_system_ca(bool); the policy carries over to redirect clients - Extract the CA loading policy into detail::load_client_ca_config() shared by SSLClient and WebSocketClient, making WebSocketClient exclusive by default (it previously always merged system certs) - Make the WebSocketClient TLS context live as long as the client, fixing a use-after-free when reconnecting after set_ca_cert_store() - Free the source store in the Mbed TLS and wolfSSL set_ca_store() backends, honoring the take-ownership contract (memory leak) - Verify IP hosts against IP SANs in the OpenSSL set_hostname() backend so WebSocket connections to IP hosts can use full verification
This commit is contained in:
1 parent
e7e7bf7b44
commit
39ec7d0508
2 files changed
+326
-48
No files matched your search
@@ -810,6 +810,11 @@ enum class SSLVerifierResponse {
|
|||||||
CertificateRejected
|
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 {
|
enum StatusCode {
|
||||||
// Information responses
|
// Information responses
|
||||||
Continue_100 = 100,
|
Continue_100 = 100,
|
||||||
@@ -2451,6 +2456,7 @@ public:
|
|||||||
const std::string &ca_cert_dir_path = std::string());
|
const std::string &ca_cert_dir_path = std::string());
|
||||||
void enable_server_certificate_verification(bool enabled);
|
void enable_server_certificate_verification(bool enabled);
|
||||||
void enable_server_hostname_verification(bool enabled);
|
void enable_server_hostname_verification(bool enabled);
|
||||||
|
void enable_system_ca(bool enabled);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string digest_auth_username_;
|
std::string digest_auth_username_;
|
||||||
@@ -2461,6 +2467,7 @@ protected:
|
|||||||
std::string ca_cert_dir_path_;
|
std::string ca_cert_dir_path_;
|
||||||
bool server_certificate_verification_ = true;
|
bool server_certificate_verification_ = true;
|
||||||
bool server_hostname_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
|
std::string ca_cert_pem_; // Store CA cert PEM for redirect transfer
|
||||||
int last_ssl_error_ = 0;
|
int last_ssl_error_ = 0;
|
||||||
uint64_t last_backend_error_ = 0;
|
uint64_t last_backend_error_ = 0;
|
||||||
@@ -2667,6 +2674,7 @@ public:
|
|||||||
const std::string &password);
|
const std::string &password);
|
||||||
void enable_server_certificate_verification(bool enabled);
|
void enable_server_certificate_verification(bool enabled);
|
||||||
void enable_server_hostname_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,
|
void set_ca_cert_path(const std::string &ca_cert_file_path,
|
||||||
const std::string &ca_cert_dir_path = std::string());
|
const std::string &ca_cert_dir_path = std::string());
|
||||||
|
|
||||||
@@ -3858,7 +3866,9 @@ public:
|
|||||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||||
void set_ca_cert_path(const std::string &path);
|
void set_ca_cert_path(const std::string &path);
|
||||||
void set_ca_cert_store(tls::ca_store_t store);
|
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_server_certificate_verification(bool enabled);
|
||||||
|
void enable_system_ca(bool enabled);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -3896,7 +3906,9 @@ private:
|
|||||||
tls::ctx_t tls_ctx_ = nullptr;
|
tls::ctx_t tls_ctx_ = nullptr;
|
||||||
tls::session_t tls_session_ = nullptr;
|
tls::session_t tls_session_ = nullptr;
|
||||||
std::string ca_cert_file_path_;
|
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;
|
bool server_certificate_verification_ = true;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
@@ -9228,25 +9240,47 @@ verify_cert_with_windows_schannel(const std::vector<unsigned char> &der_cert,
|
|||||||
}
|
}
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|
||||||
inline 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.
|
||||||
|
inline 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool setup_client_tls_session(const std::string &host, tls::ctx_t ctx,
|
||||||
tls::session_t &session, socket_t sock,
|
tls::session_t &session, socket_t sock,
|
||||||
bool server_certificate_verification,
|
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) {
|
time_t timeout_sec, time_t timeout_usec) {
|
||||||
using namespace tls;
|
using namespace tls;
|
||||||
|
|
||||||
ctx = create_client_context();
|
|
||||||
if (!ctx) { return false; }
|
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);
|
bool is_ip = is_ip_address(host);
|
||||||
|
|
||||||
#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
|
#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
|
||||||
@@ -12579,6 +12613,7 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
|
|||||||
ca_cert_dir_path_ = rhs.ca_cert_dir_path_;
|
ca_cert_dir_path_ = rhs.ca_cert_dir_path_;
|
||||||
server_certificate_verification_ = rhs.server_certificate_verification_;
|
server_certificate_verification_ = rhs.server_certificate_verification_;
|
||||||
server_hostname_verification_ = rhs.server_hostname_verification_;
|
server_hostname_verification_ = rhs.server_hostname_verification_;
|
||||||
|
system_ca_mode_ = rhs.system_ca_mode_;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13361,6 +13396,7 @@ inline bool ClientImpl::create_redirect_client(
|
|||||||
server_certificate_verification_);
|
server_certificate_verification_);
|
||||||
redirect_client.enable_server_hostname_verification(
|
redirect_client.enable_server_hostname_verification(
|
||||||
server_hostname_verification_);
|
server_hostname_verification_);
|
||||||
|
redirect_client.system_ca_mode_ = system_ca_mode_;
|
||||||
|
|
||||||
// Transfer CA certificate to redirect client
|
// Transfer CA certificate to redirect client
|
||||||
if (!ca_cert_pem_.empty()) {
|
if (!ca_cert_pem_.empty()) {
|
||||||
@@ -15012,6 +15048,10 @@ inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
|
|||||||
inline void ClientImpl::enable_server_hostname_verification(bool enabled) {
|
inline void ClientImpl::enable_server_hostname_verification(bool enabled) {
|
||||||
server_hostname_verification_ = enabled;
|
server_hostname_verification_ = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void ClientImpl::enable_system_ca(bool enabled) {
|
||||||
|
system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
inline void ClientImpl::set_logger(Logger logger) {
|
inline void ClientImpl::set_logger(Logger logger) {
|
||||||
@@ -16134,21 +16174,10 @@ inline bool SSLClient::load_certs() {
|
|||||||
std::call_once(initialize_cert_, [&]() {
|
std::call_once(initialize_cert_, [&]() {
|
||||||
std::lock_guard<std::mutex> guard(ctx_mutex_);
|
std::lock_guard<std::mutex> guard(ctx_mutex_);
|
||||||
|
|
||||||
if (!ca_cert_file_path_.empty()) {
|
ret = detail::load_client_ca_config(
|
||||||
if (!tls::load_ca_file(ctx_, ca_cert_file_path_.c_str())) {
|
ctx_, ca_cert_file_path_, ca_cert_dir_path_,
|
||||||
last_backend_error_ = tls::get_error();
|
!ca_cert_pem_.empty() || ca_cert_store_set_, system_ca_mode_,
|
||||||
ret = false;
|
last_backend_error_);
|
||||||
}
|
|
||||||
} 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() && !ca_cert_store_set_) {
|
|
||||||
if (!tls::load_system_certs(ctx_)) {
|
|
||||||
last_backend_error_ = tls::get_error();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@@ -16277,10 +16306,12 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
|||||||
// This provides real-time certificate validation with Windows Update
|
// This provides real-time certificate validation with Windows Update
|
||||||
// integration, working with both OpenSSL and MbedTLS backends.
|
// integration, working with both OpenSSL and MbedTLS backends.
|
||||||
// Skip when a custom CA cert is specified, as the Windows certificate
|
// Skip when a custom CA cert is specified, as the Windows certificate
|
||||||
// store would not know about user-provided CA certificates.
|
// store would not know about user-provided CA certificates. Also skip
|
||||||
if (enable_windows_cert_verification_ && ca_cert_file_path_.empty() &&
|
// when system CA trust is explicitly disabled.
|
||||||
ca_cert_dir_path_.empty() && ca_cert_pem_.empty() &&
|
if (enable_windows_cert_verification_ &&
|
||||||
!ca_cert_store_set_) {
|
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<unsigned char> der;
|
std::vector<unsigned char> der;
|
||||||
if (get_cert_der(server_cert, der)) {
|
if (get_cert_der(server_cert, der)) {
|
||||||
uint64_t wincrypt_error = 0;
|
uint64_t wincrypt_error = 0;
|
||||||
@@ -16319,6 +16350,10 @@ inline void Client::enable_server_hostname_verification(bool enabled) {
|
|||||||
cli_->enable_server_hostname_verification(enabled);
|
cli_->enable_server_hostname_verification(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void Client::enable_system_ca(bool enabled) {
|
||||||
|
cli_->enable_system_ca(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
||||||
inline void Client::enable_windows_certificate_verification(bool enabled) {
|
inline void Client::enable_windows_certificate_verification(bool enabled) {
|
||||||
if (is_ssl_) {
|
if (is_ssl_) {
|
||||||
@@ -16987,15 +17022,22 @@ inline bool set_hostname(session_t session, const char *hostname) {
|
|||||||
|
|
||||||
auto ssl = static_cast<SSL *>(session);
|
auto ssl = static_cast<SSL *>(session);
|
||||||
|
|
||||||
// Set SNI (Server Name Indication)
|
|
||||||
if (!set_sni(session, hostname)) { return false; }
|
|
||||||
|
|
||||||
// Enable hostname verification
|
// Enable hostname verification
|
||||||
auto param = SSL_get0_param(ssl);
|
auto param = SSL_get0_param(ssl);
|
||||||
if (!param) return false;
|
if (!param) return false;
|
||||||
|
|
||||||
X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
|
if (detail::is_ip_address(hostname)) {
|
||||||
if (X509_VERIFY_PARAM_set1_host(param, hostname, 0) != 1) { return false; }
|
// 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);
|
SSL_set_verify(ssl, SSL_VERIFY_PEER, nullptr);
|
||||||
return true;
|
return true;
|
||||||
@@ -18784,10 +18826,17 @@ inline bool set_ca_store(ctx_t ctx, ca_store_t store) {
|
|||||||
while (src != nullptr) {
|
while (src != nullptr) {
|
||||||
int ret = mbedtls_x509_crt_parse_der(&mbed_ctx->ca_chain, src->raw.p,
|
int ret = mbedtls_x509_crt_parse_der(&mbed_ctx->ca_chain, src->raw.p,
|
||||||
src->raw.len);
|
src->raw.len);
|
||||||
if (ret != 0) { return false; }
|
if (ret != 0) {
|
||||||
|
free_ca_store(store);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
src = src->next;
|
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
|
// Update the SSL config to use the new CA chain
|
||||||
mbedtls_ssl_conf_ca_chain(&mbed_ctx->conf, &mbed_ctx->ca_chain, nullptr);
|
mbedtls_ssl_conf_ca_chain(&mbed_ctx->conf, &mbed_ctx->ca_chain, nullptr);
|
||||||
return true;
|
return true;
|
||||||
@@ -19931,6 +19980,9 @@ inline bool set_ca_store(ctx_t ctx, ca_store_t store) {
|
|||||||
wctx->ctx, reinterpret_cast<const unsigned char *>(ca->pem_data.data()),
|
wctx->ctx, reinterpret_cast<const unsigned char *>(ca->pem_data.data()),
|
||||||
static_cast<long>(ca->pem_data.size()), SSL_FILETYPE_PEM);
|
static_cast<long>(ca->pem_data.size()), SSL_FILETYPE_PEM);
|
||||||
if (ret == SSL_SUCCESS) { wctx->ca_pem_data_ += ca->pem_data; }
|
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;
|
return ret == SSL_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20293,6 +20345,12 @@ inline WebSocketClient::WebSocketClient(
|
|||||||
|
|
||||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||||
is_ssl_ = is_ssl;
|
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
|
#else
|
||||||
if (is_ssl) { return; }
|
if (is_ssl) { return; }
|
||||||
#endif
|
#endif
|
||||||
@@ -20301,7 +20359,15 @@ inline WebSocketClient::WebSocketClient(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline WebSocketClient::~WebSocketClient() { shutdown_and_close(); }
|
inline WebSocketClient::~WebSocketClient() {
|
||||||
|
shutdown_and_close();
|
||||||
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||||
|
if (tls_ctx_) {
|
||||||
|
tls::free_context(tls_ctx_);
|
||||||
|
tls_ctx_ = nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
inline bool WebSocketClient::is_valid() const { return is_valid_; }
|
inline bool WebSocketClient::is_valid() const { return is_valid_; }
|
||||||
|
|
||||||
@@ -20313,10 +20379,6 @@ inline void WebSocketClient::shutdown_and_close() {
|
|||||||
tls::free_session(tls_session_);
|
tls::free_session(tls_session_);
|
||||||
tls_session_ = nullptr;
|
tls_session_ = nullptr;
|
||||||
}
|
}
|
||||||
if (tls_ctx_) {
|
|
||||||
tls::free_context(tls_ctx_);
|
|
||||||
tls_ctx_ = nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (ws_ && ws_->is_open()) { ws_->close(); }
|
if (ws_ && ws_->is_open()) { ws_->close(); }
|
||||||
@@ -20331,10 +20393,18 @@ inline void WebSocketClient::shutdown_and_close() {
|
|||||||
inline bool WebSocketClient::create_stream(std::unique_ptr<Stream> &strm) {
|
inline bool WebSocketClient::create_stream(std::unique_ptr<Stream> &strm) {
|
||||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||||
if (is_ssl_) {
|
if (is_ssl_) {
|
||||||
if (!detail::setup_client_tls_session(
|
if (server_certificate_verification_ && !certs_loaded_) {
|
||||||
host_, tls_ctx_, tls_session_, sock_,
|
uint64_t backend_error = 0;
|
||||||
server_certificate_verification_, ca_cert_file_path_,
|
detail::load_client_ca_config(tls_ctx_, ca_cert_file_path_, std::string(),
|
||||||
ca_cert_store_, read_timeout_sec_, read_timeout_usec_)) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20468,7 +20538,21 @@ inline void WebSocketClient::set_ca_cert_path(const std::string &path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void WebSocketClient::set_ca_cert_store(tls::ca_store_t store) {
|
inline 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
@@ -20476,6 +20560,10 @@ WebSocketClient::enable_server_certificate_verification(bool enabled) {
|
|||||||
server_certificate_verification_ = enabled;
|
server_certificate_verification_ = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void WebSocketClient::enable_system_ca(bool enabled) {
|
||||||
|
system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled;
|
||||||
|
}
|
||||||
|
|
||||||
#endif // CPPHTTPLIB_SSL_ENABLED
|
#endif // CPPHTTPLIB_SSL_ENABLED
|
||||||
|
|
||||||
} // namespace ws
|
} // namespace ws
|
||||||
|
|||||||
+190
@@ -11728,6 +11728,114 @@ TEST(UniversalClientRedirectTest, LoadCaCertStore) {
|
|||||||
ASSERT_EQ(StatusCode::OK_200, res->status);
|
ASSERT_EQ(StatusCode::OK_200, res->status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enable_system_ca(true) loads system CA certs in addition to a custom CA,
|
||||||
|
// so a host signed by a system CA verifies even though a custom CA is set
|
||||||
|
TEST(SSLClientTest, EnableSystemCaWithCustomCa_Online) {
|
||||||
|
std::string cert;
|
||||||
|
read_file(SERVER_CERT2_FILE, cert);
|
||||||
|
|
||||||
|
SSLClient cli("google.com");
|
||||||
|
cli.load_ca_cert_store(cert.c_str(), cert.size());
|
||||||
|
cli.enable_system_ca(true);
|
||||||
|
cli.enable_server_certificate_verification(true);
|
||||||
|
|
||||||
|
auto res = cli.Get("/");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
ASSERT_EQ(StatusCode::MovedPermanently_301, res->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The custom CA remains effective when combined with the system CA
|
||||||
|
TEST(SSLClientTest, EnableSystemCaCustomCaVerifiesLocalServer) {
|
||||||
|
SSLServer svr(SERVER_CERT2_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||||
|
ASSERT_TRUE(svr.is_valid());
|
||||||
|
svr.Get("/test", [&](const Request &, Response &res) {
|
||||||
|
res.set_content("test", "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
thread t = thread([&]() { ASSERT_TRUE(svr.listen("127.0.0.1", PORT)); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
SSLClient cli("127.0.0.1", PORT);
|
||||||
|
std::string cert;
|
||||||
|
read_file(SERVER_CERT2_FILE, cert);
|
||||||
|
cli.load_ca_cert_store(cert.c_str(), cert.size());
|
||||||
|
cli.enable_system_ca(true);
|
||||||
|
cli.enable_server_certificate_verification(true);
|
||||||
|
cli.set_connection_timeout(30);
|
||||||
|
|
||||||
|
auto res = cli.Get("/test");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
ASSERT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable_system_ca(false) prevents system CA loading entirely
|
||||||
|
TEST(SSLClientTest, DisableSystemCa_Online) {
|
||||||
|
SSLClient cli("google.com");
|
||||||
|
cli.enable_system_ca(false);
|
||||||
|
cli.enable_server_certificate_verification(true);
|
||||||
|
|
||||||
|
auto res = cli.Get("/");
|
||||||
|
ASSERT_FALSE(res);
|
||||||
|
// OpenSSL reports a verification failure; Mbed TLS fails the handshake
|
||||||
|
// itself when the CA chain is empty
|
||||||
|
EXPECT_TRUE(res.error() == Error::SSLServerVerification ||
|
||||||
|
res.error() == Error::SSLConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The universal Client forwards enable_system_ca()
|
||||||
|
TEST(UniversalClientTest, EnableSystemCaWithCustomCa_Online) {
|
||||||
|
std::string cert;
|
||||||
|
read_file(SERVER_CERT2_FILE, cert);
|
||||||
|
|
||||||
|
Client cli("https://google.com");
|
||||||
|
cli.load_ca_cert_store(cert.c_str(), cert.size());
|
||||||
|
cli.enable_system_ca(true);
|
||||||
|
cli.enable_server_certificate_verification(true);
|
||||||
|
|
||||||
|
auto res = cli.Get("/");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
ASSERT_EQ(StatusCode::MovedPermanently_301, res->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The system CA policy must carry over to the client created internally for
|
||||||
|
// following a cross-host HTTPS redirect
|
||||||
|
TEST(UniversalClientRedirectTest, EnableSystemCaCarriesOver_Online) {
|
||||||
|
SSLServer svr(SERVER_CERT2_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||||
|
ASSERT_TRUE(svr.is_valid());
|
||||||
|
svr.Get("/index", [&](const Request &, Response &res) {
|
||||||
|
res.set_redirect("https://www.google.com/");
|
||||||
|
});
|
||||||
|
|
||||||
|
thread t = thread([&]() { ASSERT_TRUE(svr.listen("127.0.0.1", PORT)); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
Client cli("https://127.0.0.1:" + std::to_string(PORT));
|
||||||
|
std::string cert;
|
||||||
|
read_file(SERVER_CERT2_FILE, cert);
|
||||||
|
cli.load_ca_cert_store(cert.c_str(), cert.size());
|
||||||
|
cli.enable_system_ca(true);
|
||||||
|
cli.enable_server_certificate_verification(true);
|
||||||
|
cli.set_follow_location(true);
|
||||||
|
cli.set_connection_timeout(30);
|
||||||
|
|
||||||
|
// Receiving any response proves the redirect client completed the TLS
|
||||||
|
// handshake with a system-CA-signed host
|
||||||
|
auto res = cli.Get("/index");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(MultipartFormDataTest, LargeData) {
|
TEST(MultipartFormDataTest, LargeData) {
|
||||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||||
|
|
||||||
@@ -18445,6 +18553,88 @@ TEST_F(WebSocketSSLIntegrationTest, TextEcho) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Limited to the OpenSSL backend (like WebSocketSSLIntegrationTest above):
|
||||||
|
// Mbed TLS and wolfSSL disable certificate verification for IP hosts in
|
||||||
|
// setup_client_tls_session, so the assertions below would not hold there.
|
||||||
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
|
class WebSocketSSLCATest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
server_ = httplib::detail::make_unique<SSLServer>(SERVER_CERT2_FILE,
|
||||||
|
SERVER_PRIVATE_KEY_FILE);
|
||||||
|
server_->WebSocket("/echo", [](const Request &, ws::WebSocket &ws) {
|
||||||
|
std::string msg;
|
||||||
|
while (ws.read(msg)) {
|
||||||
|
ws.send(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
port_ = server_->bind_to_any_port("127.0.0.1");
|
||||||
|
server_thread_ = std::thread([this]() { server_->listen_after_bind(); });
|
||||||
|
server_->wait_until_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
server_->stop();
|
||||||
|
if (server_thread_.joinable()) { server_thread_.join(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string url() const {
|
||||||
|
return "wss://127.0.0.1:" + std::to_string(port_) + "/echo";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<SSLServer> server_;
|
||||||
|
std::thread server_thread_;
|
||||||
|
int port_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A custom CA loaded as PEM verifies the server with full certificate
|
||||||
|
// verification enabled
|
||||||
|
TEST_F(WebSocketSSLCATest, LoadCaCertStoreVerifiesServer) {
|
||||||
|
ws::WebSocketClient client(url());
|
||||||
|
std::string cert;
|
||||||
|
read_file(SERVER_CERT2_FILE, cert);
|
||||||
|
client.load_ca_cert_store(cert.c_str(), cert.size());
|
||||||
|
|
||||||
|
ASSERT_TRUE(client.connect());
|
||||||
|
ASSERT_TRUE(client.send("hello"));
|
||||||
|
std::string msg;
|
||||||
|
EXPECT_EQ(ws::Text, client.read(msg));
|
||||||
|
EXPECT_EQ("hello", msg);
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// A custom CA that does not cover the server must fail verification (the
|
||||||
|
// custom CA is exclusive; system certs are not loaded alongside it)
|
||||||
|
TEST_F(WebSocketSSLCATest, WrongCustomCaFailsVerification) {
|
||||||
|
ws::WebSocketClient client(url());
|
||||||
|
std::string cert;
|
||||||
|
read_file(CLIENT_CA_CERT_FILE, cert);
|
||||||
|
client.load_ca_cert_store(cert.c_str(), cert.size());
|
||||||
|
|
||||||
|
ASSERT_FALSE(client.connect());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regression test: reconnecting with a native custom CA store used to reuse
|
||||||
|
// a store handle the previous TLS context had already freed (use-after-free
|
||||||
|
// under the OpenSSL backend). The context now lives as long as the client.
|
||||||
|
TEST_F(WebSocketSSLCATest, ReconnectWithCustomCaStore) {
|
||||||
|
ws::WebSocketClient client(url());
|
||||||
|
std::string cert;
|
||||||
|
read_file(SERVER_CERT2_FILE, cert);
|
||||||
|
client.set_ca_cert_store(tls::create_ca_store(cert.c_str(), cert.size()));
|
||||||
|
|
||||||
|
ASSERT_TRUE(client.connect());
|
||||||
|
client.close();
|
||||||
|
|
||||||
|
ASSERT_TRUE(client.connect());
|
||||||
|
ASSERT_TRUE(client.send("again"));
|
||||||
|
std::string msg;
|
||||||
|
EXPECT_EQ(ws::Text, client.read(msg));
|
||||||
|
EXPECT_EQ("again", msg);
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#if !defined(_WIN32)
|
#if !defined(_WIN32)
|
||||||
TEST(SymlinkTest, SymlinkEscapeFromBaseDirectory) {
|
TEST(SymlinkTest, SymlinkEscapeFromBaseDirectory) {
|
||||||
auto secret_dir = std::string("./symlink_test_secret");
|
auto secret_dir = std::string("./symlink_test_secret");
|
||||||
|
|||||||
Reference in new issue
Block a user