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 |
new file mode 100644 |
index 0000000000000000000000000000000000000000..dc63754f52cb1de5a795bd2937aaafe74327ce1d |
--- /dev/null |
+++ b/components/certificate_transparency/log_dns_client.cc |
@@ -0,0 +1,285 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "components/certificate_transparency/log_dns_client.h" |
+ |
+#include "base/bind.h" |
+#include "base/location.h" |
+#include "base/logging.h" |
+#include "base/strings/string_number_conversions.h" |
+#include "base/strings/string_util.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_protocol.h" |
+#include "net/dns/dns_response.h" |
+#include "net/dns/dns_transaction.h" |
+#include "net/dns/record_parsed.h" |
+#include "net/dns/record_rdata.h" |
+ |
+namespace certificate_transparency { |
+ |
+LogDnsClient::LogDnsClient(std::unique_ptr<net::DnsClient> dns_client, |
+ const net::BoundNetLog& net_log) |
+ : dns_client_(std::move(dns_client)), |
+ net_log_(net_log), |
+ weak_ptr_factory_(this) { |
+ CHECK(dns_client_); |
+} |
+ |
+LogDnsClient::~LogDnsClient() {} |
+ |
+void LogDnsClient::QueryLeafIndex(base::StringPiece domain_for_log, |
+ base::StringPiece leaf_hash, |
+ const LeafIndexCallback& callback) { |
+ if (leaf_hash.size() != crypto::kSHA256Length) { |
+ base::ThreadTaskRunnerHandle::Get()->PostTask( |
+ FROM_HERE, base::Bind(callback, net::Error::ERR_INVALID_ARGUMENT, 0)); |
+ return; |
+ } |
+ |
+ std::string encoded_leaf_hash = |
+ base32::Base32Encode(leaf_hash, base32::Base32EncodePolicy::OMIT_PADDING); |
+ DCHECK_EQ(encoded_leaf_hash.size(), 52u); |
+ std::string qname = encoded_leaf_hash; |
Eran Messeri
2016/07/01 09:08:00
Nit: Use StringPrintf
Rob Percival
2016/07/01 16:01:01
I've opted for using std::ostringstream instead, a
|
+ qname += ".hash."; |
+ domain_for_log.AppendToString(&qname); |
+ qname += "."; |
+ |
+ 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; |
+ } |
+ |
+ net::DnsTransactionFactory::CallbackType transaction_callback = base::Bind( |
+ &LogDnsClient::QueryLeafIndexComplete, weak_ptr_factory_.GetWeakPtr()); |
+ |
+ std::unique_ptr<net::DnsTransaction> dns_transaction = |
+ factory->CreateTransaction(qname, net::dns_protocol::kTypeTXT, |
+ transaction_callback, net_log_); |
+ |
+ dns_transaction->Start(); |
+ leaf_index_queries_.push_back({std::move(dns_transaction), callback}); |
+} |
+ |
+// 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 (leaf_index >= tree_size) { |
+ base::ThreadTaskRunnerHandle::Get()->PostTask( |
+ FROM_HERE, |
+ base::Bind(callback, net::Error::ERR_INVALID_ARGUMENT, nullptr)); |
+ return; |
+ } |
+ |
+ std::unique_ptr<net::ct::MerkleAuditProof> proof( |
+ new net::ct::MerkleAuditProof); |
+ proof->leaf_index = leaf_index; |
+ |
+ QueryAuditProofNodes(std::move(proof), domain_for_log, leaf_index, tree_size, |
Eran Messeri
2016/07/01 09:07:59
You could drop the leaf_index from the parameters
Rob Percival
2016/07/01 16:01:01
Done.
|
+ 0, callback); |
Eran Messeri
2016/07/01 09:08:00
comment what the 0 stands for, e.g.:
0 /* first pr
Rob Percival
2016/07/01 16:01:01
Done.
|
+} |
+ |
+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; |
+ } |
+ const Query<LeafIndexCallback> query = std::move(*query_iterator); |
+ leaf_index_queries_.erase(query_iterator); |
+ |
+ if (net_error != net::OK) { |
+ base::ThreadTaskRunnerHandle::Get()->PostTask( |
+ FROM_HERE, base::Bind(query.callback, net_error, 0)); |
+ return; |
+ } |
+ |
+ if (response == nullptr) { |
Eran Messeri
2016/07/01 09:08:00
Nit: You could move this condition up (to line 115
Rob Percival
2016/07/01 16:01:01
That could hide a more specific net_error though.
|
+ base::ThreadTaskRunnerHandle::Get()->PostTask( |
+ FROM_HERE, base::Bind(query.callback, net::ERR_INVALID_RESPONSE, 0)); |
+ return; |
+ } |
+ |
+ 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; |
+ } |
+ |
+ base::ThreadTaskRunnerHandle::Get()->PostTask( |
+ FROM_HERE, base::Bind(query.callback, net::OK, leaf_index)); |
+} |
+ |
+void LogDnsClient::QueryAuditProofNodes( |
+ std::unique_ptr<net::ct::MerkleAuditProof> proof, |
+ base::StringPiece domain_for_log, |
+ uint64_t leaf_index, |
+ uint64_t tree_size, |
+ uint64_t node_index, |
+ const AuditProofCallback& callback) { |
+ CHECK(proof); |
+ CHECK_LT(leaf_index, tree_size); |
+ CHECK_LT(node_index, |
+ net::ct::CalculateAuditPathLength(leaf_index, tree_size)); |
+ |
+ std::string qname = base::Uint64ToString(node_index); |
+ qname += "."; |
+ qname += base::Uint64ToString(leaf_index); |
+ qname += "."; |
+ qname += base::Uint64ToString(tree_size); |
+ qname += ".tree."; |
+ domain_for_log.AppendToString(&qname); |
+ qname += "."; |
+ |
+ 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; |
+ } |
+ |
+ net::DnsTransactionFactory::CallbackType transaction_callback = |
+ base::Bind(&LogDnsClient::QueryAuditProofNodesComplete, |
+ weak_ptr_factory_.GetWeakPtr(), base::Passed(std::move(proof)), |
+ domain_for_log, leaf_index, tree_size); |
+ |
+ std::unique_ptr<net::DnsTransaction> dns_transaction = |
+ factory->CreateTransaction(qname, net::dns_protocol::kTypeTXT, |
+ transaction_callback, net_log_); |
+ dns_transaction->Start(); |
+ audit_proof_queries_.push_back({std::move(dns_transaction), callback}); |
+} |
+ |
+void LogDnsClient::QueryAuditProofNodesComplete( |
+ std::unique_ptr<net::ct::MerkleAuditProof> proof, |
+ base::StringPiece domain_for_log, |
+ uint64_t leaf_index, |
+ uint64_t tree_size, |
+ net::DnsTransaction* transaction, |
+ int net_error, |
+ const net::DnsResponse* response) { |
+ auto query_iterator = |
+ std::find_if(audit_proof_queries_.begin(), audit_proof_queries_.end(), |
+ [transaction](const Query<AuditProofCallback>& query) { |
+ return query.transaction.get() == transaction; |
+ }); |
+ |
+ if (query_iterator == audit_proof_queries_.end()) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ const Query<AuditProofCallback> query = std::move(*query_iterator); |
+ audit_proof_queries_.erase(query_iterator); |
+ |
+ if (net_error != net::OK) { |
+ base::ThreadTaskRunnerHandle::Get()->PostTask( |
+ FROM_HERE, base::Bind(query.callback, net_error, nullptr)); |
+ return; |
+ } |
+ |
+ if (response == nullptr) { |
+ base::ThreadTaskRunnerHandle::Get()->PostTask( |
+ FROM_HERE, |
+ base::Bind(query.callback, net::ERR_INVALID_RESPONSE, nullptr)); |
+ return; |
+ } |
+ |
+ const uint64_t audit_path_length = |
+ net::ct::CalculateAuditPathLength(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, leaf_index, |
+ tree_size, audit_path_nodes_received, query.callback); |
+ return; |
+ } |
+ |
+ base::ThreadTaskRunnerHandle::Get()->PostTask( |
+ FROM_HERE, |
+ base::Bind(query.callback, net::OK, base::Passed(std::move(proof)))); |
+} |
+ |
+bool LogDnsClient::ParseTxtResponse(const net::DnsResponse& response, |
+ std::string* txt) { |
+ DCHECK(txt); |
+ |
+ net::DnsRecordParser parser = response.Parser(); |
+ // We don't care about the creation time, since we're going to throw |
+ // |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) |
+ return false; |
+ |
+ auto txt_record = parsed_record->rdata<net::TxtRecordRdata>(); |
+ if (txt_record == nullptr) |
+ return false; |
+ |
+ *txt = base::JoinString(txt_record->texts(), ""); |
+ return true; |
+} |
+ |
+bool LogDnsClient::ParseLeafIndex(const net::DnsResponse& response, |
+ uint64_t* index) { |
+ std::string index_str; |
+ if (!ParseTxtResponse(response, &index_str)) |
+ return false; |
+ |
+ return base::StringToUint64(index_str, index); |
+} |
+ |
+bool LogDnsClient::ParseAuditPath(const net::DnsResponse& response, |
+ net::ct::MerkleAuditProof* proof) { |
+ std::string audit_path; |
+ if (!ParseTxtResponse(response, &audit_path)) |
Eran Messeri
2016/07/01 09:07:59
Also return false if the audit_path is of length 0
Rob Percival
2016/07/01 16:01:01
Done.
|
+ return false; |
+ |
+ for (size_t i = 0; i < audit_path.size(); i += crypto::kSHA256Length) { |
+ std::string node = audit_path.substr(i, crypto::kSHA256Length); |
+ |
+ if (node.size() == crypto::kSHA256Length) |
Eran Messeri
2016/07/01 09:08:00
You could check beforehand:
if (audit_path.size()
Rob Percival
2016/07/01 16:01:01
Done.
|
+ proof->nodes.push_back(node); |
+ else |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+} // namespace certificate_transparency |