diff options
author | wtc@google.com <wtc@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-16 17:27:15 +0000 |
---|---|---|
committer | wtc@google.com <wtc@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-16 17:27:15 +0000 |
commit | 822581d32a6836feae73b96a2ce494a058004423 (patch) | |
tree | 925796acd3c3aeaa357378c096c5d9efec31bf36 /net/base | |
parent | ae89b8d559bfa6b3a2c1d404b21386bcc8995472 (diff) | |
download | chromium_src-822581d32a6836feae73b96a2ce494a058004423.zip chromium_src-822581d32a6836feae73b96a2ce494a058004423.tar.gz chromium_src-822581d32a6836feae73b96a2ce494a058004423.tar.bz2 |
Cache certificate verification results in memory.
R=agl
BUG=63357
TEST=none
Review URL: http://codereview.chromium.org/5386001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@69414 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/base')
-rw-r--r-- | net/base/cert_verifier.cc | 505 | ||||
-rw-r--r-- | net/base/cert_verifier.h | 184 | ||||
-rw-r--r-- | net/base/cert_verifier_unittest.cc | 260 |
3 files changed, 838 insertions, 111 deletions
diff --git a/net/base/cert_verifier.cc b/net/base/cert_verifier.cc index ae910b4..4b3d904 100644 --- a/net/base/cert_verifier.cc +++ b/net/base/cert_verifier.cc @@ -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: + // 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: - Request(CertVerifier* verifier, - X509Certificate* cert, - const std::string& hostname, - int flags, - CertVerifyResult* verify_result, - CompletionCallback* callback) + 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) { + } + + bool Start() { + DCHECK_EQ(MessageLoop::current(), origin_loop_); + + return WorkerPool::PostTask( + FROM_HERE, NewRunnableMethod(this, &CertVerifierWorker::Run), + true /* task is slow */); } - void DoVerify() { - // Running on the worker thread - error_ = cert_->Verify(hostname_, flags_, &result_); + // 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 @@ class CertVerifier::Request : // destructors run. PR_DetachThread(); #endif + Finish(); + } - scoped_ptr<Task> reply(NewRunnableMethod(this, &Request::DoCallback)); - - // 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!"; + // 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; } - void DoCallback() { - // Running on the origin thread. + 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. - // We may have been cancelled! - if (!verifier_) - return; + bool canceled; + { + AutoLock locked(lock_); + canceled = canceled_; + if (!canceled) { + origin_loop_->PostTask( + FROM_HERE, NewRunnableMethod(this, &CertVerifierWorker::DoReply)); + } + } - *verify_result_ = result_; + if (canceled) + delete this; + } - // 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; + scoped_refptr<X509Certificate> cert_; + const std::string hostname_; + const int flags_; + MessageLoop* const origin_loop_; + CertVerifier* const cert_verifier_; - callback_->Run(error_); - } + // lock_ protects canceled_. + Lock lock_; - void Cancel() { - verifier_ = NULL; + // 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); +}; - AutoLock locked(origin_loop_proxy_lock_); - origin_loop_proxy_ = NULL; +// 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) { } - private: - friend class base::RefCountedThreadSafe<CertVerifier::Request>; + ~CertVerifierJob() { + if (worker_) + worker_->Cancel(); + } - ~Request() {} + void AddRequest(CertVerifierRequest* request) { + requests_.push_back(request); + } - // 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_; + void HandleResult(const CachedCertVerifyResult& verify_result) { + worker_ = NULL; + PostAll(verify_result); + } - // Only used on the origin thread (where Verify was called). - CertVerifier* verifier_; - CertVerifyResult* verify_result_; - CompletionCallback* callback_; + private: + void PostAll(const CachedCertVerifyResult& verify_result) { + std::vector<CertVerifierRequest*> requests; + requests_.swap(requests); - // 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_; + for (std::vector<CertVerifierRequest*>::iterator + i = requests.begin(); i != requests.end(); i++) { + (*i)->Post(verify_result); + // Post() causes the CertVerifierRequest to delete itself. + } + } - // 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); + diff --git a/net/base/cert_verifier.h b/net/base/cert_verifier.h index 791f8d3..3d19abb 100644 --- a/net/base/cert_verifier.h +++ b/net/base/cert_verifier.h @@ -1,4 +1,4 @@ -// Copyright (c) 2008-2009 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. @@ -6,29 +6,68 @@ #define NET_BASE_CERT_VERIFIER_H_ #pragma once +#include <map> #include <string> #include "base/basictypes.h" -#include "base/ref_counted.h" +#include "base/non_thread_safe.h" +#include "base/scoped_ptr.h" +#include "base/time.h" +#include "net/base/cert_verify_result.h" #include "net/base/completion_callback.h" +#include "net/base/x509_cert_types.h" namespace net { -class CertVerifyResult; +class CertVerifierJob; +class CertVerifierWorker; class X509Certificate; -// This class represents the task of verifying a certificate. It can only -// verify a single certificate at a time, so if you need to verify multiple -// certificates at the same time, you will need to allocate a CertVerifier -// object for each certificate. +// CachedCertVerifyResult contains the result of a certificate verification. +struct CachedCertVerifyResult { + CachedCertVerifyResult(); + ~CachedCertVerifyResult(); + + int error; // The return value of CertVerifier::Verify. + CertVerifyResult result; // The output of CertVerifier::Verify. + + // The time at which the certificate verification result expires. + base::Time expiry; + + // Returns true if |current_time| is greater than or equal to |expiry|. + bool HasExpired(base::Time current_time) const; +}; + +// CertVerifier represents a service for verifying certificates. // -class CertVerifier { +// CertVerifier can handle multiple requests at a time, so when canceling a +// request the RequestHandle that was returned by Verify() needs to be +// given. A simpler alternative for consumers that only have 1 outstanding +// request at a time is to create a SingleRequestCertVerifier wrapper around +// CertVerifier (which will automatically cancel the single request when it +// goes out of scope). +class CertVerifier : public NonThreadSafe { public: + // Opaque type used to cancel a request. + typedef void* RequestHandle; + + // CertVerifier must not call base::Time::Now() directly. It must call + // time_service_->Now(). This allows unit tests to mock the current time. + class TimeService { + public: + virtual ~TimeService() {} + + virtual base::Time Now() = 0; + }; + CertVerifier(); - // If a completion callback is pending when the verifier is destroyed, the - // certificate verification is cancelled, and the completion callback will - // not be called. + // Used by unit tests to mock the current time. Takes ownership of + // |time_service|. + explicit CertVerifier(TimeService* time_service); + + // When the verifier is destroyed, all certificate verifications requests are + // canceled, and their completion callbacks will not be called. ~CertVerifier(); // Verifies the given certificate against the given hostname. Returns OK if @@ -49,23 +88,128 @@ class CertVerifier { // VERIFY_REV_CHECKING_ENABLED is not set), EV certificate verification will // not be performed. // - // When callback is null, the operation completes synchronously. - // - // When callback is non-null, ERR_IO_PENDING is returned if the operation + // |callback| must not be null. ERR_IO_PENDING is returned if the operation // could not be completed synchronously, in which case the result code will // be passed to the callback when available. // - int Verify(X509Certificate* cert, const std::string& hostname, - int flags, CertVerifyResult* verify_result, - CompletionCallback* callback); + // If |out_req| is non-NULL, then |*out_req| will be filled with a handle to + // the async request. This handle is not valid after the request has + // completed. + int Verify(X509Certificate* cert, + const std::string& hostname, + int flags, + CertVerifyResult* verify_result, + CompletionCallback* callback, + RequestHandle* out_req); + + // Cancels the specified request. |req| is the handle returned by Verify(). + // After a request is canceled, its completion callback will not be called. + void CancelRequest(RequestHandle req); + + // Clears the verification result cache. + void ClearCache(); + + size_t GetCacheSize() const; + + uint64 requests() const { return requests_; } + uint64 cache_hits() const { return cache_hits_; } + uint64 inflight_joins() const { return inflight_joins_; } private: - class Request; - friend class Request; - scoped_refptr<Request> request_; + friend class CertVerifierWorker; // Calls HandleResult. + + // Input parameters of a certificate verification request. + struct RequestParams { + bool operator==(const RequestParams& other) const { + // |flags| is compared before |cert_fingerprint| and |hostname| under + // assumption that integer comparisons are faster than memory and string + // comparisons. + return (flags == other.flags && + memcmp(cert_fingerprint.data, other.cert_fingerprint.data, + sizeof(cert_fingerprint.data)) == 0 && + hostname == other.hostname); + } + + bool operator<(const RequestParams& other) const { + // |flags| is compared before |cert_fingerprint| and |hostname| under + // assumption that integer comparisons are faster than memory and string + // comparisons. + if (flags != other.flags) + return flags < other.flags; + int rv = memcmp(cert_fingerprint.data, other.cert_fingerprint.data, + sizeof(cert_fingerprint.data)); + if (rv != 0) + return rv < 0; + return hostname < other.hostname; + } + + SHA1Fingerprint cert_fingerprint; + std::string hostname; + int flags; + }; + + void HandleResult(X509Certificate* cert, + const std::string& hostname, + int flags, + int error, + const CertVerifyResult& verify_result); + + // cache_ maps from a request to a cached result. The cached result may + // have expired and the size of |cache_| must be <= kMaxCacheEntries. + std::map<RequestParams, CachedCertVerifyResult> cache_; + + // inflight_ maps from a request to an active verification which is taking + // place. + std::map<RequestParams, CertVerifierJob*> inflight_; + + scoped_ptr<TimeService> time_service_; + + uint64 requests_; + uint64 cache_hits_; + uint64 inflight_joins_; + DISALLOW_COPY_AND_ASSIGN(CertVerifier); }; +// This class represents the task of verifying a certificate. It wraps +// CertVerifier to verify only a single certificate at a time and cancels this +// request when going out of scope. +class SingleRequestCertVerifier { + public: + // |cert_verifier| must remain valid for the lifetime of |this|. + explicit SingleRequestCertVerifier(CertVerifier* cert_verifier); + + // If a completion callback is pending when the verifier is destroyed, the + // certificate verification is canceled, and the completion callback will + // not be called. + ~SingleRequestCertVerifier(); + + // Verifies the given certificate, filling out the |verify_result| object + // upon success. See CertVerifier::Verify() for details. + int Verify(X509Certificate* cert, + const std::string& hostname, + int flags, + CertVerifyResult* verify_result, + CompletionCallback* callback); + + private: + // Callback for when the request to |cert_verifier_| completes, so we + // dispatch to the user's callback. + void OnVerifyCompletion(int result); + + // The actual certificate verifier that will handle the request. + CertVerifier* const cert_verifier_; + + // The current request (if any). + CertVerifier::RequestHandle cur_request_; + CompletionCallback* cur_request_callback_; + + // Completion callback for when request to |cert_verifier_| completes. + net::CompletionCallbackImpl<SingleRequestCertVerifier> callback_; + + DISALLOW_COPY_AND_ASSIGN(SingleRequestCertVerifier); +}; + } // namespace net #endif // NET_BASE_CERT_VERIFIER_H_ diff --git a/net/base/cert_verifier_unittest.cc b/net/base/cert_verifier_unittest.cc new file mode 100644 index 0000000..ca5e1f4 --- /dev/null +++ b/net/base/cert_verifier_unittest.cc @@ -0,0 +1,260 @@ +// 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" + +#include "base/callback.h" +#include "base/file_path.h" +#include "base/stringprintf.h" +#include "net/base/cert_test_util.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "net/base/x509_certificate.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +class TestTimeService : public CertVerifier::TimeService { + public: + // CertVerifier::TimeService methods: + virtual base::Time Now() { return current_time_; } + + void set_current_time(base::Time now) { current_time_ = now; } + + private: + base::Time current_time_; +}; + +class CertVerifierTest : public testing::Test { +}; + +class ExplodingCallback : public CallbackRunner<Tuple1<int> > { + public: + virtual void RunWithParams(const Tuple1<int>& params) { + FAIL(); + } +}; + +// Tests a cache hit, which should results in synchronous completion. +TEST_F(CertVerifierTest, CacheHit) { + TestTimeService* time_service = new TestTimeService; + base::Time current_time = base::Time::Now(); + time_service->set_current_time(current_time); + CertVerifier verifier(time_service); + + FilePath certs_dir = GetTestCertsDirectory(); + scoped_refptr<X509Certificate> google_cert( + ImportCertFromFile(certs_dir, "google.single.der")); + ASSERT_NE(static_cast<X509Certificate*>(NULL), google_cert); + + int error; + CertVerifyResult verify_result; + TestCompletionCallback callback; + CertVerifier::RequestHandle request_handle; + + error = verifier.Verify(google_cert, "www.example.com", 0, &verify_result, + &callback, &request_handle); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle != NULL); + error = callback.WaitForResult(); + ASSERT_TRUE(IsCertificateError(error)); + ASSERT_EQ(1u, verifier.requests()); + ASSERT_EQ(0u, verifier.cache_hits()); + ASSERT_EQ(0u, verifier.inflight_joins()); + + error = verifier.Verify(google_cert, "www.example.com", 0, &verify_result, + &callback, &request_handle); + // Synchronous completion. + ASSERT_NE(ERR_IO_PENDING, error); + ASSERT_TRUE(IsCertificateError(error)); + ASSERT_TRUE(request_handle == NULL); + ASSERT_EQ(2u, verifier.requests()); + ASSERT_EQ(1u, verifier.cache_hits()); + ASSERT_EQ(0u, verifier.inflight_joins()); +} + +// Tests an inflight join. +TEST_F(CertVerifierTest, InflightJoin) { + TestTimeService* time_service = new TestTimeService; + base::Time current_time = base::Time::Now(); + time_service->set_current_time(current_time); + CertVerifier verifier(time_service); + + FilePath certs_dir = GetTestCertsDirectory(); + scoped_refptr<X509Certificate> google_cert( + ImportCertFromFile(certs_dir, "google.single.der")); + ASSERT_NE(static_cast<X509Certificate*>(NULL), google_cert); + + int error; + CertVerifyResult verify_result; + TestCompletionCallback callback; + CertVerifier::RequestHandle request_handle; + CertVerifyResult verify_result2; + TestCompletionCallback callback2; + CertVerifier::RequestHandle request_handle2; + + error = verifier.Verify(google_cert, "www.example.com", 0, &verify_result, + &callback, &request_handle); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle != NULL); + error = verifier.Verify(google_cert, "www.example.com", 0, &verify_result2, + &callback2, &request_handle2); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle2 != NULL); + error = callback.WaitForResult(); + ASSERT_TRUE(IsCertificateError(error)); + error = callback2.WaitForResult(); + ASSERT_TRUE(IsCertificateError(error)); + ASSERT_EQ(2u, verifier.requests()); + ASSERT_EQ(0u, verifier.cache_hits()); + ASSERT_EQ(1u, verifier.inflight_joins()); +} + +// Tests cache entry expiration. +TEST_F(CertVerifierTest, ExpiredCacheEntry) { + TestTimeService* time_service = new TestTimeService; + base::Time current_time = base::Time::Now(); + time_service->set_current_time(current_time); + CertVerifier verifier(time_service); + + FilePath certs_dir = GetTestCertsDirectory(); + scoped_refptr<X509Certificate> google_cert( + ImportCertFromFile(certs_dir, "google.single.der")); + ASSERT_NE(static_cast<X509Certificate*>(NULL), google_cert); + + int error; + CertVerifyResult verify_result; + TestCompletionCallback callback; + CertVerifier::RequestHandle request_handle; + + error = verifier.Verify(google_cert, "www.example.com", 0, &verify_result, + &callback, &request_handle); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle != NULL); + error = callback.WaitForResult(); + ASSERT_TRUE(IsCertificateError(error)); + ASSERT_EQ(1u, verifier.requests()); + ASSERT_EQ(0u, verifier.cache_hits()); + ASSERT_EQ(0u, verifier.inflight_joins()); + + // Before expiration, should have a cache hit. + error = verifier.Verify(google_cert, "www.example.com", 0, &verify_result, + &callback, &request_handle); + // Synchronous completion. + ASSERT_NE(ERR_IO_PENDING, error); + ASSERT_TRUE(IsCertificateError(error)); + ASSERT_TRUE(request_handle == NULL); + ASSERT_EQ(2u, verifier.requests()); + ASSERT_EQ(1u, verifier.cache_hits()); + ASSERT_EQ(0u, verifier.inflight_joins()); + + // After expiration, should not have a cache hit. + ASSERT_EQ(1u, verifier.GetCacheSize()); + current_time += base::TimeDelta::FromMinutes(60); + time_service->set_current_time(current_time); + error = verifier.Verify(google_cert, "www.example.com", 0, &verify_result, + &callback, &request_handle); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle != NULL); + ASSERT_EQ(0u, verifier.GetCacheSize()); + error = callback.WaitForResult(); + ASSERT_TRUE(IsCertificateError(error)); + ASSERT_EQ(3u, verifier.requests()); + ASSERT_EQ(1u, verifier.cache_hits()); + ASSERT_EQ(0u, verifier.inflight_joins()); +} + +// Tests a full cache. +TEST_F(CertVerifierTest, FullCache) { + TestTimeService* time_service = new TestTimeService; + base::Time current_time = base::Time::Now(); + time_service->set_current_time(current_time); + CertVerifier verifier(time_service); + + FilePath certs_dir = GetTestCertsDirectory(); + scoped_refptr<X509Certificate> google_cert( + ImportCertFromFile(certs_dir, "google.single.der")); + ASSERT_NE(static_cast<X509Certificate*>(NULL), google_cert); + + int error; + CertVerifyResult verify_result; + TestCompletionCallback callback; + CertVerifier::RequestHandle request_handle; + + error = verifier.Verify(google_cert, "www.example.com", 0, &verify_result, + &callback, &request_handle); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle != NULL); + error = callback.WaitForResult(); + ASSERT_TRUE(IsCertificateError(error)); + ASSERT_EQ(1u, verifier.requests()); + ASSERT_EQ(0u, verifier.cache_hits()); + ASSERT_EQ(0u, verifier.inflight_joins()); + + const unsigned kCacheSize = 256; + + for (unsigned i = 0; i < kCacheSize; i++) { + std::string hostname = base::StringPrintf("www%d.example.com", i + 1); + error = verifier.Verify(google_cert, hostname, 0, &verify_result, + &callback, &request_handle); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle != NULL); + error = callback.WaitForResult(); + ASSERT_TRUE(IsCertificateError(error)); + } + ASSERT_EQ(kCacheSize + 1, verifier.requests()); + ASSERT_EQ(0u, verifier.cache_hits()); + ASSERT_EQ(0u, verifier.inflight_joins()); + + ASSERT_EQ(kCacheSize, verifier.GetCacheSize()); + current_time += base::TimeDelta::FromMinutes(60); + time_service->set_current_time(current_time); + error = verifier.Verify(google_cert, "www999.example.com", 0, &verify_result, + &callback, &request_handle); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle != NULL); + ASSERT_EQ(kCacheSize, verifier.GetCacheSize()); + error = callback.WaitForResult(); + ASSERT_EQ(1u, verifier.GetCacheSize()); + ASSERT_TRUE(IsCertificateError(error)); + ASSERT_EQ(kCacheSize + 2, verifier.requests()); + ASSERT_EQ(0u, verifier.cache_hits()); + ASSERT_EQ(0u, verifier.inflight_joins()); +} + +// Tests that the callback of a canceled request is never made. +TEST_F(CertVerifierTest, CancelRequest) { + CertVerifier verifier; + + FilePath certs_dir = GetTestCertsDirectory(); + scoped_refptr<X509Certificate> google_cert( + ImportCertFromFile(certs_dir, "google.single.der")); + ASSERT_NE(static_cast<X509Certificate*>(NULL), google_cert); + + int error; + CertVerifyResult verify_result; + ExplodingCallback exploding_callback; + CertVerifier::RequestHandle request_handle; + + error = verifier.Verify(google_cert, "www.example.com", 0, &verify_result, + &exploding_callback, &request_handle); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle != NULL); + verifier.CancelRequest(request_handle); + + // Issue a few more requests to the worker pool and wait for their + // completion, so that the task of the canceled request (which runs on a + // worker thread) is likely to complete by the end of this test. + TestCompletionCallback callback; + for (int i = 0; i < 5; ++i) { + error = verifier.Verify(google_cert, "www2.example.com", 0, &verify_result, + &callback, &request_handle); + ASSERT_EQ(ERR_IO_PENDING, error); + ASSERT_TRUE(request_handle != NULL); + error = callback.WaitForResult(); + verifier.ClearCache(); + } +} + +} // namespace net |