Add simple cache backend experiment hidden behind a command line option.
[chromium-blink-merge.git] / net / ssl / server_bound_cert_service.cc
blobece1a2c8a461ffb3d9b86d3711b6a3bbdf536797
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"
7 #include <algorithm>
8 #include <limits>
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"
30 #if defined(USE_NSS)
31 #include <private/pprthred.h> // PR_DetachThread
32 #endif
34 namespace net {
36 namespace {
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
42 // while.
43 const int kSystemTimeValidityBufferInDays = 90;
45 bool IsSupportedCertType(uint8 type) {
46 switch(type) {
47 case CLIENT_CERT_ECDSA_SIGN:
48 return true;
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.
51 default:
52 return false;
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;
61 return false;
62 } else if (!IsSupportedCertType(type)) {
63 DVLOG(1) << "Cert store had cert of wrong type " << type << " for "
64 << domain;
65 return false;
67 return true;
70 // Used by the GetDomainBoundCertResult histogram to record the final
71 // outcome of each GetDomainBoundCert call. Do not re-use values.
72 enum GetCertResult {
73 // Synchronously found and returned an existing domain bound cert.
74 SYNC_SUCCESS = 0,
75 // Retrieved or generated and returned a domain bound cert asynchronously.
76 ASYNC_SUCCESS = 1,
77 // Retrieval/generation request was cancelled before the cert generation
78 // completed.
79 ASYNC_CANCELLED = 2,
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.
86 INVALID_ARGUMENT = 7,
87 // We don't support any of the cert types the server requested.
88 UNSUPPORTED_TYPE = 8,
89 // Server asked for a different type of certs while we were generating one.
90 TYPE_MISMATCH = 9,
91 // Couldn't start a worker to generate a cert.
92 WORKER_FAILURE = 10,
93 GET_CERT_RESULT_MAX
96 void RecordGetDomainBoundCertResult(GetCertResult result) {
97 UMA_HISTOGRAM_ENUMERATION("DomainBoundCerts.GetDomainBoundCertResult", result,
98 GET_CERT_RESULT_MAX);
101 void RecordGetCertTime(base::TimeDelta request_time) {
102 UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.GetCertTime",
103 request_time,
104 base::TimeDelta::FromMilliseconds(1),
105 base::TimeDelta::FromMinutes(5),
106 50);
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,
117 int* error) {
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;
126 switch (type) {
127 case CLIENT_CERT_ECDSA_SIGN: {
128 scoped_ptr<crypto::ECPrivateKey> key(crypto::ECPrivateKey::Create());
129 if (!key.get()) {
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();
148 break;
150 default:
151 NOTREACHED();
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,
162 der_cert));
163 UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.GenerateCertTime",
164 base::TimeTicks::Now() - start,
165 base::TimeDelta::FromMilliseconds(1),
166 base::TimeDelta::FromMinutes(5),
167 50);
168 *error = OK;
169 return result.Pass();
172 } // namespace
174 // Represents the output and result callback of a request.
175 class ServerBoundCertServiceRequest {
176 public:
177 ServerBoundCertServiceRequest(base::TimeTicks request_start,
178 const CompletionCallback& callback,
179 SSLClientCertType* type,
180 std::string* private_key,
181 std::string* cert)
182 : request_start_(request_start),
183 callback_(callback),
184 type_(type),
185 private_key_(private_key),
186 cert_(cert) {
189 // Ensures that the result callback will never be made.
190 void Cancel() {
191 RecordGetDomainBoundCertResult(ASYNC_CANCELLED);
192 callback_.Reset();
193 type_ = NULL;
194 private_key_ = NULL;
195 cert_ = NULL;
198 // Copies the contents of |private_key| and |cert| to the caller's output
199 // arguments and calls the callback.
200 void Post(int error,
201 SSLClientCertType type,
202 const std::string& private_key,
203 const std::string& cert) {
204 switch (error) {
205 case OK: {
206 base::TimeDelta request_time = base::TimeTicks::Now() - request_start_;
207 UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.GetCertTimeAsync",
208 request_time,
209 base::TimeDelta::FromMilliseconds(1),
210 base::TimeDelta::FromMinutes(5),
211 50);
212 RecordGetCertTime(request_time);
213 RecordGetDomainBoundCertResult(ASYNC_SUCCESS);
214 break;
216 case ERR_KEY_GENERATION_FAILED:
217 RecordGetDomainBoundCertResult(ASYNC_FAILURE_KEYGEN);
218 break;
219 case ERR_ORIGIN_BOUND_CERT_GENERATION_FAILED:
220 RecordGetDomainBoundCertResult(ASYNC_FAILURE_CREATE_CERT);
221 break;
222 case ERR_PRIVATE_KEY_EXPORT_FAILED:
223 RecordGetDomainBoundCertResult(ASYNC_FAILURE_EXPORT_KEY);
224 break;
225 case ERR_INSUFFICIENT_RESOURCES:
226 RecordGetDomainBoundCertResult(WORKER_FAILURE);
227 break;
228 default:
229 RecordGetDomainBoundCertResult(ASYNC_FAILURE_UNKNOWN);
230 break;
232 if (!callback_.is_null()) {
233 *type_ = type;
234 *private_key_ = private_key;
235 *cert_ = cert;
236 callback_.Run(error);
238 delete this;
241 bool canceled() const { return callback_.is_null(); }
243 private:
244 base::TimeTicks request_start_;
245 CompletionCallback callback_;
246 SSLClientCertType* type_;
247 std::string* private_key_;
248 std::string* cert_;
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 {
255 public:
256 typedef base::Callback<void(
257 const std::string&,
258 int,
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),
266 type_(type),
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(
276 FROM_HERE,
277 base::Bind(&ServerBoundCertServiceWorker::Run, base::Owned(this)));
280 private:
281 void Run() {
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;
288 #if defined(USE_NSS)
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
295 // destructors run.
296 PR_DetachThread();
297 #endif
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 {
318 public:
319 ServerBoundCertServiceJob(SSLClientCertType type)
320 : type_(type) {
323 ~ServerBoundCertServiceJob() {
324 if (!requests_.empty())
325 DeleteAllCanceled();
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);
341 private:
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()) {
360 delete *i;
361 } else {
362 LOG(DFATAL) << "ServerBoundCertServiceRequest leaked!";
367 std::vector<ServerBoundCertServiceRequest*> requests_;
368 SSLClientCertType type_;
371 // static
372 const char ServerBoundCertService::kEPKIPassword[] = "";
374 ServerBoundCertService::RequestHandle::RequestHandle()
375 : service_(NULL),
376 request_(NULL) {}
378 ServerBoundCertService::RequestHandle::~RequestHandle() {
379 Cancel();
382 void ServerBoundCertService::RequestHandle::Cancel() {
383 if (request_) {
384 service_->CancelRequest(request_);
385 request_ = NULL;
386 callback_.Reset();
390 void ServerBoundCertService::RequestHandle::RequestStarted(
391 ServerBoundCertService* service,
392 ServerBoundCertServiceRequest* request,
393 const CompletionCallback& callback) {
394 DCHECK(request_ == NULL);
395 service_ = service;
396 request_ = request;
397 callback_ = callback;
400 void ServerBoundCertService::RequestHandle::OnRequestComplete(int result) {
401 request_ = NULL;
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)),
413 requests_(0),
414 cert_store_hits_(0),
415 inflight_joins_(0) {
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_);
426 //static
427 std::string ServerBoundCertService::GetDomainForHost(const std::string& host) {
428 std::string domain =
429 RegistryControlledDomainService::GetDomainAndRegistry(host);
430 if (domain.empty())
431 return host;
432 return domain;
435 int ServerBoundCertService::GetDomainBoundCert(
436 const std::string& origin,
437 const std::vector<uint8>& requested_types,
438 SSLClientCertType* type,
439 std::string* private_key,
440 std::string* cert,
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]);
465 break;
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;
474 requests_++;
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
482 // callback.
483 job = j->second;
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
493 // job finish.
494 RecordGetDomainBoundCertResult(TYPE_MISMATCH);
495 return ERR_ORIGIN_BOUND_CERT_GENERATION_TYPE_MISMATCH;
497 inflight_joins_++;
499 ServerBoundCertServiceRequest* request = new ServerBoundCertServiceRequest(
500 request_start,
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(
513 domain,
514 type,
515 &expiration_time,
516 private_key,
517 cert,
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;
525 cert_store_hits_++;
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);
530 return OK;
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(
537 domain,
538 preferred_type,
539 base::Bind(&ServerBoundCertService::GeneratedServerBoundCert,
540 weak_ptr_factory_.GetWeakPtr()));
541 if (!worker->Start(task_runner_)) {
542 delete worker;
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(
556 request_start,
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()) {
575 NOTREACHED();
576 return;
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;
585 cert_store_hits_++;
586 // ServerBoundCertServiceRequest::Post will do the histograms and stuff.
587 HandleResult(OK, server_identifier, type, key, cert);
588 return;
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(
595 server_identifier,
596 job->type(),
597 base::Bind(&ServerBoundCertService::GeneratedServerBoundCert,
598 weak_ptr_factory_.GetWeakPtr()));
599 if (!worker->Start(task_runner_)) {
600 delete worker;
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, "", "");
605 return;
609 ServerBoundCertStore* ServerBoundCertService::GetCertStore() {
610 return server_bound_cert_store_.get();
613 void ServerBoundCertService::CancelRequest(ServerBoundCertServiceRequest* req) {
614 DCHECK(CalledOnValidThread());
615 req->Cancel();
618 void ServerBoundCertService::GeneratedServerBoundCert(
619 const std::string& server_identifier,
620 int error,
621 scoped_ptr<ServerBoundCertStore::ServerBoundCert> cert) {
622 DCHECK(CalledOnValidThread());
624 if (error == OK) {
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(),
632 cert->cert());
633 } else {
634 HandleResult(error, server_identifier, CLIENT_CERT_INVALID_TYPE, "", "");
638 void ServerBoundCertService::HandleResult(
639 int error,
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()) {
649 NOTREACHED();
650 return;
652 ServerBoundCertServiceJob* job = j->second;
653 inflight_.erase(j);
655 job->HandleResult(error, type, private_key, cert);
656 delete job;
659 int ServerBoundCertService::cert_count() {
660 return server_bound_cert_store_->GetCertCount();
663 } // namespace net