blob: 40a5932c4d67d501fde69f4ca4da8bbbb18b175f [file] [log] [blame]
// 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 "net/ssl/default_channel_id_store.h"
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "crypto/ec_private_key.h"
#include "net/base/net_errors.h"
#include "net/test/channel_id_test_util.h"
#include "net/test/gtest_util.h"
#include "net/test/test_with_scoped_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using net::test::IsError;
using net::test::IsOk;
namespace net {
namespace {
void CallCounter(int* counter) {
(*counter)++;
}
void GetChannelIDCallbackNotCalled(
int err,
const std::string& server_identifier,
std::unique_ptr<crypto::ECPrivateKey> key_result) {
ADD_FAILURE() << "Unexpected callback execution.";
}
class AsyncGetChannelIDHelper {
public:
AsyncGetChannelIDHelper() : called_(false) {}
void Callback(int err,
const std::string& server_identifier,
std::unique_ptr<crypto::ECPrivateKey> key_result) {
err_ = err;
server_identifier_ = server_identifier;
key_ = std::move(key_result);
called_ = true;
}
int err_;
std::string server_identifier_;
std::unique_ptr<crypto::ECPrivateKey> key_;
bool called_;
};
void GetAllCallback(
ChannelIDStore::ChannelIDList* dest,
const ChannelIDStore::ChannelIDList& result) {
*dest = result;
}
class MockPersistentStore
: public DefaultChannelIDStore::PersistentStore {
public:
MockPersistentStore();
// DefaultChannelIDStore::PersistentStore implementation.
void Load(const LoadedCallback& loaded_callback) override;
void AddChannelID(
const DefaultChannelIDStore::ChannelID& channel_id) override;
void DeleteChannelID(
const DefaultChannelIDStore::ChannelID& channel_id) override;
void SetForceKeepSessionState() override;
void Flush() override;
protected:
~MockPersistentStore() override;
private:
typedef std::map<std::string, DefaultChannelIDStore::ChannelID>
ChannelIDMap;
ChannelIDMap channel_ids_;
};
MockPersistentStore::MockPersistentStore() = default;
void MockPersistentStore::Load(const LoadedCallback& loaded_callback) {
std::unique_ptr<
std::vector<std::unique_ptr<DefaultChannelIDStore::ChannelID>>>
channel_ids(
new std::vector<std::unique_ptr<DefaultChannelIDStore::ChannelID>>());
ChannelIDMap::iterator it;
for (it = channel_ids_.begin(); it != channel_ids_.end(); ++it) {
channel_ids->push_back(
std::make_unique<DefaultChannelIDStore::ChannelID>(it->second));
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::Bind(loaded_callback, base::Passed(&channel_ids)));
}
void MockPersistentStore::AddChannelID(
const DefaultChannelIDStore::ChannelID& channel_id) {
channel_ids_[channel_id.server_identifier()] = channel_id;
}
void MockPersistentStore::DeleteChannelID(
const DefaultChannelIDStore::ChannelID& channel_id) {
channel_ids_.erase(channel_id.server_identifier());
}
void MockPersistentStore::SetForceKeepSessionState() {}
void MockPersistentStore::Flush() {}
MockPersistentStore::~MockPersistentStore() = default;
bool DomainEquals(const std::string& domain1, const std::string& domain2) {
return domain1 == domain2;
}
bool DomainNotEquals(const std::string& domain1, const std::string& domain2) {
return !DomainEquals(domain1, domain2);
}
} // namespace
using DefaultChannelIDStoreTest = TestWithScopedTaskEnvironment;
TEST_F(DefaultChannelIDStoreTest, TestLoading) {
scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
persistent_store->AddChannelID(DefaultChannelIDStore::ChannelID(
"google.com", base::Time(), crypto::ECPrivateKey::Create()));
persistent_store->AddChannelID(DefaultChannelIDStore::ChannelID(
"verisign.com", base::Time(), crypto::ECPrivateKey::Create()));
// Make sure channel_ids load properly.
DefaultChannelIDStore store(persistent_store.get());
// Load has not occurred yet.
EXPECT_EQ(0, store.GetChannelIDCount());
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"verisign.com", base::Time(), crypto::ECPrivateKey::Create()));
// Wait for load & queued set task.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(2, store.GetChannelIDCount());
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"twitter.com", base::Time(), crypto::ECPrivateKey::Create()));
// Set should be synchronous now that load is done.
EXPECT_EQ(3, store.GetChannelIDCount());
}
//TODO(mattm): add more tests of without a persistent store?
TEST_F(DefaultChannelIDStoreTest, TestSettingAndGetting) {
// No persistent store, all calls will be synchronous.
DefaultChannelIDStore store(NULL);
std::unique_ptr<crypto::ECPrivateKey> expected_key(
crypto::ECPrivateKey::Create());
std::unique_ptr<crypto::ECPrivateKey> key;
EXPECT_EQ(0, store.GetChannelIDCount());
EXPECT_EQ(ERR_FILE_NOT_FOUND,
store.GetChannelID("verisign.com", &key,
base::Bind(&GetChannelIDCallbackNotCalled)));
EXPECT_FALSE(key);
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"verisign.com", base::Time::FromInternalValue(123),
expected_key->Copy()));
EXPECT_EQ(OK, store.GetChannelID("verisign.com", &key,
base::Bind(&GetChannelIDCallbackNotCalled)));
EXPECT_TRUE(KeysEqual(expected_key.get(), key.get()));
}
TEST_F(DefaultChannelIDStoreTest, TestDuplicateChannelIds) {
scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
DefaultChannelIDStore store(persistent_store.get());
std::unique_ptr<crypto::ECPrivateKey> expected_key(
crypto::ECPrivateKey::Create());
std::unique_ptr<crypto::ECPrivateKey> key;
EXPECT_EQ(0, store.GetChannelIDCount());
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"verisign.com", base::Time::FromInternalValue(123),
crypto::ECPrivateKey::Create()));
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"verisign.com", base::Time::FromInternalValue(456),
expected_key->Copy()));
// Wait for load & queued set tasks.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, store.GetChannelIDCount());
EXPECT_EQ(OK, store.GetChannelID("verisign.com", &key,
base::Bind(&GetChannelIDCallbackNotCalled)));
EXPECT_TRUE(KeysEqual(expected_key.get(), key.get()));
}
TEST_F(DefaultChannelIDStoreTest, TestAsyncGet) {
scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
std::unique_ptr<crypto::ECPrivateKey> expected_key(
crypto::ECPrivateKey::Create());
persistent_store->AddChannelID(ChannelIDStore::ChannelID(
"verisign.com", base::Time::FromInternalValue(123),
expected_key->Copy()));
DefaultChannelIDStore store(persistent_store.get());
AsyncGetChannelIDHelper helper;
std::unique_ptr<crypto::ECPrivateKey> key;
EXPECT_EQ(0, store.GetChannelIDCount());
EXPECT_EQ(ERR_IO_PENDING,
store.GetChannelID("verisign.com", &key,
base::Bind(&AsyncGetChannelIDHelper::Callback,
base::Unretained(&helper))));
// Wait for load & queued get tasks.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, store.GetChannelIDCount());
EXPECT_FALSE(key);
EXPECT_TRUE(helper.called_);
EXPECT_THAT(helper.err_, IsOk());
EXPECT_EQ("verisign.com", helper.server_identifier_);
EXPECT_TRUE(KeysEqual(expected_key.get(), helper.key_.get()));
}
TEST_F(DefaultChannelIDStoreTest, TestDeleteAll) {
scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
DefaultChannelIDStore store(persistent_store.get());
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"verisign.com", base::Time(), crypto::ECPrivateKey::Create()));
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"google.com", base::Time(), crypto::ECPrivateKey::Create()));
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"harvard.com", base::Time(), crypto::ECPrivateKey::Create()));
// Wait for load & queued set tasks.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(3, store.GetChannelIDCount());
int delete_finished = 0;
store.DeleteAll(base::Bind(&CallCounter, &delete_finished));
ASSERT_EQ(1, delete_finished);
EXPECT_EQ(0, store.GetChannelIDCount());
}
TEST_F(DefaultChannelIDStoreTest, TestDeleteForDomains) {
scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
DefaultChannelIDStore store(persistent_store.get());
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"verisign.com", base::Time(), crypto::ECPrivateKey::Create()));
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"google.com", base::Time(), crypto::ECPrivateKey::Create()));
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"harvard.com", base::Time(), crypto::ECPrivateKey::Create()));
// Wait for load & queued set tasks.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(3, store.GetChannelIDCount());
// Whitelist deletion.
int deletions_finished = 0;
store.DeleteForDomainsCreatedBetween(
base::Bind(&DomainEquals, base::ConstRef(std::string("verisign.com"))),
base::Time(), base::Time(),
base::Bind(&CallCounter, &deletions_finished));
ASSERT_EQ(1, deletions_finished);
EXPECT_EQ(2, store.GetChannelIDCount());
ChannelIDStore::ChannelIDList channel_ids;
store.GetAllChannelIDs(base::Bind(GetAllCallback, &channel_ids));
EXPECT_EQ("google.com", channel_ids.begin()->server_identifier());
EXPECT_EQ("harvard.com", channel_ids.back().server_identifier());
// Blacklist deletion.
store.DeleteForDomainsCreatedBetween(
base::Bind(&DomainNotEquals, base::ConstRef(std::string("google.com"))),
base::Time(), base::Time(),
base::Bind(&CallCounter, &deletions_finished));
ASSERT_EQ(2, deletions_finished);
EXPECT_EQ(1, store.GetChannelIDCount());
store.GetAllChannelIDs(base::Bind(GetAllCallback, &channel_ids));
EXPECT_EQ("google.com", channel_ids.begin()->server_identifier());
}
TEST_F(DefaultChannelIDStoreTest, TestAsyncGetAndDeleteAll) {
scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
persistent_store->AddChannelID(ChannelIDStore::ChannelID(
"verisign.com", base::Time(), crypto::ECPrivateKey::Create()));
persistent_store->AddChannelID(ChannelIDStore::ChannelID(
"google.com", base::Time(), crypto::ECPrivateKey::Create()));
ChannelIDStore::ChannelIDList pre_channel_ids;
ChannelIDStore::ChannelIDList post_channel_ids;
int delete_finished = 0;
DefaultChannelIDStore store(persistent_store.get());
store.GetAllChannelIDs(base::Bind(GetAllCallback, &pre_channel_ids));
store.DeleteAll(base::Bind(&CallCounter, &delete_finished));
store.GetAllChannelIDs(base::Bind(GetAllCallback, &post_channel_ids));
// Tasks have not run yet.
EXPECT_EQ(0u, pre_channel_ids.size());
// Wait for load & queued tasks.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, store.GetChannelIDCount());
EXPECT_EQ(2u, pre_channel_ids.size());
EXPECT_EQ(0u, post_channel_ids.size());
}
TEST_F(DefaultChannelIDStoreTest, TestDelete) {
scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
DefaultChannelIDStore store(persistent_store.get());
std::unique_ptr<crypto::ECPrivateKey> key;
EXPECT_EQ(0, store.GetChannelIDCount());
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"verisign.com", base::Time(), crypto::ECPrivateKey::Create()));
// Wait for load & queued set task.
base::RunLoop().RunUntilIdle();
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"google.com", base::Time(), crypto::ECPrivateKey::Create()));
EXPECT_EQ(2, store.GetChannelIDCount());
int delete_finished = 0;
store.DeleteChannelID("verisign.com",
base::Bind(&CallCounter, &delete_finished));
ASSERT_EQ(1, delete_finished);
EXPECT_EQ(1, store.GetChannelIDCount());
EXPECT_EQ(ERR_FILE_NOT_FOUND,
store.GetChannelID("verisign.com", &key,
base::Bind(&GetChannelIDCallbackNotCalled)));
EXPECT_EQ(OK, store.GetChannelID("google.com", &key,
base::Bind(&GetChannelIDCallbackNotCalled)));
int delete2_finished = 0;
store.DeleteChannelID("google.com",
base::Bind(&CallCounter, &delete2_finished));
ASSERT_EQ(1, delete2_finished);
EXPECT_EQ(0, store.GetChannelIDCount());
EXPECT_EQ(ERR_FILE_NOT_FOUND,
store.GetChannelID("google.com", &key,
base::Bind(&GetChannelIDCallbackNotCalled)));
}
TEST_F(DefaultChannelIDStoreTest, TestAsyncDelete) {
scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
std::unique_ptr<crypto::ECPrivateKey> expected_key(
crypto::ECPrivateKey::Create());
persistent_store->AddChannelID(
ChannelIDStore::ChannelID("a.com", base::Time::FromInternalValue(1),
crypto::ECPrivateKey::Create()));
persistent_store->AddChannelID(ChannelIDStore::ChannelID(
"b.com", base::Time::FromInternalValue(3), expected_key->Copy()));
DefaultChannelIDStore store(persistent_store.get());
int delete_finished = 0;
store.DeleteChannelID("a.com",
base::Bind(&CallCounter, &delete_finished));
AsyncGetChannelIDHelper a_helper;
AsyncGetChannelIDHelper b_helper;
std::unique_ptr<crypto::ECPrivateKey> key;
EXPECT_EQ(0, store.GetChannelIDCount());
EXPECT_EQ(ERR_IO_PENDING,
store.GetChannelID("a.com", &key,
base::Bind(&AsyncGetChannelIDHelper::Callback,
base::Unretained(&a_helper))));
EXPECT_EQ(ERR_IO_PENDING,
store.GetChannelID("b.com", &key,
base::Bind(&AsyncGetChannelIDHelper::Callback,
base::Unretained(&b_helper))));
EXPECT_EQ(0, delete_finished);
EXPECT_FALSE(a_helper.called_);
EXPECT_FALSE(b_helper.called_);
// Wait for load & queued tasks.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, delete_finished);
EXPECT_EQ(1, store.GetChannelIDCount());
EXPECT_FALSE(key);
EXPECT_TRUE(a_helper.called_);
EXPECT_THAT(a_helper.err_, IsError(ERR_FILE_NOT_FOUND));
EXPECT_EQ("a.com", a_helper.server_identifier_);
EXPECT_FALSE(a_helper.key_);
EXPECT_TRUE(b_helper.called_);
EXPECT_THAT(b_helper.err_, IsOk());
EXPECT_EQ("b.com", b_helper.server_identifier_);
EXPECT_TRUE(KeysEqual(expected_key.get(), b_helper.key_.get()));
}
TEST_F(DefaultChannelIDStoreTest, TestGetAll) {
scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
DefaultChannelIDStore store(persistent_store.get());
EXPECT_EQ(0, store.GetChannelIDCount());
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"verisign.com", base::Time(), crypto::ECPrivateKey::Create()));
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"google.com", base::Time(), crypto::ECPrivateKey::Create()));
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"harvard.com", base::Time(), crypto::ECPrivateKey::Create()));
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"mit.com", base::Time(), crypto::ECPrivateKey::Create()));
// Wait for load & queued set tasks.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(4, store.GetChannelIDCount());
ChannelIDStore::ChannelIDList channel_ids;
store.GetAllChannelIDs(base::Bind(GetAllCallback, &channel_ids));
EXPECT_EQ(4u, channel_ids.size());
}
TEST_F(DefaultChannelIDStoreTest, TestInitializeFrom) {
scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
DefaultChannelIDStore store(persistent_store.get());
std::unique_ptr<crypto::ECPrivateKey> preexisting_key(
crypto::ECPrivateKey::Create());
std::unique_ptr<crypto::ECPrivateKey> both_key(
crypto::ECPrivateKey::Create());
std::unique_ptr<crypto::ECPrivateKey> copied_key(
crypto::ECPrivateKey::Create());
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"preexisting.com", base::Time(), preexisting_key->Copy()));
store.SetChannelID(std::make_unique<ChannelIDStore::ChannelID>(
"both.com", base::Time(), crypto::ECPrivateKey::Create()));
// Wait for load & queued set tasks.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(2, store.GetChannelIDCount());
ChannelIDStore::ChannelIDList source_channel_ids;
source_channel_ids.push_back(ChannelIDStore::ChannelID(
"both.com", base::Time(),
// Key differs from above to test that existing entries are overwritten.
both_key->Copy()));
source_channel_ids.push_back(ChannelIDStore::ChannelID(
"copied.com", base::Time(), copied_key->Copy()));
store.InitializeFrom(source_channel_ids);
EXPECT_EQ(3, store.GetChannelIDCount());
ChannelIDStore::ChannelIDList channel_ids;
store.GetAllChannelIDs(base::Bind(GetAllCallback, &channel_ids));
ASSERT_EQ(3u, channel_ids.size());
auto channel_id = channel_ids.begin();
EXPECT_EQ("both.com", channel_id->server_identifier());
EXPECT_TRUE(KeysEqual(both_key.get(), channel_id->key()));
++channel_id;
EXPECT_EQ("copied.com", channel_id->server_identifier());
EXPECT_TRUE(KeysEqual(copied_key.get(), channel_id->key()));
++channel_id;
EXPECT_EQ("preexisting.com", channel_id->server_identifier());
EXPECT_TRUE(KeysEqual(preexisting_key.get(), channel_id->key()));
}
TEST_F(DefaultChannelIDStoreTest, TestAsyncInitializeFrom) {
scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
std::unique_ptr<crypto::ECPrivateKey> preexisting_key(
crypto::ECPrivateKey::Create());
std::unique_ptr<crypto::ECPrivateKey> both_key(
crypto::ECPrivateKey::Create());
std::unique_ptr<crypto::ECPrivateKey> copied_key(
crypto::ECPrivateKey::Create());
persistent_store->AddChannelID(ChannelIDStore::ChannelID(
"preexisting.com", base::Time(), preexisting_key->Copy()));
persistent_store->AddChannelID(ChannelIDStore::ChannelID(
"both.com", base::Time(), crypto::ECPrivateKey::Create()));
DefaultChannelIDStore store(persistent_store.get());
ChannelIDStore::ChannelIDList source_channel_ids;
source_channel_ids.push_back(ChannelIDStore::ChannelID(
"both.com", base::Time(),
// Key differs from above to test that existing entries are overwritten.
both_key->Copy()));
source_channel_ids.push_back(ChannelIDStore::ChannelID(
"copied.com", base::Time(), copied_key->Copy()));
store.InitializeFrom(source_channel_ids);
EXPECT_EQ(0, store.GetChannelIDCount());
// Wait for load & queued tasks.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(3, store.GetChannelIDCount());
ChannelIDStore::ChannelIDList channel_ids;
store.GetAllChannelIDs(base::Bind(GetAllCallback, &channel_ids));
ASSERT_EQ(3u, channel_ids.size());
auto channel_id = channel_ids.begin();
EXPECT_EQ("both.com", channel_id->server_identifier());
EXPECT_TRUE(KeysEqual(both_key.get(), channel_id->key()));
++channel_id;
EXPECT_EQ("copied.com", channel_id->server_identifier());
EXPECT_TRUE(KeysEqual(copied_key.get(), channel_id->key()));
++channel_id;
EXPECT_EQ("preexisting.com", channel_id->server_identifier());
EXPECT_TRUE(KeysEqual(preexisting_key.get(), channel_id->key()));
}
} // namespace net