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/stl_util.h"
15 #include "base/synchronization/lock.h"
16 #include "base/threading/worker_pool.h"
17 #include "base/time/time.h"
18 #include "base/values.h"
19 #include "net/base/hash_value.h"
20 #include "net/base/net_errors.h"
21 #include "net/cert/cert_trust_anchor_provider.h"
22 #include "net/cert/cert_verify_proc.h"
23 #include "net/cert/crl_set.h"
24 #include "net/cert/x509_certificate.h"
25 #include "net/cert/x509_certificate_net_log_param.h"
26 #include "net/log/net_log.h"
28 #if defined(USE_NSS) || defined(OS_IOS)
29 #include <private/pprthred.h> // PR_DetachThread
34 ////////////////////////////////////////////////////////////////////////////
38 // MultiThreadedCertVerifier CertVerifierJob CertVerifierWorker Request
39 // | (origin loop) (worker loop)
42 // |---->-------------------------------------<creates>
44 // |---->-------------------<creates>
46 // |---->-------------------------------------------------------<creates>
48 // |---->---------------------------------------Start
52 // | <starts verifying>
53 // |---->-------------------AddRequest |
63 // |----<-----------------------------------------|
66 // |---->------------------HandleResult
68 // |------>---------------------------Post
72 // On a cache hit, MultiThreadedCertVerifier::Verify() returns synchronously
73 // without posting a task to a worker thread.
77 // The default value of max_cache_entries_.
78 const unsigned kMaxCacheEntries
= 256;
80 // The number of seconds for which we'll cache a cache entry.
81 const unsigned kTTLSecs
= 1800; // 30 minutes.
83 base::Value
* CertVerifyResultCallback(const CertVerifyResult
& verify_result
,
84 NetLog::LogLevel log_level
) {
85 base::DictionaryValue
* results
= new base::DictionaryValue();
86 results
->SetBoolean("has_md5", verify_result
.has_md5
);
87 results
->SetBoolean("has_md2", verify_result
.has_md2
);
88 results
->SetBoolean("has_md4", verify_result
.has_md4
);
89 results
->SetBoolean("is_issued_by_known_root",
90 verify_result
.is_issued_by_known_root
);
91 results
->SetBoolean("is_issued_by_additional_trust_anchor",
92 verify_result
.is_issued_by_additional_trust_anchor
);
93 results
->SetBoolean("common_name_fallback_used",
94 verify_result
.common_name_fallback_used
);
95 results
->SetInteger("cert_status", verify_result
.cert_status
);
96 results
->Set("verified_cert",
97 NetLogX509CertificateCallback(verify_result
.verified_cert
.get(),
100 base::ListValue
* hashes
= new base::ListValue();
101 for (std::vector
<HashValue
>::const_iterator it
=
102 verify_result
.public_key_hashes
.begin();
103 it
!= verify_result
.public_key_hashes
.end();
105 hashes
->AppendString(it
->ToString());
107 results
->Set("public_key_hashes", hashes
);
114 MultiThreadedCertVerifier::CachedResult::CachedResult() : error(ERR_FAILED
) {}
116 MultiThreadedCertVerifier::CachedResult::~CachedResult() {}
118 MultiThreadedCertVerifier::CacheValidityPeriod::CacheValidityPeriod(
119 const base::Time
& now
)
120 : verification_time(now
),
121 expiration_time(now
) {
124 MultiThreadedCertVerifier::CacheValidityPeriod::CacheValidityPeriod(
125 const base::Time
& now
,
126 const base::Time
& expiration
)
127 : verification_time(now
),
128 expiration_time(expiration
) {
131 bool MultiThreadedCertVerifier::CacheExpirationFunctor::operator()(
132 const CacheValidityPeriod
& now
,
133 const CacheValidityPeriod
& expiration
) const {
134 // Ensure this functor is being used for expiration only, and not strict
135 // weak ordering/sorting. |now| should only ever contain a single
137 // Note: DCHECK_EQ is not used due to operator<< overloading requirements.
138 DCHECK(now
.verification_time
== now
.expiration_time
);
140 // |now| contains only a single time (verification_time), while |expiration|
141 // contains the validity range - both when the certificate was verified and
142 // when the verification result should expire.
144 // If the user receives a "not yet valid" message, and adjusts their clock
145 // foward to the correct time, this will (typically) cause
146 // now.verification_time to advance past expiration.expiration_time, thus
147 // treating the cached result as an expired entry and re-verifying.
148 // If the user receives a "expired" message, and adjusts their clock
149 // backwards to the correct time, this will cause now.verification_time to
150 // be less than expiration_verification_time, thus treating the cached
151 // result as an expired entry and re-verifying.
152 // If the user receives either of those messages, and does not adjust their
153 // clock, then the result will be (typically) be cached until the expiration
156 // This algorithm is only problematic if the user consistently keeps
157 // adjusting their clock backwards in increments smaller than the expiration
158 // TTL, in which case, cached elements continue to be added. However,
159 // because the cache has a fixed upper bound, if no entries are expired, a
160 // 'random' entry will be, thus keeping the memory constraints bounded over
162 return now
.verification_time
>= expiration
.verification_time
&&
163 now
.verification_time
< expiration
.expiration_time
;
167 // Represents the output and result callback of a request.
168 class CertVerifierRequest
{
170 CertVerifierRequest(const CompletionCallback
& callback
,
171 CertVerifyResult
* verify_result
,
172 const BoundNetLog
& net_log
)
173 : callback_(callback
),
174 verify_result_(verify_result
),
176 net_log_
.BeginEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST
);
179 ~CertVerifierRequest() {
182 // Ensures that the result callback will never be made.
185 verify_result_
= NULL
;
186 net_log_
.AddEvent(NetLog::TYPE_CANCELLED
);
187 net_log_
.EndEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST
);
190 // Copies the contents of |verify_result| to the caller's
191 // CertVerifyResult and calls the callback.
192 void Post(const MultiThreadedCertVerifier::CachedResult
& verify_result
) {
193 if (!callback_
.is_null()) {
194 net_log_
.EndEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST
);
195 *verify_result_
= verify_result
.result
;
196 callback_
.Run(verify_result
.error
);
201 bool canceled() const { return callback_
.is_null(); }
203 const BoundNetLog
& net_log() const { return net_log_
; }
206 CompletionCallback callback_
;
207 CertVerifyResult
* verify_result_
;
208 const BoundNetLog net_log_
;
212 // CertVerifierWorker runs on a worker thread and takes care of the blocking
213 // process of performing the certificate verification. Deletes itself
214 // eventually if Start() succeeds.
215 class CertVerifierWorker
{
217 CertVerifierWorker(CertVerifyProc
* verify_proc
,
218 X509Certificate
* cert
,
219 const std::string
& hostname
,
222 const CertificateList
& additional_trust_anchors
,
223 MultiThreadedCertVerifier
* cert_verifier
)
224 : verify_proc_(verify_proc
),
229 additional_trust_anchors_(additional_trust_anchors
),
230 origin_loop_(base::MessageLoop::current()),
231 cert_verifier_(cert_verifier
),
236 // Returns the certificate being verified. May only be called /before/
237 // Start() is called.
238 X509Certificate
* certificate() const { return cert_
.get(); }
241 DCHECK_EQ(base::MessageLoop::current(), origin_loop_
);
243 return base::WorkerPool::PostTask(
244 FROM_HERE
, base::Bind(&CertVerifierWorker::Run
, base::Unretained(this)),
245 true /* task is slow */);
248 // Cancel is called from the origin loop when the MultiThreadedCertVerifier is
251 DCHECK_EQ(base::MessageLoop::current(), origin_loop_
);
252 base::AutoLock
locked(lock_
);
258 // Runs on a worker thread.
259 error_
= verify_proc_
->Verify(cert_
.get(),
263 additional_trust_anchors_
,
265 #if defined(USE_NSS) || defined(OS_IOS)
266 // Detach the thread from NSPR.
267 // Calling NSS functions attaches the thread to NSPR, which stores
268 // the NSPR thread ID in thread-specific data.
269 // The threads in our thread pool terminate after we have called
270 // PR_Cleanup. Unless we detach them from NSPR, net_unittests gets
271 // segfaults on shutdown when the threads' thread-specific data
278 // DoReply runs on the origin thread.
280 DCHECK_EQ(base::MessageLoop::current(), origin_loop_
);
282 // We lock here because the worker thread could still be in Finished,
283 // after the PostTask, but before unlocking |lock_|. If we do not lock in
284 // this case, we will end up deleting a locked Lock, which can lead to
285 // memory leaks or worse errors.
286 base::AutoLock
locked(lock_
);
288 cert_verifier_
->HandleResult(cert_
.get(),
291 additional_trust_anchors_
,
300 // Runs on the worker thread.
301 // We assume that the origin loop outlives the MultiThreadedCertVerifier. If
302 // the MultiThreadedCertVerifier is deleted, it will call Cancel on us. If
303 // it does so before the Acquire, we'll delete ourselves and return. If it's
304 // trying to do so concurrently, then it'll block on the lock and we'll call
305 // PostTask while the MultiThreadedCertVerifier (and therefore the
306 // MessageLoop) is still alive.
307 // If it does so after this function, we assume that the MessageLoop will
308 // process pending tasks. In which case we'll notice the |canceled_| flag
313 base::AutoLock
locked(lock_
);
314 canceled
= canceled_
;
316 origin_loop_
->PostTask(
317 FROM_HERE
, base::Bind(
318 &CertVerifierWorker::DoReply
, base::Unretained(this)));
326 scoped_refptr
<CertVerifyProc
> verify_proc_
;
327 scoped_refptr
<X509Certificate
> cert_
;
328 const std::string hostname_
;
330 scoped_refptr
<CRLSet
> crl_set_
;
331 const CertificateList additional_trust_anchors_
;
332 base::MessageLoop
* const origin_loop_
;
333 MultiThreadedCertVerifier
* const cert_verifier_
;
335 // lock_ protects canceled_.
338 // If canceled_ is true,
339 // * origin_loop_ cannot be accessed by the worker thread,
340 // * cert_verifier_ cannot be accessed by any thread.
344 CertVerifyResult verify_result_
;
346 DISALLOW_COPY_AND_ASSIGN(CertVerifierWorker
);
349 // A CertVerifierJob is a one-to-one counterpart of a CertVerifierWorker. It
350 // lives only on the CertVerifier's origin message loop.
351 class CertVerifierJob
{
353 CertVerifierJob(CertVerifierWorker
* worker
,
354 const BoundNetLog
& net_log
)
355 : start_time_(base::TimeTicks::Now()),
359 NetLog::TYPE_CERT_VERIFIER_JOB
,
360 base::Bind(&NetLogX509CertificateCallback
,
361 base::Unretained(worker_
->certificate())));
366 net_log_
.AddEvent(NetLog::TYPE_CANCELLED
);
367 net_log_
.EndEvent(NetLog::TYPE_CERT_VERIFIER_JOB
);
373 void AddRequest(CertVerifierRequest
* request
) {
374 request
->net_log().AddEvent(
375 NetLog::TYPE_CERT_VERIFIER_REQUEST_BOUND_TO_JOB
,
376 net_log_
.source().ToEventParametersCallback());
378 requests_
.push_back(request
);
382 const MultiThreadedCertVerifier::CachedResult
& verify_result
,
386 NetLog::TYPE_CERT_VERIFIER_JOB
,
387 base::Bind(&CertVerifyResultCallback
, verify_result
.result
));
388 base::TimeDelta latency
= base::TimeTicks::Now() - start_time_
;
389 UMA_HISTOGRAM_CUSTOM_TIMES("Net.CertVerifier_Job_Latency",
391 base::TimeDelta::FromMilliseconds(1),
392 base::TimeDelta::FromMinutes(10),
395 UMA_HISTOGRAM_CUSTOM_TIMES("Net.CertVerifier_First_Job_Latency",
397 base::TimeDelta::FromMilliseconds(1),
398 base::TimeDelta::FromMinutes(10),
401 PostAll(verify_result
);
405 void PostAll(const MultiThreadedCertVerifier::CachedResult
& verify_result
) {
406 std::vector
<CertVerifierRequest
*> requests
;
407 requests_
.swap(requests
);
409 for (std::vector
<CertVerifierRequest
*>::iterator
410 i
= requests
.begin(); i
!= requests
.end(); i
++) {
411 (*i
)->Post(verify_result
);
412 // Post() causes the CertVerifierRequest to delete itself.
416 void DeleteAllCanceled() {
417 for (std::vector
<CertVerifierRequest
*>::iterator
418 i
= requests_
.begin(); i
!= requests_
.end(); i
++) {
419 if ((*i
)->canceled()) {
422 LOG(DFATAL
) << "CertVerifierRequest leaked!";
427 const base::TimeTicks start_time_
;
428 std::vector
<CertVerifierRequest
*> requests_
;
429 CertVerifierWorker
* worker_
;
430 const BoundNetLog net_log_
;
433 MultiThreadedCertVerifier::MultiThreadedCertVerifier(
434 CertVerifyProc
* verify_proc
)
435 : cache_(kMaxCacheEntries
),
440 verify_proc_(verify_proc
),
441 trust_anchor_provider_(NULL
) {
442 CertDatabase::GetInstance()->AddObserver(this);
445 MultiThreadedCertVerifier::~MultiThreadedCertVerifier() {
446 STLDeleteValues(&inflight_
);
447 CertDatabase::GetInstance()->RemoveObserver(this);
450 void MultiThreadedCertVerifier::SetCertTrustAnchorProvider(
451 CertTrustAnchorProvider
* trust_anchor_provider
) {
452 DCHECK(CalledOnValidThread());
453 trust_anchor_provider_
= trust_anchor_provider
;
456 int MultiThreadedCertVerifier::Verify(X509Certificate
* cert
,
457 const std::string
& hostname
,
460 CertVerifyResult
* verify_result
,
461 const CompletionCallback
& callback
,
462 RequestHandle
* out_req
,
463 const BoundNetLog
& net_log
) {
464 DCHECK(CalledOnValidThread());
466 if (callback
.is_null() || !verify_result
|| hostname
.empty()) {
468 return ERR_INVALID_ARGUMENT
;
473 const CertificateList empty_cert_list
;
474 const CertificateList
& additional_trust_anchors
=
475 trust_anchor_provider_
?
476 trust_anchor_provider_
->GetAdditionalTrustAnchors() : empty_cert_list
;
478 const RequestParams
key(cert
->fingerprint(), cert
->ca_fingerprint(),
479 hostname
, flags
, additional_trust_anchors
);
480 const CertVerifierCache::value_type
* cached_entry
=
481 cache_
.Get(key
, CacheValidityPeriod(base::Time::Now()));
485 *verify_result
= cached_entry
->result
;
486 return cached_entry
->error
;
489 // No cache hit. See if an identical request is currently in flight.
490 CertVerifierJob
* job
;
491 std::map
<RequestParams
, CertVerifierJob
*>::const_iterator j
;
492 j
= inflight_
.find(key
);
493 if (j
!= inflight_
.end()) {
494 // An identical request is in flight already. We'll just attach our
499 // Need to make a new request.
500 CertVerifierWorker
* worker
=
501 new CertVerifierWorker(verify_proc_
.get(),
506 additional_trust_anchors
,
508 job
= new CertVerifierJob(
510 BoundNetLog::Make(net_log
.net_log(), NetLog::SOURCE_CERT_VERIFIER_JOB
));
511 if (!worker
->Start()) {
515 // TODO(wtc): log to the NetLog.
516 LOG(ERROR
) << "CertVerifierWorker couldn't be started.";
517 return ERR_INSUFFICIENT_RESOURCES
; // Just a guess.
519 inflight_
.insert(std::make_pair(key
, job
));
520 if (requests_
== 1) {
521 // Cleared in HandleResult.
526 CertVerifierRequest
* request
=
527 new CertVerifierRequest(callback
, verify_result
, net_log
);
528 job
->AddRequest(request
);
530 return ERR_IO_PENDING
;
533 void MultiThreadedCertVerifier::CancelRequest(RequestHandle req
) {
534 DCHECK(CalledOnValidThread());
535 CertVerifierRequest
* request
= reinterpret_cast<CertVerifierRequest
*>(req
);
539 MultiThreadedCertVerifier::RequestParams::RequestParams(
540 const SHA1HashValue
& cert_fingerprint_arg
,
541 const SHA1HashValue
& ca_fingerprint_arg
,
542 const std::string
& hostname_arg
,
544 const CertificateList
& additional_trust_anchors
)
545 : hostname(hostname_arg
),
547 hash_values
.reserve(2 + additional_trust_anchors
.size());
548 hash_values
.push_back(cert_fingerprint_arg
);
549 hash_values
.push_back(ca_fingerprint_arg
);
550 for (size_t i
= 0; i
< additional_trust_anchors
.size(); ++i
)
551 hash_values
.push_back(additional_trust_anchors
[i
]->fingerprint());
554 MultiThreadedCertVerifier::RequestParams::~RequestParams() {}
556 bool MultiThreadedCertVerifier::RequestParams::operator<(
557 const RequestParams
& other
) const {
558 // |flags| is compared before |cert_fingerprint|, |ca_fingerprint|, and
559 // |hostname| under assumption that integer comparisons are faster than
560 // memory and string comparisons.
561 if (flags
!= other
.flags
)
562 return flags
< other
.flags
;
563 if (hostname
!= other
.hostname
)
564 return hostname
< other
.hostname
;
565 return std::lexicographical_compare(
566 hash_values
.begin(), hash_values
.end(),
567 other
.hash_values
.begin(), other
.hash_values
.end(),
568 net::SHA1HashValueLessThan());
571 // HandleResult is called by CertVerifierWorker on the origin message loop.
572 // It deletes CertVerifierJob.
573 void MultiThreadedCertVerifier::HandleResult(
574 X509Certificate
* cert
,
575 const std::string
& hostname
,
577 const CertificateList
& additional_trust_anchors
,
579 const CertVerifyResult
& verify_result
) {
580 DCHECK(CalledOnValidThread());
582 const RequestParams
key(cert
->fingerprint(), cert
->ca_fingerprint(),
583 hostname
, flags
, additional_trust_anchors
);
585 CachedResult cached_result
;
586 cached_result
.error
= error
;
587 cached_result
.result
= verify_result
;
588 base::Time now
= base::Time::Now();
590 key
, cached_result
, CacheValidityPeriod(now
),
591 CacheValidityPeriod(now
, now
+ base::TimeDelta::FromSeconds(kTTLSecs
)));
593 std::map
<RequestParams
, CertVerifierJob
*>::iterator j
;
594 j
= inflight_
.find(key
);
595 if (j
== inflight_
.end()) {
599 CertVerifierJob
* job
= j
->second
;
601 bool is_first_job
= false;
602 if (first_job_
== job
) {
607 job
->HandleResult(cached_result
, is_first_job
);
611 void MultiThreadedCertVerifier::OnCACertChanged(
612 const X509Certificate
* cert
) {
613 DCHECK(CalledOnValidThread());