Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1618)

Unified Diff: components/certificate_transparency/log_dns_client.cc

Issue 2369373002: LogDnsClient now returns some errors synchronously (Closed)
Patch Set: Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: components/certificate_transparency/log_dns_client.cc
diff --git a/components/certificate_transparency/log_dns_client.cc b/components/certificate_transparency/log_dns_client.cc
index 610e5b96ce263f98b196144b5cac0eb5c99b26ab..a8a35974abdb47edc692569d0338f204b7bea167 100644
--- a/components/certificate_transparency/log_dns_client.cc
+++ b/components/certificate_transparency/log_dns_client.cc
@@ -93,179 +93,250 @@ class LogDnsClient::AuditProofQuery {
// to leave ownership of |dns_client| with LogDnsClient.
AuditProofQuery(net::DnsClient* dns_client,
const std::string& domain_for_log,
uint64_t tree_size,
const net::BoundNetLog& net_log)
- : domain_for_log_(domain_for_log),
+ : next_state_(State::NONE),
+ domain_for_log_(domain_for_log),
tree_size_(tree_size),
dns_client_(dns_client),
net_log_(net_log),
weak_ptr_factory_(this) {
DCHECK(dns_client_);
DCHECK(!domain_for_log_.empty());
}
// Start the query.
- void Start(base::StringPiece leaf_hash, CompletionCallback callback) {
+ // If a query was already in progress, this will abort it first.
Eran Messeri 2016/09/28 13:44:08 How often do you expect that case to happen? If no
Rob Percival 2016/09/28 16:19:13 It's not the intended usage (there's little reason
+ int Start(base::StringPiece leaf_hash, CompletionCallback callback) {
+ // Cancel any existing DNS transaction.
current_dns_transaction_.reset();
+ // Start a new audit proof.
proof_.reset(new net::ct::MerkleAuditProof);
+ // The leaf hash and callback will be used later.
+ leaf_hash.CopyToString(&leaf_hash_);
callback_ = callback;
- QueryLeafIndex(leaf_hash);
+ // 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);
}
+ // Take the audit proof obtained by the query.
+ // Should only be called once the CompletionCallback is invoked.
std::unique_ptr<net::ct::MerkleAuditProof> TakeProof() {
return std::move(proof_);
}
private:
- void QueryLeafIndex(base::StringPiece leaf_hash) {
+ enum class State {
+ NONE,
+ REQUEST_LEAF_INDEX,
+ REQUEST_LEAF_INDEX_COMPLETE,
+ REQUEST_AUDIT_PROOF_NODES,
+ REQUEST_AUDIT_PROOF_NODES_COMPLETE,
+ };
+
+ int DoLoop(int net_error) {
+ CHECK_NE(State::NONE, next_state_);
+ do {
+ State state = next_state_;
+ next_state_ = State::NONE;
+ switch (state) {
+ case State::REQUEST_LEAF_INDEX:
+ net_error = RequestLeafIndex();
+ break;
+ case State::REQUEST_LEAF_INDEX_COMPLETE:
+ net_error = RequestLeafIndexComplete(net_error);
+ break;
+ case State::REQUEST_AUDIT_PROOF_NODES:
+ net_error = RequestAuditProofNodes();
+ break;
+ case State::REQUEST_AUDIT_PROOF_NODES_COMPLETE:
+ net_error = RequestAuditProofNodesComplete(net_error);
+ break;
+ case State::NONE:
+ NOTREACHED();
+ break;
+ }
+ } while (net_error != net::ERR_IO_PENDING && next_state_ != State::NONE);
+
+ return net_error;
+ }
+
+ // 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) {
+ last_dns_response_ = response;
+ net_error = DoLoop(net_error);
+
+ if (net_error != net::ERR_IO_PENDING) {
Eran Messeri 2016/09/28 13:44:08 Nit: Document why not doing anything in case of ne
Rob Percival 2016/09/28 16:19:13 Done.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback_, net_error, base::Unretained(this)));
+ }
+ }
+
+ // Requests the leaf index for the CT log entry with |leaf_hash_|.
+ int RequestLeafIndex() {
std::string encoded_leaf_hash = base32::Base32Encode(
- leaf_hash, base32::Base32EncodePolicy::OMIT_PADDING);
+ leaf_hash_, base32::Base32EncodePolicy::OMIT_PADDING);
DCHECK_EQ(encoded_leaf_hash.size(), 52u);
- std::string qname = base::StringPrintf(
- "%s.hash.%s.", encoded_leaf_hash.c_str(), domain_for_log_.data());
-
net::DnsTransactionFactory* factory = dns_client_->GetTransactionFactory();
if (factory == nullptr) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- base::Bind(callback_, net::Error::ERR_NAME_RESOLUTION_FAILED,
- base::Unretained(this)));
- return;
+ return net::ERR_NAME_RESOLUTION_FAILED;
}
+ std::string qname = base::StringPrintf(
+ "%s.hash.%s.", encoded_leaf_hash.c_str(), domain_for_log_.c_str());
+
net::DnsTransactionFactory::CallbackType transaction_callback =
Eran Messeri 2016/09/28 13:44:08 Inline transaction_callback creation & use into Cr
Rob Percival 2016/09/28 16:19:13 Done.
- base::Bind(&LogDnsClient::AuditProofQuery::QueryLeafIndexComplete,
+ base::Bind(&LogDnsClient::AuditProofQuery::OnDnsTransactionComplete,
weak_ptr_factory_.GetWeakPtr());
current_dns_transaction_ = factory->CreateTransaction(
qname, net::dns_protocol::kTypeTXT, transaction_callback, net_log_);
-
current_dns_transaction_->Start();
+
+ next_state_ = State::REQUEST_LEAF_INDEX_COMPLETE;
+ return net::ERR_IO_PENDING;
}
- void QueryLeafIndexComplete(net::DnsTransaction* transaction,
- int net_error,
- const net::DnsResponse* response) {
- // If we've received no response but no net::error either (shouldn't
- // happen),
+ // Stores the received leaf index in |proof_->leaf_index|.
+ // If successful, the audit proof nodes will be requested next.
+ int RequestLeafIndexComplete(int net_error) {
+ // If we receive no response but no net::error either (shouldn't happen),
Eran Messeri 2016/09/28 13:44:08 "shouldn't happen" implies this is a programming e
Rob Percival 2016/09/28 16:19:13 Done.
// report the response as invalid.
- if (response == nullptr && net_error == net::OK) {
+ if (last_dns_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(callback_, net_error, base::Unretained(this)));
- return;
+ return net_error;
}
- if (!ParseLeafIndex(*response, &proof_->leaf_index)) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(callback_, net::ERR_DNS_MALFORMED_RESPONSE,
- base::Unretained(this)));
- return;
+ if (!ParseLeafIndex(*last_dns_response_, &proof_->leaf_index)) {
+ return net::ERR_DNS_MALFORMED_RESPONSE;
}
// 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 >= tree_size_) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(callback_, net::ERR_INVALID_ARGUMENT,
- base::Unretained(this)));
- return;
+ return net::ERR_INVALID_ARGUMENT;
}
- // QueryAuditProof for the first batch of audit proof_ nodes (i.e. starting
- // from 0).
- QueryAuditProofNodes(0 /* start node index */);
+ next_state_ = State::REQUEST_AUDIT_PROOF_NODES;
+ return net::OK;
}
- // 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(uint64_t node_index) {
+ // 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.
+ int RequestAuditProofNodes() {
DCHECK_LT(proof_->leaf_index, tree_size_);
- DCHECK_LT(node_index, net::ct::CalculateAuditPathLength(proof_->leaf_index,
- tree_size_));
-
- std::string qname = base::StringPrintf(
- "%" PRIu64 ".%" PRIu64 ".%" PRIu64 ".tree.%s.", node_index,
- proof_->leaf_index, tree_size_, domain_for_log_.data());
+ DCHECK_LT(proof_->nodes.size(), 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,
- base::Unretained(this)));
- return;
+ return net::ERR_NAME_RESOLUTION_FAILED;
}
+ std::string qname = base::StringPrintf(
+ "%" PRIu64 ".%" PRIu64 ".%" PRIu64 ".tree.%s.", proof_->nodes.size(),
+ proof_->leaf_index, tree_size_, domain_for_log_.c_str());
+
net::DnsTransactionFactory::CallbackType transaction_callback =
Eran Messeri 2016/09/28 13:44:08 Ditto, inline callback creation.
Rob Percival 2016/09/28 16:19:13 Done.
- base::Bind(&LogDnsClient::AuditProofQuery::QueryAuditProofNodesComplete,
+ base::Bind(&LogDnsClient::AuditProofQuery::OnDnsTransactionComplete,
weak_ptr_factory_.GetWeakPtr());
current_dns_transaction_ = factory->CreateTransaction(
qname, net::dns_protocol::kTypeTXT, transaction_callback, net_log_);
current_dns_transaction_->Start();
+
+ next_state_ = State::REQUEST_AUDIT_PROOF_NODES_COMPLETE;
+ return net::ERR_IO_PENDING;
}
- void QueryAuditProofNodesComplete(net::DnsTransaction* transaction,
- int net_error,
- const net::DnsResponse* response) {
- // If we receive no response but no net::error either (shouldn't happen),
+ // Appends the received audit proof nodes to |proof_->nodes|.
+ // If any nodes are missing, another request will follow this one.
+ int RequestAuditProofNodesComplete(int net_error) {
+ // If we received no response but no net::error either (shouldn't happen),
// report the response as invalid.
- if (response == nullptr && net_error == net::OK) {
+ if (last_dns_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(callback_, net_error, base::Unretained(this)));
- return;
+ return net_error;
}
const uint64_t audit_path_length =
net::ct::CalculateAuditPathLength(proof_->leaf_index, 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);
- if (!ParseAuditPath(*response, proof_.get())) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(callback_, net::ERR_DNS_MALFORMED_RESPONSE,
- base::Unretained(this)));
- return;
+ if (!ParseAuditPath(*last_dns_response_, proof_.get())) {
+ return net::ERR_DNS_MALFORMED_RESPONSE;
}
- const uint64_t audit_path_nodes_received = proof_->nodes.size();
- if (audit_path_nodes_received < audit_path_length) {
- QueryAuditProofNodes(audit_path_nodes_received);
- return;
+ // If we don't have all of the proof nodes yet, request more.
+ if (proof_->nodes.size() < audit_path_length) {
+ next_state_ = State::REQUEST_AUDIT_PROOF_NODES;
}
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(callback_, net::OK, base::Unretained(this)));
+ return net::OK;
}
+ // 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 size of the CT log's tree.
Eran Messeri 2016/09/28 13:44:08 Add: from which the proof is requested.
// TODO(robpercival): Remove |tree_size| once |proof_| has a tree_size member.
uint64_t tree_size_;
+ // The audit proof. It will be null until the query is started and incomplete
+ // until the query is finished.
std::unique_ptr<net::ct::MerkleAuditProof> proof_;
+ // The callback to invoke when the query is complete.
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::BoundNetLog net_log_;
+ // Produces WeakPtrs to |this| for binding callbacks.
base::WeakPtrFactory<AuditProofQuery> weak_ptr_factory_;
};
LogDnsClient::LogDnsClient(std::unique_ptr<net::DnsClient> dns_client,
const net::BoundNetLog& net_log,
@@ -289,46 +360,32 @@ void LogDnsClient::OnDNSChanged() {
void LogDnsClient::OnInitialDNSConfigRead() {
UpdateDnsConfig();
}
-// 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. Throttling of queries would also need
-// to take into account this increase in parallelism.
-void LogDnsClient::QueryAuditProof(const std::string& domain_for_log,
- base::StringPiece leaf_hash,
- uint64_t tree_size,
- const AuditProofCallback& callback) {
+int LogDnsClient::QueryAuditProof(base::StringPiece domain_for_log,
+ base::StringPiece leaf_hash,
+ uint64_t tree_size,
+ const AuditProofCallback& 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, nullptr));
- return;
+ return net::ERR_INVALID_ARGUMENT;
}
if (HasMaxConcurrentQueriesInProgress()) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- base::Bind(callback, net::Error::ERR_TEMPORARILY_THROTTLED, nullptr));
- return;
+ return net::ERR_TEMPORARILY_THROTTLED;
}
- audit_proof_queries_.emplace_back(
- new AuditProofQuery(dns_client_.get(), domain_for_log, tree_size));
+ AuditProofQuery* query = new AuditProofQuery(
+ dns_client_.get(), domain_for_log.as_string(), tree_size, net_log_);
+ // Transfers ownership of |query| to |audit_proof_queries_|.
+ audit_proof_queries_.emplace_back(query);
AuditProofQuery::CompletionCallback internal_callback =
base::Bind(&LogDnsClient::QueryAuditProofComplete,
weak_ptr_factory_.GetWeakPtr(), callback);
- audit_proof_queries_.back()->Start(leaf_hash, internal_callback);
+ return query->Start(leaf_hash, internal_callback);
}
void LogDnsClient::QueryAuditProofComplete(const AuditProofCallback& callback,
int result,
AuditProofQuery* query) {

Powered by Google App Engine
This is Rietveld 408576698