OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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 "net/base/multi_threaded_cert_verifier.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "base/bind.h" | |
10 #include "base/bind_helpers.h" | |
11 #include "base/compiler_specific.h" | |
12 #include "base/message_loop.h" | |
13 #include "base/metrics/histogram.h" | |
14 #include "base/stl_util.h" | |
15 #include "base/synchronization/lock.h" | |
16 #include "base/time.h" | |
17 #include "base/threading/worker_pool.h" | |
18 #include "net/base/cert_trust_anchor_provider.h" | |
19 #include "net/base/cert_verify_proc.h" | |
20 #include "net/base/crl_set.h" | |
21 #include "net/base/net_errors.h" | |
22 #include "net/base/net_log.h" | |
23 #include "net/base/x509_certificate.h" | |
24 #include "net/base/x509_certificate_net_log_param.h" | |
25 | |
26 #if defined(USE_NSS) || defined(OS_IOS) | |
27 #include <private/pprthred.h> // PR_DetachThread | |
28 #endif | |
29 | |
30 namespace net { | |
31 | |
32 //////////////////////////////////////////////////////////////////////////// | |
33 | |
34 // Life of a request: | |
35 // | |
36 // MultiThreadedCertVerifier CertVerifierJob CertVerifierWorker Request | |
37 // | (origin loop) (worker loop) | |
38 // | | |
39 // Verify() | |
40 // |---->-------------------------------------<creates> | |
41 // | | |
42 // |---->-------------------<creates> | |
43 // | | |
44 // |---->-------------------------------------------------------<creates> | |
45 // | | |
46 // |---->---------------------------------------Start | |
47 // | | | |
48 // | PostTask | |
49 // | | |
50 // | <starts verifying> | |
51 // |---->-------------------AddRequest | | |
52 // | | |
53 // | | |
54 // | | |
55 // Finish | |
56 // | | |
57 // PostTask | |
58 // | |
59 // | | |
60 // DoReply | |
61 // |----<-----------------------------------------| | |
62 // HandleResult | |
63 // | | |
64 // |---->------------------HandleResult | |
65 // | | |
66 // |------>---------------------------Post | |
67 // | |
68 // | |
69 // | |
70 // On a cache hit, MultiThreadedCertVerifier::Verify() returns synchronously | |
71 // without posting a task to a worker thread. | |
72 | |
73 namespace { | |
74 | |
75 // The default value of max_cache_entries_. | |
76 const unsigned kMaxCacheEntries = 256; | |
77 | |
78 // The number of seconds for which we'll cache a cache entry. | |
79 const unsigned kTTLSecs = 1800; // 30 minutes. | |
80 | |
81 } // namespace | |
82 | |
83 MultiThreadedCertVerifier::CachedResult::CachedResult() : error(ERR_FAILED) {} | |
84 | |
85 MultiThreadedCertVerifier::CachedResult::~CachedResult() {} | |
86 | |
87 MultiThreadedCertVerifier::CacheValidityPeriod::CacheValidityPeriod( | |
88 const base::Time& now) | |
89 : verification_time(now), | |
90 expiration_time(now) { | |
91 } | |
92 | |
93 MultiThreadedCertVerifier::CacheValidityPeriod::CacheValidityPeriod( | |
94 const base::Time& now, | |
95 const base::Time& expiration) | |
96 : verification_time(now), | |
97 expiration_time(expiration) { | |
98 } | |
99 | |
100 bool MultiThreadedCertVerifier::CacheExpirationFunctor::operator()( | |
101 const CacheValidityPeriod& now, | |
102 const CacheValidityPeriod& expiration) const { | |
103 // Ensure this functor is being used for expiration only, and not strict | |
104 // weak ordering/sorting. |now| should only ever contain a single | |
105 // base::Time. | |
106 // Note: DCHECK_EQ is not used due to operator<< overloading requirements. | |
107 DCHECK(now.verification_time == now.expiration_time); | |
108 | |
109 // |now| contains only a single time (verification_time), while |expiration| | |
110 // contains the validity range - both when the certificate was verified and | |
111 // when the verification result should expire. | |
112 // | |
113 // If the user receives a "not yet valid" message, and adjusts their clock | |
114 // foward to the correct time, this will (typically) cause | |
115 // now.verification_time to advance past expiration.expiration_time, thus | |
116 // treating the cached result as an expired entry and re-verifying. | |
117 // If the user receives a "expired" message, and adjusts their clock | |
118 // backwards to the correct time, this will cause now.verification_time to | |
119 // be less than expiration_verification_time, thus treating the cached | |
120 // result as an expired entry and re-verifying. | |
121 // If the user receives either of those messages, and does not adjust their | |
122 // clock, then the result will be (typically) be cached until the expiration | |
123 // TTL. | |
124 // | |
125 // This algorithm is only problematic if the user consistently keeps | |
126 // adjusting their clock backwards in increments smaller than the expiration | |
127 // TTL, in which case, cached elements continue to be added. However, | |
128 // because the cache has a fixed upper bound, if no entries are expired, a | |
129 // 'random' entry will be, thus keeping the memory constraints bounded over | |
130 // time. | |
131 return now.verification_time >= expiration.verification_time && | |
132 now.verification_time < expiration.expiration_time; | |
133 }; | |
134 | |
135 | |
136 // Represents the output and result callback of a request. | |
137 class CertVerifierRequest { | |
138 public: | |
139 CertVerifierRequest(const CompletionCallback& callback, | |
140 CertVerifyResult* verify_result, | |
141 const BoundNetLog& net_log) | |
142 : callback_(callback), | |
143 verify_result_(verify_result), | |
144 net_log_(net_log) { | |
145 net_log_.BeginEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST); | |
146 } | |
147 | |
148 ~CertVerifierRequest() { | |
149 } | |
150 | |
151 // Ensures that the result callback will never be made. | |
152 void Cancel() { | |
153 callback_.Reset(); | |
154 verify_result_ = NULL; | |
155 net_log_.AddEvent(NetLog::TYPE_CANCELLED); | |
156 net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST); | |
157 } | |
158 | |
159 // Copies the contents of |verify_result| to the caller's | |
160 // CertVerifyResult and calls the callback. | |
161 void Post(const MultiThreadedCertVerifier::CachedResult& verify_result) { | |
162 if (!callback_.is_null()) { | |
163 net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST); | |
164 *verify_result_ = verify_result.result; | |
165 callback_.Run(verify_result.error); | |
166 } | |
167 delete this; | |
168 } | |
169 | |
170 bool canceled() const { return callback_.is_null(); } | |
171 | |
172 const BoundNetLog& net_log() const { return net_log_; } | |
173 | |
174 private: | |
175 CompletionCallback callback_; | |
176 CertVerifyResult* verify_result_; | |
177 const BoundNetLog net_log_; | |
178 }; | |
179 | |
180 | |
181 // CertVerifierWorker runs on a worker thread and takes care of the blocking | |
182 // process of performing the certificate verification. Deletes itself | |
183 // eventually if Start() succeeds. | |
184 class CertVerifierWorker { | |
185 public: | |
186 CertVerifierWorker(CertVerifyProc* verify_proc, | |
187 X509Certificate* cert, | |
188 const std::string& hostname, | |
189 int flags, | |
190 CRLSet* crl_set, | |
191 const CertificateList& additional_trust_anchors, | |
192 MultiThreadedCertVerifier* cert_verifier) | |
193 : verify_proc_(verify_proc), | |
194 cert_(cert), | |
195 hostname_(hostname), | |
196 flags_(flags), | |
197 crl_set_(crl_set), | |
198 additional_trust_anchors_(additional_trust_anchors), | |
199 origin_loop_(MessageLoop::current()), | |
200 cert_verifier_(cert_verifier), | |
201 canceled_(false), | |
202 error_(ERR_FAILED) { | |
203 } | |
204 | |
205 // Returns the certificate being verified. May only be called /before/ | |
206 // Start() is called. | |
207 X509Certificate* certificate() const { return cert_; } | |
208 | |
209 bool Start() { | |
210 DCHECK_EQ(MessageLoop::current(), origin_loop_); | |
211 | |
212 return base::WorkerPool::PostTask( | |
213 FROM_HERE, base::Bind(&CertVerifierWorker::Run, base::Unretained(this)), | |
214 true /* task is slow */); | |
215 } | |
216 | |
217 // Cancel is called from the origin loop when the MultiThreadedCertVerifier is | |
218 // getting deleted. | |
219 void Cancel() { | |
220 DCHECK_EQ(MessageLoop::current(), origin_loop_); | |
221 base::AutoLock locked(lock_); | |
222 canceled_ = true; | |
223 } | |
224 | |
225 private: | |
226 void Run() { | |
227 // Runs on a worker thread. | |
228 error_ = verify_proc_->Verify(cert_, hostname_, flags_, crl_set_, | |
229 additional_trust_anchors_, &verify_result_); | |
230 #if defined(USE_NSS) || defined(OS_IOS) | |
231 // Detach the thread from NSPR. | |
232 // Calling NSS functions attaches the thread to NSPR, which stores | |
233 // the NSPR thread ID in thread-specific data. | |
234 // The threads in our thread pool terminate after we have called | |
235 // PR_Cleanup. Unless we detach them from NSPR, net_unittests gets | |
236 // segfaults on shutdown when the threads' thread-specific data | |
237 // destructors run. | |
238 PR_DetachThread(); | |
239 #endif | |
240 Finish(); | |
241 } | |
242 | |
243 // DoReply runs on the origin thread. | |
244 void DoReply() { | |
245 DCHECK_EQ(MessageLoop::current(), origin_loop_); | |
246 { | |
247 // We lock here because the worker thread could still be in Finished, | |
248 // after the PostTask, but before unlocking |lock_|. If we do not lock in | |
249 // this case, we will end up deleting a locked Lock, which can lead to | |
250 // memory leaks or worse errors. | |
251 base::AutoLock locked(lock_); | |
252 if (!canceled_) { | |
253 cert_verifier_->HandleResult(cert_, hostname_, flags_, | |
254 additional_trust_anchors_, error_, | |
255 verify_result_); | |
256 } | |
257 } | |
258 delete this; | |
259 } | |
260 | |
261 void Finish() { | |
262 // Runs on the worker thread. | |
263 // We assume that the origin loop outlives the MultiThreadedCertVerifier. If | |
264 // the MultiThreadedCertVerifier is deleted, it will call Cancel on us. If | |
265 // it does so before the Acquire, we'll delete ourselves and return. If it's | |
266 // trying to do so concurrently, then it'll block on the lock and we'll call | |
267 // PostTask while the MultiThreadedCertVerifier (and therefore the | |
268 // MessageLoop) is still alive. | |
269 // If it does so after this function, we assume that the MessageLoop will | |
270 // process pending tasks. In which case we'll notice the |canceled_| flag | |
271 // in DoReply. | |
272 | |
273 bool canceled; | |
274 { | |
275 base::AutoLock locked(lock_); | |
276 canceled = canceled_; | |
277 if (!canceled) { | |
278 origin_loop_->PostTask( | |
279 FROM_HERE, base::Bind( | |
280 &CertVerifierWorker::DoReply, base::Unretained(this))); | |
281 } | |
282 } | |
283 | |
284 if (canceled) | |
285 delete this; | |
286 } | |
287 | |
288 scoped_refptr<CertVerifyProc> verify_proc_; | |
289 scoped_refptr<X509Certificate> cert_; | |
290 const std::string hostname_; | |
291 const int flags_; | |
292 scoped_refptr<CRLSet> crl_set_; | |
293 const CertificateList additional_trust_anchors_; | |
294 MessageLoop* const origin_loop_; | |
295 MultiThreadedCertVerifier* const cert_verifier_; | |
296 | |
297 // lock_ protects canceled_. | |
298 base::Lock lock_; | |
299 | |
300 // If canceled_ is true, | |
301 // * origin_loop_ cannot be accessed by the worker thread, | |
302 // * cert_verifier_ cannot be accessed by any thread. | |
303 bool canceled_; | |
304 | |
305 int error_; | |
306 CertVerifyResult verify_result_; | |
307 | |
308 DISALLOW_COPY_AND_ASSIGN(CertVerifierWorker); | |
309 }; | |
310 | |
311 // A CertVerifierJob is a one-to-one counterpart of a CertVerifierWorker. It | |
312 // lives only on the CertVerifier's origin message loop. | |
313 class CertVerifierJob { | |
314 public: | |
315 CertVerifierJob(CertVerifierWorker* worker, | |
316 const BoundNetLog& net_log) | |
317 : start_time_(base::TimeTicks::Now()), | |
318 worker_(worker), | |
319 net_log_(net_log) { | |
320 net_log_.BeginEvent( | |
321 NetLog::TYPE_CERT_VERIFIER_JOB, | |
322 base::Bind(&NetLogX509CertificateCallback, | |
323 base::Unretained(worker_->certificate()))); | |
324 } | |
325 | |
326 ~CertVerifierJob() { | |
327 if (worker_) { | |
328 net_log_.AddEvent(NetLog::TYPE_CANCELLED); | |
329 net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_JOB); | |
330 worker_->Cancel(); | |
331 DeleteAllCanceled(); | |
332 } | |
333 } | |
334 | |
335 void AddRequest(CertVerifierRequest* request) { | |
336 request->net_log().AddEvent( | |
337 NetLog::TYPE_CERT_VERIFIER_REQUEST_BOUND_TO_JOB, | |
338 net_log_.source().ToEventParametersCallback()); | |
339 | |
340 requests_.push_back(request); | |
341 } | |
342 | |
343 void HandleResult( | |
344 const MultiThreadedCertVerifier::CachedResult& verify_result) { | |
345 worker_ = NULL; | |
346 net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_JOB); | |
347 UMA_HISTOGRAM_CUSTOM_TIMES("Net.CertVerifier_Job_Latency", | |
348 base::TimeTicks::Now() - start_time_, | |
349 base::TimeDelta::FromMilliseconds(1), | |
350 base::TimeDelta::FromMinutes(10), | |
351 100); | |
352 PostAll(verify_result); | |
353 } | |
354 | |
355 private: | |
356 void PostAll(const MultiThreadedCertVerifier::CachedResult& verify_result) { | |
357 std::vector<CertVerifierRequest*> requests; | |
358 requests_.swap(requests); | |
359 | |
360 for (std::vector<CertVerifierRequest*>::iterator | |
361 i = requests.begin(); i != requests.end(); i++) { | |
362 (*i)->Post(verify_result); | |
363 // Post() causes the CertVerifierRequest to delete itself. | |
364 } | |
365 } | |
366 | |
367 void DeleteAllCanceled() { | |
368 for (std::vector<CertVerifierRequest*>::iterator | |
369 i = requests_.begin(); i != requests_.end(); i++) { | |
370 if ((*i)->canceled()) { | |
371 delete *i; | |
372 } else { | |
373 LOG(DFATAL) << "CertVerifierRequest leaked!"; | |
374 } | |
375 } | |
376 } | |
377 | |
378 const base::TimeTicks start_time_; | |
379 std::vector<CertVerifierRequest*> requests_; | |
380 CertVerifierWorker* worker_; | |
381 const BoundNetLog net_log_; | |
382 }; | |
383 | |
384 MultiThreadedCertVerifier::MultiThreadedCertVerifier( | |
385 CertVerifyProc* verify_proc) | |
386 : cache_(kMaxCacheEntries), | |
387 requests_(0), | |
388 cache_hits_(0), | |
389 inflight_joins_(0), | |
390 verify_proc_(verify_proc), | |
391 trust_anchor_provider_(NULL) { | |
392 CertDatabase::GetInstance()->AddObserver(this); | |
393 } | |
394 | |
395 MultiThreadedCertVerifier::~MultiThreadedCertVerifier() { | |
396 STLDeleteValues(&inflight_); | |
397 CertDatabase::GetInstance()->RemoveObserver(this); | |
398 } | |
399 | |
400 void MultiThreadedCertVerifier::SetCertTrustAnchorProvider( | |
401 CertTrustAnchorProvider* trust_anchor_provider) { | |
402 DCHECK(CalledOnValidThread()); | |
403 trust_anchor_provider_ = trust_anchor_provider; | |
404 } | |
405 | |
406 int MultiThreadedCertVerifier::Verify(X509Certificate* cert, | |
407 const std::string& hostname, | |
408 int flags, | |
409 CRLSet* crl_set, | |
410 CertVerifyResult* verify_result, | |
411 const CompletionCallback& callback, | |
412 RequestHandle* out_req, | |
413 const BoundNetLog& net_log) { | |
414 DCHECK(CalledOnValidThread()); | |
415 | |
416 if (callback.is_null() || !verify_result || hostname.empty()) { | |
417 *out_req = NULL; | |
418 return ERR_INVALID_ARGUMENT; | |
419 } | |
420 | |
421 requests_++; | |
422 | |
423 const CertificateList empty_cert_list; | |
424 const CertificateList& additional_trust_anchors = | |
425 trust_anchor_provider_ ? | |
426 trust_anchor_provider_->GetAdditionalTrustAnchors() : empty_cert_list; | |
427 | |
428 const RequestParams key(cert->fingerprint(), cert->ca_fingerprint(), | |
429 hostname, flags, additional_trust_anchors); | |
430 const CertVerifierCache::value_type* cached_entry = | |
431 cache_.Get(key, CacheValidityPeriod(base::Time::Now())); | |
432 if (cached_entry) { | |
433 ++cache_hits_; | |
434 *out_req = NULL; | |
435 *verify_result = cached_entry->result; | |
436 return cached_entry->error; | |
437 } | |
438 | |
439 // No cache hit. See if an identical request is currently in flight. | |
440 CertVerifierJob* job; | |
441 std::map<RequestParams, CertVerifierJob*>::const_iterator j; | |
442 j = inflight_.find(key); | |
443 if (j != inflight_.end()) { | |
444 // An identical request is in flight already. We'll just attach our | |
445 // callback. | |
446 inflight_joins_++; | |
447 job = j->second; | |
448 } else { | |
449 // Need to make a new request. | |
450 CertVerifierWorker* worker = | |
451 new CertVerifierWorker(verify_proc_, cert, hostname, flags, crl_set, | |
452 additional_trust_anchors, this); | |
453 job = new CertVerifierJob( | |
454 worker, | |
455 BoundNetLog::Make(net_log.net_log(), NetLog::SOURCE_CERT_VERIFIER_JOB)); | |
456 if (!worker->Start()) { | |
457 delete job; | |
458 delete worker; | |
459 *out_req = NULL; | |
460 // TODO(wtc): log to the NetLog. | |
461 LOG(ERROR) << "CertVerifierWorker couldn't be started."; | |
462 return ERR_INSUFFICIENT_RESOURCES; // Just a guess. | |
463 } | |
464 inflight_.insert(std::make_pair(key, job)); | |
465 } | |
466 | |
467 CertVerifierRequest* request = | |
468 new CertVerifierRequest(callback, verify_result, net_log); | |
469 job->AddRequest(request); | |
470 *out_req = request; | |
471 return ERR_IO_PENDING; | |
472 } | |
473 | |
474 void MultiThreadedCertVerifier::CancelRequest(RequestHandle req) { | |
475 DCHECK(CalledOnValidThread()); | |
476 CertVerifierRequest* request = reinterpret_cast<CertVerifierRequest*>(req); | |
477 request->Cancel(); | |
478 } | |
479 | |
480 MultiThreadedCertVerifier::RequestParams::RequestParams( | |
481 const SHA1HashValue& cert_fingerprint_arg, | |
482 const SHA1HashValue& ca_fingerprint_arg, | |
483 const std::string& hostname_arg, | |
484 int flags_arg, | |
485 const CertificateList& additional_trust_anchors) | |
486 : hostname(hostname_arg), | |
487 flags(flags_arg) { | |
488 hash_values.reserve(2 + additional_trust_anchors.size()); | |
489 hash_values.push_back(cert_fingerprint_arg); | |
490 hash_values.push_back(ca_fingerprint_arg); | |
491 for (size_t i = 0; i < additional_trust_anchors.size(); ++i) | |
492 hash_values.push_back(additional_trust_anchors[i]->fingerprint()); | |
493 } | |
494 | |
495 MultiThreadedCertVerifier::RequestParams::~RequestParams() {} | |
496 | |
497 bool MultiThreadedCertVerifier::RequestParams::operator<( | |
498 const RequestParams& other) const { | |
499 // |flags| is compared before |cert_fingerprint|, |ca_fingerprint|, and | |
500 // |hostname| under assumption that integer comparisons are faster than | |
501 // memory and string comparisons. | |
502 if (flags != other.flags) | |
503 return flags < other.flags; | |
504 if (hostname != other.hostname) | |
505 return hostname < other.hostname; | |
506 return std::lexicographical_compare( | |
507 hash_values.begin(), hash_values.end(), | |
508 other.hash_values.begin(), other.hash_values.end(), | |
509 net::SHA1HashValueLessThan()); | |
510 } | |
511 | |
512 // HandleResult is called by CertVerifierWorker on the origin message loop. | |
513 // It deletes CertVerifierJob. | |
514 void MultiThreadedCertVerifier::HandleResult( | |
515 X509Certificate* cert, | |
516 const std::string& hostname, | |
517 int flags, | |
518 const CertificateList& additional_trust_anchors, | |
519 int error, | |
520 const CertVerifyResult& verify_result) { | |
521 DCHECK(CalledOnValidThread()); | |
522 | |
523 const RequestParams key(cert->fingerprint(), cert->ca_fingerprint(), | |
524 hostname, flags, additional_trust_anchors); | |
525 | |
526 CachedResult cached_result; | |
527 cached_result.error = error; | |
528 cached_result.result = verify_result; | |
529 base::Time now = base::Time::Now(); | |
530 cache_.Put( | |
531 key, cached_result, CacheValidityPeriod(now), | |
532 CacheValidityPeriod(now, now + base::TimeDelta::FromSeconds(kTTLSecs))); | |
533 | |
534 std::map<RequestParams, CertVerifierJob*>::iterator j; | |
535 j = inflight_.find(key); | |
536 if (j == inflight_.end()) { | |
537 NOTREACHED(); | |
538 return; | |
539 } | |
540 CertVerifierJob* job = j->second; | |
541 inflight_.erase(j); | |
542 | |
543 job->HandleResult(cached_result); | |
544 delete job; | |
545 } | |
546 | |
547 void MultiThreadedCertVerifier::OnCertTrustChanged( | |
548 const X509Certificate* cert) { | |
549 DCHECK(CalledOnValidThread()); | |
550 | |
551 ClearCache(); | |
552 } | |
553 | |
554 } // namespace net | |
OLD | NEW |