summaryrefslogtreecommitdiff
path: root/chromium/components/certificate_transparency
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2017-03-08 10:28:10 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2017-03-20 13:40:30 +0000
commite733310db58160074f574c429d48f8308c0afe17 (patch)
treef8aef4b7e62a69928dbcf880620eece20f98c6df /chromium/components/certificate_transparency
parent2f583e4aec1ae3a86fa047829c96b310dc12ecdf (diff)
downloadqtwebengine-chromium-e733310db58160074f574c429d48f8308c0afe17.tar.gz
BASELINE: Update Chromium to 56.0.2924.122
Change-Id: I4e04de8f47e47e501c46ed934c76a431c6337ced Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/components/certificate_transparency')
-rw-r--r--chromium/components/certificate_transparency/log_dns_client.cc495
-rw-r--r--chromium/components/certificate_transparency/log_dns_client.h127
-rw-r--r--chromium/components/certificate_transparency/log_dns_client_unittest.cc950
-rw-r--r--chromium/components/certificate_transparency/mock_log_dns_traffic.cc274
-rw-r--r--chromium/components/certificate_transparency/mock_log_dns_traffic.h118
5 files changed, 1175 insertions, 789 deletions
diff --git a/chromium/components/certificate_transparency/log_dns_client.cc b/chromium/components/certificate_transparency/log_dns_client.cc
index ce7e8627a70..fabbbac4698 100644
--- a/chromium/components/certificate_transparency/log_dns_client.cc
+++ b/chromium/components/certificate_transparency/log_dns_client.cc
@@ -4,18 +4,19 @@
#include "components/certificate_transparency/log_dns_client.h"
-#include <sstream>
-
#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/format_macros.h"
#include "base/location.h"
#include "base/logging.h"
+#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "components/base32/base32.h"
#include "crypto/sha2.h"
-#include "net/base/net_errors.h"
#include "net/cert/merkle_audit_proof.h"
#include "net/dns/dns_client.h"
#include "net/dns/dns_config_service.h"
@@ -41,11 +42,11 @@ bool ParseTxtResponse(const net::DnsResponse& response, std::string* txt) {
// |parsed_record| away as soon as we've extracted the payload, so provide
// the "null" time.
auto parsed_record = net::RecordParsed::CreateFrom(&parser, base::Time());
- if (parsed_record == nullptr)
+ if (!parsed_record)
return false;
auto* txt_record = parsed_record->rdata<net::TxtRecordRdata>();
- if (txt_record == nullptr)
+ if (!txt_record)
return false;
// The draft CT-over-DNS RFC says that there MUST be exactly one string in the
@@ -97,227 +98,375 @@ bool ParseAuditPath(const net::DnsResponse& response,
} // namespace
-LogDnsClient::LogDnsClient(std::unique_ptr<net::DnsClient> dns_client,
- const net::NetLogWithSource& net_log)
- : dns_client_(std::move(dns_client)),
+// Encapsulates the state machine required to get an audit proof from a Merkle
+// leaf hash. This requires a DNS request to obtain the leaf index, then a
+// series of DNS requests to get the nodes of the proof.
+class LogDnsClient::AuditProofQuery {
+ public:
+ // The LogDnsClient is guaranteed to outlive the AuditProofQuery, so it's safe
+ // to leave ownership of |dns_client| with LogDnsClient.
+ AuditProofQuery(net::DnsClient* dns_client,
+ const std::string& domain_for_log,
+ const net::NetLogWithSource& net_log);
+
+ // Begins the process of getting an audit proof for the CT log entry with a
+ // leaf hash of |leaf_hash|. The proof will be for a tree of size |tree_size|.
+ // If it cannot be obtained synchronously, net::ERR_IO_PENDING will be
+ // returned and |callback| will be invoked when the operation has completed
+ // asynchronously. Ownership of |proof| remains with the caller, and it must
+ // not be deleted until the operation is complete.
+ net::Error Start(std::string leaf_hash,
+ uint64_t tree_size,
+ const net::CompletionCallback& callback,
+ net::ct::MerkleAuditProof* out_proof);
+
+ private:
+ enum class State {
+ NONE,
+ REQUEST_LEAF_INDEX,
+ REQUEST_LEAF_INDEX_COMPLETE,
+ REQUEST_AUDIT_PROOF_NODES,
+ REQUEST_AUDIT_PROOF_NODES_COMPLETE,
+ };
+
+ net::Error DoLoop(net::Error result);
+
+ // When a DnsTransaction completes, store the response and resume the state
+ // machine. It is safe to store a pointer to |response| because |transaction|
+ // is kept alive in |current_dns_transaction_|.
+ void OnDnsTransactionComplete(net::DnsTransaction* transaction,
+ int net_error,
+ const net::DnsResponse* response);
+
+ // Requests the leaf index for the CT log entry with |leaf_hash_|.
+ net::Error RequestLeafIndex();
+
+ // Stores the received leaf index in |proof_->leaf_index|.
+ // If successful, the audit proof nodes will be requested next.
+ net::Error RequestLeafIndexComplete(net::Error result);
+
+ // Requests the next batch of audit proof nodes from a CT log.
+ // The index of the first node required is determined by looking at how many
+ // nodes are already in |proof_->nodes|.
+ // The CT log may return up to 7 nodes - this is the maximum allowed by the
+ // CT-over-DNS draft RFC, as a TXT RDATA string can have a maximum length of
+ // 255 bytes and each node is 32 bytes long (a SHA-256 hash).
+ //
+ // The performance of this could be improved by sending all of the expected
+ // requests up front. Each response can contain a maximum of 7 audit path
+ // nodes, so for an audit proof of size 20, it could send 3 queries (for nodes
+ // 0-6, 7-13 and 14-19) immediately. Currently, it sends only the first and
+ // then, based on the number of nodes received, sends the next query.
+ // The complexity of the code would increase though, as it would need to
+ // detect gaps in the audit proof caused by the server not responding with the
+ // anticipated number of nodes. It would also undermine LogDnsClient's ability
+ // to rate-limit DNS requests.
+ net::Error RequestAuditProofNodes();
+
+ // Appends the received audit proof nodes to |proof_->nodes|.
+ // If any nodes are missing, another request will follow this one.
+ net::Error RequestAuditProofNodesComplete(net::Error result);
+
+ // Sends a TXT record request for the domain |qname|.
+ // Returns true if the request could be started.
+ // OnDnsTransactionComplete() will be invoked with the result of the request.
+ bool StartDnsTransaction(const std::string& qname);
+
+ // The next state that this query will enter.
+ State next_state_;
+ // The DNS domain of the CT log that is being queried.
+ std::string domain_for_log_;
+ // The Merkle leaf hash of the CT log entry an audit proof is required for.
+ std::string leaf_hash_;
+ // The audit proof to populate.
+ net::ct::MerkleAuditProof* proof_;
+ // The callback to invoke when the query is complete.
+ net::CompletionCallback callback_;
+ // The DnsClient to use for sending DNS requests to the CT log.
+ net::DnsClient* dns_client_;
+ // The most recent DNS request. Null if no DNS requests have been made.
+ std::unique_ptr<net::DnsTransaction> current_dns_transaction_;
+ // The most recent DNS response. Only valid so long as the corresponding DNS
+ // request is stored in |current_dns_transaction_|.
+ const net::DnsResponse* last_dns_response_;
+ // The NetLog that DNS transactions will log to.
+ net::NetLogWithSource net_log_;
+ // Produces WeakPtrs to |this| for binding callbacks.
+ base::WeakPtrFactory<AuditProofQuery> weak_ptr_factory_;
+};
+
+LogDnsClient::AuditProofQuery::AuditProofQuery(
+ net::DnsClient* dns_client,
+ const std::string& domain_for_log,
+ const net::NetLogWithSource& net_log)
+ : next_state_(State::NONE),
+ domain_for_log_(domain_for_log),
+ dns_client_(dns_client),
net_log_(net_log),
weak_ptr_factory_(this) {
- CHECK(dns_client_);
- net::NetworkChangeNotifier::AddDNSObserver(this);
- UpdateDnsConfig();
+ DCHECK(dns_client_);
+ DCHECK(!domain_for_log_.empty());
}
-LogDnsClient::~LogDnsClient() {
- net::NetworkChangeNotifier::RemoveDNSObserver(this);
-}
-
-void LogDnsClient::OnDNSChanged() {
- UpdateDnsConfig();
+// |leaf_hash| is not a const-ref to allow callers to std::move that string into
+// the method, avoiding the need to make a copy.
+net::Error LogDnsClient::AuditProofQuery::Start(
+ std::string leaf_hash,
+ uint64_t tree_size,
+ const net::CompletionCallback& callback,
+ net::ct::MerkleAuditProof* proof) {
+ // It should not already be in progress.
+ DCHECK_EQ(State::NONE, next_state_);
+ proof_ = proof;
+ proof_->tree_size = tree_size;
+ leaf_hash_ = std::move(leaf_hash);
+ callback_ = callback;
+ // The first step in the query is to request the leaf index corresponding to
+ // |leaf_hash| from the CT log.
+ next_state_ = State::REQUEST_LEAF_INDEX;
+ // Begin the state machine.
+ return DoLoop(net::OK);
}
-void LogDnsClient::OnInitialDNSConfigRead() {
- UpdateDnsConfig();
+net::Error LogDnsClient::AuditProofQuery::DoLoop(net::Error result) {
+ CHECK_NE(State::NONE, next_state_);
+ do {
+ State state = next_state_;
+ next_state_ = State::NONE;
+ switch (state) {
+ case State::REQUEST_LEAF_INDEX:
+ result = RequestLeafIndex();
+ break;
+ case State::REQUEST_LEAF_INDEX_COMPLETE:
+ result = RequestLeafIndexComplete(result);
+ break;
+ case State::REQUEST_AUDIT_PROOF_NODES:
+ result = RequestAuditProofNodes();
+ break;
+ case State::REQUEST_AUDIT_PROOF_NODES_COMPLETE:
+ result = RequestAuditProofNodesComplete(result);
+ break;
+ case State::NONE:
+ NOTREACHED();
+ break;
+ }
+ } while (result != net::ERR_IO_PENDING && next_state_ != State::NONE);
+
+ return result;
}
-void LogDnsClient::QueryLeafIndex(base::StringPiece domain_for_log,
- base::StringPiece leaf_hash,
- const LeafIndexCallback& callback) {
- if (domain_for_log.empty() || leaf_hash.size() != crypto::kSHA256Length) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(callback, net::Error::ERR_INVALID_ARGUMENT, 0));
- return;
+void LogDnsClient::AuditProofQuery::OnDnsTransactionComplete(
+ net::DnsTransaction* transaction,
+ int net_error,
+ const net::DnsResponse* response) {
+ DCHECK_EQ(current_dns_transaction_.get(), transaction);
+ last_dns_response_ = response;
+ net::Error result = DoLoop(static_cast<net::Error>(net_error));
+
+ // If DoLoop() indicates that I/O is pending, don't invoke the completion
+ // callback. OnDnsTransactionComplete() will be invoked again once the I/O
+ // is complete, and can invoke the completion callback then if appropriate.
+ if (result != net::ERR_IO_PENDING) {
+ // The callback will delete this query (now that it has finished), so copy
+ // |callback_| before running it so that it is not deleted along with the
+ // query, mid-callback-execution (which would result in a crash).
+ base::ResetAndReturn(&callback_).Run(result);
}
+}
- std::string encoded_leaf_hash =
- base32::Base32Encode(leaf_hash, base32::Base32EncodePolicy::OMIT_PADDING);
+net::Error LogDnsClient::AuditProofQuery::RequestLeafIndex() {
+ std::string encoded_leaf_hash = base32::Base32Encode(
+ leaf_hash_, base32::Base32EncodePolicy::OMIT_PADDING);
DCHECK_EQ(encoded_leaf_hash.size(), 52u);
- net::DnsTransactionFactory* factory = dns_client_->GetTransactionFactory();
- if (factory == nullptr) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- base::Bind(callback, net::Error::ERR_NAME_RESOLUTION_FAILED, 0));
- return;
+ std::string qname = base::StringPrintf(
+ "%s.hash.%s.", encoded_leaf_hash.c_str(), domain_for_log_.c_str());
+
+ if (!StartDnsTransaction(qname)) {
+ return net::ERR_NAME_RESOLUTION_FAILED;
}
- std::ostringstream qname;
- qname << encoded_leaf_hash << ".hash." << domain_for_log << ".";
+ next_state_ = State::REQUEST_LEAF_INDEX_COMPLETE;
+ return net::ERR_IO_PENDING;
+}
- net::DnsTransactionFactory::CallbackType transaction_callback = base::Bind(
- &LogDnsClient::QueryLeafIndexComplete, weak_ptr_factory_.GetWeakPtr());
+// Stores the received leaf index in |proof_->leaf_index|.
+// If successful, the audit proof nodes will be requested next.
+net::Error LogDnsClient::AuditProofQuery::RequestLeafIndexComplete(
+ net::Error result) {
+ if (result != net::OK) {
+ return result;
+ }
+
+ DCHECK(last_dns_response_);
+ if (!ParseLeafIndex(*last_dns_response_, &proof_->leaf_index)) {
+ return net::ERR_DNS_MALFORMED_RESPONSE;
+ }
- std::unique_ptr<net::DnsTransaction> dns_transaction =
- factory->CreateTransaction(qname.str(), net::dns_protocol::kTypeTXT,
- transaction_callback, net_log_);
+ // Reject leaf index if it is out-of-range.
+ // This indicates either:
+ // a) the wrong tree_size was provided.
+ // b) the wrong leaf hash was provided.
+ // c) there is a bug server-side.
+ // The first two are more likely, so return ERR_INVALID_ARGUMENT.
+ if (proof_->leaf_index >= proof_->tree_size) {
+ return net::ERR_INVALID_ARGUMENT;
+ }
- dns_transaction->Start();
- leaf_index_queries_.push_back({std::move(dns_transaction), callback});
+ next_state_ = State::REQUEST_AUDIT_PROOF_NODES;
+ return net::OK;
}
-// The performance of this could be improved by sending all of the expected
-// queries up front. Each response can contain a maximum of 7 audit path nodes,
-// so for an audit proof of size 20, it could send 3 queries (for nodes 0-6,
-// 7-13 and 14-19) immediately. Currently, it sends only the first and then,
-// based on the number of nodes received, sends the next query. The complexity
-// of the code would increase though, as it would need to detect gaps in the
-// audit proof caused by the server not responding with the anticipated number
-// of nodes. Ownership of the proof would need to change, as it would be shared
-// between simultaneous DNS transactions.
-void LogDnsClient::QueryAuditProof(base::StringPiece domain_for_log,
- uint64_t leaf_index,
- uint64_t tree_size,
- const AuditProofCallback& callback) {
- if (domain_for_log.empty() || leaf_index >= tree_size) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- base::Bind(callback, net::Error::ERR_INVALID_ARGUMENT, nullptr));
- return;
+net::Error LogDnsClient::AuditProofQuery::RequestAuditProofNodes() {
+ // Test pre-conditions (should be guaranteed by DNS response validation).
+ if (proof_->leaf_index >= proof_->tree_size ||
+ proof_->nodes.size() >= net::ct::CalculateAuditPathLength(
+ proof_->leaf_index, proof_->tree_size)) {
+ return net::ERR_UNEXPECTED;
}
- std::unique_ptr<net::ct::MerkleAuditProof> proof(
- new net::ct::MerkleAuditProof);
- proof->leaf_index = leaf_index;
- // TODO(robpercival): Once a "tree_size" field is added to MerkleAuditProof,
- // pass |tree_size| to QueryAuditProofNodes using that.
+ std::string qname = base::StringPrintf(
+ "%zu.%" PRIu64 ".%" PRIu64 ".tree.%s.", proof_->nodes.size(),
+ proof_->leaf_index, proof_->tree_size, domain_for_log_.c_str());
+
+ if (!StartDnsTransaction(qname)) {
+ return net::ERR_NAME_RESOLUTION_FAILED;
+ }
- // Query for the first batch of audit proof nodes (i.e. starting from 0).
- QueryAuditProofNodes(std::move(proof), domain_for_log, tree_size, 0,
- callback);
+ next_state_ = State::REQUEST_AUDIT_PROOF_NODES_COMPLETE;
+ return net::ERR_IO_PENDING;
}
-void LogDnsClient::QueryLeafIndexComplete(net::DnsTransaction* transaction,
- int net_error,
- const net::DnsResponse* response) {
- auto query_iterator =
- std::find_if(leaf_index_queries_.begin(), leaf_index_queries_.end(),
- [transaction](const Query<LeafIndexCallback>& query) {
- return query.transaction.get() == transaction;
- });
- if (query_iterator == leaf_index_queries_.end()) {
- NOTREACHED();
- return;
+net::Error LogDnsClient::AuditProofQuery::RequestAuditProofNodesComplete(
+ net::Error result) {
+ if (result != net::OK) {
+ return result;
}
- const Query<LeafIndexCallback> query = std::move(*query_iterator);
- leaf_index_queries_.erase(query_iterator);
- // If we've received no response but no net::error either (shouldn't happen),
- // report the response as invalid.
- if (response == nullptr && net_error == net::OK) {
- net_error = net::ERR_INVALID_RESPONSE;
+ const uint64_t audit_path_length =
+ net::ct::CalculateAuditPathLength(proof_->leaf_index, proof_->tree_size);
+
+ // The calculated |audit_path_length| can't ever be greater than 64, so
+ // deriving the amount of memory to reserve from the untrusted |leaf_index|
+ // is safe.
+ proof_->nodes.reserve(audit_path_length);
+
+ DCHECK(last_dns_response_);
+ if (!ParseAuditPath(*last_dns_response_, proof_)) {
+ return net::ERR_DNS_MALFORMED_RESPONSE;
}
- if (net_error != net::OK) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(query.callback, net_error, 0));
- return;
+ // Keep requesting more proof nodes until all of them are received.
+ if (proof_->nodes.size() < audit_path_length) {
+ next_state_ = State::REQUEST_AUDIT_PROOF_NODES;
}
- uint64_t leaf_index;
- if (!ParseLeafIndex(*response, &leaf_index)) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- base::Bind(query.callback, net::ERR_DNS_MALFORMED_RESPONSE, 0));
- return;
+ return net::OK;
+}
+
+bool LogDnsClient::AuditProofQuery::StartDnsTransaction(
+ const std::string& qname) {
+ net::DnsTransactionFactory* factory = dns_client_->GetTransactionFactory();
+ if (!factory) {
+ return false;
}
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(query.callback, net::OK, leaf_index));
+ current_dns_transaction_ = factory->CreateTransaction(
+ qname, net::dns_protocol::kTypeTXT,
+ base::Bind(&LogDnsClient::AuditProofQuery::OnDnsTransactionComplete,
+ weak_ptr_factory_.GetWeakPtr()),
+ net_log_);
+
+ current_dns_transaction_->Start();
+ return true;
}
-void LogDnsClient::QueryAuditProofNodes(
- std::unique_ptr<net::ct::MerkleAuditProof> proof,
+LogDnsClient::LogDnsClient(std::unique_ptr<net::DnsClient> dns_client,
+ const net::NetLogWithSource& net_log,
+ size_t max_concurrent_queries)
+ : dns_client_(std::move(dns_client)),
+ net_log_(net_log),
+ max_concurrent_queries_(max_concurrent_queries),
+ weak_ptr_factory_(this) {
+ CHECK(dns_client_);
+ net::NetworkChangeNotifier::AddDNSObserver(this);
+ UpdateDnsConfig();
+}
+
+LogDnsClient::~LogDnsClient() {
+ net::NetworkChangeNotifier::RemoveDNSObserver(this);
+}
+
+void LogDnsClient::OnDNSChanged() {
+ UpdateDnsConfig();
+}
+
+void LogDnsClient::OnInitialDNSConfigRead() {
+ UpdateDnsConfig();
+}
+
+void LogDnsClient::NotifyWhenNotThrottled(const base::Closure& callback) {
+ DCHECK(HasMaxConcurrentQueriesInProgress());
+ not_throttled_callbacks_.push_back(callback);
+}
+
+// |leaf_hash| is not a const-ref to allow callers to std::move that string into
+// the method, avoiding LogDnsClient::AuditProofQuery having to make a copy.
+net::Error LogDnsClient::QueryAuditProof(
base::StringPiece domain_for_log,
+ std::string leaf_hash,
uint64_t tree_size,
- uint64_t node_index,
- const AuditProofCallback& callback) {
- // Preconditions that should be guaranteed internally by this class.
+ net::ct::MerkleAuditProof* proof,
+ const net::CompletionCallback& callback) {
DCHECK(proof);
- DCHECK(!domain_for_log.empty());
- DCHECK_LT(proof->leaf_index, tree_size);
- DCHECK_LT(node_index,
- net::ct::CalculateAuditPathLength(proof->leaf_index, tree_size));
- net::DnsTransactionFactory* factory = dns_client_->GetTransactionFactory();
- if (factory == nullptr) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- base::Bind(callback, net::Error::ERR_NAME_RESOLUTION_FAILED, nullptr));
- return;
+ if (domain_for_log.empty() || leaf_hash.size() != crypto::kSHA256Length) {
+ return net::ERR_INVALID_ARGUMENT;
}
- std::ostringstream qname;
- qname << node_index << "." << proof->leaf_index << "." << tree_size
- << ".tree." << domain_for_log << ".";
+ if (HasMaxConcurrentQueriesInProgress()) {
+ return net::ERR_TEMPORARILY_THROTTLED;
+ }
- net::DnsTransactionFactory::CallbackType transaction_callback =
- base::Bind(&LogDnsClient::QueryAuditProofNodesComplete,
- weak_ptr_factory_.GetWeakPtr(), base::Passed(std::move(proof)),
- domain_for_log, tree_size);
+ AuditProofQuery* query = new AuditProofQuery(
+ dns_client_.get(), domain_for_log.as_string(), net_log_);
+ // Transfers ownership of |query| to |audit_proof_queries_|.
+ audit_proof_queries_.emplace_back(query);
- std::unique_ptr<net::DnsTransaction> dns_transaction =
- factory->CreateTransaction(qname.str(), net::dns_protocol::kTypeTXT,
- transaction_callback, net_log_);
- dns_transaction->Start();
- audit_proof_queries_.push_back({std::move(dns_transaction), callback});
+ return query->Start(std::move(leaf_hash), tree_size,
+ base::Bind(&LogDnsClient::QueryAuditProofComplete,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Unretained(query), callback),
+ proof);
}
-void LogDnsClient::QueryAuditProofNodesComplete(
- std::unique_ptr<net::ct::MerkleAuditProof> proof,
- base::StringPiece domain_for_log,
- uint64_t tree_size,
- net::DnsTransaction* transaction,
- int net_error,
- const net::DnsResponse* response) {
- // Preconditions that should be guaranteed internally by this class.
- DCHECK(proof);
- DCHECK(!domain_for_log.empty());
+void LogDnsClient::QueryAuditProofComplete(
+ AuditProofQuery* query,
+ const net::CompletionCallback& callback,
+ int net_error) {
+ DCHECK(query);
+ // Finished with the query - destroy it.
auto query_iterator =
std::find_if(audit_proof_queries_.begin(), audit_proof_queries_.end(),
- [transaction](const Query<AuditProofCallback>& query) {
- return query.transaction.get() == transaction;
+ [query](const std::unique_ptr<AuditProofQuery>& p) {
+ return p.get() == query;
});
-
- if (query_iterator == audit_proof_queries_.end()) {
- NOTREACHED();
- return;
- }
- const Query<AuditProofCallback> query = std::move(*query_iterator);
+ DCHECK(query_iterator != audit_proof_queries_.end());
audit_proof_queries_.erase(query_iterator);
- // If we've received no response but no net::error either (shouldn't happen),
- // report the response as invalid.
- if (response == nullptr && net_error == net::OK) {
- net_error = net::ERR_INVALID_RESPONSE;
- }
-
- if (net_error != net::OK) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(query.callback, net_error, nullptr));
- return;
- }
+ callback.Run(net_error);
- const uint64_t audit_path_length =
- net::ct::CalculateAuditPathLength(proof->leaf_index, tree_size);
- proof->nodes.reserve(audit_path_length);
-
- if (!ParseAuditPath(*response, proof.get())) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- base::Bind(query.callback, net::ERR_DNS_MALFORMED_RESPONSE, nullptr));
- return;
- }
-
- const uint64_t audit_path_nodes_received = proof->nodes.size();
- if (audit_path_nodes_received < audit_path_length) {
- QueryAuditProofNodes(std::move(proof), domain_for_log, tree_size,
- audit_path_nodes_received, query.callback);
- return;
+ // Notify interested parties that the next query will not be throttled.
+ std::list<base::Closure> callbacks = std::move(not_throttled_callbacks_);
+ for (const base::Closure& callback : callbacks) {
+ callback.Run();
}
+}
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- base::Bind(query.callback, net::OK, base::Passed(std::move(proof))));
+bool LogDnsClient::HasMaxConcurrentQueriesInProgress() const {
+ return max_concurrent_queries_ != 0 &&
+ audit_proof_queries_.size() >= max_concurrent_queries_;
}
void LogDnsClient::UpdateDnsConfig() {
diff --git a/chromium/components/certificate_transparency/log_dns_client.h b/chromium/components/certificate_transparency/log_dns_client.h
index 11b99990e2b..f3966a8d2b2 100644
--- a/chromium/components/certificate_transparency/log_dns_client.h
+++ b/chromium/components/certificate_transparency/log_dns_client.h
@@ -8,18 +8,17 @@
#include <stdint.h>
#include <list>
-#include <string>
#include "base/callback.h"
#include "base/macros.h"
#include "base/strings/string_piece.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_errors.h"
#include "net/base/network_change_notifier.h"
#include "net/log/net_log_with_source.h"
namespace net {
class DnsClient;
-class DnsResponse;
-class DnsTransaction;
namespace ct {
struct MerkleAuditProof;
} // namespace ct
@@ -34,26 +33,17 @@ namespace certificate_transparency {
// It must be created and deleted on the same thread. It is not thread-safe.
class LogDnsClient : public net::NetworkChangeNotifier::DNSObserver {
public:
- // Invoked when a leaf index query completes.
- // If an error occured, |net_error| will be a net::Error code, otherwise it
- // will be net::OK and |leaf_index| will be the leaf index that was received.
- using LeafIndexCallback =
- base::Callback<void(int net_error, uint64_t leaf_index)>;
- // Invoked when an audit proof query completes.
- // If an error occurred, |net_error| will be a net::Error code, otherwise it
- // will be net::OK and |proof| will be the audit proof that was received.
- // The log ID of |proof| will not be set, as that is not known by this class,
- // but the leaf index will be set.
- using AuditProofCallback =
- base::Callback<void(int net_error,
- std::unique_ptr<net::ct::MerkleAuditProof> proof)>;
-
// Creates a log client that will take ownership of |dns_client| and use it
// to perform DNS queries. Queries will be logged to |net_log|.
// The |dns_client| does not need to be configured first - this will be done
// automatically as needed.
+ // A limit can be set on the number of concurrent DNS queries by providing a
+ // positive value for |max_concurrent_queries|. Queries that would exceed this
+ // limit will fail with net::TEMPORARILY_THROTTLED. Setting this to 0 will
+ // disable this limit.
LogDnsClient(std::unique_ptr<net::DnsClient> dns_client,
- const net::NetLogWithSource& net_log);
+ const net::NetLogWithSource& net_log,
+ size_t max_concurrent_queries);
// Must be deleted on the same thread that it was created on.
~LogDnsClient() override;
@@ -65,67 +55,70 @@ class LogDnsClient : public net::NetworkChangeNotifier::DNSObserver {
// The DnsClient's config will be updated in response.
void OnInitialDNSConfigRead() override;
- // Queries a CT log to discover the index of the leaf with |leaf_hash|.
- // The log is identified by |domain_for_log|, which is the DNS name used as a
- // suffix for all queries.
- // The |leaf_hash| is the SHA-256 hash of a Merkle tree leaf in that log.
- // The |callback| is invoked when the query is complete, or an error occurs.
- void QueryLeafIndex(base::StringPiece domain_for_log,
- base::StringPiece leaf_hash,
- const LeafIndexCallback& callback);
-
- // Queries a CT log to retrieve an audit proof for the leaf at |leaf_index|.
- // The size of the CT log tree must be provided in |tree_size|.
+ // Registers a callback to be invoked when the number of concurrent queries
+ // falls below the limit defined by |max_concurrent_queries| (passed to the
+ // constructor of LogDnsClient). This callback will fire once and then be
+ // unregistered. Should only be used if QueryAuditProof() returns
+ // net::ERR_TEMPORARILY_THROTTLED.
+ void NotifyWhenNotThrottled(const base::Closure& callback);
+
+ // Queries a CT log to retrieve an audit proof for the leaf with |leaf_hash|.
// The log is identified by |domain_for_log|, which is the DNS name used as a
// suffix for all queries.
- // The |callback| is invoked when the query is complete, or an error occurs.
- void QueryAuditProof(base::StringPiece domain_for_log,
- uint64_t leaf_index,
- uint64_t tree_size,
- const AuditProofCallback& callback);
+ // The |leaf_hash| is the SHA-256 Merkle leaf hash (see RFC6962, section 2.1).
+ // The size of the CT log tree, for which the proof is requested, must be
+ // provided in |tree_size|.
+ // The leaf index and audit proof obtained from the CT log will be placed in
+ // |out_proof|.
+ // If the proof cannot be obtained synchronously, this method will return
+ // net::ERR_IO_PENDING and invoke |callback| once the query is complete.
+ // Returns:
+ // - net::OK if the query was successful.
+ // - net::ERR_IO_PENDING if the query was successfully started and is
+ // continuing asynchronously.
+ // - net::ERR_TEMPORARILY_THROTTLED if the maximum number of concurrent
+ // queries are already in progress. Try again later.
+ // - net::ERR_NAME_RESOLUTION_FAILED if DNS queries are not possible.
+ // Check that the DnsConfig returned by NetworkChangeNotifier is valid.
+ // - net::ERR_INVALID_ARGUMENT if an argument is invalid, e.g. |leaf_hash| is
+ // not a SHA-256 hash.
+ net::Error QueryAuditProof(base::StringPiece domain_for_log,
+ std::string leaf_hash,
+ uint64_t tree_size,
+ net::ct::MerkleAuditProof* out_proof,
+ const net::CompletionCallback& callback);
private:
- void QueryLeafIndexComplete(net::DnsTransaction* transaction,
- int neterror,
- const net::DnsResponse* response);
-
- // Queries a CT log to retrieve part of an audit |proof|. The |node_index|
- // indicates which node of the audit proof/ should be requested. The CT log
- // may return up to 7 nodes, starting from |node_index| (this is the maximum
- // that will fit in a DNS UDP packet). The nodes will be appended to
- // |proof->nodes|.
- void QueryAuditProofNodes(std::unique_ptr<net::ct::MerkleAuditProof> proof,
- base::StringPiece domain_for_log,
- uint64_t tree_size,
- uint64_t node_index,
- const AuditProofCallback& callback);
-
- void QueryAuditProofNodesComplete(
- std::unique_ptr<net::ct::MerkleAuditProof> proof,
- base::StringPiece domain_for_log,
- uint64_t tree_size,
- net::DnsTransaction* transaction,
- int net_error,
- const net::DnsResponse* response);
+ class AuditProofQuery;
+
+ // Invoked when an audit proof query completes.
+ // |query| is the query that has completed.
+ // |callback| is the user-provided callback that should be notified.
+ // |net_error| is a net::Error indicating success or failure.
+ void QueryAuditProofComplete(AuditProofQuery* query,
+ const net::CompletionCallback& callback,
+ int net_error);
+
+ // Returns true if the maximum number of queries are currently in flight.
+ // If the maximum number of concurrency queries is set to 0, this will always
+ // return false.
+ bool HasMaxConcurrentQueriesInProgress() const;
// Updates the |dns_client_| config using NetworkChangeNotifier.
void UpdateDnsConfig();
- // A DNS query that is in flight.
- template <typename CallbackType>
- struct Query {
- std::unique_ptr<net::DnsTransaction> transaction;
- CallbackType callback;
- };
-
// Used to perform DNS queries.
std::unique_ptr<net::DnsClient> dns_client_;
// Passed to the DNS client for logging.
net::NetLogWithSource net_log_;
- // Leaf index queries that haven't completed yet.
- std::list<Query<LeafIndexCallback>> leaf_index_queries_;
- // Audit proof queries that haven't completed yet.
- std::list<Query<AuditProofCallback>> audit_proof_queries_;
+ // A FIFO queue of ongoing queries. Since entries will always be appended to
+ // the end and lookups will typically yield entries at the beginning,
+ // std::list is an efficient choice.
+ std::list<std::unique_ptr<AuditProofQuery>> audit_proof_queries_;
+ // The maximum number of queries that can be in flight at one time.
+ size_t max_concurrent_queries_;
+ // Callbacks to invoke when the number of concurrent queries is at its limit.
+ std::list<base::Closure> not_throttled_callbacks_;
// Creates weak_ptrs to this, for callback purposes.
base::WeakPtrFactory<LogDnsClient> weak_ptr_factory_;
diff --git a/chromium/components/certificate_transparency/log_dns_client_unittest.cc b/chromium/components/certificate_transparency/log_dns_client_unittest.cc
index 5f7a0240273..51397c6ca64 100644
--- a/chromium/components/certificate_transparency/log_dns_client_unittest.cc
+++ b/chromium/components/certificate_transparency/log_dns_client_unittest.cc
@@ -10,8 +10,13 @@
#include <utility>
#include <vector>
+#include "base/format_macros.h"
+#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.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"
@@ -20,6 +25,7 @@
#include "net/dns/dns_client.h"
#include "net/dns/dns_config_service.h"
#include "net/dns/dns_protocol.h"
+#include "net/log/net_log.h"
#include "net/test/gtest_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -27,17 +33,47 @@
namespace certificate_transparency {
namespace {
+using ::testing::AllOf;
+using ::testing::Eq;
using ::testing::IsEmpty;
-using ::testing::IsNull;
+using ::testing::Le;
using ::testing::Not;
using ::testing::NotNull;
using net::test::IsError;
using net::test::IsOk;
-constexpr char kLeafHash[] =
+// 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";
+ "\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
@@ -54,63 +90,7 @@ std::vector<std::string> GetSampleAuditProof(size_t length) {
return audit_proof;
}
-class MockLeafIndexCallback {
- public:
- MockLeafIndexCallback() : called_(false) {}
-
- bool called() const { return called_; }
- int net_error() const { return net_error_; }
- uint64_t leaf_index() const { return leaf_index_; }
-
- void Run(int net_error, uint64_t leaf_index) {
- EXPECT_TRUE(!called_);
- called_ = true;
- net_error_ = net_error;
- leaf_index_ = leaf_index;
- run_loop_.Quit();
- }
-
- LogDnsClient::LeafIndexCallback AsCallback() {
- return base::Bind(&MockLeafIndexCallback::Run, base::Unretained(this));
- }
-
- void WaitUntilRun() { run_loop_.Run(); }
-
- private:
- bool called_;
- int net_error_;
- uint64_t leaf_index_;
- base::RunLoop run_loop_;
-};
-
-class MockAuditProofCallback {
- public:
- MockAuditProofCallback() : called_(false) {}
-
- bool called() const { return called_; }
- int net_error() const { return net_error_; }
- const net::ct::MerkleAuditProof* proof() const { return proof_.get(); }
-
- void Run(int net_error, std::unique_ptr<net::ct::MerkleAuditProof> proof) {
- EXPECT_TRUE(!called_);
- called_ = true;
- net_error_ = net_error;
- proof_ = std::move(proof);
- run_loop_.Quit();
- }
-
- LogDnsClient::AuditProofCallback AsCallback() {
- return base::Bind(&MockAuditProofCallback::Run, base::Unretained(this));
- }
-
- void WaitUntilRun() { run_loop_.Run(); }
-
- private:
- bool called_;
- int net_error_;
- std::unique_ptr<net::ct::MerkleAuditProof> proof_;
- base::RunLoop run_loop_;
-};
+} // namespace
class LogDnsClientTest : public ::testing::TestWithParam<net::IoMode> {
protected:
@@ -120,24 +100,24 @@ class LogDnsClientTest : public ::testing::TestWithParam<net::IoMode> {
mock_dns_.InitializeDnsConfig();
}
- void QueryLeafIndex(base::StringPiece log_domain,
- base::StringPiece leaf_hash,
- MockLeafIndexCallback* callback) {
- LogDnsClient log_client(mock_dns_.CreateDnsClient(),
- net::NetLogWithSource());
- log_client.QueryLeafIndex(log_domain, leaf_hash, callback->AsCallback());
- callback->WaitUntilRun();
+ std::unique_ptr<LogDnsClient> CreateLogDnsClient(
+ size_t max_concurrent_queries) {
+ return base::MakeUnique<LogDnsClient>(mock_dns_.CreateDnsClient(),
+ net::NetLogWithSource(),
+ max_concurrent_queries);
}
- void QueryAuditProof(base::StringPiece log_domain,
- uint64_t leaf_index,
- uint64_t tree_size,
- MockAuditProofCallback* callback) {
- LogDnsClient log_client(mock_dns_.CreateDnsClient(),
- net::NetLogWithSource());
- log_client.QueryAuditProof(log_domain, leaf_index, tree_size,
- callback->AsCallback());
- callback->WaitUntilRun();
+ // 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
@@ -150,313 +130,258 @@ class LogDnsClientTest : public ::testing::TestWithParam<net::IoMode> {
MockLogDnsTraffic mock_dns_;
};
-TEST_P(LogDnsClientTest, QueryLeafIndex) {
- mock_dns_.ExpectLeafIndexRequestAndResponse(
- "D4S6DSV2J743QJZEQMH4UYHEYK7KRQ5JIQOCPMFUHZVJNFGHXACA.hash.ct.test.",
- 123456);
-
- MockLeafIndexCallback callback;
- QueryLeafIndex("ct.test", kLeafHash, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsOk());
- EXPECT_THAT(callback.leaf_index(), 123456);
-}
-
-TEST_P(LogDnsClientTest, QueryLeafIndexReportsThatLogDomainDoesNotExist) {
- mock_dns_.ExpectRequestAndErrorResponse(
- "D4S6DSV2J743QJZEQMH4UYHEYK7KRQ5JIQOCPMFUHZVJNFGHXACA.hash.ct.test.",
- net::dns_protocol::kRcodeNXDOMAIN);
-
- MockLeafIndexCallback callback;
- QueryLeafIndex("ct.test", kLeafHash, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_NAME_NOT_RESOLVED));
- EXPECT_THAT(callback.leaf_index(), 0);
-}
-
-TEST_P(LogDnsClientTest, QueryLeafIndexReportsServerFailure) {
- mock_dns_.ExpectRequestAndErrorResponse(
- "D4S6DSV2J743QJZEQMH4UYHEYK7KRQ5JIQOCPMFUHZVJNFGHXACA.hash.ct.test.",
- net::dns_protocol::kRcodeSERVFAIL);
+TEST_P(LogDnsClientTest, QueryAuditProofReportsThatLogDomainDoesNotExist) {
+ ASSERT_TRUE(mock_dns_.ExpectRequestAndErrorResponse(
+ kLeafIndexQnames[0], net::dns_protocol::kRcodeNXDOMAIN));
- MockLeafIndexCallback callback;
- QueryLeafIndex("ct.test", kLeafHash, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_DNS_SERVER_FAILED));
- EXPECT_THAT(callback.leaf_index(), 0);
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ IsError(net::ERR_NAME_NOT_RESOLVED));
}
-TEST_P(LogDnsClientTest, QueryLeafIndexReportsServerRefusal) {
- mock_dns_.ExpectRequestAndErrorResponse(
- "D4S6DSV2J743QJZEQMH4UYHEYK7KRQ5JIQOCPMFUHZVJNFGHXACA.hash.ct.test.",
- net::dns_protocol::kRcodeREFUSED);
+TEST_P(LogDnsClientTest,
+ QueryAuditProofReportsServerFailuresDuringLeafIndexRequests) {
+ ASSERT_TRUE(mock_dns_.ExpectRequestAndErrorResponse(
+ kLeafIndexQnames[0], net::dns_protocol::kRcodeSERVFAIL));
- MockLeafIndexCallback callback;
- QueryLeafIndex("ct.test", kLeafHash, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_DNS_SERVER_FAILED));
- EXPECT_THAT(callback.leaf_index(), 0);
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ IsError(net::ERR_DNS_SERVER_FAILED));
}
TEST_P(LogDnsClientTest,
- QueryLeafIndexReportsMalformedResponseIfContainsNoStrings) {
- mock_dns_.ExpectRequestAndResponse(
- "D4S6DSV2J743QJZEQMH4UYHEYK7KRQ5JIQOCPMFUHZVJNFGHXACA.hash.ct.test.",
- std::vector<base::StringPiece>());
+ QueryAuditProofReportsServerRefusalsDuringLeafIndexRequests) {
+ ASSERT_TRUE(mock_dns_.ExpectRequestAndErrorResponse(
+ kLeafIndexQnames[0], net::dns_protocol::kRcodeREFUSED));
- MockLeafIndexCallback callback;
- QueryLeafIndex("ct.test", kLeafHash, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_DNS_MALFORMED_RESPONSE));
- EXPECT_THAT(callback.leaf_index(), 0);
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ IsError(net::ERR_DNS_SERVER_FAILED));
}
-TEST_P(LogDnsClientTest,
- QueryLeafIndexReportsMalformedResponseIfContainsMoreThanOneString) {
- mock_dns_.ExpectRequestAndResponse(
- "D4S6DSV2J743QJZEQMH4UYHEYK7KRQ5JIQOCPMFUHZVJNFGHXACA.hash.ct.test.",
- {"123456", "7"});
+TEST_P(
+ LogDnsClientTest,
+ QueryAuditProofReportsMalformedResponseIfLeafIndexResponseContainsNoStrings) {
+ ASSERT_TRUE(mock_dns_.ExpectRequestAndResponse(
+ kLeafIndexQnames[0], std::vector<base::StringPiece>()));
- MockLeafIndexCallback callback;
- QueryLeafIndex("ct.test", kLeafHash, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_DNS_MALFORMED_RESPONSE));
- EXPECT_THAT(callback.leaf_index(), 0);
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
-TEST_P(LogDnsClientTest,
- QueryLeafIndexReportsMalformedResponseIfLeafIndexIsNotNumeric) {
- mock_dns_.ExpectRequestAndResponse(
- "D4S6DSV2J743QJZEQMH4UYHEYK7KRQ5JIQOCPMFUHZVJNFGHXACA.hash.ct.test.",
- {"foo"});
+TEST_P(
+ LogDnsClientTest,
+ QueryAuditProofReportsMalformedResponseIfLeafIndexResponseContainsMoreThanOneString) {
+ ASSERT_TRUE(
+ mock_dns_.ExpectRequestAndResponse(kLeafIndexQnames[0], {"123456", "7"}));
- MockLeafIndexCallback callback;
- QueryLeafIndex("ct.test", kLeafHash, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_DNS_MALFORMED_RESPONSE));
- EXPECT_THAT(callback.leaf_index(), 0);
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
TEST_P(LogDnsClientTest,
- QueryLeafIndexReportsMalformedResponseIfLeafIndexIsFloatingPoint) {
- mock_dns_.ExpectRequestAndResponse(
- "D4S6DSV2J743QJZEQMH4UYHEYK7KRQ5JIQOCPMFUHZVJNFGHXACA.hash.ct.test.",
- {"123456.0"});
+ QueryAuditProofReportsMalformedResponseIfLeafIndexIsNotNumeric) {
+ ASSERT_TRUE(mock_dns_.ExpectRequestAndResponse(kLeafIndexQnames[0], {"foo"}));
- MockLeafIndexCallback callback;
- QueryLeafIndex("ct.test", kLeafHash, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_DNS_MALFORMED_RESPONSE));
- EXPECT_THAT(callback.leaf_index(), 0);
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
TEST_P(LogDnsClientTest,
- QueryLeafIndexReportsMalformedResponseIfLeafIndexIsEmpty) {
- mock_dns_.ExpectRequestAndResponse(
- "D4S6DSV2J743QJZEQMH4UYHEYK7KRQ5JIQOCPMFUHZVJNFGHXACA.hash.ct.test.",
- {""});
+ QueryAuditProofReportsMalformedResponseIfLeafIndexIsFloatingPoint) {
+ ASSERT_TRUE(
+ mock_dns_.ExpectRequestAndResponse(kLeafIndexQnames[0], {"123456.0"}));
- MockLeafIndexCallback callback;
- QueryLeafIndex("ct.test", kLeafHash, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_DNS_MALFORMED_RESPONSE));
- EXPECT_THAT(callback.leaf_index(), 0);
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
TEST_P(LogDnsClientTest,
- QueryLeafIndexReportsMalformedResponseIfLeafIndexHasNonNumericPrefix) {
- mock_dns_.ExpectRequestAndResponse(
- "D4S6DSV2J743QJZEQMH4UYHEYK7KRQ5JIQOCPMFUHZVJNFGHXACA.hash.ct.test.",
- {"foo123456"});
+ QueryAuditProofReportsMalformedResponseIfLeafIndexIsEmpty) {
+ ASSERT_TRUE(mock_dns_.ExpectRequestAndResponse(kLeafIndexQnames[0], {""}));
- MockLeafIndexCallback callback;
- QueryLeafIndex("ct.test", kLeafHash, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_DNS_MALFORMED_RESPONSE));
- EXPECT_THAT(callback.leaf_index(), 0);
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
TEST_P(LogDnsClientTest,
- QueryLeafIndexReportsMalformedResponseIfLeafIndexHasNonNumericSuffix) {
- mock_dns_.ExpectRequestAndResponse(
- "D4S6DSV2J743QJZEQMH4UYHEYK7KRQ5JIQOCPMFUHZVJNFGHXACA.hash.ct.test.",
- {"123456foo"});
+ QueryAuditProofReportsMalformedResponseIfLeafIndexHasNonNumericPrefix) {
+ ASSERT_TRUE(
+ mock_dns_.ExpectRequestAndResponse(kLeafIndexQnames[0], {"foo123456"}));
- MockLeafIndexCallback callback;
- QueryLeafIndex("ct.test", kLeafHash, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_DNS_MALFORMED_RESPONSE));
- EXPECT_THAT(callback.leaf_index(), 0);
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
-TEST_P(LogDnsClientTest, QueryLeafIndexReportsInvalidArgIfLogDomainIsEmpty) {
- MockLeafIndexCallback callback;
- QueryLeafIndex("", kLeafHash, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_INVALID_ARGUMENT));
- EXPECT_THAT(callback.leaf_index(), 0);
-}
+TEST_P(LogDnsClientTest,
+ QueryAuditProofReportsMalformedResponseIfLeafIndexHasNonNumericSuffix) {
+ ASSERT_TRUE(
+ mock_dns_.ExpectRequestAndResponse(kLeafIndexQnames[0], {"123456foo"}));
-TEST_P(LogDnsClientTest, QueryLeafIndexReportsInvalidArgIfLogDomainIsNull) {
- MockLeafIndexCallback callback;
- QueryLeafIndex(nullptr, kLeafHash, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_INVALID_ARGUMENT));
- EXPECT_THAT(callback.leaf_index(), 0);
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
-TEST_P(LogDnsClientTest, QueryLeafIndexReportsInvalidArgIfLeafHashIsInvalid) {
- MockLeafIndexCallback callback;
- QueryLeafIndex("ct.test", "foo", &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_INVALID_ARGUMENT));
- EXPECT_THAT(callback.leaf_index(), 0);
+TEST_P(LogDnsClientTest, QueryAuditProofReportsInvalidArgIfLogDomainIsEmpty) {
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("", kLeafHashes[0], kTreeSizes[0], &proof),
+ IsError(net::ERR_INVALID_ARGUMENT));
}
-TEST_P(LogDnsClientTest, QueryLeafIndexReportsInvalidArgIfLeafHashIsEmpty) {
- MockLeafIndexCallback callback;
- QueryLeafIndex("ct.test", "", &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_INVALID_ARGUMENT));
- EXPECT_THAT(callback.leaf_index(), 0);
+TEST_P(LogDnsClientTest, QueryAuditProofReportsInvalidArgIfLeafHashIsInvalid) {
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", "foo", kTreeSizes[0], &proof),
+ IsError(net::ERR_INVALID_ARGUMENT));
}
-TEST_P(LogDnsClientTest, QueryLeafIndexReportsInvalidArgIfLeafHashIsNull) {
- MockLeafIndexCallback callback;
- QueryLeafIndex("ct.test", nullptr, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_INVALID_ARGUMENT));
- EXPECT_THAT(callback.leaf_index(), 0);
+TEST_P(LogDnsClientTest, QueryAuditProofReportsInvalidArgIfLeafHashIsEmpty) {
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", "", kTreeSizes[0], &proof),
+ IsError(net::ERR_INVALID_ARGUMENT));
}
-TEST_P(LogDnsClientTest, QueryLeafIndexReportsSocketError) {
- mock_dns_.ExpectRequestAndSocketError(
- "D4S6DSV2J743QJZEQMH4UYHEYK7KRQ5JIQOCPMFUHZVJNFGHXACA.hash.ct.test.",
- net::ERR_CONNECTION_REFUSED);
+TEST_P(LogDnsClientTest,
+ QueryAuditProofReportsSocketErrorsDuringLeafIndexRequests) {
+ ASSERT_TRUE(mock_dns_.ExpectRequestAndSocketError(
+ kLeafIndexQnames[0], net::ERR_CONNECTION_REFUSED));
- MockLeafIndexCallback callback;
- QueryLeafIndex("ct.test", kLeafHash, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_CONNECTION_REFUSED));
- EXPECT_THAT(callback.leaf_index(), 0);
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ IsError(net::ERR_CONNECTION_REFUSED));
}
-TEST_P(LogDnsClientTest, QueryLeafIndexReportsTimeout) {
- mock_dns_.ExpectRequestAndTimeout(
- "D4S6DSV2J743QJZEQMH4UYHEYK7KRQ5JIQOCPMFUHZVJNFGHXACA.hash.ct.test.");
+TEST_P(LogDnsClientTest,
+ QueryAuditProofReportsTimeoutsDuringLeafIndexRequests) {
+ ASSERT_TRUE(mock_dns_.ExpectRequestAndTimeout(kLeafIndexQnames[0]));
- MockLeafIndexCallback callback;
- QueryLeafIndex("ct.test", kLeafHash, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_DNS_TIMED_OUT));
- EXPECT_THAT(callback.leaf_index(), 0);
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], kTreeSizes[0], &proof),
+ IsError(net::ERR_DNS_TIMED_OUT));
}
TEST_P(LogDnsClientTest, QueryAuditProof) {
const std::vector<std::string> audit_proof = GetSampleAuditProof(20);
- // It should require 3 queries to collect the entire audit proof, as there is
- // only space for 7 nodes per UDP packet.
- mock_dns_.ExpectAuditProofRequestAndResponse("0.123456.999999.tree.ct.test.",
- audit_proof.begin(),
- audit_proof.begin() + 7);
- mock_dns_.ExpectAuditProofRequestAndResponse("7.123456.999999.tree.ct.test.",
- audit_proof.begin() + 7,
- audit_proof.begin() + 14);
- mock_dns_.ExpectAuditProofRequestAndResponse("14.123456.999999.tree.ct.test.",
- audit_proof.begin() + 14,
- audit_proof.end());
-
- MockAuditProofCallback callback;
- QueryAuditProof("ct.test", 123456, 999999, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsOk());
- ASSERT_THAT(callback.proof(), NotNull());
- EXPECT_THAT(callback.proof()->leaf_index, 123456);
- // EXPECT_THAT(callback.proof()->tree_size, 999999);
- EXPECT_THAT(callback.proof()->nodes, audit_proof);
+ // 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));
+ }
+
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ IsOk());
+ EXPECT_THAT(proof.leaf_index, Eq(123456u));
+ EXPECT_THAT(proof.tree_size, Eq(999999u));
+ EXPECT_THAT(proof.nodes, Eq(audit_proof));
}
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.
- mock_dns_.ExpectAuditProofRequestAndResponse("0.123456.999999.tree.ct.test.",
- audit_proof.begin(),
- audit_proof.begin() + 1);
- mock_dns_.ExpectAuditProofRequestAndResponse("1.123456.999999.tree.ct.test.",
- audit_proof.begin() + 1,
- audit_proof.begin() + 3);
- mock_dns_.ExpectAuditProofRequestAndResponse("3.123456.999999.tree.ct.test.",
- audit_proof.begin() + 3,
- audit_proof.begin() + 6);
- mock_dns_.ExpectAuditProofRequestAndResponse("6.123456.999999.tree.ct.test.",
- audit_proof.begin() + 6,
- audit_proof.begin() + 10);
- mock_dns_.ExpectAuditProofRequestAndResponse("10.123456.999999.tree.ct.test.",
- audit_proof.begin() + 10,
- audit_proof.begin() + 13);
- mock_dns_.ExpectAuditProofRequestAndResponse("13.123456.999999.tree.ct.test.",
- audit_proof.begin() + 13,
- audit_proof.end());
-
- MockAuditProofCallback callback;
- QueryAuditProof("ct.test", 123456, 999999, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsOk());
- ASSERT_THAT(callback.proof(), NotNull());
- EXPECT_THAT(callback.proof()->leaf_index, 123456);
- // EXPECT_THAT(callback.proof()->tree_size, 999999);
- EXPECT_THAT(callback.proof()->nodes, audit_proof);
+ 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()));
+
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ IsOk());
+ EXPECT_THAT(proof.leaf_index, Eq(123456u));
+ EXPECT_THAT(proof.tree_size, Eq(999999u));
+ EXPECT_THAT(proof.nodes, Eq(audit_proof));
}
-TEST_P(LogDnsClientTest, QueryAuditProofReportsThatLogDomainDoesNotExist) {
- mock_dns_.ExpectRequestAndErrorResponse("0.123456.999999.tree.ct.test.",
- net::dns_protocol::kRcodeNXDOMAIN);
+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));
- MockAuditProofCallback callback;
- QueryAuditProof("ct.test", 123456, 999999, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_NAME_NOT_RESOLVED));
- EXPECT_THAT(callback.proof(), IsNull());
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ IsError(net::ERR_NAME_NOT_RESOLVED));
}
-TEST_P(LogDnsClientTest, QueryAuditProofReportsServerFailure) {
- mock_dns_.ExpectRequestAndErrorResponse("0.123456.999999.tree.ct.test.",
- net::dns_protocol::kRcodeSERVFAIL);
+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));
- MockAuditProofCallback callback;
- QueryAuditProof("ct.test", 123456, 999999, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_DNS_SERVER_FAILED));
- EXPECT_THAT(callback.proof(), IsNull());
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ IsError(net::ERR_DNS_SERVER_FAILED));
}
-TEST_P(LogDnsClientTest, QueryAuditProofReportsServerRefusal) {
- mock_dns_.ExpectRequestAndErrorResponse("0.123456.999999.tree.ct.test.",
- net::dns_protocol::kRcodeREFUSED);
+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));
- MockAuditProofCallback callback;
- QueryAuditProof("ct.test", 123456, 999999, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_DNS_SERVER_FAILED));
- EXPECT_THAT(callback.proof(), IsNull());
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ IsError(net::ERR_DNS_SERVER_FAILED));
}
-TEST_P(LogDnsClientTest,
- QueryAuditProofReportsResponseMalformedIfContainsNoStrings) {
- mock_dns_.ExpectRequestAndResponse("0.123456.999999.tree.ct.test.",
- std::vector<base::StringPiece>());
+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));
- MockAuditProofCallback callback;
- QueryAuditProof("ct.test", 123456, 999999, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_DNS_MALFORMED_RESPONSE));
- EXPECT_THAT(callback.proof(), IsNull());
+ ASSERT_TRUE(mock_dns_.ExpectRequestAndResponse(
+ "0.123456.999999.tree.ct.test.", std::vector<base::StringPiece>()));
+
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
-TEST_P(LogDnsClientTest,
- QueryAuditProofReportsResponseMalformedIfContainsMoreThanOneString) {
+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);
@@ -466,15 +391,17 @@ TEST_P(LogDnsClientTest,
std::string second_chunk_of_proof = std::accumulate(
audit_proof.begin() + 7, audit_proof.end(), std::string());
- mock_dns_.ExpectRequestAndResponse(
+ // 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});
+ {first_chunk_of_proof, second_chunk_of_proof}));
- MockAuditProofCallback callback;
- QueryAuditProof("ct.test", 123456, 999999, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_DNS_MALFORMED_RESPONSE));
- EXPECT_THAT(callback.proof(), IsNull());
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
TEST_P(LogDnsClientTest,
@@ -482,102 +409,91 @@ TEST_P(LogDnsClientTest,
// node is shorter than a SHA-256 hash (31 vs 32 bytes)
const std::vector<std::string> audit_proof(1, std::string(31, 'a'));
- mock_dns_.ExpectAuditProofRequestAndResponse(
- "0.123456.999999.tree.ct.test.", audit_proof.begin(), audit_proof.end());
+ 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()));
- MockAuditProofCallback callback;
- QueryAuditProof("ct.test", 123456, 999999, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_DNS_MALFORMED_RESPONSE));
- EXPECT_THAT(callback.proof(), IsNull());
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
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'));
- mock_dns_.ExpectAuditProofRequestAndResponse(
- "0.123456.999999.tree.ct.test.", audit_proof.begin(), audit_proof.end());
+ 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()));
- MockAuditProofCallback callback;
- QueryAuditProof("ct.test", 123456, 999999, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_DNS_MALFORMED_RESPONSE));
- EXPECT_THAT(callback.proof(), IsNull());
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
TEST_P(LogDnsClientTest, QueryAuditProofReportsResponseMalformedIfEmpty) {
const std::vector<std::string> audit_proof;
- mock_dns_.ExpectAuditProofRequestAndResponse(
- "0.123456.999999.tree.ct.test.", audit_proof.begin(), audit_proof.end());
-
- MockAuditProofCallback callback;
- QueryAuditProof("ct.test", 123456, 999999, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_DNS_MALFORMED_RESPONSE));
- EXPECT_THAT(callback.proof(), IsNull());
-}
-
-TEST_P(LogDnsClientTest, QueryAuditProofReportsInvalidArgIfLogDomainIsEmpty) {
- MockAuditProofCallback callback;
- QueryAuditProof("", 123456, 999999, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_INVALID_ARGUMENT));
- EXPECT_THAT(callback.proof(), IsNull());
-}
+ 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()));
-TEST_P(LogDnsClientTest, QueryAuditProofReportsInvalidArgIfLogDomainIsNull) {
- MockAuditProofCallback callback;
- QueryAuditProof(nullptr, 123456, 999999, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_INVALID_ARGUMENT));
- EXPECT_THAT(callback.proof(), IsNull());
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ IsError(net::ERR_DNS_MALFORMED_RESPONSE));
}
TEST_P(LogDnsClientTest,
QueryAuditProofReportsInvalidArgIfLeafIndexEqualToTreeSize) {
- MockAuditProofCallback callback;
- QueryAuditProof("ct.test", 123456, 123456, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_INVALID_ARGUMENT));
- EXPECT_THAT(callback.proof(), IsNull());
+ ASSERT_TRUE(
+ mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456));
+
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 123456, &proof),
+ IsError(net::ERR_INVALID_ARGUMENT));
}
TEST_P(LogDnsClientTest,
QueryAuditProofReportsInvalidArgIfLeafIndexGreaterThanTreeSize) {
- MockAuditProofCallback callback;
- QueryAuditProof("ct.test", 999999, 123456, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_INVALID_ARGUMENT));
- EXPECT_THAT(callback.proof(), IsNull());
+ ASSERT_TRUE(
+ mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 999999));
+
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 123456, &proof),
+ IsError(net::ERR_INVALID_ARGUMENT));
}
-TEST_P(LogDnsClientTest, QueryAuditProofReportsSocketError) {
- mock_dns_.ExpectRequestAndSocketError("0.123456.999999.tree.ct.test.",
- net::ERR_CONNECTION_REFUSED);
+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));
- MockAuditProofCallback callback;
- QueryAuditProof("ct.test", 123456, 999999, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_CONNECTION_REFUSED));
- EXPECT_THAT(callback.proof(), IsNull());
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ IsError(net::ERR_CONNECTION_REFUSED));
}
-TEST_P(LogDnsClientTest, QueryAuditProofReportsTimeout) {
- mock_dns_.ExpectRequestAndTimeout("0.123456.999999.tree.ct.test.");
+TEST_P(LogDnsClientTest,
+ QueryAuditProofReportsTimeoutsDuringAuditProofRequests) {
+ ASSERT_TRUE(
+ mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456));
+ ASSERT_TRUE(
+ mock_dns_.ExpectRequestAndTimeout("0.123456.999999.tree.ct.test."));
- MockAuditProofCallback callback;
- QueryAuditProof("ct.test", 123456, 999999, &callback);
- ASSERT_TRUE(callback.called());
- EXPECT_THAT(callback.net_error(), IsError(net::ERR_DNS_TIMED_OUT));
- EXPECT_THAT(callback.proof(), IsNull());
+ net::ct::MerkleAuditProof proof;
+ ASSERT_THAT(QueryAuditProof("ct.test", kLeafHashes[0], 999999, &proof),
+ IsError(net::ERR_DNS_TIMED_OUT));
}
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), net::NetLogWithSource());
+ LogDnsClient log_client(std::move(tmp), net::NetLogWithSource(), 0);
// Get the current DNS config, modify it and broadcast the update.
net::DnsConfig config(*dns_client->GetConfig());
@@ -593,7 +509,7 @@ TEST_P(LogDnsClientTest, AdoptsLatestDnsConfigIfValid) {
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), net::NetLogWithSource());
+ LogDnsClient log_client(std::move(tmp), net::NetLogWithSource(), 0);
// Get the current DNS config, modify it and broadcast the update.
net::DnsConfig config(*dns_client->GetConfig());
@@ -606,10 +522,274 @@ TEST_P(LogDnsClientTest, IgnoresLatestDnsConfigIfInvalid) {
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), net::NetLogWithSource(), 0);
+
+ // Start query.
+ net::ct::MerkleAuditProof proof;
+ net::TestCompletionCallback callback;
+ ASSERT_THAT(log_client.QueryAuditProof("ct.test", kLeafHashes[0], 999999,
+ &proof, 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());
+ 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(arraysize(kLeafIndexQnames)),
+ Le(arraysize(kLeafIndices)), Le(arraysize(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));
+ }
+ }
+ }
+
+ net::ct::MerkleAuditProof proofs[kNumOfParallelQueries];
+
+ // Start the queries.
+ for (size_t i = 0; i < kNumOfParallelQueries; ++i) {
+ ASSERT_THAT(
+ log_client->QueryAuditProof("ct.test", kLeafHashes[i], kTreeSizes[i],
+ &proofs[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());
+ EXPECT_THAT(proofs[i].leaf_index, Eq(kLeafIndices[i]));
+ EXPECT_THAT(proofs[i].tree_size, Eq(kTreeSizes[i]));
+ EXPECT_THAT(proofs[i].nodes, Eq(audit_proofs[i]));
+ }
+}
+
+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 should require 3 requests to collect the entire audit proof, as there is
+ // only space for 7 nodes per TXT record. One node is 32 bytes long and the
+ // TXT RDATA can have a maximum length of 255 bytes (255 / 32).
+ // Rate limiting should not interfere with these requests.
+ ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse(
+ "0.123456.999999.tree.ct.test.", audit_proof.begin(),
+ audit_proof.begin() + 7));
+ ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse(
+ "7.123456.999999.tree.ct.test.", audit_proof.begin() + 7,
+ audit_proof.begin() + 14));
+ ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse(
+ "14.123456.999999.tree.ct.test.", audit_proof.begin() + 14,
+ audit_proof.end()));
+
+ const size_t kMaxConcurrentQueries = 1;
+ std::unique_ptr<LogDnsClient> log_client =
+ CreateLogDnsClient(kMaxConcurrentQueries);
+
+ // Try to start the queries.
+ net::ct::MerkleAuditProof proof1;
+ net::TestCompletionCallback callback1;
+ ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[0], 999999,
+ &proof1, callback1.callback()),
+ IsError(net::ERR_IO_PENDING));
+
+ net::ct::MerkleAuditProof proof2;
+ net::TestCompletionCallback callback2;
+ ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[1], 999999,
+ &proof2, callback2.callback()),
+ IsError(net::ERR_TEMPORARILY_THROTTLED));
+
+ // Check that the first query succeeded.
+ EXPECT_THAT(callback1.WaitForResult(), IsOk());
+ 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));
+ ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse(
+ "0.666.999999.tree.ct.test.", audit_proof.begin(),
+ audit_proof.begin() + 7));
+ ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse(
+ "7.666.999999.tree.ct.test.", audit_proof.begin() + 7,
+ audit_proof.begin() + 14));
+ ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse(
+ "14.666.999999.tree.ct.test.", audit_proof.begin() + 14,
+ audit_proof.end()));
+
+ net::ct::MerkleAuditProof proof3;
+ net::TestCompletionCallback callback3;
+ ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[2], 999999,
+ &proof3, callback3.callback()),
+ IsError(net::ERR_IO_PENDING));
+
+ // Check that the third query succeeded.
+ EXPECT_THAT(callback3.WaitForResult(), IsOk());
+ EXPECT_THAT(proof3.leaf_index, Eq(666u));
+ EXPECT_THAT(proof3.tree_size, Eq(999999u));
+ EXPECT_THAT(proof3.nodes, Eq(audit_proof));
+}
+
+TEST_P(LogDnsClientTest, NotifiesWhenNoLongerThrottled) {
+ const std::vector<std::string> audit_proof = GetSampleAuditProof(20);
+
+ ASSERT_TRUE(
+ mock_dns_.ExpectLeafIndexRequestAndResponse(kLeafIndexQnames[0], 123456));
+ ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse(
+ "0.123456.999999.tree.ct.test.", audit_proof.begin(),
+ audit_proof.begin() + 7));
+ ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse(
+ "7.123456.999999.tree.ct.test.", audit_proof.begin() + 7,
+ audit_proof.begin() + 14));
+ ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse(
+ "14.123456.999999.tree.ct.test.", audit_proof.begin() + 14,
+ audit_proof.end()));
+
+ const size_t kMaxConcurrentQueries = 1;
+ std::unique_ptr<LogDnsClient> log_client =
+ CreateLogDnsClient(kMaxConcurrentQueries);
+
+ // Start a query.
+ net::ct::MerkleAuditProof proof1;
+ net::TestCompletionCallback proof_callback1;
+ ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[0], 999999,
+ &proof1, proof_callback1.callback()),
+ IsError(net::ERR_IO_PENDING));
+
+ net::TestClosure not_throttled_callback;
+ log_client->NotifyWhenNotThrottled(not_throttled_callback.closure());
+
+ ASSERT_THAT(proof_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));
+ ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse(
+ "0.666.999999.tree.ct.test.", audit_proof.begin(),
+ audit_proof.begin() + 7));
+ ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse(
+ "7.666.999999.tree.ct.test.", audit_proof.begin() + 7,
+ audit_proof.begin() + 14));
+ ASSERT_TRUE(mock_dns_.ExpectAuditProofRequestAndResponse(
+ "14.666.999999.tree.ct.test.", audit_proof.begin() + 14,
+ audit_proof.end()));
+
+ net::ct::MerkleAuditProof proof2;
+ net::TestCompletionCallback proof_callback2;
+ ASSERT_THAT(log_client->QueryAuditProof("ct.test", kLeafHashes[1], 999999,
+ &proof2, proof_callback2.callback()),
+ IsError(net::ERR_IO_PENDING));
+
+ // Give the query a chance to run.
+ ASSERT_THAT(proof_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());
+}
+
INSTANTIATE_TEST_CASE_P(ReadMode,
LogDnsClientTest,
::testing::Values(net::IoMode::ASYNC,
net::IoMode::SYNCHRONOUS));
-} // namespace
} // namespace certificate_transparency
diff --git a/chromium/components/certificate_transparency/mock_log_dns_traffic.cc b/chromium/components/certificate_transparency/mock_log_dns_traffic.cc
index 22599950dd7..646125bb26d 100644
--- a/chromium/components/certificate_transparency/mock_log_dns_traffic.cc
+++ b/chromium/components/certificate_transparency/mock_log_dns_traffic.cc
@@ -23,6 +23,10 @@ namespace certificate_transparency {
namespace {
+// This is used for the last mock socket response as a sentinel to prevent
+// trying to read more data than expected.
+const net::MockRead kNoMoreData(net::SYNCHRONOUS, net::ERR_UNEXPECTED, 2);
+
// Necessary to expose SetDnsConfig for testing.
class DnsChangeNotifier : public net::NetworkChangeNotifier {
public:
@@ -41,9 +45,12 @@ int FakeRandInt(int min, int max) {
return min;
}
-std::vector<char> CreateDnsTxtRequest(base::StringPiece qname) {
+bool CreateDnsTxtRequest(base::StringPiece qname, std::vector<char>* request) {
std::string encoded_qname;
- EXPECT_TRUE(net::DNSDomainFromDot(qname, &encoded_qname));
+ if (!net::DNSDomainFromDot(qname, &encoded_qname)) {
+ // |qname| is an invalid domain name.
+ return false;
+ }
// DNS query section:
// N bytes - qname
@@ -52,26 +59,32 @@ std::vector<char> CreateDnsTxtRequest(base::StringPiece qname) {
// Total = N + 4 bytes
const size_t query_section_size = encoded_qname.size() + 4;
- std::vector<char> request(sizeof(net::dns_protocol::Header) +
- query_section_size);
- base::BigEndianWriter writer(request.data(), request.size());
+ request->resize(sizeof(net::dns_protocol::Header) + query_section_size);
+ base::BigEndianWriter writer(request->data(), request->size());
// Header
net::dns_protocol::Header header = {};
header.flags = base::HostToNet16(net::dns_protocol::kFlagRD);
header.qdcount = base::HostToNet16(1);
- EXPECT_TRUE(writer.WriteBytes(&header, sizeof(header)));
- // Query section
- EXPECT_TRUE(writer.WriteBytes(encoded_qname.data(), encoded_qname.size()));
- EXPECT_TRUE(writer.WriteU16(net::dns_protocol::kTypeTXT));
- EXPECT_TRUE(writer.WriteU16(net::dns_protocol::kClassIN));
- EXPECT_EQ(0, writer.remaining());
-
- return request;
+
+ if (!writer.WriteBytes(&header, sizeof(header)) ||
+ !writer.WriteBytes(encoded_qname.data(), encoded_qname.size()) ||
+ !writer.WriteU16(net::dns_protocol::kTypeTXT) ||
+ !writer.WriteU16(net::dns_protocol::kClassIN)) {
+ return false;
+ }
+
+ if (writer.remaining() != 0) {
+ // Less than the expected amount of data was written.
+ return false;
+ }
+
+ return true;
}
-std::vector<char> CreateDnsTxtResponse(const std::vector<char>& request,
- base::StringPiece answer) {
+bool CreateDnsTxtResponse(const std::vector<char>& request,
+ base::StringPiece answer,
+ std::vector<char>* response) {
// DNS answers section:
// 2 bytes - qname pointer
// 2 bytes - record type
@@ -83,119 +96,170 @@ std::vector<char> CreateDnsTxtResponse(const std::vector<char>& request,
const size_t answers_section_size = 12 + answer.size();
constexpr uint32_t ttl = 86400; // seconds
- std::vector<char> response(request.size() + answers_section_size);
- std::copy(request.begin(), request.end(), response.begin());
+ response->resize(request.size() + answers_section_size);
+ std::copy(request.begin(), request.end(), response->begin());
+
// Modify the header
net::dns_protocol::Header* header =
- reinterpret_cast<net::dns_protocol::Header*>(response.data());
+ reinterpret_cast<net::dns_protocol::Header*>(response->data());
header->ancount = base::HostToNet16(1);
header->flags |= base::HostToNet16(net::dns_protocol::kFlagResponse);
+ // The qname is at the start of the query section (just after the header).
+ const uint8_t qname_ptr = sizeof(*header);
+
// Write the answer section
- base::BigEndianWriter writer(response.data() + request.size(),
- response.size() - request.size());
- EXPECT_TRUE(writer.WriteU8(0xc0)); // qname is a pointer
- EXPECT_TRUE(writer.WriteU8(
- sizeof(*header))); // address of qname (start of query section)
- EXPECT_TRUE(writer.WriteU16(net::dns_protocol::kTypeTXT));
- EXPECT_TRUE(writer.WriteU16(net::dns_protocol::kClassIN));
- EXPECT_TRUE(writer.WriteU32(ttl));
- EXPECT_TRUE(writer.WriteU16(answer.size()));
- EXPECT_TRUE(writer.WriteBytes(answer.data(), answer.size()));
- EXPECT_EQ(0, writer.remaining());
-
- return response;
+ base::BigEndianWriter writer(response->data() + request.size(),
+ response->size() - request.size());
+ if (!writer.WriteU8(net::dns_protocol::kLabelPointer) ||
+ !writer.WriteU8(qname_ptr) ||
+ !writer.WriteU16(net::dns_protocol::kTypeTXT) ||
+ !writer.WriteU16(net::dns_protocol::kClassIN) || !writer.WriteU32(ttl) ||
+ !writer.WriteU16(answer.size()) ||
+ !writer.WriteBytes(answer.data(), answer.size())) {
+ return false;
+ }
+
+ if (writer.remaining() != 0) {
+ // Less than the expected amount of data was written.
+ return false;
+ }
+
+ return true;
}
-std::vector<char> CreateDnsErrorResponse(const std::vector<char>& request,
- uint8_t rcode) {
- std::vector<char> response(request);
+bool CreateDnsErrorResponse(const std::vector<char>& request,
+ uint8_t rcode,
+ std::vector<char>* response) {
+ *response = request;
// Modify the header
net::dns_protocol::Header* header =
- reinterpret_cast<net::dns_protocol::Header*>(response.data());
+ reinterpret_cast<net::dns_protocol::Header*>(response->data());
header->ancount = base::HostToNet16(1);
header->flags |= base::HostToNet16(net::dns_protocol::kFlagResponse | rcode);
-
- return response;
+ return true;
}
} // namespace
-namespace internal {
-
-MockSocketData::MockSocketData(const std::vector<char>& write,
- const std::vector<char>& read)
- : expected_write_payload_(write),
- expected_read_payload_(read),
- expected_write_(net::SYNCHRONOUS,
- expected_write_payload_.data(),
- expected_write_payload_.size(),
- 0),
- expected_reads_{net::MockRead(net::ASYNC,
- expected_read_payload_.data(),
- expected_read_payload_.size(),
- 1),
- no_more_data_},
- socket_data_(expected_reads_, 2, &expected_write_, 1) {}
-
-MockSocketData::MockSocketData(const std::vector<char>& write, int net_error)
- : expected_write_payload_(write),
- expected_write_(net::SYNCHRONOUS,
- expected_write_payload_.data(),
- expected_write_payload_.size(),
- 0),
- expected_reads_{net::MockRead(net::ASYNC, net_error, 1), no_more_data_},
- socket_data_(expected_reads_, 2, &expected_write_, 1) {}
-
-MockSocketData::MockSocketData(const std::vector<char>& write)
- : expected_write_payload_(write),
- expected_write_(net::SYNCHRONOUS,
- expected_write_payload_.data(),
- expected_write_payload_.size(),
- 0),
- expected_reads_{net::MockRead(net::SYNCHRONOUS, net::ERR_IO_PENDING, 1),
- no_more_data_},
- socket_data_(expected_reads_, 2, &expected_write_, 1) {}
-
-MockSocketData::~MockSocketData() {}
-
-void MockSocketData::AddToFactory(
- net::MockClientSocketFactory* socket_factory) {
- socket_factory->AddSocketDataProvider(&socket_data_);
-}
+// A container for all of the data needed for simulating a socket.
+// This is useful because Mock{Read,Write}, SequencedSocketData and
+// MockClientSocketFactory all do not take ownership of or copy their arguments,
+// so it is necessary to manage the lifetime of those arguments. Wrapping all
+// of that up in a single class simplifies this.
+class MockLogDnsTraffic::MockSocketData {
+ public:
+ // A socket that expects one write and one read operation.
+ MockSocketData(const std::vector<char>& write, const std::vector<char>& read)
+ : expected_write_payload_(write),
+ expected_read_payload_(read),
+ expected_write_(net::SYNCHRONOUS,
+ expected_write_payload_.data(),
+ expected_write_payload_.size(),
+ 0),
+ expected_reads_{net::MockRead(net::ASYNC,
+ expected_read_payload_.data(),
+ expected_read_payload_.size(),
+ 1),
+ kNoMoreData},
+ socket_data_(expected_reads_, 2, &expected_write_, 1) {}
+
+ // A socket that expects one write and a read error.
+ MockSocketData(const std::vector<char>& write, net::Error error)
+ : expected_write_payload_(write),
+ expected_write_(net::SYNCHRONOUS,
+ expected_write_payload_.data(),
+ expected_write_payload_.size(),
+ 0),
+ expected_reads_{net::MockRead(net::ASYNC, error, 1), kNoMoreData},
+ socket_data_(expected_reads_, 2, &expected_write_, 1) {}
+
+ // A socket that expects one write and no response.
+ explicit MockSocketData(const std::vector<char>& write)
+ : expected_write_payload_(write),
+ expected_write_(net::SYNCHRONOUS,
+ expected_write_payload_.data(),
+ expected_write_payload_.size(),
+ 0),
+ expected_reads_{net::MockRead(net::SYNCHRONOUS, net::ERR_IO_PENDING, 1),
+ kNoMoreData},
+ socket_data_(expected_reads_, 2, &expected_write_, 1) {}
+
+ ~MockSocketData() {}
+
+ void SetWriteMode(net::IoMode mode) { expected_write_.mode = mode; }
+ void SetReadMode(net::IoMode mode) { expected_reads_[0].mode = mode; }
+
+ void AddToFactory(net::MockClientSocketFactory* socket_factory) {
+ socket_factory->AddSocketDataProvider(&socket_data_);
+ }
-const net::MockRead MockSocketData::no_more_data_(net::SYNCHRONOUS,
- net::ERR_IO_PENDING,
- 2);
+ private:
+ // This class only supports one write and one read, so just need to store one
+ // payload each.
+ const std::vector<char> expected_write_payload_;
+ const std::vector<char> expected_read_payload_;
-} // namespace internal
+ // Encapsulates the data that is expected to be written to a socket.
+ net::MockWrite expected_write_;
-using internal::MockSocketData;
+ // Encapsulates the data/error that should be returned when reading from a
+ // socket. The second "expected" read is a sentinel (see |kNoMoreData|).
+ net::MockRead expected_reads_[2];
+
+ // Holds pointers to |expected_write_| and |expected_reads_|. This is what is
+ // added to net::MockClientSocketFactory to prepare a mock socket.
+ net::SequencedSocketData socket_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockSocketData);
+};
MockLogDnsTraffic::MockLogDnsTraffic() : socket_read_mode_(net::ASYNC) {}
MockLogDnsTraffic::~MockLogDnsTraffic() {}
-void MockLogDnsTraffic::ExpectRequestAndErrorResponse(base::StringPiece qname,
+bool MockLogDnsTraffic::ExpectRequestAndErrorResponse(base::StringPiece qname,
uint8_t rcode) {
- std::vector<char> request = CreateDnsTxtRequest(qname);
- EmplaceMockSocketData(CreateDnsTxtRequest(qname),
- CreateDnsErrorResponse(request, rcode));
+ std::vector<char> request;
+ if (!CreateDnsTxtRequest(qname, &request)) {
+ return false;
+ }
+
+ std::vector<char> response;
+ if (!CreateDnsErrorResponse(request, rcode, &response)) {
+ return false;
+ }
+
+ EmplaceMockSocketData(request, response);
+ return true;
}
-void MockLogDnsTraffic::ExpectRequestAndSocketError(base::StringPiece qname,
- int net_error) {
- EmplaceMockSocketData(CreateDnsTxtRequest(qname), net_error);
+bool MockLogDnsTraffic::ExpectRequestAndSocketError(base::StringPiece qname,
+ net::Error error) {
+ std::vector<char> request;
+ if (!CreateDnsTxtRequest(qname, &request)) {
+ return false;
+ }
+
+ EmplaceMockSocketData(request, error);
+ return true;
}
-void MockLogDnsTraffic::ExpectRequestAndTimeout(base::StringPiece qname) {
- EmplaceMockSocketData(CreateDnsTxtRequest(qname));
+bool MockLogDnsTraffic::ExpectRequestAndTimeout(base::StringPiece qname) {
+ std::vector<char> request;
+ if (!CreateDnsTxtRequest(qname, &request)) {
+ return false;
+ }
+
+ EmplaceMockSocketData(request);
// Speed up timeout tests.
SetDnsTimeout(TestTimeouts::tiny_timeout());
+
+ return true;
}
-void MockLogDnsTraffic::ExpectRequestAndResponse(
+bool MockLogDnsTraffic::ExpectRequestAndResponse(
base::StringPiece qname,
const std::vector<base::StringPiece>& txt_strings) {
std::string answer;
@@ -205,17 +269,27 @@ void MockLogDnsTraffic::ExpectRequestAndResponse(
str.AppendToString(&answer);
}
- std::vector<char> request = CreateDnsTxtRequest(qname);
- EmplaceMockSocketData(request, CreateDnsTxtResponse(request, answer));
+ std::vector<char> request;
+ if (!CreateDnsTxtRequest(qname, &request)) {
+ return false;
+ }
+
+ std::vector<char> response;
+ if (!CreateDnsTxtResponse(request, answer, &response)) {
+ return false;
+ }
+
+ EmplaceMockSocketData(request, response);
+ return true;
}
-void MockLogDnsTraffic::ExpectLeafIndexRequestAndResponse(
+bool MockLogDnsTraffic::ExpectLeafIndexRequestAndResponse(
base::StringPiece qname,
uint64_t leaf_index) {
- ExpectRequestAndResponse(qname, { base::Uint64ToString(leaf_index) });
+ return ExpectRequestAndResponse(qname, {base::Uint64ToString(leaf_index)});
}
-void MockLogDnsTraffic::ExpectAuditProofRequestAndResponse(
+bool MockLogDnsTraffic::ExpectAuditProofRequestAndResponse(
base::StringPiece qname,
std::vector<std::string>::const_iterator audit_path_start,
std::vector<std::string>::const_iterator audit_path_end) {
@@ -223,7 +297,7 @@ void MockLogDnsTraffic::ExpectAuditProofRequestAndResponse(
std::string proof =
std::accumulate(audit_path_start, audit_path_end, std::string());
- ExpectRequestAndResponse(qname, { proof });
+ return ExpectRequestAndResponse(qname, {proof});
}
void MockLogDnsTraffic::InitializeDnsConfig() {
diff --git a/chromium/components/certificate_transparency/mock_log_dns_traffic.h b/chromium/components/certificate_transparency/mock_log_dns_traffic.h
index 2496737f864..7ce4dfeee81 100644
--- a/chromium/components/certificate_transparency/mock_log_dns_traffic.h
+++ b/chromium/components/certificate_transparency/mock_log_dns_traffic.h
@@ -11,6 +11,7 @@
#include <string>
#include <vector>
+#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/strings/string_piece.h"
#include "net/dns/dns_client.h"
@@ -19,62 +20,28 @@
namespace certificate_transparency {
-namespace internal {
-
-// A container for all of the data we need to keep alive for a mock socket.
-// This is useful because Mock{Read,Write}, SequencedSocketData and
-// MockClientSocketFactory all do not take ownership of or copy their arguments,
-// so we have to manage the lifetime of those arguments ourselves. Wrapping all
-// of that up in a single class simplifies this.
-// This cannot be forward declared because MockLogDnsTraffic has a
-// vector<unique_ptr<MockSocketData>> member, which requires MockSocketData be
-// defined.
-class MockSocketData {
- public:
- // A socket that expects one write and one read operation.
- MockSocketData(const std::vector<char>& write, const std::vector<char>& read);
- // A socket that expects one write and a read error.
- MockSocketData(const std::vector<char>& write, int net_error);
- // A socket that expects one write and no response.
- explicit MockSocketData(const std::vector<char>& write);
-
- ~MockSocketData();
-
- void SetWriteMode(net::IoMode mode) { expected_write_.mode = mode; }
- void SetReadMode(net::IoMode mode) { expected_reads_[0].mode = mode; }
-
- void AddToFactory(net::MockClientSocketFactory* socket_factory);
-
- private:
- // Prevents read overruns and makes a socket timeout the default behaviour.
- static const net::MockRead no_more_data_;
-
- // This class only supports one write and one read, so just need to store one
- // payload each.
- const std::vector<char> expected_write_payload_;
- const std::vector<char> expected_read_payload_;
- // Encapsulates the data that is expected to be written to a socket.
- net::MockWrite expected_write_;
- // Encapsulates the data/error that should be returned when reading from a
- // socket. The second "expected" read is always |no_more_data_|, which
- // causes the socket read to hang until it times out. This results in better
- // test failure messages (rather than a CHECK-fail due to a socket read
- // overrunning the MockRead array) and behaviour more like a real socket when
- // an unexpected second socket read occurs.
- net::MockRead expected_reads_[2];
- // Holds pointers to |expected_write_| and |expected_reads_|. This is what is
- // added to net::MockClientSocketFactory to prepare a mock socket.
- net::SequencedSocketData socket_data_;
-
- DISALLOW_COPY_AND_ASSIGN(MockSocketData);
-};
-
-} // namespace internal
-
// Mocks DNS requests and responses for a Certificate Transparency (CT) log.
// This is implemented using mock sockets. Call the CreateDnsClient() method to
// get a net::DnsClient wired up to these mock sockets.
// The Expect*() methods must be called from within a GTest test case.
+//
+// Example Usage:
+// // net::DnsClient requires an I/O message loop for async operations.
+// base::MessageLoopForIO message_loop;
+//
+// // Create a mock NetworkChangeNotifier to propagate DNS config.
+// std::unique_ptr<net::NetworkChangeNotifier> net_change_notifier(
+// net::NetworkChangeNotifier::CreateMock());
+//
+// MockLogDnsTraffic mock_dns;
+// mock_dns.InitializeDnsConfig();
+// // Use the Expect* methods to define expected DNS requests and responses.
+// mock_dns.ExpectLeafIndexRequestAndResponse(
+// "D4S6DSV2J743QJZEQMH4UYHEYK7KRQ5JIQOCPMFUHZVJNFGHXACA.hash.ct.test.",
+// "123456");
+//
+// LogDnsClient log_client(mock_dns.CreateDnsClient(), ...);
+// log_client.QueryAuditProof("ct.test", ..., base::Bind(...));
class MockLogDnsTraffic {
public:
MockLogDnsTraffic();
@@ -84,32 +51,48 @@ class MockLogDnsTraffic {
// Such a request will receive a DNS response indicating that the error
// specified by |rcode| occurred. See RFC1035, Section 4.1.1 for |rcode|
// values.
- void ExpectRequestAndErrorResponse(base::StringPiece qname, uint8_t rcode);
+ // Returns false if any of the arguments are invalid.
+ WARN_UNUSED_RESULT
+ bool ExpectRequestAndErrorResponse(base::StringPiece qname, uint8_t rcode);
+
// Expect a CT DNS request for the domain |qname|.
- // Such a request will trigger a socket error of type |net_error|.
- // |net_error| can be any net:Error value.
- void ExpectRequestAndSocketError(base::StringPiece qname, int net_error);
+ // Such a request will trigger a socket error of type |error|.
+ // Returns false if any of the arguments are invalid.
+ WARN_UNUSED_RESULT
+ bool ExpectRequestAndSocketError(base::StringPiece qname, net::Error error);
+
// Expect a CT DNS request for the domain |qname|.
// Such a request will timeout.
// This will reduce the DNS timeout to minimize test duration.
- void ExpectRequestAndTimeout(base::StringPiece qname);
+ // Returns false if |qname| is invalid.
+ WARN_UNUSED_RESULT
+ bool ExpectRequestAndTimeout(base::StringPiece qname);
+
// Expect a CT DNS request for the domain |qname|.
// Such a request will receive a DNS TXT response containing |txt_strings|.
- void ExpectRequestAndResponse(
- base::StringPiece qname,
- const std::vector<base::StringPiece>& txt_strings);
+ // Returns false if any of the arguments are invalid.
+ WARN_UNUSED_RESULT
+ bool ExpectRequestAndResponse(
+ base::StringPiece qname,
+ const std::vector<base::StringPiece>& txt_strings);
+
// Expect a CT DNS request for the domain |qname|.
// Such a request will receive a DNS response containing |leaf_index|.
// A description of such a request and response can be seen here:
// https://github.com/google/certificate-transparency-rfcs/blob/c8844de6bd0b5d3d16bac79865e6edef533d760b/dns/draft-ct-over-dns.md#hash-query-hashquery
- void ExpectLeafIndexRequestAndResponse(base::StringPiece qname,
+ // Returns false if any of the arguments are invalid.
+ WARN_UNUSED_RESULT
+ bool ExpectLeafIndexRequestAndResponse(base::StringPiece qname,
uint64_t leaf_index);
+
// Expect a CT DNS request for the domain |qname|.
// Such a request will receive a DNS response containing the inclusion proof
// nodes between |audit_path_start| and |audit_path_end|.
// A description of such a request and response can be seen here:
// https://github.com/google/certificate-transparency-rfcs/blob/c8844de6bd0b5d3d16bac79865e6edef533d760b/dns/draft-ct-over-dns.md#tree-query-treequery
- void ExpectAuditProofRequestAndResponse(
+ // Returns false if any of the arguments are invalid.
+ WARN_UNUSED_RESULT
+ bool ExpectAuditProofRequestAndResponse(
base::StringPiece qname,
std::vector<std::string>::const_iterator audit_path_start,
std::vector<std::string>::const_iterator audit_path_end);
@@ -130,12 +113,19 @@ class MockLogDnsTraffic {
// It is this DNS client that the expectations will be tested against.
std::unique_ptr<net::DnsClient> CreateDnsClient();
+ private:
+ // Allows tests to change socket read mode. Only the LogDnsClient tests should
+ // need to do so, to ensure consistent behaviour regardless of mode.
+ friend class LogDnsClientTest;
+
+ class MockSocketData;
+
// Sets whether mock reads should complete synchronously or asynchronously.
+ // By default, they complete asynchronously.
void SetSocketReadMode(net::IoMode read_mode) {
socket_read_mode_ = read_mode;
}
- private:
// Constructs MockSocketData from |args| and adds it to |socket_factory_|.
template <typename... Args>
void EmplaceMockSocketData(Args&&... args);
@@ -148,7 +138,7 @@ class MockLogDnsTraffic {
// One MockSocketData for each socket that is created. This corresponds to one
// for each DNS request sent.
- std::vector<std::unique_ptr<internal::MockSocketData>> mock_socket_data_;
+ std::vector<std::unique_ptr<MockSocketData>> mock_socket_data_;
// Provides as many mock sockets as there are entries in |mock_socket_data_|.
net::MockClientSocketFactory socket_factory_;
// Controls whether mock socket reads are asynchronous.