| // Copyright 2016 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/stateless_rejector.h" |
| |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake_message.h" |
| #include "net/third_party/quiche/src/quic/core/crypto/proof_source.h" |
| #include "net/third_party/quiche/src/quic/core/quic_utils.h" |
| #include "net/third_party/quiche/src/quic/core/tls_server_handshaker.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_flags.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_test.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h" |
| #include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_crypto_server_config_peer.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h" |
| |
| namespace quic { |
| namespace test { |
| namespace { |
| |
| QuicConnectionId TestServerDesignatedConnectionId() { |
| return TestConnectionId(24); |
| } |
| |
| // All four combinations of the two flags involved. |
| enum FlagsMode { ENABLED, STATELESS_DISABLED, CHEAP_DISABLED, BOTH_DISABLED }; |
| |
| const char* FlagsModeToString(FlagsMode mode) { |
| switch (mode) { |
| case ENABLED: |
| return "ENABLED"; |
| case STATELESS_DISABLED: |
| return "STATELESS_DISABLED"; |
| case CHEAP_DISABLED: |
| return "CHEAP_DISABLED"; |
| case BOTH_DISABLED: |
| return "BOTH_DISABLED"; |
| default: |
| QUIC_DLOG(FATAL) << "Unexpected FlagsMode"; |
| return nullptr; |
| } |
| } |
| |
| // Test various combinations of QUIC version and flag state. |
| struct TestParams { |
| ParsedQuicVersion version = UnsupportedQuicVersion(); |
| FlagsMode flags; |
| }; |
| |
| std::string TestParamToString( |
| const testing::TestParamInfo<TestParams>& params) { |
| return QuicStrCat("v", ParsedQuicVersionToString(params.param.version), "_", |
| FlagsModeToString(params.param.flags)); |
| } |
| |
| std::vector<TestParams> GetTestParams() { |
| std::vector<TestParams> params; |
| for (FlagsMode flags : |
| {ENABLED, STATELESS_DISABLED, CHEAP_DISABLED, BOTH_DISABLED}) { |
| for (ParsedQuicVersion version : AllSupportedVersions()) { |
| TestParams param; |
| param.version = version; |
| param.flags = flags; |
| params.push_back(param); |
| } |
| } |
| return params; |
| } |
| |
| class StatelessRejectorTest : public QuicTestWithParam<TestParams> { |
| public: |
| StatelessRejectorTest() |
| : proof_source_(crypto_test_utils::ProofSourceForTesting()), |
| config_(QuicCryptoServerConfig::TESTING, |
| QuicRandom::GetInstance(), |
| crypto_test_utils::ProofSourceForTesting(), |
| KeyExchangeSource::Default(), |
| TlsServerHandshaker::CreateSslCtx()), |
| config_peer_(&config_), |
| compressed_certs_cache_( |
| QuicCompressedCertsCache::kQuicCompressedCertsCacheSize), |
| rejector_(QuicMakeUnique<StatelessRejector>( |
| GetParam().version, |
| AllSupportedVersions(), |
| &config_, |
| &compressed_certs_cache_, |
| &clock_, |
| QuicRandom::GetInstance(), |
| kDefaultMaxPacketSize, |
| QuicSocketAddress(QuicIpAddress::Loopback4(), 12345), |
| QuicSocketAddress(QuicIpAddress::Loopback4(), 443))) { |
| SetQuicReloadableFlag( |
| enable_quic_stateless_reject_support, |
| GetParam().flags == ENABLED || GetParam().flags == CHEAP_DISABLED); |
| SetQuicReloadableFlag( |
| quic_use_cheap_stateless_rejects, |
| GetParam().flags == ENABLED || GetParam().flags == STATELESS_DISABLED); |
| |
| // Add a new primary config. |
| std::unique_ptr<CryptoHandshakeMessage> msg(config_.AddDefaultConfig( |
| QuicRandom::GetInstance(), &clock_, config_options_)); |
| |
| // Save the server config. |
| scid_hex_ = |
| "#" + QuicTextUtils::HexEncode(config_peer_.GetPrimaryConfig()->id); |
| |
| // Encode the QUIC version. |
| ver_hex_ = ParsedQuicVersionToString(GetParam().version); |
| |
| // Generate a public value. |
| char public_value[32]; |
| memset(public_value, 42, sizeof(public_value)); |
| pubs_hex_ = |
| "#" + QuicTextUtils::HexEncode(public_value, sizeof(public_value)); |
| |
| // Generate a client nonce. |
| std::string nonce; |
| CryptoUtils::GenerateNonce( |
| clock_.WallNow(), QuicRandom::GetInstance(), |
| QuicStringPiece( |
| reinterpret_cast<char*>(config_peer_.GetPrimaryConfig()->orbit), |
| kOrbitSize), |
| &nonce); |
| nonc_hex_ = "#" + QuicTextUtils::HexEncode(nonce); |
| |
| // Generate a source address token. |
| SourceAddressTokens previous_tokens; |
| QuicIpAddress ip = QuicIpAddress::Loopback4(); |
| MockRandom rand; |
| std::string stk = config_peer_.NewSourceAddressToken( |
| config_peer_.GetPrimaryConfig()->id, previous_tokens, ip, &rand, |
| clock_.WallNow(), nullptr); |
| stk_hex_ = "#" + QuicTextUtils::HexEncode(stk); |
| } |
| |
| protected: |
| class ProcessDoneCallback : public StatelessRejector::ProcessDoneCallback { |
| public: |
| explicit ProcessDoneCallback(StatelessRejectorTest* test) : test_(test) {} |
| void Run(std::unique_ptr<StatelessRejector> rejector) override { |
| test_->rejector_ = std::move(rejector); |
| } |
| |
| private: |
| StatelessRejectorTest* test_; |
| }; |
| |
| std::unique_ptr<ProofSource> proof_source_; |
| MockClock clock_; |
| QuicCryptoServerConfig config_; |
| QuicCryptoServerConfigPeer config_peer_; |
| QuicCompressedCertsCache compressed_certs_cache_; |
| QuicCryptoServerConfig::ConfigOptions config_options_; |
| std::unique_ptr<StatelessRejector> rejector_; |
| |
| // Values used in CHLO messages |
| std::string scid_hex_; |
| std::string nonc_hex_; |
| std::string pubs_hex_; |
| std::string ver_hex_; |
| std::string stk_hex_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(Flags, |
| StatelessRejectorTest, |
| ::testing::ValuesIn(GetTestParams()), |
| TestParamToString); |
| |
| TEST_P(StatelessRejectorTest, InvalidChlo) { |
| // clang-format off |
| const CryptoHandshakeMessage client_hello = crypto_test_utils::CreateCHLO( |
| {{"PDMD", "X509"}, |
| {"COPT", "SREJ"}}); |
| // clang-format on |
| rejector_->OnChlo(GetParam().version.transport_version, TestConnectionId(), |
| TestServerDesignatedConnectionId(), client_hello); |
| |
| if (GetParam().flags != ENABLED) { |
| EXPECT_EQ(StatelessRejector::UNSUPPORTED, rejector_->state()); |
| return; |
| } |
| |
| // The StatelessRejector is undecided - proceed with async processing |
| ASSERT_EQ(StatelessRejector::UNKNOWN, rejector_->state()); |
| StatelessRejector::Process(std::move(rejector_), |
| QuicMakeUnique<ProcessDoneCallback>(this)); |
| |
| EXPECT_EQ(StatelessRejector::FAILED, rejector_->state()); |
| EXPECT_EQ(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER, rejector_->error()); |
| } |
| |
| TEST_P(StatelessRejectorTest, ValidChloWithoutSrejSupport) { |
| // clang-format off |
| const CryptoHandshakeMessage client_hello = crypto_test_utils::CreateCHLO( |
| {{"PDMD", "X509"}, |
| {"AEAD", "AESG"}, |
| {"KEXS", "C255"}, |
| {"PUBS", pubs_hex_}, |
| {"NONC", nonc_hex_}, |
| {"VER\0", ver_hex_}}, |
| kClientHelloMinimumSize); |
| // clang-format on |
| |
| rejector_->OnChlo(GetParam().version.transport_version, TestConnectionId(), |
| TestServerDesignatedConnectionId(), client_hello); |
| EXPECT_EQ(StatelessRejector::UNSUPPORTED, rejector_->state()); |
| } |
| |
| TEST_P(StatelessRejectorTest, RejectChlo) { |
| // clang-format off |
| const CryptoHandshakeMessage client_hello = crypto_test_utils::CreateCHLO( |
| {{"PDMD", "X509"}, |
| {"AEAD", "AESG"}, |
| {"KEXS", "C255"}, |
| {"COPT", "SREJ"}, |
| {"SCID", scid_hex_}, |
| {"PUBS", pubs_hex_}, |
| {"NONC", nonc_hex_}, |
| {"#004b5453", stk_hex_}, |
| {"VER\0", ver_hex_}}, |
| kClientHelloMinimumSize); |
| // clang-format on |
| |
| rejector_->OnChlo(GetParam().version.transport_version, TestConnectionId(), |
| TestServerDesignatedConnectionId(), client_hello); |
| if (GetParam().flags != ENABLED) { |
| EXPECT_EQ(StatelessRejector::UNSUPPORTED, rejector_->state()); |
| return; |
| } |
| |
| // The StatelessRejector is undecided - proceed with async processing |
| ASSERT_EQ(StatelessRejector::UNKNOWN, rejector_->state()); |
| StatelessRejector::Process(std::move(rejector_), |
| QuicMakeUnique<ProcessDoneCallback>(this)); |
| |
| ASSERT_EQ(StatelessRejector::REJECTED, rejector_->state()); |
| const CryptoHandshakeMessage& reply = rejector_->reply(); |
| EXPECT_EQ(kSREJ, reply.tag()); |
| QuicTagVector reject_reasons; |
| EXPECT_EQ(QUIC_NO_ERROR, reply.GetTaglist(kRREJ, &reject_reasons)); |
| EXPECT_EQ(1u, reject_reasons.size()); |
| EXPECT_EQ(INVALID_EXPECTED_LEAF_CERTIFICATE, |
| static_cast<HandshakeFailureReason>(reject_reasons[0])); |
| } |
| |
| TEST_P(StatelessRejectorTest, AcceptChlo) { |
| const uint64_t xlct = crypto_test_utils::LeafCertHashForTesting(); |
| const std::string xlct_hex = |
| "#" + QuicTextUtils::HexEncode(reinterpret_cast<const char*>(&xlct), |
| sizeof(xlct)); |
| // clang-format off |
| const CryptoHandshakeMessage client_hello = crypto_test_utils::CreateCHLO( |
| {{"PDMD", "X509"}, |
| {"AEAD", "AESG"}, |
| {"KEXS", "C255"}, |
| {"COPT", "SREJ"}, |
| {"SCID", scid_hex_}, |
| {"PUBS", pubs_hex_}, |
| {"NONC", nonc_hex_}, |
| {"#004b5453", stk_hex_}, |
| {"VER\0", ver_hex_}, |
| {"XLCT", xlct_hex}}, |
| kClientHelloMinimumSize); |
| // clang-format on |
| |
| rejector_->OnChlo(GetParam().version.transport_version, TestConnectionId(), |
| TestServerDesignatedConnectionId(), client_hello); |
| if (GetParam().flags != ENABLED) { |
| EXPECT_EQ(StatelessRejector::UNSUPPORTED, rejector_->state()); |
| return; |
| } |
| |
| // The StatelessRejector is undecided - proceed with async processing |
| ASSERT_EQ(StatelessRejector::UNKNOWN, rejector_->state()); |
| StatelessRejector::Process(std::move(rejector_), |
| QuicMakeUnique<ProcessDoneCallback>(this)); |
| |
| EXPECT_EQ(StatelessRejector::ACCEPTED, rejector_->state()); |
| } |
| |
| } // namespace |
| } // namespace test |
| } // namespace quic |