// Copyright (c) 2011 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"
#include "base/compiler_specific.h"
#include "base/message_loop.h"
#include "base/stl_util-inl.h"
#include "base/synchronization/lock.h"
#include "base/threading/worker_pool.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 {
////////////////////////////////////////////////////////////////////////////
// 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:
// 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;
}
bool canceled() const { return !callback_; }
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),
origin_loop_(MessageLoop::current()),
cert_verifier_(cert_verifier),
canceled_(false),
error_(ERR_FAILED) {
}
bool Start() {
DCHECK_EQ(MessageLoop::current(), origin_loop_);
return base::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_);
base::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
// 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(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_, hostname_, flags_,
error_, verify_result_);
}
}
delete this;
}
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;
{
base::AutoLock locked(lock_);
canceled = canceled_;
if (!canceled) {
origin_loop_->PostTask(
FROM_HERE, NewRunnableMethod(this, &CertVerifierWorker::DoReply));
}
}
if (canceled)
delete this;
}
scoped_refptr<X509Certificate> cert_;
const std::string hostname_;
const int flags_;
MessageLoop* const origin_loop_;
CertVerifier* 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:
explicit CertVerifierJob(CertVerifierWorker* worker) : worker_(worker) {
}
~CertVerifierJob() {
if (worker_) {
worker_->Cancel();
DeleteAllCanceled();
}
}
void AddRequest(CertVerifierRequest* request) {
requests_.push_back(request);
}
void HandleResult(const CachedCertVerifyResult& verify_result) {
worker_ = NULL;
PostAll(verify_result);
}
private:
void PostAll(const CachedCertVerifyResult& 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!";
}
}
}
std::vector<CertVerifierRequest*> requests_;
CertVerifierWorker* worker_;
};
CertVerifier::CertVerifier()
: time_service_(new DefaultTimeService),
requests_(0),
cache_hits_(0),
inflight_joins_(0) {
CertDatabase::AddObserver(this);
}
CertVerifier::CertVerifier(TimeService* time_service)
: time_service_(time_service),
requests_(0),
cache_hits_(0),
inflight_joins_(0) {
CertDatabase::AddObserver(this);
}
CertVerifier::~CertVerifier() {
STLDeleteValues(&inflight_);
CertDatabase::RemoveObserver(this);
}
int CertVerifier::Verify(X509Certificate* cert,
const std::string& hostname,
int flags,
CertVerifyResult* verify_result,
CompletionCallback* callback,
RequestHandle* out_req) {
DCHECK(CalledOnValidThread());
if (!callback || !verify_result || hostname.empty()) {
*out_req = NULL;
return ERR_INVALID_ARGUMENT;
}
requests_++;
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);
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);
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;
}
void CertVerifier::OnCertTrustChanged(const X509Certificate* cert) {
DCHECK(CalledOnValidThread());
ClearCache();
}
/////////////////////////////////////////////////////////////////////
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);