| // 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 "quic/core/tls_chlo_extractor.h" | |
| #include <cstring> | |
| #include <memory> | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "third_party/boringssl/src/include/openssl/ssl.h" | |
| #include "quic/core/frames/quic_crypto_frame.h" | |
| #include "quic/core/quic_data_reader.h" | |
| #include "quic/core/quic_error_codes.h" | |
| #include "quic/core/quic_framer.h" | |
| #include "quic/core/quic_time.h" | |
| #include "quic/core/quic_types.h" | |
| #include "quic/core/quic_versions.h" | |
| #include "quic/platform/api/quic_bug_tracker.h" | |
| namespace quic { | |
| TlsChloExtractor::TlsChloExtractor() | |
| : crypto_stream_sequencer_(this), | |
| state_(State::kInitial), | |
| parsed_crypto_frame_in_this_packet_(false) {} | |
| TlsChloExtractor::TlsChloExtractor(TlsChloExtractor&& other) | |
| : TlsChloExtractor() { | |
| *this = std::move(other); | |
| } | |
| TlsChloExtractor& TlsChloExtractor::operator=(TlsChloExtractor&& other) { | |
| framer_ = std::move(other.framer_); | |
| if (framer_) { | |
| framer_->set_visitor(this); | |
| } | |
| crypto_stream_sequencer_ = std::move(other.crypto_stream_sequencer_); | |
| crypto_stream_sequencer_.set_stream(this); | |
| ssl_ = std::move(other.ssl_); | |
| if (ssl_) { | |
| std::pair<SSL_CTX*, int> shared_handles = GetSharedSslHandles(); | |
| int ex_data_index = shared_handles.second; | |
| const int rv = SSL_set_ex_data(ssl_.get(), ex_data_index, this); | |
| QUICHE_CHECK_EQ(rv, 1) << "Internal allocation failure in SSL_set_ex_data"; | |
| } | |
| state_ = other.state_; | |
| error_details_ = std::move(other.error_details_); | |
| parsed_crypto_frame_in_this_packet_ = | |
| other.parsed_crypto_frame_in_this_packet_; | |
| alpns_ = std::move(other.alpns_); | |
| server_name_ = std::move(other.server_name_); | |
| return *this; | |
| } | |
| 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(quic_bug_10855_1) << "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(absl::StrCat( | |
| "Crypto stream error ", QuicErrorCodeToString(error), ": ", details)); | |
| } | |
| void TlsChloExtractor::OnUnrecoverableError( | |
| QuicErrorCode error, | |
| QuicIetfTransportErrorCodes ietf_error, | |
| const std::string& details) { | |
| HandleUnrecoverableError(absl::StrCat( | |
| "Crypto stream error ", QuicErrorCodeToString(error), "(", | |
| QuicIetfTransportErrorCodeString(ietf_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(quic_bug_10855_2) << "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 = | |
| absl::StrCat("Unexpected callback ", callback_name); | |
| QUIC_BUG(quic_bug_10855_3) << 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(absl::StrCat( | |
| "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); | |
| absl::string_view 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()) { | |
| absl::string_view 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(quic_bug_10855_4) | |
| << "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); | |
| QUICHE_CHECK_EQ(rv, 1) << "Internal allocation failure in SSL_set_ex_data"; | |
| SSL_set_accept_state(ssl_.get()); | |
| // Make sure we use the right TLS extension codepoint. | |
| int use_legacy_extension = 0; | |
| if (framer_->version().UsesLegacyTlsExtension()) { | |
| use_legacy_extension = 1; | |
| } | |
| SSL_set_quic_use_legacy_codepoint(ssl_.get(), use_legacy_extension); | |
| } | |
| // 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_ = absl::StrCat(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 absl::StrCat("Unknown(", static_cast<int>(state), ")"); | |
| } | |
| std::ostream& operator<<(std::ostream& os, | |
| const TlsChloExtractor::State& state) { | |
| os << TlsChloExtractor::StateToString(state); | |
| return os; | |
| } | |
| } // namespace quic |