Move pending tile priorities to active on tree activation
[chromium-blink-merge.git] / net / base / server_bound_cert_service.cc
blob3194a6623ad2491e3b9fb1a4f672ac3aebe2e326
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"
7 #include <algorithm>
8 #include <limits>
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"
29 #if defined(USE_NSS)
30 #include <private/pprthred.h> // PR_DetachThread
31 #endif
33 namespace net {
35 namespace {
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
41 // while.
42 const int kSystemTimeValidityBufferInDays = 90;
44 bool IsSupportedCertType(uint8 type) {
45 switch(type) {
46 case CLIENT_CERT_ECDSA_SIGN:
47 return true;
48 default:
49 return false;
53 // Used by the GetDomainBoundCertResult histogram to record the final
54 // outcome of each GetDomainBoundCert call. Do not re-use values.
55 enum GetCertResult {
56 // Synchronously found and returned an existing domain bound cert.
57 SYNC_SUCCESS = 0,
58 // Generated and returned a domain bound cert asynchronously.
59 ASYNC_SUCCESS = 1,
60 // Generation request was cancelled before the cert generation completed.
61 ASYNC_CANCELLED = 2,
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.
68 INVALID_ARGUMENT = 7,
69 // We don't support any of the cert types the server requested.
70 UNSUPPORTED_TYPE = 8,
71 // Server asked for a different type of certs while we were generating one.
72 TYPE_MISMATCH = 9,
73 // Couldn't start a worker to generate a cert.
74 WORKER_FAILURE = 10,
75 GET_CERT_RESULT_MAX
78 void RecordGetDomainBoundCertResult(GetCertResult result) {
79 UMA_HISTOGRAM_ENUMERATION("DomainBoundCerts.GetDomainBoundCertResult", result,
80 GET_CERT_RESULT_MAX);
83 void RecordGetCertTime(base::TimeDelta request_time) {
84 UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.GetCertTime",
85 request_time,
86 base::TimeDelta::FromMilliseconds(1),
87 base::TimeDelta::FromMinutes(5),
88 50);
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,
98 uint32 serial_number,
99 int* error) {
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;
108 switch (type) {
109 case CLIENT_CERT_ECDSA_SIGN: {
110 scoped_ptr<crypto::ECPrivateKey> key(crypto::ECPrivateKey::Create());
111 if (!key.get()) {
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();
130 break;
132 default:
133 NOTREACHED();
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,
144 der_cert));
145 UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.GenerateCertTime",
146 base::TimeTicks::Now() - start,
147 base::TimeDelta::FromMilliseconds(1),
148 base::TimeDelta::FromMinutes(5),
149 50);
150 *error = OK;
151 return result.Pass();
154 } // namespace
156 // Represents the output and result callback of a request.
157 class ServerBoundCertServiceRequest {
158 public:
159 ServerBoundCertServiceRequest(base::TimeTicks request_start,
160 const CompletionCallback& callback,
161 SSLClientCertType* type,
162 std::string* private_key,
163 std::string* cert)
164 : request_start_(request_start),
165 callback_(callback),
166 type_(type),
167 private_key_(private_key),
168 cert_(cert) {
171 // Ensures that the result callback will never be made.
172 void Cancel() {
173 RecordGetDomainBoundCertResult(ASYNC_CANCELLED);
174 callback_.Reset();
175 type_ = NULL;
176 private_key_ = NULL;
177 cert_ = NULL;
180 // Copies the contents of |private_key| and |cert| to the caller's output
181 // arguments and calls the callback.
182 void Post(int error,
183 SSLClientCertType type,
184 const std::string& private_key,
185 const std::string& cert) {
186 switch (error) {
187 case OK: {
188 base::TimeDelta request_time = base::TimeTicks::Now() - request_start_;
189 UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.GetCertTimeAsync",
190 request_time,
191 base::TimeDelta::FromMilliseconds(1),
192 base::TimeDelta::FromMinutes(5),
193 50);
194 RecordGetCertTime(request_time);
195 RecordGetDomainBoundCertResult(ASYNC_SUCCESS);
196 break;
198 case ERR_KEY_GENERATION_FAILED:
199 RecordGetDomainBoundCertResult(ASYNC_FAILURE_KEYGEN);
200 break;
201 case ERR_ORIGIN_BOUND_CERT_GENERATION_FAILED:
202 RecordGetDomainBoundCertResult(ASYNC_FAILURE_CREATE_CERT);
203 break;
204 case ERR_PRIVATE_KEY_EXPORT_FAILED:
205 RecordGetDomainBoundCertResult(ASYNC_FAILURE_EXPORT_KEY);
206 break;
207 default:
208 RecordGetDomainBoundCertResult(ASYNC_FAILURE_UNKNOWN);
209 break;
211 if (!callback_.is_null()) {
212 *type_ = type;
213 *private_key_ = private_key;
214 *cert_ = cert;
215 callback_.Run(error);
217 delete this;
220 bool canceled() const { return callback_.is_null(); }
222 private:
223 base::TimeTicks request_start_;
224 CompletionCallback callback_;
225 SSLClientCertType* type_;
226 std::string* private_key_;
227 std::string* cert_;
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 {
234 public:
235 typedef base::Callback<void(
236 const std::string&,
237 int,
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),
245 type_(type),
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(
255 FROM_HERE,
256 base::Bind(&ServerBoundCertServiceWorker::Run, base::Owned(this)));
259 private:
260 void Run() {
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;
267 #if defined(USE_NSS)
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
274 // destructors run.
275 PR_DetachThread();
276 #endif
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 {
297 public:
298 ServerBoundCertServiceJob(SSLClientCertType type) : type_(type) {
301 ~ServerBoundCertServiceJob() {
302 if (!requests_.empty())
303 DeleteAllCanceled();
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);
319 private:
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()) {
338 delete *i;
339 } else {
340 LOG(DFATAL) << "ServerBoundCertServiceRequest leaked!";
345 std::vector<ServerBoundCertServiceRequest*> requests_;
346 SSLClientCertType type_;
349 // static
350 const char ServerBoundCertService::kEPKIPassword[] = "";
352 ServerBoundCertService::RequestHandle::RequestHandle()
353 : service_(NULL),
354 request_(NULL) {}
356 ServerBoundCertService::RequestHandle::~RequestHandle() {
357 Cancel();
360 void ServerBoundCertService::RequestHandle::Cancel() {
361 if (request_) {
362 service_->CancelRequest(request_);
363 request_ = NULL;
364 callback_.Reset();
368 void ServerBoundCertService::RequestHandle::RequestStarted(
369 ServerBoundCertService* service,
370 ServerBoundCertServiceRequest* request,
371 const CompletionCallback& callback) {
372 DCHECK(request_ == NULL);
373 service_ = service;
374 request_ = request;
375 callback_ = callback;
378 void ServerBoundCertService::RequestHandle::OnRequestComplete(int result) {
379 request_ = NULL;
380 callback_.Run(result);
381 callback_.Reset();
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)),
390 requests_(0),
391 cert_store_hits_(0),
392 inflight_joins_(0) {
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_);
403 //static
404 std::string ServerBoundCertService::GetDomainForHost(const std::string& host) {
405 std::string domain =
406 RegistryControlledDomainService::GetDomainAndRegistry(host);
407 if (domain.empty())
408 return host;
409 return domain;
412 int ServerBoundCertService::GetDomainBoundCert(
413 const std::string& origin,
414 const std::vector<uint8>& requested_types,
415 SSLClientCertType* type,
416 std::string* private_key,
417 std::string* cert,
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]);
442 break;
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;
451 requests_++;
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,
459 type,
460 &creation_time,
461 &expiration_time,
462 private_key,
463 cert)) {
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 "
470 << domain;
471 } else {
472 DVLOG(1) << "Cert store had valid cert for " << domain
473 << " of type " << *type;
474 cert_store_hits_++;
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);
479 return OK;
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
490 // callback.
491 job = j->second;
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
501 // job finish.
502 RecordGetDomainBoundCertResult(TYPE_MISMATCH);
503 return ERR_ORIGIN_BOUND_CERT_GENERATION_TYPE_MISMATCH;
505 inflight_joins_++;
506 } else {
507 // Need to make a new request.
508 ServerBoundCertServiceWorker* worker = new ServerBoundCertServiceWorker(
509 domain,
510 preferred_type,
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(
524 request_start,
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());
538 req->Cancel();
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,
545 int error,
546 scoped_ptr<ServerBoundCertStore::ServerBoundCert> cert) {
547 DCHECK(CalledOnValidThread());
549 if (error == OK) {
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()) {
560 NOTREACHED();
561 return;
563 ServerBoundCertServiceJob* job = j->second;
564 inflight_.erase(j);
566 if (cert)
567 job->HandleResult(error, cert->type(), cert->private_key(), cert->cert());
568 else
569 job->HandleResult(error, CLIENT_CERT_INVALID_TYPE, "", "");
570 delete job;
573 int ServerBoundCertService::cert_count() {
574 return server_bound_cert_store_->GetCertCount();
577 } // namespace net