Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(148)

Side by Side Diff: net/base/cert_verifier.cc

Issue 5386001: Cache certificate verification results in memory. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Add unit tests. Ready for review. Created 10 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2008 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "net/base/cert_verifier.h" 5 #include "net/base/cert_verifier.h"
6 6
7 #if defined(USE_NSS) 7 #include "base/compiler_specific.h"
8 #include <private/pprthred.h> // PR_DetatchThread
9 #endif
10
11 #include "base/lock.h" 8 #include "base/lock.h"
12 #include "base/message_loop_proxy.h" 9 #include "base/message_loop.h"
13 #include "base/scoped_ptr.h" 10 #include "base/stl_util-inl.h"
14 #include "base/worker_pool.h" 11 #include "base/worker_pool.h"
15 #include "net/base/cert_verify_result.h"
16 #include "net/base/net_errors.h" 12 #include "net/base/net_errors.h"
17 #include "net/base/x509_certificate.h" 13 #include "net/base/x509_certificate.h"
18 14
15 #if defined(USE_NSS)
16 #include <private/pprthred.h> // PR_DetachThread
17 #endif
18
19 namespace net { 19 namespace net {
20 20
21 class CertVerifier::Request : 21 ////////////////////////////////////////////////////////////////////////////
22 public base::RefCountedThreadSafe<CertVerifier::Request> { 22
23 // Life of a request:
24 //
25 // CertVerifier CertVerifierJob CertVerifierWorker Request
26 // | (origin loop) (worker loop)
27 // |
28 // Verify()
29 // |---->-------------------<creates>
30 // |
31 // |---->----<creates>
32 // |
33 // |---->---------------------------------------------------<creates>
34 // |
35 // |---->--------------------Start
36 // | |
37 // | PostTask
38 // |
39 // | <starts verifying>
40 // |---->-----AddRequest |
41 // |
42 // |
43 // |
44 // Finish
45 // |
46 // PostTask
47 //
48 // |
49 // DoReply
50 // |----<-----------------------|
51 // HandleResult
52 // |
53 // |---->-----HandleResult
54 // |
55 // |------>-----------------------------------Post
56 //
57 //
58 //
59 // On a cache hit, CertVerifier::Verify() returns synchronously without
60 // posting a task to a worker thread.
61
62 // The number of CachedCertVerifyResult objects that we'll cache.
63 static const unsigned kMaxCacheEntries = 64;
agl 2010/12/13 16:25:04 My feeling is that this is probably a little low?
64
65 // The number of seconds for which we'll cache a cache entry.
66 static const unsigned kTTLSecs = 1800; // 30 minutes.
67
68 namespace {
69
70 class DefaultTimeService : public CertVerifier::TimeService {
23 public: 71 public:
24 Request(CertVerifier* verifier, 72 // CertVerifier::TimeService methods:
25 X509Certificate* cert, 73 virtual base::Time Now() { return base::Time::Now(); }
26 const std::string& hostname, 74 };
27 int flags, 75
28 CertVerifyResult* verify_result, 76 } // namespace
29 CompletionCallback* callback) 77
78 CachedCertVerifyResult::CachedCertVerifyResult() : error(ERR_FAILED) {
79 }
80
81 CachedCertVerifyResult::~CachedCertVerifyResult() {}
82
83 bool CachedCertVerifyResult::HasExpired(const base::Time current_time) const {
84 return current_time >= expiry;
85 }
86
87 // Represents the output and result callback of a request.
88 class CertVerifierRequest {
willchan no longer on Chromium 2010/12/13 09:30:53 Should this be within the anonymous namespace? I
89 public:
90 CertVerifierRequest(CompletionCallback* callback,
91 CertVerifyResult* verify_result)
92 : callback_(callback),
93 verify_result_(verify_result) {
94 }
95
96 // Ensures that the result callback will never be made.
97 void Cancel() {
98 callback_ = NULL;
99 verify_result_ = NULL;
100 }
101
102 // Copies the contents of |verify_result| to the caller's
103 // CertVerifyResult and calls the callback.
104 void Post(const CachedCertVerifyResult& verify_result) {
105 if (callback_) {
106 *verify_result_ = verify_result.result;
107 callback_->Run(verify_result.error);
108 }
109 delete this;
110 }
111
112 private:
113 CompletionCallback* callback_;
114 CertVerifyResult* verify_result_;
115 };
116
117
118 // CertVerifierWorker runs on a worker thread and takes care of the blocking
119 // process of performing the certificate verification. Deletes itself
120 // eventually if Start() succeeds.
121 class CertVerifierWorker {
122 public:
123 CertVerifierWorker(X509Certificate* cert,
124 const std::string& hostname,
125 int flags,
126 CertVerifier* cert_verifier)
willchan no longer on Chromium 2010/12/13 09:30:53 I think you should just pass in a callback. Then
30 : cert_(cert), 127 : cert_(cert),
31 hostname_(hostname), 128 hostname_(hostname),
32 flags_(flags), 129 flags_(flags),
33 verifier_(verifier), 130 origin_loop_(MessageLoop::current()),
34 verify_result_(verify_result), 131 cert_verifier_(cert_verifier),
35 callback_(callback), 132 canceled_(false),
36 origin_loop_proxy_(base::MessageLoopProxy::CreateForCurrentThread()), 133 error_(ERR_FAILED) {
37 error_(OK) {
38 } 134 }
39 135
40 void DoVerify() { 136 bool Start() {
41 // Running on the worker thread 137 DCHECK_EQ(MessageLoop::current(), origin_loop_);
willchan no longer on Chromium 2010/12/13 09:30:53 You may want to consider using ThreadChecker inste
42 error_ = cert_->Verify(hostname_, flags_, &result_); 138
139 return WorkerPool::PostTask(
140 FROM_HERE, NewRunnableMethod(this, &CertVerifierWorker::Run),
141 true /* task is slow */);
142 }
143
144 // Cancel is called from the origin loop when the CertVerifier is getting
145 // deleted.
146 void Cancel() {
147 DCHECK_EQ(MessageLoop::current(), origin_loop_);
148 AutoLock locked(lock_);
149 canceled_ = true;
150 }
151
152 private:
153 void Run() {
154 // Runs on a worker thread.
155 error_ = cert_->Verify(hostname_, flags_, &verify_result_);
43 #if defined(USE_NSS) 156 #if defined(USE_NSS)
44 // Detach the thread from NSPR. 157 // Detach the thread from NSPR.
45 // Calling NSS functions attaches the thread to NSPR, which stores 158 // Calling NSS functions attaches the thread to NSPR, which stores
46 // the NSPR thread ID in thread-specific data. 159 // the NSPR thread ID in thread-specific data.
47 // The threads in our thread pool terminate after we have called 160 // The threads in our thread pool terminate after we have called
48 // PR_Cleanup. Unless we detach them from NSPR, net_unittests gets 161 // PR_Cleanup. Unless we detach them from NSPR, net_unittests gets
49 // segfaults on shutdown when the threads' thread-specific data 162 // segfaults on shutdown when the threads' thread-specific data
50 // destructors run. 163 // destructors run.
51 PR_DetachThread(); 164 PR_DetachThread();
52 #endif 165 #endif
53 166 Finish();
54 scoped_ptr<Task> reply(NewRunnableMethod(this, &Request::DoCallback)); 167 }
55 168
56 // The origin loop could go away while we are trying to post to it, so we 169 // DoReply runs on the origin thread.
57 // need to call its PostTask method inside a lock. See ~CertVerifier. 170 void DoReply() {
58 AutoLock locked(origin_loop_proxy_lock_); 171 DCHECK_EQ(MessageLoop::current(), origin_loop_);
59 if (origin_loop_proxy_) { 172 {
60 bool posted = origin_loop_proxy_->PostTask(FROM_HERE, reply.release()); 173 // We lock here because the worker thread could still be in Finished,
61 // TODO(willchan): Fix leaks and then change this to a DCHECK. 174 // after the PostTask, but before unlocking |lock_|. If we do not lock in
62 LOG_IF(ERROR, !posted) << "Leaked CertVerifier!"; 175 // this case, we will end up deleting a locked Lock, which can lead to
63 } 176 // memory leaks or worse errors.
64 } 177 AutoLock locked(lock_);
65 178 if (!canceled_) {
66 void DoCallback() { 179 cert_verifier_->HandleResult(cert_, hostname_, flags_,
67 // Running on the origin thread. 180 error_, verify_result_);
68 181 }
69 // We may have been cancelled! 182 }
70 if (!verifier_) 183 delete this;
71 return; 184 }
72 185
73 *verify_result_ = result_; 186 void Finish() {
74 187 // Runs on the worker thread.
75 // Drop the verifier's reference to us. Do this before running the 188 // We assume that the origin loop outlives the CertVerifier. If the
willchan no longer on Chromium 2010/12/13 09:30:53 See my note below for avoiding this complexity by
76 // callback since the callback might result in the verifier being 189 // CertVerifier is deleted, it will call Cancel on us. If it does so
77 // destroyed. 190 // before the Acquire, we'll delete ourselves and return. If it's trying to
78 verifier_->request_ = NULL; 191 // do so concurrently, then it'll block on the lock and we'll call PostTask
79 192 // while the CertVerifier (and therefore the MessageLoop) is still alive.
80 callback_->Run(error_); 193 // If it does so after this function, we assume that the MessageLoop will
81 } 194 // process pending tasks. In which case we'll notice the |canceled_| flag
82 195 // in DoReply.
83 void Cancel() { 196
84 verifier_ = NULL; 197 bool canceled;
85 198 {
86 AutoLock locked(origin_loop_proxy_lock_); 199 AutoLock locked(lock_);
87 origin_loop_proxy_ = NULL; 200 canceled = canceled_;
201 if (!canceled) {
202 origin_loop_->PostTask(
203 FROM_HERE, NewRunnableMethod(this, &CertVerifierWorker::DoReply));
204 }
205 }
206
207 if (canceled)
208 delete this;
209 }
210
211 scoped_refptr<X509Certificate> cert_;
212 const std::string hostname_;
213 const int flags_;
214 MessageLoop* const origin_loop_;
willchan no longer on Chromium 2010/12/13 09:30:53 Note that if an object is leaked (not canceled pro
215 CertVerifier* const cert_verifier_;
216
217 Lock lock_;
willchan no longer on Chromium 2010/12/13 09:30:53 You should document what |lock_| locks. In partic
agl 2010/12/13 16:25:04 I used to do this by indenting the protected varia
218 bool canceled_;
219
220 int error_;
221 CertVerifyResult verify_result_;
222
223 DISALLOW_COPY_AND_ASSIGN(CertVerifierWorker);
224 };
225
226 // A CertVerifierJob is a one-to-one counterpart of a CertVerifierWorker. It
227 // lives only on the CertVerifier's origin message loop.
228 class CertVerifierJob {
229 public:
230 explicit CertVerifierJob(CertVerifierWorker* worker) : worker_(worker) {
231 }
232
233 ~CertVerifierJob() {
234 if (worker_)
235 worker_->Cancel();
236 }
237
238 void AddRequest(CertVerifierRequest* request) {
239 requests_.push_back(request);
240 }
241
242 void HandleResult(const CachedCertVerifyResult& verify_result) {
243 worker_ = NULL;
244 PostAll(verify_result);
88 } 245 }
89 246
90 private: 247 private:
91 friend class base::RefCountedThreadSafe<CertVerifier::Request>; 248 void PostAll(const CachedCertVerifyResult& verify_result) {
92 249 std::vector<CertVerifierRequest*> requests;
93 ~Request() {} 250 requests_.swap(requests);
94 251
95 // Set on the origin thread, read on the worker thread. 252 for (std::vector<CertVerifierRequest*>::iterator
96 scoped_refptr<X509Certificate> cert_; 253 i = requests.begin(); i != requests.end(); i++) {
97 std::string hostname_; 254 (*i)->Post(verify_result);
98 // bitwise OR'd of X509Certificate::VerifyFlags. 255 // Post() causes the CertVerifierRequest to delete itself.
99 int flags_; 256 }
100 257 }
101 // Only used on the origin thread (where Verify was called). 258
102 CertVerifier* verifier_; 259 std::vector<CertVerifierRequest*> requests_;
103 CertVerifyResult* verify_result_; 260 CertVerifierWorker* worker_;
104 CompletionCallback* callback_;
105
106 // Used to post ourselves onto the origin thread.
107 Lock origin_loop_proxy_lock_;
108 // Use a MessageLoopProxy in case the owner of the CertVerifier is leaked, so
109 // this code won't crash: http://crbug.com/42275. If this is leaked, then it
110 // doesn't get Cancel()'d, so |origin_loop_proxy_| doesn't get NULL'd out. If
111 // the MessageLoop goes away, then if we had used a MessageLoop, this would
112 // crash.
113 scoped_refptr<base::MessageLoopProxy> origin_loop_proxy_;
114
115 // Assigned on the worker thread, read on the origin thread.
116 int error_;
117 CertVerifyResult result_;
118 }; 261 };
119 262
120 //----------------------------------------------------------------------------- 263
121 264 CertVerifier::CertVerifier()
122 CertVerifier::CertVerifier() { 265 : time_service_(new DefaultTimeService),
266 requests_(0),
267 cache_hits_(0),
268 inflight_joins_(0) {
269 }
270
271 CertVerifier::CertVerifier(TimeService* time_service)
272 : time_service_(time_service),
273 requests_(0),
274 cache_hits_(0),
275 inflight_joins_(0) {
123 } 276 }
124 277
125 CertVerifier::~CertVerifier() { 278 CertVerifier::~CertVerifier() {
126 if (request_) 279 STLDeleteValues(&inflight_);
127 request_->Cancel();
128 } 280 }
129 281
130 int CertVerifier::Verify(X509Certificate* cert, 282 int CertVerifier::Verify(X509Certificate* cert,
131 const std::string& hostname, 283 const std::string& hostname,
132 int flags, 284 int flags,
133 CertVerifyResult* verify_result, 285 CertVerifyResult* verify_result,
134 CompletionCallback* callback) { 286 CompletionCallback* callback,
135 DCHECK(!request_) << "verifier already in use"; 287 RequestHandle* out_req) {
288 DCHECK(CalledOnValidThread());
289
290 if (!callback || !verify_result || hostname.empty()) {
willchan no longer on Chromium 2010/12/13 09:30:53 You should probably add a NOTREACHED(). These rep
291 *out_req = NULL;
292 return ERR_INVALID_ARGUMENT;
293 }
294
295 requests_++;
296
297 const RequestParams key = {cert->fingerprint(), hostname, flags};
298 // First check the cache.
299 std::map<RequestParams, CachedCertVerifyResult>::iterator i;
300 i = cache_.find(key);
301 if (i != cache_.end()) {
302 if (!i->second.HasExpired(time_service_->Now())) {
303 cache_hits_++;
304 *out_req = NULL;
305 *verify_result = i->second.result;
306 return i->second.error;
307 }
308 // Cache entry has expired.
309 cache_.erase(i);
310 }
311
312 // No cache hit. See if an identical request is currently in flight.
313 CertVerifierJob* job;
314 std::map<RequestParams, CertVerifierJob*>::const_iterator j;
315 j = inflight_.find(key);
316 if (j != inflight_.end()) {
317 // An identical request is in flight already. We'll just attach our
318 // callback.
319 inflight_joins_++;
320 job = j->second;
321 } else {
322 // Need to make a new request.
323 CertVerifierWorker* worker = new CertVerifierWorker(cert, hostname, flags,
324 this);
325 job = new CertVerifierJob(worker);
326 inflight_.insert(std::make_pair(key, job));
willchan no longer on Chromium 2010/12/13 09:30:53 Should this be moved after the conditional? So we
agl 2010/12/13 16:25:04 I think, originally, my code could reenter this ob
327 if (!worker->Start()) {
328 inflight_.erase(key);
329 delete job;
330 delete worker;
331 *out_req = NULL;
332 return ERR_FAILED; // TODO(wtc): Log an error message.
willchan no longer on Chromium 2010/12/13 09:30:53 Should this be ERR_UNEXPECTED?
333 }
334 }
335
336 CertVerifierRequest* request =
337 new CertVerifierRequest(callback, verify_result);
338 job->AddRequest(request);
339 *out_req = request;
340 return ERR_IO_PENDING;
341 }
342
343 void CertVerifier::CancelRequest(RequestHandle req) {
344 DCHECK(CalledOnValidThread());
345 CertVerifierRequest* request = reinterpret_cast<CertVerifierRequest*>(req);
346 request->Cancel();
347 }
348
349 void CertVerifier::ClearCache() {
350 DCHECK(CalledOnValidThread());
351
352 cache_.clear();
353 // Leaves inflight_ alone.
354 }
355
356 int CertVerifier::GetCacheSize() const {
357 DCHECK(CalledOnValidThread());
358
359 return cache_.size();
360 }
361
362 // HandleResult is called by CertVerifierWorker on the origin message loop.
363 // It deletes CertVerifierJob.
364 void CertVerifier::HandleResult(X509Certificate* cert,
365 const std::string& hostname,
366 int flags,
367 int error,
368 const CertVerifyResult& verify_result) {
369 DCHECK(CalledOnValidThread());
370
371 const base::Time current_time(time_service_->Now());
372
373 CachedCertVerifyResult cached_result;
374 cached_result.error = error;
375 cached_result.result = verify_result;
376 uint32 ttl = kTTLSecs;
377 cached_result.expiry = current_time + base::TimeDelta::FromSeconds(ttl);
378
379 const RequestParams key = {cert->fingerprint(), hostname, flags};
380
381 DCHECK_GE(kMaxCacheEntries, 1u);
382 DCHECK_LE(cache_.size(), kMaxCacheEntries);
383 if (cache_.size() == kMaxCacheEntries) {
384 // Need to remove an element of the cache.
385 std::map<RequestParams, CachedCertVerifyResult>::iterator i, cur;
386 for (i = cache_.begin(); i != cache_.end(); ) {
387 cur = i++;
388 if (cur->second.HasExpired(current_time))
389 cache_.erase(cur);
390 }
391 }
392 if (cache_.size() == kMaxCacheEntries) {
393 // If we didn't clear out any expired entries, we just remove the first
394 // element. Crummy but simple.
395 cache_.erase(cache_.begin());
396 }
397
398 cache_.insert(std::make_pair(key, cached_result));
399
400 std::map<RequestParams, CertVerifierJob*>::iterator j;
401 j = inflight_.find(key);
402 if (j == inflight_.end()) {
403 NOTREACHED();
404 return;
405 }
406 CertVerifierJob* job = j->second;
407 inflight_.erase(j);
408
409 job->HandleResult(cached_result);
410 delete job;
411 }
412
413 /////////////////////////////////////////////////////////////////////
414
415 SingleRequestCertVerifier::SingleRequestCertVerifier(
416 CertVerifier* cert_verifier)
417 : cert_verifier_(cert_verifier),
418 cur_request_(NULL),
419 cur_request_callback_(NULL),
420 ALLOW_THIS_IN_INITIALIZER_LIST(
421 callback_(this, &SingleRequestCertVerifier::OnVerifyCompletion)) {
422 DCHECK(cert_verifier_ != NULL);
423 }
424
425 SingleRequestCertVerifier::~SingleRequestCertVerifier() {
426 if (cur_request_) {
427 cert_verifier_->CancelRequest(cur_request_);
428 cur_request_ = NULL;
429 }
430 }
431
432 int SingleRequestCertVerifier::Verify(X509Certificate* cert,
433 const std::string& hostname,
434 int flags,
435 CertVerifyResult* verify_result,
436 CompletionCallback* callback) {
437 // Should not be already in use.
438 DCHECK(!cur_request_ && !cur_request_callback_);
136 439
137 // Do a synchronous verification. 440 // Do a synchronous verification.
138 if (!callback) { 441 if (!callback)
139 CertVerifyResult result; 442 return cert->Verify(hostname, flags, verify_result);
140 int rv = cert->Verify(hostname, flags, &result); 443
141 *verify_result = result; 444 CertVerifier::RequestHandle request = NULL;
142 return rv; 445
143 } 446 // We need to be notified of completion before |callback| is called, so that
144 447 // we can clear out |cur_request_*|.
145 request_ = new Request(this, cert, hostname, flags, verify_result, callback); 448 int rv = cert_verifier_->Verify(
146 449 cert, hostname, flags, verify_result, &callback_, &request);
147 // Dispatch to worker thread... 450
148 if (!WorkerPool::PostTask(FROM_HERE, 451 if (rv == ERR_IO_PENDING) {
149 NewRunnableMethod(request_.get(), &Request::DoVerify), true)) { 452 // Cleared in OnVerifyCompletion().
150 NOTREACHED(); 453 cur_request_ = request;
151 request_ = NULL; 454 cur_request_callback_ = callback;
152 return ERR_FAILED; 455 }
153 } 456
154 457 return rv;
155 return ERR_IO_PENDING; 458 }
459
460 void SingleRequestCertVerifier::OnVerifyCompletion(int result) {
461 DCHECK(cur_request_ && cur_request_callback_);
462
463 CompletionCallback* callback = cur_request_callback_;
464
465 // Clear the outstanding request information.
466 cur_request_ = NULL;
467 cur_request_callback_ = NULL;
468
469 // Call the user's original callback.
470 callback->Run(result);
156 } 471 }
157 472
158 } // namespace net 473 } // namespace net
474
475 DISABLE_RUNNABLE_METHOD_REFCOUNT(net::CertVerifierWorker);
476
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698