Parse and log cert compression algos from CHLO. Protected by FLAGS_quic_reloadable_flag_quic_parse_cert_compression_algos_from_chlo. PiperOrigin-RevId: 651509489
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h index 3871993..fe07be7 100755 --- a/quiche/common/quiche_feature_flags_list.h +++ b/quiche/common/quiche_feature_flags_list.h
@@ -41,6 +41,7 @@ QUICHE_FLAG(bool, quiche_reloadable_flag_quic_no_write_control_frame_upon_connection_close, false, true, "If trrue, early return before write control frame in OnCanWrite() if the connection is already closed.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_no_write_control_frame_upon_connection_close2, false, false, "If true, QuicSession will block outgoing control frames when the connection is closed.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_pacing_remove_non_initial_burst, false, false, "If true, remove the non-initial burst in QUIC PacingSender.") +QUICHE_FLAG(bool, quiche_reloadable_flag_quic_parse_cert_compression_algos_from_chlo, false, false, "If true, parse offered cert compression algorithms from received CHLOs.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_priority_respect_incremental, false, false, "If true, respect the incremental parameter of each stream in QuicWriteBlockedList.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_require_handshake_confirmation, true, true, "If true, require handshake confirmation for QUIC connections, functionally disabling 0-rtt handshakes.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_send_placeholder_ticket_when_encrypt_ticket_fails, false, true, "If true, when TicketCrypter fails to encrypt a session ticket, quic::TlsServerHandshaker will send a placeholder ticket, instead of an empty one, to the client.")
diff --git a/quiche/quic/core/quic_buffered_packet_store.cc b/quiche/quic/core/quic_buffered_packet_store.cc index cb990ab..e2fb225 100644 --- a/quiche/quic/core/quic_buffered_packet_store.cc +++ b/quiche/quic/core/quic_buffered_packet_store.cc
@@ -317,6 +317,7 @@ const QuicConnectionId& connection_id, const ParsedQuicVersion& version, const QuicReceivedPacket& packet, std::vector<uint16_t>* out_supported_groups, + std::vector<uint16_t>* out_cert_compression_algos, std::vector<std::string>* out_alpns, std::string* out_sni, bool* out_resumption_attempted, bool* out_early_data_attempted, std::optional<uint8_t>* tls_alert) { @@ -337,6 +338,7 @@ } const TlsChloExtractor& tls_chlo_extractor = it->second.tls_chlo_extractor; *out_supported_groups = tls_chlo_extractor.supported_groups(); + *out_cert_compression_algos = tls_chlo_extractor.cert_compression_algos(); *out_alpns = tls_chlo_extractor.alpns(); *out_sni = tls_chlo_extractor.server_name(); *out_resumption_attempted = tls_chlo_extractor.resumption_attempted();
diff --git a/quiche/quic/core/quic_buffered_packet_store.h b/quiche/quic/core/quic_buffered_packet_store.h index 354c9cf..db9b74f 100644 --- a/quiche/quic/core/quic_buffered_packet_store.h +++ b/quiche/quic/core/quic_buffered_packet_store.h
@@ -146,6 +146,7 @@ const QuicConnectionId& connection_id, const ParsedQuicVersion& version, const QuicReceivedPacket& packet, std::vector<uint16_t>* out_supported_groups, + std::vector<uint16_t>* out_cert_compression_algos, std::vector<std::string>* out_alpns, std::string* out_sni, bool* out_resumption_attempted, bool* out_early_data_attempted, std::optional<uint8_t>* tls_alert);
diff --git a/quiche/quic/core/quic_buffered_packet_store_test.cc b/quiche/quic/core/quic_buffered_packet_store_test.cc index 7f38170..85ed960 100644 --- a/quiche/quic/core/quic_buffered_packet_store_test.cc +++ b/quiche/quic/core/quic_buffered_packet_store_test.cc
@@ -576,6 +576,7 @@ QuicConnectionId connection_id = TestConnectionId(1); std::vector<std::string> alpns; std::vector<uint16_t> supported_groups; + std::vector<uint16_t> cert_compression_algos; std::string sni; bool resumption_attempted = false; bool early_data_attempted = false; @@ -590,8 +591,9 @@ // The packet in 'packet_' is not a TLS CHLO packet. EXPECT_FALSE(store_.IngestPacketForTlsChloExtraction( - connection_id, valid_version_, packet_, &supported_groups, &alpns, &sni, - &resumption_attempted, &early_data_attempted, &tls_alert)); + connection_id, valid_version_, packet_, &supported_groups, + &cert_compression_algos, &alpns, &sni, &resumption_attempted, + &early_data_attempted, &tls_alert)); store_.DiscardPackets(connection_id); @@ -613,11 +615,13 @@ EXPECT_TRUE(store_.HasBufferedPackets(connection_id)); EXPECT_FALSE(store_.IngestPacketForTlsChloExtraction( - connection_id, valid_version_, *packets[0], &supported_groups, &alpns, - &sni, &resumption_attempted, &early_data_attempted, &tls_alert)); + connection_id, valid_version_, *packets[0], &supported_groups, + &cert_compression_algos, &alpns, &sni, &resumption_attempted, + &early_data_attempted, &tls_alert)); EXPECT_TRUE(store_.IngestPacketForTlsChloExtraction( - connection_id, valid_version_, *packets[1], &supported_groups, &alpns, - &sni, &resumption_attempted, &early_data_attempted, &tls_alert)); + connection_id, valid_version_, *packets[1], &supported_groups, + &cert_compression_algos, &alpns, &sni, &resumption_attempted, + &early_data_attempted, &tls_alert)); EXPECT_THAT(alpns, ElementsAre(AlpnForVersion(valid_version_))); EXPECT_FALSE(supported_groups.empty());
diff --git a/quiche/quic/core/quic_dispatcher.cc b/quiche/quic/core/quic_dispatcher.cc index 4ced4aa..dfa4171 100644 --- a/quiche/quic/core/quic_dispatcher.cc +++ b/quiche/quic/core/quic_dispatcher.cc
@@ -601,6 +601,7 @@ bool has_full_tls_chlo = false; std::string sni; std::vector<uint16_t> supported_groups; + std::vector<uint16_t> cert_compression_algos; std::vector<std::string> alpns; bool resumption_attempted = false, early_data_attempted = false; if (buffered_packets_.HasBufferedPackets( @@ -609,8 +610,9 @@ // use the associated TlsChloExtractor to parse this packet. has_full_tls_chlo = buffered_packets_.IngestPacketForTlsChloExtraction( packet_info.destination_connection_id, packet_info.version, - packet_info.packet, &supported_groups, &alpns, &sni, - &resumption_attempted, &early_data_attempted, &result.tls_alert); + packet_info.packet, &supported_groups, &cert_compression_algos, + &alpns, &sni, &resumption_attempted, &early_data_attempted, + &result.tls_alert); } else { // If we do not have a BufferedPacketList for this connection ID, // create a single-use one to check whether this packet contains a @@ -621,6 +623,7 @@ // This packet contains a full single-packet CHLO. has_full_tls_chlo = true; supported_groups = tls_chlo_extractor.supported_groups(); + cert_compression_algos = tls_chlo_extractor.cert_compression_algos(); alpns = tls_chlo_extractor.alpns(); sni = tls_chlo_extractor.server_name(); resumption_attempted = tls_chlo_extractor.resumption_attempted(); @@ -648,6 +651,7 @@ ParsedClientHello& parsed_chlo = result.parsed_chlo.emplace(); parsed_chlo.sni = std::move(sni); parsed_chlo.supported_groups = std::move(supported_groups); + parsed_chlo.cert_compression_algos = std::move(cert_compression_algos); parsed_chlo.alpns = std::move(alpns); if (packet_info.retry_token.has_value()) { parsed_chlo.retry_token = std::string(*packet_info.retry_token);
diff --git a/quiche/quic/core/quic_types.cc b/quiche/quic/core/quic_types.cc index e1507ad..43b5ace 100644 --- a/quiche/quic/core/quic_types.cc +++ b/quiche/quic/core/quic_types.cc
@@ -422,8 +422,9 @@ bool operator==(const ParsedClientHello& a, const ParsedClientHello& b) { return a.sni == b.sni && a.uaid == b.uaid && - a.supported_groups == b.supported_groups && a.alpns == b.alpns && - a.retry_token == b.retry_token && + a.supported_groups == b.supported_groups && + a.cert_compression_algos == b.cert_compression_algos && + a.alpns == b.alpns && a.retry_token == b.retry_token && a.resumption_attempted == b.resumption_attempted && a.early_data_attempted == b.early_data_attempted; } @@ -434,6 +435,8 @@ << ", alpns:" << quiche::PrintElements(parsed_chlo.alpns) << ", supported_groups:" << quiche::PrintElements(parsed_chlo.supported_groups) + << ", cert_compression_algos:" + << quiche::PrintElements(parsed_chlo.cert_compression_algos) << ", resumption_attempted:" << parsed_chlo.resumption_attempted << ", early_data_attempted:" << parsed_chlo.early_data_attempted << ", len(retry_token):" << parsed_chlo.retry_token.size() << " }";
diff --git a/quiche/quic/core/quic_types.h b/quiche/quic/core/quic_types.h index 93a7e5e..b85e079 100644 --- a/quiche/quic/core/quic_types.h +++ b/quiche/quic/core/quic_types.h
@@ -875,6 +875,7 @@ std::string sni; // QUIC crypto and TLS. std::string uaid; // QUIC crypto only. std::vector<uint16_t> supported_groups; // TLS only. + std::vector<uint16_t> cert_compression_algos; // TLS only. std::vector<std::string> alpns; // QUIC crypto and TLS. // The unvalidated retry token from the last received packet of a potentially // multi-packet client hello. TLS only.
diff --git a/quiche/quic/core/tls_chlo_extractor.cc b/quiche/quic/core/tls_chlo_extractor.cc index 1ba0a20..82beb9f 100644 --- a/quiche/quic/core/tls_chlo_extractor.cc +++ b/quiche/quic/core/tls_chlo_extractor.cc
@@ -23,6 +23,7 @@ #include "quiche/quic/core/quic_types.h" #include "quiche/quic/core/quic_versions.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/common/platform/api/quiche_logging.h" @@ -71,6 +72,42 @@ return named_groups; } +std::vector<uint16_t> GetCertCompressionAlgos( + const SSL_CLIENT_HELLO* client_hello) { + const uint8_t* extension_data; + size_t extension_len; + int rv = SSL_early_callback_ctx_extension_get( + client_hello, TLSEXT_TYPE_cert_compression, &extension_data, + &extension_len); + if (rv != 1) { + return {}; + } + // See https://datatracker.ietf.org/doc/html/rfc8879#section-3 for the format + // of this extension. + QuicDataReader cert_compression_algos_reader( + reinterpret_cast<const char*>(extension_data), extension_len); + uint8_t algos_len; + if (!cert_compression_algos_reader.ReadUInt8(&algos_len) || algos_len == 0 || + algos_len % sizeof(uint16_t) != 0 || + algos_len + sizeof(uint8_t) != extension_len) { + QUIC_CODE_COUNT(quic_chlo_cert_compression_algos_invalid_length); + return {}; + } + + size_t num_algos = algos_len / sizeof(uint16_t); + std::vector<uint16_t> cert_compression_algos; + cert_compression_algos.reserve(num_algos); + for (size_t i = 0; i < num_algos; ++i) { + uint16_t cert_compression_algo; + if (!cert_compression_algos_reader.ReadUInt16(&cert_compression_algo)) { + QUIC_CODE_COUNT(quic_chlo_fail_to_read_cert_compression_algo); + return {}; + } + cert_compression_algos.push_back(cert_compression_algo); + } + return cert_compression_algos; +} + } // namespace TlsChloExtractor::TlsChloExtractor() @@ -102,6 +139,7 @@ parsed_crypto_frame_in_this_packet_ = other.parsed_crypto_frame_in_this_packet_; supported_groups_ = std::move(other.supported_groups_); + cert_compression_algos_ = std::move(other.cert_compression_algos_); alpns_ = std::move(other.alpns_); server_name_ = std::move(other.server_name_); client_hello_bytes_ = std::move(other.client_hello_bytes_); @@ -366,6 +404,16 @@ } supported_groups_ = GetSupportedGroups(client_hello); + if (GetQuicReloadableFlag(quic_parse_cert_compression_algos_from_chlo)) { + cert_compression_algos_ = GetCertCompressionAlgos(client_hello); + if (cert_compression_algos_.empty()) { + QUIC_RELOADABLE_FLAG_COUNT_N(quic_parse_cert_compression_algos_from_chlo, + 1, 2); + } else { + QUIC_RELOADABLE_FLAG_COUNT_N(quic_parse_cert_compression_algos_from_chlo, + 2, 2); + } + } // Update our state now that we've parsed a full CHLO. if (state_ == State::kInitial) {
diff --git a/quiche/quic/core/tls_chlo_extractor.h b/quiche/quic/core/tls_chlo_extractor.h index 2aaa0eb..8ded967 100644 --- a/quiche/quic/core/tls_chlo_extractor.h +++ b/quiche/quic/core/tls_chlo_extractor.h
@@ -54,6 +54,9 @@ const std::vector<uint16_t>& supported_groups() const { return supported_groups_; } + const std::vector<uint16_t>& cert_compression_algos() const { + return cert_compression_algos_; + } absl::Span<const uint8_t> client_hello_bytes() const { return client_hello_bytes_; } @@ -263,6 +266,9 @@ bool parsed_crypto_frame_in_this_packet_; // Array of NamedGroups parsed from the CHLO's supported_groups extension. std::vector<uint16_t> supported_groups_; + // Array of cert compression algos parsed from the CHLO's + // compress_certificate extension. + std::vector<uint16_t> cert_compression_algos_; // Array of ALPNs parsed from the CHLO. std::vector<std::string> alpns_; // SNI parsed from the CHLO.
diff --git a/quiche/quic/core/tls_chlo_extractor_test.cc b/quiche/quic/core/tls_chlo_extractor_test.cc index 238d7b4..2bc2044 100644 --- a/quiche/quic/core/tls_chlo_extractor_test.cc +++ b/quiche/quic/core/tls_chlo_extractor_test.cc
@@ -15,16 +15,29 @@ #include "quiche/quic/core/quic_connection.h" #include "quiche/quic/core/quic_types.h" #include "quiche/quic/core/quic_versions.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/first_flight.h" #include "quiche/quic/test_tools/quic_test_utils.h" #include "quiche/quic/test_tools/simple_session_cache.h" +#include "quiche/common/print_elements.h" namespace quic { namespace test { namespace { +static int DummyCompressFunc(SSL* /*ssl*/, CBB* /*out*/, const uint8_t* /*in*/, + size_t /*in_len*/) { + return 1; +} + +static int DummyDecompressFunc(SSL* /*ssl*/, CRYPTO_BUFFER** /*out*/, + size_t /*uncompressed_len*/, + const uint8_t* /*in*/, size_t /*in_len*/) { + return 1; +} + using testing::_; using testing::AnyNumber; @@ -234,6 +247,43 @@ } } +TEST_P(TlsChloExtractorTest, TlsExtensionInfo_CertCompressionAlgos) { + const std::vector<std::vector<uint16_t>> supported_groups_to_test = { + // No cert compression algos + {}, + // One cert compression algo + {1}, + // Two cert compression algos + {1, 2}, + // Three cert compression algos + {1, 2, 3}, + // Four cert compression algos + {1, 2, 3, 65535}, + }; + for (const std::vector<uint16_t>& supported_cert_compression_algos : + supported_groups_to_test) { + auto crypto_client_config = std::make_unique<QuicCryptoClientConfig>( + crypto_test_utils::ProofVerifierForTesting()); + for (uint16_t cert_compression_algo : supported_cert_compression_algos) { + ASSERT_TRUE(SSL_CTX_add_cert_compression_alg( + crypto_client_config->ssl_ctx(), cert_compression_algo, + DummyCompressFunc, DummyDecompressFunc)); + } + + Initialize(std::move(crypto_client_config)); + IngestPackets(); + ValidateChloDetails(); + if (GetQuicReloadableFlag(quic_parse_cert_compression_algos_from_chlo)) { + EXPECT_EQ(tls_chlo_extractor_->cert_compression_algos(), + supported_cert_compression_algos) + << quiche::PrintElements( + tls_chlo_extractor_->cert_compression_algos()); + } else { + EXPECT_TRUE(tls_chlo_extractor_->cert_compression_algos().empty()); + } + } +} + TEST_P(TlsChloExtractorTest, MultiPacket) { IncreaseSizeOfChlo(); Initialize();