Procházet zdrojové kódy

Drop the SNI of a failed handshake on the Mbed TLS server

The Mbed TLS server keeps the SNI from the ClientHello in a
thread-local until the handshake succeeds, and its SNI callback only
runs for a ClientHello that carries one. A handshake that failed left
its SNI behind, so the next connection served by the same thread
without SNI reported the earlier client's name in req.sni(). Start
each handshake with the thread-local cleared.
yhirose před 3 dny
rodič
revize
7a9cee0e05
2 změnil soubory, kde provedl 44 přidání a 2 odebrání
  1. 6 2
      httplib.h
  2. 38 0
      test/test.cc

+ 6 - 2
httplib.h

@@ -21467,6 +21467,9 @@ inline TlsError connect(session_t session) {
 }
 
 inline TlsError accept(session_t session) {
+  // A failed handshake leaves its SNI behind, so start without it
+  impl::mbedpending_sni().clear();
+
   // Same as connect for Mbed TLS - handshake works for both client and server
   auto result = connect(session);
 
@@ -21474,7 +21477,6 @@ inline TlsError accept(session_t session) {
   if (result.code == ErrorCode::Success && session) {
     auto msession = static_cast<impl::MbedTlsSession *>(session);
     msession->sni_hostname = std::move(impl::mbedpending_sni());
-    impl::mbedpending_sni().clear();
   }
 
   return result;
@@ -21519,6 +21521,9 @@ inline bool connect_nonblocking(session_t session, socket_t sock,
 inline bool accept_nonblocking(session_t session, socket_t sock,
                                time_t timeout_sec, time_t timeout_usec,
                                TlsError *err) {
+  // A failed handshake leaves its SNI behind, so start without it
+  impl::mbedpending_sni().clear();
+
   // Same implementation as connect for Mbed TLS
   bool result =
       connect_nonblocking(session, sock, timeout_sec, timeout_usec, err);
@@ -21527,7 +21532,6 @@ inline bool accept_nonblocking(session_t session, socket_t sock,
   if (result && session) {
     auto msession = static_cast<impl::MbedTlsSession *>(session);
     msession->sni_hostname = std::move(impl::mbedpending_sni());
-    impl::mbedpending_sni().clear();
   }
 
   return result;

+ 38 - 0
test/test.cc

@@ -12977,6 +12977,44 @@ TEST(SNI_AutoDetectionTest, SNI_Logic) {
     }
   }
 }
+
+// The SNI of a failed handshake does not reach the next connection that the
+// same worker thread serves
+TEST(SNI_AutoDetectionTest, FailedHandshakeLeavesNoSNI) {
+  SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
+  ASSERT_TRUE(svr.is_valid());
+  svr.new_task_queue = [] { return new ThreadPool(1); };
+  svr.Get("/sni", [](const Request &req, Response &res) {
+    res.set_content(req.sni(), "text/plain");
+  });
+
+  auto port = svr.bind_to_any_port("127.0.0.1");
+  ASSERT_LT(0, port);
+  thread t = thread([&] { svr.listen_after_bind(); });
+  auto se = detail::scope_exit([&] {
+    svr.stop();
+    t.join();
+    ASSERT_FALSE(svr.is_running());
+  });
+  svr.wait_until_ready();
+
+  {
+    // Sends SNI, then rejects the server certificate, which the CA did not sign
+    SSLClient cli("localhost", port);
+    cli.set_hostname_addr_map({{"localhost", "127.0.0.1"}});
+    cli.set_ca_cert_path(CLIENT_CA_CERT_FILE);
+    auto res = cli.Get("/sni");
+    ASSERT_FALSE(res);
+    EXPECT_EQ(Error::SSLServerVerification, res.error());
+  }
+
+  // An IP host sends no SNI (RFC 6066)
+  SSLClient cli("127.0.0.1", port);
+  cli.enable_server_certificate_verification(false);
+  auto res = cli.Get("/sni");
+  ASSERT_TRUE(res) << "Error: " << to_string(res.error());
+  EXPECT_EQ("", res->body);
+}
 #endif
 
 TEST(ClientProblemDetectionTest, ContentProvider) {