OLD | NEW |
---|---|
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 | |
OLD | NEW |