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/cert/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/message_loop.h"
13 #include "base/metrics/histogram.h"
14 #include "base/stl_util.h"
15 #include "base/synchronization/lock.h"
16 #include "base/threading/worker_pool.h"
17 #include "base/time/time.h"
18 #include "net/base/net_errors.h"
19 #include "net/base/net_log.h"
20 #include "net/cert/cert_trust_anchor_provider.h"
21 #include "net/cert/cert_verify_proc.h"
22 #include "net/cert/crl_set.h"
23 #include "net/cert/x509_certificate.h"
24 #include "net/cert/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
CachedResult()83 MultiThreadedCertVerifier::CachedResult::CachedResult() : error(ERR_FAILED) {}
84
~CachedResult()85 MultiThreadedCertVerifier::CachedResult::~CachedResult() {}
86
CacheValidityPeriod(const base::Time & now)87 MultiThreadedCertVerifier::CacheValidityPeriod::CacheValidityPeriod(
88 const base::Time& now)
89 : verification_time(now),
90 expiration_time(now) {
91 }
92
CacheValidityPeriod(const base::Time & now,const base::Time & expiration)93 MultiThreadedCertVerifier::CacheValidityPeriod::CacheValidityPeriod(
94 const base::Time& now,
95 const base::Time& expiration)
96 : verification_time(now),
97 expiration_time(expiration) {
98 }
99
operator ()(const CacheValidityPeriod & now,const CacheValidityPeriod & expiration) const100 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:
CertVerifierRequest(const CompletionCallback & callback,CertVerifyResult * verify_result,const BoundNetLog & net_log)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
~CertVerifierRequest()148 ~CertVerifierRequest() {
149 }
150
151 // Ensures that the result callback will never be made.
Cancel()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.
Post(const MultiThreadedCertVerifier::CachedResult & verify_result)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
canceled() const170 bool canceled() const { return callback_.is_null(); }
171
net_log() const172 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:
CertVerifierWorker(CertVerifyProc * verify_proc,X509Certificate * cert,const std::string & hostname,int flags,CRLSet * crl_set,const CertificateList & additional_trust_anchors,MultiThreadedCertVerifier * cert_verifier)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_(base::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.
certificate() const207 X509Certificate* certificate() const { return cert_.get(); }
208
Start()209 bool Start() {
210 DCHECK_EQ(base::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.
Cancel()219 void Cancel() {
220 DCHECK_EQ(base::MessageLoop::current(), origin_loop_);
221 base::AutoLock locked(lock_);
222 canceled_ = true;
223 }
224
225 private:
Run()226 void Run() {
227 // Runs on a worker thread.
228 error_ = verify_proc_->Verify(cert_.get(),
229 hostname_,
230 flags_,
231 crl_set_.get(),
232 additional_trust_anchors_,
233 &verify_result_);
234 #if defined(USE_NSS) || defined(OS_IOS)
235 // Detach the thread from NSPR.
236 // Calling NSS functions attaches the thread to NSPR, which stores
237 // the NSPR thread ID in thread-specific data.
238 // The threads in our thread pool terminate after we have called
239 // PR_Cleanup. Unless we detach them from NSPR, net_unittests gets
240 // segfaults on shutdown when the threads' thread-specific data
241 // destructors run.
242 PR_DetachThread();
243 #endif
244 Finish();
245 }
246
247 // DoReply runs on the origin thread.
DoReply()248 void DoReply() {
249 DCHECK_EQ(base::MessageLoop::current(), origin_loop_);
250 {
251 // We lock here because the worker thread could still be in Finished,
252 // after the PostTask, but before unlocking |lock_|. If we do not lock in
253 // this case, we will end up deleting a locked Lock, which can lead to
254 // memory leaks or worse errors.
255 base::AutoLock locked(lock_);
256 if (!canceled_) {
257 cert_verifier_->HandleResult(cert_.get(),
258 hostname_,
259 flags_,
260 additional_trust_anchors_,
261 error_,
262 verify_result_);
263 }
264 }
265 delete this;
266 }
267
Finish()268 void Finish() {
269 // Runs on the worker thread.
270 // We assume that the origin loop outlives the MultiThreadedCertVerifier. If
271 // the MultiThreadedCertVerifier is deleted, it will call Cancel on us. If
272 // it does so before the Acquire, we'll delete ourselves and return. If it's
273 // trying to do so concurrently, then it'll block on the lock and we'll call
274 // PostTask while the MultiThreadedCertVerifier (and therefore the
275 // MessageLoop) is still alive.
276 // If it does so after this function, we assume that the MessageLoop will
277 // process pending tasks. In which case we'll notice the |canceled_| flag
278 // in DoReply.
279
280 bool canceled;
281 {
282 base::AutoLock locked(lock_);
283 canceled = canceled_;
284 if (!canceled) {
285 origin_loop_->PostTask(
286 FROM_HERE, base::Bind(
287 &CertVerifierWorker::DoReply, base::Unretained(this)));
288 }
289 }
290
291 if (canceled)
292 delete this;
293 }
294
295 scoped_refptr<CertVerifyProc> verify_proc_;
296 scoped_refptr<X509Certificate> cert_;
297 const std::string hostname_;
298 const int flags_;
299 scoped_refptr<CRLSet> crl_set_;
300 const CertificateList additional_trust_anchors_;
301 base::MessageLoop* const origin_loop_;
302 MultiThreadedCertVerifier* const cert_verifier_;
303
304 // lock_ protects canceled_.
305 base::Lock lock_;
306
307 // If canceled_ is true,
308 // * origin_loop_ cannot be accessed by the worker thread,
309 // * cert_verifier_ cannot be accessed by any thread.
310 bool canceled_;
311
312 int error_;
313 CertVerifyResult verify_result_;
314
315 DISALLOW_COPY_AND_ASSIGN(CertVerifierWorker);
316 };
317
318 // A CertVerifierJob is a one-to-one counterpart of a CertVerifierWorker. It
319 // lives only on the CertVerifier's origin message loop.
320 class CertVerifierJob {
321 public:
CertVerifierJob(CertVerifierWorker * worker,const BoundNetLog & net_log)322 CertVerifierJob(CertVerifierWorker* worker,
323 const BoundNetLog& net_log)
324 : start_time_(base::TimeTicks::Now()),
325 worker_(worker),
326 net_log_(net_log) {
327 net_log_.BeginEvent(
328 NetLog::TYPE_CERT_VERIFIER_JOB,
329 base::Bind(&NetLogX509CertificateCallback,
330 base::Unretained(worker_->certificate())));
331 }
332
~CertVerifierJob()333 ~CertVerifierJob() {
334 if (worker_) {
335 net_log_.AddEvent(NetLog::TYPE_CANCELLED);
336 net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_JOB);
337 worker_->Cancel();
338 DeleteAllCanceled();
339 }
340 }
341
AddRequest(CertVerifierRequest * request)342 void AddRequest(CertVerifierRequest* request) {
343 request->net_log().AddEvent(
344 NetLog::TYPE_CERT_VERIFIER_REQUEST_BOUND_TO_JOB,
345 net_log_.source().ToEventParametersCallback());
346
347 requests_.push_back(request);
348 }
349
HandleResult(const MultiThreadedCertVerifier::CachedResult & verify_result)350 void HandleResult(
351 const MultiThreadedCertVerifier::CachedResult& verify_result) {
352 worker_ = NULL;
353 net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_JOB);
354 UMA_HISTOGRAM_CUSTOM_TIMES("Net.CertVerifier_Job_Latency",
355 base::TimeTicks::Now() - start_time_,
356 base::TimeDelta::FromMilliseconds(1),
357 base::TimeDelta::FromMinutes(10),
358 100);
359 PostAll(verify_result);
360 }
361
362 private:
PostAll(const MultiThreadedCertVerifier::CachedResult & verify_result)363 void PostAll(const MultiThreadedCertVerifier::CachedResult& verify_result) {
364 std::vector<CertVerifierRequest*> requests;
365 requests_.swap(requests);
366
367 for (std::vector<CertVerifierRequest*>::iterator
368 i = requests.begin(); i != requests.end(); i++) {
369 (*i)->Post(verify_result);
370 // Post() causes the CertVerifierRequest to delete itself.
371 }
372 }
373
DeleteAllCanceled()374 void DeleteAllCanceled() {
375 for (std::vector<CertVerifierRequest*>::iterator
376 i = requests_.begin(); i != requests_.end(); i++) {
377 if ((*i)->canceled()) {
378 delete *i;
379 } else {
380 LOG(DFATAL) << "CertVerifierRequest leaked!";
381 }
382 }
383 }
384
385 const base::TimeTicks start_time_;
386 std::vector<CertVerifierRequest*> requests_;
387 CertVerifierWorker* worker_;
388 const BoundNetLog net_log_;
389 };
390
MultiThreadedCertVerifier(CertVerifyProc * verify_proc)391 MultiThreadedCertVerifier::MultiThreadedCertVerifier(
392 CertVerifyProc* verify_proc)
393 : cache_(kMaxCacheEntries),
394 requests_(0),
395 cache_hits_(0),
396 inflight_joins_(0),
397 verify_proc_(verify_proc),
398 trust_anchor_provider_(NULL) {
399 CertDatabase::GetInstance()->AddObserver(this);
400 }
401
~MultiThreadedCertVerifier()402 MultiThreadedCertVerifier::~MultiThreadedCertVerifier() {
403 STLDeleteValues(&inflight_);
404 CertDatabase::GetInstance()->RemoveObserver(this);
405 }
406
SetCertTrustAnchorProvider(CertTrustAnchorProvider * trust_anchor_provider)407 void MultiThreadedCertVerifier::SetCertTrustAnchorProvider(
408 CertTrustAnchorProvider* trust_anchor_provider) {
409 DCHECK(CalledOnValidThread());
410 trust_anchor_provider_ = trust_anchor_provider;
411 }
412
Verify(X509Certificate * cert,const std::string & hostname,int flags,CRLSet * crl_set,CertVerifyResult * verify_result,const CompletionCallback & callback,RequestHandle * out_req,const BoundNetLog & net_log)413 int MultiThreadedCertVerifier::Verify(X509Certificate* cert,
414 const std::string& hostname,
415 int flags,
416 CRLSet* crl_set,
417 CertVerifyResult* verify_result,
418 const CompletionCallback& callback,
419 RequestHandle* out_req,
420 const BoundNetLog& net_log) {
421 DCHECK(CalledOnValidThread());
422
423 if (callback.is_null() || !verify_result || hostname.empty()) {
424 *out_req = NULL;
425 return ERR_INVALID_ARGUMENT;
426 }
427
428 requests_++;
429
430 const CertificateList empty_cert_list;
431 const CertificateList& additional_trust_anchors =
432 trust_anchor_provider_ ?
433 trust_anchor_provider_->GetAdditionalTrustAnchors() : empty_cert_list;
434
435 const RequestParams key(cert->fingerprint(), cert->ca_fingerprint(),
436 hostname, flags, additional_trust_anchors);
437 const CertVerifierCache::value_type* cached_entry =
438 cache_.Get(key, CacheValidityPeriod(base::Time::Now()));
439 if (cached_entry) {
440 ++cache_hits_;
441 *out_req = NULL;
442 *verify_result = cached_entry->result;
443 return cached_entry->error;
444 }
445
446 // No cache hit. See if an identical request is currently in flight.
447 CertVerifierJob* job;
448 std::map<RequestParams, CertVerifierJob*>::const_iterator j;
449 j = inflight_.find(key);
450 if (j != inflight_.end()) {
451 // An identical request is in flight already. We'll just attach our
452 // callback.
453 inflight_joins_++;
454 job = j->second;
455 } else {
456 // Need to make a new request.
457 CertVerifierWorker* worker =
458 new CertVerifierWorker(verify_proc_.get(),
459 cert,
460 hostname,
461 flags,
462 crl_set,
463 additional_trust_anchors,
464 this);
465 job = new CertVerifierJob(
466 worker,
467 BoundNetLog::Make(net_log.net_log(), NetLog::SOURCE_CERT_VERIFIER_JOB));
468 if (!worker->Start()) {
469 delete job;
470 delete worker;
471 *out_req = NULL;
472 // TODO(wtc): log to the NetLog.
473 LOG(ERROR) << "CertVerifierWorker couldn't be started.";
474 return ERR_INSUFFICIENT_RESOURCES; // Just a guess.
475 }
476 inflight_.insert(std::make_pair(key, job));
477 }
478
479 CertVerifierRequest* request =
480 new CertVerifierRequest(callback, verify_result, net_log);
481 job->AddRequest(request);
482 *out_req = request;
483 return ERR_IO_PENDING;
484 }
485
CancelRequest(RequestHandle req)486 void MultiThreadedCertVerifier::CancelRequest(RequestHandle req) {
487 DCHECK(CalledOnValidThread());
488 CertVerifierRequest* request = reinterpret_cast<CertVerifierRequest*>(req);
489 request->Cancel();
490 }
491
RequestParams(const SHA1HashValue & cert_fingerprint_arg,const SHA1HashValue & ca_fingerprint_arg,const std::string & hostname_arg,int flags_arg,const CertificateList & additional_trust_anchors)492 MultiThreadedCertVerifier::RequestParams::RequestParams(
493 const SHA1HashValue& cert_fingerprint_arg,
494 const SHA1HashValue& ca_fingerprint_arg,
495 const std::string& hostname_arg,
496 int flags_arg,
497 const CertificateList& additional_trust_anchors)
498 : hostname(hostname_arg),
499 flags(flags_arg) {
500 hash_values.reserve(2 + additional_trust_anchors.size());
501 hash_values.push_back(cert_fingerprint_arg);
502 hash_values.push_back(ca_fingerprint_arg);
503 for (size_t i = 0; i < additional_trust_anchors.size(); ++i)
504 hash_values.push_back(additional_trust_anchors[i]->fingerprint());
505 }
506
~RequestParams()507 MultiThreadedCertVerifier::RequestParams::~RequestParams() {}
508
operator <(const RequestParams & other) const509 bool MultiThreadedCertVerifier::RequestParams::operator<(
510 const RequestParams& other) const {
511 // |flags| is compared before |cert_fingerprint|, |ca_fingerprint|, and
512 // |hostname| under assumption that integer comparisons are faster than
513 // memory and string comparisons.
514 if (flags != other.flags)
515 return flags < other.flags;
516 if (hostname != other.hostname)
517 return hostname < other.hostname;
518 return std::lexicographical_compare(
519 hash_values.begin(), hash_values.end(),
520 other.hash_values.begin(), other.hash_values.end(),
521 net::SHA1HashValueLessThan());
522 }
523
524 // HandleResult is called by CertVerifierWorker on the origin message loop.
525 // It deletes CertVerifierJob.
HandleResult(X509Certificate * cert,const std::string & hostname,int flags,const CertificateList & additional_trust_anchors,int error,const CertVerifyResult & verify_result)526 void MultiThreadedCertVerifier::HandleResult(
527 X509Certificate* cert,
528 const std::string& hostname,
529 int flags,
530 const CertificateList& additional_trust_anchors,
531 int error,
532 const CertVerifyResult& verify_result) {
533 DCHECK(CalledOnValidThread());
534
535 const RequestParams key(cert->fingerprint(), cert->ca_fingerprint(),
536 hostname, flags, additional_trust_anchors);
537
538 CachedResult cached_result;
539 cached_result.error = error;
540 cached_result.result = verify_result;
541 base::Time now = base::Time::Now();
542 cache_.Put(
543 key, cached_result, CacheValidityPeriod(now),
544 CacheValidityPeriod(now, now + base::TimeDelta::FromSeconds(kTTLSecs)));
545
546 std::map<RequestParams, CertVerifierJob*>::iterator j;
547 j = inflight_.find(key);
548 if (j == inflight_.end()) {
549 NOTREACHED();
550 return;
551 }
552 CertVerifierJob* job = j->second;
553 inflight_.erase(j);
554
555 job->HandleResult(cached_result);
556 delete job;
557 }
558
OnCACertChanged(const X509Certificate * cert)559 void MultiThreadedCertVerifier::OnCACertChanged(
560 const X509Certificate* cert) {
561 DCHECK(CalledOnValidThread());
562
563 ClearCache();
564 }
565
566 } // namespace net
567