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/ssl/server_bound_cert_service.h"
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/callback_helpers.h"
13 #include "base/compiler_specific.h"
14 #include "base/location.h"
15 #include "base/logging.h"
16 #include "base/memory/ref_counted.h"
17 #include "base/memory/scoped_ptr.h"
18 #include "base/message_loop_proxy.h"
19 #include "base/metrics/histogram.h"
20 #include "base/rand_util.h"
21 #include "base/stl_util.h"
22 #include "base/task_runner.h"
23 #include "crypto/ec_private_key.h"
24 #include "googleurl/src/gurl.h"
25 #include "net/base/net_errors.h"
26 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
27 #include "net/base/x509_certificate.h"
28 #include "net/base/x509_util.h"
31 #include <private/pprthred.h> // PR_DetachThread
38 const int kKeySizeInBits
= 1024;
39 const int kValidityPeriodInDays
= 365;
40 // When we check the system time, we add this many days to the end of the check
41 // so the result will still hold even after chrome has been running for a
43 const int kSystemTimeValidityBufferInDays
= 90;
45 bool IsSupportedCertType(uint8 type
) {
47 case CLIENT_CERT_ECDSA_SIGN
:
49 // If we add any more supported types, CertIsValid will need to be updated
50 // to check that the returned type matches one of the requested types.
56 bool CertIsValid(const std::string
& domain
,
57 SSLClientCertType type
,
58 base::Time expiration_time
) {
59 if (expiration_time
< base::Time::Now()) {
60 DVLOG(1) << "Cert store had expired cert for " << domain
;
62 } else if (!IsSupportedCertType(type
)) {
63 DVLOG(1) << "Cert store had cert of wrong type " << type
<< " for "
70 // Used by the GetDomainBoundCertResult histogram to record the final
71 // outcome of each GetDomainBoundCert call. Do not re-use values.
73 // Synchronously found and returned an existing domain bound cert.
75 // Retrieved or generated and returned a domain bound cert asynchronously.
77 // Retrieval/generation request was cancelled before the cert generation
80 // Cert generation failed.
81 ASYNC_FAILURE_KEYGEN
= 3,
82 ASYNC_FAILURE_CREATE_CERT
= 4,
83 ASYNC_FAILURE_EXPORT_KEY
= 5,
84 ASYNC_FAILURE_UNKNOWN
= 6,
85 // GetDomainBoundCert was called with invalid arguments.
87 // We don't support any of the cert types the server requested.
89 // Server asked for a different type of certs while we were generating one.
91 // Couldn't start a worker to generate a cert.
96 void RecordGetDomainBoundCertResult(GetCertResult result
) {
97 UMA_HISTOGRAM_ENUMERATION("DomainBoundCerts.GetDomainBoundCertResult", result
,
101 void RecordGetCertTime(base::TimeDelta request_time
) {
102 UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.GetCertTime",
104 base::TimeDelta::FromMilliseconds(1),
105 base::TimeDelta::FromMinutes(5),
109 // On success, returns a ServerBoundCert object and sets |*error| to OK.
110 // Otherwise, returns NULL, and |*error| will be set to a net error code.
111 // |serial_number| is passed in because base::RandInt cannot be called from an
112 // unjoined thread, due to relying on a non-leaked LazyInstance
113 scoped_ptr
<ServerBoundCertStore::ServerBoundCert
> GenerateCert(
114 const std::string
& server_identifier
,
115 SSLClientCertType type
,
116 uint32 serial_number
,
118 scoped_ptr
<ServerBoundCertStore::ServerBoundCert
> result
;
120 base::TimeTicks start
= base::TimeTicks::Now();
121 base::Time not_valid_before
= base::Time::Now();
122 base::Time not_valid_after
=
123 not_valid_before
+ base::TimeDelta::FromDays(kValidityPeriodInDays
);
124 std::string der_cert
;
125 std::vector
<uint8
> private_key_info
;
127 case CLIENT_CERT_ECDSA_SIGN
: {
128 scoped_ptr
<crypto::ECPrivateKey
> key(crypto::ECPrivateKey::Create());
130 DLOG(ERROR
) << "Unable to create key pair for client";
131 *error
= ERR_KEY_GENERATION_FAILED
;
132 return result
.Pass();
134 if (!x509_util::CreateDomainBoundCertEC(key
.get(), server_identifier
,
135 serial_number
, not_valid_before
,
136 not_valid_after
, &der_cert
)) {
137 DLOG(ERROR
) << "Unable to create x509 cert for client";
138 *error
= ERR_ORIGIN_BOUND_CERT_GENERATION_FAILED
;
139 return result
.Pass();
142 if (!key
->ExportEncryptedPrivateKey(ServerBoundCertService::kEPKIPassword
,
143 1, &private_key_info
)) {
144 DLOG(ERROR
) << "Unable to export private key";
145 *error
= ERR_PRIVATE_KEY_EXPORT_FAILED
;
146 return result
.Pass();
152 *error
= ERR_INVALID_ARGUMENT
;
153 return result
.Pass();
156 // TODO(rkn): Perhaps ExportPrivateKey should be changed to output a
157 // std::string* to prevent this copying.
158 std::string
key_out(private_key_info
.begin(), private_key_info
.end());
160 result
.reset(new ServerBoundCertStore::ServerBoundCert(
161 server_identifier
, type
, not_valid_before
, not_valid_after
, key_out
,
163 UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.GenerateCertTime",
164 base::TimeTicks::Now() - start
,
165 base::TimeDelta::FromMilliseconds(1),
166 base::TimeDelta::FromMinutes(5),
169 return result
.Pass();
174 // Represents the output and result callback of a request.
175 class ServerBoundCertServiceRequest
{
177 ServerBoundCertServiceRequest(base::TimeTicks request_start
,
178 const CompletionCallback
& callback
,
179 SSLClientCertType
* type
,
180 std::string
* private_key
,
182 : request_start_(request_start
),
185 private_key_(private_key
),
189 // Ensures that the result callback will never be made.
191 RecordGetDomainBoundCertResult(ASYNC_CANCELLED
);
198 // Copies the contents of |private_key| and |cert| to the caller's output
199 // arguments and calls the callback.
201 SSLClientCertType type
,
202 const std::string
& private_key
,
203 const std::string
& cert
) {
206 base::TimeDelta request_time
= base::TimeTicks::Now() - request_start_
;
207 UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.GetCertTimeAsync",
209 base::TimeDelta::FromMilliseconds(1),
210 base::TimeDelta::FromMinutes(5),
212 RecordGetCertTime(request_time
);
213 RecordGetDomainBoundCertResult(ASYNC_SUCCESS
);
216 case ERR_KEY_GENERATION_FAILED
:
217 RecordGetDomainBoundCertResult(ASYNC_FAILURE_KEYGEN
);
219 case ERR_ORIGIN_BOUND_CERT_GENERATION_FAILED
:
220 RecordGetDomainBoundCertResult(ASYNC_FAILURE_CREATE_CERT
);
222 case ERR_PRIVATE_KEY_EXPORT_FAILED
:
223 RecordGetDomainBoundCertResult(ASYNC_FAILURE_EXPORT_KEY
);
225 case ERR_INSUFFICIENT_RESOURCES
:
226 RecordGetDomainBoundCertResult(WORKER_FAILURE
);
229 RecordGetDomainBoundCertResult(ASYNC_FAILURE_UNKNOWN
);
232 if (!callback_
.is_null()) {
234 *private_key_
= private_key
;
236 callback_
.Run(error
);
241 bool canceled() const { return callback_
.is_null(); }
244 base::TimeTicks request_start_
;
245 CompletionCallback callback_
;
246 SSLClientCertType
* type_
;
247 std::string
* private_key_
;
251 // ServerBoundCertServiceWorker runs on a worker thread and takes care of the
252 // blocking process of performing key generation. Will take care of deleting
253 // itself once Start() is called.
254 class ServerBoundCertServiceWorker
{
256 typedef base::Callback
<void(
259 scoped_ptr
<ServerBoundCertStore::ServerBoundCert
>)> WorkerDoneCallback
;
261 ServerBoundCertServiceWorker(
262 const std::string
& server_identifier
,
263 SSLClientCertType type
,
264 const WorkerDoneCallback
& callback
)
265 : server_identifier_(server_identifier
),
267 serial_number_(base::RandInt(0, std::numeric_limits
<int>::max())),
268 origin_loop_(base::MessageLoopProxy::current()),
269 callback_(callback
) {
272 bool Start(const scoped_refptr
<base::TaskRunner
>& task_runner
) {
273 DCHECK(origin_loop_
->RunsTasksOnCurrentThread());
275 return task_runner
->PostTask(
277 base::Bind(&ServerBoundCertServiceWorker::Run
, base::Owned(this)));
282 // Runs on a worker thread.
283 int error
= ERR_FAILED
;
284 scoped_ptr
<ServerBoundCertStore::ServerBoundCert
> cert
=
285 GenerateCert(server_identifier_
, type_
, serial_number_
, &error
);
286 DVLOG(1) << "GenerateCert " << server_identifier_
<< " " << type_
287 << " returned " << error
;
289 // Detach the thread from NSPR.
290 // Calling NSS functions attaches the thread to NSPR, which stores
291 // the NSPR thread ID in thread-specific data.
292 // The threads in our thread pool terminate after we have called
293 // PR_Cleanup. Unless we detach them from NSPR, net_unittests gets
294 // segfaults on shutdown when the threads' thread-specific data
298 origin_loop_
->PostTask(FROM_HERE
,
299 base::Bind(callback_
, server_identifier_
, error
,
300 base::Passed(&cert
)));
303 const std::string server_identifier_
;
304 const SSLClientCertType type_
;
305 // Note that serial_number_ must be initialized on a non-worker thread
306 // (see documentation for GenerateCert).
307 uint32 serial_number_
;
308 scoped_refptr
<base::SequencedTaskRunner
> origin_loop_
;
309 WorkerDoneCallback callback_
;
311 DISALLOW_COPY_AND_ASSIGN(ServerBoundCertServiceWorker
);
314 // A ServerBoundCertServiceJob is a one-to-one counterpart of an
315 // ServerBoundCertServiceWorker. It lives only on the ServerBoundCertService's
316 // origin message loop.
317 class ServerBoundCertServiceJob
{
319 ServerBoundCertServiceJob(SSLClientCertType type
)
323 ~ServerBoundCertServiceJob() {
324 if (!requests_
.empty())
328 SSLClientCertType
type() const { return type_
; }
330 void AddRequest(ServerBoundCertServiceRequest
* request
) {
331 requests_
.push_back(request
);
334 void HandleResult(int error
,
335 SSLClientCertType type
,
336 const std::string
& private_key
,
337 const std::string
& cert
) {
338 PostAll(error
, type
, private_key
, cert
);
342 void PostAll(int error
,
343 SSLClientCertType type
,
344 const std::string
& private_key
,
345 const std::string
& cert
) {
346 std::vector
<ServerBoundCertServiceRequest
*> requests
;
347 requests_
.swap(requests
);
349 for (std::vector
<ServerBoundCertServiceRequest
*>::iterator
350 i
= requests
.begin(); i
!= requests
.end(); i
++) {
351 (*i
)->Post(error
, type
, private_key
, cert
);
352 // Post() causes the ServerBoundCertServiceRequest to delete itself.
356 void DeleteAllCanceled() {
357 for (std::vector
<ServerBoundCertServiceRequest
*>::iterator
358 i
= requests_
.begin(); i
!= requests_
.end(); i
++) {
359 if ((*i
)->canceled()) {
362 LOG(DFATAL
) << "ServerBoundCertServiceRequest leaked!";
367 std::vector
<ServerBoundCertServiceRequest
*> requests_
;
368 SSLClientCertType type_
;
372 const char ServerBoundCertService::kEPKIPassword
[] = "";
374 ServerBoundCertService::RequestHandle::RequestHandle()
378 ServerBoundCertService::RequestHandle::~RequestHandle() {
382 void ServerBoundCertService::RequestHandle::Cancel() {
384 service_
->CancelRequest(request_
);
390 void ServerBoundCertService::RequestHandle::RequestStarted(
391 ServerBoundCertService
* service
,
392 ServerBoundCertServiceRequest
* request
,
393 const CompletionCallback
& callback
) {
394 DCHECK(request_
== NULL
);
397 callback_
= callback
;
400 void ServerBoundCertService::RequestHandle::OnRequestComplete(int result
) {
402 // Running the callback might delete |this|, so we can't touch any of our
403 // members afterwards. Reset callback_ first.
404 base::ResetAndReturn(&callback_
).Run(result
);
407 ServerBoundCertService::ServerBoundCertService(
408 ServerBoundCertStore
* server_bound_cert_store
,
409 const scoped_refptr
<base::TaskRunner
>& task_runner
)
410 : server_bound_cert_store_(server_bound_cert_store
),
411 task_runner_(task_runner
),
412 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
416 base::Time start
= base::Time::Now();
417 base::Time end
= start
+ base::TimeDelta::FromDays(
418 kValidityPeriodInDays
+ kSystemTimeValidityBufferInDays
);
419 is_system_time_valid_
= x509_util::IsSupportedValidityRange(start
, end
);
422 ServerBoundCertService::~ServerBoundCertService() {
423 STLDeleteValues(&inflight_
);
427 std::string
ServerBoundCertService::GetDomainForHost(const std::string
& host
) {
429 RegistryControlledDomainService::GetDomainAndRegistry(host
);
435 int ServerBoundCertService::GetDomainBoundCert(
436 const std::string
& origin
,
437 const std::vector
<uint8
>& requested_types
,
438 SSLClientCertType
* type
,
439 std::string
* private_key
,
441 const CompletionCallback
& callback
,
442 RequestHandle
* out_req
) {
443 DVLOG(1) << __FUNCTION__
<< " " << origin
<< " "
444 << (requested_types
.empty() ? -1 : requested_types
[0])
445 << (requested_types
.size() > 1 ? "..." : "");
446 DCHECK(CalledOnValidThread());
447 base::TimeTicks request_start
= base::TimeTicks::Now();
449 if (callback
.is_null() || !private_key
|| !cert
|| origin
.empty() ||
450 requested_types
.empty()) {
451 RecordGetDomainBoundCertResult(INVALID_ARGUMENT
);
452 return ERR_INVALID_ARGUMENT
;
455 std::string domain
= GetDomainForHost(GURL(origin
).host());
456 if (domain
.empty()) {
457 RecordGetDomainBoundCertResult(INVALID_ARGUMENT
);
458 return ERR_INVALID_ARGUMENT
;
461 SSLClientCertType preferred_type
= CLIENT_CERT_INVALID_TYPE
;
462 for (size_t i
= 0; i
< requested_types
.size(); ++i
) {
463 if (IsSupportedCertType(requested_types
[i
])) {
464 preferred_type
= static_cast<SSLClientCertType
>(requested_types
[i
]);
468 if (preferred_type
== CLIENT_CERT_INVALID_TYPE
) {
469 RecordGetDomainBoundCertResult(UNSUPPORTED_TYPE
);
470 // None of the requested types are supported.
471 return ERR_CLIENT_AUTH_CERT_TYPE_UNSUPPORTED
;
476 // See if an identical request is currently in flight.
477 ServerBoundCertServiceJob
* job
= NULL
;
478 std::map
<std::string
, ServerBoundCertServiceJob
*>::const_iterator j
;
479 j
= inflight_
.find(domain
);
480 if (j
!= inflight_
.end()) {
481 // An identical request is in flight already. We'll just attach our
484 // Check that the job is for an acceptable type of cert.
485 if (std::find(requested_types
.begin(), requested_types
.end(), job
->type())
486 == requested_types
.end()) {
487 DVLOG(1) << "Found inflight job of wrong type " << job
->type()
488 << " for " << domain
;
489 // If we get here, the server is asking for different types of certs in
490 // short succession. This probably means the server is broken or
491 // misconfigured. Since we only store one type of cert per domain, we
492 // are unable to handle this well. Just return an error and let the first
494 RecordGetDomainBoundCertResult(TYPE_MISMATCH
);
495 return ERR_ORIGIN_BOUND_CERT_GENERATION_TYPE_MISMATCH
;
499 ServerBoundCertServiceRequest
* request
= new ServerBoundCertServiceRequest(
501 base::Bind(&RequestHandle::OnRequestComplete
,
502 base::Unretained(out_req
)),
503 type
, private_key
, cert
);
504 job
->AddRequest(request
);
505 out_req
->RequestStarted(this, request
, callback
);
506 return ERR_IO_PENDING
;
509 // Check if a domain bound cert of an acceptable type already exists for this
510 // domain, and that it has not expired.
511 base::Time expiration_time
;
512 if (server_bound_cert_store_
->GetServerBoundCert(
518 base::Bind(&ServerBoundCertService::GotServerBoundCert
,
519 weak_ptr_factory_
.GetWeakPtr()))) {
520 if (*type
!= CLIENT_CERT_INVALID_TYPE
) {
521 // Sync lookup found a cert.
522 if (CertIsValid(domain
, *type
, expiration_time
)) {
523 DVLOG(1) << "Cert store had valid cert for " << domain
524 << " of type " << *type
;
526 RecordGetDomainBoundCertResult(SYNC_SUCCESS
);
527 base::TimeDelta request_time
= base::TimeTicks::Now() - request_start
;
528 UMA_HISTOGRAM_TIMES("DomainBoundCerts.GetCertTimeSync", request_time
);
529 RecordGetCertTime(request_time
);
534 // Sync lookup did not find a cert, or it found an expired one. Start
535 // generating a new one.
536 ServerBoundCertServiceWorker
* worker
= new ServerBoundCertServiceWorker(
539 base::Bind(&ServerBoundCertService::GeneratedServerBoundCert
,
540 weak_ptr_factory_
.GetWeakPtr()));
541 if (!worker
->Start(task_runner_
)) {
543 // TODO(rkn): Log to the NetLog.
544 LOG(ERROR
) << "ServerBoundCertServiceWorker couldn't be started.";
545 RecordGetDomainBoundCertResult(WORKER_FAILURE
);
546 return ERR_INSUFFICIENT_RESOURCES
;
550 // We are either waiting for async DB lookup, or waiting for cert generation.
551 // Create a job & request to track it.
552 job
= new ServerBoundCertServiceJob(preferred_type
);
553 inflight_
[domain
] = job
;
555 ServerBoundCertServiceRequest
* request
= new ServerBoundCertServiceRequest(
557 base::Bind(&RequestHandle::OnRequestComplete
, base::Unretained(out_req
)),
558 type
, private_key
, cert
);
559 job
->AddRequest(request
);
560 out_req
->RequestStarted(this, request
, callback
);
561 return ERR_IO_PENDING
;
564 void ServerBoundCertService::GotServerBoundCert(
565 const std::string
& server_identifier
,
566 SSLClientCertType type
,
567 base::Time expiration_time
,
568 const std::string
& key
,
569 const std::string
& cert
) {
570 DCHECK(CalledOnValidThread());
572 std::map
<std::string
, ServerBoundCertServiceJob
*>::iterator j
;
573 j
= inflight_
.find(server_identifier
);
574 if (j
== inflight_
.end()) {
578 ServerBoundCertServiceJob
* job
= j
->second
;
580 if (type
!= CLIENT_CERT_INVALID_TYPE
) {
581 // Async DB lookup found a cert.
582 if (CertIsValid(server_identifier
, type
, expiration_time
)) {
583 DVLOG(1) << "Cert store had valid cert for " << server_identifier
584 << " of type " << type
;
586 // ServerBoundCertServiceRequest::Post will do the histograms and stuff.
587 HandleResult(OK
, server_identifier
, type
, key
, cert
);
592 // Async lookup did not find a cert, or it found an expired one. Start
593 // generating a new one.
594 ServerBoundCertServiceWorker
* worker
= new ServerBoundCertServiceWorker(
597 base::Bind(&ServerBoundCertService::GeneratedServerBoundCert
,
598 weak_ptr_factory_
.GetWeakPtr()));
599 if (!worker
->Start(task_runner_
)) {
601 // TODO(rkn): Log to the NetLog.
602 LOG(ERROR
) << "ServerBoundCertServiceWorker couldn't be started.";
603 HandleResult(ERR_INSUFFICIENT_RESOURCES
, server_identifier
,
604 CLIENT_CERT_INVALID_TYPE
, "", "");
609 ServerBoundCertStore
* ServerBoundCertService::GetCertStore() {
610 return server_bound_cert_store_
.get();
613 void ServerBoundCertService::CancelRequest(ServerBoundCertServiceRequest
* req
) {
614 DCHECK(CalledOnValidThread());
618 void ServerBoundCertService::GeneratedServerBoundCert(
619 const std::string
& server_identifier
,
621 scoped_ptr
<ServerBoundCertStore::ServerBoundCert
> cert
) {
622 DCHECK(CalledOnValidThread());
625 // TODO(mattm): we should just Pass() the cert object to
626 // SetServerBoundCert().
627 server_bound_cert_store_
->SetServerBoundCert(
628 cert
->server_identifier(), cert
->type(), cert
->creation_time(),
629 cert
->expiration_time(), cert
->private_key(), cert
->cert());
631 HandleResult(error
, server_identifier
, cert
->type(), cert
->private_key(),
634 HandleResult(error
, server_identifier
, CLIENT_CERT_INVALID_TYPE
, "", "");
638 void ServerBoundCertService::HandleResult(
640 const std::string
& server_identifier
,
641 SSLClientCertType type
,
642 const std::string
& private_key
,
643 const std::string
& cert
) {
644 DCHECK(CalledOnValidThread());
646 std::map
<std::string
, ServerBoundCertServiceJob
*>::iterator j
;
647 j
= inflight_
.find(server_identifier
);
648 if (j
== inflight_
.end()) {
652 ServerBoundCertServiceJob
* job
= j
->second
;
655 job
->HandleResult(error
, type
, private_key
, cert
);
659 int ServerBoundCertService::cert_count() {
660 return server_bound_cert_store_
->GetCertCount();