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 = 256; |
| 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 { |
| 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) |
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_); |
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 |
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_; |
| 215 CertVerifier* const cert_verifier_; |
| 216 |
| 217 // lock_ protects canceled_. |
| 218 Lock lock_; |
| 219 |
| 220 // If canceled_ is true, |
| 221 // * origin_loop_ cannot be accessed by the worker thread, |
| 222 // * cert_verifier_ cannot be accessed by any thread. |
| 223 bool canceled_; |
| 224 |
| 225 int error_; |
| 226 CertVerifyResult verify_result_; |
| 227 |
| 228 DISALLOW_COPY_AND_ASSIGN(CertVerifierWorker); |
| 229 }; |
| 230 |
| 231 // A CertVerifierJob is a one-to-one counterpart of a CertVerifierWorker. It |
| 232 // lives only on the CertVerifier's origin message loop. |
| 233 class CertVerifierJob { |
| 234 public: |
| 235 explicit CertVerifierJob(CertVerifierWorker* worker) : worker_(worker) { |
| 236 } |
| 237 |
| 238 ~CertVerifierJob() { |
| 239 if (worker_) |
| 240 worker_->Cancel(); |
| 241 } |
| 242 |
| 243 void AddRequest(CertVerifierRequest* request) { |
| 244 requests_.push_back(request); |
| 245 } |
| 246 |
| 247 void HandleResult(const CachedCertVerifyResult& verify_result) { |
| 248 worker_ = NULL; |
| 249 PostAll(verify_result); |
88 } | 250 } |
89 | 251 |
90 private: | 252 private: |
91 friend class base::RefCountedThreadSafe<CertVerifier::Request>; | 253 void PostAll(const CachedCertVerifyResult& verify_result) { |
92 | 254 std::vector<CertVerifierRequest*> requests; |
93 ~Request() {} | 255 requests_.swap(requests); |
94 | 256 |
95 // Set on the origin thread, read on the worker thread. | 257 for (std::vector<CertVerifierRequest*>::iterator |
96 scoped_refptr<X509Certificate> cert_; | 258 i = requests.begin(); i != requests.end(); i++) { |
97 std::string hostname_; | 259 (*i)->Post(verify_result); |
98 // bitwise OR'd of X509Certificate::VerifyFlags. | 260 // Post() causes the CertVerifierRequest to delete itself. |
99 int flags_; | 261 } |
100 | 262 } |
101 // Only used on the origin thread (where Verify was called). | 263 |
102 CertVerifier* verifier_; | 264 std::vector<CertVerifierRequest*> requests_; |
103 CertVerifyResult* verify_result_; | 265 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 }; | 266 }; |
119 | 267 |
120 //----------------------------------------------------------------------------- | 268 |
121 | 269 CertVerifier::CertVerifier() |
122 CertVerifier::CertVerifier() { | 270 : time_service_(new DefaultTimeService), |
| 271 requests_(0), |
| 272 cache_hits_(0), |
| 273 inflight_joins_(0) { |
| 274 } |
| 275 |
| 276 CertVerifier::CertVerifier(TimeService* time_service) |
| 277 : time_service_(time_service), |
| 278 requests_(0), |
| 279 cache_hits_(0), |
| 280 inflight_joins_(0) { |
123 } | 281 } |
124 | 282 |
125 CertVerifier::~CertVerifier() { | 283 CertVerifier::~CertVerifier() { |
126 if (request_) | 284 STLDeleteValues(&inflight_); |
127 request_->Cancel(); | |
128 } | 285 } |
129 | 286 |
130 int CertVerifier::Verify(X509Certificate* cert, | 287 int CertVerifier::Verify(X509Certificate* cert, |
131 const std::string& hostname, | 288 const std::string& hostname, |
132 int flags, | 289 int flags, |
133 CertVerifyResult* verify_result, | 290 CertVerifyResult* verify_result, |
134 CompletionCallback* callback) { | 291 CompletionCallback* callback, |
135 DCHECK(!request_) << "verifier already in use"; | 292 RequestHandle* out_req) { |
| 293 DCHECK(CalledOnValidThread()); |
| 294 |
| 295 if (!callback || !verify_result || hostname.empty()) { |
| 296 *out_req = NULL; |
| 297 return ERR_INVALID_ARGUMENT; |
| 298 } |
| 299 |
| 300 requests_++; |
| 301 |
| 302 const RequestParams key = {cert->fingerprint(), hostname, flags}; |
| 303 // First check the cache. |
| 304 std::map<RequestParams, CachedCertVerifyResult>::iterator i; |
| 305 i = cache_.find(key); |
| 306 if (i != cache_.end()) { |
| 307 if (!i->second.HasExpired(time_service_->Now())) { |
| 308 cache_hits_++; |
| 309 *out_req = NULL; |
| 310 *verify_result = i->second.result; |
| 311 return i->second.error; |
| 312 } |
| 313 // Cache entry has expired. |
| 314 cache_.erase(i); |
| 315 } |
| 316 |
| 317 // No cache hit. See if an identical request is currently in flight. |
| 318 CertVerifierJob* job; |
| 319 std::map<RequestParams, CertVerifierJob*>::const_iterator j; |
| 320 j = inflight_.find(key); |
| 321 if (j != inflight_.end()) { |
| 322 // An identical request is in flight already. We'll just attach our |
| 323 // callback. |
| 324 inflight_joins_++; |
| 325 job = j->second; |
| 326 } else { |
| 327 // Need to make a new request. |
| 328 CertVerifierWorker* worker = new CertVerifierWorker(cert, hostname, flags, |
| 329 this); |
| 330 job = new CertVerifierJob(worker); |
| 331 inflight_.insert(std::make_pair(key, job)); |
| 332 if (!worker->Start()) { |
| 333 inflight_.erase(key); |
| 334 delete job; |
| 335 delete worker; |
| 336 *out_req = NULL; |
| 337 return ERR_FAILED; // TODO(wtc): Log an error message. |
| 338 } |
| 339 } |
| 340 |
| 341 CertVerifierRequest* request = |
| 342 new CertVerifierRequest(callback, verify_result); |
| 343 job->AddRequest(request); |
| 344 *out_req = request; |
| 345 return ERR_IO_PENDING; |
| 346 } |
| 347 |
| 348 void CertVerifier::CancelRequest(RequestHandle req) { |
| 349 DCHECK(CalledOnValidThread()); |
| 350 CertVerifierRequest* request = reinterpret_cast<CertVerifierRequest*>(req); |
| 351 request->Cancel(); |
| 352 } |
| 353 |
| 354 void CertVerifier::ClearCache() { |
| 355 DCHECK(CalledOnValidThread()); |
| 356 |
| 357 cache_.clear(); |
| 358 // Leaves inflight_ alone. |
| 359 } |
| 360 |
| 361 size_t CertVerifier::GetCacheSize() const { |
| 362 DCHECK(CalledOnValidThread()); |
| 363 |
| 364 return cache_.size(); |
| 365 } |
| 366 |
| 367 // HandleResult is called by CertVerifierWorker on the origin message loop. |
| 368 // It deletes CertVerifierJob. |
| 369 void CertVerifier::HandleResult(X509Certificate* cert, |
| 370 const std::string& hostname, |
| 371 int flags, |
| 372 int error, |
| 373 const CertVerifyResult& verify_result) { |
| 374 DCHECK(CalledOnValidThread()); |
| 375 |
| 376 const base::Time current_time(time_service_->Now()); |
| 377 |
| 378 CachedCertVerifyResult cached_result; |
| 379 cached_result.error = error; |
| 380 cached_result.result = verify_result; |
| 381 uint32 ttl = kTTLSecs; |
| 382 cached_result.expiry = current_time + base::TimeDelta::FromSeconds(ttl); |
| 383 |
| 384 const RequestParams key = {cert->fingerprint(), hostname, flags}; |
| 385 |
| 386 DCHECK_GE(kMaxCacheEntries, 1u); |
| 387 DCHECK_LE(cache_.size(), kMaxCacheEntries); |
| 388 if (cache_.size() == kMaxCacheEntries) { |
| 389 // Need to remove an element of the cache. |
| 390 std::map<RequestParams, CachedCertVerifyResult>::iterator i, cur; |
| 391 for (i = cache_.begin(); i != cache_.end(); ) { |
| 392 cur = i++; |
| 393 if (cur->second.HasExpired(current_time)) |
| 394 cache_.erase(cur); |
| 395 } |
| 396 } |
| 397 if (cache_.size() == kMaxCacheEntries) { |
| 398 // If we didn't clear out any expired entries, we just remove the first |
| 399 // element. Crummy but simple. |
| 400 cache_.erase(cache_.begin()); |
| 401 } |
| 402 |
| 403 cache_.insert(std::make_pair(key, cached_result)); |
| 404 |
| 405 std::map<RequestParams, CertVerifierJob*>::iterator j; |
| 406 j = inflight_.find(key); |
| 407 if (j == inflight_.end()) { |
| 408 NOTREACHED(); |
| 409 return; |
| 410 } |
| 411 CertVerifierJob* job = j->second; |
| 412 inflight_.erase(j); |
| 413 |
| 414 job->HandleResult(cached_result); |
| 415 delete job; |
| 416 } |
| 417 |
| 418 ///////////////////////////////////////////////////////////////////// |
| 419 |
| 420 SingleRequestCertVerifier::SingleRequestCertVerifier( |
| 421 CertVerifier* cert_verifier) |
| 422 : cert_verifier_(cert_verifier), |
| 423 cur_request_(NULL), |
| 424 cur_request_callback_(NULL), |
| 425 ALLOW_THIS_IN_INITIALIZER_LIST( |
| 426 callback_(this, &SingleRequestCertVerifier::OnVerifyCompletion)) { |
| 427 DCHECK(cert_verifier_ != NULL); |
| 428 } |
| 429 |
| 430 SingleRequestCertVerifier::~SingleRequestCertVerifier() { |
| 431 if (cur_request_) { |
| 432 cert_verifier_->CancelRequest(cur_request_); |
| 433 cur_request_ = NULL; |
| 434 } |
| 435 } |
| 436 |
| 437 int SingleRequestCertVerifier::Verify(X509Certificate* cert, |
| 438 const std::string& hostname, |
| 439 int flags, |
| 440 CertVerifyResult* verify_result, |
| 441 CompletionCallback* callback) { |
| 442 // Should not be already in use. |
| 443 DCHECK(!cur_request_ && !cur_request_callback_); |
136 | 444 |
137 // Do a synchronous verification. | 445 // Do a synchronous verification. |
138 if (!callback) { | 446 if (!callback) |
139 CertVerifyResult result; | 447 return cert->Verify(hostname, flags, verify_result); |
140 int rv = cert->Verify(hostname, flags, &result); | 448 |
141 *verify_result = result; | 449 CertVerifier::RequestHandle request = NULL; |
142 return rv; | 450 |
143 } | 451 // We need to be notified of completion before |callback| is called, so that |
144 | 452 // we can clear out |cur_request_*|. |
145 request_ = new Request(this, cert, hostname, flags, verify_result, callback); | 453 int rv = cert_verifier_->Verify( |
146 | 454 cert, hostname, flags, verify_result, &callback_, &request); |
147 // Dispatch to worker thread... | 455 |
148 if (!WorkerPool::PostTask(FROM_HERE, | 456 if (rv == ERR_IO_PENDING) { |
149 NewRunnableMethod(request_.get(), &Request::DoVerify), true)) { | 457 // Cleared in OnVerifyCompletion(). |
150 NOTREACHED(); | 458 cur_request_ = request; |
151 request_ = NULL; | 459 cur_request_callback_ = callback; |
152 return ERR_FAILED; | 460 } |
153 } | 461 |
154 | 462 return rv; |
155 return ERR_IO_PENDING; | 463 } |
| 464 |
| 465 void SingleRequestCertVerifier::OnVerifyCompletion(int result) { |
| 466 DCHECK(cur_request_ && cur_request_callback_); |
| 467 |
| 468 CompletionCallback* callback = cur_request_callback_; |
| 469 |
| 470 // Clear the outstanding request information. |
| 471 cur_request_ = NULL; |
| 472 cur_request_callback_ = NULL; |
| 473 |
| 474 // Call the user's original callback. |
| 475 callback->Run(result); |
156 } | 476 } |
157 | 477 |
158 } // namespace net | 478 } // namespace net |
| 479 |
| 480 DISABLE_RUNNABLE_METHOD_REFCOUNT(net::CertVerifierWorker); |
| 481 |
OLD | NEW |