summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authormiu@chromium.org <miu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-08-15 23:02:30 +0000
committermiu@chromium.org <miu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-08-15 23:03:33 +0000
commit8faaaa1db317847005281e04eaa3a44909a4f097 (patch)
tree636edef2aacada9eb6d15f58c851b008602a26e6 /chrome/browser
parent9a341ca5ce10ddcdca8b750df56bc2e9eb46ba4e (diff)
downloadchromium_src-8faaaa1db317847005281e04eaa3a44909a4f097.zip
chromium_src-8faaaa1db317847005281e04eaa3a44909a4f097.tar.gz
chromium_src-8faaaa1db317847005281e04eaa3a44909a4f097.tar.bz2
Revert of Move sqlite_channel_id_store from chrome/browser/net to net/extras. (patchset #26 of https://codereview.chromium.org/381073002/)
Reason for revert: Closed the tree on failing net_unittests: http://build.chromium.org/p/chromium.linux/buildstatus?builder=Linux%20Tests%20%28dbg%29%281%29&number=32912 Original issue's description: > Move sqlite_channel_id_store from chrome/browser/net to net/extras. > Application of special storage policy is split out into chrome/browser/net/quota_policy_channel_id_store. > > TEST=net_unittests --gtest_filter=SQLiteChannelIDStoreTest* > TEST=unit_tests --gtest_filter=QuotaPolicyChannelIDStore* > > BUG=397545 > > Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=289996 TBR=mef@chromium.org NOTREECHECKS=true NOTRY=true Review URL: https://codereview.chromium.org/477253002 Cr-Commit-Position: refs/heads/master@{#290038} git-svn-id: svn://svn.chromium.org/chrome/trunk/src@290038 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/net/quota_policy_channel_id_store.cc79
-rw-r--r--chrome/browser/net/quota_policy_channel_id_store.h67
-rw-r--r--chrome/browser/net/quota_policy_channel_id_store_unittest.cc203
-rw-r--r--chrome/browser/net/sqlite_channel_id_store.cc641
-rw-r--r--chrome/browser/net/sqlite_channel_id_store.h55
-rw-r--r--chrome/browser/net/sqlite_channel_id_store_unittest.cc473
-rw-r--r--chrome/browser/profiles/profile_impl_io_data.cc6
7 files changed, 1172 insertions, 352 deletions
diff --git a/chrome/browser/net/quota_policy_channel_id_store.cc b/chrome/browser/net/quota_policy_channel_id_store.cc
deleted file mode 100644
index 08ed7d275..0000000
--- a/chrome/browser/net/quota_policy_channel_id_store.cc
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2014 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/quota_policy_channel_id_store.h"
-
-#include <list>
-
-#include "base/basictypes.h"
-#include "base/bind.h"
-#include "base/file_util.h"
-#include "base/files/file_path.h"
-#include "base/logging.h"
-#include "base/metrics/histogram.h"
-#include "base/strings/string_util.h"
-#include "base/threading/thread.h"
-#include "base/threading/thread_restrictions.h"
-#include "net/cookies/cookie_util.h"
-#include "net/extras/sqlite/sqlite_channel_id_store.h"
-#include "url/gurl.h"
-#include "webkit/browser/quota/special_storage_policy.h"
-
-QuotaPolicyChannelIDStore::QuotaPolicyChannelIDStore(
- const base::FilePath& path,
- const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
- quota::SpecialStoragePolicy* special_storage_policy)
- : special_storage_policy_(special_storage_policy),
- persistent_store_(
- new net::SQLiteChannelIDStore(path, background_task_runner)) {
- DCHECK(background_task_runner);
-}
-
-QuotaPolicyChannelIDStore::~QuotaPolicyChannelIDStore() {
- if (!special_storage_policy_.get() ||
- !special_storage_policy_->HasSessionOnlyOrigins()) {
- return;
- }
- std::list<std::string> session_only_server_identifiers;
- for (std::set<std::string>::iterator it = server_identifiers_.begin();
- it != server_identifiers_.end();
- ++it) {
- GURL url(net::cookie_util::CookieOriginToURL(*it, true));
- if (special_storage_policy_->IsStorageSessionOnly(url))
- session_only_server_identifiers.push_back(*it);
- }
- persistent_store_->DeleteAllInList(session_only_server_identifiers);
-}
-
-void QuotaPolicyChannelIDStore::Load(const LoadedCallback& loaded_callback) {
- persistent_store_->Load(
- base::Bind(&QuotaPolicyChannelIDStore::OnLoad, this, loaded_callback));
-}
-
-void QuotaPolicyChannelIDStore::AddChannelID(
- const net::DefaultChannelIDStore::ChannelID& channel_id) {
- server_identifiers_.insert(channel_id.server_identifier());
- persistent_store_->AddChannelID(channel_id);
-}
-
-void QuotaPolicyChannelIDStore::DeleteChannelID(
- const net::DefaultChannelIDStore::ChannelID& channel_id) {
- server_identifiers_.erase(channel_id.server_identifier());
- persistent_store_->DeleteChannelID(channel_id);
-}
-
-void QuotaPolicyChannelIDStore::SetForceKeepSessionState() {
- special_storage_policy_ = NULL;
-}
-
-void QuotaPolicyChannelIDStore::OnLoad(
- const LoadedCallback& loaded_callback,
- scoped_ptr<ChannelIDVector> channel_ids) {
- for (ChannelIDVector::const_iterator channel_id = channel_ids->begin();
- channel_id != channel_ids->end();
- ++channel_id) {
- server_identifiers_.insert((*channel_id)->server_identifier());
- }
- loaded_callback.Run(channel_ids.Pass());
-}
diff --git a/chrome/browser/net/quota_policy_channel_id_store.h b/chrome/browser/net/quota_policy_channel_id_store.h
deleted file mode 100644
index 30847e8..0000000
--- a/chrome/browser/net/quota_policy_channel_id_store.h
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2014 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_QUOTA_POLICY_CHANNEL_ID_STORE_H_
-#define CHROME_BROWSER_NET_QUOTA_POLICY_CHANNEL_ID_STORE_H_
-
-#include <set>
-#include <string>
-
-#include "base/callback_forward.h"
-#include "base/compiler_specific.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/memory/scoped_vector.h"
-#include "net/extras/sqlite/sqlite_channel_id_store.h"
-#include "net/ssl/default_channel_id_store.h"
-
-namespace base {
-class FilePath;
-class SequencedTaskRunner;
-}
-
-namespace quota {
-class SpecialStoragePolicy;
-}
-
-// Persistent ChannelID Store that takes into account SpecialStoragePolicy and
-// removes ChannelIDs that are StorageSessionOnly when store is closed.
-class QuotaPolicyChannelIDStore
- : public net::DefaultChannelIDStore::PersistentStore {
- public:
- // Create or open persistent store in file |path|. All I/O tasks are performed
- // in background using |background_task_runner|. If provided, a
- // |special_storage_policy| is consulted when the store is closed to decide
- // which certificates to keep.
- QuotaPolicyChannelIDStore(
- const base::FilePath& path,
- const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
- quota::SpecialStoragePolicy* special_storage_policy);
-
- // net::DefaultChannelIDStore::PersistentStore:
- virtual void Load(const LoadedCallback& loaded_callback) OVERRIDE;
- virtual void AddChannelID(
- const net::DefaultChannelIDStore::ChannelID& channel_id) OVERRIDE;
- virtual void DeleteChannelID(
- const net::DefaultChannelIDStore::ChannelID& channel_id) OVERRIDE;
- virtual void SetForceKeepSessionState() OVERRIDE;
-
- private:
- typedef ScopedVector<net::DefaultChannelIDStore::ChannelID> ChannelIDVector;
-
- virtual ~QuotaPolicyChannelIDStore();
-
- void OnLoad(const LoadedCallback& loaded_callback,
- scoped_ptr<ChannelIDVector> channel_ids);
-
- scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_;
- scoped_refptr<net::SQLiteChannelIDStore> persistent_store_;
- // Cache of server identifiers we have channel IDs stored for.
- std::set<std::string> server_identifiers_;
-
- DISALLOW_COPY_AND_ASSIGN(QuotaPolicyChannelIDStore);
-};
-
-#endif // CHROME_BROWSER_NET_QUOTA_POLICY_CHANNEL_ID_STORE_H_
diff --git a/chrome/browser/net/quota_policy_channel_id_store_unittest.cc b/chrome/browser/net/quota_policy_channel_id_store_unittest.cc
deleted file mode 100644
index 67a7b83..0000000
--- a/chrome/browser/net/quota_policy_channel_id_store_unittest.cc
+++ /dev/null
@@ -1,203 +0,0 @@
-// Copyright 2014 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/bind.h"
-#include "base/file_util.h"
-#include "base/files/scoped_temp_dir.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_vector.h"
-#include "base/message_loop/message_loop.h"
-#include "base/run_loop.h"
-#include "base/stl_util.h"
-#include "base/thread_task_runner_handle.h"
-#include "base/time/time.h"
-#include "chrome/browser/net/quota_policy_channel_id_store.h"
-#include "content/public/test/mock_special_storage_policy.h"
-#include "content/public/test/test_browser_thread_bundle.h"
-#include "net/base/test_data_directory.h"
-#include "net/cookies/cookie_util.h"
-#include "net/ssl/ssl_client_cert_type.h"
-#include "net/test/cert_test_util.h"
-#include "sql/statement.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-const base::FilePath::CharType kTestChannelIDFilename[] =
- FILE_PATH_LITERAL("ChannelID");
-
-class QuotaPolicyChannelIDStoreTest : public testing::Test {
- public:
- void Load(ScopedVector<net::DefaultChannelIDStore::ChannelID>* channel_ids) {
- base::RunLoop run_loop;
- store_->Load(base::Bind(&QuotaPolicyChannelIDStoreTest::OnLoaded,
- base::Unretained(this),
- &run_loop));
- run_loop.Run();
- channel_ids->swap(channel_ids_);
- channel_ids_.clear();
- }
-
- void OnLoaded(base::RunLoop* run_loop,
- scoped_ptr<ScopedVector<net::DefaultChannelIDStore::ChannelID> >
- channel_ids) {
- channel_ids_.swap(*channel_ids);
- run_loop->Quit();
- }
-
- protected:
- static base::Time GetTestCertExpirationTime() {
- // Cert expiration time from 'dumpasn1 unittest.originbound.der':
- // GeneralizedTime 19/11/2111 02:23:45 GMT
- // base::Time::FromUTCExploded can't generate values past 2038 on 32-bit
- // linux, so we use the raw value here.
- return base::Time::FromInternalValue(GG_INT64_C(16121816625000000));
- }
-
- static base::Time GetTestCertCreationTime() {
- // UTCTime 13/12/2011 02:23:45 GMT
- base::Time::Exploded exploded_time;
- exploded_time.year = 2011;
- exploded_time.month = 12;
- exploded_time.day_of_week = 0; // Unused.
- exploded_time.day_of_month = 13;
- exploded_time.hour = 2;
- exploded_time.minute = 23;
- exploded_time.second = 45;
- exploded_time.millisecond = 0;
- return base::Time::FromUTCExploded(exploded_time);
- }
-
- virtual void SetUp() {
- ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
- store_ = new QuotaPolicyChannelIDStore(
- temp_dir_.path().Append(kTestChannelIDFilename),
- base::ThreadTaskRunnerHandle::Get(),
- NULL);
- ScopedVector<net::DefaultChannelIDStore::ChannelID> channel_ids;
- Load(&channel_ids);
- ASSERT_EQ(0u, channel_ids.size());
- // Make sure the store gets written at least once.
- store_->AddChannelID(
- net::DefaultChannelIDStore::ChannelID("google.com",
- base::Time::FromInternalValue(1),
- base::Time::FromInternalValue(2),
- "a",
- "b"));
- }
-
- virtual void TearDown() {
- store_ = NULL;
- loop_.RunUntilIdle();
- }
-
- base::ScopedTempDir temp_dir_;
- scoped_refptr<QuotaPolicyChannelIDStore> store_;
- ScopedVector<net::DefaultChannelIDStore::ChannelID> channel_ids_;
- base::MessageLoop loop_;
-};
-
-// Test if data is stored as expected in the QuotaPolicy database.
-TEST_F(QuotaPolicyChannelIDStoreTest, TestPersistence) {
- store_->AddChannelID(
- net::DefaultChannelIDStore::ChannelID("foo.com",
- base::Time::FromInternalValue(3),
- base::Time::FromInternalValue(4),
- "c",
- "d"));
-
- ScopedVector<net::DefaultChannelIDStore::ChannelID> channel_ids;
- // Replace the store effectively destroying the current one and forcing it
- // to write its 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.
- base::RunLoop().RunUntilIdle();
- store_ = new QuotaPolicyChannelIDStore(
- temp_dir_.path().Append(kTestChannelIDFilename),
- base::MessageLoopProxy::current(),
- NULL);
-
- // Reload and test for persistence
- Load(&channel_ids);
- ASSERT_EQ(2U, channel_ids.size());
- net::DefaultChannelIDStore::ChannelID* goog_channel_id;
- net::DefaultChannelIDStore::ChannelID* foo_channel_id;
- if (channel_ids[0]->server_identifier() == "google.com") {
- goog_channel_id = channel_ids[0];
- foo_channel_id = channel_ids[1];
- } else {
- goog_channel_id = channel_ids[1];
- foo_channel_id = channel_ids[0];
- }
- ASSERT_EQ("google.com", goog_channel_id->server_identifier());
- ASSERT_STREQ("a", goog_channel_id->private_key().c_str());
- ASSERT_STREQ("b", goog_channel_id->cert().c_str());
- ASSERT_EQ(1, goog_channel_id->creation_time().ToInternalValue());
- ASSERT_EQ(2, goog_channel_id->expiration_time().ToInternalValue());
- ASSERT_EQ("foo.com", foo_channel_id->server_identifier());
- ASSERT_STREQ("c", foo_channel_id->private_key().c_str());
- ASSERT_STREQ("d", foo_channel_id->cert().c_str());
- ASSERT_EQ(3, foo_channel_id->creation_time().ToInternalValue());
- ASSERT_EQ(4, foo_channel_id->expiration_time().ToInternalValue());
-
- // Now delete the cert and check persistence again.
- store_->DeleteChannelID(*channel_ids[0]);
- store_->DeleteChannelID(*channel_ids[1]);
- store_ = NULL;
- // Make sure we wait until the destructor has run.
- base::RunLoop().RunUntilIdle();
- channel_ids.clear();
- store_ = new QuotaPolicyChannelIDStore(
- temp_dir_.path().Append(kTestChannelIDFilename),
- base::MessageLoopProxy::current(),
- NULL);
-
- // Reload and check if the cert has been removed.
- Load(&channel_ids);
- ASSERT_EQ(0U, channel_ids.size());
-}
-
-// Test if data is stored as expected in the QuotaPolicy database.
-TEST_F(QuotaPolicyChannelIDStoreTest, TestPolicy) {
- store_->AddChannelID(
- net::DefaultChannelIDStore::ChannelID("foo.com",
- base::Time::FromInternalValue(3),
- base::Time::FromInternalValue(4),
- "c",
- "d"));
-
- ScopedVector<net::DefaultChannelIDStore::ChannelID> channel_ids;
- // Replace the store effectively destroying the current one and forcing it
- // to write its 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.
- base::RunLoop().RunUntilIdle();
- // Specify storage policy that makes "foo.com" session only.
- scoped_refptr<content::MockSpecialStoragePolicy> storage_policy =
- new content::MockSpecialStoragePolicy();
- storage_policy->AddSessionOnly(
- net::cookie_util::CookieOriginToURL("foo.com", true));
- // Reload store, it should still have both channel ids.
- store_ = new QuotaPolicyChannelIDStore(
- temp_dir_.path().Append(kTestChannelIDFilename),
- base::MessageLoopProxy::current(),
- storage_policy);
- Load(&channel_ids);
- ASSERT_EQ(2U, channel_ids.size());
-
- // Now close the store, and "foo.com" should be deleted according to policy.
- store_ = NULL;
- // Make sure we wait until the destructor has run.
- base::RunLoop().RunUntilIdle();
- channel_ids.clear();
- store_ = new QuotaPolicyChannelIDStore(
- temp_dir_.path().Append(kTestChannelIDFilename),
- base::MessageLoopProxy::current(),
- NULL);
-
- // Reload and check that the "foo.com" cert has been removed.
- Load(&channel_ids);
- ASSERT_EQ(1U, channel_ids.size());
- ASSERT_EQ("google.com", channel_ids[0]->server_identifier());
-}
diff --git a/chrome/browser/net/sqlite_channel_id_store.cc b/chrome/browser/net/sqlite_channel_id_store.cc
new file mode 100644
index 0000000..15bad3d
--- /dev/null
+++ b/chrome/browser/net/sqlite_channel_id_store.cc
@@ -0,0 +1,641 @@
+// Copyright 2014 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_channel_id_store.h"
+
+#include <list>
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cookies/cookie_util.h"
+#include "net/ssl/ssl_client_cert_type.h"
+#include "sql/error_delegate_util.h"
+#include "sql/meta_table.h"
+#include "sql/statement.h"
+#include "sql/transaction.h"
+#include "third_party/sqlite/sqlite3.h"
+#include "url/gurl.h"
+#include "webkit/browser/quota/special_storage_policy.h"
+
+// This class is designed to be shared between any calling threads and the
+// background task runner. It batches operations and commits them on a timer.
+class SQLiteChannelIDStore::Backend
+ : public base::RefCountedThreadSafe<SQLiteChannelIDStore::Backend> {
+ public:
+ Backend(
+ const base::FilePath& path,
+ const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
+ quota::SpecialStoragePolicy* special_storage_policy)
+ : path_(path),
+ num_pending_(0),
+ force_keep_session_state_(false),
+ background_task_runner_(background_task_runner),
+ special_storage_policy_(special_storage_policy),
+ corruption_detected_(false) {}
+
+ // Creates or loads the SQLite database.
+ void Load(const LoadedCallback& loaded_callback);
+
+ // Batch a channel ID addition.
+ void AddChannelID(
+ const net::DefaultChannelIDStore::ChannelID& channel_id);
+
+ // Batch a channel ID deletion.
+ void DeleteChannelID(
+ const net::DefaultChannelIDStore::ChannelID& channel_id);
+
+ // Commit any pending operations and close the database. This must be called
+ // before the object is destructed.
+ void Close();
+
+ void SetForceKeepSessionState();
+
+ private:
+ void LoadOnDBThread(
+ ScopedVector<net::DefaultChannelIDStore::ChannelID>* channel_ids);
+
+ friend class base::RefCountedThreadSafe<SQLiteChannelIDStore::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 {
+ CHANNEL_ID_ADD,
+ CHANNEL_ID_DELETE
+ } OperationType;
+
+ PendingOperation(
+ OperationType op,
+ const net::DefaultChannelIDStore::ChannelID& channel_id)
+ : op_(op), channel_id_(channel_id) {}
+
+ OperationType op() const { return op_; }
+ const net::DefaultChannelIDStore::ChannelID& channel_id() const {
+ return channel_id_;
+ }
+
+ private:
+ OperationType op_;
+ net::DefaultChannelIDStore::ChannelID channel_id_;
+ };
+
+ private:
+ // Batch a channel id operation (add or delete).
+ void BatchOperation(
+ PendingOperation::OperationType op,
+ const net::DefaultChannelIDStore::ChannelID& channel_id);
+ // Commit our pending operations to the database.
+ void Commit();
+ // Close() executed on the background thread.
+ void InternalBackgroundClose();
+
+ void DeleteCertificatesOnShutdown();
+
+ void DatabaseErrorCallback(int error, sql::Statement* stmt);
+ void KillDatabase();
+
+ base::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 skip clear on exit rules.
+ bool force_keep_session_state_;
+ // Guard |pending_|, |num_pending_| and |force_keep_session_state_|.
+ base::Lock lock_;
+
+ // Cache of origins we have channel IDs stored for.
+ std::set<std::string> channel_id_origins_;
+
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
+
+ scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_;
+
+ // Indicates if the kill-database callback has been scheduled.
+ bool corruption_detected_;
+
+ DISALLOW_COPY_AND_ASSIGN(Backend);
+};
+
+// Version number of the database.
+static const int kCurrentVersionNumber = 4;
+static const int kCompatibleVersionNumber = 1;
+
+namespace {
+
+// Initializes the certs table, returning true on success.
+bool InitTable(sql::Connection* db) {
+ // The table is named "origin_bound_certs" for backwards compatability before
+ // we renamed this class to SQLiteChannelIDStore. Likewise, the primary
+ // key is "origin", but now can be other things like a plain domain.
+ 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,"
+ "cert_type INTEGER,"
+ "expiration_time INTEGER,"
+ "creation_time INTEGER)"))
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+void SQLiteChannelIDStore::Backend::Load(
+ const LoadedCallback& loaded_callback) {
+ // This function should be called only once per instance.
+ DCHECK(!db_.get());
+ scoped_ptr<ScopedVector<net::DefaultChannelIDStore::ChannelID> >
+ channel_ids(new ScopedVector<net::DefaultChannelIDStore::ChannelID>());
+ ScopedVector<net::DefaultChannelIDStore::ChannelID>* channel_ids_ptr =
+ channel_ids.get();
+
+ background_task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&Backend::LoadOnDBThread, this, channel_ids_ptr),
+ base::Bind(loaded_callback, base::Passed(&channel_ids)));
+}
+
+void SQLiteChannelIDStore::Backend::LoadOnDBThread(
+ ScopedVector<net::DefaultChannelIDStore::ChannelID>* channel_ids) {
+ DCHECK(background_task_runner_->RunsTasksOnCurrentThread());
+
+ // This method should be called only once per instance.
+ DCHECK(!db_.get());
+
+ base::TimeTicks start = base::TimeTicks::Now();
+
+ // Ensure the parent directory for storing certs is created before reading
+ // from it.
+ const base::FilePath dir = path_.DirName();
+ if (!base::PathExists(dir) && !base::CreateDirectory(dir))
+ return;
+
+ int64 db_size = 0;
+ if (base::GetFileSize(path_, &db_size))
+ UMA_HISTOGRAM_COUNTS("DomainBoundCerts.DBSizeInKB", db_size / 1024 );
+
+ db_.reset(new sql::Connection);
+ db_->set_histogram_tag("DomainBoundCerts");
+
+ // Unretained to avoid a ref loop with db_.
+ db_->set_error_callback(
+ base::Bind(&SQLiteChannelIDStore::Backend::DatabaseErrorCallback,
+ base::Unretained(this)));
+
+ if (!db_->Open(path_)) {
+ NOTREACHED() << "Unable to open cert DB.";
+ if (corruption_detected_)
+ KillDatabase();
+ db_.reset();
+ return;
+ }
+
+ if (!EnsureDatabaseVersion() || !InitTable(db_.get())) {
+ NOTREACHED() << "Unable to open cert DB.";
+ if (corruption_detected_)
+ KillDatabase();
+ meta_table_.Reset();
+ db_.reset();
+ return;
+ }
+
+ db_->Preload();
+
+ // Slurp all the certs into the out-vector.
+ sql::Statement smt(db_->GetUniqueStatement(
+ "SELECT origin, private_key, cert, cert_type, expiration_time, "
+ "creation_time FROM origin_bound_certs"));
+ if (!smt.is_valid()) {
+ if (corruption_detected_)
+ KillDatabase();
+ meta_table_.Reset();
+ db_.reset();
+ return;
+ }
+
+ while (smt.Step()) {
+ net::SSLClientCertType type =
+ static_cast<net::SSLClientCertType>(smt.ColumnInt(3));
+ if (type != net::CLIENT_CERT_ECDSA_SIGN)
+ continue;
+ 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::DefaultChannelIDStore::ChannelID> channel_id(
+ new net::DefaultChannelIDStore::ChannelID(
+ smt.ColumnString(0), // origin
+ base::Time::FromInternalValue(smt.ColumnInt64(5)),
+ base::Time::FromInternalValue(smt.ColumnInt64(4)),
+ private_key_from_db,
+ cert_from_db));
+ channel_id_origins_.insert(channel_id->server_identifier());
+ channel_ids->push_back(channel_id.release());
+ }
+
+ UMA_HISTOGRAM_COUNTS_10000("DomainBoundCerts.DBLoadedCount",
+ channel_ids->size());
+ base::TimeDelta load_time = base::TimeTicks::Now() - start;
+ UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.DBLoadTime",
+ load_time,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(1),
+ 50);
+ DVLOG(1) << "loaded " << channel_ids->size() << " in "
+ << load_time.InMilliseconds() << " ms";
+}
+
+bool SQLiteChannelIDStore::Backend::EnsureDatabaseVersion() {
+ // Version check.
+ if (!meta_table_.Init(
+ db_.get(), kCurrentVersionNumber, kCompatibleVersionNumber)) {
+ return false;
+ }
+
+ if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
+ LOG(WARNING) << "Server bound cert database is too new.";
+ return false;
+ }
+
+ int cur_version = meta_table_.GetVersionNumber();
+ if (cur_version == 1) {
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin())
+ return false;
+ if (!db_->Execute("ALTER TABLE origin_bound_certs ADD COLUMN cert_type "
+ "INTEGER")) {
+ LOG(WARNING) << "Unable to update server bound cert database to "
+ << "version 2.";
+ return false;
+ }
+ // All certs in version 1 database are rsa_sign, which are unsupported.
+ // Just discard them all.
+ if (!db_->Execute("DELETE from origin_bound_certs")) {
+ LOG(WARNING) << "Unable to update server bound cert database to "
+ << "version 2.";
+ return false;
+ }
+ ++cur_version;
+ meta_table_.SetVersionNumber(cur_version);
+ meta_table_.SetCompatibleVersionNumber(
+ std::min(cur_version, kCompatibleVersionNumber));
+ transaction.Commit();
+ }
+
+ if (cur_version <= 3) {
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin())
+ return false;
+
+ if (cur_version == 2) {
+ if (!db_->Execute("ALTER TABLE origin_bound_certs ADD COLUMN "
+ "expiration_time INTEGER")) {
+ LOG(WARNING) << "Unable to update server bound cert database to "
+ << "version 4.";
+ return false;
+ }
+ }
+
+ if (!db_->Execute("ALTER TABLE origin_bound_certs ADD COLUMN "
+ "creation_time INTEGER")) {
+ LOG(WARNING) << "Unable to update server bound cert database to "
+ << "version 4.";
+ return false;
+ }
+
+ sql::Statement smt(db_->GetUniqueStatement(
+ "SELECT origin, cert FROM origin_bound_certs"));
+ sql::Statement update_expires_smt(db_->GetUniqueStatement(
+ "UPDATE origin_bound_certs SET expiration_time = ? WHERE origin = ?"));
+ sql::Statement update_creation_smt(db_->GetUniqueStatement(
+ "UPDATE origin_bound_certs SET creation_time = ? WHERE origin = ?"));
+ if (!smt.is_valid() ||
+ !update_expires_smt.is_valid() ||
+ !update_creation_smt.is_valid()) {
+ LOG(WARNING) << "Unable to update server bound cert database to "
+ << "version 4.";
+ return false;
+ }
+
+ while (smt.Step()) {
+ std::string origin = smt.ColumnString(0);
+ std::string cert_from_db;
+ smt.ColumnBlobAsString(1, &cert_from_db);
+ // Parse the cert and extract the real value and then update the DB.
+ scoped_refptr<net::X509Certificate> cert(
+ net::X509Certificate::CreateFromBytes(
+ cert_from_db.data(), cert_from_db.size()));
+ if (cert.get()) {
+ if (cur_version == 2) {
+ update_expires_smt.Reset(true);
+ update_expires_smt.BindInt64(0,
+ cert->valid_expiry().ToInternalValue());
+ update_expires_smt.BindString(1, origin);
+ if (!update_expires_smt.Run()) {
+ LOG(WARNING) << "Unable to update server bound cert database to "
+ << "version 4.";
+ return false;
+ }
+ }
+
+ update_creation_smt.Reset(true);
+ update_creation_smt.BindInt64(0, cert->valid_start().ToInternalValue());
+ update_creation_smt.BindString(1, origin);
+ if (!update_creation_smt.Run()) {
+ LOG(WARNING) << "Unable to update server bound cert database to "
+ << "version 4.";
+ return false;
+ }
+ } else {
+ // If there's a cert we can't parse, just leave it. It'll get replaced
+ // with a new one if we ever try to use it.
+ LOG(WARNING) << "Error parsing cert for database upgrade for origin "
+ << smt.ColumnString(0);
+ }
+ }
+
+ cur_version = 4;
+ meta_table_.SetVersionNumber(cur_version);
+ meta_table_.SetCompatibleVersionNumber(
+ std::min(cur_version, kCompatibleVersionNumber));
+ transaction.Commit();
+ }
+
+ // 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) <<
+ "Server bound cert database version " << cur_version <<
+ " is too old to handle.";
+
+ return true;
+}
+
+void SQLiteChannelIDStore::Backend::DatabaseErrorCallback(
+ int error,
+ sql::Statement* stmt) {
+ DCHECK(background_task_runner_->RunsTasksOnCurrentThread());
+
+ if (!sql::IsErrorCatastrophic(error))
+ return;
+
+ // TODO(shess): Running KillDatabase() multiple times should be
+ // safe.
+ if (corruption_detected_)
+ return;
+
+ corruption_detected_ = true;
+
+ // TODO(shess): Consider just calling RazeAndClose() immediately.
+ // db_ may not be safe to reset at this point, but RazeAndClose()
+ // would cause the stack to unwind safely with errors.
+ background_task_runner_->PostTask(FROM_HERE,
+ base::Bind(&Backend::KillDatabase, this));
+}
+
+void SQLiteChannelIDStore::Backend::KillDatabase() {
+ DCHECK(background_task_runner_->RunsTasksOnCurrentThread());
+
+ if (db_) {
+ // This Backend will now be in-memory only. In a future run the database
+ // will be recreated. Hopefully things go better then!
+ bool success = db_->RazeAndClose();
+ UMA_HISTOGRAM_BOOLEAN("DomainBoundCerts.KillDatabaseResult", success);
+ meta_table_.Reset();
+ db_.reset();
+ }
+}
+
+void SQLiteChannelIDStore::Backend::AddChannelID(
+ const net::DefaultChannelIDStore::ChannelID& channel_id) {
+ BatchOperation(PendingOperation::CHANNEL_ID_ADD, channel_id);
+}
+
+void SQLiteChannelIDStore::Backend::DeleteChannelID(
+ const net::DefaultChannelIDStore::ChannelID& channel_id) {
+ BatchOperation(PendingOperation::CHANNEL_ID_DELETE, channel_id);
+}
+
+void SQLiteChannelIDStore::Backend::BatchOperation(
+ PendingOperation::OperationType op,
+ const net::DefaultChannelIDStore::ChannelID& channel_id) {
+ // 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;
+
+ // We do a full copy of the cert here, and hopefully just here.
+ scoped_ptr<PendingOperation> po(new PendingOperation(op, channel_id));
+
+ 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.
+ background_task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&Backend::Commit, this),
+ base::TimeDelta::FromMilliseconds(kCommitIntervalMs));
+ } else if (num_pending == kCommitAfterBatchSize) {
+ // We've reached a big enough batch, fire off a commit now.
+ background_task_runner_->PostTask(FROM_HERE,
+ base::Bind(&Backend::Commit, this));
+ }
+}
+
+void SQLiteChannelIDStore::Backend::Commit() {
+ DCHECK(background_task_runner_->RunsTasksOnCurrentThread());
+
+ 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, cert_type, "
+ "expiration_time, creation_time) VALUES (?,?,?,?,?,?)"));
+ if (!add_smt.is_valid())
+ return;
+
+ sql::Statement del_smt(db_->GetCachedStatement(SQL_FROM_HERE,
+ "DELETE FROM origin_bound_certs WHERE origin=?"));
+ if (!del_smt.is_valid())
+ return;
+
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin())
+ 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::CHANNEL_ID_ADD: {
+ channel_id_origins_.insert(po->channel_id().server_identifier());
+ add_smt.Reset(true);
+ add_smt.BindString(0, po->channel_id().server_identifier());
+ const std::string& private_key = po->channel_id().private_key();
+ add_smt.BindBlob(1, private_key.data(), private_key.size());
+ const std::string& cert = po->channel_id().cert();
+ add_smt.BindBlob(2, cert.data(), cert.size());
+ add_smt.BindInt(3, net::CLIENT_CERT_ECDSA_SIGN);
+ add_smt.BindInt64(4,
+ po->channel_id().expiration_time().ToInternalValue());
+ add_smt.BindInt64(5,
+ po->channel_id().creation_time().ToInternalValue());
+ if (!add_smt.Run())
+ NOTREACHED() << "Could not add a server bound cert to the DB.";
+ break;
+ }
+ case PendingOperation::CHANNEL_ID_DELETE:
+ channel_id_origins_.erase(po->channel_id().server_identifier());
+ del_smt.Reset(true);
+ del_smt.BindString(0, po->channel_id().server_identifier());
+ if (!del_smt.Run())
+ NOTREACHED() << "Could not delete a server bound cert from the DB.";
+ break;
+
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+ transaction.Commit();
+}
+
+// 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 SQLiteChannelIDStore::Backend::Close() {
+ // Must close the backend on the background thread.
+ background_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Backend::InternalBackgroundClose, this));
+}
+
+void SQLiteChannelIDStore::Backend::InternalBackgroundClose() {
+ DCHECK(background_task_runner_->RunsTasksOnCurrentThread());
+ // Commit any pending operations
+ Commit();
+
+ if (!force_keep_session_state_ &&
+ special_storage_policy_.get() &&
+ special_storage_policy_->HasSessionOnlyOrigins()) {
+ DeleteCertificatesOnShutdown();
+ }
+
+ db_.reset();
+}
+
+void SQLiteChannelIDStore::Backend::DeleteCertificatesOnShutdown() {
+ DCHECK(background_task_runner_->RunsTasksOnCurrentThread());
+
+ if (!db_.get())
+ return;
+
+ if (channel_id_origins_.empty())
+ return;
+
+ if (!special_storage_policy_.get())
+ return;
+
+ sql::Statement del_smt(db_->GetCachedStatement(
+ SQL_FROM_HERE, "DELETE FROM origin_bound_certs WHERE origin=?"));
+ if (!del_smt.is_valid()) {
+ LOG(WARNING) << "Unable to delete certificates on shutdown.";
+ return;
+ }
+
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin()) {
+ LOG(WARNING) << "Unable to delete certificates on shutdown.";
+ return;
+ }
+
+ for (std::set<std::string>::iterator it = channel_id_origins_.begin();
+ it != channel_id_origins_.end(); ++it) {
+ const GURL url(net::cookie_util::CookieOriginToURL(*it, true));
+ if (!url.is_valid() || !special_storage_policy_->IsStorageSessionOnly(url))
+ continue;
+ del_smt.Reset(true);
+ del_smt.BindString(0, *it);
+ if (!del_smt.Run())
+ NOTREACHED() << "Could not delete a certificate from the DB.";
+ }
+
+ if (!transaction.Commit())
+ LOG(WARNING) << "Unable to delete certificates on shutdown.";
+}
+
+void SQLiteChannelIDStore::Backend::SetForceKeepSessionState() {
+ base::AutoLock locked(lock_);
+ force_keep_session_state_ = true;
+}
+
+SQLiteChannelIDStore::SQLiteChannelIDStore(
+ const base::FilePath& path,
+ const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
+ quota::SpecialStoragePolicy* special_storage_policy)
+ : backend_(new Backend(path,
+ background_task_runner,
+ special_storage_policy)) {}
+
+void SQLiteChannelIDStore::Load(
+ const LoadedCallback& loaded_callback) {
+ backend_->Load(loaded_callback);
+}
+
+void SQLiteChannelIDStore::AddChannelID(
+ const net::DefaultChannelIDStore::ChannelID& channel_id) {
+ backend_->AddChannelID(channel_id);
+}
+
+void SQLiteChannelIDStore::DeleteChannelID(
+ const net::DefaultChannelIDStore::ChannelID& channel_id) {
+ backend_->DeleteChannelID(channel_id);
+}
+
+void SQLiteChannelIDStore::SetForceKeepSessionState() {
+ backend_->SetForceKeepSessionState();
+}
+
+SQLiteChannelIDStore::~SQLiteChannelIDStore() {
+ backend_->Close();
+ // We release our reference to the Backend, though it will probably still have
+ // a reference if the background thread has not run Close() yet.
+}
diff --git a/chrome/browser/net/sqlite_channel_id_store.h b/chrome/browser/net/sqlite_channel_id_store.h
new file mode 100644
index 0000000..13fe532
--- /dev/null
+++ b/chrome/browser/net/sqlite_channel_id_store.h
@@ -0,0 +1,55 @@
+// Copyright 2014 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_CHANNEL_ID_STORE_H_
+#define CHROME_BROWSER_NET_SQLITE_CHANNEL_ID_STORE_H_
+
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "net/ssl/default_channel_id_store.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+}
+
+namespace quota {
+class SpecialStoragePolicy;
+}
+
+// Implements the net::DefaultChannelIDStore::PersistentStore interface
+// in terms of a SQLite database. For documentation about the actual member
+// functions consult the documentation of the parent class
+// |net::DefaultChannelIDStore::PersistentCertStore|.
+// If provided, a |SpecialStoragePolicy| is consulted when the SQLite database
+// is closed to decide which certificates to keep.
+class SQLiteChannelIDStore
+ : public net::DefaultChannelIDStore::PersistentStore {
+ public:
+ SQLiteChannelIDStore(
+ const base::FilePath& path,
+ const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
+ quota::SpecialStoragePolicy* special_storage_policy);
+
+ // net::DefaultChannelIDStore::PersistentStore:
+ virtual void Load(const LoadedCallback& loaded_callback) OVERRIDE;
+ virtual void AddChannelID(
+ const net::DefaultChannelIDStore::ChannelID& channel_id) OVERRIDE;
+ virtual void DeleteChannelID(
+ const net::DefaultChannelIDStore::ChannelID& channel_idx) OVERRIDE;
+ virtual void SetForceKeepSessionState() OVERRIDE;
+
+ protected:
+ virtual ~SQLiteChannelIDStore();
+
+ private:
+ class Backend;
+
+ scoped_refptr<Backend> backend_;
+
+ DISALLOW_COPY_AND_ASSIGN(SQLiteChannelIDStore);
+};
+
+#endif // CHROME_BROWSER_NET_SQLITE_CHANNEL_ID_STORE_H_
diff --git a/chrome/browser/net/sqlite_channel_id_store_unittest.cc b/chrome/browser/net/sqlite_channel_id_store_unittest.cc
new file mode 100644
index 0000000..7d2a2ca
--- /dev/null
+++ b/chrome/browser/net/sqlite_channel_id_store_unittest.cc
@@ -0,0 +1,473 @@
+// Copyright 2014 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/bind.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "chrome/browser/net/sqlite_channel_id_store.h"
+#include "chrome/common/chrome_constants.h"
+#include "content/public/test/mock_special_storage_policy.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "net/base/test_data_directory.h"
+#include "net/ssl/ssl_client_cert_type.h"
+#include "net/test/cert_test_util.h"
+#include "sql/statement.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class SQLiteChannelIDStoreTest : public testing::Test {
+ public:
+ void Load(
+ ScopedVector<net::DefaultChannelIDStore::ChannelID>* channel_ids) {
+ base::RunLoop run_loop;
+ store_->Load(base::Bind(&SQLiteChannelIDStoreTest::OnLoaded,
+ base::Unretained(this),
+ &run_loop));
+ run_loop.Run();
+ channel_ids->swap(channel_ids_);
+ channel_ids_.clear();
+ }
+
+ void OnLoaded(
+ base::RunLoop* run_loop,
+ scoped_ptr<ScopedVector<
+ net::DefaultChannelIDStore::ChannelID> > channel_ids) {
+ channel_ids_.swap(*channel_ids);
+ run_loop->Quit();
+ }
+
+ protected:
+ static void ReadTestKeyAndCert(std::string* key, std::string* cert) {
+ base::FilePath key_path = net::GetTestCertsDirectory().AppendASCII(
+ "unittest.originbound.key.der");
+ base::FilePath cert_path = net::GetTestCertsDirectory().AppendASCII(
+ "unittest.originbound.der");
+ ASSERT_TRUE(base::ReadFileToString(key_path, key));
+ ASSERT_TRUE(base::ReadFileToString(cert_path, cert));
+ }
+
+ static base::Time GetTestCertExpirationTime() {
+ // Cert expiration time from 'dumpasn1 unittest.originbound.der':
+ // GeneralizedTime 19/11/2111 02:23:45 GMT
+ // base::Time::FromUTCExploded can't generate values past 2038 on 32-bit
+ // linux, so we use the raw value here.
+ return base::Time::FromInternalValue(GG_INT64_C(16121816625000000));
+ }
+
+ static base::Time GetTestCertCreationTime() {
+ // UTCTime 13/12/2011 02:23:45 GMT
+ base::Time::Exploded exploded_time;
+ exploded_time.year = 2011;
+ exploded_time.month = 12;
+ exploded_time.day_of_week = 0; // Unused.
+ exploded_time.day_of_month = 13;
+ exploded_time.hour = 2;
+ exploded_time.minute = 23;
+ exploded_time.second = 45;
+ exploded_time.millisecond = 0;
+ return base::Time::FromUTCExploded(exploded_time);
+ }
+
+ virtual void SetUp() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ store_ = new SQLiteChannelIDStore(
+ temp_dir_.path().Append(chrome::kChannelIDFilename),
+ base::MessageLoopProxy::current(),
+ NULL);
+ ScopedVector<net::DefaultChannelIDStore::ChannelID> channel_ids;
+ Load(&channel_ids);
+ ASSERT_EQ(0u, channel_ids.size());
+ // Make sure the store gets written at least once.
+ store_->AddChannelID(
+ net::DefaultChannelIDStore::ChannelID(
+ "google.com",
+ base::Time::FromInternalValue(1),
+ base::Time::FromInternalValue(2),
+ "a", "b"));
+ }
+
+ content::TestBrowserThreadBundle thread_bundle_;
+ base::ScopedTempDir temp_dir_;
+ scoped_refptr<SQLiteChannelIDStore> store_;
+ ScopedVector<net::DefaultChannelIDStore::ChannelID> channel_ids_;
+};
+
+// Test if data is stored as expected in the SQLite database.
+TEST_F(SQLiteChannelIDStoreTest, TestPersistence) {
+ store_->AddChannelID(
+ net::DefaultChannelIDStore::ChannelID(
+ "foo.com",
+ base::Time::FromInternalValue(3),
+ base::Time::FromInternalValue(4),
+ "c", "d"));
+
+ ScopedVector<net::DefaultChannelIDStore::ChannelID> channel_ids;
+ // Replace the store effectively destroying the current one and forcing it
+ // to write its 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.
+ base::RunLoop().RunUntilIdle();
+ store_ = new SQLiteChannelIDStore(
+ temp_dir_.path().Append(chrome::kChannelIDFilename),
+ base::MessageLoopProxy::current(),
+ NULL);
+
+ // Reload and test for persistence
+ Load(&channel_ids);
+ ASSERT_EQ(2U, channel_ids.size());
+ net::DefaultChannelIDStore::ChannelID* goog_channel_id;
+ net::DefaultChannelIDStore::ChannelID* foo_channel_id;
+ if (channel_ids[0]->server_identifier() == "google.com") {
+ goog_channel_id = channel_ids[0];
+ foo_channel_id = channel_ids[1];
+ } else {
+ goog_channel_id = channel_ids[1];
+ foo_channel_id = channel_ids[0];
+ }
+ ASSERT_EQ("google.com", goog_channel_id->server_identifier());
+ ASSERT_STREQ("a", goog_channel_id->private_key().c_str());
+ ASSERT_STREQ("b", goog_channel_id->cert().c_str());
+ ASSERT_EQ(1, goog_channel_id->creation_time().ToInternalValue());
+ ASSERT_EQ(2, goog_channel_id->expiration_time().ToInternalValue());
+ ASSERT_EQ("foo.com", foo_channel_id->server_identifier());
+ ASSERT_STREQ("c", foo_channel_id->private_key().c_str());
+ ASSERT_STREQ("d", foo_channel_id->cert().c_str());
+ ASSERT_EQ(3, foo_channel_id->creation_time().ToInternalValue());
+ ASSERT_EQ(4, foo_channel_id->expiration_time().ToInternalValue());
+
+ // Now delete the cert and check persistence again.
+ store_->DeleteChannelID(*channel_ids[0]);
+ store_->DeleteChannelID(*channel_ids[1]);
+ store_ = NULL;
+ // Make sure we wait until the destructor has run.
+ base::RunLoop().RunUntilIdle();
+ channel_ids.clear();
+ store_ = new SQLiteChannelIDStore(
+ temp_dir_.path().Append(chrome::kChannelIDFilename),
+ base::MessageLoopProxy::current(),
+ NULL);
+
+ // Reload and check if the cert has been removed.
+ Load(&channel_ids);
+ ASSERT_EQ(0U, channel_ids.size());
+}
+
+TEST_F(SQLiteChannelIDStoreTest, TestUpgradeV1) {
+ // Reset the store. We'll be using a different database for this test.
+ store_ = NULL;
+
+ base::FilePath v1_db_path(temp_dir_.path().AppendASCII("v1db"));
+
+ std::string key_data;
+ std::string cert_data;
+ ReadTestKeyAndCert(&key_data, &cert_data);
+
+ // Create a version 1 database.
+ {
+ sql::Connection db;
+ ASSERT_TRUE(db.Open(v1_db_path));
+ ASSERT_TRUE(db.Execute(
+ "CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY,"
+ "value LONGVARCHAR);"
+ "INSERT INTO \"meta\" VALUES('version','1');"
+ "INSERT INTO \"meta\" VALUES('last_compatible_version','1');"
+ "CREATE TABLE origin_bound_certs ("
+ "origin TEXT NOT NULL UNIQUE PRIMARY KEY,"
+ "private_key BLOB NOT NULL,cert BLOB NOT NULL);"));
+
+ sql::Statement add_smt(db.GetUniqueStatement(
+ "INSERT INTO origin_bound_certs (origin, private_key, cert) "
+ "VALUES (?,?,?)"));
+ add_smt.BindString(0, "google.com");
+ add_smt.BindBlob(1, key_data.data(), key_data.size());
+ add_smt.BindBlob(2, cert_data.data(), cert_data.size());
+ ASSERT_TRUE(add_smt.Run());
+
+ ASSERT_TRUE(db.Execute(
+ "INSERT INTO \"origin_bound_certs\" VALUES("
+ "'foo.com',X'AA',X'BB');"
+ ));
+ }
+
+ // Load and test the DB contents twice. First time ensures that we can use
+ // the updated values immediately. Second time ensures that the updated
+ // values are stored and read correctly on next load.
+ for (int i = 0; i < 2; ++i) {
+ SCOPED_TRACE(i);
+
+ ScopedVector<net::DefaultChannelIDStore::ChannelID> channel_ids;
+ store_ = new SQLiteChannelIDStore(
+ v1_db_path, base::MessageLoopProxy::current(), NULL);
+
+ // Load the database. Because the existing v1 certs are implicitly of type
+ // RSA, which is unsupported, they're discarded.
+ Load(&channel_ids);
+ ASSERT_EQ(0U, channel_ids.size());
+
+ store_ = NULL;
+ base::RunLoop().RunUntilIdle();
+
+ // Verify the database version is updated.
+ {
+ sql::Connection db;
+ ASSERT_TRUE(db.Open(v1_db_path));
+ sql::Statement smt(db.GetUniqueStatement(
+ "SELECT value FROM meta WHERE key = \"version\""));
+ ASSERT_TRUE(smt.Step());
+ EXPECT_EQ(4, smt.ColumnInt(0));
+ EXPECT_FALSE(smt.Step());
+ }
+ }
+}
+
+TEST_F(SQLiteChannelIDStoreTest, TestUpgradeV2) {
+ // Reset the store. We'll be using a different database for this test.
+ store_ = NULL;
+
+ base::FilePath v2_db_path(temp_dir_.path().AppendASCII("v2db"));
+
+ std::string key_data;
+ std::string cert_data;
+ ReadTestKeyAndCert(&key_data, &cert_data);
+
+ // Create a version 2 database.
+ {
+ sql::Connection db;
+ ASSERT_TRUE(db.Open(v2_db_path));
+ ASSERT_TRUE(db.Execute(
+ "CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY,"
+ "value LONGVARCHAR);"
+ "INSERT INTO \"meta\" VALUES('version','2');"
+ "INSERT INTO \"meta\" VALUES('last_compatible_version','1');"
+ "CREATE TABLE origin_bound_certs ("
+ "origin TEXT NOT NULL UNIQUE PRIMARY KEY,"
+ "private_key BLOB NOT NULL,"
+ "cert BLOB NOT NULL,"
+ "cert_type INTEGER);"
+ ));
+
+ sql::Statement add_smt(db.GetUniqueStatement(
+ "INSERT INTO origin_bound_certs (origin, private_key, cert, cert_type) "
+ "VALUES (?,?,?,?)"));
+ add_smt.BindString(0, "google.com");
+ add_smt.BindBlob(1, key_data.data(), key_data.size());
+ add_smt.BindBlob(2, cert_data.data(), cert_data.size());
+ add_smt.BindInt64(3, 64);
+ ASSERT_TRUE(add_smt.Run());
+
+ ASSERT_TRUE(db.Execute(
+ "INSERT INTO \"origin_bound_certs\" VALUES("
+ "'foo.com',X'AA',X'BB',64);"
+ ));
+ }
+
+ // Load and test the DB contents twice. First time ensures that we can use
+ // the updated values immediately. Second time ensures that the updated
+ // values are saved and read correctly on next load.
+ for (int i = 0; i < 2; ++i) {
+ SCOPED_TRACE(i);
+
+ ScopedVector<net::DefaultChannelIDStore::ChannelID> channel_ids;
+ store_ = new SQLiteChannelIDStore(
+ v2_db_path, base::MessageLoopProxy::current(), NULL);
+
+ // Load the database and ensure the certs can be read.
+ Load(&channel_ids);
+ ASSERT_EQ(2U, channel_ids.size());
+
+ ASSERT_EQ("google.com", channel_ids[0]->server_identifier());
+ ASSERT_EQ(GetTestCertExpirationTime(),
+ channel_ids[0]->expiration_time());
+ ASSERT_EQ(key_data, channel_ids[0]->private_key());
+ ASSERT_EQ(cert_data, channel_ids[0]->cert());
+
+ ASSERT_EQ("foo.com", channel_ids[1]->server_identifier());
+ // Undecodable cert, expiration time will be uninitialized.
+ ASSERT_EQ(base::Time(), channel_ids[1]->expiration_time());
+ ASSERT_STREQ("\xaa", channel_ids[1]->private_key().c_str());
+ ASSERT_STREQ("\xbb", channel_ids[1]->cert().c_str());
+
+ store_ = NULL;
+ // Make sure we wait until the destructor has run.
+ base::RunLoop().RunUntilIdle();
+
+ // Verify the database version is updated.
+ {
+ sql::Connection db;
+ ASSERT_TRUE(db.Open(v2_db_path));
+ sql::Statement smt(db.GetUniqueStatement(
+ "SELECT value FROM meta WHERE key = \"version\""));
+ ASSERT_TRUE(smt.Step());
+ EXPECT_EQ(4, smt.ColumnInt(0));
+ EXPECT_FALSE(smt.Step());
+ }
+ }
+}
+
+TEST_F(SQLiteChannelIDStoreTest, TestUpgradeV3) {
+ // Reset the store. We'll be using a different database for this test.
+ store_ = NULL;
+
+ base::FilePath v3_db_path(temp_dir_.path().AppendASCII("v3db"));
+
+ std::string key_data;
+ std::string cert_data;
+ ReadTestKeyAndCert(&key_data, &cert_data);
+
+ // Create a version 3 database.
+ {
+ sql::Connection db;
+ ASSERT_TRUE(db.Open(v3_db_path));
+ ASSERT_TRUE(db.Execute(
+ "CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY,"
+ "value LONGVARCHAR);"
+ "INSERT INTO \"meta\" VALUES('version','3');"
+ "INSERT INTO \"meta\" VALUES('last_compatible_version','1');"
+ "CREATE TABLE origin_bound_certs ("
+ "origin TEXT NOT NULL UNIQUE PRIMARY KEY,"
+ "private_key BLOB NOT NULL,"
+ "cert BLOB NOT NULL,"
+ "cert_type INTEGER,"
+ "expiration_time INTEGER);"
+ ));
+
+ sql::Statement add_smt(db.GetUniqueStatement(
+ "INSERT INTO origin_bound_certs (origin, private_key, cert, cert_type, "
+ "expiration_time) VALUES (?,?,?,?,?)"));
+ add_smt.BindString(0, "google.com");
+ add_smt.BindBlob(1, key_data.data(), key_data.size());
+ add_smt.BindBlob(2, cert_data.data(), cert_data.size());
+ add_smt.BindInt64(3, 64);
+ add_smt.BindInt64(4, 1000);
+ ASSERT_TRUE(add_smt.Run());
+
+ ASSERT_TRUE(db.Execute(
+ "INSERT INTO \"origin_bound_certs\" VALUES("
+ "'foo.com',X'AA',X'BB',64,2000);"
+ ));
+ }
+
+ // Load and test the DB contents twice. First time ensures that we can use
+ // the updated values immediately. Second time ensures that the updated
+ // values are saved and read correctly on next load.
+ for (int i = 0; i < 2; ++i) {
+ SCOPED_TRACE(i);
+
+ ScopedVector<net::DefaultChannelIDStore::ChannelID> channel_ids;
+ store_ = new SQLiteChannelIDStore(
+ v3_db_path, base::MessageLoopProxy::current(), NULL);
+
+ // Load the database and ensure the certs can be read.
+ Load(&channel_ids);
+ ASSERT_EQ(2U, channel_ids.size());
+
+ ASSERT_EQ("google.com", channel_ids[0]->server_identifier());
+ ASSERT_EQ(1000, channel_ids[0]->expiration_time().ToInternalValue());
+ ASSERT_EQ(GetTestCertCreationTime(),
+ channel_ids[0]->creation_time());
+ ASSERT_EQ(key_data, channel_ids[0]->private_key());
+ ASSERT_EQ(cert_data, channel_ids[0]->cert());
+
+ ASSERT_EQ("foo.com", channel_ids[1]->server_identifier());
+ ASSERT_EQ(2000, channel_ids[1]->expiration_time().ToInternalValue());
+ // Undecodable cert, creation time will be uninitialized.
+ ASSERT_EQ(base::Time(), channel_ids[1]->creation_time());
+ ASSERT_STREQ("\xaa", channel_ids[1]->private_key().c_str());
+ ASSERT_STREQ("\xbb", channel_ids[1]->cert().c_str());
+
+ store_ = NULL;
+ // Make sure we wait until the destructor has run.
+ base::RunLoop().RunUntilIdle();
+
+ // Verify the database version is updated.
+ {
+ sql::Connection db;
+ ASSERT_TRUE(db.Open(v3_db_path));
+ sql::Statement smt(db.GetUniqueStatement(
+ "SELECT value FROM meta WHERE key = \"version\""));
+ ASSERT_TRUE(smt.Step());
+ EXPECT_EQ(4, smt.ColumnInt(0));
+ EXPECT_FALSE(smt.Step());
+ }
+ }
+}
+
+TEST_F(SQLiteChannelIDStoreTest, TestRSADiscarded) {
+ // Reset the store. We'll be using a different database for this test.
+ store_ = NULL;
+
+ base::FilePath v4_db_path(temp_dir_.path().AppendASCII("v4dbrsa"));
+
+ std::string key_data;
+ std::string cert_data;
+ ReadTestKeyAndCert(&key_data, &cert_data);
+
+ // Create a version 4 database with a mix of RSA and ECDSA certs.
+ {
+ sql::Connection db;
+ ASSERT_TRUE(db.Open(v4_db_path));
+ ASSERT_TRUE(db.Execute(
+ "CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY,"
+ "value LONGVARCHAR);"
+ "INSERT INTO \"meta\" VALUES('version','4');"
+ "INSERT INTO \"meta\" VALUES('last_compatible_version','1');"
+ "CREATE TABLE origin_bound_certs ("
+ "origin TEXT NOT NULL UNIQUE PRIMARY KEY,"
+ "private_key BLOB NOT NULL,"
+ "cert BLOB NOT NULL,"
+ "cert_type INTEGER,"
+ "expiration_time INTEGER,"
+ "creation_time INTEGER);"
+ ));
+
+ sql::Statement add_smt(db.GetUniqueStatement(
+ "INSERT INTO origin_bound_certs "
+ "(origin, private_key, cert, cert_type, expiration_time, creation_time)"
+ " VALUES (?,?,?,?,?,?)"));
+ add_smt.BindString(0, "google.com");
+ add_smt.BindBlob(1, key_data.data(), key_data.size());
+ add_smt.BindBlob(2, cert_data.data(), cert_data.size());
+ add_smt.BindInt64(3, 64);
+ add_smt.BindInt64(4, GetTestCertExpirationTime().ToInternalValue());
+ add_smt.BindInt64(5, base::Time::Now().ToInternalValue());
+ ASSERT_TRUE(add_smt.Run());
+
+ add_smt.Clear();
+ add_smt.Assign(db.GetUniqueStatement(
+ "INSERT INTO origin_bound_certs "
+ "(origin, private_key, cert, cert_type, expiration_time, creation_time)"
+ " VALUES (?,?,?,?,?,?)"));
+ add_smt.BindString(0, "foo.com");
+ add_smt.BindBlob(1, key_data.data(), key_data.size());
+ add_smt.BindBlob(2, cert_data.data(), cert_data.size());
+ add_smt.BindInt64(3, 1);
+ add_smt.BindInt64(4, GetTestCertExpirationTime().ToInternalValue());
+ add_smt.BindInt64(5, base::Time::Now().ToInternalValue());
+ ASSERT_TRUE(add_smt.Run());
+ }
+
+ ScopedVector<net::DefaultChannelIDStore::ChannelID> channel_ids;
+ store_ = new SQLiteChannelIDStore(
+ v4_db_path, base::MessageLoopProxy::current(), NULL);
+
+ // Load the database and ensure the certs can be read.
+ Load(&channel_ids);
+ // Only the ECDSA cert (for google.com) is read, the RSA one is discarded.
+ ASSERT_EQ(1U, channel_ids.size());
+
+ ASSERT_EQ("google.com", channel_ids[0]->server_identifier());
+ ASSERT_EQ(GetTestCertExpirationTime(),
+ channel_ids[0]->expiration_time());
+ ASSERT_EQ(key_data, channel_ids[0]->private_key());
+ ASSERT_EQ(cert_data, channel_ids[0]->cert());
+
+ store_ = NULL;
+ // Make sure we wait until the destructor has run.
+ base::RunLoop().RunUntilIdle();
+}
diff --git a/chrome/browser/profiles/profile_impl_io_data.cc b/chrome/browser/profiles/profile_impl_io_data.cc
index 2e98ff7..a53d195 100644
--- a/chrome/browser/profiles/profile_impl_io_data.cc
+++ b/chrome/browser/profiles/profile_impl_io_data.cc
@@ -26,8 +26,8 @@
#include "chrome/browser/net/cookie_store_util.h"
#include "chrome/browser/net/http_server_properties_manager_factory.h"
#include "chrome/browser/net/predictor.h"
-#include "chrome/browser/net/quota_policy_channel_id_store.h"
#include "chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_configurator.h"
+#include "chrome/browser/net/sqlite_channel_id_store.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
@@ -515,8 +515,8 @@ void ProfileImplIOData::InitializeInternal(
if (!channel_id_service) {
DCHECK(!lazy_params_->channel_id_path.empty());
- scoped_refptr<QuotaPolicyChannelIDStore> channel_id_db =
- new QuotaPolicyChannelIDStore(
+ scoped_refptr<SQLiteChannelIDStore> channel_id_db =
+ new SQLiteChannelIDStore(
lazy_params_->channel_id_path,
BrowserThread::GetBlockingPool()->GetSequencedTaskRunner(
BrowserThread::GetBlockingPool()->GetSequenceToken()),