Index: net/base/cert_verifier.cc |
=================================================================== |
--- net/base/cert_verifier.cc (revision 69359) |
+++ net/base/cert_verifier.cc (working copy) |
@@ -1,45 +1,158 @@ |
-// Copyright (c) 2008 The Chromium Authors. All rights reserved. |
+// Copyright (c) 2010 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/base/cert_verifier.h" |
-#if defined(USE_NSS) |
-#include <private/pprthred.h> // PR_DetatchThread |
-#endif |
- |
+#include "base/compiler_specific.h" |
#include "base/lock.h" |
-#include "base/message_loop_proxy.h" |
-#include "base/scoped_ptr.h" |
+#include "base/message_loop.h" |
+#include "base/stl_util-inl.h" |
#include "base/worker_pool.h" |
-#include "net/base/cert_verify_result.h" |
#include "net/base/net_errors.h" |
#include "net/base/x509_certificate.h" |
+#if defined(USE_NSS) |
+#include <private/pprthred.h> // PR_DetachThread |
+#endif |
+ |
namespace net { |
-class CertVerifier::Request : |
- public base::RefCountedThreadSafe<CertVerifier::Request> { |
+//////////////////////////////////////////////////////////////////////////// |
+ |
+// Life of a request: |
+// |
+// CertVerifier 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, CertVerifier::Verify() returns synchronously without |
+// posting a task to a worker thread. |
+ |
+// The number of CachedCertVerifyResult objects that we'll cache. |
+static const unsigned kMaxCacheEntries = 256; |
+ |
+// The number of seconds for which we'll cache a cache entry. |
+static const unsigned kTTLSecs = 1800; // 30 minutes. |
+ |
+namespace { |
+ |
+class DefaultTimeService : public CertVerifier::TimeService { |
public: |
- Request(CertVerifier* verifier, |
- X509Certificate* cert, |
- const std::string& hostname, |
- int flags, |
- CertVerifyResult* verify_result, |
- CompletionCallback* callback) |
+ // CertVerifier::TimeService methods: |
+ virtual base::Time Now() { return base::Time::Now(); } |
+}; |
+ |
+} // namespace |
+ |
+CachedCertVerifyResult::CachedCertVerifyResult() : error(ERR_FAILED) { |
+} |
+ |
+CachedCertVerifyResult::~CachedCertVerifyResult() {} |
+ |
+bool CachedCertVerifyResult::HasExpired(const base::Time current_time) const { |
+ return current_time >= expiry; |
+} |
+ |
+// Represents the output and result callback of a request. |
+class CertVerifierRequest { |
+ public: |
+ CertVerifierRequest(CompletionCallback* callback, |
+ CertVerifyResult* verify_result) |
+ : callback_(callback), |
+ verify_result_(verify_result) { |
+ } |
+ |
+ // Ensures that the result callback will never be made. |
+ void Cancel() { |
+ callback_ = NULL; |
+ verify_result_ = NULL; |
+ } |
+ |
+ // Copies the contents of |verify_result| to the caller's |
+ // CertVerifyResult and calls the callback. |
+ void Post(const CachedCertVerifyResult& verify_result) { |
+ if (callback_) { |
+ *verify_result_ = verify_result.result; |
+ callback_->Run(verify_result.error); |
+ } |
+ delete this; |
+ } |
+ |
+ private: |
+ CompletionCallback* callback_; |
+ CertVerifyResult* verify_result_; |
+}; |
+ |
+ |
+// 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(X509Certificate* cert, |
+ const std::string& hostname, |
+ int flags, |
+ CertVerifier* cert_verifier) |
: cert_(cert), |
hostname_(hostname), |
flags_(flags), |
- verifier_(verifier), |
- verify_result_(verify_result), |
- callback_(callback), |
- origin_loop_proxy_(base::MessageLoopProxy::CreateForCurrentThread()), |
- error_(OK) { |
+ origin_loop_(MessageLoop::current()), |
+ cert_verifier_(cert_verifier), |
+ canceled_(false), |
+ error_(ERR_FAILED) { |
} |
- void DoVerify() { |
- // Running on the worker thread |
- error_ = cert_->Verify(hostname_, flags_, &result_); |
+ bool Start() { |
+ DCHECK_EQ(MessageLoop::current(), origin_loop_); |
+ |
+ return WorkerPool::PostTask( |
+ FROM_HERE, NewRunnableMethod(this, &CertVerifierWorker::Run), |
+ true /* task is slow */); |
+ } |
+ |
+ // Cancel is called from the origin loop when the CertVerifier is getting |
+ // deleted. |
+ void Cancel() { |
+ DCHECK_EQ(MessageLoop::current(), origin_loop_); |
+ AutoLock locked(lock_); |
+ canceled_ = true; |
+ } |
+ |
+ private: |
+ void Run() { |
+ // Runs on a worker thread. |
+ error_ = cert_->Verify(hostname_, flags_, &verify_result_); |
#if defined(USE_NSS) |
// Detach the thread from NSPR. |
// Calling NSS functions attaches the thread to NSPR, which stores |
@@ -50,109 +163,319 @@ |
// destructors run. |
PR_DetachThread(); |
#endif |
+ Finish(); |
+ } |
- scoped_ptr<Task> reply(NewRunnableMethod(this, &Request::DoCallback)); |
+ // DoReply runs on the origin thread. |
+ void DoReply() { |
+ DCHECK_EQ(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. |
+ AutoLock locked(lock_); |
+ if (!canceled_) { |
+ cert_verifier_->HandleResult(cert_, hostname_, flags_, |
+ error_, verify_result_); |
+ } |
+ } |
+ delete this; |
+ } |
- // The origin loop could go away while we are trying to post to it, so we |
- // need to call its PostTask method inside a lock. See ~CertVerifier. |
- AutoLock locked(origin_loop_proxy_lock_); |
- if (origin_loop_proxy_) { |
- bool posted = origin_loop_proxy_->PostTask(FROM_HERE, reply.release()); |
- // TODO(willchan): Fix leaks and then change this to a DCHECK. |
- LOG_IF(ERROR, !posted) << "Leaked CertVerifier!"; |
+ void Finish() { |
+ // Runs on the worker thread. |
+ // We assume that the origin loop outlives the CertVerifier. If the |
+ // CertVerifier 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 CertVerifier (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; |
+ { |
+ AutoLock locked(lock_); |
+ canceled = canceled_; |
+ if (!canceled) { |
+ origin_loop_->PostTask( |
+ FROM_HERE, NewRunnableMethod(this, &CertVerifierWorker::DoReply)); |
+ } |
} |
+ |
+ if (canceled) |
+ delete this; |
} |
- void DoCallback() { |
- // Running on the origin thread. |
+ scoped_refptr<X509Certificate> cert_; |
+ const std::string hostname_; |
+ const int flags_; |
+ MessageLoop* const origin_loop_; |
+ CertVerifier* const cert_verifier_; |
- // We may have been cancelled! |
- if (!verifier_) |
- return; |
+ // lock_ protects canceled_. |
+ Lock lock_; |
- *verify_result_ = result_; |
+ // If canceled_ is true, |
+ // * origin_loop_ cannot be accessed by the worker thread, |
+ // * cert_verifier_ cannot be accessed by any thread. |
+ bool canceled_; |
- // Drop the verifier's reference to us. Do this before running the |
- // callback since the callback might result in the verifier being |
- // destroyed. |
- verifier_->request_ = NULL; |
+ int error_; |
+ CertVerifyResult verify_result_; |
- callback_->Run(error_); |
+ 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: |
+ explicit CertVerifierJob(CertVerifierWorker* worker) : worker_(worker) { |
} |
- void Cancel() { |
- verifier_ = NULL; |
+ ~CertVerifierJob() { |
+ if (worker_) |
+ worker_->Cancel(); |
+ } |
- AutoLock locked(origin_loop_proxy_lock_); |
- origin_loop_proxy_ = NULL; |
+ void AddRequest(CertVerifierRequest* request) { |
+ requests_.push_back(request); |
} |
+ void HandleResult(const CachedCertVerifyResult& verify_result) { |
+ worker_ = NULL; |
+ PostAll(verify_result); |
+ } |
+ |
private: |
- friend class base::RefCountedThreadSafe<CertVerifier::Request>; |
+ void PostAll(const CachedCertVerifyResult& verify_result) { |
+ std::vector<CertVerifierRequest*> requests; |
+ requests_.swap(requests); |
- ~Request() {} |
+ for (std::vector<CertVerifierRequest*>::iterator |
+ i = requests.begin(); i != requests.end(); i++) { |
+ (*i)->Post(verify_result); |
+ // Post() causes the CertVerifierRequest to delete itself. |
+ } |
+ } |
- // Set on the origin thread, read on the worker thread. |
- scoped_refptr<X509Certificate> cert_; |
- std::string hostname_; |
- // bitwise OR'd of X509Certificate::VerifyFlags. |
- int flags_; |
- |
- // Only used on the origin thread (where Verify was called). |
- CertVerifier* verifier_; |
- CertVerifyResult* verify_result_; |
- CompletionCallback* callback_; |
- |
- // Used to post ourselves onto the origin thread. |
- Lock origin_loop_proxy_lock_; |
- // Use a MessageLoopProxy in case the owner of the CertVerifier is leaked, so |
- // this code won't crash: http://crbug.com/42275. If this is leaked, then it |
- // doesn't get Cancel()'d, so |origin_loop_proxy_| doesn't get NULL'd out. If |
- // the MessageLoop goes away, then if we had used a MessageLoop, this would |
- // crash. |
- scoped_refptr<base::MessageLoopProxy> origin_loop_proxy_; |
- |
- // Assigned on the worker thread, read on the origin thread. |
- int error_; |
- CertVerifyResult result_; |
+ std::vector<CertVerifierRequest*> requests_; |
+ CertVerifierWorker* worker_; |
}; |
-//----------------------------------------------------------------------------- |
-CertVerifier::CertVerifier() { |
+CertVerifier::CertVerifier() |
+ : time_service_(new DefaultTimeService), |
+ requests_(0), |
+ cache_hits_(0), |
+ inflight_joins_(0) { |
} |
+CertVerifier::CertVerifier(TimeService* time_service) |
+ : time_service_(time_service), |
+ requests_(0), |
+ cache_hits_(0), |
+ inflight_joins_(0) { |
+} |
+ |
CertVerifier::~CertVerifier() { |
- if (request_) |
- request_->Cancel(); |
+ STLDeleteValues(&inflight_); |
} |
int CertVerifier::Verify(X509Certificate* cert, |
const std::string& hostname, |
int flags, |
CertVerifyResult* verify_result, |
- CompletionCallback* callback) { |
- DCHECK(!request_) << "verifier already in use"; |
+ CompletionCallback* callback, |
+ RequestHandle* out_req) { |
+ DCHECK(CalledOnValidThread()); |
- // Do a synchronous verification. |
- if (!callback) { |
- CertVerifyResult result; |
- int rv = cert->Verify(hostname, flags, &result); |
- *verify_result = result; |
- return rv; |
+ if (!callback || !verify_result || hostname.empty()) { |
+ *out_req = NULL; |
+ return ERR_INVALID_ARGUMENT; |
} |
- request_ = new Request(this, cert, hostname, flags, verify_result, callback); |
+ requests_++; |
- // Dispatch to worker thread... |
- if (!WorkerPool::PostTask(FROM_HERE, |
- NewRunnableMethod(request_.get(), &Request::DoVerify), true)) { |
- NOTREACHED(); |
- request_ = NULL; |
- return ERR_FAILED; |
+ const RequestParams key = {cert->fingerprint(), hostname, flags}; |
+ // First check the cache. |
+ std::map<RequestParams, CachedCertVerifyResult>::iterator i; |
+ i = cache_.find(key); |
+ if (i != cache_.end()) { |
+ if (!i->second.HasExpired(time_service_->Now())) { |
+ cache_hits_++; |
+ *out_req = NULL; |
+ *verify_result = i->second.result; |
+ return i->second.error; |
+ } |
+ // Cache entry has expired. |
+ cache_.erase(i); |
} |
+ // 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(cert, hostname, flags, |
+ this); |
+ job = new CertVerifierJob(worker); |
+ inflight_.insert(std::make_pair(key, job)); |
+ if (!worker->Start()) { |
+ inflight_.erase(key); |
+ delete job; |
+ delete worker; |
+ *out_req = NULL; |
+ return ERR_FAILED; // TODO(wtc): Log an error message. |
+ } |
+ } |
+ |
+ CertVerifierRequest* request = |
+ new CertVerifierRequest(callback, verify_result); |
+ job->AddRequest(request); |
+ *out_req = request; |
return ERR_IO_PENDING; |
} |
+void CertVerifier::CancelRequest(RequestHandle req) { |
+ DCHECK(CalledOnValidThread()); |
+ CertVerifierRequest* request = reinterpret_cast<CertVerifierRequest*>(req); |
+ request->Cancel(); |
+} |
+ |
+void CertVerifier::ClearCache() { |
+ DCHECK(CalledOnValidThread()); |
+ |
+ cache_.clear(); |
+ // Leaves inflight_ alone. |
+} |
+ |
+size_t CertVerifier::GetCacheSize() const { |
+ DCHECK(CalledOnValidThread()); |
+ |
+ return cache_.size(); |
+} |
+ |
+// HandleResult is called by CertVerifierWorker on the origin message loop. |
+// It deletes CertVerifierJob. |
+void CertVerifier::HandleResult(X509Certificate* cert, |
+ const std::string& hostname, |
+ int flags, |
+ int error, |
+ const CertVerifyResult& verify_result) { |
+ DCHECK(CalledOnValidThread()); |
+ |
+ const base::Time current_time(time_service_->Now()); |
+ |
+ CachedCertVerifyResult cached_result; |
+ cached_result.error = error; |
+ cached_result.result = verify_result; |
+ uint32 ttl = kTTLSecs; |
+ cached_result.expiry = current_time + base::TimeDelta::FromSeconds(ttl); |
+ |
+ const RequestParams key = {cert->fingerprint(), hostname, flags}; |
+ |
+ DCHECK_GE(kMaxCacheEntries, 1u); |
+ DCHECK_LE(cache_.size(), kMaxCacheEntries); |
+ if (cache_.size() == kMaxCacheEntries) { |
+ // Need to remove an element of the cache. |
+ std::map<RequestParams, CachedCertVerifyResult>::iterator i, cur; |
+ for (i = cache_.begin(); i != cache_.end(); ) { |
+ cur = i++; |
+ if (cur->second.HasExpired(current_time)) |
+ cache_.erase(cur); |
+ } |
+ } |
+ if (cache_.size() == kMaxCacheEntries) { |
+ // If we didn't clear out any expired entries, we just remove the first |
+ // element. Crummy but simple. |
+ cache_.erase(cache_.begin()); |
+ } |
+ |
+ cache_.insert(std::make_pair(key, cached_result)); |
+ |
+ 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; |
+} |
+ |
+///////////////////////////////////////////////////////////////////// |
+ |
+SingleRequestCertVerifier::SingleRequestCertVerifier( |
+ CertVerifier* cert_verifier) |
+ : cert_verifier_(cert_verifier), |
+ cur_request_(NULL), |
+ cur_request_callback_(NULL), |
+ ALLOW_THIS_IN_INITIALIZER_LIST( |
+ callback_(this, &SingleRequestCertVerifier::OnVerifyCompletion)) { |
+ DCHECK(cert_verifier_ != NULL); |
+} |
+ |
+SingleRequestCertVerifier::~SingleRequestCertVerifier() { |
+ if (cur_request_) { |
+ cert_verifier_->CancelRequest(cur_request_); |
+ cur_request_ = NULL; |
+ } |
+} |
+ |
+int SingleRequestCertVerifier::Verify(X509Certificate* cert, |
+ const std::string& hostname, |
+ int flags, |
+ CertVerifyResult* verify_result, |
+ CompletionCallback* callback) { |
+ // Should not be already in use. |
+ DCHECK(!cur_request_ && !cur_request_callback_); |
+ |
+ // Do a synchronous verification. |
+ if (!callback) |
+ return cert->Verify(hostname, flags, verify_result); |
+ |
+ CertVerifier::RequestHandle request = NULL; |
+ |
+ // We need to be notified of completion before |callback| is called, so that |
+ // we can clear out |cur_request_*|. |
+ int rv = cert_verifier_->Verify( |
+ cert, hostname, flags, verify_result, &callback_, &request); |
+ |
+ if (rv == ERR_IO_PENDING) { |
+ // Cleared in OnVerifyCompletion(). |
+ cur_request_ = request; |
+ cur_request_callback_ = callback; |
+ } |
+ |
+ return rv; |
+} |
+ |
+void SingleRequestCertVerifier::OnVerifyCompletion(int result) { |
+ DCHECK(cur_request_ && cur_request_callback_); |
+ |
+ CompletionCallback* callback = cur_request_callback_; |
+ |
+ // Clear the outstanding request information. |
+ cur_request_ = NULL; |
+ cur_request_callback_ = NULL; |
+ |
+ // Call the user's original callback. |
+ callback->Run(result); |
+} |
+ |
} // namespace net |
+ |
+DISABLE_RUNNABLE_METHOD_REFCOUNT(net::CertVerifierWorker); |
+ |