diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-03-08 10:28:10 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-03-20 13:40:30 +0000 |
commit | e733310db58160074f574c429d48f8308c0afe17 (patch) | |
tree | f8aef4b7e62a69928dbcf880620eece20f98c6df /chromium/components/certificate_transparency | |
parent | 2f583e4aec1ae3a86fa047829c96b310dc12ecdf (diff) | |
download | qtwebengine-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')
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. |