Przeglądaj źródła

Let WebSocketClient take a CA directory the way ClientImpl does

WebSocketClient::set_ca_cert_path took a single path and create_stream()
hardcoded an empty directory when calling detail::load_client_ca_config, while
ClientImpl has always accepted (ca_cert_file_path, ca_cert_dir_path = ""). Give
WebSocketClient the same signature and store the directory, so both clients
configure CA loading identically. The one-argument form is unchanged for
callers.

Also note at both call sites why the "load the CA config once" guard differs:
SSLClient needs call_once because one client serves concurrent requests, and
WebSocketClient does not because connect() is not safe to call concurrently
anyway.
yhirose 2 miesięcy temu
rodzic
commit
d2ef193b9c
3 zmienionych plików z 44 dodań i 7 usunięć
  1. 6 1
      README-websocket.md
  2. 16 6
      httplib.h
  3. 22 0
      test/test.cc

+ 6 - 1
README-websocket.md

@@ -155,9 +155,14 @@ bool is_open() const;
 // Timeouts
 void set_read_timeout(time_t sec, time_t usec = 0);
 void set_write_timeout(time_t sec, time_t usec = 0);
+template <class Rep, class Period>
+void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
+template <class Rep, class Period>
+void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
 
 // SSL configuration (wss:// only, requires CPPHTTPLIB_OPENSSL_SUPPORT)
-void set_ca_cert_path(const std::string &path);
+void set_ca_cert_path(const std::string &ca_cert_file_path,
+                      const std::string &ca_cert_dir_path = std::string());
 void set_ca_cert_store(tls::ca_store_t store);
 void enable_server_certificate_verification(bool enabled);
 ```

+ 16 - 6
httplib.h

@@ -4302,7 +4302,8 @@ public:
   void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
 
 #ifdef CPPHTTPLIB_SSL_ENABLED
-  void set_ca_cert_path(const std::string &path);
+  void set_ca_cert_path(const std::string &ca_cert_file_path,
+                        const std::string &ca_cert_dir_path = std::string());
   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);
@@ -4346,6 +4347,7 @@ private:
   tls::ctx_t tls_ctx_ = nullptr;
   tls::session_t tls_session_ = nullptr;
   std::string ca_cert_file_path_;
+  std::string ca_cert_dir_path_;
   bool custom_ca_loaded_ = false;
   bool certs_loaded_ = false;
   SystemCAMode system_ca_mode_ = SystemCAMode::Auto;
@@ -17077,6 +17079,8 @@ inline void SSLClient::load_ca_cert_store(const char *ca_cert,
 inline bool SSLClient::load_certs() {
   auto ret = true;
 
+  // call_once rather than the plain flag WebSocketClient::create_stream() uses:
+  // one client is shared across concurrent requests here.
   std::call_once(initialize_cert_, [&]() {
     std::lock_guard<std::mutex> guard(ctx_mutex_);
 
@@ -21352,11 +21356,14 @@ inline void WebSocketClient::shutdown_and_close() {
 inline bool WebSocketClient::create_stream(std::unique_ptr<Stream> &strm) {
 #ifdef CPPHTTPLIB_SSL_ENABLED
   if (is_ssl_) {
+    // A plain flag rather than SSLClient::load_certs()'s call_once: connect()
+    // is not safe to call concurrently on one client to begin with, since
+    // nothing else here is guarded either.
     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);
+      detail::load_client_ca_config(tls_ctx_, ca_cert_file_path_,
+                                    ca_cert_dir_path_, custom_ca_loaded_,
+                                    system_ca_mode_, backend_error);
       certs_loaded_ = true;
     }
 
@@ -21518,8 +21525,11 @@ inline void WebSocketClient::set_hostname_addr_map(
 
 #ifdef CPPHTTPLIB_SSL_ENABLED
 
-inline void WebSocketClient::set_ca_cert_path(const std::string &path) {
-  ca_cert_file_path_ = path;
+inline void
+WebSocketClient::set_ca_cert_path(const std::string &ca_cert_file_path,
+                                  const std::string &ca_cert_dir_path) {
+  ca_cert_file_path_ = ca_cert_file_path;
+  ca_cert_dir_path_ = ca_cert_dir_path;
 }
 
 inline void WebSocketClient::set_ca_cert_store(tls::ca_store_t store) {

+ 22 - 0
test/test.cc

@@ -20958,6 +20958,28 @@ TEST_F(WebSocketSSLCATest, WrongCustomCaFailsVerification) {
   ASSERT_FALSE(client.connect());
 }
 
+// The same CA as a file path rather than PEM in memory
+TEST_F(WebSocketSSLCATest, SetCaCertPathVerifiesServer) {
+  ws::WebSocketClient client(url());
+  client.set_ca_cert_path(SERVER_CERT2_FILE);
+
+  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();
+}
+
+// ...and a CA file that does not cover the server still fails, so it is the
+// path above that decides the outcome
+TEST_F(WebSocketSSLCATest, WrongCaCertPathFailsVerification) {
+  ws::WebSocketClient client(url());
+  client.set_ca_cert_path(CLIENT_CA_CERT_FILE);
+
+  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.