| 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..8b45c34b4a71bbc972ea4b026379e07fa5993124
|
| --- /dev/null
|
| +++ b/components/certificate_transparency/log_dns_client.cc
|
| @@ -0,0 +1,284 @@
|
| +// 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 "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,
|
| + base::Clock* clock)
|
| + : dns_client_(std::move(dns_client)),
|
| + net_log_(net_log),
|
| + clock_(clock),
|
| + weak_ptr_factory_(this) {
|
| + CHECK(dns_client_);
|
| + CHECK(clock_);
|
| +}
|
| +
|
| +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;
|
| + 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,
|
| + 0, callback);
|
| +}
|
| +
|
| +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) {
|
| + 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();
|
| + auto parsed_record = net::RecordParsed::CreateFrom(&parser, clock_->Now());
|
| + 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))
|
| + 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)
|
| + proof->nodes.push_back(node);
|
| + else
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +} // namespace certificate_transparency
|
|
|