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.
5 #include "net/cert/multi_threaded_cert_verifier.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/profiler/scoped_tracker.h"
15 #include "base/stl_util.h"
16 #include "base/synchronization/lock.h"
17 #include "base/threading/worker_pool.h"
18 #include "base/time/time.h"
19 #include "base/values.h"
20 #include "net/base/hash_value.h"
21 #include "net/base/net_errors.h"
22 #include "net/cert/cert_trust_anchor_provider.h"
23 #include "net/cert/cert_verify_proc.h"
24 #include "net/cert/crl_set.h"
25 #include "net/cert/x509_certificate.h"
26 #include "net/cert/x509_certificate_net_log_param.h"
27 #include "net/log/net_log.h"
29 #if defined(USE_NSS_CERTS) || defined(OS_IOS)
30 #include <private/pprthred.h> // PR_DetachThread
35 ////////////////////////////////////////////////////////////////////////////
39 // MultiThreadedCertVerifier CertVerifierJob CertVerifierWorker Request
40 // | (origin loop) (worker loop)
43 // |---->-------------------------------------<creates>
45 // |---->-------------------<creates>
47 // |---->-------------------------------------------------------<creates>
49 // |---->---------------------------------------Start
53 // | <starts verifying>
54 // |---->-------------------AddRequest |
64 // |----<-----------------------------------------|
67 // |---->------------------HandleResult
69 // |------>---------------------------Post
73 // On a cache hit, MultiThreadedCertVerifier::Verify() returns synchronously
74 // without posting a task to a worker thread.
78 // The default value of max_cache_entries_.
79 const unsigned kMaxCacheEntries
= 256;
81 // The number of seconds for which we'll cache a cache entry.
82 const unsigned kTTLSecs
= 1800; // 30 minutes.
84 base::Value
* CertVerifyResultCallback(const CertVerifyResult
& verify_result
,
85 NetLog::LogLevel log_level
) {
86 base::DictionaryValue
* results
= new base::DictionaryValue();
87 results
->SetBoolean("has_md5", verify_result
.has_md5
);
88 results
->SetBoolean("has_md2", verify_result
.has_md2
);
89 results
->SetBoolean("has_md4", verify_result
.has_md4
);
90 results
->SetBoolean("is_issued_by_known_root",
91 verify_result
.is_issued_by_known_root
);
92 results
->SetBoolean("is_issued_by_additional_trust_anchor",
93 verify_result
.is_issued_by_additional_trust_anchor
);
94 results
->SetBoolean("common_name_fallback_used",
95 verify_result
.common_name_fallback_used
);
96 results
->SetInteger("cert_status", verify_result
.cert_status
);
97 results
->Set("verified_cert",
98 NetLogX509CertificateCallback(verify_result
.verified_cert
.get(),
101 base::ListValue
* hashes
= new base::ListValue();
102 for (std::vector
<HashValue
>::const_iterator it
=
103 verify_result
.public_key_hashes
.begin();
104 it
!= verify_result
.public_key_hashes
.end();
106 hashes
->AppendString(it
->ToString());
108 results
->Set("public_key_hashes", hashes
);
115 MultiThreadedCertVerifier::CachedResult::CachedResult() : error(ERR_FAILED
) {}
117 MultiThreadedCertVerifier::CachedResult::~CachedResult() {}
119 MultiThreadedCertVerifier::CacheValidityPeriod::CacheValidityPeriod(
120 const base::Time
& now
)
121 : verification_time(now
),
122 expiration_time(now
) {
125 MultiThreadedCertVerifier::CacheValidityPeriod::CacheValidityPeriod(
126 const base::Time
& now
,
127 const base::Time
& expiration
)
128 : verification_time(now
),
129 expiration_time(expiration
) {
132 bool MultiThreadedCertVerifier::CacheExpirationFunctor::operator()(
133 const CacheValidityPeriod
& now
,
134 const CacheValidityPeriod
& expiration
) const {
135 // Ensure this functor is being used for expiration only, and not strict
136 // weak ordering/sorting. |now| should only ever contain a single
138 // Note: DCHECK_EQ is not used due to operator<< overloading requirements.
139 DCHECK(now
.verification_time
== now
.expiration_time
);
141 // |now| contains only a single time (verification_time), while |expiration|
142 // contains the validity range - both when the certificate was verified and
143 // when the verification result should expire.
145 // If the user receives a "not yet valid" message, and adjusts their clock
146 // foward to the correct time, this will (typically) cause
147 // now.verification_time to advance past expiration.expiration_time, thus
148 // treating the cached result as an expired entry and re-verifying.
149 // If the user receives a "expired" message, and adjusts their clock
150 // backwards to the correct time, this will cause now.verification_time to
151 // be less than expiration_verification_time, thus treating the cached
152 // result as an expired entry and re-verifying.
153 // If the user receives either of those messages, and does not adjust their
154 // clock, then the result will be (typically) be cached until the expiration
157 // This algorithm is only problematic if the user consistently keeps
158 // adjusting their clock backwards in increments smaller than the expiration
159 // TTL, in which case, cached elements continue to be added. However,
160 // because the cache has a fixed upper bound, if no entries are expired, a
161 // 'random' entry will be, thus keeping the memory constraints bounded over
163 return now
.verification_time
>= expiration
.verification_time
&&
164 now
.verification_time
< expiration
.expiration_time
;
168 // Represents the output and result callback of a request.
169 class CertVerifierRequest
{
171 CertVerifierRequest(const CompletionCallback
& callback
,
172 CertVerifyResult
* verify_result
,
173 const BoundNetLog
& net_log
)
174 : callback_(callback
),
175 verify_result_(verify_result
),
177 net_log_
.BeginEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST
);
180 ~CertVerifierRequest() {
183 // Ensures that the result callback will never be made.
186 verify_result_
= NULL
;
187 net_log_
.AddEvent(NetLog::TYPE_CANCELLED
);
188 net_log_
.EndEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST
);
191 // Copies the contents of |verify_result| to the caller's
192 // CertVerifyResult and calls the callback.
193 void Post(const MultiThreadedCertVerifier::CachedResult
& verify_result
) {
194 if (!callback_
.is_null()) {
195 net_log_
.EndEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST
);
196 *verify_result_
= verify_result
.result
;
197 callback_
.Run(verify_result
.error
);
202 bool canceled() const { return callback_
.is_null(); }
204 const BoundNetLog
& net_log() const { return net_log_
; }
207 CompletionCallback callback_
;
208 CertVerifyResult
* verify_result_
;
209 const BoundNetLog net_log_
;
213 // CertVerifierWorker runs on a worker thread and takes care of the blocking
214 // process of performing the certificate verification. Deletes itself
215 // eventually if Start() succeeds.
216 class CertVerifierWorker
{
218 CertVerifierWorker(CertVerifyProc
* verify_proc
,
219 X509Certificate
* cert
,
220 const std::string
& hostname
,
223 const CertificateList
& additional_trust_anchors
,
224 MultiThreadedCertVerifier
* cert_verifier
)
225 : verify_proc_(verify_proc
),
230 additional_trust_anchors_(additional_trust_anchors
),
231 origin_loop_(base::MessageLoop::current()),
232 cert_verifier_(cert_verifier
),
237 // Returns the certificate being verified. May only be called /before/
238 // Start() is called.
239 X509Certificate
* certificate() const { return cert_
.get(); }
242 DCHECK_EQ(base::MessageLoop::current(), origin_loop_
);
244 return base::WorkerPool::PostTask(
245 FROM_HERE
, base::Bind(&CertVerifierWorker::Run
, base::Unretained(this)),
246 true /* task is slow */);
249 // Cancel is called from the origin loop when the MultiThreadedCertVerifier is
252 DCHECK_EQ(base::MessageLoop::current(), origin_loop_
);
253 base::AutoLock
locked(lock_
);
259 // Runs on a worker thread.
260 error_
= verify_proc_
->Verify(cert_
.get(),
264 additional_trust_anchors_
,
266 #if defined(USE_NSS_CERTS) || defined(OS_IOS)
267 // Detach the thread from NSPR.
268 // Calling NSS functions attaches the thread to NSPR, which stores
269 // the NSPR thread ID in thread-specific data.
270 // The threads in our thread pool terminate after we have called
271 // PR_Cleanup. Unless we detach them from NSPR, net_unittests gets
272 // segfaults on shutdown when the threads' thread-specific data
279 // DoReply runs on the origin thread.
281 // TODO(pkasting): Remove ScopedTracker below once crbug.com/477117 is
283 tracked_objects::ScopedTracker
tracking_profile(
284 FROM_HERE_WITH_EXPLICIT_FUNCTION("477117 CertVerifierWorker::DoReply"));
285 DCHECK_EQ(base::MessageLoop::current(), origin_loop_
);
287 // We lock here because the worker thread could still be in Finished,
288 // after the PostTask, but before unlocking |lock_|. If we do not lock in
289 // this case, we will end up deleting a locked Lock, which can lead to
290 // memory leaks or worse errors.
291 base::AutoLock
locked(lock_
);
293 cert_verifier_
->HandleResult(cert_
.get(),
296 additional_trust_anchors_
,
305 // Runs on the worker thread.
306 // We assume that the origin loop outlives the MultiThreadedCertVerifier. If
307 // the MultiThreadedCertVerifier is deleted, it will call Cancel on us. If
308 // it does so before the Acquire, we'll delete ourselves and return. If it's
309 // trying to do so concurrently, then it'll block on the lock and we'll call
310 // PostTask while the MultiThreadedCertVerifier (and therefore the
311 // MessageLoop) is still alive.
312 // If it does so after this function, we assume that the MessageLoop will
313 // process pending tasks. In which case we'll notice the |canceled_| flag
318 base::AutoLock
locked(lock_
);
319 canceled
= canceled_
;
321 origin_loop_
->PostTask(
322 FROM_HERE
, base::Bind(
323 &CertVerifierWorker::DoReply
, base::Unretained(this)));
331 scoped_refptr
<CertVerifyProc
> verify_proc_
;
332 scoped_refptr
<X509Certificate
> cert_
;
333 const std::string hostname_
;
335 scoped_refptr
<CRLSet
> crl_set_
;
336 const CertificateList additional_trust_anchors_
;
337 base::MessageLoop
* const origin_loop_
;
338 MultiThreadedCertVerifier
* const cert_verifier_
;
340 // lock_ protects canceled_.
343 // If canceled_ is true,
344 // * origin_loop_ cannot be accessed by the worker thread,
345 // * cert_verifier_ cannot be accessed by any thread.
349 CertVerifyResult verify_result_
;
351 DISALLOW_COPY_AND_ASSIGN(CertVerifierWorker
);
354 // A CertVerifierJob is a one-to-one counterpart of a CertVerifierWorker. It
355 // lives only on the CertVerifier's origin message loop.
356 class CertVerifierJob
{
358 CertVerifierJob(CertVerifierWorker
* worker
,
359 const BoundNetLog
& net_log
)
360 : start_time_(base::TimeTicks::Now()),
364 NetLog::TYPE_CERT_VERIFIER_JOB
,
365 base::Bind(&NetLogX509CertificateCallback
,
366 base::Unretained(worker_
->certificate())));
371 net_log_
.AddEvent(NetLog::TYPE_CANCELLED
);
372 net_log_
.EndEvent(NetLog::TYPE_CERT_VERIFIER_JOB
);
378 void AddRequest(CertVerifierRequest
* request
) {
379 request
->net_log().AddEvent(
380 NetLog::TYPE_CERT_VERIFIER_REQUEST_BOUND_TO_JOB
,
381 net_log_
.source().ToEventParametersCallback());
383 requests_
.push_back(request
);
387 const MultiThreadedCertVerifier::CachedResult
& verify_result
,
391 NetLog::TYPE_CERT_VERIFIER_JOB
,
392 base::Bind(&CertVerifyResultCallback
, verify_result
.result
));
393 base::TimeDelta latency
= base::TimeTicks::Now() - start_time_
;
394 UMA_HISTOGRAM_CUSTOM_TIMES("Net.CertVerifier_Job_Latency",
396 base::TimeDelta::FromMilliseconds(1),
397 base::TimeDelta::FromMinutes(10),
400 UMA_HISTOGRAM_CUSTOM_TIMES("Net.CertVerifier_First_Job_Latency",
402 base::TimeDelta::FromMilliseconds(1),
403 base::TimeDelta::FromMinutes(10),
406 PostAll(verify_result
);
410 void PostAll(const MultiThreadedCertVerifier::CachedResult
& verify_result
) {
411 std::vector
<CertVerifierRequest
*> requests
;
412 requests_
.swap(requests
);
414 for (std::vector
<CertVerifierRequest
*>::iterator
415 i
= requests
.begin(); i
!= requests
.end(); i
++) {
416 (*i
)->Post(verify_result
);
417 // Post() causes the CertVerifierRequest to delete itself.
421 void DeleteAllCanceled() {
422 for (std::vector
<CertVerifierRequest
*>::iterator
423 i
= requests_
.begin(); i
!= requests_
.end(); i
++) {
424 if ((*i
)->canceled()) {
427 LOG(DFATAL
) << "CertVerifierRequest leaked!";
432 const base::TimeTicks start_time_
;
433 std::vector
<CertVerifierRequest
*> requests_
;
434 CertVerifierWorker
* worker_
;
435 const BoundNetLog net_log_
;
438 MultiThreadedCertVerifier::MultiThreadedCertVerifier(
439 CertVerifyProc
* verify_proc
)
440 : cache_(kMaxCacheEntries
),
445 verify_proc_(verify_proc
),
446 trust_anchor_provider_(NULL
) {
447 CertDatabase::GetInstance()->AddObserver(this);
450 MultiThreadedCertVerifier::~MultiThreadedCertVerifier() {
451 STLDeleteValues(&inflight_
);
452 CertDatabase::GetInstance()->RemoveObserver(this);
455 void MultiThreadedCertVerifier::SetCertTrustAnchorProvider(
456 CertTrustAnchorProvider
* trust_anchor_provider
) {
457 DCHECK(CalledOnValidThread());
458 trust_anchor_provider_
= trust_anchor_provider
;
461 int MultiThreadedCertVerifier::Verify(X509Certificate
* cert
,
462 const std::string
& hostname
,
465 CertVerifyResult
* verify_result
,
466 const CompletionCallback
& callback
,
467 RequestHandle
* out_req
,
468 const BoundNetLog
& net_log
) {
469 DCHECK(CalledOnValidThread());
471 if (callback
.is_null() || !verify_result
|| hostname
.empty()) {
473 return ERR_INVALID_ARGUMENT
;
478 const CertificateList empty_cert_list
;
479 const CertificateList
& additional_trust_anchors
=
480 trust_anchor_provider_
?
481 trust_anchor_provider_
->GetAdditionalTrustAnchors() : empty_cert_list
;
483 const RequestParams
key(cert
->fingerprint(), cert
->ca_fingerprint(),
484 hostname
, flags
, additional_trust_anchors
);
485 const CertVerifierCache::value_type
* cached_entry
=
486 cache_
.Get(key
, CacheValidityPeriod(base::Time::Now()));
490 *verify_result
= cached_entry
->result
;
491 return cached_entry
->error
;
494 // No cache hit. See if an identical request is currently in flight.
495 CertVerifierJob
* job
;
496 std::map
<RequestParams
, CertVerifierJob
*>::const_iterator j
;
497 j
= inflight_
.find(key
);
498 if (j
!= inflight_
.end()) {
499 // An identical request is in flight already. We'll just attach our
504 // Need to make a new request.
505 CertVerifierWorker
* worker
=
506 new CertVerifierWorker(verify_proc_
.get(),
511 additional_trust_anchors
,
513 job
= new CertVerifierJob(
515 BoundNetLog::Make(net_log
.net_log(), NetLog::SOURCE_CERT_VERIFIER_JOB
));
516 if (!worker
->Start()) {
520 // TODO(wtc): log to the NetLog.
521 LOG(ERROR
) << "CertVerifierWorker couldn't be started.";
522 return ERR_INSUFFICIENT_RESOURCES
; // Just a guess.
524 inflight_
.insert(std::make_pair(key
, job
));
525 if (requests_
== 1) {
526 // Cleared in HandleResult.
531 CertVerifierRequest
* request
=
532 new CertVerifierRequest(callback
, verify_result
, net_log
);
533 job
->AddRequest(request
);
535 return ERR_IO_PENDING
;
538 void MultiThreadedCertVerifier::CancelRequest(RequestHandle req
) {
539 DCHECK(CalledOnValidThread());
540 CertVerifierRequest
* request
= reinterpret_cast<CertVerifierRequest
*>(req
);
544 MultiThreadedCertVerifier::RequestParams::RequestParams(
545 const SHA1HashValue
& cert_fingerprint_arg
,
546 const SHA1HashValue
& ca_fingerprint_arg
,
547 const std::string
& hostname_arg
,
549 const CertificateList
& additional_trust_anchors
)
550 : hostname(hostname_arg
),
552 hash_values
.reserve(2 + additional_trust_anchors
.size());
553 hash_values
.push_back(cert_fingerprint_arg
);
554 hash_values
.push_back(ca_fingerprint_arg
);
555 for (size_t i
= 0; i
< additional_trust_anchors
.size(); ++i
)
556 hash_values
.push_back(additional_trust_anchors
[i
]->fingerprint());
559 MultiThreadedCertVerifier::RequestParams::~RequestParams() {}
561 bool MultiThreadedCertVerifier::RequestParams::operator<(
562 const RequestParams
& other
) const {
563 // |flags| is compared before |cert_fingerprint|, |ca_fingerprint|, and
564 // |hostname| under assumption that integer comparisons are faster than
565 // memory and string comparisons.
566 if (flags
!= other
.flags
)
567 return flags
< other
.flags
;
568 if (hostname
!= other
.hostname
)
569 return hostname
< other
.hostname
;
570 return std::lexicographical_compare(
571 hash_values
.begin(), hash_values
.end(),
572 other
.hash_values
.begin(), other
.hash_values
.end(),
573 net::SHA1HashValueLessThan());
576 // HandleResult is called by CertVerifierWorker on the origin message loop.
577 // It deletes CertVerifierJob.
578 void MultiThreadedCertVerifier::HandleResult(
579 X509Certificate
* cert
,
580 const std::string
& hostname
,
582 const CertificateList
& additional_trust_anchors
,
584 const CertVerifyResult
& verify_result
) {
585 DCHECK(CalledOnValidThread());
587 const RequestParams
key(cert
->fingerprint(), cert
->ca_fingerprint(),
588 hostname
, flags
, additional_trust_anchors
);
590 CachedResult cached_result
;
591 cached_result
.error
= error
;
592 cached_result
.result
= verify_result
;
593 base::Time now
= base::Time::Now();
595 key
, cached_result
, CacheValidityPeriod(now
),
596 CacheValidityPeriod(now
, now
+ base::TimeDelta::FromSeconds(kTTLSecs
)));
598 std::map
<RequestParams
, CertVerifierJob
*>::iterator j
;
599 j
= inflight_
.find(key
);
600 if (j
== inflight_
.end()) {
604 CertVerifierJob
* job
= j
->second
;
606 bool is_first_job
= false;
607 if (first_job_
== job
) {
612 job
->HandleResult(cached_result
, is_first_job
);
616 void MultiThreadedCertVerifier::OnCACertChanged(
617 const X509Certificate
* cert
) {
618 DCHECK(CalledOnValidThread());