diff options
author | miu@chromium.org <miu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-08-15 23:02:30 +0000 |
---|---|---|
committer | miu@chromium.org <miu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-08-15 23:03:33 +0000 |
commit | 8faaaa1db317847005281e04eaa3a44909a4f097 (patch) | |
tree | 636edef2aacada9eb6d15f58c851b008602a26e6 /chrome/browser | |
parent | 9a341ca5ce10ddcdca8b750df56bc2e9eb46ba4e (diff) | |
download | chromium_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.cc | 79 | ||||
-rw-r--r-- | chrome/browser/net/quota_policy_channel_id_store.h | 67 | ||||
-rw-r--r-- | chrome/browser/net/quota_policy_channel_id_store_unittest.cc | 203 | ||||
-rw-r--r-- | chrome/browser/net/sqlite_channel_id_store.cc | 641 | ||||
-rw-r--r-- | chrome/browser/net/sqlite_channel_id_store.h | 55 | ||||
-rw-r--r-- | chrome/browser/net/sqlite_channel_id_store_unittest.cc | 473 | ||||
-rw-r--r-- | chrome/browser/profiles/profile_impl_io_data.cc | 6 |
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()), |