Log encrypted ClientHellos through QuicConnectionDebugVisitor

As TLS messages are encrypted, it can be useful to log the cleartext versions
for debugging. QUIC already has callbacks to observe CRYPTO frames, so we,
broadly, already do this.

However, when ECH is enabled, the ClientHelloInner is sent encrypted. For
debugging, BoringSSL exposes this through the message callback under a custom
"content type". Chromium, when using ECH over TCP, exposes this through NetLog.

This CL registers the same callback in QUICHE and exports it out of
QuicConnectionDebugVisitor. I went ahead and did both sending (client) and
receiving (server), though receiving is currently moot since we don't do ECH
for QUIC servers yet.

PiperOrigin-RevId: 532598203
diff --git a/quiche/quic/core/crypto/tls_connection.cc b/quiche/quic/core/crypto/tls_connection.cc
index 1b54b14..1977aa9 100644
--- a/quiche/quic/core/crypto/tls_connection.cc
+++ b/quiche/quic/core/crypto/tls_connection.cc
@@ -132,6 +132,7 @@
   SSL_CTX_set_min_proto_version(ssl_ctx.get(), TLS1_3_VERSION);
   SSL_CTX_set_max_proto_version(ssl_ctx.get(), TLS1_3_VERSION);
   SSL_CTX_set_quic_method(ssl_ctx.get(), &kSslQuicMethod);
+  SSL_CTX_set_msg_callback(ssl_ctx.get(), &MessageCallback);
   return ssl_ctx;
 }
 
@@ -203,4 +204,13 @@
   return 1;
 }
 
+// static
+void TlsConnection::MessageCallback(int is_write, int version, int content_type,
+                                    const void* buf, size_t len, SSL* ssl,
+                                    void*) {
+  ConnectionFromSsl(ssl)->delegate_->MessageCallback(
+      is_write != 0, version, content_type,
+      absl::string_view(static_cast<const char*>(buf), len));
+}
+
 }  // namespace quic
diff --git a/quiche/quic/core/crypto/tls_connection.h b/quiche/quic/core/crypto/tls_connection.h
index 5c4e8b8..ca9f70b 100644
--- a/quiche/quic/core/crypto/tls_connection.h
+++ b/quiche/quic/core/crypto/tls_connection.h
@@ -79,6 +79,12 @@
     // See |SSL_CTX_set_info_callback| for the meaning of |type| and |value|.
     virtual void InfoCallback(int type, int value) = 0;
 
+    // Message callback from BoringSSL, for debugging purposes. See
+    // |SSL_CTX_set_msg_callback| for how to interpret |version|,
+    // |content_type|, and |data|.
+    virtual void MessageCallback(bool is_write, int version, int content_type,
+                                 absl::string_view data) = 0;
+
     friend class TlsConnection;
   };
 
@@ -142,6 +148,8 @@
   static int FlushFlightCallback(SSL* ssl);
   static int SendAlertCallback(SSL* ssl, enum ssl_encryption_level_t level,
                                uint8_t desc);
+  static void MessageCallback(int is_write, int version, int content_type,
+                              const void* buf, size_t len, SSL* ssl, void* arg);
 
   Delegate* delegate_;
   bssl::UniquePtr<SSL> ssl_;
diff --git a/quiche/quic/core/handshaker_delegate_interface.h b/quiche/quic/core/handshaker_delegate_interface.h
index d6e2501..9c0adef 100644
--- a/quiche/quic/core/handshaker_delegate_interface.h
+++ b/quiche/quic/core/handshaker_delegate_interface.h
@@ -78,6 +78,14 @@
   // Get the QUIC version currently in use. tls_handshaker needs this to pass
   // to crypto_utils to apply version-dependent HKDF labels.
   virtual ParsedQuicVersion parsed_version() const = 0;
+
+  // Called after an ClientHelloInner is encrypted and sent as a client.
+  virtual void OnEncryptedClientHelloSent(
+      absl::string_view client_hello) const = 0;
+
+  // Called after an ClientHelloInner is received and decrypted as a server.
+  virtual void OnEncryptedClientHelloReceived(
+      absl::string_view client_hello) const = 0;
 };
 
 }  // namespace quic
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index fd0bb95..4c2fe67 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -1172,6 +1172,20 @@
   }
 }
 
+void QuicConnection::OnEncryptedClientHelloSent(
+    absl::string_view client_hello) const {
+  if (debug_visitor_ != nullptr) {
+    debug_visitor_->OnEncryptedClientHelloSent(client_hello);
+  }
+}
+
+void QuicConnection::OnEncryptedClientHelloReceived(
+    absl::string_view client_hello) const {
+  if (debug_visitor_ != nullptr) {
+    debug_visitor_->OnEncryptedClientHelloReceived(client_hello);
+  }
+}
+
 bool QuicConnection::HasPendingAcks() const { return ack_alarm_->IsSet(); }
 
 void QuicConnection::OnUserAgentIdKnown(const std::string& /*user_agent_id*/) {
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index a5dfe53..fbfd952 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -459,6 +459,13 @@
 
   // Called after peer migration is validated.
   virtual void OnPeerMigrationValidated(QuicTime::Delta /*connection_time*/) {}
+
+  // Called after an ClientHelloInner is encrypted and sent as a client.
+  virtual void OnEncryptedClientHelloSent(absl::string_view /*client_hello*/) {}
+
+  // Called after an ClientHelloInner is received and decrypted as a server.
+  virtual void OnEncryptedClientHelloReceived(
+      absl::string_view /*client_hello*/) {}
 };
 
 class QUIC_EXPORT_PRIVATE QuicConnectionHelperInterface {
@@ -1171,6 +1178,12 @@
   void OnTransportParametersResumed(
       const TransportParameters& transport_parameters) const;
 
+  // Called after an ClientHelloInner is encrypted and sent as a client.
+  void OnEncryptedClientHelloSent(absl::string_view client_hello) const;
+
+  // Called after an ClientHelloInner is received and decrypted as a server.
+  void OnEncryptedClientHelloReceived(absl::string_view client_hello) const;
+
   // Returns true if ack_alarm_ is set.
   bool HasPendingAcks() const;
 
diff --git a/quiche/quic/core/quic_session.cc b/quiche/quic/core/quic_session.cc
index a5664ac..39fef52 100644
--- a/quiche/quic/core/quic_session.cc
+++ b/quiche/quic/core/quic_session.cc
@@ -1824,6 +1824,16 @@
   return connection()->packet_creator().PacketFlusherAttached();
 }
 
+void QuicSession::OnEncryptedClientHelloSent(
+    absl::string_view client_hello) const {
+  connection()->OnEncryptedClientHelloSent(client_hello);
+}
+
+void QuicSession::OnEncryptedClientHelloReceived(
+    absl::string_view client_hello) const {
+  connection()->OnEncryptedClientHelloReceived(client_hello);
+}
+
 void QuicSession::OnCryptoHandshakeMessageSent(
     const CryptoHandshakeMessage& /*message*/) {}
 
diff --git a/quiche/quic/core/quic_session.h b/quiche/quic/core/quic_session.h
index 05e8ab3..d503b01 100644
--- a/quiche/quic/core/quic_session.h
+++ b/quiche/quic/core/quic_session.h
@@ -324,6 +324,10 @@
   void OnHandshakeCallbackDone() override;
   bool PacketFlusherAttached() const override;
   ParsedQuicVersion parsed_version() const override { return version(); }
+  void OnEncryptedClientHelloSent(
+      absl::string_view client_hello) const override;
+  void OnEncryptedClientHelloReceived(
+      absl::string_view client_hello) const override;
 
   // Implement StreamDelegateInterface.
   void OnStreamError(QuicErrorCode error_code,
diff --git a/quiche/quic/core/tls_handshaker.cc b/quiche/quic/core/tls_handshaker.cc
index 1a1c335..03ec0c9 100644
--- a/quiche/quic/core/tls_handshaker.cc
+++ b/quiche/quic/core/tls_handshaker.cc
@@ -387,4 +387,18 @@
   last_tls_alert_ = tls_alert;
 }
 
+void TlsHandshaker::MessageCallback(bool is_write, int /*version*/,
+                                    int content_type, absl::string_view data) {
+  if (content_type == SSL3_RT_CLIENT_HELLO_INNER) {
+    // Notify QuicConnectionDebugVisitor. Most TLS messages can be seen in
+    // CRYPTO frames, but, with ECH enabled, the ClientHelloInner is encrypted
+    // separately.
+    if (is_write) {
+      handshaker_delegate_->OnEncryptedClientHelloSent(data);
+    } else {
+      handshaker_delegate_->OnEncryptedClientHelloReceived(data);
+    }
+  }
+}
+
 }  // namespace quic
diff --git a/quiche/quic/core/tls_handshaker.h b/quiche/quic/core/tls_handshaker.h
index 72f1776..8c1651c 100644
--- a/quiche/quic/core/tls_handshaker.h
+++ b/quiche/quic/core/tls_handshaker.h
@@ -166,6 +166,12 @@
   // See |SSL_CTX_set_info_callback| for the meaning of |type| and |value|.
   void InfoCallback(int /*type*/, int /*value*/) override {}
 
+  // Message callback from BoringSSL, for debugging purposes. See
+  // |SSL_CTX_set_msg_callback| for how to interpret |version|, |content_type|,
+  // and |data|.
+  void MessageCallback(bool is_write, int version, int content_type,
+                       absl::string_view data) override;
+
  private:
   // ProofVerifierCallbackImpl handles the result of an asynchronous certificate
   // verification operation.