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