Require on-the-wire SNI to pass IsValidSNI check

This requirement existed in QUIC Crypto; it should exist when we run QUIC
with TLS.

Restrict sni in ietf quic draft versions. protected by reloadable flag quic_tls_enforce_valid_sni.

PiperOrigin-RevId: 310054163
Change-Id: I9ffdea55c350e9c1592a71debb3fbb271eca7750
diff --git a/quic/core/tls_client_handshaker.cc b/quic/core/tls_client_handshaker.cc
index 324c378..09ab71e 100644
--- a/quic/core/tls_client_handshaker.cc
+++ b/quic/core/tls_client_handshaker.cc
@@ -12,6 +12,7 @@
 #include "net/third_party/quiche/src/quic/core/crypto/quic_encrypter.h"
 #include "net/third_party/quiche/src/quic/core/crypto/transport_parameters.h"
 #include "net/third_party/quiche/src/quic/core/quic_session.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_hostname_utils.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h"
 
@@ -87,7 +88,14 @@
 
   // Set the SNI to send, if any.
   SSL_set_connect_state(ssl());
+  if (QUIC_DLOG_INFO_IS_ON() &&
+      !QuicHostnameUtils::IsValidSNI(server_id_.host())) {
+    QUIC_DLOG(INFO) << "Client configured with invalid hostname \""
+                    << server_id_.host() << "\", not sending as SNI";
+  }
   if (!server_id_.host().empty() &&
+      (QuicHostnameUtils::IsValidSNI(server_id_.host()) ||
+       allow_invalid_sni_for_tests_) &&
       SSL_set_tlsext_host_name(ssl(), server_id_.host().c_str()) != 1) {
     return false;
   }
diff --git a/quic/core/tls_client_handshaker.h b/quic/core/tls_client_handshaker.h
index fdd68c2..cc314b7 100644
--- a/quic/core/tls_client_handshaker.h
+++ b/quic/core/tls_client_handshaker.h
@@ -75,6 +75,7 @@
       std::unique_ptr<ApplicationState> application_state) override;
 
   void AllowEmptyAlpnForTests() { allow_empty_alpn_for_tests_ = true; }
+  void AllowInvalidSNIForTests() { allow_invalid_sni_for_tests_ = true; }
 
  protected:
   const TlsConnection* tls_connection() const override {
@@ -169,6 +170,7 @@
       crypto_negotiated_params_;
 
   bool allow_empty_alpn_for_tests_ = false;
+  bool allow_invalid_sni_for_tests_ = false;
 
   const bool has_application_state_;
 
diff --git a/quic/core/tls_client_handshaker_test.cc b/quic/core/tls_client_handshaker_test.cc
index fe757b4..2629bd4 100644
--- a/quic/core/tls_client_handshaker_test.cc
+++ b/quic/core/tls_client_handshaker_test.cc
@@ -392,6 +392,26 @@
   EXPECT_TRUE(server_stream()->encryption_established());
 }
 
+TEST_P(TlsClientHandshakerTest, InvalidSNI) {
+  // Test that a client will skip sending SNI if configured to send an invalid
+  // hostname. In this case, the inclusion of '!' is invalid.
+  server_id_ = QuicServerId("invalid!.example.com", 443);
+  crypto_config_.reset(new QuicCryptoClientConfig(
+      std::make_unique<FakeProofVerifier>(), nullptr));
+  CreateConnection();
+  InitializeFakeServer();
+
+  stream()->CryptoConnect();
+  crypto_test_utils::CommunicateHandshakeMessages(
+      connection_, stream(), server_connection_, server_stream());
+
+  EXPECT_EQ(PROTOCOL_TLS1_3, stream()->handshake_protocol());
+  EXPECT_TRUE(stream()->encryption_established());
+  EXPECT_TRUE(stream()->one_rtt_keys_available());
+
+  EXPECT_EQ(server_stream()->crypto_negotiated_params().sni, "");
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/tls_server_handshaker.cc b/quic/core/tls_server_handshaker.cc
index 890abae..54a066f 100644
--- a/quic/core/tls_server_handshaker.cc
+++ b/quic/core/tls_server_handshaker.cc
@@ -484,6 +484,15 @@
     hostname_ = hostname;
     crypto_negotiated_params_->sni =
         QuicHostnameUtils::NormalizeHostname(hostname_);
+    if (GetQuicReloadableFlag(quic_tls_enforce_valid_sni)) {
+      QUIC_RELOADABLE_FLAG_COUNT(quic_tls_enforce_valid_sni);
+      if (!QuicHostnameUtils::IsValidSNI(hostname_)) {
+        // TODO(b/151676147): Include this error string in the CONNECTION_CLOSE
+        // frame.
+        QUIC_LOG(ERROR) << "Invalid SNI provided: \"" << hostname_ << "\"";
+        return SSL_TLSEXT_ERR_ALERT_FATAL;
+      }
+    }
   } else {
     QUIC_LOG(INFO) << "No hostname indicated in SNI";
   }
diff --git a/quic/core/tls_server_handshaker_test.cc b/quic/core/tls_server_handshaker_test.cc
index 28f13c6..89fb325 100644
--- a/quic/core/tls_server_handshaker_test.cc
+++ b/quic/core/tls_server_handshaker_test.cc
@@ -356,6 +356,20 @@
   ExpectHandshakeSuccessful();
 }
 
+TEST_F(TlsServerHandshakerTest, RejectInvalidSNI) {
+  SetQuicReloadableFlag(quic_tls_enforce_valid_sni, true);
+  server_id_ = QuicServerId("invalid!.example.com", kServerPort, false);
+  InitializeFakeClient();
+  static_cast<TlsClientHandshaker*>(
+      QuicCryptoClientStreamPeer::GetHandshaker(client_stream()))
+      ->AllowInvalidSNIForTests();
+
+  // Run the handshake and expect it to fail.
+  AdvanceHandshakeWithFakeClient();
+  EXPECT_FALSE(server_stream()->encryption_established());
+  EXPECT_FALSE(server_stream()->one_rtt_keys_available());
+}
+
 TEST_F(TlsServerHandshakerTest, Resumption) {
   // Do the first handshake
   InitializeFakeClient();