summaryrefslogtreecommitdiff
path: root/chromium/net/cert/multi_threaded_cert_verifier.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/net/cert/multi_threaded_cert_verifier.cc')
-rw-r--r--chromium/net/cert/multi_threaded_cert_verifier.cc566
1 files changed, 566 insertions, 0 deletions
diff --git a/chromium/net/cert/multi_threaded_cert_verifier.cc b/chromium/net/cert/multi_threaded_cert_verifier.cc
new file mode 100644
index 00000000000..821cec1220b
--- /dev/null
+++ b/chromium/net/cert/multi_threaded_cert_verifier.cc
@@ -0,0 +1,566 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/cert/multi_threaded_cert_verifier.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/stl_util.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/worker_pool.h"
+#include "base/time/time.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/cert/cert_trust_anchor_provider.h"
+#include "net/cert/cert_verify_proc.h"
+#include "net/cert/crl_set.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_certificate_net_log_param.h"
+
+#if defined(USE_NSS) || defined(OS_IOS)
+#include <private/pprthred.h> // PR_DetachThread
+#endif
+
+namespace net {
+
+////////////////////////////////////////////////////////////////////////////
+
+// Life of a request:
+//
+// MultiThreadedCertVerifier CertVerifierJob CertVerifierWorker Request
+// | (origin loop) (worker loop)
+// |
+// Verify()
+// |---->-------------------------------------<creates>
+// |
+// |---->-------------------<creates>
+// |
+// |---->-------------------------------------------------------<creates>
+// |
+// |---->---------------------------------------Start
+// | |
+// | PostTask
+// |
+// | <starts verifying>
+// |---->-------------------AddRequest |
+// |
+// |
+// |
+// Finish
+// |
+// PostTask
+//
+// |
+// DoReply
+// |----<-----------------------------------------|
+// HandleResult
+// |
+// |---->------------------HandleResult
+// |
+// |------>---------------------------Post
+//
+//
+//
+// On a cache hit, MultiThreadedCertVerifier::Verify() returns synchronously
+// without posting a task to a worker thread.
+
+namespace {
+
+// The default value of max_cache_entries_.
+const unsigned kMaxCacheEntries = 256;
+
+// The number of seconds for which we'll cache a cache entry.
+const unsigned kTTLSecs = 1800; // 30 minutes.
+
+} // namespace
+
+MultiThreadedCertVerifier::CachedResult::CachedResult() : error(ERR_FAILED) {}
+
+MultiThreadedCertVerifier::CachedResult::~CachedResult() {}
+
+MultiThreadedCertVerifier::CacheValidityPeriod::CacheValidityPeriod(
+ const base::Time& now)
+ : verification_time(now),
+ expiration_time(now) {
+}
+
+MultiThreadedCertVerifier::CacheValidityPeriod::CacheValidityPeriod(
+ const base::Time& now,
+ const base::Time& expiration)
+ : verification_time(now),
+ expiration_time(expiration) {
+}
+
+bool MultiThreadedCertVerifier::CacheExpirationFunctor::operator()(
+ const CacheValidityPeriod& now,
+ const CacheValidityPeriod& expiration) const {
+ // Ensure this functor is being used for expiration only, and not strict
+ // weak ordering/sorting. |now| should only ever contain a single
+ // base::Time.
+ // Note: DCHECK_EQ is not used due to operator<< overloading requirements.
+ DCHECK(now.verification_time == now.expiration_time);
+
+ // |now| contains only a single time (verification_time), while |expiration|
+ // contains the validity range - both when the certificate was verified and
+ // when the verification result should expire.
+ //
+ // If the user receives a "not yet valid" message, and adjusts their clock
+ // foward to the correct time, this will (typically) cause
+ // now.verification_time to advance past expiration.expiration_time, thus
+ // treating the cached result as an expired entry and re-verifying.
+ // If the user receives a "expired" message, and adjusts their clock
+ // backwards to the correct time, this will cause now.verification_time to
+ // be less than expiration_verification_time, thus treating the cached
+ // result as an expired entry and re-verifying.
+ // If the user receives either of those messages, and does not adjust their
+ // clock, then the result will be (typically) be cached until the expiration
+ // TTL.
+ //
+ // This algorithm is only problematic if the user consistently keeps
+ // adjusting their clock backwards in increments smaller than the expiration
+ // TTL, in which case, cached elements continue to be added. However,
+ // because the cache has a fixed upper bound, if no entries are expired, a
+ // 'random' entry will be, thus keeping the memory constraints bounded over
+ // time.
+ return now.verification_time >= expiration.verification_time &&
+ now.verification_time < expiration.expiration_time;
+};
+
+
+// Represents the output and result callback of a request.
+class CertVerifierRequest {
+ public:
+ CertVerifierRequest(const CompletionCallback& callback,
+ CertVerifyResult* verify_result,
+ const BoundNetLog& net_log)
+ : callback_(callback),
+ verify_result_(verify_result),
+ net_log_(net_log) {
+ net_log_.BeginEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST);
+ }
+
+ ~CertVerifierRequest() {
+ }
+
+ // Ensures that the result callback will never be made.
+ void Cancel() {
+ callback_.Reset();
+ verify_result_ = NULL;
+ net_log_.AddEvent(NetLog::TYPE_CANCELLED);
+ net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST);
+ }
+
+ // Copies the contents of |verify_result| to the caller's
+ // CertVerifyResult and calls the callback.
+ void Post(const MultiThreadedCertVerifier::CachedResult& verify_result) {
+ if (!callback_.is_null()) {
+ net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST);
+ *verify_result_ = verify_result.result;
+ callback_.Run(verify_result.error);
+ }
+ delete this;
+ }
+
+ bool canceled() const { return callback_.is_null(); }
+
+ const BoundNetLog& net_log() const { return net_log_; }
+
+ private:
+ CompletionCallback callback_;
+ CertVerifyResult* verify_result_;
+ const BoundNetLog net_log_;
+};
+
+
+// CertVerifierWorker runs on a worker thread and takes care of the blocking
+// process of performing the certificate verification. Deletes itself
+// eventually if Start() succeeds.
+class CertVerifierWorker {
+ public:
+ CertVerifierWorker(CertVerifyProc* verify_proc,
+ X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ MultiThreadedCertVerifier* cert_verifier)
+ : verify_proc_(verify_proc),
+ cert_(cert),
+ hostname_(hostname),
+ flags_(flags),
+ crl_set_(crl_set),
+ additional_trust_anchors_(additional_trust_anchors),
+ origin_loop_(base::MessageLoop::current()),
+ cert_verifier_(cert_verifier),
+ canceled_(false),
+ error_(ERR_FAILED) {
+ }
+
+ // Returns the certificate being verified. May only be called /before/
+ // Start() is called.
+ X509Certificate* certificate() const { return cert_.get(); }
+
+ bool Start() {
+ DCHECK_EQ(base::MessageLoop::current(), origin_loop_);
+
+ return base::WorkerPool::PostTask(
+ FROM_HERE, base::Bind(&CertVerifierWorker::Run, base::Unretained(this)),
+ true /* task is slow */);
+ }
+
+ // Cancel is called from the origin loop when the MultiThreadedCertVerifier is
+ // getting deleted.
+ void Cancel() {
+ DCHECK_EQ(base::MessageLoop::current(), origin_loop_);
+ base::AutoLock locked(lock_);
+ canceled_ = true;
+ }
+
+ private:
+ void Run() {
+ // Runs on a worker thread.
+ error_ = verify_proc_->Verify(cert_.get(),
+ hostname_,
+ flags_,
+ crl_set_.get(),
+ additional_trust_anchors_,
+ &verify_result_);
+#if defined(USE_NSS) || defined(OS_IOS)
+ // Detach the thread from NSPR.
+ // Calling NSS functions attaches the thread to NSPR, which stores
+ // the NSPR thread ID in thread-specific data.
+ // The threads in our thread pool terminate after we have called
+ // PR_Cleanup. Unless we detach them from NSPR, net_unittests gets
+ // segfaults on shutdown when the threads' thread-specific data
+ // destructors run.
+ PR_DetachThread();
+#endif
+ Finish();
+ }
+
+ // DoReply runs on the origin thread.
+ void DoReply() {
+ DCHECK_EQ(base::MessageLoop::current(), origin_loop_);
+ {
+ // We lock here because the worker thread could still be in Finished,
+ // after the PostTask, but before unlocking |lock_|. If we do not lock in
+ // this case, we will end up deleting a locked Lock, which can lead to
+ // memory leaks or worse errors.
+ base::AutoLock locked(lock_);
+ if (!canceled_) {
+ cert_verifier_->HandleResult(cert_.get(),
+ hostname_,
+ flags_,
+ additional_trust_anchors_,
+ error_,
+ verify_result_);
+ }
+ }
+ delete this;
+ }
+
+ void Finish() {
+ // Runs on the worker thread.
+ // We assume that the origin loop outlives the MultiThreadedCertVerifier. If
+ // the MultiThreadedCertVerifier is deleted, it will call Cancel on us. If
+ // it does so before the Acquire, we'll delete ourselves and return. If it's
+ // trying to do so concurrently, then it'll block on the lock and we'll call
+ // PostTask while the MultiThreadedCertVerifier (and therefore the
+ // MessageLoop) is still alive.
+ // If it does so after this function, we assume that the MessageLoop will
+ // process pending tasks. In which case we'll notice the |canceled_| flag
+ // in DoReply.
+
+ bool canceled;
+ {
+ base::AutoLock locked(lock_);
+ canceled = canceled_;
+ if (!canceled) {
+ origin_loop_->PostTask(
+ FROM_HERE, base::Bind(
+ &CertVerifierWorker::DoReply, base::Unretained(this)));
+ }
+ }
+
+ if (canceled)
+ delete this;
+ }
+
+ scoped_refptr<CertVerifyProc> verify_proc_;
+ scoped_refptr<X509Certificate> cert_;
+ const std::string hostname_;
+ const int flags_;
+ scoped_refptr<CRLSet> crl_set_;
+ const CertificateList additional_trust_anchors_;
+ base::MessageLoop* const origin_loop_;
+ MultiThreadedCertVerifier* const cert_verifier_;
+
+ // lock_ protects canceled_.
+ base::Lock lock_;
+
+ // If canceled_ is true,
+ // * origin_loop_ cannot be accessed by the worker thread,
+ // * cert_verifier_ cannot be accessed by any thread.
+ bool canceled_;
+
+ int error_;
+ CertVerifyResult verify_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(CertVerifierWorker);
+};
+
+// A CertVerifierJob is a one-to-one counterpart of a CertVerifierWorker. It
+// lives only on the CertVerifier's origin message loop.
+class CertVerifierJob {
+ public:
+ CertVerifierJob(CertVerifierWorker* worker,
+ const BoundNetLog& net_log)
+ : start_time_(base::TimeTicks::Now()),
+ worker_(worker),
+ net_log_(net_log) {
+ net_log_.BeginEvent(
+ NetLog::TYPE_CERT_VERIFIER_JOB,
+ base::Bind(&NetLogX509CertificateCallback,
+ base::Unretained(worker_->certificate())));
+ }
+
+ ~CertVerifierJob() {
+ if (worker_) {
+ net_log_.AddEvent(NetLog::TYPE_CANCELLED);
+ net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_JOB);
+ worker_->Cancel();
+ DeleteAllCanceled();
+ }
+ }
+
+ void AddRequest(CertVerifierRequest* request) {
+ request->net_log().AddEvent(
+ NetLog::TYPE_CERT_VERIFIER_REQUEST_BOUND_TO_JOB,
+ net_log_.source().ToEventParametersCallback());
+
+ requests_.push_back(request);
+ }
+
+ void HandleResult(
+ const MultiThreadedCertVerifier::CachedResult& verify_result) {
+ worker_ = NULL;
+ net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_JOB);
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.CertVerifier_Job_Latency",
+ base::TimeTicks::Now() - start_time_,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+ PostAll(verify_result);
+ }
+
+ private:
+ void PostAll(const MultiThreadedCertVerifier::CachedResult& verify_result) {
+ std::vector<CertVerifierRequest*> requests;
+ requests_.swap(requests);
+
+ for (std::vector<CertVerifierRequest*>::iterator
+ i = requests.begin(); i != requests.end(); i++) {
+ (*i)->Post(verify_result);
+ // Post() causes the CertVerifierRequest to delete itself.
+ }
+ }
+
+ void DeleteAllCanceled() {
+ for (std::vector<CertVerifierRequest*>::iterator
+ i = requests_.begin(); i != requests_.end(); i++) {
+ if ((*i)->canceled()) {
+ delete *i;
+ } else {
+ LOG(DFATAL) << "CertVerifierRequest leaked!";
+ }
+ }
+ }
+
+ const base::TimeTicks start_time_;
+ std::vector<CertVerifierRequest*> requests_;
+ CertVerifierWorker* worker_;
+ const BoundNetLog net_log_;
+};
+
+MultiThreadedCertVerifier::MultiThreadedCertVerifier(
+ CertVerifyProc* verify_proc)
+ : cache_(kMaxCacheEntries),
+ requests_(0),
+ cache_hits_(0),
+ inflight_joins_(0),
+ verify_proc_(verify_proc),
+ trust_anchor_provider_(NULL) {
+ CertDatabase::GetInstance()->AddObserver(this);
+}
+
+MultiThreadedCertVerifier::~MultiThreadedCertVerifier() {
+ STLDeleteValues(&inflight_);
+ CertDatabase::GetInstance()->RemoveObserver(this);
+}
+
+void MultiThreadedCertVerifier::SetCertTrustAnchorProvider(
+ CertTrustAnchorProvider* trust_anchor_provider) {
+ DCHECK(CalledOnValidThread());
+ trust_anchor_provider_ = trust_anchor_provider;
+}
+
+int MultiThreadedCertVerifier::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) {
+ DCHECK(CalledOnValidThread());
+
+ if (callback.is_null() || !verify_result || hostname.empty()) {
+ *out_req = NULL;
+ return ERR_INVALID_ARGUMENT;
+ }
+
+ requests_++;
+
+ const CertificateList empty_cert_list;
+ const CertificateList& additional_trust_anchors =
+ trust_anchor_provider_ ?
+ trust_anchor_provider_->GetAdditionalTrustAnchors() : empty_cert_list;
+
+ const RequestParams key(cert->fingerprint(), cert->ca_fingerprint(),
+ hostname, flags, additional_trust_anchors);
+ const CertVerifierCache::value_type* cached_entry =
+ cache_.Get(key, CacheValidityPeriod(base::Time::Now()));
+ if (cached_entry) {
+ ++cache_hits_;
+ *out_req = NULL;
+ *verify_result = cached_entry->result;
+ return cached_entry->error;
+ }
+
+ // No cache hit. See if an identical request is currently in flight.
+ CertVerifierJob* job;
+ std::map<RequestParams, CertVerifierJob*>::const_iterator j;
+ j = inflight_.find(key);
+ if (j != inflight_.end()) {
+ // An identical request is in flight already. We'll just attach our
+ // callback.
+ inflight_joins_++;
+ job = j->second;
+ } else {
+ // Need to make a new request.
+ CertVerifierWorker* worker =
+ new CertVerifierWorker(verify_proc_.get(),
+ cert,
+ hostname,
+ flags,
+ crl_set,
+ additional_trust_anchors,
+ this);
+ job = new CertVerifierJob(
+ worker,
+ BoundNetLog::Make(net_log.net_log(), NetLog::SOURCE_CERT_VERIFIER_JOB));
+ if (!worker->Start()) {
+ delete job;
+ delete worker;
+ *out_req = NULL;
+ // TODO(wtc): log to the NetLog.
+ LOG(ERROR) << "CertVerifierWorker couldn't be started.";
+ return ERR_INSUFFICIENT_RESOURCES; // Just a guess.
+ }
+ inflight_.insert(std::make_pair(key, job));
+ }
+
+ CertVerifierRequest* request =
+ new CertVerifierRequest(callback, verify_result, net_log);
+ job->AddRequest(request);
+ *out_req = request;
+ return ERR_IO_PENDING;
+}
+
+void MultiThreadedCertVerifier::CancelRequest(RequestHandle req) {
+ DCHECK(CalledOnValidThread());
+ CertVerifierRequest* request = reinterpret_cast<CertVerifierRequest*>(req);
+ request->Cancel();
+}
+
+MultiThreadedCertVerifier::RequestParams::RequestParams(
+ const SHA1HashValue& cert_fingerprint_arg,
+ const SHA1HashValue& ca_fingerprint_arg,
+ const std::string& hostname_arg,
+ int flags_arg,
+ const CertificateList& additional_trust_anchors)
+ : hostname(hostname_arg),
+ flags(flags_arg) {
+ hash_values.reserve(2 + additional_trust_anchors.size());
+ hash_values.push_back(cert_fingerprint_arg);
+ hash_values.push_back(ca_fingerprint_arg);
+ for (size_t i = 0; i < additional_trust_anchors.size(); ++i)
+ hash_values.push_back(additional_trust_anchors[i]->fingerprint());
+}
+
+MultiThreadedCertVerifier::RequestParams::~RequestParams() {}
+
+bool MultiThreadedCertVerifier::RequestParams::operator<(
+ const RequestParams& other) const {
+ // |flags| is compared before |cert_fingerprint|, |ca_fingerprint|, and
+ // |hostname| under assumption that integer comparisons are faster than
+ // memory and string comparisons.
+ if (flags != other.flags)
+ return flags < other.flags;
+ if (hostname != other.hostname)
+ return hostname < other.hostname;
+ return std::lexicographical_compare(
+ hash_values.begin(), hash_values.end(),
+ other.hash_values.begin(), other.hash_values.end(),
+ net::SHA1HashValueLessThan());
+}
+
+// HandleResult is called by CertVerifierWorker on the origin message loop.
+// It deletes CertVerifierJob.
+void MultiThreadedCertVerifier::HandleResult(
+ X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ const CertificateList& additional_trust_anchors,
+ int error,
+ const CertVerifyResult& verify_result) {
+ DCHECK(CalledOnValidThread());
+
+ const RequestParams key(cert->fingerprint(), cert->ca_fingerprint(),
+ hostname, flags, additional_trust_anchors);
+
+ CachedResult cached_result;
+ cached_result.error = error;
+ cached_result.result = verify_result;
+ base::Time now = base::Time::Now();
+ cache_.Put(
+ key, cached_result, CacheValidityPeriod(now),
+ CacheValidityPeriod(now, now + base::TimeDelta::FromSeconds(kTTLSecs)));
+
+ std::map<RequestParams, CertVerifierJob*>::iterator j;
+ j = inflight_.find(key);
+ if (j == inflight_.end()) {
+ NOTREACHED();
+ return;
+ }
+ CertVerifierJob* job = j->second;
+ inflight_.erase(j);
+
+ job->HandleResult(cached_result);
+ delete job;
+}
+
+void MultiThreadedCertVerifier::OnCertTrustChanged(
+ const X509Certificate* cert) {
+ DCHECK(CalledOnValidThread());
+
+ ClearCache();
+}
+
+} // namespace net