Home | History | Annotate | Download | only in cert
      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 
     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_(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.
    207   X509Certificate* certificate() const { return cert_.get(); }
    208 
    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.
    219   void Cancel() {
    220     DCHECK_EQ(base::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_.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.
    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 
    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:
    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 
    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 
    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 
    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:
    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 
    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 
    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 
    402 MultiThreadedCertVerifier::~MultiThreadedCertVerifier() {
    403   STLDeleteValues(&inflight_);
    404   CertDatabase::GetInstance()->RemoveObserver(this);
    405 }
    406 
    407 void MultiThreadedCertVerifier::SetCertTrustAnchorProvider(
    408     CertTrustAnchorProvider* trust_anchor_provider) {
    409   DCHECK(CalledOnValidThread());
    410   trust_anchor_provider_ = trust_anchor_provider;
    411 }
    412 
    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 
    486 void MultiThreadedCertVerifier::CancelRequest(RequestHandle req) {
    487   DCHECK(CalledOnValidThread());
    488   CertVerifierRequest* request = reinterpret_cast<CertVerifierRequest*>(req);
    489   request->Cancel();
    490 }
    491 
    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 
    507 MultiThreadedCertVerifier::RequestParams::~RequestParams() {}
    508 
    509 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.
    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 
    559 void MultiThreadedCertVerifier::OnCertTrustChanged(
    560     const X509Certificate* cert) {
    561   DCHECK(CalledOnValidThread());
    562 
    563   ClearCache();
    564 }
    565 
    566 }  // namespace net
    567