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.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.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"
26 #if defined(USE_NSS) || defined(OS_IOS)
27 #include <private/pprthred.h> // PR_DetachThread
32 ////////////////////////////////////////////////////////////////////////////
36 // MultiThreadedCertVerifier CertVerifierJob CertVerifierWorker Request
37 // | (origin loop) (worker loop)
40 // |---->-------------------------------------<creates>
42 // |---->-------------------<creates>
44 // |---->-------------------------------------------------------<creates>
46 // |---->---------------------------------------Start
50 // | <starts verifying>
51 // |---->-------------------AddRequest |
61 // |----<-----------------------------------------|
64 // |---->------------------HandleResult
66 // |------>---------------------------Post
70 // On a cache hit, MultiThreadedCertVerifier::Verify() returns synchronously
71 // without posting a task to a worker thread.
75 // The default value of max_cache_entries_.
76 const unsigned kMaxCacheEntries
= 256;
78 // The number of seconds for which we'll cache a cache entry.
79 const unsigned kTTLSecs
= 1800; // 30 minutes.
83 MultiThreadedCertVerifier::CachedResult::CachedResult() : error(ERR_FAILED
) {}
85 MultiThreadedCertVerifier::CachedResult::~CachedResult() {}
87 MultiThreadedCertVerifier::CacheValidityPeriod::CacheValidityPeriod(
88 const base::Time
& now
)
89 : verification_time(now
),
90 expiration_time(now
) {
93 MultiThreadedCertVerifier::CacheValidityPeriod::CacheValidityPeriod(
94 const base::Time
& now
,
95 const base::Time
& expiration
)
96 : verification_time(now
),
97 expiration_time(expiration
) {
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
106 // Note: DCHECK_EQ is not used due to operator<< overloading requirements.
107 DCHECK(now
.verification_time
== now
.expiration_time
);
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.
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
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
131 return now
.verification_time
>= expiration
.verification_time
&&
132 now
.verification_time
< expiration
.expiration_time
;
136 // Represents the output and result callback of a request.
137 class CertVerifierRequest
{
139 CertVerifierRequest(const CompletionCallback
& callback
,
140 CertVerifyResult
* verify_result
,
141 const BoundNetLog
& net_log
)
142 : callback_(callback
),
143 verify_result_(verify_result
),
145 net_log_
.BeginEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST
);
148 ~CertVerifierRequest() {
151 // Ensures that the result callback will never be made.
154 verify_result_
= NULL
;
155 net_log_
.AddEvent(NetLog::TYPE_CANCELLED
);
156 net_log_
.EndEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST
);
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
);
170 bool canceled() const { return callback_
.is_null(); }
172 const BoundNetLog
& net_log() const { return net_log_
; }
175 CompletionCallback callback_
;
176 CertVerifyResult
* verify_result_
;
177 const BoundNetLog net_log_
;
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
{
186 CertVerifierWorker(CertVerifyProc
* verify_proc
,
187 X509Certificate
* cert
,
188 const std::string
& hostname
,
191 const CertificateList
& additional_trust_anchors
,
192 MultiThreadedCertVerifier
* cert_verifier
)
193 : verify_proc_(verify_proc
),
198 additional_trust_anchors_(additional_trust_anchors
),
199 origin_loop_(MessageLoop::current()),
200 cert_verifier_(cert_verifier
),
205 // Returns the certificate being verified. May only be called /before/
206 // Start() is called.
207 X509Certificate
* certificate() const { return cert_
; }
210 DCHECK_EQ(MessageLoop::current(), origin_loop_
);
212 return base::WorkerPool::PostTask(
213 FROM_HERE
, base::Bind(&CertVerifierWorker::Run
, base::Unretained(this)),
214 true /* task is slow */);
217 // Cancel is called from the origin loop when the MultiThreadedCertVerifier is
220 DCHECK_EQ(MessageLoop::current(), origin_loop_
);
221 base::AutoLock
locked(lock_
);
227 // Runs on a worker thread.
228 error_
= verify_proc_
->Verify(cert_
, hostname_
, flags_
, crl_set_
,
229 additional_trust_anchors_
, &verify_result_
);
230 #if defined(USE_NSS) || defined(OS_IOS)
231 // Detach the thread from NSPR.
232 // Calling NSS functions attaches the thread to NSPR, which stores
233 // the NSPR thread ID in thread-specific data.
234 // The threads in our thread pool terminate after we have called
235 // PR_Cleanup. Unless we detach them from NSPR, net_unittests gets
236 // segfaults on shutdown when the threads' thread-specific data
243 // DoReply runs on the origin thread.
245 DCHECK_EQ(MessageLoop::current(), origin_loop_
);
247 // We lock here because the worker thread could still be in Finished,
248 // after the PostTask, but before unlocking |lock_|. If we do not lock in
249 // this case, we will end up deleting a locked Lock, which can lead to
250 // memory leaks or worse errors.
251 base::AutoLock
locked(lock_
);
253 cert_verifier_
->HandleResult(cert_
, hostname_
, flags_
,
254 additional_trust_anchors_
, error_
,
262 // Runs on the worker thread.
263 // We assume that the origin loop outlives the MultiThreadedCertVerifier. If
264 // the MultiThreadedCertVerifier is deleted, it will call Cancel on us. If
265 // it does so before the Acquire, we'll delete ourselves and return. If it's
266 // trying to do so concurrently, then it'll block on the lock and we'll call
267 // PostTask while the MultiThreadedCertVerifier (and therefore the
268 // MessageLoop) is still alive.
269 // If it does so after this function, we assume that the MessageLoop will
270 // process pending tasks. In which case we'll notice the |canceled_| flag
275 base::AutoLock
locked(lock_
);
276 canceled
= canceled_
;
278 origin_loop_
->PostTask(
279 FROM_HERE
, base::Bind(
280 &CertVerifierWorker::DoReply
, base::Unretained(this)));
288 scoped_refptr
<CertVerifyProc
> verify_proc_
;
289 scoped_refptr
<X509Certificate
> cert_
;
290 const std::string hostname_
;
292 scoped_refptr
<CRLSet
> crl_set_
;
293 const CertificateList additional_trust_anchors_
;
294 MessageLoop
* const origin_loop_
;
295 MultiThreadedCertVerifier
* const cert_verifier_
;
297 // lock_ protects canceled_.
300 // If canceled_ is true,
301 // * origin_loop_ cannot be accessed by the worker thread,
302 // * cert_verifier_ cannot be accessed by any thread.
306 CertVerifyResult verify_result_
;
308 DISALLOW_COPY_AND_ASSIGN(CertVerifierWorker
);
311 // A CertVerifierJob is a one-to-one counterpart of a CertVerifierWorker. It
312 // lives only on the CertVerifier's origin message loop.
313 class CertVerifierJob
{
315 CertVerifierJob(CertVerifierWorker
* worker
,
316 const BoundNetLog
& net_log
)
317 : start_time_(base::TimeTicks::Now()),
321 NetLog::TYPE_CERT_VERIFIER_JOB
,
322 base::Bind(&NetLogX509CertificateCallback
,
323 base::Unretained(worker_
->certificate())));
328 net_log_
.AddEvent(NetLog::TYPE_CANCELLED
);
329 net_log_
.EndEvent(NetLog::TYPE_CERT_VERIFIER_JOB
);
335 void AddRequest(CertVerifierRequest
* request
) {
336 request
->net_log().AddEvent(
337 NetLog::TYPE_CERT_VERIFIER_REQUEST_BOUND_TO_JOB
,
338 net_log_
.source().ToEventParametersCallback());
340 requests_
.push_back(request
);
344 const MultiThreadedCertVerifier::CachedResult
& verify_result
) {
346 net_log_
.EndEvent(NetLog::TYPE_CERT_VERIFIER_JOB
);
347 UMA_HISTOGRAM_CUSTOM_TIMES("Net.CertVerifier_Job_Latency",
348 base::TimeTicks::Now() - start_time_
,
349 base::TimeDelta::FromMilliseconds(1),
350 base::TimeDelta::FromMinutes(10),
352 PostAll(verify_result
);
356 void PostAll(const MultiThreadedCertVerifier::CachedResult
& verify_result
) {
357 std::vector
<CertVerifierRequest
*> requests
;
358 requests_
.swap(requests
);
360 for (std::vector
<CertVerifierRequest
*>::iterator
361 i
= requests
.begin(); i
!= requests
.end(); i
++) {
362 (*i
)->Post(verify_result
);
363 // Post() causes the CertVerifierRequest to delete itself.
367 void DeleteAllCanceled() {
368 for (std::vector
<CertVerifierRequest
*>::iterator
369 i
= requests_
.begin(); i
!= requests_
.end(); i
++) {
370 if ((*i
)->canceled()) {
373 LOG(DFATAL
) << "CertVerifierRequest leaked!";
378 const base::TimeTicks start_time_
;
379 std::vector
<CertVerifierRequest
*> requests_
;
380 CertVerifierWorker
* worker_
;
381 const BoundNetLog net_log_
;
384 MultiThreadedCertVerifier::MultiThreadedCertVerifier(
385 CertVerifyProc
* verify_proc
)
386 : cache_(kMaxCacheEntries
),
390 verify_proc_(verify_proc
),
391 trust_anchor_provider_(NULL
) {
392 CertDatabase::GetInstance()->AddObserver(this);
395 MultiThreadedCertVerifier::~MultiThreadedCertVerifier() {
396 STLDeleteValues(&inflight_
);
397 CertDatabase::GetInstance()->RemoveObserver(this);
400 void MultiThreadedCertVerifier::SetCertTrustAnchorProvider(
401 CertTrustAnchorProvider
* trust_anchor_provider
) {
402 DCHECK(CalledOnValidThread());
403 trust_anchor_provider_
= trust_anchor_provider
;
406 int MultiThreadedCertVerifier::Verify(X509Certificate
* cert
,
407 const std::string
& hostname
,
410 CertVerifyResult
* verify_result
,
411 const CompletionCallback
& callback
,
412 RequestHandle
* out_req
,
413 const BoundNetLog
& net_log
) {
414 DCHECK(CalledOnValidThread());
416 if (callback
.is_null() || !verify_result
|| hostname
.empty()) {
418 return ERR_INVALID_ARGUMENT
;
423 const CertificateList empty_cert_list
;
424 const CertificateList
& additional_trust_anchors
=
425 trust_anchor_provider_
?
426 trust_anchor_provider_
->GetAdditionalTrustAnchors() : empty_cert_list
;
428 const RequestParams
key(cert
->fingerprint(), cert
->ca_fingerprint(),
429 hostname
, flags
, additional_trust_anchors
);
430 const CertVerifierCache::value_type
* cached_entry
=
431 cache_
.Get(key
, CacheValidityPeriod(base::Time::Now()));
435 *verify_result
= cached_entry
->result
;
436 return cached_entry
->error
;
439 // No cache hit. See if an identical request is currently in flight.
440 CertVerifierJob
* job
;
441 std::map
<RequestParams
, CertVerifierJob
*>::const_iterator j
;
442 j
= inflight_
.find(key
);
443 if (j
!= inflight_
.end()) {
444 // An identical request is in flight already. We'll just attach our
449 // Need to make a new request.
450 CertVerifierWorker
* worker
=
451 new CertVerifierWorker(verify_proc_
, cert
, hostname
, flags
, crl_set
,
452 additional_trust_anchors
, this);
453 job
= new CertVerifierJob(
455 BoundNetLog::Make(net_log
.net_log(), NetLog::SOURCE_CERT_VERIFIER_JOB
));
456 if (!worker
->Start()) {
460 // TODO(wtc): log to the NetLog.
461 LOG(ERROR
) << "CertVerifierWorker couldn't be started.";
462 return ERR_INSUFFICIENT_RESOURCES
; // Just a guess.
464 inflight_
.insert(std::make_pair(key
, job
));
467 CertVerifierRequest
* request
=
468 new CertVerifierRequest(callback
, verify_result
, net_log
);
469 job
->AddRequest(request
);
471 return ERR_IO_PENDING
;
474 void MultiThreadedCertVerifier::CancelRequest(RequestHandle req
) {
475 DCHECK(CalledOnValidThread());
476 CertVerifierRequest
* request
= reinterpret_cast<CertVerifierRequest
*>(req
);
480 MultiThreadedCertVerifier::RequestParams::RequestParams(
481 const SHA1HashValue
& cert_fingerprint_arg
,
482 const SHA1HashValue
& ca_fingerprint_arg
,
483 const std::string
& hostname_arg
,
485 const CertificateList
& additional_trust_anchors
)
486 : hostname(hostname_arg
),
488 hash_values
.reserve(2 + additional_trust_anchors
.size());
489 hash_values
.push_back(cert_fingerprint_arg
);
490 hash_values
.push_back(ca_fingerprint_arg
);
491 for (size_t i
= 0; i
< additional_trust_anchors
.size(); ++i
)
492 hash_values
.push_back(additional_trust_anchors
[i
]->fingerprint());
495 MultiThreadedCertVerifier::RequestParams::~RequestParams() {}
497 bool MultiThreadedCertVerifier::RequestParams::operator<(
498 const RequestParams
& other
) const {
499 // |flags| is compared before |cert_fingerprint|, |ca_fingerprint|, and
500 // |hostname| under assumption that integer comparisons are faster than
501 // memory and string comparisons.
502 if (flags
!= other
.flags
)
503 return flags
< other
.flags
;
504 if (hostname
!= other
.hostname
)
505 return hostname
< other
.hostname
;
506 return std::lexicographical_compare(
507 hash_values
.begin(), hash_values
.end(),
508 other
.hash_values
.begin(), other
.hash_values
.end(),
509 net::SHA1HashValueLessThan());
512 // HandleResult is called by CertVerifierWorker on the origin message loop.
513 // It deletes CertVerifierJob.
514 void MultiThreadedCertVerifier::HandleResult(
515 X509Certificate
* cert
,
516 const std::string
& hostname
,
518 const CertificateList
& additional_trust_anchors
,
520 const CertVerifyResult
& verify_result
) {
521 DCHECK(CalledOnValidThread());
523 const RequestParams
key(cert
->fingerprint(), cert
->ca_fingerprint(),
524 hostname
, flags
, additional_trust_anchors
);
526 CachedResult cached_result
;
527 cached_result
.error
= error
;
528 cached_result
.result
= verify_result
;
529 base::Time now
= base::Time::Now();
531 key
, cached_result
, CacheValidityPeriod(now
),
532 CacheValidityPeriod(now
, now
+ base::TimeDelta::FromSeconds(kTTLSecs
)));
534 std::map
<RequestParams
, CertVerifierJob
*>::iterator j
;
535 j
= inflight_
.find(key
);
536 if (j
== inflight_
.end()) {
540 CertVerifierJob
* job
= j
->second
;
543 job
->HandleResult(cached_result
);
547 void MultiThreadedCertVerifier::OnCertTrustChanged(
548 const X509Certificate
* cert
) {
549 DCHECK(CalledOnValidThread());