summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/net/sqlite_origin_bound_cert_store.cc404
-rw-r--r--chrome/browser/net/sqlite_origin_bound_cert_store.h44
-rw-r--r--chrome/browser/net/sqlite_origin_bound_cert_store_unittest.cc175
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/common/chrome_constants.cc1
-rw-r--r--chrome/common/chrome_constants.h1
-rw-r--r--net/base/default_origin_bound_cert_store.cc137
-rw-r--r--net/base/default_origin_bound_cert_store.h163
-rw-r--r--net/base/default_origin_bound_cert_store_unittest.cc112
-rw-r--r--net/base/origin_bound_cert_service.cc29
-rw-r--r--net/base/origin_bound_cert_service.h23
-rw-r--r--net/base/origin_bound_cert_service_unittest.cc68
-rw-r--r--net/base/origin_bound_cert_store.h25
-rw-r--r--net/net.gyp4
15 files changed, 1164 insertions, 25 deletions
diff --git a/chrome/browser/net/sqlite_origin_bound_cert_store.cc b/chrome/browser/net/sqlite_origin_bound_cert_store.cc
new file mode 100644
index 0000000..9e77277
--- /dev/null
+++ b/chrome/browser/net/sqlite_origin_bound_cert_store.cc
@@ -0,0 +1,404 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/sqlite_origin_bound_cert_store.h"
+
+#include <list>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/string_util.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "chrome/browser/diagnostics/sqlite_diagnostics.h"
+#include "content/browser/browser_thread.h"
+#include "sql/meta_table.h"
+#include "sql/statement.h"
+#include "sql/transaction.h"
+
+// This class is designed to be shared between any calling threads and the
+// database thread. It batches operations and commits them on a timer.
+class SQLiteOriginBoundCertStore::Backend
+ : public base::RefCountedThreadSafe<SQLiteOriginBoundCertStore::Backend> {
+ public:
+ explicit Backend(const FilePath& path)
+ : path_(path),
+ db_(NULL),
+ num_pending_(0),
+ clear_local_state_on_exit_(false) {
+ }
+
+ // Creates or load the SQLite database.
+ bool Load(
+ std::vector<net::DefaultOriginBoundCertStore::OriginBoundCert*>* certs);
+
+ // Batch an origin bound cert addition.
+ void AddOriginBoundCert(
+ const net::DefaultOriginBoundCertStore::OriginBoundCert& cert);
+
+ // Batch an origin bound cert deletion.
+ void DeleteOriginBoundCert(
+ const net::DefaultOriginBoundCertStore::OriginBoundCert& cert);
+
+ // Commit pending operations as soon as possible.
+ void Flush(Task* completion_task);
+
+ // Commit any pending operations and close the database. This must be called
+ // before the object is destructed.
+ void Close();
+
+ void SetClearLocalStateOnExit(bool clear_local_state);
+
+ private:
+ friend class base::RefCountedThreadSafe<SQLiteOriginBoundCertStore::Backend>;
+
+ // You should call Close() before destructing this object.
+ ~Backend() {
+ DCHECK(!db_.get()) << "Close should have already been called.";
+ DCHECK(num_pending_ == 0 && pending_.empty());
+ }
+
+ // Database upgrade statements.
+ bool EnsureDatabaseVersion();
+
+ class PendingOperation {
+ public:
+ typedef enum {
+ CERT_ADD,
+ CERT_DELETE
+ } OperationType;
+
+ PendingOperation(
+ OperationType op,
+ const net::DefaultOriginBoundCertStore::OriginBoundCert& cert)
+ : op_(op), cert_(cert) {}
+
+ OperationType op() const { return op_; }
+ const net::DefaultOriginBoundCertStore::OriginBoundCert& cert() const {
+ return cert_;
+ }
+
+ private:
+ OperationType op_;
+ net::DefaultOriginBoundCertStore::OriginBoundCert cert_;
+ };
+
+ private:
+ // Batch an origin bound cert operation (add or delete)
+ void BatchOperation(
+ PendingOperation::OperationType op,
+ const net::DefaultOriginBoundCertStore::OriginBoundCert& cert);
+ // Commit our pending operations to the database.
+ void Commit();
+ // Close() executed on the background thread.
+ void InternalBackgroundClose();
+
+ FilePath path_;
+ scoped_ptr<sql::Connection> db_;
+ sql::MetaTable meta_table_;
+
+ typedef std::list<PendingOperation*> PendingOperationsList;
+ PendingOperationsList pending_;
+ PendingOperationsList::size_type num_pending_;
+ // True if the persistent store should be deleted upon destruction.
+ bool clear_local_state_on_exit_;
+ // Guard |pending_|, |num_pending_| and |clear_local_state_on_exit_|.
+ base::Lock lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(Backend);
+};
+
+// Version number of the database.
+static const int kCurrentVersionNumber = 1;
+static const int kCompatibleVersionNumber = 1;
+
+namespace {
+
+// Initializes the certs table, returning true on success.
+bool InitTable(sql::Connection* db) {
+ if (!db->DoesTableExist("origin_bound_certs")) {
+ if (!db->Execute("CREATE TABLE origin_bound_certs ("
+ "origin TEXT NOT NULL UNIQUE PRIMARY KEY,"
+ "private_key BLOB NOT NULL,"
+ "cert BLOB NOT NULL)"))
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+bool SQLiteOriginBoundCertStore::Backend::Load(
+ std::vector<net::DefaultOriginBoundCertStore::OriginBoundCert*>* certs) {
+ // This function should be called only once per instance.
+ DCHECK(!db_.get());
+
+ // Ensure the parent directory for storing certs is created before reading
+ // from it. We make an exception to allow IO on the UI thread here because
+ // we are going to disk anyway in db_->Open. (This code will be moved to the
+ // DB thread as part of http://crbug.com/52909.)
+ {
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+ const FilePath dir = path_.DirName();
+ if (!file_util::PathExists(dir) && !file_util::CreateDirectory(dir))
+ return false;
+ }
+
+ db_.reset(new sql::Connection);
+ if (!db_->Open(path_)) {
+ NOTREACHED() << "Unable to open cert DB.";
+ db_.reset();
+ return false;
+ }
+
+ if (!EnsureDatabaseVersion() || !InitTable(db_.get())) {
+ NOTREACHED() << "Unable to open cert DB.";
+ db_.reset();
+ return false;
+ }
+
+ db_->Preload();
+
+ // Slurp all the certs into the out-vector.
+ sql::Statement smt(db_->GetUniqueStatement(
+ "SELECT origin, private_key, cert FROM origin_bound_certs"));
+ if (!smt) {
+ NOTREACHED() << "select statement prep failed";
+ db_.reset();
+ return false;
+ }
+
+ while (smt.Step()) {
+ std::string private_key_from_db, cert_from_db;
+ smt.ColumnBlobAsString(1, &private_key_from_db);
+ smt.ColumnBlobAsString(2, &cert_from_db);
+ scoped_ptr<net::DefaultOriginBoundCertStore::OriginBoundCert> cert(
+ new net::DefaultOriginBoundCertStore::OriginBoundCert(
+ smt.ColumnString(0), // origin
+ private_key_from_db,
+ cert_from_db));
+ certs->push_back(cert.release());
+ }
+
+ return true;
+}
+
+bool SQLiteOriginBoundCertStore::Backend::EnsureDatabaseVersion() {
+ // Version check.
+ if (!meta_table_.Init(
+ db_.get(), kCurrentVersionNumber, kCompatibleVersionNumber)) {
+ return false;
+ }
+
+ if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
+ LOG(WARNING) << "Origin bound cert database is too new.";
+ return false;
+ }
+
+ int cur_version = meta_table_.GetVersionNumber();
+
+ // Put future migration cases here.
+
+ // When the version is too old, we just try to continue anyway, there should
+ // not be a released product that makes a database too old for us to handle.
+ LOG_IF(WARNING, cur_version < kCurrentVersionNumber) <<
+ "Origin bound cert database version " << cur_version <<
+ " is too old to handle.";
+
+ return true;
+}
+
+void SQLiteOriginBoundCertStore::Backend::AddOriginBoundCert(
+ const net::DefaultOriginBoundCertStore::OriginBoundCert& cert) {
+ BatchOperation(PendingOperation::CERT_ADD, cert);
+}
+
+void SQLiteOriginBoundCertStore::Backend::DeleteOriginBoundCert(
+ const net::DefaultOriginBoundCertStore::OriginBoundCert& cert) {
+ BatchOperation(PendingOperation::CERT_DELETE, cert);
+}
+
+void SQLiteOriginBoundCertStore::Backend::BatchOperation(
+ PendingOperation::OperationType op,
+ const net::DefaultOriginBoundCertStore::OriginBoundCert& cert) {
+ // Commit every 30 seconds.
+ static const int kCommitIntervalMs = 30 * 1000;
+ // Commit right away if we have more than 512 outstanding operations.
+ static const size_t kCommitAfterBatchSize = 512;
+ DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::DB));
+
+ // We do a full copy of the cert here, and hopefully just here.
+ scoped_ptr<PendingOperation> po(new PendingOperation(op, cert));
+
+ PendingOperationsList::size_type num_pending;
+ {
+ base::AutoLock locked(lock_);
+ pending_.push_back(po.release());
+ num_pending = ++num_pending_;
+ }
+
+ if (num_pending == 1) {
+ // We've gotten our first entry for this batch, fire off the timer.
+ BrowserThread::PostDelayedTask(
+ BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(this, &Backend::Commit), kCommitIntervalMs);
+ } else if (num_pending == kCommitAfterBatchSize) {
+ // We've reached a big enough batch, fire off a commit now.
+ BrowserThread::PostTask(
+ BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(this, &Backend::Commit));
+ }
+}
+
+void SQLiteOriginBoundCertStore::Backend::Commit() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+
+ PendingOperationsList ops;
+ {
+ base::AutoLock locked(lock_);
+ pending_.swap(ops);
+ num_pending_ = 0;
+ }
+
+ // Maybe an old timer fired or we are already Close()'ed.
+ if (!db_.get() || ops.empty())
+ return;
+
+ sql::Statement add_smt(db_->GetCachedStatement(SQL_FROM_HERE,
+ "INSERT INTO origin_bound_certs (origin, private_key, cert) "
+ "VALUES (?,?,?)"));
+ if (!add_smt) {
+ NOTREACHED();
+ return;
+ }
+
+ sql::Statement del_smt(db_->GetCachedStatement(SQL_FROM_HERE,
+ "DELETE FROM origin_bound_certs WHERE origin=?"));
+ if (!del_smt) {
+ NOTREACHED();
+ return;
+ }
+
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin()) {
+ NOTREACHED();
+ return;
+ }
+ for (PendingOperationsList::iterator it = ops.begin();
+ it != ops.end(); ++it) {
+ // Free the certs as we commit them to the database.
+ scoped_ptr<PendingOperation> po(*it);
+ switch (po->op()) {
+ case PendingOperation::CERT_ADD: {
+ add_smt.Reset();
+ add_smt.BindString(0, po->cert().origin());
+ const std::string& private_key = po->cert().private_key();
+ add_smt.BindBlob(1, private_key.data(), private_key.size());
+ const std::string& cert = po->cert().cert();
+ add_smt.BindBlob(2, cert.data(), cert.size());
+ if (!add_smt.Run())
+ NOTREACHED() << "Could not add an origin bound cert to the DB.";
+ break;
+ }
+ case PendingOperation::CERT_DELETE:
+ del_smt.Reset();
+ del_smt.BindString(0, po->cert().origin());
+ if (!del_smt.Run())
+ NOTREACHED() << "Could not delete an origin bound cert from the DB.";
+ break;
+
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+ transaction.Commit();
+}
+
+void SQLiteOriginBoundCertStore::Backend::Flush(Task* completion_task) {
+ DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::DB));
+ BrowserThread::PostTask(
+ BrowserThread::DB, FROM_HERE, NewRunnableMethod(this, &Backend::Commit));
+ if (completion_task) {
+ // We want the completion task to run immediately after Commit() returns.
+ // Posting it from here means there is less chance of another task getting
+ // onto the message queue first, than if we posted it from Commit() itself.
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, completion_task);
+ }
+}
+
+// Fire off a close message to the background thread. We could still have a
+// pending commit timer that will be holding a reference on us, but if/when
+// this fires we will already have been cleaned up and it will be ignored.
+void SQLiteOriginBoundCertStore::Backend::Close() {
+ DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::DB));
+ // Must close the backend on the background thread.
+ BrowserThread::PostTask(
+ BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(this, &Backend::InternalBackgroundClose));
+}
+
+void SQLiteOriginBoundCertStore::Backend::InternalBackgroundClose() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+ // Commit any pending operations
+ Commit();
+
+ db_.reset();
+
+ if (clear_local_state_on_exit_)
+ file_util::Delete(path_, false);
+}
+
+void SQLiteOriginBoundCertStore::Backend::SetClearLocalStateOnExit(
+ bool clear_local_state) {
+ base::AutoLock locked(lock_);
+ clear_local_state_on_exit_ = clear_local_state;
+}
+
+SQLiteOriginBoundCertStore::SQLiteOriginBoundCertStore(const FilePath& path)
+ : backend_(new Backend(path)) {
+}
+
+SQLiteOriginBoundCertStore::~SQLiteOriginBoundCertStore() {
+ if (backend_.get()) {
+ backend_->Close();
+ // Release our reference, it will probably still have a reference if the
+ // background thread has not run Close() yet.
+ backend_ = NULL;
+ }
+}
+
+bool SQLiteOriginBoundCertStore::Load(
+ std::vector<net::DefaultOriginBoundCertStore::OriginBoundCert*>* certs) {
+ return backend_->Load(certs);
+}
+
+void SQLiteOriginBoundCertStore::AddOriginBoundCert(
+ const net::DefaultOriginBoundCertStore::OriginBoundCert& cert) {
+ if (backend_.get())
+ backend_->AddOriginBoundCert(cert);
+}
+
+void SQLiteOriginBoundCertStore::DeleteOriginBoundCert(
+ const net::DefaultOriginBoundCertStore::OriginBoundCert& cert) {
+ if (backend_.get())
+ backend_->DeleteOriginBoundCert(cert);
+}
+
+void SQLiteOriginBoundCertStore::SetClearLocalStateOnExit(
+ bool clear_local_state) {
+ if (backend_.get())
+ backend_->SetClearLocalStateOnExit(clear_local_state);
+}
+
+void SQLiteOriginBoundCertStore::Flush(Task* completion_task) {
+ if (backend_.get())
+ backend_->Flush(completion_task);
+ else if (completion_task)
+ MessageLoop::current()->PostTask(FROM_HERE, completion_task);
+}
diff --git a/chrome/browser/net/sqlite_origin_bound_cert_store.h b/chrome/browser/net/sqlite_origin_bound_cert_store.h
new file mode 100644
index 0000000..f112ad8
--- /dev/null
+++ b/chrome/browser/net/sqlite_origin_bound_cert_store.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_SQLITE_ORIGIN_BOUND_CERT_STORE_H_
+#define CHROME_BROWSER_NET_SQLITE_ORIGIN_BOUND_CERT_STORE_H_
+#pragma once
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/default_origin_bound_cert_store.h"
+
+class FilePath;
+
+// Implements the net::DefaultOriginBoundCertStore::PersistentStore interface
+// in terms of a SQLite database. For documentation about the actual member
+// functions consult the documentation of the parent class
+// |net::DefaultOriginBoundCertStore::PersistentCertStore|.
+class SQLiteOriginBoundCertStore
+ : public net::DefaultOriginBoundCertStore::PersistentStore {
+ public:
+ explicit SQLiteOriginBoundCertStore(const FilePath& path);
+ virtual ~SQLiteOriginBoundCertStore();
+
+ // net::DefaultOriginBoundCertStore::PersistentStore implementation.
+ virtual bool Load(
+ std::vector<net::DefaultOriginBoundCertStore::OriginBoundCert*>* certs)
+ OVERRIDE;
+ virtual void AddOriginBoundCert(
+ const net::DefaultOriginBoundCertStore::OriginBoundCert& cert) OVERRIDE;
+ virtual void DeleteOriginBoundCert(
+ const net::DefaultOriginBoundCertStore::OriginBoundCert& cert) OVERRIDE;
+ virtual void SetClearLocalStateOnExit(bool clear_local_state) OVERRIDE;
+ virtual void Flush(Task* completion_task) OVERRIDE;
+
+ private:
+ class Backend;
+
+ scoped_refptr<Backend> backend_;
+
+ DISALLOW_COPY_AND_ASSIGN(SQLiteOriginBoundCertStore);
+};
+
+#endif // CHROME_BROWSER_NET_SQLITE_ORIGIN_BOUND_CERT_STORE_H_
diff --git a/chrome/browser/net/sqlite_origin_bound_cert_store_unittest.cc b/chrome/browser/net/sqlite_origin_bound_cert_store_unittest.cc
new file mode 100644
index 0000000..aae87a9
--- /dev/null
+++ b/chrome/browser/net/sqlite_origin_bound_cert_store_unittest.cc
@@ -0,0 +1,175 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/file_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop.h"
+#include "base/scoped_temp_dir.h"
+#include "base/stl_util.h"
+#include "base/test/thread_test_helper.h"
+#include "chrome/browser/net/sqlite_origin_bound_cert_store.h"
+#include "chrome/common/chrome_constants.h"
+#include "content/browser/browser_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class SQLiteOriginBoundCertStoreTest : public testing::Test {
+ public:
+ SQLiteOriginBoundCertStoreTest()
+ : db_thread_(BrowserThread::DB) {
+ }
+
+ protected:
+ virtual void SetUp() {
+ db_thread_.Start();
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ store_ = new SQLiteOriginBoundCertStore(
+ temp_dir_.path().Append(chrome::kOBCertFilename));
+ std::vector<net::DefaultOriginBoundCertStore::OriginBoundCert*> certs;
+ ASSERT_TRUE(store_->Load(&certs));
+ ASSERT_EQ(0u, certs.size());
+ // Make sure the store gets written at least once.
+ store_->AddOriginBoundCert(
+ net::DefaultOriginBoundCertStore::OriginBoundCert(
+ "https://encrypted.google.com:8443", "a", "b"));
+ }
+
+ BrowserThread db_thread_;
+ ScopedTempDir temp_dir_;
+ scoped_refptr<SQLiteOriginBoundCertStore> store_;
+};
+
+TEST_F(SQLiteOriginBoundCertStoreTest, KeepOnDestruction) {
+ store_->SetClearLocalStateOnExit(false);
+ store_ = NULL;
+ // Make sure we wait until the destructor has run.
+ scoped_refptr<base::ThreadTestHelper> helper(
+ new base::ThreadTestHelper(
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB)));
+ ASSERT_TRUE(helper->Run());
+
+ ASSERT_TRUE(file_util::PathExists(
+ temp_dir_.path().Append(chrome::kOBCertFilename)));
+ ASSERT_TRUE(file_util::Delete(
+ temp_dir_.path().Append(chrome::kOBCertFilename), false));
+}
+
+TEST_F(SQLiteOriginBoundCertStoreTest, RemoveOnDestruction) {
+ store_->SetClearLocalStateOnExit(true);
+ // Replace the store effectively destroying the current one and forcing it
+ // to write it's data to disk. Then we can see if after loading it again it
+ // is still there.
+ store_ = NULL;
+ // Make sure we wait until the destructor has run.
+ scoped_refptr<base::ThreadTestHelper> helper(
+ new base::ThreadTestHelper(
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB)));
+ ASSERT_TRUE(helper->Run());
+
+ ASSERT_FALSE(file_util::PathExists(
+ temp_dir_.path().Append(chrome::kOBCertFilename)));
+}
+
+// Test if data is stored as expected in the SQLite database.
+TEST_F(SQLiteOriginBoundCertStoreTest, TestPersistence) {
+ std::vector<net::DefaultOriginBoundCertStore::OriginBoundCert*> certs;
+ // Replace the store effectively destroying the current one and forcing it
+ // to write it's data to disk. Then we can see if after loading it again it
+ // is still there.
+ store_ = NULL;
+ scoped_refptr<base::ThreadTestHelper> helper(
+ new base::ThreadTestHelper(
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB)));
+ // Make sure we wait until the destructor has run.
+ ASSERT_TRUE(helper->Run());
+ store_ = new SQLiteOriginBoundCertStore(
+ temp_dir_.path().Append(chrome::kOBCertFilename));
+
+ // Reload and test for persistence
+ ASSERT_TRUE(store_->Load(&certs));
+ ASSERT_EQ(1U, certs.size());
+ ASSERT_STREQ("https://encrypted.google.com:8443", certs[0]->origin().c_str());
+ ASSERT_STREQ("a", certs[0]->private_key().c_str());
+ ASSERT_STREQ("b", certs[0]->cert().c_str());
+
+ // Now delete the cert and check persistence again.
+ store_->DeleteOriginBoundCert(*certs[0]);
+ store_ = NULL;
+ // Make sure we wait until the destructor has run.
+ ASSERT_TRUE(helper->Run());
+ STLDeleteContainerPointers(certs.begin(), certs.end());
+ certs.clear();
+ store_ = new SQLiteOriginBoundCertStore(
+ temp_dir_.path().Append(chrome::kOBCertFilename));
+
+ // Reload and check if the cert has been removed.
+ ASSERT_TRUE(store_->Load(&certs));
+ ASSERT_EQ(0U, certs.size());
+}
+
+// Test that we can force the database to be written by calling Flush().
+TEST_F(SQLiteOriginBoundCertStoreTest, TestFlush) {
+ // File timestamps don't work well on all platforms, so we'll determine
+ // whether the DB file has been modified by checking its size.
+ FilePath path = temp_dir_.path().Append(chrome::kOBCertFilename);
+ base::PlatformFileInfo info;
+ ASSERT_TRUE(file_util::GetFileInfo(path, &info));
+ int64 base_size = info.size;
+
+ // Write some certs, so the DB will have to expand by several KB.
+ for (char c = 'a'; c < 'z'; ++c) {
+ std::string origin(1, c);
+ std::string private_key(1000, c);
+ std::string cert(1000, c);
+ store_->AddOriginBoundCert(
+ net::DefaultOriginBoundCertStore::OriginBoundCert(origin,
+ private_key,
+ cert));
+ }
+
+ // Call Flush() and wait until the DB thread is idle.
+ store_->Flush(NULL);
+ scoped_refptr<base::ThreadTestHelper> helper(
+ new base::ThreadTestHelper(
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB)));
+ ASSERT_TRUE(helper->Run());
+
+ // We forced a write, so now the file will be bigger.
+ ASSERT_TRUE(file_util::GetFileInfo(path, &info));
+ ASSERT_GT(info.size, base_size);
+}
+
+// Counts the number of times Callback() has been run.
+class CallbackCounter : public base::RefCountedThreadSafe<CallbackCounter> {
+ public:
+ CallbackCounter() : callback_count_(0) {}
+
+ void Callback() {
+ ++callback_count_;
+ }
+
+ int callback_count() {
+ return callback_count_;
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<CallbackCounter>;
+ volatile int callback_count_;
+};
+
+// Test that we can get a completion callback after a Flush().
+TEST_F(SQLiteOriginBoundCertStoreTest, TestFlushCompletionCallback) {
+ scoped_refptr<CallbackCounter> counter(new CallbackCounter());
+
+ // Callback shouldn't be invoked until we call Flush().
+ ASSERT_EQ(0, counter->callback_count());
+
+ store_->Flush(NewRunnableMethod(counter.get(), &CallbackCounter::Callback));
+
+ scoped_refptr<base::ThreadTestHelper> helper(
+ new base::ThreadTestHelper(
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB)));
+ ASSERT_TRUE(helper->Run());
+
+ ASSERT_EQ(1, counter->callback_count());
+}
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 08f6128..540017b 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1472,6 +1472,8 @@
'browser/net/sdch_dictionary_fetcher.h',
'browser/net/service_providers_win.cc',
'browser/net/service_providers_win.h',
+ 'browser/net/sqlite_origin_bound_cert_store.cc',
+ 'browser/net/sqlite_origin_bound_cert_store.h',
'browser/net/sqlite_persistent_cookie_store.cc',
'browser/net/sqlite_persistent_cookie_store.h',
'browser/net/ssl_config_service_manager.h',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index cbf6f88..f872890 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1501,6 +1501,7 @@
'browser/net/predictor_unittest.cc',
'browser/net/pref_proxy_config_service_unittest.cc',
'browser/net/quoted_printable_unittest.cc',
+ 'browser/net/sqlite_origin_bound_cert_store_unittest.cc',
'browser/net/sqlite_persistent_cookie_store_unittest.cc',
'browser/net/ssl_config_service_manager_pref_unittest.cc',
'browser/net/url_fixer_upper_unittest.cc',
diff --git a/chrome/common/chrome_constants.cc b/chrome/common/chrome_constants.cc
index f1af5b6..2d241ad 100644
--- a/chrome/common/chrome_constants.cc
+++ b/chrome/common/chrome_constants.cc
@@ -126,6 +126,7 @@ const FilePath::CharType kOffTheRecordMediaCacheDirname[] =
const FilePath::CharType kAppCacheDirname[] = FPL("Application Cache");
const FilePath::CharType kThemePackFilename[] = FPL("Cached Theme.pak");
const FilePath::CharType kCookieFilename[] = FPL("Cookies");
+const FilePath::CharType kOBCertFilename[] = FPL("Origin Bound Certs");
const FilePath::CharType kExtensionsCookieFilename[] = FPL("Extension Cookies");
const FilePath::CharType kIsolatedAppStateDirname[] = FPL("Isolated Apps");
const FilePath::CharType kFaviconsFilename[] = FPL("Favicons");
diff --git a/chrome/common/chrome_constants.h b/chrome/common/chrome_constants.h
index ac5129d..6cd6e45 100644
--- a/chrome/common/chrome_constants.h
+++ b/chrome/common/chrome_constants.h
@@ -50,6 +50,7 @@ extern const FilePath::CharType kOffTheRecordMediaCacheDirname[];
extern const FilePath::CharType kAppCacheDirname[];
extern const FilePath::CharType kThemePackFilename[];
extern const FilePath::CharType kCookieFilename[];
+extern const FilePath::CharType kOBCertFilename[];
extern const FilePath::CharType kExtensionsCookieFilename[];
extern const FilePath::CharType kIsolatedAppStateDirname[];
extern const FilePath::CharType kFaviconsFilename[];
diff --git a/net/base/default_origin_bound_cert_store.cc b/net/base/default_origin_bound_cert_store.cc
new file mode 100644
index 0000000..e047af8
--- /dev/null
+++ b/net/base/default_origin_bound_cert_store.cc
@@ -0,0 +1,137 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/base/default_origin_bound_cert_store.h"
+
+#include "base/message_loop.h"
+
+namespace net {
+
+// static
+const size_t DefaultOriginBoundCertStore::kMaxCerts = 3300;
+
+DefaultOriginBoundCertStore::DefaultOriginBoundCertStore(
+ PersistentStore* store)
+ : initialized_(false),
+ store_(store) {}
+
+void DefaultOriginBoundCertStore::FlushStore(Task* completion_task) {
+ base::AutoLock autolock(lock_);
+
+ if (initialized_ && store_)
+ store_->Flush(completion_task);
+ else if (completion_task)
+ MessageLoop::current()->PostTask(FROM_HERE, completion_task);
+}
+
+bool DefaultOriginBoundCertStore::GetOriginBoundCert(
+ const std::string& origin,
+ std::string* private_key_result,
+ std::string* cert_result) {
+ base::AutoLock autolock(lock_);
+ InitIfNecessary();
+
+ OriginBoundCertMap::iterator it = origin_bound_certs_.find(origin);
+
+ if (it == origin_bound_certs_.end())
+ return false;
+
+ OriginBoundCert* cert = it->second;
+ *private_key_result = cert->private_key();
+ *cert_result = cert->cert();
+
+ return true;
+}
+
+bool DefaultOriginBoundCertStore::SetOriginBoundCert(
+ const std::string& origin,
+ const std::string& private_key,
+ const std::string& cert) {
+ base::AutoLock autolock(lock_);
+ InitIfNecessary();
+
+ InternalDeleteOriginBoundCert(origin);
+ InternalInsertOriginBoundCert(origin,
+ new OriginBoundCert(origin, private_key, cert));
+
+ return true;
+}
+
+int DefaultOriginBoundCertStore::GetCertCount() {
+ base::AutoLock autolock(lock_);
+ InitIfNecessary();
+
+ return origin_bound_certs_.size();
+}
+
+DefaultOriginBoundCertStore::~DefaultOriginBoundCertStore() {
+ DeleteAll();
+}
+
+void DefaultOriginBoundCertStore::DeleteAll() {
+ base::AutoLock autolock(lock_);
+
+ for (OriginBoundCertMap::iterator it = origin_bound_certs_.begin();
+ it != origin_bound_certs_.end(); it++) {
+ delete it->second;
+ }
+ origin_bound_certs_.clear();
+}
+
+void DefaultOriginBoundCertStore::InitStore() {
+ lock_.AssertAcquired();
+
+ DCHECK(store_) << "Store must exist to initialize";
+
+ // Initialize the store and sync in any saved persistent certs.
+ std::vector<OriginBoundCert*> certs;
+ // Reserve space for the maximum amount of certs a database should have.
+ // This prevents multiple vector growth / copies as we append certs.
+ certs.reserve(kMaxCerts);
+ store_->Load(&certs);
+
+ for (std::vector<OriginBoundCert*>::const_iterator it = certs.begin();
+ it != certs.end(); ++it) {
+ origin_bound_certs_[(*it)->origin()] = *it;
+ }
+}
+
+void DefaultOriginBoundCertStore::InternalDeleteOriginBoundCert(
+ const std::string& origin) {
+ lock_.AssertAcquired();
+
+ OriginBoundCertMap::iterator it = origin_bound_certs_.find(origin);
+ if (it == origin_bound_certs_.end())
+ return; // There is nothing to delete.
+
+ OriginBoundCert* cert = it->second;
+ if (store_)
+ store_->DeleteOriginBoundCert(*cert);
+ origin_bound_certs_.erase(it);
+ delete cert;
+}
+
+void DefaultOriginBoundCertStore::InternalInsertOriginBoundCert(
+ const std::string& origin,
+ OriginBoundCert* cert) {
+ lock_.AssertAcquired();
+
+ if (store_)
+ store_->AddOriginBoundCert(*cert);
+ origin_bound_certs_[origin] = cert;
+}
+
+DefaultOriginBoundCertStore::OriginBoundCert::OriginBoundCert() {}
+
+DefaultOriginBoundCertStore::OriginBoundCert::OriginBoundCert(
+ const std::string& origin,
+ const std::string& private_key,
+ const std::string& cert)
+ : origin_(origin),
+ private_key_(private_key),
+ cert_(cert) {}
+
+DefaultOriginBoundCertStore::PersistentStore::PersistentStore() {}
+
+} // namespace net
diff --git a/net/base/default_origin_bound_cert_store.h b/net/base/default_origin_bound_cert_store.h
new file mode 100644
index 0000000..4b8b8a9
--- /dev/null
+++ b/net/base/default_origin_bound_cert_store.h
@@ -0,0 +1,163 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_BASE_DEFAULT_ORIGIN_BOUND_CERT_STORE_H_
+#define NET_BASE_DEFAULT_ORIGIN_BOUND_CERT_STORE_H_
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "net/base/origin_bound_cert_store.h"
+
+class Task;
+
+namespace net {
+
+// This class is the system for storing and retrieving origin bound certs.
+// Modelled after the CookieMonster class, it has an in-memory cert store,
+// and synchronizes origin bound certs to an optional permanent storage that
+// implements the PersistentStore interface. The use case is described in
+// http://balfanz.github.com/tls-obc-spec/draft-balfanz-tls-obc-00.html
+//
+// This class can be accessed by multiple threads. For example, it can be used
+// by IO and origin bound cert management UI.
+class DefaultOriginBoundCertStore : public OriginBoundCertStore {
+ public:
+ class OriginBoundCert;
+ class PersistentStore;
+
+ // The key for each OriginBoundCert* in OriginBoundCertMap is the
+ // corresponding origin.
+ typedef std::map<std::string, OriginBoundCert*> OriginBoundCertMap;
+
+ // The store passed in should not have had Init() called on it yet. This
+ // class will take care of initializing it. The backing store is NOT owned by
+ // this class, but it must remain valid for the duration of the
+ // DefaultOriginBoundCertStore's existence. If |store| is NULL, then no
+ // backing store will be updated.
+ explicit DefaultOriginBoundCertStore(PersistentStore* store);
+
+ virtual ~DefaultOriginBoundCertStore();
+
+ // Flush the backing store (if any) to disk and post the given task when done.
+ // WARNING: THE CALLBACK WILL RUN ON A RANDOM THREAD. IT MUST BE THREAD SAFE.
+ // It may be posted to the current thread, or it may run on the thread that
+ // actually does the flushing. Your Task should generally post a notification
+ // to the thread you actually want to be notified on.
+ void FlushStore(Task* completion_task);
+
+ // OriginBoundCertStore implementation.
+ virtual bool GetOriginBoundCert(const std::string& origin,
+ std::string* private_key_result,
+ std::string* cert_result) OVERRIDE;
+ virtual bool SetOriginBoundCert(const std::string& origin,
+ const std::string& private_key,
+ const std::string& cert) OVERRIDE;
+ virtual int GetCertCount() OVERRIDE;
+
+ private:
+ static const size_t kMaxCerts;
+
+ // Deletes all of the certs. Does not delete them from |store_|.
+ void DeleteAll();
+
+ // Called by all non-static functions to ensure that the cert store has
+ // been initialized. This is not done during creating so it doesn't block
+ // the window showing.
+ // Note: this method should always be called with lock_ held.
+ void InitIfNecessary() {
+ if (!initialized_) {
+ if (store_)
+ InitStore();
+ initialized_ = true;
+ }
+ }
+
+ // Initializes the backing store and reads existing certs from it.
+ // Should only be called by InitIfNecessary().
+ void InitStore();
+
+ // Deletes the cert for the specified origin, if such a cert exists, from the
+ // in-memory store. Deletes it from |store_| if |store_| is not NULL.
+ void InternalDeleteOriginBoundCert(const std::string& origin);
+
+ // Takes ownership of *cert.
+ // Adds the cert for the specified origin to the in-memory store. Deletes it
+ // from |store_| if |store_| is not NULL.
+ void InternalInsertOriginBoundCert(const std::string& origin,
+ OriginBoundCert* cert);
+
+ // Indicates whether the cert store has been initialized. This happens
+ // Lazily in InitStoreIfNecessary().
+ bool initialized_;
+
+ scoped_refptr<PersistentStore> store_;
+
+ OriginBoundCertMap origin_bound_certs_;
+
+ // Lock for thread-safety
+ base::Lock lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(DefaultOriginBoundCertStore);
+};
+
+// The OriginBoundCert class contains a private key in addition to the origin
+// and the cert.
+class DefaultOriginBoundCertStore::OriginBoundCert {
+ public:
+ OriginBoundCert();
+ OriginBoundCert(const std::string& origin,
+ const std::string& privatekey,
+ const std::string& cert);
+
+ const std::string& origin() const { return origin_; }
+ const std::string& private_key() const { return private_key_; }
+ const std::string& cert() const { return cert_; }
+
+ private:
+ std::string origin_;
+ std::string private_key_;
+ std::string cert_;
+};
+
+typedef base::RefCountedThreadSafe<DefaultOriginBoundCertStore::PersistentStore>
+ RefcountedPersistentStore;
+
+class DefaultOriginBoundCertStore::PersistentStore
+ : public RefcountedPersistentStore {
+ public:
+ virtual ~PersistentStore() {}
+
+ // Initializes the store and retrieves the existing certs. This will be
+ // called only once at startup. Note that the certs are individually allocated
+ // and that ownership is transferred to the caller upon return.
+ virtual bool Load(
+ std::vector<DefaultOriginBoundCertStore::OriginBoundCert*>* certs) = 0;
+
+ virtual void AddOriginBoundCert(const OriginBoundCert& cert) = 0;
+
+ virtual void DeleteOriginBoundCert(const OriginBoundCert& cert) = 0;
+
+ // Sets the value of the user preference whether the persistent storage
+ // must be deleted upon destruction.
+ virtual void SetClearLocalStateOnExit(bool clear_local_state) = 0;
+
+ // Flush the store and post the given Task when complete.
+ virtual void Flush(Task* completion_task) = 0;
+
+ protected:
+ PersistentStore();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PersistentStore);
+};
+
+} // namespace net
+
+#endif // NET_DEFAULT_ORIGIN_BOUND_CERT_STORE_H_
diff --git a/net/base/default_origin_bound_cert_store_unittest.cc b/net/base/default_origin_bound_cert_store_unittest.cc
new file mode 100644
index 0000000..5fe77ef
--- /dev/null
+++ b/net/base/default_origin_bound_cert_store_unittest.cc
@@ -0,0 +1,112 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/base/default_origin_bound_cert_store.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+class MockPersistentStore
+ : public DefaultOriginBoundCertStore::PersistentStore {
+ public:
+ MockPersistentStore();
+ virtual ~MockPersistentStore();
+
+ // DefaultOriginBoundCertStore::PersistentStore implementation.
+ virtual bool Load(
+ std::vector<DefaultOriginBoundCertStore::OriginBoundCert*>* certs)
+ OVERRIDE;
+ virtual void AddOriginBoundCert(
+ const DefaultOriginBoundCertStore::OriginBoundCert& cert) OVERRIDE;
+ virtual void DeleteOriginBoundCert(
+ const DefaultOriginBoundCertStore::OriginBoundCert& cert) OVERRIDE;
+ virtual void SetClearLocalStateOnExit(bool clear_local_state) OVERRIDE;
+ virtual void Flush(Task* completion_task) OVERRIDE;
+
+ private:
+ typedef std::map<std::string, DefaultOriginBoundCertStore::OriginBoundCert>
+ OriginBoundCertMap;
+
+ OriginBoundCertMap origin_certs_;
+};
+
+MockPersistentStore::MockPersistentStore() {}
+
+MockPersistentStore::~MockPersistentStore() {}
+
+bool MockPersistentStore::Load(
+ std::vector<DefaultOriginBoundCertStore::OriginBoundCert*>* certs) {
+ OriginBoundCertMap::iterator it;
+
+ for (it = origin_certs_.begin(); it != origin_certs_.end(); ++it) {
+ certs->push_back(
+ new DefaultOriginBoundCertStore::OriginBoundCert(it->second));
+ }
+
+ return true;
+}
+
+void MockPersistentStore::AddOriginBoundCert(
+ const DefaultOriginBoundCertStore::OriginBoundCert& cert) {
+ origin_certs_[cert.origin()] = cert;
+}
+
+void MockPersistentStore::DeleteOriginBoundCert(
+ const DefaultOriginBoundCertStore::OriginBoundCert& cert) {
+ origin_certs_.erase(cert.origin());
+}
+
+void MockPersistentStore::SetClearLocalStateOnExit(bool clear_local_state) {}
+
+void MockPersistentStore::Flush(Task* completion_task) {
+ NOTREACHED();
+}
+
+TEST(DefaultOriginBoundCertStoreTest, TestLoading) {
+ scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
+
+ persistent_store->AddOriginBoundCert(
+ DefaultOriginBoundCertStore::OriginBoundCert(
+ "https://encrypted.google.com/", "a", "b"));
+ persistent_store->AddOriginBoundCert(
+ DefaultOriginBoundCertStore::OriginBoundCert(
+ "https://www.verisign.com/", "c", "d"));
+
+ // Make sure certs load properly.
+ scoped_ptr<DefaultOriginBoundCertStore> store(
+ new DefaultOriginBoundCertStore(persistent_store));
+ EXPECT_EQ(2, store->GetCertCount());
+ EXPECT_TRUE(store->SetOriginBoundCert("https://www.verisign.com/", "e", "f"));
+ EXPECT_EQ(2, store->GetCertCount());
+ EXPECT_TRUE(store->SetOriginBoundCert("https://www.twitter.com/", "g", "h"));
+ EXPECT_EQ(3, store->GetCertCount());
+}
+
+TEST(DefaultOriginBoundCertStoreTest, TestSettingAndGetting) {
+ scoped_ptr<DefaultOriginBoundCertStore> store(
+ new DefaultOriginBoundCertStore(NULL));
+ std::string private_key, cert;
+ EXPECT_EQ(0, store->GetCertCount());
+ EXPECT_FALSE(store->GetOriginBoundCert("https://www.verisign.com/",
+ &private_key,
+ &cert));
+ EXPECT_TRUE(private_key.empty());
+ EXPECT_TRUE(cert.empty());
+ EXPECT_TRUE(store->SetOriginBoundCert("https://www.verisign.com/", "i", "j"));
+ EXPECT_TRUE(store->GetOriginBoundCert("https://www.verisign.com/",
+ &private_key,
+ &cert));
+ EXPECT_EQ("i", private_key);
+ EXPECT_EQ("j", cert);
+}
+
+} // namespace net
diff --git a/net/base/origin_bound_cert_service.cc b/net/base/origin_bound_cert_service.cc
index 0d706bc..61a80eb 100644
--- a/net/base/origin_bound_cert_service.cc
+++ b/net/base/origin_bound_cert_service.cc
@@ -11,7 +11,6 @@
#include "base/memory/scoped_ptr.h"
#include "base/rand_util.h"
#include "crypto/rsa_private_key.h"
-#include "googleurl/src/gurl.h"
#include "net/base/origin_bound_cert_store.h"
#include "net/base/x509_certificate.h"
@@ -24,19 +23,23 @@ const int kValidityPeriodInDays = 365;
} // namespace
-bool OriginBoundCertService::GetOriginBoundCert(const GURL& url,
+OriginBoundCertService::OriginBoundCertService(
+ OriginBoundCertStore* origin_bound_cert_store)
+ : origin_bound_cert_store_(origin_bound_cert_store) {}
+
+OriginBoundCertService::~OriginBoundCertService() {}
+
+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.
- if (origin_bound_cert_store_->HasOriginBoundCert(url)) {
- return origin_bound_cert_store_->GetOriginBoundCert(url,
- private_key_result,
- cert_result);
- }
+ if (origin_bound_cert_store_->GetOriginBoundCert(origin,
+ private_key_result,
+ cert_result))
+ return true;
// No origin bound cert exists, we have to create one.
- std::string origin = url.GetOrigin().spec();
- std::string subject = "CN=origin-bound certificate for " + origin;
+ std::string subject = "CN=OBC";
scoped_ptr<crypto::RSAPrivateKey> key(
crypto::RSAPrivateKey::Create(kKeySizeInBits));
if (!key.get()) {
@@ -68,7 +71,9 @@ bool OriginBoundCertService::GetOriginBoundCert(const GURL& url,
return false;
}
- if (!origin_bound_cert_store_->SetOriginBoundCert(url, key_out, der_cert)) {
+ if (!origin_bound_cert_store_->SetOriginBoundCert(origin,
+ key_out,
+ der_cert)) {
LOG(WARNING) << "Unable to set origin bound certificate";
return false;
}
@@ -78,4 +83,8 @@ bool OriginBoundCertService::GetOriginBoundCert(const GURL& url,
return true;
}
+int OriginBoundCertService::GetCertCount() {
+ return origin_bound_cert_store_->GetCertCount();
+}
+
} // namespace net
diff --git a/net/base/origin_bound_cert_service.h b/net/base/origin_bound_cert_service.h
index c1d65b9..9f700b9 100644
--- a/net/base/origin_bound_cert_service.h
+++ b/net/base/origin_bound_cert_service.h
@@ -8,30 +8,39 @@
#include <string>
-class GURL;
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
namespace net {
class OriginBoundCertStore;
// A class for creating and fetching origin bound certs.
-class OriginBoundCertService {
+class OriginBoundCertService
+ : public base::RefCountedThreadSafe<OriginBoundCertService> {
public:
- explicit OriginBoundCertService(OriginBoundCertStore* origin_bound_cert_store)
- : origin_bound_cert_store_(origin_bound_cert_store) {}
+ // This object owns origin_bound_cert_store.
+ explicit OriginBoundCertService(
+ OriginBoundCertStore* origin_bound_cert_store);
+
+ ~OriginBoundCertService();
// TODO(rkn): Specify certificate type (RSA or DSA).
// TODO(rkn): Key generation can be time consuming, so this should have an
// asynchronous interface.
// Fetches the origin bound cert for the specified origin if one exists
// and creates one otherwise. On success, |private_key_result| stores a
- // PrivateKeyInfo struct, and |cert_result| stores a DER-encoded certificate.
- bool GetOriginBoundCert(const GURL& url,
+ // DER-encoded PrivateKeyInfo struct, and |cert_result| stores a DER-encoded
+ // certificate.
+ bool GetOriginBoundCert(const std::string& origin,
std::string* private_key_result,
std::string* cert_result);
+ // Public only for unit testing.
+ int GetCertCount();
+
private:
- OriginBoundCertStore* origin_bound_cert_store_;
+ scoped_ptr<OriginBoundCertStore> origin_bound_cert_store_;
};
} // namespace net
diff --git a/net/base/origin_bound_cert_service_unittest.cc b/net/base/origin_bound_cert_service_unittest.cc
new file mode 100644
index 0000000..59be848
--- /dev/null
+++ b/net/base/origin_bound_cert_service_unittest.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/base/origin_bound_cert_service.h"
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "crypto/rsa_private_key.h"
+#include "net/base/default_origin_bound_cert_store.h"
+#include "net/base/x509_certificate.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(OriginBoundCertServiceTest, DuplicateCertTest) {
+ scoped_refptr<OriginBoundCertService> service(
+ new OriginBoundCertService(new DefaultOriginBoundCertStore(NULL)));
+ std::string origin1("https://encrypted.google.com/");
+ std::string origin2("https://www.verisign.com/");
+
+ // The store should start out empty and should increment appropriately.
+ std::string private_key_info_1a, der_cert_1a;
+ EXPECT_EQ(0, service->GetCertCount());
+ EXPECT_TRUE(service->GetOriginBoundCert(
+ origin1, &private_key_info_1a, &der_cert_1a));
+ EXPECT_EQ(1, service->GetCertCount());
+
+ // We should get the same cert and key for the same origin.
+ std::string private_key_info_1b, der_cert_1b;
+ EXPECT_TRUE(service->GetOriginBoundCert(
+ origin1, &private_key_info_1b, &der_cert_1b));
+ EXPECT_EQ(1, service->GetCertCount());
+ EXPECT_EQ(private_key_info_1a, private_key_info_1b);
+ EXPECT_EQ(der_cert_1a, der_cert_1b);
+
+ // We should get a different cert and key for different origins.
+ std::string private_key_info_2, der_cert_2;
+ EXPECT_TRUE(service->GetOriginBoundCert(
+ origin2, &private_key_info_2, &der_cert_2));
+ EXPECT_EQ(2, service->GetCertCount());
+ EXPECT_NE(private_key_info_1a, private_key_info_2);
+ EXPECT_NE(der_cert_1a, der_cert_2);
+}
+
+TEST(OriginBoundCertServiceTest, ExtractValuesFromBytes) {
+ scoped_refptr<OriginBoundCertService> service(
+ new OriginBoundCertService(new DefaultOriginBoundCertStore(NULL)));
+ std::string origin("https://encrypted.google.com/");
+ std::string private_key_info, der_cert;
+ EXPECT_TRUE(service->GetOriginBoundCert(
+ origin, &private_key_info, &der_cert));
+ std::vector<uint8> key_vec(private_key_info.begin(), private_key_info.end());
+
+ // Check that we can retrieve the key pair from the bytes.
+ scoped_ptr<crypto::RSAPrivateKey> private_key(
+ crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(key_vec));
+ EXPECT_TRUE(private_key != NULL);
+
+ // Check that we can retrieve the cert from the bytes.
+ scoped_refptr<X509Certificate> x509cert(
+ X509Certificate::CreateFromBytes(der_cert.data(), der_cert.size()));
+ EXPECT_TRUE(x509cert != NULL);
+}
+
+} // namespace net
diff --git a/net/base/origin_bound_cert_store.h b/net/base/origin_bound_cert_store.h
index 8529fbd..63989f5 100644
--- a/net/base/origin_bound_cert_store.h
+++ b/net/base/origin_bound_cert_store.h
@@ -8,29 +8,38 @@
#include <string>
-class GURL;
-
namespace net {
// An interface for storing and retrieving origin bound certs. Origin bound
// certificates are specified in
// http://balfanz.github.com/tls-obc-spec/draft-balfanz-tls-obc-00.html.
+// Owned only by a single OriginBoundCertService object, which is responsible
+// for deleting it.
+
class OriginBoundCertStore {
public:
- virtual bool HasOriginBoundCert(const GURL& url) = 0;
+ virtual ~OriginBoundCertStore() {}
// TODO(rkn): Specify certificate type (RSA or DSA).
- // TODO(rkn): Key generation can be time consuming, so this should have an
- // asynchronous interface.
- // The output is stored in |private_key_result| and |cert_result|.
- virtual bool GetOriginBoundCert(const GURL& url,
+ // TODO(rkn): File I/O may be required, so this should have an asynchronous
+ // interface.
+ // Returns true on success. |private_key_result| stores a DER-encoded
+ // PrivateKeyInfo struct and |cert_result| stores a DER-encoded
+ // certificate. Returns false if no origin bound cert exists for the
+ // specified origin.
+ virtual bool GetOriginBoundCert(const std::string& origin,
std::string* private_key_result,
std::string* cert_result) = 0;
- virtual bool SetOriginBoundCert(const GURL& url,
+ // Adds an origin bound cert to the store.
+ virtual bool SetOriginBoundCert(const std::string& origin,
const std::string& private_key,
const std::string& cert) = 0;
+
+ // Returns the number of certs in the store.
+ // Public only for unit testing.
+ virtual int GetCertCount() = 0;
};
} // namespace net
diff --git a/net/net.gyp b/net/net.gyp
index 4ae27c0..de2b4df 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -67,6 +67,8 @@
'base/crypto_module_openssl.cc',
'base/data_url.cc',
'base/data_url.h',
+ 'base/default_origin_bound_cert_store.cc',
+ 'base/default_origin_bound_cert_store.h',
'base/directory_lister.cc',
'base/directory_lister.h',
'base/dns_reload_timer.cc',
@@ -877,6 +879,7 @@
'base/cookie_monster_unittest.cc',
'base/crl_filter_unittest.cc',
'base/data_url_unittest.cc',
+ 'base/default_origin_bound_cert_store_unittest.cc',
'base/directory_lister_unittest.cc',
'base/dnssec_unittest.cc',
'base/dns_util_unittest.cc',
@@ -900,6 +903,7 @@
'base/net_log_unittest.cc',
'base/net_log_unittest.h',
'base/net_util_unittest.cc',
+ 'base/origin_bound_cert_service_unittest.cc',
'base/pem_tokenizer_unittest.cc',
'base/registry_controlled_domain_unittest.cc',
'base/run_all_unittests.cc',