| // Copyright 2013 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/quic/crypto/quic_crypto_client_config.h" |
| |
| #include "net/quic/crypto/proof_verifier.h" |
| #include "net/quic/quic_server_id.h" |
| #include "net/quic/test_tools/mock_random.h" |
| #include "net/quic/test_tools/quic_test_utils.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using std::string; |
| using std::vector; |
| |
| namespace net { |
| namespace test { |
| namespace { |
| |
| class TestProofVerifyDetails : public ProofVerifyDetails { |
| ~TestProofVerifyDetails() override {} |
| |
| // ProofVerifyDetails implementation |
| ProofVerifyDetails* Clone() const override { |
| return new TestProofVerifyDetails; |
| } |
| }; |
| |
| } // namespace |
| |
| TEST(QuicCryptoClientConfigTest, CachedState_IsEmpty) { |
| QuicCryptoClientConfig::CachedState state; |
| EXPECT_TRUE(state.IsEmpty()); |
| } |
| |
| TEST(QuicCryptoClientConfigTest, CachedState_IsComplete) { |
| QuicCryptoClientConfig::CachedState state; |
| EXPECT_FALSE(state.IsComplete(QuicWallTime::FromUNIXSeconds(0))); |
| } |
| |
| TEST(QuicCryptoClientConfigTest, CachedState_GenerationCounter) { |
| QuicCryptoClientConfig::CachedState state; |
| EXPECT_EQ(0u, state.generation_counter()); |
| state.SetProofInvalid(); |
| EXPECT_EQ(1u, state.generation_counter()); |
| } |
| |
| TEST(QuicCryptoClientConfigTest, CachedState_SetProofVerifyDetails) { |
| QuicCryptoClientConfig::CachedState state; |
| EXPECT_TRUE(state.proof_verify_details() == nullptr); |
| ProofVerifyDetails* details = new TestProofVerifyDetails; |
| state.SetProofVerifyDetails(details); |
| EXPECT_EQ(details, state.proof_verify_details()); |
| } |
| |
| TEST(QuicCryptoClientConfigTest, CachedState_ServerDesignatedConnectionId) { |
| QuicCryptoClientConfig::CachedState state; |
| EXPECT_FALSE(state.has_server_designated_connection_id()); |
| |
| QuicConnectionId connection_id = 1234; |
| state.add_server_designated_connection_id(connection_id); |
| EXPECT_TRUE(state.has_server_designated_connection_id()); |
| EXPECT_EQ(connection_id, state.GetNextServerDesignatedConnectionId()); |
| EXPECT_FALSE(state.has_server_designated_connection_id()); |
| |
| // Allow the ID to be set multiple times. It's unusual that this would |
| // happen, but not impossible. |
| ++connection_id; |
| state.add_server_designated_connection_id(connection_id); |
| EXPECT_TRUE(state.has_server_designated_connection_id()); |
| EXPECT_EQ(connection_id, state.GetNextServerDesignatedConnectionId()); |
| ++connection_id; |
| state.add_server_designated_connection_id(connection_id); |
| EXPECT_EQ(connection_id, state.GetNextServerDesignatedConnectionId()); |
| EXPECT_FALSE(state.has_server_designated_connection_id()); |
| |
| // Test FIFO behavior. |
| const QuicConnectionId first_cid = 0xdeadbeef; |
| const QuicConnectionId second_cid = 0xfeedbead; |
| state.add_server_designated_connection_id(first_cid); |
| state.add_server_designated_connection_id(second_cid); |
| EXPECT_TRUE(state.has_server_designated_connection_id()); |
| EXPECT_EQ(first_cid, state.GetNextServerDesignatedConnectionId()); |
| EXPECT_EQ(second_cid, state.GetNextServerDesignatedConnectionId()); |
| } |
| |
| TEST(QuicCryptoClientConfigTest, CachedState_ServerIdConsumedBeforeSet) { |
| QuicCryptoClientConfig::CachedState state; |
| EXPECT_FALSE(state.has_server_designated_connection_id()); |
| #if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) |
| EXPECT_DEBUG_DEATH(state.GetNextServerDesignatedConnectionId(), |
| "Attempting to consume a connection id " |
| "that was never designated."); |
| #endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) |
| } |
| |
| TEST(QuicCryptoClientConfigTest, CachedState_InitializeFrom) { |
| QuicCryptoClientConfig::CachedState state; |
| QuicCryptoClientConfig::CachedState other; |
| state.set_source_address_token("TOKEN"); |
| // TODO(rch): Populate other fields of |state|. |
| other.InitializeFrom(state); |
| EXPECT_EQ(state.server_config(), other.server_config()); |
| EXPECT_EQ(state.source_address_token(), other.source_address_token()); |
| EXPECT_EQ(state.certs(), other.certs()); |
| EXPECT_EQ(1u, other.generation_counter()); |
| EXPECT_FALSE(state.has_server_designated_connection_id()); |
| } |
| |
| TEST(QuicCryptoClientConfigTest, InchoateChlo) { |
| QuicCryptoClientConfig::CachedState state; |
| QuicCryptoClientConfig config; |
| QuicCryptoNegotiatedParameters params; |
| CryptoHandshakeMessage msg; |
| QuicServerId server_id("www.google.com", 80, false, PRIVACY_MODE_DISABLED); |
| config.FillInchoateClientHello(server_id, QuicVersionMax(), &state, |
| ¶ms, &msg); |
| |
| QuicTag cver; |
| EXPECT_EQ(QUIC_NO_ERROR, msg.GetUint32(kVER, &cver)); |
| EXPECT_EQ(QuicVersionToQuicTag(QuicVersionMax()), cver); |
| } |
| |
| TEST(QuicCryptoClientConfigTest, PreferAesGcm) { |
| QuicCryptoClientConfig config; |
| if (config.aead.size() > 1) |
| EXPECT_NE(kAESG, config.aead[0]); |
| config.PreferAesGcm(); |
| EXPECT_EQ(kAESG, config.aead[0]); |
| } |
| |
| TEST(QuicCryptoClientConfigTest, InchoateChloSecure) { |
| QuicCryptoClientConfig::CachedState state; |
| QuicCryptoClientConfig config; |
| QuicCryptoNegotiatedParameters params; |
| CryptoHandshakeMessage msg; |
| QuicServerId server_id("www.google.com", 443, true, PRIVACY_MODE_DISABLED); |
| config.FillInchoateClientHello(server_id, QuicVersionMax(), &state, |
| ¶ms, &msg); |
| |
| QuicTag pdmd; |
| EXPECT_EQ(QUIC_NO_ERROR, msg.GetUint32(kPDMD, &pdmd)); |
| EXPECT_EQ(kX509, pdmd); |
| } |
| |
| TEST(QuicCryptoClientConfigTest, InchoateChloSecureNoEcdsa) { |
| QuicCryptoClientConfig::CachedState state; |
| QuicCryptoClientConfig config; |
| config.DisableEcdsa(); |
| QuicCryptoNegotiatedParameters params; |
| CryptoHandshakeMessage msg; |
| QuicServerId server_id("www.google.com", 443, true, PRIVACY_MODE_DISABLED); |
| config.FillInchoateClientHello(server_id, QuicVersionMax(), &state, |
| ¶ms, &msg); |
| |
| QuicTag pdmd; |
| EXPECT_EQ(QUIC_NO_ERROR, msg.GetUint32(kPDMD, &pdmd)); |
| EXPECT_EQ(kX59R, pdmd); |
| } |
| |
| TEST(QuicCryptoClientConfigTest, FillClientHello) { |
| QuicCryptoClientConfig::CachedState state; |
| QuicCryptoClientConfig config; |
| QuicCryptoNegotiatedParameters params; |
| QuicConnectionId kConnectionId = 1234; |
| string error_details; |
| MockRandom rand; |
| CryptoHandshakeMessage chlo; |
| QuicServerId server_id("www.google.com", 80, false, PRIVACY_MODE_DISABLED); |
| config.FillClientHello(server_id, |
| kConnectionId, |
| QuicVersionMax(), |
| &state, |
| QuicWallTime::Zero(), |
| &rand, |
| nullptr, // channel_id_key |
| ¶ms, |
| &chlo, |
| &error_details); |
| |
| // Verify that certain QuicTags have been set correctly in the CHLO. |
| QuicTag cver; |
| EXPECT_EQ(QUIC_NO_ERROR, chlo.GetUint32(kVER, &cver)); |
| EXPECT_EQ(QuicVersionToQuicTag(QuicVersionMax()), cver); |
| } |
| |
| TEST(QuicCryptoClientConfigTest, ProcessServerDowngradeAttack) { |
| QuicVersionVector supported_versions = QuicSupportedVersions(); |
| if (supported_versions.size() == 1) { |
| // No downgrade attack is possible if the client only supports one version. |
| return; |
| } |
| QuicTagVector supported_version_tags; |
| for (size_t i = supported_versions.size(); i > 0; --i) { |
| supported_version_tags.push_back( |
| QuicVersionToQuicTag(supported_versions[i - 1])); |
| } |
| CryptoHandshakeMessage msg; |
| msg.set_tag(kSHLO); |
| msg.SetVector(kVER, supported_version_tags); |
| |
| QuicCryptoClientConfig::CachedState cached; |
| QuicCryptoNegotiatedParameters out_params; |
| string error; |
| QuicCryptoClientConfig config; |
| EXPECT_EQ(QUIC_VERSION_NEGOTIATION_MISMATCH, |
| config.ProcessServerHello(msg, 0, supported_versions, |
| &cached, &out_params, &error)); |
| EXPECT_EQ("Downgrade attack detected", error); |
| } |
| |
| TEST(QuicCryptoClientConfigTest, InitializeFrom) { |
| QuicCryptoClientConfig config; |
| QuicServerId canonical_server_id("www.google.com", 80, false, |
| PRIVACY_MODE_DISABLED); |
| QuicCryptoClientConfig::CachedState* state = |
| config.LookupOrCreate(canonical_server_id); |
| // TODO(rch): Populate other fields of |state|. |
| state->set_source_address_token("TOKEN"); |
| state->SetProofValid(); |
| |
| QuicServerId other_server_id("mail.google.com", 80, false, |
| PRIVACY_MODE_DISABLED); |
| config.InitializeFrom(other_server_id, canonical_server_id, &config); |
| QuicCryptoClientConfig::CachedState* other = |
| config.LookupOrCreate(other_server_id); |
| |
| EXPECT_EQ(state->server_config(), other->server_config()); |
| EXPECT_EQ(state->source_address_token(), other->source_address_token()); |
| EXPECT_EQ(state->certs(), other->certs()); |
| EXPECT_EQ(1u, other->generation_counter()); |
| } |
| |
| TEST(QuicCryptoClientConfigTest, Canonical) { |
| QuicCryptoClientConfig config; |
| config.AddCanonicalSuffix(".google.com"); |
| QuicServerId canonical_id1("www.google.com", 80, false, |
| PRIVACY_MODE_DISABLED); |
| QuicServerId canonical_id2("mail.google.com", 80, false, |
| PRIVACY_MODE_DISABLED); |
| QuicCryptoClientConfig::CachedState* state = |
| config.LookupOrCreate(canonical_id1); |
| // TODO(rch): Populate other fields of |state|. |
| state->set_source_address_token("TOKEN"); |
| state->SetProofValid(); |
| |
| QuicCryptoClientConfig::CachedState* other = |
| config.LookupOrCreate(canonical_id2); |
| |
| EXPECT_TRUE(state->IsEmpty()); |
| EXPECT_EQ(state->server_config(), other->server_config()); |
| EXPECT_EQ(state->source_address_token(), other->source_address_token()); |
| EXPECT_EQ(state->certs(), other->certs()); |
| EXPECT_EQ(1u, other->generation_counter()); |
| |
| QuicServerId different_id("mail.google.org", 80, false, |
| PRIVACY_MODE_DISABLED); |
| EXPECT_TRUE(config.LookupOrCreate(different_id)->IsEmpty()); |
| } |
| |
| TEST(QuicCryptoClientConfigTest, CanonicalNotUsedIfNotValid) { |
| QuicCryptoClientConfig config; |
| config.AddCanonicalSuffix(".google.com"); |
| QuicServerId canonical_id1("www.google.com", 80, false, |
| PRIVACY_MODE_DISABLED); |
| QuicServerId canonical_id2("mail.google.com", 80, false, |
| PRIVACY_MODE_DISABLED); |
| QuicCryptoClientConfig::CachedState* state = |
| config.LookupOrCreate(canonical_id1); |
| // TODO(rch): Populate other fields of |state|. |
| state->set_source_address_token("TOKEN"); |
| |
| // Do not set the proof as valid, and check that it is not used |
| // as a canonical entry. |
| EXPECT_TRUE(config.LookupOrCreate(canonical_id2)->IsEmpty()); |
| } |
| |
| TEST(QuicCryptoClientConfigTest, ClearCachedStates) { |
| QuicCryptoClientConfig config; |
| QuicServerId server_id("www.google.com", 80, false, PRIVACY_MODE_DISABLED); |
| QuicCryptoClientConfig::CachedState* state = config.LookupOrCreate(server_id); |
| // TODO(rch): Populate other fields of |state|. |
| vector<string> certs(1); |
| certs[0] = "Hello Cert"; |
| state->SetProof(certs, "signature"); |
| state->set_source_address_token("TOKEN"); |
| state->SetProofValid(); |
| EXPECT_EQ(1u, state->generation_counter()); |
| |
| // Verify LookupOrCreate returns the same data. |
| QuicCryptoClientConfig::CachedState* other = config.LookupOrCreate(server_id); |
| |
| EXPECT_EQ(state, other); |
| EXPECT_EQ(1u, other->generation_counter()); |
| |
| // Clear the cached states. |
| config.ClearCachedStates(); |
| |
| // Verify LookupOrCreate doesn't have any data. |
| QuicCryptoClientConfig::CachedState* cleared_cache = |
| config.LookupOrCreate(server_id); |
| |
| EXPECT_EQ(state, cleared_cache); |
| EXPECT_FALSE(cleared_cache->proof_valid()); |
| EXPECT_TRUE(cleared_cache->server_config().empty()); |
| EXPECT_TRUE(cleared_cache->certs().empty()); |
| EXPECT_TRUE(cleared_cache->signature().empty()); |
| EXPECT_EQ(2u, cleared_cache->generation_counter()); |
| } |
| |
| // Creates a minimal dummy reject message that will pass the client-config |
| // validation tests. |
| void FillInDummyReject(CryptoHandshakeMessage* rej, bool reject_is_stateless) { |
| if (reject_is_stateless) { |
| rej->set_tag(kSREJ); |
| } else { |
| 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"); |
| vector<QuicTag> reject_reasons; |
| reject_reasons.push_back(CLIENT_NONCE_INVALID_FAILURE); |
| rej->SetVector(kRREJ, reject_reasons); |
| } |
| |
| TEST(QuicCryptoClientConfigTest, ProcessReject) { |
| CryptoHandshakeMessage rej; |
| FillInDummyReject(&rej, /* stateless */ false); |
| |
| // Now process the rejection. |
| QuicCryptoClientConfig::CachedState cached; |
| QuicCryptoNegotiatedParameters out_params; |
| string error; |
| QuicCryptoClientConfig config; |
| EXPECT_EQ(QUIC_NO_ERROR, config.ProcessRejection( |
| rej, QuicWallTime::FromUNIXSeconds(0), &cached, |
| true, // is_https |
| &out_params, &error)); |
| EXPECT_FALSE(cached.has_server_designated_connection_id()); |
| } |
| |
| TEST(QuicCryptoClientConfigTest, ProcessStatelessReject) { |
| // Create a dummy reject message and mark it as stateless. |
| CryptoHandshakeMessage rej; |
| FillInDummyReject(&rej, /* stateless */ true); |
| const QuicConnectionId kConnectionId = 0xdeadbeef; |
| rej.SetValue(kRCID, kConnectionId); |
| |
| // Now process the rejection. |
| QuicCryptoClientConfig::CachedState cached; |
| QuicCryptoNegotiatedParameters out_params; |
| string error; |
| QuicCryptoClientConfig config; |
| EXPECT_EQ(QUIC_NO_ERROR, config.ProcessRejection( |
| rej, QuicWallTime::FromUNIXSeconds(0), &cached, |
| true, // is_https |
| &out_params, &error)); |
| EXPECT_TRUE(cached.has_server_designated_connection_id()); |
| EXPECT_EQ(kConnectionId, cached.GetNextServerDesignatedConnectionId()); |
| } |
| |
| TEST(QuicCryptoClientConfigTest, BadlyFormattedStatelessReject) { |
| // Create a dummy reject message and mark it as stateless. Do not |
| // add an server-designated connection-id. |
| CryptoHandshakeMessage rej; |
| FillInDummyReject(&rej, /* stateless */ true); |
| |
| // Now process the rejection. |
| QuicCryptoClientConfig::CachedState cached; |
| QuicCryptoNegotiatedParameters out_params; |
| string error; |
| QuicCryptoClientConfig config; |
| EXPECT_EQ( |
| QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND, |
| config.ProcessRejection(rej, QuicWallTime::FromUNIXSeconds(0), &cached, |
| true, // is_https |
| &out_params, &error)); |
| EXPECT_FALSE(cached.has_server_designated_connection_id()); |
| EXPECT_EQ("Missing kRCID", error); |
| } |
| |
| } // namespace test |
| } // namespace net |