| // Copyright (c) 2012 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 "quiche/quic/test_tools/crypto_test_utils.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "absl/strings/escaping.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/optional.h" |
| #include "openssl/bn.h" |
| #include "openssl/ec.h" |
| #include "openssl/ecdsa.h" |
| #include "openssl/nid.h" |
| #include "openssl/sha.h" |
| #include "quiche/quic/core/crypto/certificate_view.h" |
| #include "quiche/quic/core/crypto/channel_id.h" |
| #include "quiche/quic/core/crypto/crypto_handshake.h" |
| #include "quiche/quic/core/crypto/crypto_utils.h" |
| #include "quiche/quic/core/crypto/proof_source_x509.h" |
| #include "quiche/quic/core/crypto/quic_crypto_server_config.h" |
| #include "quiche/quic/core/crypto/quic_decrypter.h" |
| #include "quiche/quic/core/crypto/quic_encrypter.h" |
| #include "quiche/quic/core/crypto/quic_random.h" |
| #include "quiche/quic/core/proto/crypto_server_config_proto.h" |
| #include "quiche/quic/core/quic_clock.h" |
| #include "quiche/quic/core/quic_crypto_client_stream.h" |
| #include "quiche/quic/core/quic_crypto_server_stream_base.h" |
| #include "quiche/quic/core/quic_crypto_stream.h" |
| #include "quiche/quic/core/quic_server_id.h" |
| #include "quiche/quic/core/quic_types.h" |
| #include "quiche/quic/core/quic_utils.h" |
| #include "quiche/quic/core/quic_versions.h" |
| #include "quiche/quic/platform/api/quic_bug_tracker.h" |
| #include "quiche/quic/platform/api/quic_hostname_utils.h" |
| #include "quiche/quic/platform/api/quic_logging.h" |
| #include "quiche/quic/platform/api/quic_socket_address.h" |
| #include "quiche/quic/platform/api/quic_test.h" |
| #include "quiche/quic/test_tools/quic_connection_peer.h" |
| #include "quiche/quic/test_tools/quic_framer_peer.h" |
| #include "quiche/quic/test_tools/quic_stream_peer.h" |
| #include "quiche/quic/test_tools/quic_test_utils.h" |
| #include "quiche/quic/test_tools/simple_quic_framer.h" |
| #include "quiche/quic/test_tools/test_certificates.h" |
| #include "quiche/common/test_tools/quiche_test_utils.h" |
| |
| namespace quic { |
| namespace test { |
| |
| namespace crypto_test_utils { |
| |
| namespace { |
| |
| using testing::_; |
| |
| // CryptoFramerVisitor is a framer visitor that records handshake messages. |
| class CryptoFramerVisitor : public CryptoFramerVisitorInterface { |
| public: |
| CryptoFramerVisitor() : error_(false) {} |
| |
| void OnError(CryptoFramer* /*framer*/) override { error_ = true; } |
| |
| void OnHandshakeMessage(const CryptoHandshakeMessage& message) override { |
| messages_.push_back(message); |
| } |
| |
| bool error() const { return error_; } |
| |
| const std::vector<CryptoHandshakeMessage>& messages() const { |
| return messages_; |
| } |
| |
| private: |
| bool error_; |
| std::vector<CryptoHandshakeMessage> messages_; |
| }; |
| |
| // HexChar parses |c| as a hex character. If valid, it sets |*value| to the |
| // value of the hex character and returns true. Otherwise it returns false. |
| bool HexChar(char c, uint8_t* value) { |
| if (c >= '0' && c <= '9') { |
| *value = c - '0'; |
| return true; |
| } |
| if (c >= 'a' && c <= 'f') { |
| *value = c - 'a' + 10; |
| return true; |
| } |
| if (c >= 'A' && c <= 'F') { |
| *value = c - 'A' + 10; |
| return true; |
| } |
| return false; |
| } |
| |
| } // anonymous namespace |
| |
| FakeClientOptions::FakeClientOptions() {} |
| |
| FakeClientOptions::~FakeClientOptions() {} |
| |
| namespace { |
| // This class is used by GenerateFullCHLO() to extract SCID and STK from |
| // REJ and to construct a full CHLO with these fields and given inchoate |
| // CHLO. |
| class FullChloGenerator { |
| public: |
| FullChloGenerator( |
| QuicCryptoServerConfig* crypto_config, QuicSocketAddress server_addr, |
| QuicSocketAddress client_addr, const QuicClock* clock, |
| ParsedQuicVersion version, |
| quiche::QuicheReferenceCountedPointer<QuicSignedServerConfig> |
| signed_config, |
| QuicCompressedCertsCache* compressed_certs_cache, |
| CryptoHandshakeMessage* out) |
| : crypto_config_(crypto_config), |
| server_addr_(server_addr), |
| client_addr_(client_addr), |
| clock_(clock), |
| version_(version), |
| signed_config_(signed_config), |
| compressed_certs_cache_(compressed_certs_cache), |
| out_(out), |
| params_(new QuicCryptoNegotiatedParameters) {} |
| |
| class ValidateClientHelloCallback : public ValidateClientHelloResultCallback { |
| public: |
| explicit ValidateClientHelloCallback(FullChloGenerator* generator) |
| : generator_(generator) {} |
| void Run(quiche::QuicheReferenceCountedPointer< |
| ValidateClientHelloResultCallback::Result> |
| result, |
| std::unique_ptr<ProofSource::Details> /* details */) override { |
| generator_->ValidateClientHelloDone(std::move(result)); |
| } |
| |
| private: |
| FullChloGenerator* generator_; |
| }; |
| |
| std::unique_ptr<ValidateClientHelloCallback> |
| GetValidateClientHelloCallback() { |
| return std::make_unique<ValidateClientHelloCallback>(this); |
| } |
| |
| private: |
| void ValidateClientHelloDone(quiche::QuicheReferenceCountedPointer< |
| ValidateClientHelloResultCallback::Result> |
| result) { |
| result_ = result; |
| crypto_config_->ProcessClientHello( |
| result_, /*reject_only=*/false, TestConnectionId(1), server_addr_, |
| client_addr_, version_, {version_}, clock_, QuicRandom::GetInstance(), |
| compressed_certs_cache_, params_, signed_config_, |
| /*total_framing_overhead=*/50, kDefaultMaxPacketSize, |
| GetProcessClientHelloCallback()); |
| } |
| |
| class ProcessClientHelloCallback : public ProcessClientHelloResultCallback { |
| public: |
| explicit ProcessClientHelloCallback(FullChloGenerator* generator) |
| : generator_(generator) {} |
| void Run(QuicErrorCode error, const std::string& error_details, |
| std::unique_ptr<CryptoHandshakeMessage> message, |
| std::unique_ptr<DiversificationNonce> /*diversification_nonce*/, |
| std::unique_ptr<ProofSource::Details> /*proof_source_details*/) |
| override { |
| ASSERT_TRUE(message) << QuicErrorCodeToString(error) << " " |
| << error_details; |
| generator_->ProcessClientHelloDone(std::move(message)); |
| } |
| |
| private: |
| FullChloGenerator* generator_; |
| }; |
| |
| std::unique_ptr<ProcessClientHelloCallback> GetProcessClientHelloCallback() { |
| return std::make_unique<ProcessClientHelloCallback>(this); |
| } |
| |
| void ProcessClientHelloDone(std::unique_ptr<CryptoHandshakeMessage> rej) { |
| // Verify output is a REJ. |
| EXPECT_THAT(rej->tag(), testing::Eq(kREJ)); |
| |
| QUIC_VLOG(1) << "Extract valid STK and SCID from\n" << rej->DebugString(); |
| absl::string_view srct; |
| ASSERT_TRUE(rej->GetStringPiece(kSourceAddressTokenTag, &srct)); |
| |
| absl::string_view scfg; |
| ASSERT_TRUE(rej->GetStringPiece(kSCFG, &scfg)); |
| std::unique_ptr<CryptoHandshakeMessage> server_config( |
| CryptoFramer::ParseMessage(scfg)); |
| |
| absl::string_view scid; |
| ASSERT_TRUE(server_config->GetStringPiece(kSCID, &scid)); |
| |
| *out_ = result_->client_hello; |
| out_->SetStringPiece(kSCID, scid); |
| out_->SetStringPiece(kSourceAddressTokenTag, srct); |
| uint64_t xlct = LeafCertHashForTesting(); |
| out_->SetValue(kXLCT, xlct); |
| } |
| |
| protected: |
| QuicCryptoServerConfig* crypto_config_; |
| QuicSocketAddress server_addr_; |
| QuicSocketAddress client_addr_; |
| const QuicClock* clock_; |
| ParsedQuicVersion version_; |
| quiche::QuicheReferenceCountedPointer<QuicSignedServerConfig> signed_config_; |
| QuicCompressedCertsCache* compressed_certs_cache_; |
| CryptoHandshakeMessage* out_; |
| |
| quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> params_; |
| quiche::QuicheReferenceCountedPointer< |
| ValidateClientHelloResultCallback::Result> |
| result_; |
| }; |
| |
| } // namespace |
| |
| std::unique_ptr<QuicCryptoServerConfig> CryptoServerConfigForTesting() { |
| return std::make_unique<QuicCryptoServerConfig>( |
| QuicCryptoServerConfig::TESTING, QuicRandom::GetInstance(), |
| ProofSourceForTesting(), KeyExchangeSource::Default()); |
| } |
| |
| int HandshakeWithFakeServer(QuicConfig* server_quic_config, |
| QuicCryptoServerConfig* crypto_config, |
| MockQuicConnectionHelper* helper, |
| MockAlarmFactory* alarm_factory, |
| PacketSavingConnection* client_conn, |
| QuicCryptoClientStreamBase* client, |
| std::string alpn) { |
| auto* server_conn = new testing::NiceMock<PacketSavingConnection>( |
| helper, alarm_factory, Perspective::IS_SERVER, |
| ParsedVersionOfIndex(client_conn->supported_versions(), 0)); |
| |
| QuicCompressedCertsCache compressed_certs_cache( |
| QuicCompressedCertsCache::kQuicCompressedCertsCacheSize); |
| SetupCryptoServerConfigForTest( |
| server_conn->clock(), server_conn->random_generator(), crypto_config); |
| |
| TestQuicSpdyServerSession server_session( |
| server_conn, *server_quic_config, client_conn->supported_versions(), |
| crypto_config, &compressed_certs_cache); |
| // Call SetServerApplicationStateForResumption so that the fake server |
| // supports 0-RTT in TLS. |
| server_session.Initialize(); |
| server_session.GetMutableCryptoStream() |
| ->SetServerApplicationStateForResumption( |
| std::make_unique<ApplicationState>()); |
| EXPECT_CALL(*server_session.helper(), |
| CanAcceptClientHello(testing::_, testing::_, testing::_, |
| testing::_, testing::_)) |
| .Times(testing::AnyNumber()); |
| EXPECT_CALL(*server_conn, OnCanWrite()).Times(testing::AnyNumber()); |
| EXPECT_CALL(*client_conn, OnCanWrite()).Times(testing::AnyNumber()); |
| EXPECT_CALL(*server_conn, SendCryptoData(_, _, _)) |
| .Times(testing::AnyNumber()); |
| EXPECT_CALL(server_session, SelectAlpn(_)) |
| .WillRepeatedly([alpn](const std::vector<absl::string_view>& alpns) { |
| return std::find(alpns.cbegin(), alpns.cend(), alpn); |
| }); |
| |
| // The client's handshake must have been started already. |
| QUICHE_CHECK_NE(0u, client_conn->encrypted_packets_.size()); |
| |
| CommunicateHandshakeMessages(client_conn, client, server_conn, |
| server_session.GetMutableCryptoStream()); |
| if (client_conn->connected() && server_conn->connected()) { |
| CompareClientAndServerKeys(client, server_session.GetMutableCryptoStream()); |
| } |
| |
| return client->num_sent_client_hellos(); |
| } |
| |
| int HandshakeWithFakeClient(MockQuicConnectionHelper* helper, |
| MockAlarmFactory* alarm_factory, |
| PacketSavingConnection* server_conn, |
| QuicCryptoServerStreamBase* server, |
| const QuicServerId& server_id, |
| const FakeClientOptions& options, |
| std::string alpn) { |
| // This function does not do version negotiation; read the supported versions |
| // directly from the server connection instead. |
| ParsedQuicVersionVector supported_versions = |
| server_conn->supported_versions(); |
| if (options.only_tls_versions) { |
| supported_versions.erase( |
| std::remove_if(supported_versions.begin(), supported_versions.end(), |
| [](const ParsedQuicVersion& version) { |
| return version.handshake_protocol != PROTOCOL_TLS1_3; |
| }), |
| supported_versions.end()); |
| QUICHE_CHECK(!options.only_quic_crypto_versions); |
| } else if (options.only_quic_crypto_versions) { |
| supported_versions.erase( |
| std::remove_if(supported_versions.begin(), supported_versions.end(), |
| [](const ParsedQuicVersion& version) { |
| return version.handshake_protocol != |
| PROTOCOL_QUIC_CRYPTO; |
| }), |
| supported_versions.end()); |
| } |
| PacketSavingConnection* client_conn = new PacketSavingConnection( |
| helper, alarm_factory, Perspective::IS_CLIENT, supported_versions); |
| // Advance the time, because timers do not like uninitialized times. |
| client_conn->AdvanceTime(QuicTime::Delta::FromSeconds(1)); |
| |
| QuicCryptoClientConfig crypto_config(ProofVerifierForTesting()); |
| TestQuicSpdyClientSession client_session(client_conn, DefaultQuicConfig(), |
| supported_versions, server_id, |
| &crypto_config); |
| |
| EXPECT_CALL(client_session, OnProofValid(testing::_)) |
| .Times(testing::AnyNumber()); |
| EXPECT_CALL(client_session, OnProofVerifyDetailsAvailable(testing::_)) |
| .Times(testing::AnyNumber()); |
| EXPECT_CALL(*client_conn, OnCanWrite()).Times(testing::AnyNumber()); |
| if (!alpn.empty()) { |
| EXPECT_CALL(client_session, GetAlpnsToOffer()) |
| .WillRepeatedly(testing::Return(std::vector<std::string>({alpn}))); |
| } else { |
| EXPECT_CALL(client_session, GetAlpnsToOffer()) |
| .WillRepeatedly(testing::Return(std::vector<std::string>( |
| {AlpnForVersion(client_conn->version())}))); |
| } |
| client_session.GetMutableCryptoStream()->CryptoConnect(); |
| QUICHE_CHECK_EQ(1u, client_conn->encrypted_packets_.size()); |
| |
| CommunicateHandshakeMessages(client_conn, |
| client_session.GetMutableCryptoStream(), |
| server_conn, server); |
| |
| if (server->one_rtt_keys_available() && server->encryption_established()) { |
| CompareClientAndServerKeys(client_session.GetMutableCryptoStream(), server); |
| } |
| |
| return client_session.GetCryptoStream()->num_sent_client_hellos(); |
| } |
| |
| void SetupCryptoServerConfigForTest(const QuicClock* clock, QuicRandom* rand, |
| QuicCryptoServerConfig* crypto_config) { |
| QuicCryptoServerConfig::ConfigOptions options; |
| options.channel_id_enabled = true; |
| std::unique_ptr<CryptoHandshakeMessage> scfg = |
| crypto_config->AddDefaultConfig(rand, clock, options); |
| } |
| |
| void SendHandshakeMessageToStream(QuicCryptoStream* stream, |
| const CryptoHandshakeMessage& message, |
| Perspective /*perspective*/) { |
| const QuicData& data = message.GetSerialized(); |
| QuicSession* session = QuicStreamPeer::session(stream); |
| if (!QuicVersionUsesCryptoFrames(session->transport_version())) { |
| QuicStreamFrame frame( |
| QuicUtils::GetCryptoStreamId(session->transport_version()), false, |
| stream->crypto_bytes_read(), data.AsStringPiece()); |
| stream->OnStreamFrame(frame); |
| } else { |
| EncryptionLevel level = session->connection()->last_decrypted_level(); |
| QuicCryptoFrame frame(level, stream->BytesReadOnLevel(level), |
| data.AsStringPiece()); |
| stream->OnCryptoFrame(frame); |
| } |
| } |
| |
| void CommunicateHandshakeMessages(PacketSavingConnection* client_conn, |
| QuicCryptoStream* client, |
| PacketSavingConnection* server_conn, |
| QuicCryptoStream* server) { |
| size_t client_i = 0, server_i = 0; |
| while (client_conn->connected() && server_conn->connected() && |
| (!client->one_rtt_keys_available() || |
| !server->one_rtt_keys_available())) { |
| ASSERT_GT(client_conn->encrypted_packets_.size(), client_i); |
| QUIC_LOG(INFO) << "Processing " |
| << client_conn->encrypted_packets_.size() - client_i |
| << " packets client->server"; |
| MovePackets(client_conn, &client_i, server, server_conn, |
| Perspective::IS_SERVER, /*process_stream_data=*/false); |
| |
| if (client->one_rtt_keys_available() && server->one_rtt_keys_available() && |
| server_conn->encrypted_packets_.size() == server_i) { |
| break; |
| } |
| ASSERT_GT(server_conn->encrypted_packets_.size(), server_i); |
| QUIC_LOG(INFO) << "Processing " |
| << server_conn->encrypted_packets_.size() - server_i |
| << " packets server->client"; |
| MovePackets(server_conn, &server_i, client, client_conn, |
| Perspective::IS_CLIENT, /*process_stream_data=*/false); |
| } |
| } |
| |
| bool CommunicateHandshakeMessagesUntil(PacketSavingConnection* client_conn, |
| QuicCryptoStream* client, |
| std::function<bool()> client_condition, |
| PacketSavingConnection* server_conn, |
| QuicCryptoStream* server, |
| std::function<bool()> server_condition, |
| bool process_stream_data) { |
| size_t client_next_packet_to_deliver = |
| client_conn->number_of_packets_delivered_; |
| size_t server_next_packet_to_deliver = |
| server_conn->number_of_packets_delivered_; |
| while ( |
| client_conn->connected() && server_conn->connected() && |
| (!client_condition() || !server_condition()) && |
| (client_conn->encrypted_packets_.size() > client_next_packet_to_deliver || |
| server_conn->encrypted_packets_.size() > |
| server_next_packet_to_deliver)) { |
| if (!server_condition()) { |
| QUIC_LOG(INFO) << "Processing " |
| << client_conn->encrypted_packets_.size() - |
| client_next_packet_to_deliver |
| << " packets client->server"; |
| MovePackets(client_conn, &client_next_packet_to_deliver, server, |
| server_conn, Perspective::IS_SERVER, process_stream_data); |
| } |
| if (!client_condition()) { |
| QUIC_LOG(INFO) << "Processing " |
| << server_conn->encrypted_packets_.size() - |
| server_next_packet_to_deliver |
| << " packets server->client"; |
| MovePackets(server_conn, &server_next_packet_to_deliver, client, |
| client_conn, Perspective::IS_CLIENT, process_stream_data); |
| } |
| } |
| client_conn->number_of_packets_delivered_ = client_next_packet_to_deliver; |
| server_conn->number_of_packets_delivered_ = server_next_packet_to_deliver; |
| bool result = client_condition() && server_condition(); |
| if (!result) { |
| QUIC_LOG(INFO) << "CommunicateHandshakeMessagesUnti failed with state: " |
| "client connected? " |
| << client_conn->connected() << " server connected? " |
| << server_conn->connected() << " client condition met? " |
| << client_condition() << " server condition met? " |
| << server_condition(); |
| } |
| return result; |
| } |
| |
| std::pair<size_t, size_t> AdvanceHandshake(PacketSavingConnection* client_conn, |
| QuicCryptoStream* client, |
| size_t client_i, |
| PacketSavingConnection* server_conn, |
| QuicCryptoStream* server, |
| size_t server_i) { |
| if (client_conn->encrypted_packets_.size() != client_i) { |
| QUIC_LOG(INFO) << "Processing " |
| << client_conn->encrypted_packets_.size() - client_i |
| << " packets client->server"; |
| MovePackets(client_conn, &client_i, server, server_conn, |
| Perspective::IS_SERVER, /*process_stream_data=*/false); |
| } |
| |
| if (server_conn->encrypted_packets_.size() != server_i) { |
| QUIC_LOG(INFO) << "Processing " |
| << server_conn->encrypted_packets_.size() - server_i |
| << " packets server->client"; |
| MovePackets(server_conn, &server_i, client, client_conn, |
| Perspective::IS_CLIENT, /*process_stream_data=*/false); |
| } |
| |
| return std::make_pair(client_i, server_i); |
| } |
| |
| std::string GetValueForTag(const CryptoHandshakeMessage& message, QuicTag tag) { |
| auto it = message.tag_value_map().find(tag); |
| if (it == message.tag_value_map().end()) { |
| return std::string(); |
| } |
| return it->second; |
| } |
| |
| uint64_t LeafCertHashForTesting() { |
| quiche::QuicheReferenceCountedPointer<ProofSource::Chain> chain; |
| QuicSocketAddress server_address(QuicIpAddress::Any4(), 42); |
| QuicSocketAddress client_address(QuicIpAddress::Any4(), 43); |
| QuicCryptoProof proof; |
| std::unique_ptr<ProofSource> proof_source(ProofSourceForTesting()); |
| |
| class Callback : public ProofSource::Callback { |
| public: |
| Callback(bool* ok, |
| quiche::QuicheReferenceCountedPointer<ProofSource::Chain>* chain) |
| : ok_(ok), chain_(chain) {} |
| |
| void Run( |
| bool ok, |
| const quiche::QuicheReferenceCountedPointer<ProofSource::Chain>& chain, |
| const QuicCryptoProof& /* proof */, |
| std::unique_ptr<ProofSource::Details> /* details */) override { |
| *ok_ = ok; |
| *chain_ = chain; |
| } |
| |
| private: |
| bool* ok_; |
| quiche::QuicheReferenceCountedPointer<ProofSource::Chain>* chain_; |
| }; |
| |
| // Note: relies on the callback being invoked synchronously |
| bool ok = false; |
| proof_source->GetProof( |
| server_address, client_address, "", "", |
| AllSupportedVersionsWithQuicCrypto().front().transport_version, "", |
| std::unique_ptr<ProofSource::Callback>(new Callback(&ok, &chain))); |
| if (!ok || chain->certs.empty()) { |
| QUICHE_DCHECK(false) << "Proof generation failed"; |
| return 0; |
| } |
| |
| return QuicUtils::FNV1a_64_Hash(chain->certs.at(0)); |
| } |
| |
| void FillInDummyReject(CryptoHandshakeMessage* rej) { |
| rej->set_tag(kREJ); |
| |
| // Minimum SCFG that passes config validation checks. |
| // clang-format off |
| unsigned char scfg[] = { |
| // SCFG |
| 0x53, 0x43, 0x46, 0x47, |
| // num entries |
| 0x01, 0x00, |
| // padding |
| 0x00, 0x00, |
| // EXPY |
| 0x45, 0x58, 0x50, 0x59, |
| // EXPY end offset |
| 0x08, 0x00, 0x00, 0x00, |
| // Value |
| '1', '2', '3', '4', |
| '5', '6', '7', '8' |
| }; |
| // clang-format on |
| rej->SetValue(kSCFG, scfg); |
| rej->SetStringPiece(kServerNonceTag, "SERVER_NONCE"); |
| int64_t ttl = 2 * 24 * 60 * 60; |
| rej->SetValue(kSTTL, ttl); |
| std::vector<QuicTag> reject_reasons; |
| reject_reasons.push_back(CLIENT_NONCE_INVALID_FAILURE); |
| rej->SetVector(kRREJ, reject_reasons); |
| } |
| |
| namespace { |
| |
| #define RETURN_STRING_LITERAL(x) \ |
| case x: \ |
| return #x |
| |
| std::string EncryptionLevelString(EncryptionLevel level) { |
| switch (level) { |
| RETURN_STRING_LITERAL(ENCRYPTION_INITIAL); |
| RETURN_STRING_LITERAL(ENCRYPTION_HANDSHAKE); |
| RETURN_STRING_LITERAL(ENCRYPTION_ZERO_RTT); |
| RETURN_STRING_LITERAL(ENCRYPTION_FORWARD_SECURE); |
| default: |
| return ""; |
| } |
| } |
| |
| void CompareCrypters(const QuicEncrypter* encrypter, |
| const QuicDecrypter* decrypter, std::string label) { |
| if (encrypter == nullptr || decrypter == nullptr) { |
| ADD_FAILURE() << "Expected non-null crypters; have " << encrypter << " and " |
| << decrypter << " for " << label; |
| return; |
| } |
| absl::string_view encrypter_key = encrypter->GetKey(); |
| absl::string_view encrypter_iv = encrypter->GetNoncePrefix(); |
| absl::string_view decrypter_key = decrypter->GetKey(); |
| absl::string_view decrypter_iv = decrypter->GetNoncePrefix(); |
| quiche::test::CompareCharArraysWithHexError( |
| label + " key", encrypter_key.data(), encrypter_key.length(), |
| decrypter_key.data(), decrypter_key.length()); |
| quiche::test::CompareCharArraysWithHexError( |
| label + " iv", encrypter_iv.data(), encrypter_iv.length(), |
| decrypter_iv.data(), decrypter_iv.length()); |
| } |
| |
| } // namespace |
| |
| void CompareClientAndServerKeys(QuicCryptoClientStreamBase* client, |
| QuicCryptoServerStreamBase* server) { |
| QuicFramer* client_framer = QuicConnectionPeer::GetFramer( |
| QuicStreamPeer::session(client)->connection()); |
| QuicFramer* server_framer = QuicConnectionPeer::GetFramer( |
| QuicStreamPeer::session(server)->connection()); |
| for (EncryptionLevel level : |
| {ENCRYPTION_HANDSHAKE, ENCRYPTION_ZERO_RTT, ENCRYPTION_FORWARD_SECURE}) { |
| SCOPED_TRACE(EncryptionLevelString(level)); |
| const QuicEncrypter* client_encrypter( |
| QuicFramerPeer::GetEncrypter(client_framer, level)); |
| const QuicDecrypter* server_decrypter( |
| QuicFramerPeer::GetDecrypter(server_framer, level)); |
| if (level == ENCRYPTION_FORWARD_SECURE || |
| !((level == ENCRYPTION_HANDSHAKE || level == ENCRYPTION_ZERO_RTT || |
| client_encrypter == nullptr) && |
| (level == ENCRYPTION_ZERO_RTT || server_decrypter == nullptr))) { |
| CompareCrypters(client_encrypter, server_decrypter, |
| "client " + EncryptionLevelString(level) + " write"); |
| } |
| const QuicEncrypter* server_encrypter( |
| QuicFramerPeer::GetEncrypter(server_framer, level)); |
| const QuicDecrypter* client_decrypter( |
| QuicFramerPeer::GetDecrypter(client_framer, level)); |
| if (level == ENCRYPTION_FORWARD_SECURE || |
| !(server_encrypter == nullptr && |
| (level == ENCRYPTION_HANDSHAKE || level == ENCRYPTION_ZERO_RTT || |
| client_decrypter == nullptr))) { |
| CompareCrypters(server_encrypter, client_decrypter, |
| "server " + EncryptionLevelString(level) + " write"); |
| } |
| } |
| |
| absl::string_view client_subkey_secret = |
| client->crypto_negotiated_params().subkey_secret; |
| absl::string_view server_subkey_secret = |
| server->crypto_negotiated_params().subkey_secret; |
| quiche::test::CompareCharArraysWithHexError( |
| "subkey secret", client_subkey_secret.data(), |
| client_subkey_secret.length(), server_subkey_secret.data(), |
| server_subkey_secret.length()); |
| } |
| |
| QuicTag ParseTag(const char* tagstr) { |
| const size_t len = strlen(tagstr); |
| QUICHE_CHECK_NE(0u, len); |
| |
| QuicTag tag = 0; |
| |
| if (tagstr[0] == '#') { |
| QUICHE_CHECK_EQ(static_cast<size_t>(1 + 2 * 4), len); |
| tagstr++; |
| |
| for (size_t i = 0; i < 8; i++) { |
| tag <<= 4; |
| |
| uint8_t v = 0; |
| QUICHE_CHECK(HexChar(tagstr[i], &v)); |
| tag |= v; |
| } |
| |
| return tag; |
| } |
| |
| QUICHE_CHECK_LE(len, 4u); |
| for (size_t i = 0; i < 4; i++) { |
| tag >>= 8; |
| if (i < len) { |
| tag |= static_cast<uint32_t>(tagstr[i]) << 24; |
| } |
| } |
| |
| return tag; |
| } |
| |
| CryptoHandshakeMessage CreateCHLO( |
| std::vector<std::pair<std::string, std::string>> tags_and_values) { |
| return CreateCHLO(tags_and_values, -1); |
| } |
| |
| CryptoHandshakeMessage CreateCHLO( |
| std::vector<std::pair<std::string, std::string>> tags_and_values, |
| int minimum_size_bytes) { |
| CryptoHandshakeMessage msg; |
| msg.set_tag(MakeQuicTag('C', 'H', 'L', 'O')); |
| |
| if (minimum_size_bytes > 0) { |
| msg.set_minimum_size(minimum_size_bytes); |
| } |
| |
| for (const auto& tag_and_value : tags_and_values) { |
| const std::string& tag = tag_and_value.first; |
| const std::string& value = tag_and_value.second; |
| |
| const QuicTag quic_tag = ParseTag(tag.c_str()); |
| |
| size_t value_len = value.length(); |
| if (value_len > 0 && value[0] == '#') { |
| // This is ascii encoded hex. |
| std::string hex_value = |
| absl::HexStringToBytes(absl::string_view(&value[1])); |
| msg.SetStringPiece(quic_tag, hex_value); |
| continue; |
| } |
| msg.SetStringPiece(quic_tag, value); |
| } |
| |
| // The CryptoHandshakeMessage needs to be serialized and parsed to ensure |
| // that any padding is included. |
| std::unique_ptr<QuicData> bytes = |
| CryptoFramer::ConstructHandshakeMessage(msg); |
| std::unique_ptr<CryptoHandshakeMessage> parsed( |
| CryptoFramer::ParseMessage(bytes->AsStringPiece())); |
| QUICHE_CHECK(parsed); |
| |
| return *parsed; |
| } |
| |
| void MovePackets(PacketSavingConnection* source_conn, |
| size_t* inout_packet_index, QuicCryptoStream* dest_stream, |
| PacketSavingConnection* dest_conn, |
| Perspective dest_perspective, bool process_stream_data) { |
| SimpleQuicFramer framer(source_conn->supported_versions(), dest_perspective); |
| QuicFramerPeer::SetLastSerializedServerConnectionId(framer.framer(), |
| TestConnectionId()); |
| |
| SimpleQuicFramer null_encryption_framer(source_conn->supported_versions(), |
| dest_perspective); |
| QuicFramerPeer::SetLastSerializedServerConnectionId( |
| null_encryption_framer.framer(), TestConnectionId()); |
| |
| size_t index = *inout_packet_index; |
| for (; index < source_conn->encrypted_packets_.size(); index++) { |
| if (!dest_conn->connected()) { |
| QUIC_LOG(INFO) |
| << "Destination connection disconnected. Skipping packet at index " |
| << index; |
| continue; |
| } |
| // In order to properly test the code we need to perform encryption and |
| // decryption so that the crypters latch when expected. The crypters are in |
| // |dest_conn|, but we don't want to try and use them there. Instead we swap |
| // them into |framer|, perform the decryption with them, and then swap ther |
| // back. |
| QuicConnectionPeer::SwapCrypters(dest_conn, framer.framer()); |
| QuicConnectionPeer::AddBytesReceived( |
| dest_conn, source_conn->encrypted_packets_[index]->length()); |
| if (!framer.ProcessPacket(*source_conn->encrypted_packets_[index])) { |
| // The framer will be unable to decrypt zero-rtt packets sent during |
| // handshake or forward-secure packets sent after the handshake is |
| // complete. Don't treat them as handshake packets. |
| QuicConnectionPeer::SwapCrypters(dest_conn, framer.framer()); |
| continue; |
| } |
| QuicConnectionPeer::SwapCrypters(dest_conn, framer.framer()); |
| |
| // Install a packet flusher such that the packets generated by |dest_conn| |
| // in response to this packet are more likely to be coalesced and/or batched |
| // in the writer. |
| QuicConnection::ScopedPacketFlusher flusher(dest_conn); |
| |
| dest_conn->OnDecryptedPacket( |
| source_conn->encrypted_packets_[index]->length(), |
| framer.last_decrypted_level()); |
| |
| if (dest_stream->handshake_protocol() == PROTOCOL_TLS1_3) { |
| // Try to process the packet with a framer that only has the NullDecrypter |
| // for decryption. If ProcessPacket succeeds, that means the packet was |
| // encrypted with the NullEncrypter. With the TLS handshaker in use, no |
| // packets should ever be encrypted with the NullEncrypter, instead |
| // they're encrypted with an obfuscation cipher based on QUIC version and |
| // connection ID. |
| QUIC_LOG(INFO) << "Attempting to decrypt with NullDecrypter: " |
| "expect a decryption failure on the next log line."; |
| ASSERT_FALSE(null_encryption_framer.ProcessPacket( |
| *source_conn->encrypted_packets_[index])) |
| << "No TLS packets should be encrypted with the NullEncrypter"; |
| } |
| |
| // Since we're using QuicFramers separate from the connections to move |
| // packets, the QuicConnection never gets notified about what level the last |
| // packet was decrypted at. This is needed by TLS to know what encryption |
| // level was used for the data it's receiving, so we plumb this information |
| // from the SimpleQuicFramer back into the connection. |
| dest_conn->OnDecryptedPacket( |
| source_conn->encrypted_packets_[index]->length(), |
| framer.last_decrypted_level()); |
| |
| QuicConnectionPeer::SetCurrentPacket( |
| dest_conn, source_conn->encrypted_packets_[index]->AsStringPiece()); |
| for (const auto& stream_frame : framer.stream_frames()) { |
| if (process_stream_data && |
| dest_stream->handshake_protocol() == PROTOCOL_TLS1_3) { |
| // Deliver STREAM_FRAME such that application state is available and can |
| // be stored along with resumption ticket in session cache, |
| dest_conn->OnStreamFrame(*stream_frame); |
| } else { |
| // Ignore stream frames that are sent on other streams in the crypto |
| // event. |
| if (stream_frame->stream_id == dest_stream->id()) { |
| dest_stream->OnStreamFrame(*stream_frame); |
| } |
| } |
| } |
| for (const auto& crypto_frame : framer.crypto_frames()) { |
| dest_stream->OnCryptoFrame(*crypto_frame); |
| } |
| if (!framer.connection_close_frames().empty() && dest_conn->connected()) { |
| dest_conn->OnConnectionCloseFrame(framer.connection_close_frames()[0]); |
| } |
| } |
| *inout_packet_index = index; |
| |
| QuicConnectionPeer::SetCurrentPacket(dest_conn, |
| absl::string_view(nullptr, 0)); |
| } |
| |
| CryptoHandshakeMessage GenerateDefaultInchoateCHLO( |
| const QuicClock* clock, QuicTransportVersion version, |
| QuicCryptoServerConfig* crypto_config) { |
| // clang-format off |
| return CreateCHLO( |
| {{"PDMD", "X509"}, |
| {"AEAD", "AESG"}, |
| {"KEXS", "C255"}, |
| {"PUBS", GenerateClientPublicValuesHex().c_str()}, |
| {"NONC", GenerateClientNonceHex(clock, crypto_config).c_str()}, |
| {"VER\0", QuicVersionLabelToString( |
| CreateQuicVersionLabel( |
| ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, version))).c_str()}}, |
| kClientHelloMinimumSize); |
| // clang-format on |
| } |
| |
| std::string GenerateClientNonceHex(const QuicClock* clock, |
| QuicCryptoServerConfig* crypto_config) { |
| QuicCryptoServerConfig::ConfigOptions old_config_options; |
| QuicCryptoServerConfig::ConfigOptions new_config_options; |
| old_config_options.id = "old-config-id"; |
| crypto_config->AddDefaultConfig(QuicRandom::GetInstance(), clock, |
| old_config_options); |
| QuicServerConfigProtobuf primary_config = crypto_config->GenerateConfig( |
| QuicRandom::GetInstance(), clock, new_config_options); |
| primary_config.set_primary_time(clock->WallNow().ToUNIXSeconds()); |
| std::unique_ptr<CryptoHandshakeMessage> msg = |
| crypto_config->AddConfig(primary_config, clock->WallNow()); |
| absl::string_view orbit; |
| QUICHE_CHECK(msg->GetStringPiece(kORBT, &orbit)); |
| std::string nonce; |
| CryptoUtils::GenerateNonce(clock->WallNow(), QuicRandom::GetInstance(), orbit, |
| &nonce); |
| return ("#" + absl::BytesToHexString(nonce)); |
| } |
| |
| std::string GenerateClientPublicValuesHex() { |
| char public_value[32]; |
| memset(public_value, 42, sizeof(public_value)); |
| return ("#" + absl::BytesToHexString( |
| absl::string_view(public_value, sizeof(public_value)))); |
| } |
| |
| void GenerateFullCHLO( |
| const CryptoHandshakeMessage& inchoate_chlo, |
| QuicCryptoServerConfig* crypto_config, QuicSocketAddress server_addr, |
| QuicSocketAddress client_addr, QuicTransportVersion transport_version, |
| const QuicClock* clock, |
| quiche::QuicheReferenceCountedPointer<QuicSignedServerConfig> signed_config, |
| QuicCompressedCertsCache* compressed_certs_cache, |
| CryptoHandshakeMessage* out) { |
| // Pass a inchoate CHLO. |
| FullChloGenerator generator( |
| crypto_config, server_addr, client_addr, clock, |
| ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, transport_version), signed_config, |
| compressed_certs_cache, out); |
| crypto_config->ValidateClientHello( |
| inchoate_chlo, client_addr, server_addr, transport_version, clock, |
| signed_config, generator.GetValidateClientHelloCallback()); |
| } |
| |
| namespace { |
| |
| constexpr char kTestProofHostname[] = "test.example.com"; |
| |
| class TestProofSource : public ProofSourceX509 { |
| public: |
| TestProofSource() |
| : ProofSourceX509( |
| quiche::QuicheReferenceCountedPointer<ProofSource::Chain>( |
| new ProofSource::Chain( |
| std::vector<std::string>{std::string(kTestCertificate)})), |
| std::move(*CertificatePrivateKey::LoadFromDer( |
| kTestCertificatePrivateKey))) { |
| QUICHE_DCHECK(valid()); |
| } |
| |
| protected: |
| void MaybeAddSctsForHostname(absl::string_view /*hostname*/, |
| std::string& leaf_cert_scts) override { |
| leaf_cert_scts = "Certificate Transparency is really nice"; |
| } |
| }; |
| |
| class TestProofVerifier : public ProofVerifier { |
| public: |
| TestProofVerifier() |
| : certificate_(std::move( |
| *CertificateView::ParseSingleCertificate(kTestCertificate))) {} |
| |
| class Details : public ProofVerifyDetails { |
| public: |
| ProofVerifyDetails* Clone() const override { return new Details(*this); } |
| }; |
| |
| QuicAsyncStatus VerifyProof( |
| const std::string& hostname, const uint16_t port, |
| const std::string& server_config, |
| QuicTransportVersion /*transport_version*/, absl::string_view chlo_hash, |
| const std::vector<std::string>& certs, const std::string& cert_sct, |
| const std::string& signature, const ProofVerifyContext* context, |
| std::string* error_details, std::unique_ptr<ProofVerifyDetails>* details, |
| std::unique_ptr<ProofVerifierCallback> callback) override { |
| absl::optional<std::string> payload = |
| CryptoUtils::GenerateProofPayloadToBeSigned(chlo_hash, server_config); |
| if (!payload.has_value()) { |
| *error_details = "Failed to serialize signed payload"; |
| return QUIC_FAILURE; |
| } |
| if (!certificate_.VerifySignature(*payload, signature, |
| SSL_SIGN_RSA_PSS_RSAE_SHA256)) { |
| *error_details = "Invalid signature"; |
| return QUIC_FAILURE; |
| } |
| |
| uint8_t out_alert; |
| return VerifyCertChain(hostname, port, certs, /*ocsp_response=*/"", |
| cert_sct, context, error_details, details, |
| &out_alert, std::move(callback)); |
| } |
| |
| QuicAsyncStatus VerifyCertChain( |
| const std::string& hostname, const uint16_t /*port*/, |
| const std::vector<std::string>& certs, |
| const std::string& /*ocsp_response*/, const std::string& /*cert_sct*/, |
| const ProofVerifyContext* /*context*/, std::string* error_details, |
| std::unique_ptr<ProofVerifyDetails>* details, uint8_t* /*out_alert*/, |
| std::unique_ptr<ProofVerifierCallback> /*callback*/) override { |
| std::string normalized_hostname = |
| QuicHostnameUtils::NormalizeHostname(hostname); |
| if (normalized_hostname != kTestProofHostname) { |
| *error_details = absl::StrCat("Invalid hostname, expected ", |
| kTestProofHostname, " got ", hostname); |
| return QUIC_FAILURE; |
| } |
| if (certs.empty() || certs.front() != kTestCertificate) { |
| *error_details = "Received certificate different from the expected"; |
| return QUIC_FAILURE; |
| } |
| *details = std::make_unique<Details>(); |
| return QUIC_SUCCESS; |
| } |
| |
| std::unique_ptr<ProofVerifyContext> CreateDefaultContext() override { |
| return nullptr; |
| } |
| |
| private: |
| CertificateView certificate_; |
| }; |
| |
| } // namespace |
| |
| std::unique_ptr<ProofSource> ProofSourceForTesting() { |
| return std::make_unique<TestProofSource>(); |
| } |
| |
| std::unique_ptr<ProofVerifier> ProofVerifierForTesting() { |
| return std::make_unique<TestProofVerifier>(); |
| } |
| |
| std::string CertificateHostnameForTesting() { return kTestProofHostname; } |
| |
| std::unique_ptr<ProofVerifyContext> ProofVerifyContextForTesting() { |
| return nullptr; |
| } |
| |
| } // namespace crypto_test_utils |
| } // namespace test |
| } // namespace quic |