Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(314)

Unified Diff: net/base/origin_bound_cert_service.cc

Issue 7565023: Gave the GetOriginBoundCertificate an asynchronous interface because certificate (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 9 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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,311 @@
} // 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 output
+ // arguments 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),
+ synchronous_completions_(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)) {
+ synchronous_completions_++;
+ *out_req = NULL;
+ return OK;
+ }
- // No origin bound cert exists, we have to create one.
+ // |origin_bound_cert_store_| has no cert for this origin. See if an
+ // identical request is currently in flight.
+ 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_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 +344,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_CERT_BYTES_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::cert_count() {
return origin_bound_cert_store_->GetCertCount();
}
} // namespace net
+
+DISABLE_RUNNABLE_METHOD_REFCOUNT(net::OriginBoundCertServiceWorker);

Powered by Google App Engine
This is Rietveld 408576698