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