OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "components/certificate_transparency/log_dns_client.h" | |
6 | |
7 #include <sstream> | |
8 | |
9 #include "base/bind.h" | |
10 #include "base/location.h" | |
11 #include "base/logging.h" | |
12 #include "base/strings/string_number_conversions.h" | |
13 #include "base/strings/string_util.h" | |
14 #include "base/threading/thread_task_runner_handle.h" | |
15 #include "base/time/time.h" | |
16 #include "components/base32/base32.h" | |
17 #include "crypto/sha2.h" | |
18 #include "net/base/net_errors.h" | |
19 #include "net/cert/merkle_audit_proof.h" | |
20 #include "net/dns/dns_client.h" | |
21 #include "net/dns/dns_protocol.h" | |
22 #include "net/dns/dns_response.h" | |
23 #include "net/dns/dns_transaction.h" | |
24 #include "net/dns/record_parsed.h" | |
25 #include "net/dns/record_rdata.h" | |
26 | |
27 namespace certificate_transparency { | |
28 | |
29 LogDnsClient::LogDnsClient(std::unique_ptr<net::DnsClient> dns_client, | |
30 const net::BoundNetLog& net_log) | |
31 : dns_client_(std::move(dns_client)), | |
32 net_log_(net_log), | |
33 weak_ptr_factory_(this) { | |
34 CHECK(dns_client_); | |
35 } | |
36 | |
37 LogDnsClient::~LogDnsClient() {} | |
38 | |
39 void LogDnsClient::QueryLeafIndex(base::StringPiece domain_for_log, | |
40 base::StringPiece leaf_hash, | |
41 const LeafIndexCallback& callback) { | |
42 if (domain_for_log.empty() || leaf_hash.size() != crypto::kSHA256Length) { | |
43 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
44 FROM_HERE, base::Bind(callback, net::Error::ERR_INVALID_ARGUMENT, 0)); | |
45 return; | |
46 } | |
47 | |
48 std::string encoded_leaf_hash = | |
49 base32::Base32Encode(leaf_hash, base32::Base32EncodePolicy::OMIT_PADDING); | |
50 DCHECK_EQ(encoded_leaf_hash.size(), 52u); | |
51 | |
52 std::ostringstream qname; | |
Eran Messeri
2016/07/05 15:28:12
Nit (here and below): You could define and fill qn
Rob Percival
2016/07/05 15:51:30
Done.
| |
53 qname << encoded_leaf_hash << ".hash." << domain_for_log << "."; | |
54 | |
55 net::DnsTransactionFactory* factory = dns_client_->GetTransactionFactory(); | |
56 if (factory == nullptr) { | |
57 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
58 FROM_HERE, | |
59 base::Bind(callback, net::Error::ERR_NAME_RESOLUTION_FAILED, 0)); | |
60 return; | |
61 } | |
62 | |
63 net::DnsTransactionFactory::CallbackType transaction_callback = base::Bind( | |
64 &LogDnsClient::QueryLeafIndexComplete, weak_ptr_factory_.GetWeakPtr()); | |
65 | |
66 std::unique_ptr<net::DnsTransaction> dns_transaction = | |
67 factory->CreateTransaction(qname.str(), net::dns_protocol::kTypeTXT, | |
68 transaction_callback, net_log_); | |
69 | |
70 dns_transaction->Start(); | |
71 leaf_index_queries_.push_back({std::move(dns_transaction), callback}); | |
72 } | |
73 | |
74 // The performance of this could be improved by sending all of the expected | |
75 // queries up front. Each response can contain a maximum of 7 audit path nodes, | |
76 // so for an audit proof of size 20, it could send 3 queries (for nodes 0-6, | |
77 // 7-13 and 14-19) immediately. Currently, it sends only the first and then, | |
78 // based on the number of nodes received, sends the next query. The complexity | |
79 // of the code would increase though, as it would need to detect gaps in the | |
80 // audit proof caused by the server not responding with the anticipated number | |
81 // of nodes. Ownership of the proof would need to change, as it would be shared | |
82 // between simultaneous DNS transactions. | |
83 void LogDnsClient::QueryAuditProof(base::StringPiece domain_for_log, | |
84 uint64_t leaf_index, | |
85 uint64_t tree_size, | |
86 const AuditProofCallback& callback) { | |
87 if (domain_for_log.empty() || leaf_index >= tree_size) { | |
88 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
89 FROM_HERE, | |
90 base::Bind(callback, net::Error::ERR_INVALID_ARGUMENT, nullptr)); | |
91 return; | |
92 } | |
93 | |
94 std::unique_ptr<net::ct::MerkleAuditProof> proof( | |
95 new net::ct::MerkleAuditProof); | |
96 proof->leaf_index = leaf_index; | |
97 // TODO: Once a "tree_size" field is added to MerkleAuditProof, pass | |
98 // |tree_size| to QueryAuditProofNodes using that. | |
99 | |
100 // Query for the first batch of audit proof nodes (i.e. starting from 0). | |
101 QueryAuditProofNodes(std::move(proof), domain_for_log, tree_size, 0, | |
102 callback); | |
103 } | |
104 | |
105 void LogDnsClient::QueryLeafIndexComplete(net::DnsTransaction* transaction, | |
106 int net_error, | |
107 const net::DnsResponse* response) { | |
108 auto query_iterator = | |
109 std::find_if(leaf_index_queries_.begin(), leaf_index_queries_.end(), | |
110 [transaction](const Query<LeafIndexCallback>& query) { | |
111 return query.transaction.get() == transaction; | |
112 }); | |
113 if (query_iterator == leaf_index_queries_.end()) { | |
114 NOTREACHED(); | |
115 return; | |
116 } | |
117 const Query<LeafIndexCallback> query = std::move(*query_iterator); | |
118 leaf_index_queries_.erase(query_iterator); | |
119 | |
120 // If we've received no response but no net::error either (shouldn't happen), | |
121 // report the response as invalid. | |
122 if (response == nullptr && net_error == net::OK) { | |
123 net_error = net::ERR_INVALID_RESPONSE; | |
124 } | |
125 | |
126 if (net_error != net::OK) { | |
127 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
128 FROM_HERE, base::Bind(query.callback, net_error, 0)); | |
129 return; | |
130 } | |
131 | |
132 uint64_t leaf_index; | |
133 if (!ParseLeafIndex(*response, &leaf_index)) { | |
134 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
135 FROM_HERE, | |
136 base::Bind(query.callback, net::ERR_DNS_MALFORMED_RESPONSE, 0)); | |
137 return; | |
138 } | |
139 | |
140 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
141 FROM_HERE, base::Bind(query.callback, net::OK, leaf_index)); | |
142 } | |
143 | |
144 void LogDnsClient::QueryAuditProofNodes( | |
145 std::unique_ptr<net::ct::MerkleAuditProof> proof, | |
146 base::StringPiece domain_for_log, | |
147 uint64_t tree_size, | |
148 uint64_t node_index, | |
149 const AuditProofCallback& callback) { | |
150 // Preconditions that should be guaranteed internally by this class. | |
151 DCHECK(proof); | |
152 DCHECK(!domain_for_log.empty()); | |
153 DCHECK_LT(proof->leaf_index, tree_size); | |
154 DCHECK_LT(node_index, | |
155 net::ct::CalculateAuditPathLength(proof->leaf_index, tree_size)); | |
156 | |
157 std::ostringstream qname; | |
158 qname << node_index << "." << proof->leaf_index << "." << tree_size | |
159 << ".tree." << domain_for_log << "."; | |
160 | |
161 net::DnsTransactionFactory* factory = dns_client_->GetTransactionFactory(); | |
162 if (factory == nullptr) { | |
163 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
164 FROM_HERE, | |
165 base::Bind(callback, net::Error::ERR_NAME_RESOLUTION_FAILED, nullptr)); | |
166 return; | |
167 } | |
168 | |
169 net::DnsTransactionFactory::CallbackType transaction_callback = | |
170 base::Bind(&LogDnsClient::QueryAuditProofNodesComplete, | |
171 weak_ptr_factory_.GetWeakPtr(), base::Passed(std::move(proof)), | |
172 domain_for_log, tree_size); | |
173 | |
174 std::unique_ptr<net::DnsTransaction> dns_transaction = | |
175 factory->CreateTransaction(qname.str(), net::dns_protocol::kTypeTXT, | |
176 transaction_callback, net_log_); | |
177 dns_transaction->Start(); | |
178 audit_proof_queries_.push_back({std::move(dns_transaction), callback}); | |
179 } | |
180 | |
181 void LogDnsClient::QueryAuditProofNodesComplete( | |
182 std::unique_ptr<net::ct::MerkleAuditProof> proof, | |
183 base::StringPiece domain_for_log, | |
184 uint64_t tree_size, | |
185 net::DnsTransaction* transaction, | |
186 int net_error, | |
187 const net::DnsResponse* response) { | |
188 // Preconditions that should be guaranteed internally by this class. | |
189 DCHECK(proof); | |
190 DCHECK(!domain_for_log.empty()); | |
191 | |
192 auto query_iterator = | |
193 std::find_if(audit_proof_queries_.begin(), audit_proof_queries_.end(), | |
194 [transaction](const Query<AuditProofCallback>& query) { | |
195 return query.transaction.get() == transaction; | |
196 }); | |
197 | |
198 if (query_iterator == audit_proof_queries_.end()) { | |
199 NOTREACHED(); | |
200 return; | |
201 } | |
202 const Query<AuditProofCallback> query = std::move(*query_iterator); | |
203 audit_proof_queries_.erase(query_iterator); | |
204 | |
205 // If we've received no response but no net::error either (shouldn't happen), | |
206 // report the response as invalid. | |
207 if (response == nullptr && net_error == net::OK) { | |
208 net_error = net::ERR_INVALID_RESPONSE; | |
209 } | |
210 | |
211 if (net_error != net::OK) { | |
212 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
213 FROM_HERE, base::Bind(query.callback, net_error, nullptr)); | |
214 return; | |
215 } | |
216 | |
217 const uint64_t audit_path_length = | |
218 net::ct::CalculateAuditPathLength(proof->leaf_index, tree_size); | |
219 proof->nodes.reserve(audit_path_length); | |
220 | |
221 if (!ParseAuditPath(*response, proof.get())) { | |
222 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
223 FROM_HERE, | |
224 base::Bind(query.callback, net::ERR_DNS_MALFORMED_RESPONSE, nullptr)); | |
225 return; | |
226 } | |
227 | |
228 const uint64_t audit_path_nodes_received = proof->nodes.size(); | |
229 if (audit_path_nodes_received < audit_path_length) { | |
230 QueryAuditProofNodes(std::move(proof), domain_for_log, tree_size, | |
231 audit_path_nodes_received, query.callback); | |
232 return; | |
233 } | |
234 | |
235 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
236 FROM_HERE, | |
237 base::Bind(query.callback, net::OK, base::Passed(std::move(proof)))); | |
238 } | |
239 | |
240 bool LogDnsClient::ParseTxtResponse(const net::DnsResponse& response, | |
241 std::string* txt) { | |
242 DCHECK(txt); | |
243 | |
244 net::DnsRecordParser parser = response.Parser(); | |
245 // We don't care about the creation time, since we're going to throw | |
246 // |parsed_record| away as soon as we've extracted the payload, so provide | |
247 // the "null" time. | |
248 auto parsed_record = net::RecordParsed::CreateFrom(&parser, base::Time()); | |
249 if (parsed_record == nullptr) | |
250 return false; | |
251 | |
252 auto txt_record = parsed_record->rdata<net::TxtRecordRdata>(); | |
253 if (txt_record == nullptr) | |
254 return false; | |
255 | |
256 *txt = base::JoinString(txt_record->texts(), ""); | |
257 return true; | |
258 } | |
259 | |
260 bool LogDnsClient::ParseLeafIndex(const net::DnsResponse& response, | |
261 uint64_t* index) { | |
262 DCHECK(index); | |
263 | |
264 std::string index_str; | |
265 if (!ParseTxtResponse(response, &index_str)) | |
266 return false; | |
267 | |
268 return base::StringToUint64(index_str, index); | |
269 } | |
270 | |
271 bool LogDnsClient::ParseAuditPath(const net::DnsResponse& response, | |
272 net::ct::MerkleAuditProof* proof) { | |
273 DCHECK(proof); | |
274 | |
275 std::string audit_path; | |
276 if (!ParseTxtResponse(response, &audit_path)) | |
277 return false; | |
278 // If empty or not a multiple of the node size, it is considered invalid. | |
279 // It's important to consider empty audit paths as invalid, as otherwise an | |
280 // infinite loop could occur if the server consistently returned empty | |
281 // responses. | |
282 if (audit_path.empty() || audit_path.size() % crypto::kSHA256Length != 0) | |
283 return false; | |
284 | |
285 for (size_t i = 0; i < audit_path.size(); i += crypto::kSHA256Length) { | |
286 proof->nodes.push_back(audit_path.substr(i, crypto::kSHA256Length)); | |
287 } | |
288 | |
289 return true; | |
290 } | |
291 | |
292 } // namespace certificate_transparency | |
OLD | NEW |