| // 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 "components/certificate_transparency/log_dns_client.h" |
| |
| #include <memory> |
| #include <numeric> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/format_macros.h" |
| #include "base/run_loop.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/test/test_timeouts.h" |
| #include "components/certificate_transparency/mock_log_dns_traffic.h" |
| #include "crypto/sha2.h" |
| #include "net/base/net_errors.h" |
| #include "net/cert/merkle_audit_proof.h" |
| #include "net/cert/signed_certificate_timestamp.h" |
| #include "net/dns/dns_client.h" |
| #include "net/dns/dns_config.h" |
| #include "net/dns/dns_test_util.h" |
| #include "net/dns/public/dns_protocol.h" |
| #include "net/log/net_log.h" |
| #include "net/test/gtest_util.h" |
| #include "net/url_request/url_request_filter.h" |
| #include "net/url_request/url_request_test_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace certificate_transparency { |
| namespace { |
| |
| using net::test::IsError; |
| using net::test::IsOk; |
| using ::testing::AllOf; |
| using ::testing::Eq; |
| using ::testing::IsEmpty; |
| using ::testing::Le; |
| using ::testing::Not; |
| using ::testing::NotNull; |
| |
| // Histogram names. |
| const char kLeafIndexErrorHistogram[] = |
| "Net.CertificateTransparency.DnsQueryLeafIndexError"; |
| const char kLeafIndexRcodeHistogram[] = |
| "Net.CertificateTransparency.DnsQueryLeafIndexRcode"; |
| const char kAuditProofErrorHistogram[] = |
| "Net.CertificateTransparency.DnsQueryAuditProofError"; |
| const char kAuditProofRcodeHistogram[] = |
| "Net.CertificateTransparency.DnsQueryAuditProofRcode"; |
| |
| // Sample Merkle leaf hashes. |
| const char* const kLeafHashes[] = { |
| "\x1f\x25\xe1\xca\xba\x4f\xf9\xb8\x27\x24\x83\x0f\xca\x60\xe4\xc2\xbe\xa8" |
| "\xc3\xa9\x44\x1c\x27\xb0\xb4\x3e\x6a\x96\x94\xc7\xb8\x04", |
| "\x2c\x26\xb4\x6b\x68\xff\xc6\x8f\xf9\x9b\x45\x3c\x1d\x30\x41\x34\x13\x42" |
| "\x2d\x70\x64\x83\xbf\xa0\xf9\x8a\x5e\x88\x62\x66\xe7\xae", |
| "\xfc\xde\x2b\x2e\xdb\xa5\x6b\xf4\x08\x60\x1f\xb7\x21\xfe\x9b\x5c\x33\x8d" |
| "\x10\xee\x42\x9e\xa0\x4f\xae\x55\x11\xb6\x8f\xbf\x8f\xb9", |
| }; |
| |
| // DNS query names for looking up the leaf index associated with each hash in |
| // |kLeafHashes|. Assumes the log domain is "ct.test". |
| const char* const kLeafIndexQnames[] = { |
| "D4S6DSV2J743QJZEQMH4UYHEYK7KRQ5JIQOCPMFUHZVJNFGHXACA.hash.ct.test.", |
| "FQTLI23I77DI76M3IU6B2MCBGQJUELLQMSB37IHZRJPIQYTG46XA.hash.ct.test.", |
| "7TPCWLW3UVV7ICDAD63SD7U3LQZY2EHOIKPKAT5OKUI3ND57R64Q.hash.ct.test.", |
| }; |
| |
| // Leaf indices and tree sizes for use with |kLeafHashes|. |
| const uint64_t kLeafIndices[] = {0, 1, 2}; |
| const uint64_t kTreeSizes[] = {100, 10000, 1000000}; |
| |
| // Only 7 audit proof nodes can fit into a DNS response, because they are sent |
| // in a TXT RDATA string, which has a maximum size of 255 bytes, and each node |
| // is a SHA-256 hash (32 bytes), i.e. (255 / 32) == 7. |
| // This means audit proofs consisting of more than 7 nodes require multiple DNS |
| // requests to retrieve. |
| const size_t kMaxProofNodesPerDnsResponse = 7; |
| |
| // Returns an example Merkle audit proof containing |length| nodes. |
| // The proof cannot be used for cryptographic purposes; it is merely a |
| // placeholder. |
| std::vector<std::string> GetSampleAuditProof(size_t length) { |
| std::vector<std::string> audit_proof(length); |
| // Makes each node of the audit proof different, so that tests are able to |
| // confirm that the audit proof is reconstructed in the correct order. |
| for (size_t i = 0; i < length; ++i) { |
| std::string node(crypto::kSHA256Length, '\0'); |
| // Each node is 32 bytes, with each byte having a different value. |
| for (size_t j = 0; j < crypto::kSHA256Length; ++j) { |
| node[j] = static_cast<char>((-127 + i + j) % 128); |
| } |
| audit_proof[i].assign(std::move(node)); |
| } |
| |
| return audit_proof; |
| } |
| |
| } // namespace |
| |
| class LogDnsClientTest : public ::testing::TestWithParam<net::IoMode> { |
| void SetUp() override { |
| net::URLRequestFilter* filter = net::URLRequestFilter::GetInstance(); |
| filter->AddHostnameInterceptor( |
| "https", "mock.http", |
| std::make_unique<MockLogDnsTraffic::DohJobInterceptor>()); |
| } |
| |
| void TearDown() override { |
| net::URLRequestFilter* filter = net::URLRequestFilter::GetInstance(); |
| filter->ClearHandlers(); |
| } |
| |
| protected: |
| LogDnsClientTest() |
| : network_change_notifier_(net::NetworkChangeNotifier::CreateMock()) { |
| mock_dns_.SetSocketReadMode(GetParam()); |
| mock_dns_.InitializeDnsConfig(); |
| } |
| |
| std::unique_ptr<LogDnsClient> CreateLogDnsClient( |
| size_t max_concurrent_queries) { |
| return std::make_unique<LogDnsClient>( |
| mock_dns_.CreateDnsClient(), new net::TestURLRequestContext(), |
| net::NetLogWithSource(), max_concurrent_queries); |
| } |
| |
| std::unique_ptr<LogDnsClient> CreateRuleBasedLogDnsClient( |
| net::MockDnsClientRuleList rules) { |
| return std::make_unique<LogDnsClient>( |
| std::make_unique<net::MockDnsClient>(net::DnsConfig(), |
| std::move(rules)), |
| new net::TestURLRequestContext(), net::NetLogWithSource(), 0); |
| } |
| |
| // Convenience function for calling QueryAuditProof synchronously. |
| template <typename... Types> |
| net::Error QueryAuditProof(Types&&... args) { |
| std::unique_ptr<LogDnsClient> log_client = CreateLogDnsClient(0); |
| net::TestCompletionCallback callback; |
| const net::Error result = log_client->QueryAuditProof( |
| std::forward<Types>(args)..., callback.callback()); |
| |
| return result != net::ERR_IO_PENDING |
| ? result |
| : static_cast<net::Error>(callback.WaitForResult()); |
| } |
| |
| // This will be the NetworkChangeNotifier singleton for the duration of the |
| // test. It is accessed statically by LogDnsClient. |
| std::unique_ptr<net::NetworkChangeNotifier> network_change_notifier_; |
| // Queues and handles asynchronous DNS tasks. Indirectly used by LogDnsClient, |
| // the underlying net::DnsClient, and NetworkChangeNotifier. |
| base::test::ScopedTaskEnvironment task_environment_{ |
| base::test::ScopedTaskEnvironment::MainThreadType::IO}; |
| // Allows mock DNS sockets to be setup. |
| MockLogDnsTraffic mock_dns_; |
| // Tests that histograms are populated as expected. |
| base::HistogramTester histograms_; |
| }; |
| |
| TEST_P(LogDnsClientTest, QueryAuditProofReportsThatLogDomainDoesNotExist) { |
| ASSERT_TRUE(mock_dns_.ExpectRequestAndErrorResponse( |
| kLeafIndexQnames[0], net::dns_protocol::kRcodeNXDOMAIN)); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT( |
| QueryAuditProof("ct.test", kLeafHashes[0], false /* lookup_securely */, |
| kTreeSizes[0], &query), |
| IsError(net::ERR_NAME_NOT_RESOLVED)); |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, |
| -net::ERR_NAME_NOT_RESOLVED, 1); |
| histograms_.ExpectUniqueSample(kLeafIndexRcodeHistogram, |
| net::dns_protocol::kRcodeNXDOMAIN, 1); |
| histograms_.ExpectTotalCount(kAuditProofErrorHistogram, 0); |
| histograms_.ExpectTotalCount(kAuditProofRcodeHistogram, 0); |
| } |
| |
| TEST_P(LogDnsClientTest, |
| QueryAuditProofReportsServerFailuresDuringLeafIndexRequests) { |
| ASSERT_TRUE(mock_dns_.ExpectRequestAndErrorResponse( |
| kLeafIndexQnames[0], net::dns_protocol::kRcodeSERVFAIL)); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT( |
| QueryAuditProof("ct.test", kLeafHashes[0], false /* lookup_securely */, |
| kTreeSizes[0], &query), |
| IsError(net::ERR_DNS_SERVER_FAILED)); |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, |
| -net::ERR_DNS_SERVER_FAILED, 1); |
| histograms_.ExpectUniqueSample(kLeafIndexRcodeHistogram, |
| net::dns_protocol::kRcodeSERVFAIL, 1); |
| histograms_.ExpectTotalCount(kAuditProofErrorHistogram, 0); |
| histograms_.ExpectTotalCount(kAuditProofRcodeHistogram, 0); |
| } |
| |
| TEST_P(LogDnsClientTest, |
| QueryAuditProofReportsServerRefusalsDuringLeafIndexRequests) { |
| ASSERT_TRUE(mock_dns_.ExpectRequestAndErrorResponse( |
| kLeafIndexQnames[0], net::dns_protocol::kRcodeREFUSED)); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT( |
| QueryAuditProof("ct.test", kLeafHashes[0], false /* lookup_securely */, |
| kTreeSizes[0], &query), |
| IsError(net::ERR_DNS_SERVER_FAILED)); |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, |
| -net::ERR_DNS_SERVER_FAILED, 1); |
| histograms_.ExpectUniqueSample(kLeafIndexRcodeHistogram, |
| net::dns_protocol::kRcodeREFUSED, 1); |
| histograms_.ExpectTotalCount(kAuditProofErrorHistogram, 0); |
| histograms_.ExpectTotalCount(kAuditProofRcodeHistogram, 0); |
| } |
| |
| TEST_P(LogDnsClientTest, QueryAuditProofReportsMalformedLeafIndexResponse) { |
| const struct { |
| std::string name; |
| std::vector<base::StringPiece> txt_strings; |
| } tests[] = {{"contains no strings", {}}, |
| {"contains more than one string", {"123456", "7"}}, |
| {"is not numeric", {"foo"}}, |
| {"is floating point", {"123456.0"}}, |
| {"is empty"}, |
| {"has non-numeric prefix", {"foo123456"}}, |
| {"has non-numeric suffix", {"123456foo"}}}; |
| |
| for (auto test : tests) { |
| SCOPED_TRACE(test.name); |
| base::HistogramTester histograms; |
| |
| ASSERT_TRUE(mock_dns_.ExpectRequestAndResponse(kLeafIndexQnames[0], |
| test.txt_strings)); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT( |
| QueryAuditProof("ct.test", kLeafHashes[0], false /* lookup_securely */, |
| kTreeSizes[0], &query), |
| IsError(net::ERR_DNS_MALFORMED_RESPONSE)); |
| histograms.ExpectUniqueSample(kLeafIndexErrorHistogram, |
| -net::ERR_DNS_MALFORMED_RESPONSE, 1); |
| histograms.ExpectUniqueSample(kLeafIndexRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| histograms_.ExpectTotalCount(kAuditProofErrorHistogram, 0); |
| histograms_.ExpectTotalCount(kAuditProofRcodeHistogram, 0); |
| } |
| } |
| |
| TEST_P(LogDnsClientTest, QueryAuditProofReportsInvalidArgIfLogDomainIsEmpty) { |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT(QueryAuditProof("", kLeafHashes[0], false /* lookup_securely */, |
| kTreeSizes[0], &query), |
| IsError(net::ERR_INVALID_ARGUMENT)); |
| } |
| |
| TEST_P(LogDnsClientTest, QueryAuditProofReportsInvalidArgIfLeafHashIsInvalid) { |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT(QueryAuditProof("ct.test", "foo", false /* lookup_securely */, |
| kTreeSizes[0], &query), |
| IsError(net::ERR_INVALID_ARGUMENT)); |
| } |
| |
| TEST_P(LogDnsClientTest, QueryAuditProofReportsInvalidArgIfLeafHashIsEmpty) { |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT(QueryAuditProof("ct.test", "", false /* lookup_securely */, |
| kTreeSizes[0], &query), |
| IsError(net::ERR_INVALID_ARGUMENT)); |
| } |
| |
| TEST_P(LogDnsClientTest, |
| QueryAuditProofReportsSocketErrorsDuringLeafIndexRequests) { |
| ASSERT_TRUE(mock_dns_.ExpectRequestAndSocketError( |
| kLeafIndexQnames[0], net::ERR_CONNECTION_REFUSED)); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT( |
| QueryAuditProof("ct.test", kLeafHashes[0], false /* lookup_securely */, |
| kTreeSizes[0], &query), |
| IsError(net::ERR_CONNECTION_REFUSED)); |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, |
| -net::ERR_CONNECTION_REFUSED, 1); |
| histograms_.ExpectTotalCount(kLeafIndexRcodeHistogram, 0); |
| histograms_.ExpectTotalCount(kAuditProofErrorHistogram, 0); |
| histograms_.ExpectTotalCount(kAuditProofRcodeHistogram, 0); |
| } |
| |
| TEST_P(LogDnsClientTest, |
| QueryAuditProofReportsTimeoutsDuringLeafIndexRequests) { |
| ASSERT_TRUE(mock_dns_.ExpectRequestAndTimeout(kLeafIndexQnames[0])); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT( |
| QueryAuditProof("ct.test", kLeafHashes[0], false /* lookup_securely */, |
| kTreeSizes[0], &query), |
| IsError(net::ERR_DNS_TIMED_OUT)); |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, |
| -net::ERR_DNS_TIMED_OUT, 1); |
| histograms_.ExpectTotalCount(kLeafIndexRcodeHistogram, 0); |
| histograms_.ExpectTotalCount(kAuditProofErrorHistogram, 0); |
| histograms_.ExpectTotalCount(kAuditProofRcodeHistogram, 0); |
| } |
| |
| TEST_P(LogDnsClientTest, QueryAuditProof) { |
| const std::vector<std::string> audit_proof = GetSampleAuditProof(20); |
| |
| // Expect a leaf index query first, to map the leaf hash to a leaf index. |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456)); |
| |
| // It takes a number of DNS requests to retrieve the entire |audit_proof| |
| // (see |kMaxProofNodesPerDnsResponse|). |
| for (size_t nodes_begin = 0; nodes_begin < audit_proof.size(); |
| nodes_begin += kMaxProofNodesPerDnsResponse) { |
| const size_t nodes_end = std::min( |
| nodes_begin + kMaxProofNodesPerDnsResponse, audit_proof.size()); |
| |
| ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse( |
| base::StringPrintf("%zu.123456.999999.tree.ct.test.", nodes_begin), |
| audit_proof.begin() + nodes_begin, audit_proof.begin() + nodes_end)); |
| } |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 999999, &query), |
| IsOk()); |
| const net::ct::MerkleAuditProof& proof = query->GetProof(); |
| EXPECT_THAT(proof.leaf_index, Eq(123456u)); |
| EXPECT_THAT(proof.tree_size, Eq(999999u)); |
| EXPECT_THAT(proof.nodes, Eq(audit_proof)); |
| |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, -net::OK, 1); |
| histograms_.ExpectUniqueSample(kLeafIndexRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| histograms_.ExpectUniqueSample(kAuditProofErrorHistogram, -net::OK, 1); |
| histograms_.ExpectUniqueSample(kAuditProofRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| } |
| |
| TEST_P(LogDnsClientTest, QueryAuditProofHandlesResponsesWithShortAuditPaths) { |
| const std::vector<std::string> audit_proof = GetSampleAuditProof(20); |
| |
| // Expect a leaf index query first, to map the leaf hash to a leaf index. |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456)); |
| |
| // Make some of the responses contain fewer proof nodes than they can hold. |
| ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse( |
| "0.123456.999999.tree.ct.test.", audit_proof.begin(), |
| audit_proof.begin() + 1)); |
| ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse( |
| "1.123456.999999.tree.ct.test.", audit_proof.begin() + 1, |
| audit_proof.begin() + 3)); |
| ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse( |
| "3.123456.999999.tree.ct.test.", audit_proof.begin() + 3, |
| audit_proof.begin() + 6)); |
| ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse( |
| "6.123456.999999.tree.ct.test.", audit_proof.begin() + 6, |
| audit_proof.begin() + 10)); |
| ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse( |
| "10.123456.999999.tree.ct.test.", audit_proof.begin() + 10, |
| audit_proof.begin() + 13)); |
| ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse( |
| "13.123456.999999.tree.ct.test.", audit_proof.begin() + 13, |
| audit_proof.end())); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 999999, &query), |
| IsOk()); |
| const net::ct::MerkleAuditProof& proof = query->GetProof(); |
| EXPECT_THAT(proof.leaf_index, Eq(123456u)); |
| EXPECT_THAT(proof.tree_size, Eq(999999u)); |
| EXPECT_THAT(proof.nodes, Eq(audit_proof)); |
| |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, -net::OK, 1); |
| histograms_.ExpectUniqueSample(kLeafIndexRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| histograms_.ExpectUniqueSample(kAuditProofErrorHistogram, -net::OK, 1); |
| histograms_.ExpectUniqueSample(kAuditProofRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| } |
| |
| TEST_P(LogDnsClientTest, |
| QueryAuditProofReportsThatAuditProofQnameDoesNotExist) { |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456)); |
| ASSERT_TRUE(mock_dns_.ExpectRequestAndErrorResponse( |
| "0.123456.999999.tree.ct.test.", net::dns_protocol::kRcodeNXDOMAIN)); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 999999, &query), |
| IsError(net::ERR_NAME_NOT_RESOLVED)); |
| |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, -net::OK, 1); |
| histograms_.ExpectUniqueSample(kLeafIndexRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| histograms_.ExpectUniqueSample(kAuditProofErrorHistogram, |
| -net::ERR_NAME_NOT_RESOLVED, 1); |
| histograms_.ExpectUniqueSample(kAuditProofRcodeHistogram, |
| net::dns_protocol::kRcodeNXDOMAIN, 1); |
| } |
| |
| TEST_P(LogDnsClientTest, |
| QueryAuditProofReportsServerFailuresDuringAuditProofRequests) { |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456)); |
| ASSERT_TRUE(mock_dns_.ExpectRequestAndErrorResponse( |
| "0.123456.999999.tree.ct.test.", net::dns_protocol::kRcodeSERVFAIL)); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 999999, &query), |
| IsError(net::ERR_DNS_SERVER_FAILED)); |
| |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, -net::OK, 1); |
| histograms_.ExpectUniqueSample(kLeafIndexRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| histograms_.ExpectUniqueSample(kAuditProofErrorHistogram, |
| -net::ERR_DNS_SERVER_FAILED, 1); |
| histograms_.ExpectUniqueSample(kAuditProofRcodeHistogram, |
| net::dns_protocol::kRcodeSERVFAIL, 1); |
| } |
| |
| TEST_P(LogDnsClientTest, |
| QueryAuditProofReportsServerRefusalsDuringAuditProofRequests) { |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456)); |
| ASSERT_TRUE(mock_dns_.ExpectRequestAndErrorResponse( |
| "0.123456.999999.tree.ct.test.", net::dns_protocol::kRcodeREFUSED)); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 999999, &query), |
| IsError(net::ERR_DNS_SERVER_FAILED)); |
| |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, -net::OK, 1); |
| histograms_.ExpectUniqueSample(kLeafIndexRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| histograms_.ExpectUniqueSample(kAuditProofErrorHistogram, |
| -net::ERR_DNS_SERVER_FAILED, 1); |
| histograms_.ExpectUniqueSample(kAuditProofRcodeHistogram, |
| net::dns_protocol::kRcodeREFUSED, 1); |
| } |
| |
| TEST_P( |
| LogDnsClientTest, |
| QueryAuditProofReportsResponseMalformedIfProofNodesResponseContainsNoStrings) { |
| // Expect a leaf index query first, to map the leaf hash to a leaf index. |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456)); |
| |
| ASSERT_TRUE(mock_dns_.ExpectRequestAndResponse( |
| "0.123456.999999.tree.ct.test.", std::vector<base::StringPiece>())); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 999999, &query), |
| IsError(net::ERR_DNS_MALFORMED_RESPONSE)); |
| |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, -net::OK, 1); |
| histograms_.ExpectUniqueSample(kLeafIndexRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| histograms_.ExpectUniqueSample(kAuditProofErrorHistogram, |
| -net::ERR_DNS_MALFORMED_RESPONSE, 1); |
| histograms_.ExpectUniqueSample(kAuditProofRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| } |
| |
| TEST_P( |
| LogDnsClientTest, |
| QueryAuditProofReportsResponseMalformedIfProofNodesResponseContainsMoreThanOneString) { |
| // The CT-over-DNS draft RFC states that the response will contain "exactly |
| // one character-string." |
| const std::vector<std::string> audit_proof = GetSampleAuditProof(10); |
| |
| std::string first_chunk_of_proof = std::accumulate( |
| audit_proof.begin(), audit_proof.begin() + 7, std::string()); |
| std::string second_chunk_of_proof = std::accumulate( |
| audit_proof.begin() + 7, audit_proof.end(), std::string()); |
| |
| // Expect a leaf index query first, to map the leaf hash to a leaf index. |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456)); |
| |
| ASSERT_TRUE(mock_dns_.ExpectRequestAndResponse( |
| "0.123456.999999.tree.ct.test.", |
| {first_chunk_of_proof, second_chunk_of_proof})); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 999999, &query), |
| IsError(net::ERR_DNS_MALFORMED_RESPONSE)); |
| |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, -net::OK, 1); |
| histograms_.ExpectUniqueSample(kLeafIndexRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| histograms_.ExpectUniqueSample(kAuditProofErrorHistogram, |
| -net::ERR_DNS_MALFORMED_RESPONSE, 1); |
| histograms_.ExpectUniqueSample(kAuditProofRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| } |
| |
| TEST_P(LogDnsClientTest, |
| QueryAuditProofReportsResponseMalformedIfNodeTooShort) { |
| // node is shorter than a SHA-256 hash (31 vs 32 bytes) |
| const std::vector<std::string> audit_proof(1, std::string(31, 'a')); |
| |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456)); |
| ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse( |
| "0.123456.999999.tree.ct.test.", audit_proof.begin(), audit_proof.end())); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 999999, &query), |
| IsError(net::ERR_DNS_MALFORMED_RESPONSE)); |
| |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, -net::OK, 1); |
| histograms_.ExpectUniqueSample(kLeafIndexRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| histograms_.ExpectUniqueSample(kAuditProofErrorHistogram, |
| -net::ERR_DNS_MALFORMED_RESPONSE, 1); |
| histograms_.ExpectUniqueSample(kAuditProofRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| } |
| |
| TEST_P(LogDnsClientTest, QueryAuditProofReportsResponseMalformedIfNodeTooLong) { |
| // node is longer than a SHA-256 hash (33 vs 32 bytes) |
| const std::vector<std::string> audit_proof(1, std::string(33, 'a')); |
| |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456)); |
| ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse( |
| "0.123456.999999.tree.ct.test.", audit_proof.begin(), audit_proof.end())); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 999999, &query), |
| IsError(net::ERR_DNS_MALFORMED_RESPONSE)); |
| |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, -net::OK, 1); |
| histograms_.ExpectUniqueSample(kLeafIndexRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| histograms_.ExpectUniqueSample(kAuditProofErrorHistogram, |
| -net::ERR_DNS_MALFORMED_RESPONSE, 1); |
| histograms_.ExpectUniqueSample(kAuditProofRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| } |
| |
| TEST_P(LogDnsClientTest, QueryAuditProofReportsResponseMalformedIfEmpty) { |
| const std::vector<std::string> audit_proof; |
| |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456)); |
| ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse( |
| "0.123456.999999.tree.ct.test.", audit_proof.begin(), audit_proof.end())); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 999999, &query), |
| IsError(net::ERR_DNS_MALFORMED_RESPONSE)); |
| |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, -net::OK, 1); |
| histograms_.ExpectUniqueSample(kLeafIndexRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| histograms_.ExpectUniqueSample(kAuditProofErrorHistogram, |
| -net::ERR_DNS_MALFORMED_RESPONSE, 1); |
| histograms_.ExpectUniqueSample(kAuditProofRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| } |
| |
| TEST_P(LogDnsClientTest, |
| QueryAuditProofReportsInvalidArgIfLeafIndexEqualToTreeSize) { |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456)); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 123456, &query), |
| IsError(net::ERR_INVALID_ARGUMENT)); |
| } |
| |
| TEST_P(LogDnsClientTest, |
| QueryAuditProofReportsInvalidArgIfLeafIndexGreaterThanTreeSize) { |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 999999)); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 123456, &query), |
| IsError(net::ERR_INVALID_ARGUMENT)); |
| } |
| |
| TEST_P(LogDnsClientTest, |
| QueryAuditProofReportsSocketErrorsDuringAuditProofRequests) { |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456)); |
| ASSERT_TRUE(mock_dns_.ExpectRequestAndSocketError( |
| "0.123456.999999.tree.ct.test.", net::ERR_CONNECTION_REFUSED)); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 999999, &query), |
| IsError(net::ERR_CONNECTION_REFUSED)); |
| |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, -net::OK, 1); |
| histograms_.ExpectUniqueSample(kLeafIndexRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| histograms_.ExpectUniqueSample(kAuditProofErrorHistogram, |
| -net::ERR_CONNECTION_REFUSED, 1); |
| histograms_.ExpectTotalCount(kAuditProofRcodeHistogram, 0); |
| } |
| |
| TEST_P(LogDnsClientTest, |
| QueryAuditProofReportsTimeoutsDuringAuditProofRequests) { |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456)); |
| ASSERT_TRUE( |
| mock_dns_.ExpectRequestAndTimeout("0.123456.999999.tree.ct.test.")); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 999999, &query), |
| IsError(net::ERR_DNS_TIMED_OUT)); |
| |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, -net::OK, 1); |
| histograms_.ExpectUniqueSample(kLeafIndexRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 1); |
| histograms_.ExpectUniqueSample(kAuditProofErrorHistogram, |
| -net::ERR_DNS_TIMED_OUT, 1); |
| histograms_.ExpectTotalCount(kAuditProofRcodeHistogram, 0); |
| } |
| |
| TEST_P(LogDnsClientTest, AdoptsLatestDnsConfigIfValid) { |
| std::unique_ptr<net::DnsClient> tmp = mock_dns_.CreateDnsClient(); |
| net::DnsClient* dns_client = tmp.get(); |
| LogDnsClient log_client(std::move(tmp), new net::TestURLRequestContext(), |
| net::NetLogWithSource(), 0); |
| |
| // Get the current DNS config, modify it and broadcast the update. |
| net::DnsConfig config(*dns_client->GetConfig()); |
| ASSERT_NE(123, config.attempts); |
| config.attempts = 123; |
| mock_dns_.SetDnsConfig(config); |
| |
| // Let the DNS config change propogate. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(123, dns_client->GetConfig()->attempts); |
| } |
| |
| TEST_P(LogDnsClientTest, IgnoresLatestDnsConfigIfInvalid) { |
| std::unique_ptr<net::DnsClient> tmp = mock_dns_.CreateDnsClient(); |
| net::DnsClient* dns_client = tmp.get(); |
| LogDnsClient log_client(std::move(tmp), new net::TestURLRequestContext(), |
| net::NetLogWithSource(), 0); |
| |
| // Get the current DNS config, modify it and broadcast the update. |
| net::DnsConfig config(*dns_client->GetConfig()); |
| ASSERT_THAT(config.nameservers, Not(IsEmpty())); |
| config.nameservers.clear(); // Makes config invalid |
| mock_dns_.SetDnsConfig(config); |
| |
| // Let the DNS config change propogate. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_THAT(dns_client->GetConfig()->nameservers, Not(IsEmpty())); |
| } |
| |
| // Test that changes to the DNS config after starting a query are adopted and |
| // that the query is not disrupted. |
| TEST_P(LogDnsClientTest, AdoptsLatestDnsConfigMidQuery) { |
| const std::vector<std::string> audit_proof = GetSampleAuditProof(20); |
| |
| // Expect a leaf index query first, to map the leaf hash to a leaf index. |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456)); |
| |
| // It takes a number of DNS requests to retrieve the entire |audit_proof| |
| // (see |kMaxProofNodesPerDnsResponse|). |
| for (size_t nodes_begin = 0; nodes_begin < audit_proof.size(); |
| nodes_begin += kMaxProofNodesPerDnsResponse) { |
| const size_t nodes_end = std::min( |
| nodes_begin + kMaxProofNodesPerDnsResponse, audit_proof.size()); |
| |
| ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse( |
| base::StringPrintf("%zu.123456.999999.tree.ct.test.", nodes_begin), |
| audit_proof.begin() + nodes_begin, audit_proof.begin() + nodes_end)); |
| } |
| |
| std::unique_ptr<net::DnsClient> tmp = mock_dns_.CreateDnsClient(); |
| net::DnsClient* dns_client = tmp.get(); |
| LogDnsClient log_client(std::move(tmp), new net::TestURLRequestContext(), |
| net::NetLogWithSource(), 0); |
| |
| // Start query. |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| net::TestCompletionCallback callback; |
| ASSERT_THAT(log_client.QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 999999, |
| &query, callback.callback()), |
| IsError(net::ERR_IO_PENDING)); |
| |
| // Get the current DNS config, modify it and publish the update. |
| // The new config is distributed asynchronously via NetworkChangeNotifier. |
| net::DnsConfig config(*dns_client->GetConfig()); |
| ASSERT_NE(123, config.attempts); |
| config.attempts = 123; |
| mock_dns_.SetDnsConfig(config); |
| // The new config is distributed asynchronously via NetworkChangeNotifier. |
| // Config change shouldn't have taken effect yet. |
| ASSERT_NE(123, dns_client->GetConfig()->attempts); |
| |
| // Wait for the query to complete, then check that it was successful. |
| // The DNS config should be updated during this time. |
| ASSERT_THAT(callback.WaitForResult(), IsOk()); |
| const net::ct::MerkleAuditProof& proof = query->GetProof(); |
| EXPECT_THAT(proof.leaf_index, Eq(123456u)); |
| EXPECT_THAT(proof.tree_size, Eq(999999u)); |
| EXPECT_THAT(proof.nodes, Eq(audit_proof)); |
| |
| // Check that the DNS config change was adopted. |
| ASSERT_EQ(123, dns_client->GetConfig()->attempts); |
| } |
| |
| TEST_P(LogDnsClientTest, CanPerformQueriesInParallel) { |
| // Check that 3 queries can be performed in parallel. |
| constexpr size_t kNumOfParallelQueries = 3; |
| ASSERT_THAT(kNumOfParallelQueries, |
| AllOf(Le(base::size(kLeafIndexQnames)), |
| Le(base::size(kLeafIndices)), Le(base::size(kTreeSizes)))) |
| << "Not enough test data for this many parallel queries"; |
| |
| std::unique_ptr<LogDnsClient> log_client = |
| CreateLogDnsClient(kNumOfParallelQueries); |
| net::TestCompletionCallback callbacks[kNumOfParallelQueries]; |
| |
| // Expect multiple leaf index requests. |
| for (size_t i = 0; i < kNumOfParallelQueries; ++i) { |
| ASSERT_TRUE(mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[i], |
| kLeafIndices[i])); |
| } |
| |
| // Make each query require one more audit proof request than the last, by |
| // increasing the number of nodes in the audit proof by |
| // kMaxProofNodesPerDnsResponse for each query. This helps to test that |
| // parallel queries do not intefere with each other, e.g. one query causing |
| // another to end prematurely. |
| std::vector<std::string> audit_proofs[kNumOfParallelQueries]; |
| for (size_t query_i = 0; query_i < kNumOfParallelQueries; ++query_i) { |
| const size_t dns_requests_required = query_i + 1; |
| audit_proofs[query_i] = GetSampleAuditProof(dns_requests_required * |
| kMaxProofNodesPerDnsResponse); |
| } |
| // The most DNS requests that are made by any of the above N queries is N. |
| const size_t kMaxDnsRequestsPerQuery = kNumOfParallelQueries; |
| |
| // Setup expectations for up to N DNS requests per query performed. |
| // All of the queries will be started at the same time, so expect the DNS |
| // requests and responses to be interleaved. |
| // NB: |
| // Ideally, the tests wouldn't require that the DNS requests sent by the |
| // parallel queries are interleaved. However, the mock socket framework does |
| // not provide a way to express this. |
| for (size_t dns_req_i = 0; dns_req_i < kMaxDnsRequestsPerQuery; ++dns_req_i) { |
| for (size_t query_i = 0; query_i < kNumOfParallelQueries; ++query_i) { |
| const std::vector<std::string>& proof = audit_proofs[query_i]; |
| // Closed-open range of |proof| nodes that are expected in this response. |
| const size_t start_node = dns_req_i * 7; |
| const size_t end_node = |
| std::min(start_node + kMaxProofNodesPerDnsResponse, proof.size()); |
| |
| // If there are any nodes left, expect another request and response. |
| if (start_node < end_node) { |
| ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse( |
| base::StringPrintf("%zu.%" PRIu64 ".%" PRIu64 ".tree.ct.test.", |
| start_node, kLeafIndices[query_i], |
| kTreeSizes[query_i]), |
| proof.begin() + start_node, proof.begin() + end_node)); |
| } |
| } |
| } |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> queries[kNumOfParallelQueries]; |
| |
| // Start the queries. |
| for (size_t i = 0; i < kNumOfParallelQueries; ++i) { |
| ASSERT_THAT(log_client->QueryAuditProof( |
| "ct.test", kLeafHashes[i], false /* lookup_securely */, |
| kTreeSizes[i], &queries[i], callbacks[i].callback()), |
| IsError(net::ERR_IO_PENDING)) |
| << "query #" << i; |
| } |
| |
| // Wait for each query to complete and check its results. |
| for (size_t i = 0; i < kNumOfParallelQueries; ++i) { |
| net::TestCompletionCallback& callback = callbacks[i]; |
| |
| SCOPED_TRACE(testing::Message() << "callbacks[" << i << "]"); |
| EXPECT_THAT(callback.WaitForResult(), IsOk()); |
| const net::ct::MerkleAuditProof& proof = queries[i]->GetProof(); |
| EXPECT_THAT(proof.leaf_index, Eq(kLeafIndices[i])); |
| EXPECT_THAT(proof.tree_size, Eq(kTreeSizes[i])); |
| EXPECT_THAT(proof.nodes, Eq(audit_proofs[i])); |
| } |
| |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, -net::OK, 3); |
| histograms_.ExpectUniqueSample(kLeafIndexRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 3); |
| histograms_.ExpectUniqueSample(kAuditProofErrorHistogram, -net::OK, 3); |
| histograms_.ExpectUniqueSample(kAuditProofRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 3); |
| } |
| |
| TEST_P(LogDnsClientTest, CanBeThrottledToOneQueryAtATime) { |
| // Check that queries can be rate-limited to one at a time. |
| // The second query, initiated while the first is in progress, should fail. |
| const std::vector<std::string> audit_proof = GetSampleAuditProof(20); |
| |
| // Expect the first query to send leaf index and audit proof requests, but the |
| // second should not due to throttling. |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456)); |
| |
| // It takes a number of DNS requests to retrieve the entire |audit_proof| |
| // (see |kMaxProofNodesPerDnsResponse|). |
| for (size_t nodes_begin = 0; nodes_begin < audit_proof.size(); |
| nodes_begin += kMaxProofNodesPerDnsResponse) { |
| const size_t nodes_end = std::min( |
| nodes_begin + kMaxProofNodesPerDnsResponse, audit_proof.size()); |
| |
| ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse( |
| base::StringPrintf("%zu.123456.999999.tree.ct.test.", nodes_begin), |
| audit_proof.begin() + nodes_begin, audit_proof.begin() + nodes_end)); |
| } |
| |
| const size_t kMaxConcurrentQueries = 1; |
| std::unique_ptr<LogDnsClient> log_client = |
| CreateLogDnsClient(kMaxConcurrentQueries); |
| |
| // Try to start the queries. |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query1; |
| net::TestCompletionCallback callback1; |
| ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 999999, |
| &query1, callback1.callback()), |
| IsError(net::ERR_IO_PENDING)); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query2; |
| net::TestCompletionCallback callback2; |
| ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[1], |
| false /* lookup_securely */, 999999, |
| &query2, callback2.callback()), |
| IsError(net::ERR_TEMPORARILY_THROTTLED)); |
| |
| // Check that the first query succeeded. |
| EXPECT_THAT(callback1.WaitForResult(), IsOk()); |
| const net::ct::MerkleAuditProof& proof1 = query1->GetProof(); |
| EXPECT_THAT(proof1.leaf_index, Eq(123456u)); |
| EXPECT_THAT(proof1.tree_size, Eq(999999u)); |
| EXPECT_THAT(proof1.nodes, Eq(audit_proof)); |
| |
| // Try a third query, which should succeed now that the first is finished. |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[2], 666)); |
| for (size_t nodes_begin = 0; nodes_begin < audit_proof.size(); |
| nodes_begin += kMaxProofNodesPerDnsResponse) { |
| const size_t nodes_end = std::min( |
| nodes_begin + kMaxProofNodesPerDnsResponse, audit_proof.size()); |
| |
| ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse( |
| base::StringPrintf("%zu.666.999999.tree.ct.test.", nodes_begin), |
| audit_proof.begin() + nodes_begin, audit_proof.begin() + nodes_end)); |
| } |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query3; |
| net::TestCompletionCallback callback3; |
| ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[2], |
| false /* lookup_securely */, 999999, |
| &query3, callback3.callback()), |
| IsError(net::ERR_IO_PENDING)); |
| |
| // Check that the third query succeeded. |
| EXPECT_THAT(callback3.WaitForResult(), IsOk()); |
| const net::ct::MerkleAuditProof& proof3 = query3->GetProof(); |
| EXPECT_THAT(proof3.leaf_index, Eq(666u)); |
| EXPECT_THAT(proof3.tree_size, Eq(999999u)); |
| EXPECT_THAT(proof3.nodes, Eq(audit_proof)); |
| |
| histograms_.ExpectUniqueSample(kLeafIndexErrorHistogram, -net::OK, 2); |
| histograms_.ExpectUniqueSample(kLeafIndexRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 2); |
| histograms_.ExpectUniqueSample(kAuditProofErrorHistogram, -net::OK, 2); |
| histograms_.ExpectUniqueSample(kAuditProofRcodeHistogram, |
| net::dns_protocol::kRcodeNOERROR, 2); |
| } |
| |
| TEST_P(LogDnsClientTest, NotifiesWhenNoLongerThrottled) { |
| const std::vector<std::string> audit_proof = GetSampleAuditProof(20); |
| |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456)); |
| for (size_t nodes_begin = 0; nodes_begin < audit_proof.size(); |
| nodes_begin += kMaxProofNodesPerDnsResponse) { |
| const size_t nodes_end = std::min( |
| nodes_begin + kMaxProofNodesPerDnsResponse, audit_proof.size()); |
| |
| ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse( |
| base::StringPrintf("%zu.123456.999999.tree.ct.test.", nodes_begin), |
| audit_proof.begin() + nodes_begin, audit_proof.begin() + nodes_end)); |
| } |
| |
| const size_t kMaxConcurrentQueries = 1; |
| std::unique_ptr<LogDnsClient> log_client = |
| CreateLogDnsClient(kMaxConcurrentQueries); |
| |
| // Start a query. |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query1; |
| net::TestCompletionCallback query_callback1; |
| ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 999999, |
| &query1, query_callback1.callback()), |
| IsError(net::ERR_IO_PENDING)); |
| |
| net::TestClosure not_throttled_callback; |
| log_client->NotifyWhenNotThrottled(not_throttled_callback.closure()); |
| |
| ASSERT_THAT(query_callback1.WaitForResult(), IsOk()); |
| not_throttled_callback.WaitForResult(); |
| |
| // Start another query to check |not_throttled_callback| doesn't fire again. |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[1], 666)); |
| for (size_t nodes_begin = 0; nodes_begin < audit_proof.size(); |
| nodes_begin += kMaxProofNodesPerDnsResponse) { |
| const size_t nodes_end = std::min( |
| nodes_begin + kMaxProofNodesPerDnsResponse, audit_proof.size()); |
| |
| ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse( |
| base::StringPrintf("%zu.666.999999.tree.ct.test.", nodes_begin), |
| audit_proof.begin() + nodes_begin, audit_proof.begin() + nodes_end)); |
| } |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query2; |
| net::TestCompletionCallback query_callback2; |
| ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[1], |
| false /* lookup_securely */, 999999, |
| &query2, query_callback2.callback()), |
| IsError(net::ERR_IO_PENDING)); |
| |
| // Give the query a chance to run. |
| ASSERT_THAT(query_callback2.WaitForResult(), IsOk()); |
| // Give |not_throttled_callback| a chance to run - it shouldn't though. |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(not_throttled_callback.have_result()); |
| } |
| |
| TEST_P(LogDnsClientTest, CanCancelQueries) { |
| const size_t kMaxConcurrentQueries = 1; |
| std::unique_ptr<LogDnsClient> log_client = |
| CreateLogDnsClient(kMaxConcurrentQueries); |
| |
| // Expect the first request of the query to be sent, but not the rest because |
| // it'll be cancelled before it gets that far. |
| ASSERT_TRUE( |
| mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456)); |
| |
| // Start query. |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| net::TestCompletionCallback callback; |
| ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 999999, |
| &query, callback.callback()), |
| IsError(net::ERR_IO_PENDING)); |
| |
| // Cancel the query. |
| query.reset(); |
| |
| // Give |callback| a chance to run - it shouldn't though. |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(callback.have_result()); |
| |
| histograms_.ExpectTotalCount(kLeafIndexErrorHistogram, 0); |
| histograms_.ExpectTotalCount(kLeafIndexRcodeHistogram, 0); |
| histograms_.ExpectTotalCount(kAuditProofErrorHistogram, 0); |
| histograms_.ExpectTotalCount(kAuditProofRcodeHistogram, 0); |
| } |
| |
| TEST_P(LogDnsClientTest, SecureDnsMode_Secure) { |
| const std::vector<std::string> audit_proof = GetSampleAuditProof(20); |
| |
| net::MockDnsClientRuleList rules; |
| // Make leaf index queries for kLeafIndexQnames[0] successful only when |
| // lookup_securely is true. |
| rules.emplace_back( |
| kLeafIndexQnames[0], net::dns_protocol::kTypeTXT, |
| net::SecureDnsMode::SECURE, |
| net::MockDnsClientRule::CreateSecureResult(net::BuildTestDnsTextResponse( |
| kLeafIndexQnames[0], |
| std::vector<std::vector<std::string>>({{"123456"}}))), |
| false /* delay */); |
| |
| // Add successful audit proof queries for lookup_securely true. |
| for (size_t nodes_begin = 0; nodes_begin < audit_proof.size(); |
| nodes_begin += kMaxProofNodesPerDnsResponse) { |
| const size_t nodes_end = std::min( |
| nodes_begin + kMaxProofNodesPerDnsResponse, audit_proof.size()); |
| rules.emplace_back( |
| base::StringPrintf("%zu.123456.999999.tree.ct.test.", nodes_begin), |
| net::dns_protocol::kTypeTXT, net::SecureDnsMode::SECURE, |
| net::MockDnsClientRule::CreateSecureResult( |
| net::BuildTestDnsTextResponse( |
| base::StringPrintf("%zu.123456.999999.tree.ct.test.", |
| nodes_begin), |
| {{std::accumulate(audit_proof.begin() + nodes_begin, |
| audit_proof.begin() + nodes_end, |
| std::string())}})), |
| false /* delay */ |
| ); |
| } |
| |
| std::unique_ptr<LogDnsClient> log_client = |
| CreateRuleBasedLogDnsClient(std::move(rules)); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| net::TestCompletionCallback callback1; |
| ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 999999, |
| &query, callback1.callback()), |
| IsError(net::ERR_IO_PENDING)); |
| EXPECT_THAT(callback1.WaitForResult(), IsError(net::ERR_NAME_NOT_RESOLVED)); |
| |
| net::TestCompletionCallback callback2; |
| ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[0], |
| true /* lookup_securely */, 999999, |
| &query, callback2.callback()), |
| IsError(net::ERR_IO_PENDING)); |
| EXPECT_THAT(callback2.WaitForResult(), IsOk()); |
| } |
| |
| TEST_P(LogDnsClientTest, SecureDnsMode_Insecure) { |
| const std::vector<std::string> audit_proof = GetSampleAuditProof(20); |
| |
| net::MockDnsClientRuleList rules; |
| // Make leaf index queries for kLeafIndexQnames[0] successful only when |
| // lookup_securely is false. |
| rules.emplace_back( |
| kLeafIndexQnames[0], net::dns_protocol::kTypeTXT, net::SecureDnsMode::OFF, |
| net::MockDnsClientRule::Result(net::BuildTestDnsTextResponse( |
| kLeafIndexQnames[0], |
| std::vector<std::vector<std::string>>({{"123456"}}))), |
| false /* delay */); |
| |
| // Add successful audit proof queries for lookup_securely false. |
| for (size_t nodes_begin = 0; nodes_begin < audit_proof.size(); |
| nodes_begin += kMaxProofNodesPerDnsResponse) { |
| const size_t nodes_end = std::min( |
| nodes_begin + kMaxProofNodesPerDnsResponse, audit_proof.size()); |
| rules.emplace_back( |
| base::StringPrintf("%zu.123456.999999.tree.ct.test.", nodes_begin), |
| net::dns_protocol::kTypeTXT, net::SecureDnsMode::OFF, |
| net::MockDnsClientRule::Result(net::BuildTestDnsTextResponse( |
| base::StringPrintf("%zu.123456.999999.tree.ct.test.", nodes_begin), |
| {{std::accumulate(audit_proof.begin() + nodes_begin, |
| audit_proof.begin() + nodes_end, |
| std::string())}})), |
| false /* delay */ |
| ); |
| } |
| |
| std::unique_ptr<LogDnsClient> log_client = |
| CreateRuleBasedLogDnsClient(std::move(rules)); |
| |
| std::unique_ptr<LogDnsClient::AuditProofQuery> query; |
| net::TestCompletionCallback callback1; |
| ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[0], |
| false /* lookup_securely */, 999999, |
| &query, callback1.callback()), |
| IsError(net::ERR_IO_PENDING)); |
| EXPECT_THAT(callback1.WaitForResult(), IsOk()); |
| |
| net::TestCompletionCallback callback2; |
| ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[0], |
| true /* lookup_securely */, 999999, |
| &query, callback2.callback()), |
| IsError(net::ERR_IO_PENDING)); |
| EXPECT_THAT(callback2.WaitForResult(), IsError(net::ERR_NAME_NOT_RESOLVED)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(ReadMode, |
| LogDnsClientTest, |
| ::testing::Values(net::IoMode::ASYNC, |
| net::IoMode::SYNCHRONOUS)); |
| |
| } // namespace certificate_transparency |