1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "net/cert/multi_threaded_cert_verifier.h" 6 7 #include <algorithm> 8 9 #include "base/bind.h" 10 #include "base/bind_helpers.h" 11 #include "base/compiler_specific.h" 12 #include "base/message_loop/message_loop.h" 13 #include "base/metrics/histogram.h" 14 #include "base/stl_util.h" 15 #include "base/synchronization/lock.h" 16 #include "base/threading/worker_pool.h" 17 #include "base/time/time.h" 18 #include "net/base/net_errors.h" 19 #include "net/base/net_log.h" 20 #include "net/cert/cert_trust_anchor_provider.h" 21 #include "net/cert/cert_verify_proc.h" 22 #include "net/cert/crl_set.h" 23 #include "net/cert/x509_certificate.h" 24 #include "net/cert/x509_certificate_net_log_param.h" 25 26 #if defined(USE_NSS) || defined(OS_IOS) 27 #include <private/pprthred.h> // PR_DetachThread 28 #endif 29 30 namespace net { 31 32 //////////////////////////////////////////////////////////////////////////// 33 34 // Life of a request: 35 // 36 // MultiThreadedCertVerifier CertVerifierJob CertVerifierWorker Request 37 // | (origin loop) (worker loop) 38 // | 39 // Verify() 40 // |---->-------------------------------------<creates> 41 // | 42 // |---->-------------------<creates> 43 // | 44 // |---->-------------------------------------------------------<creates> 45 // | 46 // |---->---------------------------------------Start 47 // | | 48 // | PostTask 49 // | 50 // | <starts verifying> 51 // |---->-------------------AddRequest | 52 // | 53 // | 54 // | 55 // Finish 56 // | 57 // PostTask 58 // 59 // | 60 // DoReply 61 // |----<-----------------------------------------| 62 // HandleResult 63 // | 64 // |---->------------------HandleResult 65 // | 66 // |------>---------------------------Post 67 // 68 // 69 // 70 // On a cache hit, MultiThreadedCertVerifier::Verify() returns synchronously 71 // without posting a task to a worker thread. 72 73 namespace { 74 75 // The default value of max_cache_entries_. 76 const unsigned kMaxCacheEntries = 256; 77 78 // The number of seconds for which we'll cache a cache entry. 79 const unsigned kTTLSecs = 1800; // 30 minutes. 80 81 } // namespace 82 83 MultiThreadedCertVerifier::CachedResult::CachedResult() : error(ERR_FAILED) {} 84 85 MultiThreadedCertVerifier::CachedResult::~CachedResult() {} 86 87 MultiThreadedCertVerifier::CacheValidityPeriod::CacheValidityPeriod( 88 const base::Time& now) 89 : verification_time(now), 90 expiration_time(now) { 91 } 92 93 MultiThreadedCertVerifier::CacheValidityPeriod::CacheValidityPeriod( 94 const base::Time& now, 95 const base::Time& expiration) 96 : verification_time(now), 97 expiration_time(expiration) { 98 } 99 100 bool MultiThreadedCertVerifier::CacheExpirationFunctor::operator()( 101 const CacheValidityPeriod& now, 102 const CacheValidityPeriod& expiration) const { 103 // Ensure this functor is being used for expiration only, and not strict 104 // weak ordering/sorting. |now| should only ever contain a single 105 // base::Time. 106 // Note: DCHECK_EQ is not used due to operator<< overloading requirements. 107 DCHECK(now.verification_time == now.expiration_time); 108 109 // |now| contains only a single time (verification_time), while |expiration| 110 // contains the validity range - both when the certificate was verified and 111 // when the verification result should expire. 112 // 113 // If the user receives a "not yet valid" message, and adjusts their clock 114 // foward to the correct time, this will (typically) cause 115 // now.verification_time to advance past expiration.expiration_time, thus 116 // treating the cached result as an expired entry and re-verifying. 117 // If the user receives a "expired" message, and adjusts their clock 118 // backwards to the correct time, this will cause now.verification_time to 119 // be less than expiration_verification_time, thus treating the cached 120 // result as an expired entry and re-verifying. 121 // If the user receives either of those messages, and does not adjust their 122 // clock, then the result will be (typically) be cached until the expiration 123 // TTL. 124 // 125 // This algorithm is only problematic if the user consistently keeps 126 // adjusting their clock backwards in increments smaller than the expiration 127 // TTL, in which case, cached elements continue to be added. However, 128 // because the cache has a fixed upper bound, if no entries are expired, a 129 // 'random' entry will be, thus keeping the memory constraints bounded over 130 // time. 131 return now.verification_time >= expiration.verification_time && 132 now.verification_time < expiration.expiration_time; 133 }; 134 135 136 // Represents the output and result callback of a request. 137 class CertVerifierRequest { 138 public: 139 CertVerifierRequest(const CompletionCallback& callback, 140 CertVerifyResult* verify_result, 141 const BoundNetLog& net_log) 142 : callback_(callback), 143 verify_result_(verify_result), 144 net_log_(net_log) { 145 net_log_.BeginEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST); 146 } 147 148 ~CertVerifierRequest() { 149 } 150 151 // Ensures that the result callback will never be made. 152 void Cancel() { 153 callback_.Reset(); 154 verify_result_ = NULL; 155 net_log_.AddEvent(NetLog::TYPE_CANCELLED); 156 net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST); 157 } 158 159 // Copies the contents of |verify_result| to the caller's 160 // CertVerifyResult and calls the callback. 161 void Post(const MultiThreadedCertVerifier::CachedResult& verify_result) { 162 if (!callback_.is_null()) { 163 net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST); 164 *verify_result_ = verify_result.result; 165 callback_.Run(verify_result.error); 166 } 167 delete this; 168 } 169 170 bool canceled() const { return callback_.is_null(); } 171 172 const BoundNetLog& net_log() const { return net_log_; } 173 174 private: 175 CompletionCallback callback_; 176 CertVerifyResult* verify_result_; 177 const BoundNetLog net_log_; 178 }; 179 180 181 // CertVerifierWorker runs on a worker thread and takes care of the blocking 182 // process of performing the certificate verification. Deletes itself 183 // eventually if Start() succeeds. 184 class CertVerifierWorker { 185 public: 186 CertVerifierWorker(CertVerifyProc* verify_proc, 187 X509Certificate* cert, 188 const std::string& hostname, 189 int flags, 190 CRLSet* crl_set, 191 const CertificateList& additional_trust_anchors, 192 MultiThreadedCertVerifier* cert_verifier) 193 : verify_proc_(verify_proc), 194 cert_(cert), 195 hostname_(hostname), 196 flags_(flags), 197 crl_set_(crl_set), 198 additional_trust_anchors_(additional_trust_anchors), 199 origin_loop_(base::MessageLoop::current()), 200 cert_verifier_(cert_verifier), 201 canceled_(false), 202 error_(ERR_FAILED) { 203 } 204 205 // Returns the certificate being verified. May only be called /before/ 206 // Start() is called. 207 X509Certificate* certificate() const { return cert_.get(); } 208 209 bool Start() { 210 DCHECK_EQ(base::MessageLoop::current(), origin_loop_); 211 212 return base::WorkerPool::PostTask( 213 FROM_HERE, base::Bind(&CertVerifierWorker::Run, base::Unretained(this)), 214 true /* task is slow */); 215 } 216 217 // Cancel is called from the origin loop when the MultiThreadedCertVerifier is 218 // getting deleted. 219 void Cancel() { 220 DCHECK_EQ(base::MessageLoop::current(), origin_loop_); 221 base::AutoLock locked(lock_); 222 canceled_ = true; 223 } 224 225 private: 226 void Run() { 227 // Runs on a worker thread. 228 error_ = verify_proc_->Verify(cert_.get(), 229 hostname_, 230 flags_, 231 crl_set_.get(), 232 additional_trust_anchors_, 233 &verify_result_); 234 #if defined(USE_NSS) || defined(OS_IOS) 235 // Detach the thread from NSPR. 236 // Calling NSS functions attaches the thread to NSPR, which stores 237 // the NSPR thread ID in thread-specific data. 238 // The threads in our thread pool terminate after we have called 239 // PR_Cleanup. Unless we detach them from NSPR, net_unittests gets 240 // segfaults on shutdown when the threads' thread-specific data 241 // destructors run. 242 PR_DetachThread(); 243 #endif 244 Finish(); 245 } 246 247 // DoReply runs on the origin thread. 248 void DoReply() { 249 DCHECK_EQ(base::MessageLoop::current(), origin_loop_); 250 { 251 // We lock here because the worker thread could still be in Finished, 252 // after the PostTask, but before unlocking |lock_|. If we do not lock in 253 // this case, we will end up deleting a locked Lock, which can lead to 254 // memory leaks or worse errors. 255 base::AutoLock locked(lock_); 256 if (!canceled_) { 257 cert_verifier_->HandleResult(cert_.get(), 258 hostname_, 259 flags_, 260 additional_trust_anchors_, 261 error_, 262 verify_result_); 263 } 264 } 265 delete this; 266 } 267 268 void Finish() { 269 // Runs on the worker thread. 270 // We assume that the origin loop outlives the MultiThreadedCertVerifier. If 271 // the MultiThreadedCertVerifier is deleted, it will call Cancel on us. If 272 // it does so before the Acquire, we'll delete ourselves and return. If it's 273 // trying to do so concurrently, then it'll block on the lock and we'll call 274 // PostTask while the MultiThreadedCertVerifier (and therefore the 275 // MessageLoop) is still alive. 276 // If it does so after this function, we assume that the MessageLoop will 277 // process pending tasks. In which case we'll notice the |canceled_| flag 278 // in DoReply. 279 280 bool canceled; 281 { 282 base::AutoLock locked(lock_); 283 canceled = canceled_; 284 if (!canceled) { 285 origin_loop_->PostTask( 286 FROM_HERE, base::Bind( 287 &CertVerifierWorker::DoReply, base::Unretained(this))); 288 } 289 } 290 291 if (canceled) 292 delete this; 293 } 294 295 scoped_refptr<CertVerifyProc> verify_proc_; 296 scoped_refptr<X509Certificate> cert_; 297 const std::string hostname_; 298 const int flags_; 299 scoped_refptr<CRLSet> crl_set_; 300 const CertificateList additional_trust_anchors_; 301 base::MessageLoop* const origin_loop_; 302 MultiThreadedCertVerifier* const cert_verifier_; 303 304 // lock_ protects canceled_. 305 base::Lock lock_; 306 307 // If canceled_ is true, 308 // * origin_loop_ cannot be accessed by the worker thread, 309 // * cert_verifier_ cannot be accessed by any thread. 310 bool canceled_; 311 312 int error_; 313 CertVerifyResult verify_result_; 314 315 DISALLOW_COPY_AND_ASSIGN(CertVerifierWorker); 316 }; 317 318 // A CertVerifierJob is a one-to-one counterpart of a CertVerifierWorker. It 319 // lives only on the CertVerifier's origin message loop. 320 class CertVerifierJob { 321 public: 322 CertVerifierJob(CertVerifierWorker* worker, 323 const BoundNetLog& net_log) 324 : start_time_(base::TimeTicks::Now()), 325 worker_(worker), 326 net_log_(net_log) { 327 net_log_.BeginEvent( 328 NetLog::TYPE_CERT_VERIFIER_JOB, 329 base::Bind(&NetLogX509CertificateCallback, 330 base::Unretained(worker_->certificate()))); 331 } 332 333 ~CertVerifierJob() { 334 if (worker_) { 335 net_log_.AddEvent(NetLog::TYPE_CANCELLED); 336 net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_JOB); 337 worker_->Cancel(); 338 DeleteAllCanceled(); 339 } 340 } 341 342 void AddRequest(CertVerifierRequest* request) { 343 request->net_log().AddEvent( 344 NetLog::TYPE_CERT_VERIFIER_REQUEST_BOUND_TO_JOB, 345 net_log_.source().ToEventParametersCallback()); 346 347 requests_.push_back(request); 348 } 349 350 void HandleResult( 351 const MultiThreadedCertVerifier::CachedResult& verify_result) { 352 worker_ = NULL; 353 net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_JOB); 354 UMA_HISTOGRAM_CUSTOM_TIMES("Net.CertVerifier_Job_Latency", 355 base::TimeTicks::Now() - start_time_, 356 base::TimeDelta::FromMilliseconds(1), 357 base::TimeDelta::FromMinutes(10), 358 100); 359 PostAll(verify_result); 360 } 361 362 private: 363 void PostAll(const MultiThreadedCertVerifier::CachedResult& verify_result) { 364 std::vector<CertVerifierRequest*> requests; 365 requests_.swap(requests); 366 367 for (std::vector<CertVerifierRequest*>::iterator 368 i = requests.begin(); i != requests.end(); i++) { 369 (*i)->Post(verify_result); 370 // Post() causes the CertVerifierRequest to delete itself. 371 } 372 } 373 374 void DeleteAllCanceled() { 375 for (std::vector<CertVerifierRequest*>::iterator 376 i = requests_.begin(); i != requests_.end(); i++) { 377 if ((*i)->canceled()) { 378 delete *i; 379 } else { 380 LOG(DFATAL) << "CertVerifierRequest leaked!"; 381 } 382 } 383 } 384 385 const base::TimeTicks start_time_; 386 std::vector<CertVerifierRequest*> requests_; 387 CertVerifierWorker* worker_; 388 const BoundNetLog net_log_; 389 }; 390 391 MultiThreadedCertVerifier::MultiThreadedCertVerifier( 392 CertVerifyProc* verify_proc) 393 : cache_(kMaxCacheEntries), 394 requests_(0), 395 cache_hits_(0), 396 inflight_joins_(0), 397 verify_proc_(verify_proc), 398 trust_anchor_provider_(NULL) { 399 CertDatabase::GetInstance()->AddObserver(this); 400 } 401 402 MultiThreadedCertVerifier::~MultiThreadedCertVerifier() { 403 STLDeleteValues(&inflight_); 404 CertDatabase::GetInstance()->RemoveObserver(this); 405 } 406 407 void MultiThreadedCertVerifier::SetCertTrustAnchorProvider( 408 CertTrustAnchorProvider* trust_anchor_provider) { 409 DCHECK(CalledOnValidThread()); 410 trust_anchor_provider_ = trust_anchor_provider; 411 } 412 413 int MultiThreadedCertVerifier::Verify(X509Certificate* cert, 414 const std::string& hostname, 415 int flags, 416 CRLSet* crl_set, 417 CertVerifyResult* verify_result, 418 const CompletionCallback& callback, 419 RequestHandle* out_req, 420 const BoundNetLog& net_log) { 421 DCHECK(CalledOnValidThread()); 422 423 if (callback.is_null() || !verify_result || hostname.empty()) { 424 *out_req = NULL; 425 return ERR_INVALID_ARGUMENT; 426 } 427 428 requests_++; 429 430 const CertificateList empty_cert_list; 431 const CertificateList& additional_trust_anchors = 432 trust_anchor_provider_ ? 433 trust_anchor_provider_->GetAdditionalTrustAnchors() : empty_cert_list; 434 435 const RequestParams key(cert->fingerprint(), cert->ca_fingerprint(), 436 hostname, flags, additional_trust_anchors); 437 const CertVerifierCache::value_type* cached_entry = 438 cache_.Get(key, CacheValidityPeriod(base::Time::Now())); 439 if (cached_entry) { 440 ++cache_hits_; 441 *out_req = NULL; 442 *verify_result = cached_entry->result; 443 return cached_entry->error; 444 } 445 446 // No cache hit. See if an identical request is currently in flight. 447 CertVerifierJob* job; 448 std::map<RequestParams, CertVerifierJob*>::const_iterator j; 449 j = inflight_.find(key); 450 if (j != inflight_.end()) { 451 // An identical request is in flight already. We'll just attach our 452 // callback. 453 inflight_joins_++; 454 job = j->second; 455 } else { 456 // Need to make a new request. 457 CertVerifierWorker* worker = 458 new CertVerifierWorker(verify_proc_.get(), 459 cert, 460 hostname, 461 flags, 462 crl_set, 463 additional_trust_anchors, 464 this); 465 job = new CertVerifierJob( 466 worker, 467 BoundNetLog::Make(net_log.net_log(), NetLog::SOURCE_CERT_VERIFIER_JOB)); 468 if (!worker->Start()) { 469 delete job; 470 delete worker; 471 *out_req = NULL; 472 // TODO(wtc): log to the NetLog. 473 LOG(ERROR) << "CertVerifierWorker couldn't be started."; 474 return ERR_INSUFFICIENT_RESOURCES; // Just a guess. 475 } 476 inflight_.insert(std::make_pair(key, job)); 477 } 478 479 CertVerifierRequest* request = 480 new CertVerifierRequest(callback, verify_result, net_log); 481 job->AddRequest(request); 482 *out_req = request; 483 return ERR_IO_PENDING; 484 } 485 486 void MultiThreadedCertVerifier::CancelRequest(RequestHandle req) { 487 DCHECK(CalledOnValidThread()); 488 CertVerifierRequest* request = reinterpret_cast<CertVerifierRequest*>(req); 489 request->Cancel(); 490 } 491 492 MultiThreadedCertVerifier::RequestParams::RequestParams( 493 const SHA1HashValue& cert_fingerprint_arg, 494 const SHA1HashValue& ca_fingerprint_arg, 495 const std::string& hostname_arg, 496 int flags_arg, 497 const CertificateList& additional_trust_anchors) 498 : hostname(hostname_arg), 499 flags(flags_arg) { 500 hash_values.reserve(2 + additional_trust_anchors.size()); 501 hash_values.push_back(cert_fingerprint_arg); 502 hash_values.push_back(ca_fingerprint_arg); 503 for (size_t i = 0; i < additional_trust_anchors.size(); ++i) 504 hash_values.push_back(additional_trust_anchors[i]->fingerprint()); 505 } 506 507 MultiThreadedCertVerifier::RequestParams::~RequestParams() {} 508 509 bool MultiThreadedCertVerifier::RequestParams::operator<( 510 const RequestParams& other) const { 511 // |flags| is compared before |cert_fingerprint|, |ca_fingerprint|, and 512 // |hostname| under assumption that integer comparisons are faster than 513 // memory and string comparisons. 514 if (flags != other.flags) 515 return flags < other.flags; 516 if (hostname != other.hostname) 517 return hostname < other.hostname; 518 return std::lexicographical_compare( 519 hash_values.begin(), hash_values.end(), 520 other.hash_values.begin(), other.hash_values.end(), 521 net::SHA1HashValueLessThan()); 522 } 523 524 // HandleResult is called by CertVerifierWorker on the origin message loop. 525 // It deletes CertVerifierJob. 526 void MultiThreadedCertVerifier::HandleResult( 527 X509Certificate* cert, 528 const std::string& hostname, 529 int flags, 530 const CertificateList& additional_trust_anchors, 531 int error, 532 const CertVerifyResult& verify_result) { 533 DCHECK(CalledOnValidThread()); 534 535 const RequestParams key(cert->fingerprint(), cert->ca_fingerprint(), 536 hostname, flags, additional_trust_anchors); 537 538 CachedResult cached_result; 539 cached_result.error = error; 540 cached_result.result = verify_result; 541 base::Time now = base::Time::Now(); 542 cache_.Put( 543 key, cached_result, CacheValidityPeriod(now), 544 CacheValidityPeriod(now, now + base::TimeDelta::FromSeconds(kTTLSecs))); 545 546 std::map<RequestParams, CertVerifierJob*>::iterator j; 547 j = inflight_.find(key); 548 if (j == inflight_.end()) { 549 NOTREACHED(); 550 return; 551 } 552 CertVerifierJob* job = j->second; 553 inflight_.erase(j); 554 555 job->HandleResult(cached_result); 556 delete job; 557 } 558 559 void MultiThreadedCertVerifier::OnCACertChanged( 560 const X509Certificate* cert) { 561 DCHECK(CalledOnValidThread()); 562 563 ClearCache(); 564 } 565 566 } // namespace net 567