Add support for handling client certificate requests in QUIC TLS.

This change introduces a mechanism for the QUIC client to respond to a server's request for a client certificate during the TLS handshake. When a CertificateRequest message is received, the handshake is suspended, and a new callback, OnCertificateRequested, is triggered on the ProofHandler. This allows the application to provide a client certificate before the handshake resumes.

Protected by quic_restart_flag_quic_client_cert_support.

PiperOrigin-RevId: 914968512
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h
index 069549d..45b8b0e 100755
--- a/quiche/common/quiche_feature_flags_list.h
+++ b/quiche/common/quiche_feature_flags_list.h
@@ -66,6 +66,7 @@
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_testonly_default_true, true, true, "A testonly reloadable flag that will always default to true.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_update_max_datagram, true, true, "If true, updates the maximum datagram size after flushing pending packets.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_use_received_client_addresses_cache, true, true, "If true, use a LRU cache to record client addresses of packets received on server's original address.")
+QUICHE_FLAG(bool, quiche_restart_flag_quic_client_cert_support, false, true, "If true, enables dynamic client certs in QUIC.")
 QUICHE_FLAG(bool, quiche_restart_flag_quic_dispatcher_close_connection_on_invalid_ack, false, false, "An invalid ack is an ack that the peer sent for a packet that was not sent by the dispatcher. If true, the dispatcher will close the connection if it receives an invalid ack.")
 QUICHE_FLAG(bool, quiche_restart_flag_quic_shed_tls_handshake_config, false, true, "If true, QUIC connections will call SSL_set_shed_handshake_config to drop BoringSSL handshake state after the handshake finishes in order to save memory.")
 QUICHE_FLAG(bool, quiche_restart_flag_quic_support_release_time_for_gso, false, false, "If true, QuicGsoBatchWriter will support release time if it is available and the process has the permission to do so.")
diff --git a/quiche/quic/core/crypto/tls_client_connection.cc b/quiche/quic/core/crypto/tls_client_connection.cc
index d70737d..5f63f78 100644
--- a/quiche/quic/core/crypto/tls_client_connection.cc
+++ b/quiche/quic/core/crypto/tls_client_connection.cc
@@ -7,6 +7,9 @@
 #include <utility>
 #include <vector>
 
+#include "quiche/quic/platform/api/quic_flag_utils.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+
 namespace quic {
 
 TlsClientConnection::TlsClientConnection(SSL_CTX* ssl_ctx, Delegate* delegate,
@@ -21,6 +24,13 @@
   bssl::UniquePtr<SSL_CTX> ssl_ctx = TlsConnection::CreateSslCtx();
   // Configure certificate verification.
   SSL_CTX_set_custom_verify(ssl_ctx.get(), SSL_VERIFY_PEER, &VerifyCallback);
+
+  if (GetQuicRestartFlag(quic_client_cert_support)) {
+    QUIC_RESTART_FLAG_COUNT_N(quic_client_cert_support, 1, 2);
+    // Register the client certificate callback.
+    SSL_CTX_set_cert_cb(ssl_ctx.get(), &ClientCertRequestCallback, nullptr);
+  }
+
   int reverify_on_resume_enabled = 1;
   SSL_CTX_set_reverify_on_resume(ssl_ctx.get(), reverify_on_resume_enabled);
 
@@ -48,4 +58,10 @@
   return 1;
 }
 
+// static
+int TlsClientConnection::ClientCertRequestCallback(SSL* ssl, void*) {
+  return static_cast<TlsClientConnection*>(ConnectionFromSsl(ssl))
+      ->delegate_->OnClientCertRequested(ssl);
+}
+
 }  // namespace quic
diff --git a/quiche/quic/core/crypto/tls_client_connection.h b/quiche/quic/core/crypto/tls_client_connection.h
index 187da35..153a173 100644
--- a/quiche/quic/core/crypto/tls_client_connection.h
+++ b/quiche/quic/core/crypto/tls_client_connection.h
@@ -23,6 +23,10 @@
     // Called when a NewSessionTicket is received from the server.
     virtual void InsertSession(bssl::UniquePtr<SSL_SESSION> session) = 0;
 
+    // Called when the server requests a client certificate.
+    // Returns 1 on success, 0 on failure, or -1 to suspend the handshake.
+    virtual int OnClientCertRequested(SSL* ssl) = 0;
+
     // Provides the delegate for callbacks that are shared between client and
     // server.
     virtual TlsConnection::Delegate* ConnectionDelegate() = 0;
@@ -46,6 +50,10 @@
   // Delegate::InsertSession.
   static int NewSessionCallback(SSL* ssl, SSL_SESSION* session);
 
+  // Registered as the callback for SSL_CTX_set_cert_cb, which calls
+  // Delegate::OnClientCertRequested.
+  static int ClientCertRequestCallback(SSL* ssl, void* arg);
+
   Delegate* delegate_;
 };
 
diff --git a/quiche/quic/core/http/quic_connection_migration_manager_test.cc b/quiche/quic/core/http/quic_connection_migration_manager_test.cc
index 71bfac6..b4fe7d3 100644
--- a/quiche/quic/core/http/quic_connection_migration_manager_test.cc
+++ b/quiche/quic/core/http/quic_connection_migration_manager_test.cc
@@ -365,6 +365,11 @@
   void OnProofVerifyDetailsAvailable(
       const ProofVerifyDetails& /*verify_details*/) override {}
 
+  bool OnCertificateRequested(
+      const std::vector<std::string>& /*cert_authorities*/) override {
+    return false;
+  }
+
   TestStream* CreateOutgoingBidirectionalStream() override {
     QuicStreamId id = GetNextOutgoingBidirectionalStreamId();
     if (id ==
diff --git a/quiche/quic/core/http/quic_spdy_client_session.cc b/quiche/quic/core/http/quic_spdy_client_session.cc
index 740c0e9..053536d 100644
--- a/quiche/quic/core/http/quic_spdy_client_session.cc
+++ b/quiche/quic/core/http/quic_spdy_client_session.cc
@@ -76,6 +76,11 @@
 void QuicSpdyClientSession::OnProofVerifyDetailsAvailable(
     const ProofVerifyDetails& /*verify_details*/) {}
 
+bool QuicSpdyClientSession::OnCertificateRequested(
+    const std::vector<std::string>& /*cert_authorities*/) {
+  return false;
+}
+
 bool QuicSpdyClientSession::ShouldCreateOutgoingBidirectionalStream() {
   if (!crypto_stream_->encryption_established()) {
     QUIC_DLOG(INFO) << "Encryption not active so no outgoing stream created.";
diff --git a/quiche/quic/core/http/quic_spdy_client_session.h b/quiche/quic/core/http/quic_spdy_client_session.h
index 4d104d1..58701c2 100644
--- a/quiche/quic/core/http/quic_spdy_client_session.h
+++ b/quiche/quic/core/http/quic_spdy_client_session.h
@@ -87,6 +87,8 @@
   void OnProofValid(const QuicCryptoClientConfig::CachedState& cached) override;
   void OnProofVerifyDetailsAvailable(
       const ProofVerifyDetails& verify_details) override;
+  bool OnCertificateRequested(
+      const std::vector<std::string>& cert_authorities) override;
 
   // QuicSpdyClientSessionWithMigration methods:
   void OnConnectionToBeClosedDueToMigrationError(
diff --git a/quiche/quic/core/quic_crypto_client_handshaker_test.cc b/quiche/quic/core/quic_crypto_client_handshaker_test.cc
index fc42e01..40f40d4 100644
--- a/quiche/quic/core/quic_crypto_client_handshaker_test.cc
+++ b/quiche/quic/core/quic_crypto_client_handshaker_test.cc
@@ -24,6 +24,10 @@
       const QuicCryptoClientConfig::CachedState& /*cached*/) override {}
   void OnProofVerifyDetailsAvailable(
       const ProofVerifyDetails& /*verify_details*/) override {}
+  bool OnCertificateRequested(
+      const std::vector<std::string>& /*cert_authorities*/) override {
+    return false;
+  }
 };
 
 class InsecureProofVerifier : public ProofVerifier {
diff --git a/quiche/quic/core/quic_crypto_client_stream.h b/quiche/quic/core/quic_crypto_client_stream.h
index 2aaa58c..b30c199 100644
--- a/quiche/quic/core/quic_crypto_client_stream.h
+++ b/quiche/quic/core/quic_crypto_client_stream.h
@@ -9,6 +9,7 @@
 #include <memory>
 #include <optional>
 #include <string>
+#include <vector>
 
 #include "openssl/ssl.h"
 #include "quiche/quic/core/crypto/proof_verifier.h"
@@ -266,6 +267,12 @@
     // will only be called for secure QUIC connections.
     virtual void OnProofVerifyDetailsAvailable(
         const ProofVerifyDetails& verify_details) = 0;
+
+    // Called when the server requests a client certificate. |cert_authorities|
+    // contains the DER-encoded requested CAs. Returns true if the handshake
+    // should be suspended to provide the certificate asynchronously.
+    virtual bool OnCertificateRequested(
+        const std::vector<std::string>& cert_authorities) = 0;
   };
 
   QuicCryptoClientStream(const QuicServerId& server_id, QuicSession* session,
diff --git a/quiche/quic/core/quic_generic_session.cc b/quiche/quic/core/quic_generic_session.cc
index a592f1a..8c2def6 100644
--- a/quiche/quic/core/quic_generic_session.cc
+++ b/quiche/quic/core/quic_generic_session.cc
@@ -31,6 +31,10 @@
  public:
   void OnProofValid(const QuicCryptoClientConfig::CachedState&) override {}
   void OnProofVerifyDetailsAvailable(const ProofVerifyDetails&) override {}
+  bool OnCertificateRequested(
+      const std::vector<std::string>& cert_authorities) override {
+    return false;
+  }
 };
 
 class NoOpServerCryptoHelper : public QuicCryptoServerStreamBase::Helper {
diff --git a/quiche/quic/core/tls_client_handshaker.cc b/quiche/quic/core/tls_client_handshaker.cc
index fb50241..5f00c09 100644
--- a/quiche/quic/core/tls_client_handshaker.cc
+++ b/quiche/quic/core/tls_client_handshaker.cc
@@ -706,6 +706,32 @@
                          received_application_state_.get());
 }
 
+int TlsClientHandshaker::OnClientCertRequested(SSL* ssl) {
+  QUICHE_DCHECK(GetQuicRestartFlag(quic_client_cert_support));
+  if (SSL_get0_chain(ssl) != nullptr) {
+    QUIC_DVLOG(1) << "Client certificate already set, continuing handshake.";
+    return 1;
+  }
+  const STACK_OF(CRYPTO_BUFFER)* ca_names = SSL_get0_server_requested_CAs(ssl);
+  std::vector<std::string> cert_authorities;
+  if (ca_names != nullptr) {
+    for (size_t i = 0; i < sk_CRYPTO_BUFFER_num(ca_names); ++i) {
+      CRYPTO_BUFFER* buffer = sk_CRYPTO_BUFFER_value(ca_names, i);
+      cert_authorities.push_back(
+          std::string(reinterpret_cast<const char*>(CRYPTO_BUFFER_data(buffer)),
+                      CRYPTO_BUFFER_len(buffer)));
+    }
+  }
+
+  // OnCertificateRequested returns true if the implementation intends to
+  // provide the client certificate asynchronously, in which case we suspend the
+  // handshake.
+  if (proof_handler_->OnCertificateRequested(cert_authorities)) {
+    return -1;
+  }
+  return 1;
+}
+
 void TlsClientHandshaker::WriteMessage(EncryptionLevel level,
                                        absl::string_view data) {
   if (level == ENCRYPTION_HANDSHAKE && state_ < HANDSHAKE_PROCESSED) {
diff --git a/quiche/quic/core/tls_client_handshaker.h b/quiche/quic/core/tls_client_handshaker.h
index 447f7b6..aaa5311 100644
--- a/quiche/quic/core/tls_client_handshaker.h
+++ b/quiche/quic/core/tls_client_handshaker.h
@@ -126,6 +126,8 @@
 
   void InsertSession(bssl::UniquePtr<SSL_SESSION> session) override;
 
+  int OnClientCertRequested(SSL* ssl) override;
+
   bool PrepareZeroRttConfig(QuicResumptionState* cached_state);
 
   QuicSession* session() { return session_; }
diff --git a/quiche/quic/core/tls_client_handshaker_test.cc b/quiche/quic/core/tls_client_handshaker_test.cc
index 4587072..a5df77e 100644
--- a/quiche/quic/core/tls_client_handshaker_test.cc
+++ b/quiche/quic/core/tls_client_handshaker_test.cc
@@ -31,6 +31,7 @@
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/quic_versions.h"
 #include "quiche/quic/platform/api/quic_expect_bug.h"
+#include "quiche/quic/platform/api/quic_flags.h"
 #include "quiche/quic/platform/api/quic_test.h"
 #include "quiche/quic/test_tools/crypto_test_utils.h"
 #include "quiche/quic/test_tools/quic_connection_peer.h"
@@ -38,6 +39,7 @@
 #include "quiche/quic/test_tools/quic_session_peer.h"
 #include "quiche/quic/test_tools/quic_test_utils.h"
 #include "quiche/quic/test_tools/simple_session_cache.h"
+#include "quiche/quic/test_tools/test_certificates.h"
 #include "quiche/quic/tools/fake_proof_verifier.h"
 
 using testing::_;
@@ -326,8 +328,10 @@
   // Send a zero-length ServerHello from server to client.
   char bogus_handshake_message[] = {
       // Handshake struct (RFC 8446 appendix B.3)
-      2,        // HandshakeType server_hello
-      0, 0, 0,  // uint24 length
+      2,  // HandshakeType server_hello
+      0,
+      0,
+      0,  // uint24 length
   };
   stream()->crypto_message_parser()->ProcessInput(
       absl::string_view(bogus_handshake_message,
@@ -1194,6 +1198,52 @@
   }
 }
 
+TEST_P(TlsClientHandshakerTest, HandshakeSuspendedOnClientCertRequest) {
+  SetQuicRestartFlag(quic_client_cert_support, true);
+  crypto_config_ = std::make_unique<QuicCryptoClientConfig>(
+      std::make_unique<FakeProofVerifier>(), nullptr);
+  CreateConnection();
+  crypto_config_->set_proof_source(nullptr);
+  InitializeFakeServer();
+  server_session_->set_client_cert_mode(ClientCertMode::kRequest);
+  server_session_->Initialize();
+
+  std::vector<std::string> requested_cert_authorities;
+  EXPECT_CALL(*session_, OnCertificateRequested)
+      .WillOnce(testing::DoAll(testing::SaveArg<0>(&requested_cert_authorities),
+                               testing::Return(true)));
+  stream()->CryptoConnect();
+
+  // Advance the handshake until the CertificateRequest message is processed.
+  crypto_test_utils::AdvanceHandshake(connection_, stream(), 0,
+                                      server_connection_, server_stream(), 0);
+
+  // Verify that the handshake is suspended (not complete, but not failed).
+  EXPECT_FALSE(stream()->one_rtt_keys_available());
+  EXPECT_TRUE(connection_->connected());
+}
+
+TEST_P(TlsClientHandshakerTest, HandshakeCompletesWithPreconfiguredCert) {
+  auto proof_source = std::make_unique<DefaultClientProofSource>();
+  auto chain = quiche::QuicheReferenceCountedPointer<ProofSource::Chain>(
+      new ProofSource::Chain({std::string(quic::test::kTestCertificate)}));
+  std::unique_ptr<CertificatePrivateKey> key =
+      CertificatePrivateKey::LoadFromDer(
+          quic::test::kTestCertificatePrivateKey);
+
+  proof_source->AddCertAndKey({"*"}, std::move(chain), std::move(*key));
+  crypto_config_->set_proof_source(std::move(proof_source));
+
+  InitializeFakeServer();
+  server_session_->set_client_cert_mode(ClientCertMode::kRequire);
+
+  // Handshake should complete normally because the cert is proactive.
+  CompleteCryptoHandshake();
+
+  EXPECT_TRUE(stream()->one_rtt_keys_available());
+  EXPECT_TRUE(stream()->encryption_established());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quiche/quic/core/tls_handshaker.cc b/quiche/quic/core/tls_handshaker.cc
index 96dcf00..9eca933 100644
--- a/quiche/quic/core/tls_handshaker.cc
+++ b/quiche/quic/core/tls_handshaker.cc
@@ -153,6 +153,14 @@
     return;
   }
   int ssl_error = SSL_get_error(ssl(), rv);
+
+  if (GetQuicRestartFlag(quic_client_cert_support) &&
+      ssl_error == SSL_ERROR_WANT_X509_LOOKUP) {
+    QUIC_RESTART_FLAG_COUNT_N(quic_client_cert_support, 2, 2);
+    // Handshake is suspended until the client certificate is set.
+    return;
+  }
+
   if (ssl_error == expected_ssl_error_) {
     return;
   }
diff --git a/quiche/quic/qbone/qbone_client_session.cc b/quiche/quic/qbone/qbone_client_session.cc
index fa88c17..9af3394 100644
--- a/quiche/quic/qbone/qbone_client_session.cc
+++ b/quiche/quic/qbone/qbone_client_session.cc
@@ -107,6 +107,11 @@
 void QboneClientSession::OnProofVerifyDetailsAvailable(
     const ProofVerifyDetails& verify_details) {}
 
+bool QboneClientSession::OnCertificateRequested(
+    const std::vector<std::string>& cert_authorities) {
+  return false;
+}
+
 bool QboneClientSession::HasActiveRequests() const {
   return GetNumActiveStreams() + num_draining_streams() > 0;
 }
diff --git a/quiche/quic/qbone/qbone_client_session.h b/quiche/quic/qbone/qbone_client_session.h
index b0bd5cf..8a36c7e 100644
--- a/quiche/quic/qbone/qbone_client_session.h
+++ b/quiche/quic/qbone/qbone_client_session.h
@@ -72,6 +72,8 @@
   void OnProofValid(const QuicCryptoClientConfig::CachedState& cached) override;
   void OnProofVerifyDetailsAvailable(
       const ProofVerifyDetails& verify_details) override;
+  bool OnCertificateRequested(
+      const std::vector<std::string>& cert_authorities) override;
 
   QuicServerId server_id() { return server_id_; }
   QuicCryptoClientConfig* crypto_client_config() {
diff --git a/quiche/quic/test_tools/quic_test_utils.h b/quiche/quic/test_tools/quic_test_utils.h
index 390345b..3ecd487 100644
--- a/quiche/quic/test_tools/quic_test_utils.h
+++ b/quiche/quic/test_tools/quic_test_utils.h
@@ -1153,6 +1153,8 @@
   MOCK_METHOD(std::vector<std::string>, GetAlpnsToOffer, (), (const, override));
   MOCK_METHOD(void, OnAlpnSelected, (absl::string_view), (override));
   MOCK_METHOD(void, OnConfigNegotiated, (), (override));
+  MOCK_METHOD(bool, OnCertificateRequested,
+              (const std::vector<std::string>& cert_authorities), (override));
 
   QuicCryptoClientStream* GetMutableCryptoStream() override;
   const QuicCryptoClientStream* GetCryptoStream() const override;