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/base/server_bound_cert_service.h"
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/compiler_specific.h"
13 #include "base/location.h"
14 #include "base/logging.h"
15 #include "base/memory/ref_counted.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/message_loop_proxy.h"
18 #include "base/metrics/histogram.h"
19 #include "base/rand_util.h"
20 #include "base/stl_util.h"
21 #include "base/task_runner.h"
22 #include "crypto/ec_private_key.h"
23 #include "googleurl/src/gurl.h"
24 #include "net/base/net_errors.h"
25 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
26 #include "net/base/x509_certificate.h"
27 #include "net/base/x509_util.h"
30 #include <private/pprthred.h> // PR_DetachThread
37 const int kKeySizeInBits
= 1024;
38 const int kValidityPeriodInDays
= 365;
39 // When we check the system time, we add this many days to the end of the check
40 // so the result will still hold even after chrome has been running for a
42 const int kSystemTimeValidityBufferInDays
= 90;
44 bool IsSupportedCertType(uint8 type
) {
46 case CLIENT_CERT_ECDSA_SIGN
:
53 // Used by the GetDomainBoundCertResult histogram to record the final
54 // outcome of each GetDomainBoundCert call. Do not re-use values.
56 // Synchronously found and returned an existing domain bound cert.
58 // Generated and returned a domain bound cert asynchronously.
60 // Generation request was cancelled before the cert generation completed.
62 // Cert generation failed.
63 ASYNC_FAILURE_KEYGEN
= 3,
64 ASYNC_FAILURE_CREATE_CERT
= 4,
65 ASYNC_FAILURE_EXPORT_KEY
= 5,
66 ASYNC_FAILURE_UNKNOWN
= 6,
67 // GetDomainBoundCert was called with invalid arguments.
69 // We don't support any of the cert types the server requested.
71 // Server asked for a different type of certs while we were generating one.
73 // Couldn't start a worker to generate a cert.
78 void RecordGetDomainBoundCertResult(GetCertResult result
) {
79 UMA_HISTOGRAM_ENUMERATION("DomainBoundCerts.GetDomainBoundCertResult", result
,
83 void RecordGetCertTime(base::TimeDelta request_time
) {
84 UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.GetCertTime",
86 base::TimeDelta::FromMilliseconds(1),
87 base::TimeDelta::FromMinutes(5),
91 // On success, returns a ServerBoundCert object and sets |*error| to OK.
92 // Otherwise, returns NULL, and |*error| will be set to a net error code.
93 // |serial_number| is passed in because base::RandInt cannot be called from an
94 // unjoined thread, due to relying on a non-leaked LazyInstance
95 scoped_ptr
<ServerBoundCertStore::ServerBoundCert
> GenerateCert(
96 const std::string
& server_identifier
,
97 SSLClientCertType type
,
100 scoped_ptr
<ServerBoundCertStore::ServerBoundCert
> result
;
102 base::TimeTicks start
= base::TimeTicks::Now();
103 base::Time not_valid_before
= base::Time::Now();
104 base::Time not_valid_after
=
105 not_valid_before
+ base::TimeDelta::FromDays(kValidityPeriodInDays
);
106 std::string der_cert
;
107 std::vector
<uint8
> private_key_info
;
109 case CLIENT_CERT_ECDSA_SIGN
: {
110 scoped_ptr
<crypto::ECPrivateKey
> key(crypto::ECPrivateKey::Create());
112 DLOG(ERROR
) << "Unable to create key pair for client";
113 *error
= ERR_KEY_GENERATION_FAILED
;
114 return result
.Pass();
116 if (!x509_util::CreateDomainBoundCertEC(key
.get(), server_identifier
,
117 serial_number
, not_valid_before
,
118 not_valid_after
, &der_cert
)) {
119 DLOG(ERROR
) << "Unable to create x509 cert for client";
120 *error
= ERR_ORIGIN_BOUND_CERT_GENERATION_FAILED
;
121 return result
.Pass();
124 if (!key
->ExportEncryptedPrivateKey(ServerBoundCertService::kEPKIPassword
,
125 1, &private_key_info
)) {
126 DLOG(ERROR
) << "Unable to export private key";
127 *error
= ERR_PRIVATE_KEY_EXPORT_FAILED
;
128 return result
.Pass();
134 *error
= ERR_INVALID_ARGUMENT
;
135 return result
.Pass();
138 // TODO(rkn): Perhaps ExportPrivateKey should be changed to output a
139 // std::string* to prevent this copying.
140 std::string
key_out(private_key_info
.begin(), private_key_info
.end());
142 result
.reset(new ServerBoundCertStore::ServerBoundCert(
143 server_identifier
, type
, not_valid_before
, not_valid_after
, key_out
,
145 UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.GenerateCertTime",
146 base::TimeTicks::Now() - start
,
147 base::TimeDelta::FromMilliseconds(1),
148 base::TimeDelta::FromMinutes(5),
151 return result
.Pass();
156 // Represents the output and result callback of a request.
157 class ServerBoundCertServiceRequest
{
159 ServerBoundCertServiceRequest(base::TimeTicks request_start
,
160 const CompletionCallback
& callback
,
161 SSLClientCertType
* type
,
162 std::string
* private_key
,
164 : request_start_(request_start
),
167 private_key_(private_key
),
171 // Ensures that the result callback will never be made.
173 RecordGetDomainBoundCertResult(ASYNC_CANCELLED
);
180 // Copies the contents of |private_key| and |cert| to the caller's output
181 // arguments and calls the callback.
183 SSLClientCertType type
,
184 const std::string
& private_key
,
185 const std::string
& cert
) {
188 base::TimeDelta request_time
= base::TimeTicks::Now() - request_start_
;
189 UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.GetCertTimeAsync",
191 base::TimeDelta::FromMilliseconds(1),
192 base::TimeDelta::FromMinutes(5),
194 RecordGetCertTime(request_time
);
195 RecordGetDomainBoundCertResult(ASYNC_SUCCESS
);
198 case ERR_KEY_GENERATION_FAILED
:
199 RecordGetDomainBoundCertResult(ASYNC_FAILURE_KEYGEN
);
201 case ERR_ORIGIN_BOUND_CERT_GENERATION_FAILED
:
202 RecordGetDomainBoundCertResult(ASYNC_FAILURE_CREATE_CERT
);
204 case ERR_PRIVATE_KEY_EXPORT_FAILED
:
205 RecordGetDomainBoundCertResult(ASYNC_FAILURE_EXPORT_KEY
);
208 RecordGetDomainBoundCertResult(ASYNC_FAILURE_UNKNOWN
);
211 if (!callback_
.is_null()) {
213 *private_key_
= private_key
;
215 callback_
.Run(error
);
220 bool canceled() const { return callback_
.is_null(); }
223 base::TimeTicks request_start_
;
224 CompletionCallback callback_
;
225 SSLClientCertType
* type_
;
226 std::string
* private_key_
;
230 // ServerBoundCertServiceWorker runs on a worker thread and takes care of the
231 // blocking process of performing key generation. Will take care of deleting
232 // itself once Start() is called.
233 class ServerBoundCertServiceWorker
{
235 typedef base::Callback
<void(
238 scoped_ptr
<ServerBoundCertStore::ServerBoundCert
>)> WorkerDoneCallback
;
240 ServerBoundCertServiceWorker(
241 const std::string
& server_identifier
,
242 SSLClientCertType type
,
243 const WorkerDoneCallback
& callback
)
244 : server_identifier_(server_identifier
),
246 serial_number_(base::RandInt(0, std::numeric_limits
<int>::max())),
247 origin_loop_(base::MessageLoopProxy::current()),
248 callback_(callback
) {
251 bool Start(const scoped_refptr
<base::TaskRunner
>& task_runner
) {
252 DCHECK(origin_loop_
->RunsTasksOnCurrentThread());
254 return task_runner
->PostTask(
256 base::Bind(&ServerBoundCertServiceWorker::Run
, base::Owned(this)));
261 // Runs on a worker thread.
262 int error
= ERR_FAILED
;
263 scoped_ptr
<ServerBoundCertStore::ServerBoundCert
> cert
=
264 GenerateCert(server_identifier_
, type_
, serial_number_
, &error
);
265 DVLOG(1) << "GenerateCert " << server_identifier_
<< " " << type_
266 << " returned " << error
;
268 // Detach the thread from NSPR.
269 // Calling NSS functions attaches the thread to NSPR, which stores
270 // the NSPR thread ID in thread-specific data.
271 // The threads in our thread pool terminate after we have called
272 // PR_Cleanup. Unless we detach them from NSPR, net_unittests gets
273 // segfaults on shutdown when the threads' thread-specific data
277 origin_loop_
->PostTask(FROM_HERE
,
278 base::Bind(callback_
, server_identifier_
, error
,
279 base::Passed(&cert
)));
282 const std::string server_identifier_
;
283 const SSLClientCertType type_
;
284 // Note that serial_number_ must be initialized on a non-worker thread
285 // (see documentation for GenerateCert).
286 uint32 serial_number_
;
287 scoped_refptr
<base::SequencedTaskRunner
> origin_loop_
;
288 WorkerDoneCallback callback_
;
290 DISALLOW_COPY_AND_ASSIGN(ServerBoundCertServiceWorker
);
293 // A ServerBoundCertServiceJob is a one-to-one counterpart of an
294 // ServerBoundCertServiceWorker. It lives only on the ServerBoundCertService's
295 // origin message loop.
296 class ServerBoundCertServiceJob
{
298 ServerBoundCertServiceJob(SSLClientCertType type
) : type_(type
) {
301 ~ServerBoundCertServiceJob() {
302 if (!requests_
.empty())
306 SSLClientCertType
type() const { return type_
; }
308 void AddRequest(ServerBoundCertServiceRequest
* request
) {
309 requests_
.push_back(request
);
312 void HandleResult(int error
,
313 SSLClientCertType type
,
314 const std::string
& private_key
,
315 const std::string
& cert
) {
316 PostAll(error
, type
, private_key
, cert
);
320 void PostAll(int error
,
321 SSLClientCertType type
,
322 const std::string
& private_key
,
323 const std::string
& cert
) {
324 std::vector
<ServerBoundCertServiceRequest
*> requests
;
325 requests_
.swap(requests
);
327 for (std::vector
<ServerBoundCertServiceRequest
*>::iterator
328 i
= requests
.begin(); i
!= requests
.end(); i
++) {
329 (*i
)->Post(error
, type
, private_key
, cert
);
330 // Post() causes the ServerBoundCertServiceRequest to delete itself.
334 void DeleteAllCanceled() {
335 for (std::vector
<ServerBoundCertServiceRequest
*>::iterator
336 i
= requests_
.begin(); i
!= requests_
.end(); i
++) {
337 if ((*i
)->canceled()) {
340 LOG(DFATAL
) << "ServerBoundCertServiceRequest leaked!";
345 std::vector
<ServerBoundCertServiceRequest
*> requests_
;
346 SSLClientCertType type_
;
350 const char ServerBoundCertService::kEPKIPassword
[] = "";
352 ServerBoundCertService::RequestHandle::RequestHandle()
356 ServerBoundCertService::RequestHandle::~RequestHandle() {
360 void ServerBoundCertService::RequestHandle::Cancel() {
362 service_
->CancelRequest(request_
);
368 void ServerBoundCertService::RequestHandle::RequestStarted(
369 ServerBoundCertService
* service
,
370 ServerBoundCertServiceRequest
* request
,
371 const CompletionCallback
& callback
) {
372 DCHECK(request_
== NULL
);
375 callback_
= callback
;
378 void ServerBoundCertService::RequestHandle::OnRequestComplete(int result
) {
380 callback_
.Run(result
);
384 ServerBoundCertService::ServerBoundCertService(
385 ServerBoundCertStore
* server_bound_cert_store
,
386 const scoped_refptr
<base::TaskRunner
>& task_runner
)
387 : server_bound_cert_store_(server_bound_cert_store
),
388 task_runner_(task_runner
),
389 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
393 base::Time start
= base::Time::Now();
394 base::Time end
= start
+ base::TimeDelta::FromDays(
395 kValidityPeriodInDays
+ kSystemTimeValidityBufferInDays
);
396 is_system_time_valid_
= x509_util::IsSupportedValidityRange(start
, end
);
399 ServerBoundCertService::~ServerBoundCertService() {
400 STLDeleteValues(&inflight_
);
404 std::string
ServerBoundCertService::GetDomainForHost(const std::string
& host
) {
406 RegistryControlledDomainService::GetDomainAndRegistry(host
);
412 int ServerBoundCertService::GetDomainBoundCert(
413 const std::string
& origin
,
414 const std::vector
<uint8
>& requested_types
,
415 SSLClientCertType
* type
,
416 std::string
* private_key
,
418 const CompletionCallback
& callback
,
419 RequestHandle
* out_req
) {
420 DVLOG(1) << __FUNCTION__
<< " " << origin
<< " "
421 << (requested_types
.empty() ? -1 : requested_types
[0])
422 << (requested_types
.size() > 1 ? "..." : "");
423 DCHECK(CalledOnValidThread());
424 base::TimeTicks request_start
= base::TimeTicks::Now();
426 if (callback
.is_null() || !private_key
|| !cert
|| origin
.empty() ||
427 requested_types
.empty()) {
428 RecordGetDomainBoundCertResult(INVALID_ARGUMENT
);
429 return ERR_INVALID_ARGUMENT
;
432 std::string domain
= GetDomainForHost(GURL(origin
).host());
433 if (domain
.empty()) {
434 RecordGetDomainBoundCertResult(INVALID_ARGUMENT
);
435 return ERR_INVALID_ARGUMENT
;
438 SSLClientCertType preferred_type
= CLIENT_CERT_INVALID_TYPE
;
439 for (size_t i
= 0; i
< requested_types
.size(); ++i
) {
440 if (IsSupportedCertType(requested_types
[i
])) {
441 preferred_type
= static_cast<SSLClientCertType
>(requested_types
[i
]);
445 if (preferred_type
== CLIENT_CERT_INVALID_TYPE
) {
446 RecordGetDomainBoundCertResult(UNSUPPORTED_TYPE
);
447 // None of the requested types are supported.
448 return ERR_CLIENT_AUTH_CERT_TYPE_UNSUPPORTED
;
453 // Check if a domain bound cert of an acceptable type already exists for this
454 // domain, and that it has not expired.
455 base::Time now
= base::Time::Now();
456 base::Time creation_time
;
457 base::Time expiration_time
;
458 if (server_bound_cert_store_
->GetServerBoundCert(domain
,
464 if (expiration_time
< now
) {
465 DVLOG(1) << "Cert store had expired cert for " << domain
;
466 } else if (!IsSupportedCertType(*type
) ||
467 std::find(requested_types
.begin(), requested_types
.end(),
468 *type
) == requested_types
.end()) {
469 DVLOG(1) << "Cert store had cert of wrong type " << *type
<< " for "
472 DVLOG(1) << "Cert store had valid cert for " << domain
473 << " of type " << *type
;
475 RecordGetDomainBoundCertResult(SYNC_SUCCESS
);
476 base::TimeDelta request_time
= base::TimeTicks::Now() - request_start
;
477 UMA_HISTOGRAM_TIMES("DomainBoundCerts.GetCertTimeSync", request_time
);
478 RecordGetCertTime(request_time
);
483 // |server_bound_cert_store_| has no cert for this domain. See if an
484 // identical request is currently in flight.
485 ServerBoundCertServiceJob
* job
= NULL
;
486 std::map
<std::string
, ServerBoundCertServiceJob
*>::const_iterator j
;
487 j
= inflight_
.find(domain
);
488 if (j
!= inflight_
.end()) {
489 // An identical request is in flight already. We'll just attach our
492 // Check that the job is for an acceptable type of cert.
493 if (std::find(requested_types
.begin(), requested_types
.end(), job
->type())
494 == requested_types
.end()) {
495 DVLOG(1) << "Found inflight job of wrong type " << job
->type()
496 << " for " << domain
;
497 // If we get here, the server is asking for different types of certs in
498 // short succession. This probably means the server is broken or
499 // misconfigured. Since we only store one type of cert per domain, we
500 // are unable to handle this well. Just return an error and let the first
502 RecordGetDomainBoundCertResult(TYPE_MISMATCH
);
503 return ERR_ORIGIN_BOUND_CERT_GENERATION_TYPE_MISMATCH
;
507 // Need to make a new request.
508 ServerBoundCertServiceWorker
* worker
= new ServerBoundCertServiceWorker(
511 base::Bind(&ServerBoundCertService::HandleResult
,
512 weak_ptr_factory_
.GetWeakPtr()));
513 if (!worker
->Start(task_runner_
)) {
514 // TODO(rkn): Log to the NetLog.
515 LOG(ERROR
) << "ServerBoundCertServiceWorker couldn't be started.";
516 RecordGetDomainBoundCertResult(WORKER_FAILURE
);
517 return ERR_INSUFFICIENT_RESOURCES
; // Just a guess.
519 job
= new ServerBoundCertServiceJob(preferred_type
);
520 inflight_
[domain
] = job
;
523 ServerBoundCertServiceRequest
* request
= new ServerBoundCertServiceRequest(
525 base::Bind(&RequestHandle::OnRequestComplete
, base::Unretained(out_req
)),
526 type
, private_key
, cert
);
527 job
->AddRequest(request
);
528 out_req
->RequestStarted(this, request
, callback
);
529 return ERR_IO_PENDING
;
532 ServerBoundCertStore
* ServerBoundCertService::GetCertStore() {
533 return server_bound_cert_store_
.get();
536 void ServerBoundCertService::CancelRequest(ServerBoundCertServiceRequest
* req
) {
537 DCHECK(CalledOnValidThread());
541 // HandleResult is called by ServerBoundCertServiceWorker on the origin message
542 // loop. It deletes ServerBoundCertServiceJob.
543 void ServerBoundCertService::HandleResult(
544 const std::string
& server_identifier
,
546 scoped_ptr
<ServerBoundCertStore::ServerBoundCert
> cert
) {
547 DCHECK(CalledOnValidThread());
550 // TODO(mattm): we should just Pass() the cert object to
551 // SetServerBoundCert().
552 server_bound_cert_store_
->SetServerBoundCert(
553 cert
->server_identifier(), cert
->type(), cert
->creation_time(),
554 cert
->expiration_time(), cert
->private_key(), cert
->cert());
557 std::map
<std::string
, ServerBoundCertServiceJob
*>::iterator j
;
558 j
= inflight_
.find(server_identifier
);
559 if (j
== inflight_
.end()) {
563 ServerBoundCertServiceJob
* job
= j
->second
;
567 job
->HandleResult(error
, cert
->type(), cert
->private_key(), cert
->cert());
569 job
->HandleResult(error
, CLIENT_CERT_INVALID_TYPE
, "", "");
573 int ServerBoundCertService::cert_count() {
574 return server_bound_cert_store_
->GetCertCount();