Add WebSocketClient::enable_server_hostname_verification

WebSocketClient's TLS setup already threaded
ClientTlsSessionOptions::server_hostname_verification through
setup_client_tls_session(), the same path SSLClient uses, but never
exposed a way to set it: create_stream() called setup_client_tls_session()
without an options argument, so the default (verification on) was the
only reachable value.

Add the public setter, mirroring ClientImpl/SSLClient/Client, and wire
it into create_stream()'s ClientTlsSessionOptions. Last open item from
issue #2531's WebSocketClient/SSLClient API alignment.
This commit is contained in:
yhirose committed 2026-08-07 18:48:59 -04:00
1 parent 6018c7feb3
commit 86d0210391
3 files changed
+34 -5

No files matched your search

+2
View File
@@ -188,6 +188,7 @@ 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());
void set_ca_cert_store(tls::ca_store_t store); void set_ca_cert_store(tls::ca_store_t store);
void enable_server_certificate_verification(bool enabled); void enable_server_certificate_verification(bool enabled);
void enable_server_hostname_verification(bool enabled);
``` ```
## Examples ## Examples
@@ -395,6 +396,7 @@ if (ws.connect()) {
httplib::ws::WebSocketClient ws("wss://example.com/ws"); httplib::ws::WebSocketClient ws("wss://example.com/ws");
ws.set_ca_cert_path("/path/to/ca-bundle.crt"); ws.set_ca_cert_path("/path/to/ca-bundle.crt");
ws.enable_server_certificate_verification(true); ws.enable_server_certificate_verification(true);
ws.enable_server_hostname_verification(true); // default; false skips the identity check
if (ws.connect()) { if (ws.connect()) {
ws.send("secure message"); ws.send("secure message");
+15 -5
View File
@@ -4360,6 +4360,7 @@ public:
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 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_server_hostname_verification(bool enabled);
void enable_system_ca(bool enabled); void enable_system_ca(bool enabled);
#endif #endif
@@ -4406,6 +4407,7 @@ private:
bool certs_loaded_ = false; bool certs_loaded_ = false;
SystemCAMode system_ca_mode_ = SystemCAMode::Auto; SystemCAMode system_ca_mode_ = SystemCAMode::Auto;
bool server_certificate_verification_ = true; bool server_certificate_verification_ = true;
bool server_hostname_verification_ = true;
#endif #endif
}; };
@@ -10137,11 +10139,12 @@ inline bool load_client_ca_config(tls::ctx_t ctx,
return ret; return ret;
} }
// The parts of session setup that only SSLClient needs. WebSocketClient takes // The parts of session setup that only SSLClient needs, plus the handful
// the defaults, which is what keeps the two clients on one implementation. // WebSocketClient also exposes; everything else takes the defaults, which is
// what keeps the two clients on one implementation.
struct ClientTlsSessionOptions { struct ClientTlsSessionOptions {
// SSLClient exposes this independently of certificate verification; // Both SSLClient and WebSocketClient expose this independently of
// WebSocketClient always checks the identity when it verifies the chain. // certificate verification.
bool server_hostname_verification = true; bool server_hostname_verification = true;
std::function<SSLVerifierResponse(tls::session_t)> session_verifier; std::function<SSLVerifierResponse(tls::session_t)> session_verifier;
// When non-null, guards session creation against concurrent use of the // When non-null, guards session creation against concurrent use of the
@@ -21463,11 +21466,14 @@ inline bool WebSocketClient::create_stream(std::unique_ptr<Stream> &strm,
certs_loaded_ = true; certs_loaded_ = true;
} }
detail::ClientTlsSessionOptions options;
options.server_hostname_verification = server_hostname_verification_;
detail::ClientTlsSessionError tls_error; detail::ClientTlsSessionError tls_error;
if (!detail::setup_client_tls_session(host_, tls_ctx_, tls_session_, sock_, if (!detail::setup_client_tls_session(host_, tls_ctx_, tls_session_, sock_,
server_certificate_verification_, server_certificate_verification_,
read_timeout_sec_, read_timeout_usec_, read_timeout_sec_, read_timeout_usec_,
&tls_error)) { &tls_error, options)) {
error = tls_error.error; error = tls_error.error;
ssl_error = tls_error.ssl_error; ssl_error = tls_error.ssl_error;
ssl_backend_error = tls_error.backend_error; ssl_backend_error = tls_error.backend_error;
@@ -21660,6 +21666,10 @@ WebSocketClient::enable_server_certificate_verification(bool enabled) {
server_certificate_verification_ = enabled; server_certificate_verification_ = enabled;
} }
inline void WebSocketClient::enable_server_hostname_verification(bool enabled) {
server_hostname_verification_ = enabled;
}
inline void WebSocketClient::enable_system_ca(bool enabled) { inline void WebSocketClient::enable_system_ca(bool enabled) {
system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled; system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled;
} }
+17
View File
@@ -21123,6 +21123,23 @@ TEST_F(WebSocketSSLDnsHostTest, TrustedChainWrongNameFails) {
EXPECT_EQ(-1, res.status()); EXPECT_EQ(-1, res.status());
} }
// Same setup as TrustedChainWrongNameFails, but with hostname verification
// disabled: the chain is still checked, only the identity check is skipped
TEST_F(WebSocketSSLDnsHostTest, HostnameVerificationDisabledAcceptsWrongName) {
Start(SERVER_CERT_FILE);
ws::WebSocketClient client(url());
client.set_ca_cert_path(SERVER_CERT_FILE);
client.enable_server_hostname_verification(false);
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 CA that did not sign the server certificate fails the chain, even though // A CA that did not sign the server certificate fails the chain, even though
// the name would match // the name would match
TEST_F(WebSocketSSLDnsHostTest, UntrustedChainFails) { TEST_F(WebSocketSSLDnsHostTest, UntrustedChainFails) {