Index: net/base/origin_bound_cert_service.cc |
=================================================================== |
--- net/base/origin_bound_cert_service.cc (revision 94628) |
+++ net/base/origin_bound_cert_service.cc (working copy) |
@@ -6,14 +6,23 @@ |
#include <limits> |
+#include "base/compiler_specific.h" |
#include "base/logging.h" |
#include "base/memory/ref_counted.h" |
#include "base/memory/scoped_ptr.h" |
+#include "base/message_loop.h" |
#include "base/rand_util.h" |
+#include "base/stl_util.h" |
+#include "base/threading/worker_pool.h" |
#include "crypto/rsa_private_key.h" |
+#include "net/base/net_errors.h" |
#include "net/base/origin_bound_cert_store.h" |
#include "net/base/x509_certificate.h" |
+#if defined(USE_NSS) |
+#include <private/pprthred.h> // PR_DetachThread |
+#endif |
+ |
namespace net { |
namespace { |
@@ -23,43 +32,310 @@ |
} // namespace |
+// Represents the output and result callback of a request. |
+class OriginBoundCertServiceRequest { |
+ public: |
+ OriginBoundCertServiceRequest(CompletionCallback* callback, |
+ std::string* private_key, |
+ std::string* cert) |
+ : callback_(callback), |
+ private_key_(private_key), |
+ cert_(cert) { |
+ } |
+ |
+ // Ensures that the result callback will never be made. |
+ void Cancel() { |
+ callback_ = NULL; |
+ private_key_ = NULL; |
+ cert_ = NULL; |
+ } |
+ |
+ // Copies the contents of |private_key| and |cert| to the caller's fields |
+ // and calls the callback. |
+ void Post(int error, |
+ const std::string& private_key, |
+ const std::string& cert) { |
+ if (callback_) { |
+ *private_key_ = private_key; |
+ *cert_ = cert; |
+ callback_->Run(error); |
+ } |
+ delete this; |
+ } |
+ |
+ bool canceled() const { return !callback_; } |
+ |
+ private: |
+ CompletionCallback* callback_; |
+ std::string* private_key_; |
+ std::string* cert_; |
+}; |
+ |
+// OriginBoundCertServiceWorker runs on a worker thread and takes care of the |
+// blocking process of performing key generation. Deletes itself eventually |
+// if Start() succeeds. |
+class OriginBoundCertServiceWorker { |
+ public: |
+ OriginBoundCertServiceWorker( |
+ const std::string& origin, |
+ OriginBoundCertService* origin_bound_cert_service) |
+ : origin_(origin), |
+ serial_number_(base::RandInt(0, std::numeric_limits<int>::max())), |
+ origin_loop_(MessageLoop::current()), |
+ origin_bound_cert_service_(origin_bound_cert_service), |
+ canceled_(false), |
+ error_(ERR_FAILED) { |
+ } |
+ |
+ bool Start() { |
+ DCHECK_EQ(MessageLoop::current(), origin_loop_); |
+ |
+ return base::WorkerPool::PostTask( |
+ FROM_HERE, |
+ NewRunnableMethod(this, &OriginBoundCertServiceWorker::Run), |
+ true /* task is slow */); |
+ } |
+ |
+ // Cancel is called from the origin loop when the OriginBoundCertService is |
+ // getting deleted. |
+ void Cancel() { |
+ DCHECK_EQ(MessageLoop::current(), origin_loop_); |
+ base::AutoLock locked(lock_); |
+ canceled_ = true; |
+ } |
+ |
+ private: |
+ void Run() { |
+ // Runs on a worker thread. |
+ error_ = OriginBoundCertService::GenerateCert(origin_, |
+ serial_number_, |
+ &private_key_, |
+ &cert_); |
+#if defined(USE_NSS) |
+ // Detach the thread from NSPR. |
+ // Calling NSS functions attaches the thread to NSPR, which stores |
+ // the NSPR thread ID in thread-specific data. |
+ // The threads in our thread pool terminate after we have called |
+ // PR_Cleanup. Unless we detach them from NSPR, net_unittests gets |
+ // segfaults on shutdown when the threads' thread-specific data |
+ // destructors run. |
+ PR_DetachThread(); |
+#endif |
+ Finish(); |
+ } |
+ |
+ // DoReply runs on the origin thread. |
+ void DoReply() { |
+ DCHECK_EQ(MessageLoop::current(), origin_loop_); |
+ { |
+ // We lock here because the worker thread could still be in Finished, |
+ // after the PostTask, but before unlocking |lock_|. If we do not lock in |
+ // this case, we will end up deleting a locked Lock, which can lead to |
+ // memory leaks or worse errors. |
+ base::AutoLock locked(lock_); |
+ if (!canceled_) { |
+ origin_bound_cert_service_->HandleResult( |
+ origin_, error_, private_key_, cert_); |
+ } |
+ } |
+ delete this; |
+ } |
+ |
+ void Finish() { |
+ // Runs on the worker thread. |
+ // We assume that the origin loop outlives the OriginBoundCertService. If |
+ // the OriginBoundCertService is deleted, it will call Cancel on us. If it |
+ // does so before the Acquire, we'll delete ourselves and return. If it's |
+ // trying to do so concurrently, then it'll block on the lock and we'll |
+ // call PostTask while the OriginBoundCertService (and therefore the |
+ // MessageLoop) is still alive. If it does so after this function, we |
+ // assume that the MessageLoop will process pending tasks. In which case |
+ // we'll notice the |canceled_| flag in DoReply. |
+ |
+ bool canceled; |
+ { |
+ base::AutoLock locked(lock_); |
+ canceled = canceled_; |
+ if (!canceled) { |
+ origin_loop_->PostTask( |
+ FROM_HERE, |
+ NewRunnableMethod(this, &OriginBoundCertServiceWorker::DoReply)); |
+ } |
+ } |
+ if (canceled) |
+ delete this; |
+ } |
+ |
+ const std::string origin_; |
+ uint32 serial_number_; |
+ MessageLoop* const origin_loop_; |
+ OriginBoundCertService* const origin_bound_cert_service_; |
+ |
+ // lock_ protects canceled_. |
+ base::Lock lock_; |
+ |
+ // If canceled_ is true, |
+ // * origin_loop_ cannot be accessed by the worker thread, |
+ // * origin_bound_cert_service_ cannot be accessed by any thread. |
+ bool canceled_; |
+ |
+ int error_; |
+ std::string private_key_; |
+ std::string cert_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(OriginBoundCertServiceWorker); |
+}; |
+ |
+// An OriginBoundCertServiceJob is a one-to-one counterpart of an |
+// OriginBoundCertServiceWorker. It lives only on the OriginBoundCertService's |
+// origin message loop. |
+class OriginBoundCertServiceJob { |
+ public: |
+ explicit OriginBoundCertServiceJob(OriginBoundCertServiceWorker* worker) |
+ : worker_(worker) { |
+ } |
+ |
+ ~OriginBoundCertServiceJob() { |
+ if (worker_) { |
+ worker_->Cancel(); |
+ DeleteAllCanceled(); |
+ } |
+ } |
+ |
+ void AddRequest(OriginBoundCertServiceRequest* request) { |
+ requests_.push_back(request); |
+ } |
+ |
+ void HandleResult(int error, |
+ const std::string& private_key, |
+ const std::string& cert) { |
+ worker_ = NULL; |
+ PostAll(error, private_key, cert); |
+ } |
+ |
+ private: |
+ void PostAll(int error, |
+ const std::string& private_key, |
+ const std::string& cert) { |
+ std::vector<OriginBoundCertServiceRequest*> requests; |
+ requests_.swap(requests); |
+ |
+ for (std::vector<OriginBoundCertServiceRequest*>::iterator |
+ i = requests.begin(); i != requests.end(); i++) { |
+ (*i)->Post(error, private_key, cert); |
+ // Post() causes the OriginBoundCertServiceRequest to delete itself. |
+ } |
+ } |
+ |
+ void DeleteAllCanceled() { |
+ for (std::vector<OriginBoundCertServiceRequest*>::iterator |
+ i = requests_.begin(); i != requests_.end(); i++) { |
+ if ((*i)->canceled()) { |
+ delete *i; |
+ } else { |
+ LOG(DFATAL) << "OriginBoundCertServiceRequest leaked!"; |
+ } |
+ } |
+ } |
+ |
+ std::vector<OriginBoundCertServiceRequest*> requests_; |
+ OriginBoundCertServiceWorker* worker_; |
+}; |
+ |
OriginBoundCertService::OriginBoundCertService( |
OriginBoundCertStore* origin_bound_cert_store) |
- : origin_bound_cert_store_(origin_bound_cert_store) {} |
+ : origin_bound_cert_store_(origin_bound_cert_store), |
+ requests_(0), |
+ cache_hits_(0), |
+ inflight_joins_(0) {} |
-OriginBoundCertService::~OriginBoundCertService() {} |
+OriginBoundCertService::~OriginBoundCertService() { |
+ STLDeleteValues(&inflight_); |
+} |
-bool OriginBoundCertService::GetOriginBoundCert(const std::string& origin, |
- std::string* private_key_result, |
- std::string* cert_result) { |
- // Check if origin bound cert already exists for this origin. |
+int OriginBoundCertService::GetOriginBoundCert(const std::string& origin, |
+ std::string* private_key, |
+ std::string* cert, |
+ CompletionCallback* callback, |
+ RequestHandle* out_req) { |
+ |
+ DCHECK(CalledOnValidThread()); |
+ |
+ if (!callback || !private_key || !cert || origin.empty()) { |
+ *out_req = NULL; |
+ return ERR_INVALID_ARGUMENT; |
+ } |
+ |
+ requests_++; |
+ |
+ // Check if an origin bound cert already exists for this origin. |
if (origin_bound_cert_store_->GetOriginBoundCert(origin, |
- private_key_result, |
- cert_result)) |
- return true; |
+ private_key, |
+ cert)) { |
+ cache_hits_++; |
+ *out_req = NULL; |
+ return OK; |
+ } |
- // No origin bound cert exists, we have to create one. |
+ // No cache hit. See if an identical request is currently in flight. |
wtc
2011/08/09 00:43:34
"No cache hit" should be updated because we aren't
|
+ OriginBoundCertServiceJob* job; |
+ std::map<std::string, OriginBoundCertServiceJob*>::const_iterator j; |
+ j = inflight_.find(origin); |
+ if (j != inflight_.end()) { |
+ // An identical request is in flight already. We'll just attach our |
+ // callback. |
+ inflight_joins_++; |
+ job = j->second; |
+ } else { |
+ // Need to make a new request. |
+ OriginBoundCertServiceWorker* worker = |
+ new OriginBoundCertServiceWorker(origin, this); |
+ job = new OriginBoundCertServiceJob(worker); |
+ if (!worker->Start()) { |
+ delete job; |
+ delete worker; |
+ *out_req = NULL; |
+ // TODO(rkn): Log to the NetLog. |
+ LOG(ERROR) << "OriginBoundCertServiceWorker couldn't be started."; |
+ return ERR_INSUFFICIENT_RESOURCES; // Just a guess. |
+ } |
+ inflight_[origin] = job; |
+ } |
+ |
+ OriginBoundCertServiceRequest* request = |
+ new OriginBoundCertServiceRequest(callback, private_key, cert); |
+ job->AddRequest(request); |
+ *out_req = request; |
+ return ERR_IO_PENDING; |
+} |
+ |
+// static |
+int OriginBoundCertService::GenerateCert(const std::string& origin, |
+ uint32 serial_number, |
+ std::string* private_key, |
+ std::string* cert) { |
std::string subject = "CN=OBC"; |
scoped_ptr<crypto::RSAPrivateKey> key( |
crypto::RSAPrivateKey::Create(kKeySizeInBits)); |
if (!key.get()) { |
LOG(WARNING) << "Unable to create key pair for client"; |
- return false; |
+ return ERR_KEY_PAIR_GENERATION_FAILED; |
} |
+ |
scoped_refptr<X509Certificate> x509_cert = X509Certificate::CreateSelfSigned( |
key.get(), |
subject, |
- base::RandInt(0, std::numeric_limits<int>::max()), |
+ serial_number, |
base::TimeDelta::FromDays(kValidityPeriodInDays)); |
if (!x509_cert) { |
LOG(WARNING) << "Unable to create x509 cert for client"; |
- return false; |
+ return ERR_ORIGIN_BOUND_CERT_GENERATION_FAILED; |
} |
std::vector<uint8> private_key_info; |
if (!key->ExportPrivateKey(&private_key_info)) { |
LOG(WARNING) << "Unable to export private key"; |
- return false; |
+ return ERR_PRIVATE_KEY_EXPORT_FAILED; |
} |
// TODO(rkn): Perhaps ExportPrivateKey should be changed to output a |
// std::string* to prevent this copying. |
@@ -67,24 +343,49 @@ |
std::string der_cert; |
if (!x509_cert->GetDEREncoded(&der_cert)) { |
- LOG(WARNING) << "Unable to get DER-enconded cert"; |
- return false; |
+ LOG(WARNING) << "Unable to get DER-encoded cert"; |
+ return ERR_GET_DER_CERT_FAILED; |
} |
- if (!origin_bound_cert_store_->SetOriginBoundCert(origin, |
- key_out, |
- der_cert)) { |
- LOG(WARNING) << "Unable to set origin bound certificate"; |
- return false; |
+ private_key->swap(key_out); |
+ cert->swap(der_cert); |
+ return OK; |
+} |
+ |
+void OriginBoundCertService::CancelRequest(RequestHandle req) { |
+ DCHECK(CalledOnValidThread()); |
+ OriginBoundCertServiceRequest* request = |
+ reinterpret_cast<OriginBoundCertServiceRequest*>(req); |
+ request->Cancel(); |
+} |
+ |
+// HandleResult is called by OriginBoundCertServiceWorker on the origin message |
+// loop. It deletes OriginBoundCertServiceJob. |
+void OriginBoundCertService::HandleResult(const std::string& origin, |
+ int error, |
+ const std::string& private_key, |
+ const std::string& cert) { |
+ DCHECK(CalledOnValidThread()); |
+ |
+ origin_bound_cert_store_->SetOriginBoundCert(origin, private_key, cert); |
+ |
+ std::map<std::string, OriginBoundCertServiceJob*>::iterator j; |
+ j = inflight_.find(origin); |
+ if (j == inflight_.end()) { |
+ NOTREACHED(); |
+ return; |
} |
+ OriginBoundCertServiceJob* job = j->second; |
+ inflight_.erase(j); |
- private_key_result->swap(key_out); |
- cert_result->swap(der_cert); |
- return true; |
+ job->HandleResult(error, private_key, cert); |
+ delete job; |
} |
-int OriginBoundCertService::GetCertCount() { |
+int OriginBoundCertService::get_cert_count() { |
return origin_bound_cert_store_->GetCertCount(); |
} |
} // namespace net |
+ |
+DISABLE_RUNNABLE_METHOD_REFCOUNT(net::OriginBoundCertServiceWorker); |