Home | History | Annotate | Download | only in ssl
      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.
      4 
      5 #include "net/ssl/server_bound_cert_service.h"
      6 
      7 #include <algorithm>
      8 #include <limits>
      9 
     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/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 "net/base/net_errors.h"
     25 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
     26 #include "net/cert/x509_certificate.h"
     27 #include "net/cert/x509_util.h"
     28 #include "url/gurl.h"
     29 
     30 #if defined(USE_NSS)
     31 #include <private/pprthred.h>  // PR_DetachThread
     32 #endif
     33 
     34 namespace net {
     35 
     36 namespace {
     37 
     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;
     44 
     45 // Used by the GetDomainBoundCertResult histogram to record the final
     46 // outcome of each GetDomainBoundCert call.  Do not re-use values.
     47 enum GetCertResult {
     48   // Synchronously found and returned an existing domain bound cert.
     49   SYNC_SUCCESS = 0,
     50   // Retrieved or generated and returned a domain bound cert asynchronously.
     51   ASYNC_SUCCESS = 1,
     52   // Retrieval/generation request was cancelled before the cert generation
     53   // completed.
     54   ASYNC_CANCELLED = 2,
     55   // Cert generation failed.
     56   ASYNC_FAILURE_KEYGEN = 3,
     57   ASYNC_FAILURE_CREATE_CERT = 4,
     58   ASYNC_FAILURE_EXPORT_KEY = 5,
     59   ASYNC_FAILURE_UNKNOWN = 6,
     60   // GetDomainBoundCert was called with invalid arguments.
     61   INVALID_ARGUMENT = 7,
     62   // We don't support any of the cert types the server requested.
     63   UNSUPPORTED_TYPE = 8,
     64   // Server asked for a different type of certs while we were generating one.
     65   TYPE_MISMATCH = 9,
     66   // Couldn't start a worker to generate a cert.
     67   WORKER_FAILURE = 10,
     68   GET_CERT_RESULT_MAX
     69 };
     70 
     71 void RecordGetDomainBoundCertResult(GetCertResult result) {
     72   UMA_HISTOGRAM_ENUMERATION("DomainBoundCerts.GetDomainBoundCertResult", result,
     73                             GET_CERT_RESULT_MAX);
     74 }
     75 
     76 void RecordGetCertTime(base::TimeDelta request_time) {
     77   UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.GetCertTime",
     78                              request_time,
     79                              base::TimeDelta::FromMilliseconds(1),
     80                              base::TimeDelta::FromMinutes(5),
     81                              50);
     82 }
     83 
     84 // On success, returns a ServerBoundCert object and sets |*error| to OK.
     85 // Otherwise, returns NULL, and |*error| will be set to a net error code.
     86 // |serial_number| is passed in because base::RandInt cannot be called from an
     87 // unjoined thread, due to relying on a non-leaked LazyInstance
     88 scoped_ptr<ServerBoundCertStore::ServerBoundCert> GenerateCert(
     89     const std::string& server_identifier,
     90     uint32 serial_number,
     91     int* error) {
     92   scoped_ptr<ServerBoundCertStore::ServerBoundCert> result;
     93 
     94   base::TimeTicks start = base::TimeTicks::Now();
     95   base::Time not_valid_before = base::Time::Now();
     96   base::Time not_valid_after =
     97       not_valid_before + base::TimeDelta::FromDays(kValidityPeriodInDays);
     98   std::string der_cert;
     99   std::vector<uint8> private_key_info;
    100   scoped_ptr<crypto::ECPrivateKey> key(crypto::ECPrivateKey::Create());
    101   if (!key.get()) {
    102     DLOG(ERROR) << "Unable to create key pair for client";
    103     *error = ERR_KEY_GENERATION_FAILED;
    104     return result.Pass();
    105   }
    106   if (!x509_util::CreateDomainBoundCertEC(key.get(), server_identifier,
    107                                           serial_number, not_valid_before,
    108                                           not_valid_after, &der_cert)) {
    109     DLOG(ERROR) << "Unable to create x509 cert for client";
    110     *error = ERR_ORIGIN_BOUND_CERT_GENERATION_FAILED;
    111     return result.Pass();
    112   }
    113 
    114   if (!key->ExportEncryptedPrivateKey(ServerBoundCertService::kEPKIPassword,
    115                                       1, &private_key_info)) {
    116     DLOG(ERROR) << "Unable to export private key";
    117     *error = ERR_PRIVATE_KEY_EXPORT_FAILED;
    118     return result.Pass();
    119   }
    120 
    121   // TODO(rkn): Perhaps ExportPrivateKey should be changed to output a
    122   // std::string* to prevent this copying.
    123   std::string key_out(private_key_info.begin(), private_key_info.end());
    124 
    125   result.reset(new ServerBoundCertStore::ServerBoundCert(
    126       server_identifier,
    127       not_valid_before,
    128       not_valid_after,
    129       key_out,
    130       der_cert));
    131   UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.GenerateCertTime",
    132                              base::TimeTicks::Now() - start,
    133                              base::TimeDelta::FromMilliseconds(1),
    134                              base::TimeDelta::FromMinutes(5),
    135                              50);
    136   *error = OK;
    137   return result.Pass();
    138 }
    139 
    140 }  // namespace
    141 
    142 // Represents the output and result callback of a request.
    143 class ServerBoundCertServiceRequest {
    144  public:
    145   ServerBoundCertServiceRequest(base::TimeTicks request_start,
    146                                 const CompletionCallback& callback,
    147                                 std::string* private_key,
    148                                 std::string* cert)
    149       : request_start_(request_start),
    150         callback_(callback),
    151         private_key_(private_key),
    152         cert_(cert) {
    153   }
    154 
    155   // Ensures that the result callback will never be made.
    156   void Cancel() {
    157     RecordGetDomainBoundCertResult(ASYNC_CANCELLED);
    158     callback_.Reset();
    159     private_key_ = NULL;
    160     cert_ = NULL;
    161   }
    162 
    163   // Copies the contents of |private_key| and |cert| to the caller's output
    164   // arguments and calls the callback.
    165   void Post(int error,
    166             const std::string& private_key,
    167             const std::string& cert) {
    168     switch (error) {
    169       case OK: {
    170         base::TimeDelta request_time = base::TimeTicks::Now() - request_start_;
    171         UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.GetCertTimeAsync",
    172                                    request_time,
    173                                    base::TimeDelta::FromMilliseconds(1),
    174                                    base::TimeDelta::FromMinutes(5),
    175                                    50);
    176         RecordGetCertTime(request_time);
    177         RecordGetDomainBoundCertResult(ASYNC_SUCCESS);
    178         break;
    179       }
    180       case ERR_KEY_GENERATION_FAILED:
    181         RecordGetDomainBoundCertResult(ASYNC_FAILURE_KEYGEN);
    182         break;
    183       case ERR_ORIGIN_BOUND_CERT_GENERATION_FAILED:
    184         RecordGetDomainBoundCertResult(ASYNC_FAILURE_CREATE_CERT);
    185         break;
    186       case ERR_PRIVATE_KEY_EXPORT_FAILED:
    187         RecordGetDomainBoundCertResult(ASYNC_FAILURE_EXPORT_KEY);
    188         break;
    189       case ERR_INSUFFICIENT_RESOURCES:
    190         RecordGetDomainBoundCertResult(WORKER_FAILURE);
    191         break;
    192       default:
    193         RecordGetDomainBoundCertResult(ASYNC_FAILURE_UNKNOWN);
    194         break;
    195     }
    196     if (!callback_.is_null()) {
    197       *private_key_ = private_key;
    198       *cert_ = cert;
    199       callback_.Run(error);
    200     }
    201     delete this;
    202   }
    203 
    204   bool canceled() const { return callback_.is_null(); }
    205 
    206  private:
    207   base::TimeTicks request_start_;
    208   CompletionCallback callback_;
    209   std::string* private_key_;
    210   std::string* cert_;
    211 };
    212 
    213 // ServerBoundCertServiceWorker runs on a worker thread and takes care of the
    214 // blocking process of performing key generation. Will take care of deleting
    215 // itself once Start() is called.
    216 class ServerBoundCertServiceWorker {
    217  public:
    218   typedef base::Callback<void(
    219       const std::string&,
    220       int,
    221       scoped_ptr<ServerBoundCertStore::ServerBoundCert>)> WorkerDoneCallback;
    222 
    223   ServerBoundCertServiceWorker(
    224       const std::string& server_identifier,
    225       const WorkerDoneCallback& callback)
    226       : server_identifier_(server_identifier),
    227         serial_number_(base::RandInt(0, std::numeric_limits<int>::max())),
    228         origin_loop_(base::MessageLoopProxy::current()),
    229         callback_(callback) {
    230   }
    231 
    232   // Starts the worker on |task_runner|. If the worker fails to start, such as
    233   // if the task runner is shutting down, then it will take care of deleting
    234   // itself.
    235   bool Start(const scoped_refptr<base::TaskRunner>& task_runner) {
    236     DCHECK(origin_loop_->RunsTasksOnCurrentThread());
    237 
    238     return task_runner->PostTask(
    239         FROM_HERE,
    240         base::Bind(&ServerBoundCertServiceWorker::Run, base::Owned(this)));
    241   }
    242 
    243  private:
    244   void Run() {
    245     // Runs on a worker thread.
    246     int error = ERR_FAILED;
    247     scoped_ptr<ServerBoundCertStore::ServerBoundCert> cert =
    248         GenerateCert(server_identifier_, serial_number_, &error);
    249     DVLOG(1) << "GenerateCert " << server_identifier_ << " returned " << error;
    250 #if defined(USE_NSS)
    251     // Detach the thread from NSPR.
    252     // Calling NSS functions attaches the thread to NSPR, which stores
    253     // the NSPR thread ID in thread-specific data.
    254     // The threads in our thread pool terminate after we have called
    255     // PR_Cleanup. Unless we detach them from NSPR, net_unittests gets
    256     // segfaults on shutdown when the threads' thread-specific data
    257     // destructors run.
    258     PR_DetachThread();
    259 #endif
    260     origin_loop_->PostTask(FROM_HERE,
    261                            base::Bind(callback_, server_identifier_, error,
    262                                       base::Passed(&cert)));
    263   }
    264 
    265   const std::string server_identifier_;
    266   // Note that serial_number_ must be initialized on a non-worker thread
    267   // (see documentation for GenerateCert).
    268   uint32 serial_number_;
    269   scoped_refptr<base::SequencedTaskRunner> origin_loop_;
    270   WorkerDoneCallback callback_;
    271 
    272   DISALLOW_COPY_AND_ASSIGN(ServerBoundCertServiceWorker);
    273 };
    274 
    275 // A ServerBoundCertServiceJob is a one-to-one counterpart of an
    276 // ServerBoundCertServiceWorker. It lives only on the ServerBoundCertService's
    277 // origin message loop.
    278 class ServerBoundCertServiceJob {
    279  public:
    280   ServerBoundCertServiceJob() { }
    281 
    282   ~ServerBoundCertServiceJob() {
    283     if (!requests_.empty())
    284       DeleteAllCanceled();
    285   }
    286 
    287   void AddRequest(ServerBoundCertServiceRequest* request) {
    288     requests_.push_back(request);
    289   }
    290 
    291   void HandleResult(int error,
    292                     const std::string& private_key,
    293                     const std::string& cert) {
    294     PostAll(error, private_key, cert);
    295   }
    296 
    297  private:
    298   void PostAll(int error,
    299                const std::string& private_key,
    300                const std::string& cert) {
    301     std::vector<ServerBoundCertServiceRequest*> requests;
    302     requests_.swap(requests);
    303 
    304     for (std::vector<ServerBoundCertServiceRequest*>::iterator
    305          i = requests.begin(); i != requests.end(); i++) {
    306       (*i)->Post(error, private_key, cert);
    307       // Post() causes the ServerBoundCertServiceRequest to delete itself.
    308     }
    309   }
    310 
    311   void DeleteAllCanceled() {
    312     for (std::vector<ServerBoundCertServiceRequest*>::iterator
    313          i = requests_.begin(); i != requests_.end(); i++) {
    314       if ((*i)->canceled()) {
    315         delete *i;
    316       } else {
    317         LOG(DFATAL) << "ServerBoundCertServiceRequest leaked!";
    318       }
    319     }
    320   }
    321 
    322   std::vector<ServerBoundCertServiceRequest*> requests_;
    323 };
    324 
    325 // static
    326 const char ServerBoundCertService::kEPKIPassword[] = "";
    327 
    328 ServerBoundCertService::RequestHandle::RequestHandle()
    329     : service_(NULL),
    330       request_(NULL) {}
    331 
    332 ServerBoundCertService::RequestHandle::~RequestHandle() {
    333   Cancel();
    334 }
    335 
    336 void ServerBoundCertService::RequestHandle::Cancel() {
    337   if (request_) {
    338     service_->CancelRequest(request_);
    339     request_ = NULL;
    340     callback_.Reset();
    341   }
    342 }
    343 
    344 void ServerBoundCertService::RequestHandle::RequestStarted(
    345     ServerBoundCertService* service,
    346     ServerBoundCertServiceRequest* request,
    347     const CompletionCallback& callback) {
    348   DCHECK(request_ == NULL);
    349   service_ = service;
    350   request_ = request;
    351   callback_ = callback;
    352 }
    353 
    354 void ServerBoundCertService::RequestHandle::OnRequestComplete(int result) {
    355   request_ = NULL;
    356   // Running the callback might delete |this|, so we can't touch any of our
    357   // members afterwards. Reset callback_ first.
    358   base::ResetAndReturn(&callback_).Run(result);
    359 }
    360 
    361 ServerBoundCertService::ServerBoundCertService(
    362     ServerBoundCertStore* server_bound_cert_store,
    363     const scoped_refptr<base::TaskRunner>& task_runner)
    364     : server_bound_cert_store_(server_bound_cert_store),
    365       task_runner_(task_runner),
    366       weak_ptr_factory_(this),
    367       requests_(0),
    368       cert_store_hits_(0),
    369       inflight_joins_(0),
    370       workers_created_(0) {
    371   base::Time start = base::Time::Now();
    372   base::Time end = start + base::TimeDelta::FromDays(
    373       kValidityPeriodInDays + kSystemTimeValidityBufferInDays);
    374   is_system_time_valid_ = x509_util::IsSupportedValidityRange(start, end);
    375 }
    376 
    377 ServerBoundCertService::~ServerBoundCertService() {
    378   STLDeleteValues(&inflight_);
    379 }
    380 
    381 //static
    382 std::string ServerBoundCertService::GetDomainForHost(const std::string& host) {
    383   std::string domain =
    384       registry_controlled_domains::GetDomainAndRegistry(
    385           host, registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
    386   if (domain.empty())
    387     return host;
    388   return domain;
    389 }
    390 
    391 int ServerBoundCertService::GetDomainBoundCert(
    392     const std::string& host,
    393     std::string* private_key,
    394     std::string* cert,
    395     const CompletionCallback& callback,
    396     RequestHandle* out_req) {
    397   DVLOG(1) << __FUNCTION__ << " " << host;
    398   DCHECK(CalledOnValidThread());
    399   base::TimeTicks request_start = base::TimeTicks::Now();
    400 
    401   if (callback.is_null() || !private_key || !cert || host.empty()) {
    402     RecordGetDomainBoundCertResult(INVALID_ARGUMENT);
    403     return ERR_INVALID_ARGUMENT;
    404   }
    405 
    406   std::string domain = GetDomainForHost(host);
    407   if (domain.empty()) {
    408     RecordGetDomainBoundCertResult(INVALID_ARGUMENT);
    409     return ERR_INVALID_ARGUMENT;
    410   }
    411 
    412   requests_++;
    413 
    414   // See if an identical request is currently in flight.
    415   ServerBoundCertServiceJob* job = NULL;
    416   std::map<std::string, ServerBoundCertServiceJob*>::const_iterator j;
    417   j = inflight_.find(domain);
    418   if (j != inflight_.end()) {
    419     // An identical request is in flight already. We'll just attach our
    420     // callback.
    421     job = j->second;
    422     inflight_joins_++;
    423 
    424     ServerBoundCertServiceRequest* request = new ServerBoundCertServiceRequest(
    425         request_start,
    426         base::Bind(&RequestHandle::OnRequestComplete,
    427                    base::Unretained(out_req)),
    428         private_key, cert);
    429     job->AddRequest(request);
    430     out_req->RequestStarted(this, request, callback);
    431     return ERR_IO_PENDING;
    432   }
    433 
    434   // Check if a domain bound cert of an acceptable type already exists for this
    435   // domain. Note that |expiration_time| is ignored, and expired certs are
    436   // considered valid.
    437   base::Time expiration_time;
    438   int err = server_bound_cert_store_->GetServerBoundCert(
    439       domain,
    440       &expiration_time  /* ignored */,
    441       private_key,
    442       cert,
    443       base::Bind(&ServerBoundCertService::GotServerBoundCert,
    444                  weak_ptr_factory_.GetWeakPtr()));
    445 
    446   if (err == OK) {
    447     // Sync lookup found a valid cert.
    448     DVLOG(1) << "Cert store had valid cert for " << domain;
    449     cert_store_hits_++;
    450     RecordGetDomainBoundCertResult(SYNC_SUCCESS);
    451     base::TimeDelta request_time = base::TimeTicks::Now() - request_start;
    452     UMA_HISTOGRAM_TIMES("DomainBoundCerts.GetCertTimeSync", request_time);
    453     RecordGetCertTime(request_time);
    454     return OK;
    455   }
    456 
    457   if (err == ERR_FILE_NOT_FOUND) {
    458     // Sync lookup did not find a valid cert.  Start generating a new one.
    459     workers_created_++;
    460     ServerBoundCertServiceWorker* worker = new ServerBoundCertServiceWorker(
    461         domain,
    462         base::Bind(&ServerBoundCertService::GeneratedServerBoundCert,
    463                    weak_ptr_factory_.GetWeakPtr()));
    464     if (!worker->Start(task_runner_)) {
    465       // TODO(rkn): Log to the NetLog.
    466       LOG(ERROR) << "ServerBoundCertServiceWorker couldn't be started.";
    467       RecordGetDomainBoundCertResult(WORKER_FAILURE);
    468       return ERR_INSUFFICIENT_RESOURCES;
    469     }
    470   }
    471 
    472   if (err == ERR_IO_PENDING || err == ERR_FILE_NOT_FOUND) {
    473     // We are either waiting for async DB lookup, or waiting for cert
    474     // generation.  Create a job & request to track it.
    475     job = new ServerBoundCertServiceJob();
    476     inflight_[domain] = job;
    477 
    478     ServerBoundCertServiceRequest* request = new ServerBoundCertServiceRequest(
    479         request_start,
    480         base::Bind(&RequestHandle::OnRequestComplete,
    481                    base::Unretained(out_req)),
    482         private_key, cert);
    483     job->AddRequest(request);
    484     out_req->RequestStarted(this, request, callback);
    485     return ERR_IO_PENDING;
    486   }
    487 
    488   return err;
    489 }
    490 
    491 void ServerBoundCertService::GotServerBoundCert(
    492     int err,
    493     const std::string& server_identifier,
    494     base::Time expiration_time,
    495     const std::string& key,
    496     const std::string& cert) {
    497   DCHECK(CalledOnValidThread());
    498 
    499   std::map<std::string, ServerBoundCertServiceJob*>::iterator j;
    500   j = inflight_.find(server_identifier);
    501   if (j == inflight_.end()) {
    502     NOTREACHED();
    503     return;
    504   }
    505 
    506   if (err == OK) {
    507     // Async DB lookup found a valid cert.
    508     DVLOG(1) << "Cert store had valid cert for " << server_identifier;
    509     cert_store_hits_++;
    510     // ServerBoundCertServiceRequest::Post will do the histograms and stuff.
    511     HandleResult(OK, server_identifier, key, cert);
    512     return;
    513   }
    514   // Async lookup did not find a valid cert. Start generating a new one.
    515   workers_created_++;
    516   ServerBoundCertServiceWorker* worker = new ServerBoundCertServiceWorker(
    517       server_identifier,
    518       base::Bind(&ServerBoundCertService::GeneratedServerBoundCert,
    519                  weak_ptr_factory_.GetWeakPtr()));
    520   if (!worker->Start(task_runner_)) {
    521     // TODO(rkn): Log to the NetLog.
    522     LOG(ERROR) << "ServerBoundCertServiceWorker couldn't be started.";
    523     HandleResult(ERR_INSUFFICIENT_RESOURCES,
    524                  server_identifier,
    525                  std::string(),
    526                  std::string());
    527     return;
    528   }
    529 }
    530 
    531 ServerBoundCertStore* ServerBoundCertService::GetCertStore() {
    532   return server_bound_cert_store_.get();
    533 }
    534 
    535 void ServerBoundCertService::CancelRequest(ServerBoundCertServiceRequest* req) {
    536   DCHECK(CalledOnValidThread());
    537   req->Cancel();
    538 }
    539 
    540 void ServerBoundCertService::GeneratedServerBoundCert(
    541     const std::string& server_identifier,
    542     int error,
    543     scoped_ptr<ServerBoundCertStore::ServerBoundCert> cert) {
    544   DCHECK(CalledOnValidThread());
    545 
    546   if (error == OK) {
    547     // TODO(mattm): we should just Pass() the cert object to
    548     // SetServerBoundCert().
    549     server_bound_cert_store_->SetServerBoundCert(
    550         cert->server_identifier(),
    551         cert->creation_time(),
    552         cert->expiration_time(),
    553         cert->private_key(),
    554         cert->cert());
    555 
    556     HandleResult(error, server_identifier, cert->private_key(), cert->cert());
    557   } else {
    558     HandleResult(error, server_identifier, std::string(), std::string());
    559   }
    560 }
    561 
    562 void ServerBoundCertService::HandleResult(
    563     int error,
    564     const std::string& server_identifier,
    565     const std::string& private_key,
    566     const std::string& cert) {
    567   DCHECK(CalledOnValidThread());
    568 
    569   std::map<std::string, ServerBoundCertServiceJob*>::iterator j;
    570   j = inflight_.find(server_identifier);
    571   if (j == inflight_.end()) {
    572     NOTREACHED();
    573     return;
    574   }
    575   ServerBoundCertServiceJob* job = j->second;
    576   inflight_.erase(j);
    577 
    578   job->HandleResult(error, private_key, cert);
    579   delete job;
    580 }
    581 
    582 int ServerBoundCertService::cert_count() {
    583   return server_bound_cert_store_->GetCertCount();
    584 }
    585 
    586 }  // namespace net
    587