Introduce TlsChloExtractor This utility class can ingest QUIC packets and parse the QUIC+TLS ClientHello to extract the SNI and ALPN. gfe-relnote: n/a, adds unused code PiperOrigin-RevId: 307951629 Change-Id: I69ff2f8136b4970e12543c879b18406304670e8f
diff --git a/quic/core/tls_chlo_extractor.cc b/quic/core/tls_chlo_extractor.cc new file mode 100644 index 0000000..5221ee1 --- /dev/null +++ b/quic/core/tls_chlo_extractor.cc
@@ -0,0 +1,367 @@ +// Copyright (c) 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/core/tls_chlo_extractor.h" +#include <cstring> +#include <memory> + +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "net/third_party/quiche/src/quic/core/frames/quic_crypto_frame.h" +#include "net/third_party/quiche/src/quic/core/quic_data_reader.h" +#include "net/third_party/quiche/src/quic/core/quic_error_codes.h" +#include "net/third_party/quiche/src/quic/core/quic_framer.h" +#include "net/third_party/quiche/src/quic/core/quic_time.h" +#include "net/third_party/quiche/src/quic/core/quic_types.h" +#include "net/third_party/quiche/src/quic/core/quic_versions.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_str_cat.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h" + +namespace quic { + +TlsChloExtractor::TlsChloExtractor() + : crypto_stream_sequencer_(this), + state_(State::kInitial), + parsed_crypto_frame_in_this_packet_(false) {} + +void TlsChloExtractor::IngestPacket(const ParsedQuicVersion& version, + const QuicReceivedPacket& packet) { + if (state_ == State::kUnrecoverableFailure) { + QUIC_DLOG(ERROR) << "Not ingesting packet after unrecoverable error"; + return; + } + if (version == UnsupportedQuicVersion()) { + QUIC_DLOG(ERROR) << "Not ingesting packet with unsupported version"; + return; + } + if (version.handshake_protocol != PROTOCOL_TLS1_3) { + QUIC_DLOG(ERROR) << "Not ingesting packet with non-TLS version " << version; + return; + } + if (framer_) { + // This is not the first packet we have ingested, check if version matches. + if (!framer_->IsSupportedVersion(version)) { + QUIC_DLOG(ERROR) + << "Not ingesting packet with version mismatch, expected " + << framer_->version() << ", got " << version; + return; + } + } else { + // This is the first packet we have ingested, setup parser. + framer_ = std::make_unique<QuicFramer>( + ParsedQuicVersionVector{version}, QuicTime::Zero(), + Perspective::IS_SERVER, /*expected_server_connection_id_length=*/0); + // Note that expected_server_connection_id_length only matters for short + // headers and we explicitly drop those so we can pass any value here. + framer_->set_visitor(this); + } + + // When the framer parses |packet|, if it sees a CRYPTO frame it will call + // OnCryptoFrame below and that will set parsed_crypto_frame_in_this_packet_ + // to true. + parsed_crypto_frame_in_this_packet_ = false; + const bool parse_success = framer_->ProcessPacket(packet); + if (state_ == State::kInitial && parsed_crypto_frame_in_this_packet_) { + // If we parsed a CRYPTO frame but didn't advance the state from initial, + // then it means that we will need more packets to reassemble the full CHLO, + // so we advance the state here. This can happen when the first packet + // received is not the first one in the crypto stream. This allows us to + // differentiate our state between single-packet CHLO and multi-packet CHLO. + state_ = State::kParsedPartialChloFragment; + } + + if (!parse_success) { + // This could be due to the packet being non-initial for example. + QUIC_DLOG(ERROR) << "Failed to process packet"; + return; + } +} + +// This is called when the framer parsed the unencrypted parts of the header. +bool TlsChloExtractor::OnUnauthenticatedPublicHeader( + const QuicPacketHeader& header) { + if (header.form != IETF_QUIC_LONG_HEADER_PACKET) { + QUIC_DLOG(ERROR) << "Not parsing non-long-header packet " << header; + return false; + } + if (header.long_packet_type != INITIAL) { + QUIC_DLOG(ERROR) << "Not parsing non-initial packet " << header; + return false; + } + // QuicFramer is constructed without knowledge of the server's connection ID + // so it needs to be set up here in order to decrypt the packet. + framer_->SetInitialObfuscators(header.destination_connection_id); + return true; +} + +// This is called by the framer if it detects a change in version during +// parsing. +bool TlsChloExtractor::OnProtocolVersionMismatch(ParsedQuicVersion version) { + // This should never be called because we already check versions in + // IngestPacket. + QUIC_BUG << "Unexpected version mismatch, expected " << framer_->version() + << ", got " << version; + return false; +} + +// This is called by the QuicStreamSequencer if it encounters an unrecoverable +// error that will prevent it from reassembling the crypto stream data. +void TlsChloExtractor::OnUnrecoverableError(QuicErrorCode error, + const std::string& details) { + HandleUnrecoverableError(quiche::QuicheStrCat( + "Crypto stream error ", QuicErrorCodeToString(error), ": ", details)); +} + +// This is called by the framer if it sees a CRYPTO frame during parsing. +bool TlsChloExtractor::OnCryptoFrame(const QuicCryptoFrame& frame) { + if (frame.level != ENCRYPTION_INITIAL) { + // Since we drop non-INITIAL packets in OnUnauthenticatedPublicHeader, + // we should never receive any CRYPTO frames at other encryption levels. + QUIC_BUG << "Parsed bad-level CRYPTO frame " << frame; + return false; + } + // parsed_crypto_frame_in_this_packet_ is checked in IngestPacket to allow + // advancing our state to track the difference between single-packet CHLO + // and multi-packet CHLO. + parsed_crypto_frame_in_this_packet_ = true; + crypto_stream_sequencer_.OnCryptoFrame(frame); + return true; +} + +// Called by the QuicStreamSequencer when it receives a CRYPTO frame that +// advances the amount of contiguous data we now have starting from offset 0. +void TlsChloExtractor::OnDataAvailable() { + // Lazily set up BoringSSL handle. + SetupSslHandle(); + + // Get data from the stream sequencer and pass it to BoringSSL. + struct iovec iov; + while (crypto_stream_sequencer_.GetReadableRegion(&iov)) { + const int rv = SSL_provide_quic_data( + ssl_.get(), ssl_encryption_initial, + reinterpret_cast<const uint8_t*>(iov.iov_base), iov.iov_len); + if (rv != 1) { + HandleUnrecoverableError("SSL_provide_quic_data failed"); + return; + } + crypto_stream_sequencer_.MarkConsumed(iov.iov_len); + } + + // Instruct BoringSSL to attempt parsing a full CHLO from the provided data. + // We ignore the return value since we know the handshake is going to fail + // because we explicitly cancel processing once we've parsed the CHLO. + (void)SSL_do_handshake(ssl_.get()); +} + +// static +TlsChloExtractor* TlsChloExtractor::GetInstanceFromSSL(SSL* ssl) { + std::pair<SSL_CTX*, int> shared_handles = GetSharedSslHandles(); + int ex_data_index = shared_handles.second; + return reinterpret_cast<TlsChloExtractor*>( + SSL_get_ex_data(ssl, ex_data_index)); +} + +// static +int TlsChloExtractor::SetReadSecretCallback( + SSL* ssl, + enum ssl_encryption_level_t /*level*/, + const SSL_CIPHER* /*cipher*/, + const uint8_t* /*secret*/, + size_t /*secret_length*/) { + GetInstanceFromSSL(ssl)->HandleUnexpectedCallback("SetReadSecretCallback"); + return 0; +} + +// static +int TlsChloExtractor::SetWriteSecretCallback( + SSL* ssl, + enum ssl_encryption_level_t /*level*/, + const SSL_CIPHER* /*cipher*/, + const uint8_t* /*secret*/, + size_t /*secret_length*/) { + GetInstanceFromSSL(ssl)->HandleUnexpectedCallback("SetWriteSecretCallback"); + return 0; +} + +// static +int TlsChloExtractor::WriteMessageCallback( + SSL* ssl, + enum ssl_encryption_level_t /*level*/, + const uint8_t* /*data*/, + size_t /*len*/) { + GetInstanceFromSSL(ssl)->HandleUnexpectedCallback("WriteMessageCallback"); + return 0; +} + +// static +int TlsChloExtractor::FlushFlightCallback(SSL* ssl) { + GetInstanceFromSSL(ssl)->HandleUnexpectedCallback("FlushFlightCallback"); + return 0; +} + +void TlsChloExtractor::HandleUnexpectedCallback( + const std::string& callback_name) { + std::string error_details = + quiche::QuicheStrCat("Unexpected callback ", callback_name); + QUIC_BUG << error_details; + HandleUnrecoverableError(error_details); +} + +// static +int TlsChloExtractor::SendAlertCallback(SSL* ssl, + enum ssl_encryption_level_t /*level*/, + uint8_t desc) { + GetInstanceFromSSL(ssl)->SendAlert(desc); + return 0; +} + +void TlsChloExtractor::SendAlert(uint8_t tls_alert_value) { + if (tls_alert_value == SSL3_AD_HANDSHAKE_FAILURE && HasParsedFullChlo()) { + // This is the most common scenario. Since we return an error from + // SelectCertCallback in order to cancel further processing, BoringSSL will + // try to send this alert to tell the client that the handshake failed. + return; + } + HandleUnrecoverableError(quiche::QuicheStrCat( + "BoringSSL attempted to send alert ", static_cast<int>(tls_alert_value), + " ", SSL_alert_desc_string_long(tls_alert_value))); +} + +// static +enum ssl_select_cert_result_t TlsChloExtractor::SelectCertCallback( + const SSL_CLIENT_HELLO* client_hello) { + GetInstanceFromSSL(client_hello->ssl)->HandleParsedChlo(client_hello); + // Always return an error to cancel any further processing in BoringSSL. + return ssl_select_cert_error; +} + +// Extracts the server name and ALPN from the parsed ClientHello. +void TlsChloExtractor::HandleParsedChlo(const SSL_CLIENT_HELLO* client_hello) { + const char* server_name = + SSL_get_servername(client_hello->ssl, TLSEXT_NAMETYPE_host_name); + if (server_name) { + server_name_ = std::string(server_name); + } + const uint8_t* alpn_data; + size_t alpn_len; + int rv = SSL_early_callback_ctx_extension_get( + client_hello, TLSEXT_TYPE_application_layer_protocol_negotiation, + &alpn_data, &alpn_len); + if (rv == 1) { + QuicDataReader alpns_reader(reinterpret_cast<const char*>(alpn_data), + alpn_len); + quiche::QuicheStringPiece alpns_payload; + if (!alpns_reader.ReadStringPiece16(&alpns_payload)) { + HandleUnrecoverableError("Failed to read alpns_payload"); + return; + } + QuicDataReader alpns_payload_reader(alpns_payload); + while (!alpns_payload_reader.IsDoneReading()) { + quiche::QuicheStringPiece alpn_payload; + if (!alpns_payload_reader.ReadStringPiece8(&alpn_payload)) { + HandleUnrecoverableError("Failed to read alpn_payload"); + return; + } + alpns_.emplace_back(std::string(alpn_payload)); + } + } + + // Update our state now that we've parsed a full CHLO. + if (state_ == State::kInitial) { + state_ = State::kParsedFullSinglePacketChlo; + } else if (state_ == State::kParsedPartialChloFragment) { + state_ = State::kParsedFullMultiPacketChlo; + } else { + QUIC_BUG << "Unexpected state on successful parse " + << StateToString(state_); + } +} + +// static +std::pair<SSL_CTX*, int> TlsChloExtractor::GetSharedSslHandles() { + // Use a lambda to benefit from C++11 guarantee that static variables are + // initialized lazily in a thread-safe manner. |shared_handles| is therefore + // guaranteed to be initialized exactly once and never destructed. + static std::pair<SSL_CTX*, int>* shared_handles = []() { + CRYPTO_library_init(); + SSL_CTX* ssl_ctx = SSL_CTX_new(TLS_with_buffers_method()); + SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION); + static const SSL_QUIC_METHOD kQuicCallbacks{ + TlsChloExtractor::SetReadSecretCallback, + TlsChloExtractor::SetWriteSecretCallback, + TlsChloExtractor::WriteMessageCallback, + TlsChloExtractor::FlushFlightCallback, + TlsChloExtractor::SendAlertCallback}; + SSL_CTX_set_quic_method(ssl_ctx, &kQuicCallbacks); + SSL_CTX_set_select_certificate_cb(ssl_ctx, + TlsChloExtractor::SelectCertCallback); + int ex_data_index = + SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr); + return new std::pair<SSL_CTX*, int>(ssl_ctx, ex_data_index); + }(); + return *shared_handles; +} + +// Sets up the per-instance SSL handle needed by BoringSSL. +void TlsChloExtractor::SetupSslHandle() { + if (ssl_) { + // Handles have already been set up. + return; + } + + std::pair<SSL_CTX*, int> shared_handles = GetSharedSslHandles(); + SSL_CTX* ssl_ctx = shared_handles.first; + int ex_data_index = shared_handles.second; + + ssl_ = bssl::UniquePtr<SSL>(SSL_new(ssl_ctx)); + const int rv = SSL_set_ex_data(ssl_.get(), ex_data_index, this); + if (rv != 1) { + std::string error_details = + quiche::QuicheStrCat("SSL_set_ex_data(", ex_data_index, ") failed"); + QUIC_BUG << error_details; + HandleUnrecoverableError(error_details); + return; + } + SSL_set_accept_state(ssl_.get()); +} + +// Called by other methods to record any unrecoverable failures they experience. +void TlsChloExtractor::HandleUnrecoverableError( + const std::string& error_details) { + if (HasParsedFullChlo()) { + // Ignore errors if we've parsed everything successfully. + QUIC_DLOG(ERROR) << "Ignoring error: " << error_details; + return; + } + QUIC_DLOG(ERROR) << "Handling error: " << error_details; + + state_ = State::kUnrecoverableFailure; + + if (error_details_.empty()) { + error_details_ = error_details; + } else { + error_details_ = quiche::QuicheStrCat(error_details_, "; ", error_details); + } +} + +// static +std::string TlsChloExtractor::StateToString(State state) { + switch (state) { + case State::kInitial: + return "Initial"; + case State::kParsedFullSinglePacketChlo: + return "ParsedFullSinglePacketChlo"; + case State::kParsedFullMultiPacketChlo: + return "ParsedFullMultiPacketChlo"; + case State::kParsedPartialChloFragment: + return "ParsedPartialChloFragment"; + case State::kUnrecoverableFailure: + return "UnrecoverableFailure"; + } + return quiche::QuicheStrCat("Unknown(", static_cast<int>(state), ")"); +} + +} // namespace quic
diff --git a/quic/core/tls_chlo_extractor.h b/quic/core/tls_chlo_extractor.h new file mode 100644 index 0000000..af47144 --- /dev/null +++ b/quic/core/tls_chlo_extractor.h
@@ -0,0 +1,234 @@ +// Copyright (c) 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_CORE_TLS_CHLO_EXTRACTOR_H_ +#define QUICHE_QUIC_CORE_TLS_CHLO_EXTRACTOR_H_ + +#include <memory> +#include <string> +#include <vector> +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "net/third_party/quiche/src/quic/core/quic_framer.h" +#include "net/third_party/quiche/src/quic/core/quic_packets.h" +#include "net/third_party/quiche/src/quic/core/quic_stream_sequencer.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_export.h" + +namespace quic { + +// Utility class that allows extracting information from a QUIC-TLS Client +// Hello. This class creates a QuicFramer to parse the packet, and implements +// QuicFramerVisitorInterface to access the frames parsed by the QuicFramer. It +// then uses a QuicStreamSequencer to reassemble the contents of the crypto +// stream, and implements QuicStreamSequencer::StreamInterface to access the +// reassembled data. +class QUIC_NO_EXPORT TlsChloExtractor + : public QuicFramerVisitorInterface, + public QuicStreamSequencer::StreamInterface { + public: + TlsChloExtractor(); + TlsChloExtractor(const TlsChloExtractor&) = delete; + TlsChloExtractor(TlsChloExtractor&&) = default; + TlsChloExtractor& operator=(const TlsChloExtractor&) = delete; + TlsChloExtractor& operator=(TlsChloExtractor&&) = default; + + enum class State : uint8_t { + kInitial = 0, + kParsedFullSinglePacketChlo = 1, + kParsedFullMultiPacketChlo = 2, + kParsedPartialChloFragment = 3, + kUnrecoverableFailure = 4, + }; + + State state() const { return state_; } + std::vector<std::string> alpns() const { return alpns_; } + std::string server_name() const { return server_name_; } + + // Converts |state| to a human-readable string suitable for logging. + static std::string StateToString(State state); + + // Ingests |packet| and attempts to parse out the CHLO. + void IngestPacket(const ParsedQuicVersion& version, + const QuicReceivedPacket& packet); + + // Returns whether the ingested packets have allowed parsing a complete CHLO. + bool HasParsedFullChlo() const { + return state_ == State::kParsedFullSinglePacketChlo || + state_ == State::kParsedFullMultiPacketChlo; + } + + // Methods from QuicFramerVisitorInterface. + void OnError(QuicFramer* /*framer*/) override {} + bool OnProtocolVersionMismatch(ParsedQuicVersion version) override; + void OnPacket() override {} + void OnPublicResetPacket(const QuicPublicResetPacket& /*packet*/) override {} + void OnVersionNegotiationPacket( + const QuicVersionNegotiationPacket& /*packet*/) override {} + void OnRetryPacket(QuicConnectionId /*original_connection_id*/, + QuicConnectionId /*new_connection_id*/, + quiche::QuicheStringPiece /*retry_token*/, + quiche::QuicheStringPiece /*retry_integrity_tag*/, + quiche::QuicheStringPiece /*retry_without_tag*/) override { + } + bool OnUnauthenticatedPublicHeader(const QuicPacketHeader& header) override; + bool OnUnauthenticatedHeader(const QuicPacketHeader& /*header*/) override { + return true; + } + void OnDecryptedPacket(EncryptionLevel /*level*/) override {} + bool OnPacketHeader(const QuicPacketHeader& /*header*/) override { + return true; + } + void OnCoalescedPacket(const QuicEncryptedPacket& /*packet*/) override {} + void OnUndecryptablePacket(const QuicEncryptedPacket& /*packet*/, + EncryptionLevel /*decryption_level*/, + bool /*has_decryption_key*/) override {} + bool OnStreamFrame(const QuicStreamFrame& /*frame*/) override { return true; } + bool OnCryptoFrame(const QuicCryptoFrame& frame) override; + bool OnAckFrameStart(QuicPacketNumber /*largest_acked*/, + QuicTime::Delta /*ack_delay_time*/) override { + return true; + } + bool OnAckRange(QuicPacketNumber /*start*/, + QuicPacketNumber /*end*/) override { + return true; + } + bool OnAckTimestamp(QuicPacketNumber /*packet_number*/, + QuicTime /*timestamp*/) override { + return true; + } + bool OnAckFrameEnd(QuicPacketNumber /*start*/) override { return true; } + bool OnStopWaitingFrame(const QuicStopWaitingFrame& /*frame*/) override { + return true; + } + bool OnPingFrame(const QuicPingFrame& /*frame*/) override { return true; } + bool OnRstStreamFrame(const QuicRstStreamFrame& /*frame*/) override { + return true; + } + bool OnConnectionCloseFrame( + const QuicConnectionCloseFrame& /*frame*/) override { + return true; + } + bool OnNewConnectionIdFrame( + const QuicNewConnectionIdFrame& /*frame*/) override { + return true; + } + bool OnRetireConnectionIdFrame( + const QuicRetireConnectionIdFrame& /*frame*/) override { + return true; + } + bool OnNewTokenFrame(const QuicNewTokenFrame& /*frame*/) override { + return true; + } + bool OnStopSendingFrame(const QuicStopSendingFrame& /*frame*/) override { + return true; + } + bool OnPathChallengeFrame(const QuicPathChallengeFrame& /*frame*/) override { + return true; + } + bool OnPathResponseFrame(const QuicPathResponseFrame& /*frame*/) override { + return true; + } + bool OnGoAwayFrame(const QuicGoAwayFrame& /*frame*/) override { return true; } + bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& /*frame*/) override { + return true; + } + bool OnStreamsBlockedFrame( + const QuicStreamsBlockedFrame& /*frame*/) override { + return true; + } + bool OnWindowUpdateFrame(const QuicWindowUpdateFrame& /*frame*/) override { + return true; + } + bool OnBlockedFrame(const QuicBlockedFrame& /*frame*/) override { + return true; + } + bool OnPaddingFrame(const QuicPaddingFrame& /*frame*/) override { + return true; + } + bool OnMessageFrame(const QuicMessageFrame& /*frame*/) override { + return true; + } + bool OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& /*frame*/) override { + return true; + } + void OnPacketComplete() override {} + bool IsValidStatelessResetToken(QuicUint128 /*token*/) const override { + return true; + } + void OnAuthenticatedIetfStatelessResetPacket( + const QuicIetfStatelessResetPacket& /*packet*/) override {} + + // Methods from QuicStreamSequencer::StreamInterface. + void OnDataAvailable() override; + void OnFinRead() override {} + void AddBytesConsumed(QuicByteCount /*bytes*/) override {} + void Reset(QuicRstStreamErrorCode /*error*/) override {} + void OnUnrecoverableError(QuicErrorCode error, + const std::string& details) override; + QuicStreamId id() const override { return 0; } + + private: + // Parses the length of the CHLO message by looking at the first four bytes. + // Returns whether we have received enough data to parse the full CHLO now. + bool MaybeAttemptToParseChloLength(); + // Parses the full CHLO message if enough data has been received. + void AttemptToParseFullChlo(); + // Moves to the failed state and records the error details. + void HandleUnrecoverableError(const std::string& error_details); + // Lazily sets up shared SSL handles if needed. + static std::pair<SSL_CTX*, int> GetSharedSslHandles(); + // Lazily sets up the per-instance SSL handle if needed. + void SetupSslHandle(); + // Extract the TlsChloExtractor instance from |ssl|. + static TlsChloExtractor* GetInstanceFromSSL(SSL* ssl); + + // BoringSSL static TLS callbacks. + static enum ssl_select_cert_result_t SelectCertCallback( + const SSL_CLIENT_HELLO* client_hello); + static int SetReadSecretCallback(SSL* ssl, + enum ssl_encryption_level_t level, + const SSL_CIPHER* cipher, + const uint8_t* secret, + size_t secret_length); + static int SetWriteSecretCallback(SSL* ssl, + enum ssl_encryption_level_t level, + const SSL_CIPHER* cipher, + const uint8_t* secret, + size_t secret_length); + static int WriteMessageCallback(SSL* ssl, + enum ssl_encryption_level_t level, + const uint8_t* data, + size_t len); + static int FlushFlightCallback(SSL* ssl); + static int SendAlertCallback(SSL* ssl, + enum ssl_encryption_level_t level, + uint8_t desc); + + // Called by SelectCertCallback. + void HandleParsedChlo(const SSL_CLIENT_HELLO* client_hello); + // Called by callbacks that should never be called. + void HandleUnexpectedCallback(const std::string& callback_name); + // Called by SendAlertCallback. + void SendAlert(uint8_t tls_alert_value); + + // Used to parse received packets to extract single frames. + std::unique_ptr<QuicFramer> framer_; + // Used to reassemble the crypto stream from received CRYPTO frames. + QuicStreamSequencer crypto_stream_sequencer_; + // BoringSSL handle required to parse the CHLO. + bssl::UniquePtr<SSL> ssl_; + // State of this TlsChloExtractor. + State state_; + // Detail string that can be logged in the presence of unrecoverable errors. + std::string error_details_; + // Whether a CRYPTO frame was parsed in this packet. + bool parsed_crypto_frame_in_this_packet_; + // Array of ALPNs parsed from the CHLO. + std::vector<std::string> alpns_; + // SNI parsed from the CHLO. + std::string server_name_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_TLS_CHLO_EXTRACTOR_H_
diff --git a/quic/core/tls_chlo_extractor_test.cc b/quic/core/tls_chlo_extractor_test.cc new file mode 100644 index 0000000..ab99a72 --- /dev/null +++ b/quic/core/tls_chlo_extractor_test.cc
@@ -0,0 +1,109 @@ +// Copyright (c) 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/core/tls_chlo_extractor.h" +#include <memory> + +#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h" +#include "net/third_party/quiche/src/quic/core/quic_connection.h" +#include "net/third_party/quiche/src/quic/core/quic_packet_writer_wrapper.h" +#include "net/third_party/quiche/src/quic/core/quic_versions.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_test.h" +#include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h" +#include "net/third_party/quiche/src/quic/test_tools/first_flight.h" +#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h" + +namespace quic { +namespace test { +namespace { + +class TlsChloExtractorTest : public QuicTestWithParam<ParsedQuicVersion> { + protected: + TlsChloExtractorTest() : version_(GetParam()) {} + + void Initialize() { packets_ = GetFirstFlightOfPackets(version_, config_); } + + void IngestPackets() { + for (const std::unique_ptr<QuicReceivedPacket>& packet : packets_) { + ReceivedPacketInfo packet_info( + QuicSocketAddress(TestPeerIPAddress(), kTestPort), + QuicSocketAddress(TestPeerIPAddress(), kTestPort), *packet); + std::string detailed_error; + bool retry_token_present; + quiche::QuicheStringPiece retry_token; + const QuicErrorCode error = QuicFramer::ParsePublicHeaderDispatcher( + *packet, /*expected_destination_connection_id_length=*/0, + &packet_info.form, &packet_info.long_packet_type, + &packet_info.version_flag, &packet_info.use_length_prefix, + &packet_info.version_label, &packet_info.version, + &packet_info.destination_connection_id, + &packet_info.source_connection_id, &retry_token_present, &retry_token, + &detailed_error); + ASSERT_THAT(error, IsQuicNoError()) << detailed_error; + tls_chlo_extractor_.IngestPacket(packet_info.version, packet_info.packet); + } + packets_.clear(); + } + + void ValidateChloDetails() { + EXPECT_TRUE(tls_chlo_extractor_.HasParsedFullChlo()); + ASSERT_EQ(tls_chlo_extractor_.alpns().size(), 1u); + EXPECT_EQ(tls_chlo_extractor_.alpns()[0], AlpnForVersion(version_)); + EXPECT_EQ(tls_chlo_extractor_.server_name(), TestHostname()); + } + + void IncreaseSizeOfChlo() { + // Add a 2000-byte custom parameter to increase the length of the CHLO. + constexpr auto kCustomParameterId = + static_cast<TransportParameters::TransportParameterId>(0xff33); + std::string kCustomParameterValue(2000, '-'); + config_.custom_transport_parameters_to_send()[kCustomParameterId] = + kCustomParameterValue; + } + + ParsedQuicVersion version_; + TlsChloExtractor tls_chlo_extractor_; + QuicConfig config_; + std::vector<std::unique_ptr<QuicReceivedPacket>> packets_; +}; + +INSTANTIATE_TEST_SUITE_P(TlsChloExtractorTests, + TlsChloExtractorTest, + ::testing::ValuesIn(AllSupportedVersionsWithTls()), + ::testing::PrintToStringParamName()); + +TEST_P(TlsChloExtractorTest, Simple) { + Initialize(); + EXPECT_EQ(packets_.size(), 1u); + IngestPackets(); + ValidateChloDetails(); + EXPECT_EQ(tls_chlo_extractor_.state(), + TlsChloExtractor::State::kParsedFullSinglePacketChlo); +} + +TEST_P(TlsChloExtractorTest, MultiPacket) { + IncreaseSizeOfChlo(); + Initialize(); + EXPECT_EQ(packets_.size(), 2u); + IngestPackets(); + ValidateChloDetails(); + EXPECT_EQ(tls_chlo_extractor_.state(), + TlsChloExtractor::State::kParsedFullMultiPacketChlo); +} + +TEST_P(TlsChloExtractorTest, MultiPacketReordered) { + IncreaseSizeOfChlo(); + Initialize(); + ASSERT_EQ(packets_.size(), 2u); + // Artifically reorder both packets. + std::swap(packets_[0], packets_[1]); + IngestPackets(); + ValidateChloDetails(); + EXPECT_EQ(tls_chlo_extractor_.state(), + TlsChloExtractor::State::kParsedFullMultiPacketChlo); +} + +} // namespace +} // namespace test +} // namespace quic