Add `quic::CredentialExData` as the ex_data of QUIC's SSL_CREDENTIAL objects.

This can be used by QUIC handshake monitoring, cl/915476086.

Protected by FLAGS_quic_restart_flag_quic_set_credential_ex_data.

PiperOrigin-RevId: 919704563
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h
index 55c1577..b6b75dc 100755
--- a/quiche/common/quiche_feature_flags_list.h
+++ b/quiche/common/quiche_feature_flags_list.h
@@ -67,6 +67,7 @@
 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_set_credential_ex_data, false, false, "If true, TlsServerConnection::AddCertChain will add CredentialExData as the ex_data of the SSL_CREDENTIAL.")
 QUICHE_FLAG(bool, quiche_restart_flag_quic_shed_tls_handshake_config, true, 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.")
 QUICHE_FLAG(bool, quiche_restart_flag_quic_testonly_default_false, false, false, "A testonly restart flag that will always default to false.")
diff --git a/quiche/quic/core/crypto/proof_source.cc b/quiche/quic/core/crypto/proof_source.cc
index c14b141..3acb2fc 100644
--- a/quiche/quic/core/crypto/proof_source.cc
+++ b/quiche/quic/core/crypto/proof_source.cc
@@ -11,21 +11,81 @@
 #include <vector>
 
 #include "openssl/base.h"
+#include "openssl/ex_data.h"
 #include "openssl/pool.h"
 #include "openssl/ssl.h"
 #include "quiche/quic/core/crypto/certificate_view.h"
 #include "quiche/quic/platform/api/quic_bug_tracker.h"
+#include "quiche/quic/platform/api/quic_flag_utils.h"
+#include "quiche/quic/platform/api/quic_flags.h"
 #include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/common/platform/api/quiche_logging.h"
 #include "quiche/common/platform/api/quiche_reference_counted.h"
 
 namespace quic {
 
+namespace {
+
+void CredentialExDataFree(void*, void* ptr, CRYPTO_EX_DATA*, int,
+                          long,  // NOLINT
+                          void*) {
+  delete static_cast<CredentialExData*>(ptr);
+}
+
+int GetCredentialExDataIndex() {
+  static const int index = [] {
+    int ret = SSL_CREDENTIAL_get_ex_new_index(0, nullptr, nullptr, nullptr,
+                                              CredentialExDataFree);
+    if (ret < 0) {
+      QUIC_BUG(quic_credential_ex_data_index_failure)
+          << "Failed to get SSL credential ex data index. "
+             "(Get|Set)CredentialExData will not work.";
+    }
+    return ret;
+  }();
+  return index;
+}
+
+}  // namespace
+
 CryptoBuffers::~CryptoBuffers() {
   for (size_t i = 0; i < value.size(); i++) {
     CRYPTO_BUFFER_free(value[i]);
   }
 }
 
+void SetCredentialExData(SSL_CREDENTIAL& credential,
+                         std::unique_ptr<CredentialExData> exdata) {
+  if (!GetQuicRestartFlag(quic_set_credential_ex_data)) {
+    return;
+  }
+  QUIC_RESTART_FLAG_COUNT_N(quic_set_credential_ex_data, 2, 3);
+
+  int index = GetCredentialExDataIndex();
+  if (index < 0 || exdata == nullptr) {
+    return;
+  }
+  if (SSL_CREDENTIAL_set_ex_data(&credential, index, exdata.get())) {
+    exdata.release();  // Ownership transferred.
+  } else {
+    QUICHE_LOG_FIRST_N(ERROR, 1) << "SetCredentialExData failed.";
+  }
+}
+
+const CredentialExData* GetCredentialExData(const SSL_CREDENTIAL& credential) {
+  if (!GetQuicRestartFlag(quic_set_credential_ex_data)) {
+    return nullptr;
+  }
+  QUIC_RESTART_FLAG_COUNT_N(quic_set_credential_ex_data, 3, 3);
+
+  int index = GetCredentialExDataIndex();
+  if (index < 0) {
+    return nullptr;
+  }
+  return static_cast<const CredentialExData*>(
+      SSL_CREDENTIAL_get_ex_data(&credential, index));
+}
+
 ProofSource::Chain::Chain(const std::vector<std::string>& certs,
                           const std::string& trust_anchor_id)
     : certs(certs), trust_anchor_id(trust_anchor_id) {}
diff --git a/quiche/quic/core/crypto/proof_source.h b/quiche/quic/core/crypto/proof_source.h
index 645e611..b9f28bb 100644
--- a/quiche/quic/core/crypto/proof_source.h
+++ b/quiche/quic/core/crypto/proof_source.h
@@ -43,11 +43,33 @@
   CryptoBuffers() = default;
   CryptoBuffers(const CryptoBuffers&) = delete;
   CryptoBuffers(CryptoBuffers&&) = default;
+  CryptoBuffers& operator=(const CryptoBuffers&) = delete;
+  CryptoBuffers& operator=(CryptoBuffers&&) = default;
   ~CryptoBuffers();
 
   std::vector<CRYPTO_BUFFER*> value;
 };
 
+// The ex_data for SSL_CREDENTIAL.
+struct QUICHE_EXPORT CredentialExData {
+  explicit CredentialExData(CryptoBuffers cert_chain)
+      : cert_chain_buffers(std::move(cert_chain)) {}
+  CryptoBuffers cert_chain_buffers;
+};
+
+// Sets ex_data for a SSL_CREDENTIAL.
+QUICHE_EXPORT void SetCredentialExData(
+    SSL_CREDENTIAL& credential, std::unique_ptr<CredentialExData> exdata);
+
+// Gets ex_data for a SSL_CREDENTIAL. Returns nullptr if not set.
+//
+// Note that the SSL_CREDENTIAL used by a handshake is deleted after the
+// handshake, so there is only a short window to get the ex_data from
+// SSL_get0_selected_credential(). See test `GetCredentialExData` in
+// `TlsServerHandshakerTest` for an example.
+QUICHE_EXPORT const CredentialExData* GetCredentialExData(
+    const SSL_CREDENTIAL& credential);
+
 // ProofSource is an interface by which a QUIC server can obtain certificate
 // chains and signatures that prove its identity.
 class QUICHE_EXPORT ProofSource {
diff --git a/quiche/quic/core/crypto/tls_server_connection.cc b/quiche/quic/core/crypto/tls_server_connection.cc
index 4771097..a6c6b8d 100644
--- a/quiche/quic/core/crypto/tls_server_connection.cc
+++ b/quiche/quic/core/crypto/tls_server_connection.cc
@@ -6,6 +6,7 @@
 
 #include <cstddef>
 #include <cstdint>
+#include <memory>
 #include <utility>
 #include <vector>
 
@@ -17,6 +18,7 @@
 #include "quiche/quic/core/crypto/tls_connection.h"
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/platform/api/quic_flag_utils.h"
+#include "quiche/quic/platform/api/quic_flags.h"
 #include "quiche/common/platform/api/quiche_logging.h"
 
 namespace quic {
@@ -73,12 +75,20 @@
                                   TlsServerConnection::kPrivateKeyMethod);
 }
 
-void TlsServerConnection::AddCertChain(
-    const std::vector<CRYPTO_BUFFER*>& cert_chain,
-    absl::string_view trust_anchor_id) {
+void TlsServerConnection::AddCertChain(CryptoBuffers cert_chain,
+                                       absl::string_view trust_anchor_id) {
   bssl::UniquePtr<SSL_CREDENTIAL> credential(SSL_CREDENTIAL_new_x509());
-  SSL_CREDENTIAL_set1_cert_chain(credential.get(), cert_chain.data(),
-                                 cert_chain.size());
+  std::unique_ptr<CredentialExData> exdata;
+  if (GetQuicRestartFlag(quic_set_credential_ex_data)) {
+    QUIC_RESTART_FLAG_COUNT_N(quic_set_credential_ex_data, 1, 3);
+    exdata = std::make_unique<CredentialExData>(std::move(cert_chain));
+    SSL_CREDENTIAL_set1_cert_chain(credential.get(),
+                                   exdata->cert_chain_buffers.value.data(),
+                                   exdata->cert_chain_buffers.value.size());
+  } else {
+    SSL_CREDENTIAL_set1_cert_chain(credential.get(), cert_chain.value.data(),
+                                   cert_chain.value.size());
+  }
   if (ssl_config().signing_algorithm_prefs.has_value()) {
     SSL_CREDENTIAL_set1_signing_algorithm_prefs(
         credential.get(), ssl_config().signing_algorithm_prefs->data(),
@@ -96,6 +106,9 @@
   } else {
     QUIC_CODE_COUNT(quic_tls_server_connection_trust_anchor_id_empty);
   }
+  if (exdata != nullptr) {
+    SetCredentialExData(*credential, std::move(exdata));
+  }
   SSL_add1_credential(ssl(), credential.get());
 }
 
diff --git a/quiche/quic/core/crypto/tls_server_connection.h b/quiche/quic/core/crypto/tls_server_connection.h
index 9e4fede..ea4ace9 100644
--- a/quiche/quic/core/crypto/tls_server_connection.h
+++ b/quiche/quic/core/crypto/tls_server_connection.h
@@ -139,7 +139,7 @@
   // ID for `cert_chain`, and BoringSSL will be configured to enforce issuer
   // matching on this certificate. See
   // https://tlswg.org/tls-trust-anchor-ids/draft-ietf-tls-trust-anchor-ids.html#section-4.1.
-  void AddCertChain(const std::vector<CRYPTO_BUFFER*>& cert_chain,
+  void AddCertChain(CryptoBuffers cert_chain,
                     absl::string_view trust_anchor_id);
 
   // Set the client cert mode to be used on this connection. This should be
diff --git a/quiche/quic/core/tls_server_handshaker.cc b/quiche/quic/core/tls_server_handshaker.cc
index 2745f46..8294a63 100644
--- a/quiche/quic/core/tls_server_handshaker.cc
+++ b/quiche/quic/core/tls_server_handshaker.cc
@@ -1129,7 +1129,7 @@
              local_config->chains) {
           if (!chain->certs.empty()) {
             QUIC_CODE_COUNT(quic_tls_server_chain_with_certs_nonempty);
-            tls_connection_.AddCertChain(chain->ToCryptoBuffers().value,
+            tls_connection_.AddCertChain(chain->ToCryptoBuffers(),
                                          chain->trust_anchor_id);
           } else {
             QUIC_CODE_COUNT(quic_tls_server_chain_with_certs_empty);
diff --git a/quiche/quic/core/tls_server_handshaker_test.cc b/quiche/quic/core/tls_server_handshaker_test.cc
index 9f41676..31521ce 100644
--- a/quiche/quic/core/tls_server_handshaker_test.cc
+++ b/quiche/quic/core/tls_server_handshaker_test.cc
@@ -153,6 +153,10 @@
   MOCK_METHOD(void, OnSelectCertificateDone,
               (bool, bool, SSLConfig, absl::string_view, bool), (override));
 
+  MOCK_METHOD(void, InfoCallback, (int, int), (override));
+
+  void EnableInfoCallback() { tls_connection().EnableInfoCallback(); }
+
   // Makes the next call to `MaybeCreateProofSourceHandle()` return a
   // `FakeProofSourceHandle` instead of a real `ProofSourceHandle`.
   void SetupProofSourceHandle(
@@ -1713,6 +1717,45 @@
             TLS1_3_CK_AES_256_GCM_SHA384);
 }
 
+TEST_P(TlsServerHandshakerTest, GetCredentialExData) {
+  InitializeServerWithFakeProofSourceHandle();
+
+  server_handshaker_->EnableInfoCallback();
+
+  bool credential_ex_data_verified = false;
+  EXPECT_CALL(*server_handshaker_, InfoCallback(_, _))
+      .WillRepeatedly([&](int type, int /*value*/) {
+        if (type & SSL_CB_HANDSHAKE_DONE) {
+          SSL* ssl = server_ssl();
+
+          // Get the selected credential used for this connection.
+          const SSL_CREDENTIAL* cred = SSL_get0_selected_credential(ssl);
+          ASSERT_NE(cred, nullptr);
+
+          // Verify that the extension data is present and non-null.
+          const CredentialExData* ex_data = GetCredentialExData(*cred);
+
+          if (GetQuicRestartFlag(quic_set_credential_ex_data)) {
+            ASSERT_NE(ex_data, nullptr);
+          } else {
+            ASSERT_EQ(ex_data, nullptr);
+          }
+          credential_ex_data_verified = true;
+        }
+      });
+
+  server_handshaker_->SetupProofSourceHandle(
+      /*select_cert_action=*/FakeProofSourceHandle::Action::DELEGATE_SYNC,
+      /*compute_signature_action=*/FakeProofSourceHandle::Action::
+          DELEGATE_SYNC);
+
+  InitializeFakeClient();
+  CompleteCryptoHandshake();
+  ExpectHandshakeSuccessful();
+
+  ASSERT_TRUE(credential_ex_data_verified);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic