• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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/cert_verifier.h"
6 
7 #include "base/compiler_specific.h"
8 #include "base/message_loop.h"
9 #include "base/stl_util-inl.h"
10 #include "base/synchronization/lock.h"
11 #include "base/threading/worker_pool.h"
12 #include "net/base/net_errors.h"
13 #include "net/base/x509_certificate.h"
14 
15 #if defined(USE_NSS)
16 #include <private/pprthred.h>  // PR_DetachThread
17 #endif
18 
19 namespace net {
20 
21 ////////////////////////////////////////////////////////////////////////////
22 
23 // Life of a request:
24 //
25 // CertVerifier CertVerifierJob       CertVerifierWorker          Request
26 //      |                       (origin loop)    (worker loop)
27 //      |
28 //   Verify()
29 //      |---->-------------------<creates>
30 //      |
31 //      |---->----<creates>
32 //      |
33 //      |---->---------------------------------------------------<creates>
34 //      |
35 //      |---->--------------------Start
36 //      |                           |
37 //      |                        PostTask
38 //      |
39 //      |                                     <starts verifying>
40 //      |---->-----AddRequest                         |
41 //                                                    |
42 //                                                    |
43 //                                                    |
44 //                                                  Finish
45 //                                                    |
46 //                                                 PostTask
47 //
48 //                                   |
49 //                                DoReply
50 //      |----<-----------------------|
51 //  HandleResult
52 //      |
53 //      |---->-----HandleResult
54 //                      |
55 //                      |------>-----------------------------------Post
56 //
57 //
58 //
59 // On a cache hit, CertVerifier::Verify() returns synchronously without
60 // posting a task to a worker thread.
61 
62 // The number of CachedCertVerifyResult objects that we'll cache.
63 static const unsigned kMaxCacheEntries = 256;
64 
65 // The number of seconds for which we'll cache a cache entry.
66 static const unsigned kTTLSecs = 1800;  // 30 minutes.
67 
68 namespace {
69 
70 class DefaultTimeService : public CertVerifier::TimeService {
71  public:
72   // CertVerifier::TimeService methods:
Now()73   virtual base::Time Now() { return base::Time::Now(); }
74 };
75 
76 }  // namespace
77 
CachedCertVerifyResult()78 CachedCertVerifyResult::CachedCertVerifyResult() : error(ERR_FAILED) {
79 }
80 
~CachedCertVerifyResult()81 CachedCertVerifyResult::~CachedCertVerifyResult() {}
82 
HasExpired(const base::Time current_time) const83 bool CachedCertVerifyResult::HasExpired(const base::Time current_time) const {
84   return current_time >= expiry;
85 }
86 
87 // Represents the output and result callback of a request.
88 class CertVerifierRequest {
89  public:
CertVerifierRequest(CompletionCallback * callback,CertVerifyResult * verify_result)90   CertVerifierRequest(CompletionCallback* callback,
91                       CertVerifyResult* verify_result)
92       : callback_(callback),
93         verify_result_(verify_result) {
94   }
95 
96   // Ensures that the result callback will never be made.
Cancel()97   void Cancel() {
98     callback_ = NULL;
99     verify_result_ = NULL;
100   }
101 
102   // Copies the contents of |verify_result| to the caller's
103   // CertVerifyResult and calls the callback.
Post(const CachedCertVerifyResult & verify_result)104   void Post(const CachedCertVerifyResult& verify_result) {
105     if (callback_) {
106       *verify_result_ = verify_result.result;
107       callback_->Run(verify_result.error);
108     }
109     delete this;
110   }
111 
canceled() const112   bool canceled() const { return !callback_; }
113 
114  private:
115   CompletionCallback* callback_;
116   CertVerifyResult* verify_result_;
117 };
118 
119 
120 // CertVerifierWorker runs on a worker thread and takes care of the blocking
121 // process of performing the certificate verification.  Deletes itself
122 // eventually if Start() succeeds.
123 class CertVerifierWorker {
124  public:
CertVerifierWorker(X509Certificate * cert,const std::string & hostname,int flags,CertVerifier * cert_verifier)125   CertVerifierWorker(X509Certificate* cert,
126                      const std::string& hostname,
127                      int flags,
128                      CertVerifier* cert_verifier)
129       : cert_(cert),
130         hostname_(hostname),
131         flags_(flags),
132         origin_loop_(MessageLoop::current()),
133         cert_verifier_(cert_verifier),
134         canceled_(false),
135         error_(ERR_FAILED) {
136   }
137 
Start()138   bool Start() {
139     DCHECK_EQ(MessageLoop::current(), origin_loop_);
140 
141     return base::WorkerPool::PostTask(
142         FROM_HERE, NewRunnableMethod(this, &CertVerifierWorker::Run),
143         true /* task is slow */);
144   }
145 
146   // Cancel is called from the origin loop when the CertVerifier is getting
147   // deleted.
Cancel()148   void Cancel() {
149     DCHECK_EQ(MessageLoop::current(), origin_loop_);
150     base::AutoLock locked(lock_);
151     canceled_ = true;
152   }
153 
154  private:
Run()155   void Run() {
156     // Runs on a worker thread.
157     error_ = cert_->Verify(hostname_, flags_, &verify_result_);
158 #if defined(USE_NSS)
159     // Detach the thread from NSPR.
160     // Calling NSS functions attaches the thread to NSPR, which stores
161     // the NSPR thread ID in thread-specific data.
162     // The threads in our thread pool terminate after we have called
163     // PR_Cleanup.  Unless we detach them from NSPR, net_unittests gets
164     // segfaults on shutdown when the threads' thread-specific data
165     // destructors run.
166     PR_DetachThread();
167 #endif
168     Finish();
169   }
170 
171   // DoReply runs on the origin thread.
DoReply()172   void DoReply() {
173     DCHECK_EQ(MessageLoop::current(), origin_loop_);
174     {
175       // We lock here because the worker thread could still be in Finished,
176       // after the PostTask, but before unlocking |lock_|. If we do not lock in
177       // this case, we will end up deleting a locked Lock, which can lead to
178       // memory leaks or worse errors.
179       base::AutoLock locked(lock_);
180       if (!canceled_) {
181         cert_verifier_->HandleResult(cert_, hostname_, flags_,
182                                      error_, verify_result_);
183       }
184     }
185     delete this;
186   }
187 
Finish()188   void Finish() {
189     // Runs on the worker thread.
190     // We assume that the origin loop outlives the CertVerifier. If the
191     // CertVerifier is deleted, it will call Cancel on us. If it does so
192     // before the Acquire, we'll delete ourselves and return. If it's trying to
193     // do so concurrently, then it'll block on the lock and we'll call PostTask
194     // while the CertVerifier (and therefore the MessageLoop) is still alive.
195     // If it does so after this function, we assume that the MessageLoop will
196     // process pending tasks. In which case we'll notice the |canceled_| flag
197     // in DoReply.
198 
199     bool canceled;
200     {
201       base::AutoLock locked(lock_);
202       canceled = canceled_;
203       if (!canceled) {
204         origin_loop_->PostTask(
205             FROM_HERE, NewRunnableMethod(this, &CertVerifierWorker::DoReply));
206       }
207     }
208 
209     if (canceled)
210       delete this;
211   }
212 
213   scoped_refptr<X509Certificate> cert_;
214   const std::string hostname_;
215   const int flags_;
216   MessageLoop* const origin_loop_;
217   CertVerifier* const cert_verifier_;
218 
219   // lock_ protects canceled_.
220   base::Lock lock_;
221 
222   // If canceled_ is true,
223   // * origin_loop_ cannot be accessed by the worker thread,
224   // * cert_verifier_ cannot be accessed by any thread.
225   bool canceled_;
226 
227   int error_;
228   CertVerifyResult verify_result_;
229 
230   DISALLOW_COPY_AND_ASSIGN(CertVerifierWorker);
231 };
232 
233 // A CertVerifierJob is a one-to-one counterpart of a CertVerifierWorker. It
234 // lives only on the CertVerifier's origin message loop.
235 class CertVerifierJob {
236  public:
CertVerifierJob(CertVerifierWorker * worker)237   explicit CertVerifierJob(CertVerifierWorker* worker) : worker_(worker) {
238   }
239 
~CertVerifierJob()240   ~CertVerifierJob() {
241     if (worker_) {
242       worker_->Cancel();
243       DeleteAllCanceled();
244     }
245   }
246 
AddRequest(CertVerifierRequest * request)247   void AddRequest(CertVerifierRequest* request) {
248     requests_.push_back(request);
249   }
250 
HandleResult(const CachedCertVerifyResult & verify_result)251   void HandleResult(const CachedCertVerifyResult& verify_result) {
252     worker_ = NULL;
253     PostAll(verify_result);
254   }
255 
256  private:
PostAll(const CachedCertVerifyResult & verify_result)257   void PostAll(const CachedCertVerifyResult& verify_result) {
258     std::vector<CertVerifierRequest*> requests;
259     requests_.swap(requests);
260 
261     for (std::vector<CertVerifierRequest*>::iterator
262          i = requests.begin(); i != requests.end(); i++) {
263       (*i)->Post(verify_result);
264       // Post() causes the CertVerifierRequest to delete itself.
265     }
266   }
267 
DeleteAllCanceled()268   void DeleteAllCanceled() {
269     for (std::vector<CertVerifierRequest*>::iterator
270          i = requests_.begin(); i != requests_.end(); i++) {
271       if ((*i)->canceled()) {
272         delete *i;
273       } else {
274         LOG(DFATAL) << "CertVerifierRequest leaked!";
275       }
276     }
277   }
278 
279   std::vector<CertVerifierRequest*> requests_;
280   CertVerifierWorker* worker_;
281 };
282 
283 
CertVerifier()284 CertVerifier::CertVerifier()
285     : time_service_(new DefaultTimeService),
286       requests_(0),
287       cache_hits_(0),
288       inflight_joins_(0) {
289   CertDatabase::AddObserver(this);
290 }
291 
CertVerifier(TimeService * time_service)292 CertVerifier::CertVerifier(TimeService* time_service)
293     : time_service_(time_service),
294       requests_(0),
295       cache_hits_(0),
296       inflight_joins_(0) {
297   CertDatabase::AddObserver(this);
298 }
299 
~CertVerifier()300 CertVerifier::~CertVerifier() {
301   STLDeleteValues(&inflight_);
302 
303   CertDatabase::RemoveObserver(this);
304 }
305 
Verify(X509Certificate * cert,const std::string & hostname,int flags,CertVerifyResult * verify_result,CompletionCallback * callback,RequestHandle * out_req)306 int CertVerifier::Verify(X509Certificate* cert,
307                          const std::string& hostname,
308                          int flags,
309                          CertVerifyResult* verify_result,
310                          CompletionCallback* callback,
311                          RequestHandle* out_req) {
312   DCHECK(CalledOnValidThread());
313 
314   if (!callback || !verify_result || hostname.empty()) {
315     *out_req = NULL;
316     return ERR_INVALID_ARGUMENT;
317   }
318 
319   requests_++;
320 
321   const RequestParams key = {cert->fingerprint(), hostname, flags};
322   // First check the cache.
323   std::map<RequestParams, CachedCertVerifyResult>::iterator i;
324   i = cache_.find(key);
325   if (i != cache_.end()) {
326     if (!i->second.HasExpired(time_service_->Now())) {
327       cache_hits_++;
328       *out_req = NULL;
329       *verify_result = i->second.result;
330       return i->second.error;
331     }
332     // Cache entry has expired.
333     cache_.erase(i);
334   }
335 
336   // No cache hit. See if an identical request is currently in flight.
337   CertVerifierJob* job;
338   std::map<RequestParams, CertVerifierJob*>::const_iterator j;
339   j = inflight_.find(key);
340   if (j != inflight_.end()) {
341     // An identical request is in flight already. We'll just attach our
342     // callback.
343     inflight_joins_++;
344     job = j->second;
345   } else {
346     // Need to make a new request.
347     CertVerifierWorker* worker = new CertVerifierWorker(cert, hostname, flags,
348                                                         this);
349     job = new CertVerifierJob(worker);
350     if (!worker->Start()) {
351       delete job;
352       delete worker;
353       *out_req = NULL;
354       // TODO(wtc): log to the NetLog.
355       LOG(ERROR) << "CertVerifierWorker couldn't be started.";
356       return ERR_INSUFFICIENT_RESOURCES;  // Just a guess.
357     }
358     inflight_.insert(std::make_pair(key, job));
359   }
360 
361   CertVerifierRequest* request =
362       new CertVerifierRequest(callback, verify_result);
363   job->AddRequest(request);
364   *out_req = request;
365   return ERR_IO_PENDING;
366 }
367 
CancelRequest(RequestHandle req)368 void CertVerifier::CancelRequest(RequestHandle req) {
369   DCHECK(CalledOnValidThread());
370   CertVerifierRequest* request = reinterpret_cast<CertVerifierRequest*>(req);
371   request->Cancel();
372 }
373 
ClearCache()374 void CertVerifier::ClearCache() {
375   DCHECK(CalledOnValidThread());
376 
377   cache_.clear();
378   // Leaves inflight_ alone.
379 }
380 
GetCacheSize() const381 size_t CertVerifier::GetCacheSize() const {
382   DCHECK(CalledOnValidThread());
383 
384   return cache_.size();
385 }
386 
387 // HandleResult is called by CertVerifierWorker on the origin message loop.
388 // It deletes CertVerifierJob.
HandleResult(X509Certificate * cert,const std::string & hostname,int flags,int error,const CertVerifyResult & verify_result)389 void CertVerifier::HandleResult(X509Certificate* cert,
390                                 const std::string& hostname,
391                                 int flags,
392                                 int error,
393                                 const CertVerifyResult& verify_result) {
394   DCHECK(CalledOnValidThread());
395 
396   const base::Time current_time(time_service_->Now());
397 
398   CachedCertVerifyResult cached_result;
399   cached_result.error = error;
400   cached_result.result = verify_result;
401   uint32 ttl = kTTLSecs;
402   cached_result.expiry = current_time + base::TimeDelta::FromSeconds(ttl);
403 
404   const RequestParams key = {cert->fingerprint(), hostname, flags};
405 
406   DCHECK_GE(kMaxCacheEntries, 1u);
407   DCHECK_LE(cache_.size(), kMaxCacheEntries);
408   if (cache_.size() == kMaxCacheEntries) {
409     // Need to remove an element of the cache.
410     std::map<RequestParams, CachedCertVerifyResult>::iterator i, cur;
411     for (i = cache_.begin(); i != cache_.end(); ) {
412       cur = i++;
413       if (cur->second.HasExpired(current_time))
414         cache_.erase(cur);
415     }
416   }
417   if (cache_.size() == kMaxCacheEntries) {
418     // If we didn't clear out any expired entries, we just remove the first
419     // element. Crummy but simple.
420     cache_.erase(cache_.begin());
421   }
422 
423   cache_.insert(std::make_pair(key, cached_result));
424 
425   std::map<RequestParams, CertVerifierJob*>::iterator j;
426   j = inflight_.find(key);
427   if (j == inflight_.end()) {
428     NOTREACHED();
429     return;
430   }
431   CertVerifierJob* job = j->second;
432   inflight_.erase(j);
433 
434   job->HandleResult(cached_result);
435   delete job;
436 }
437 
OnCertTrustChanged(const X509Certificate * cert)438 void CertVerifier::OnCertTrustChanged(const X509Certificate* cert) {
439   DCHECK(CalledOnValidThread());
440 
441   ClearCache();
442 }
443 
444 /////////////////////////////////////////////////////////////////////
445 
SingleRequestCertVerifier(CertVerifier * cert_verifier)446 SingleRequestCertVerifier::SingleRequestCertVerifier(
447     CertVerifier* cert_verifier)
448     : cert_verifier_(cert_verifier),
449       cur_request_(NULL),
450       cur_request_callback_(NULL),
451       ALLOW_THIS_IN_INITIALIZER_LIST(
452           callback_(this, &SingleRequestCertVerifier::OnVerifyCompletion)) {
453   DCHECK(cert_verifier_ != NULL);
454 }
455 
~SingleRequestCertVerifier()456 SingleRequestCertVerifier::~SingleRequestCertVerifier() {
457   if (cur_request_) {
458     cert_verifier_->CancelRequest(cur_request_);
459     cur_request_ = NULL;
460   }
461 }
462 
Verify(X509Certificate * cert,const std::string & hostname,int flags,CertVerifyResult * verify_result,CompletionCallback * callback)463 int SingleRequestCertVerifier::Verify(X509Certificate* cert,
464                                       const std::string& hostname,
465                                       int flags,
466                                       CertVerifyResult* verify_result,
467                                       CompletionCallback* callback) {
468   // Should not be already in use.
469   DCHECK(!cur_request_ && !cur_request_callback_);
470 
471   // Do a synchronous verification.
472   if (!callback)
473     return cert->Verify(hostname, flags, verify_result);
474 
475   CertVerifier::RequestHandle request = NULL;
476 
477   // We need to be notified of completion before |callback| is called, so that
478   // we can clear out |cur_request_*|.
479   int rv = cert_verifier_->Verify(
480       cert, hostname, flags, verify_result, &callback_, &request);
481 
482   if (rv == ERR_IO_PENDING) {
483     // Cleared in OnVerifyCompletion().
484     cur_request_ = request;
485     cur_request_callback_ = callback;
486   }
487 
488   return rv;
489 }
490 
OnVerifyCompletion(int result)491 void SingleRequestCertVerifier::OnVerifyCompletion(int result) {
492   DCHECK(cur_request_ && cur_request_callback_);
493 
494   CompletionCallback* callback = cur_request_callback_;
495 
496   // Clear the outstanding request information.
497   cur_request_ = NULL;
498   cur_request_callback_ = NULL;
499 
500   // Call the user's original callback.
501   callback->Run(result);
502 }
503 
504 }  // namespace net
505 
506 DISABLE_RUNNABLE_METHOD_REFCOUNT(net::CertVerifierWorker);
507