// 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);