Verify the signature on user cloud policy downloads.

The signature on the user cloud policy blob is already verified by the session
manager on ChromeOS, but should also be verified by Chrome before storing new
policy, and after loading policy from the cache.

BUG=174015


Review URL: https://chromiumcodereview.appspot.com/12183017

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@182279 0039d316-1c4b-4281-b951-d872f2087c98
parent 221c4beb
...@@ -256,6 +256,8 @@ void BrowserPolicyConnector::InitializeUserPolicy( ...@@ -256,6 +256,8 @@ void BrowserPolicyConnector::InitializeUserPolicy(
const base::FilePath policy_dir = profile_dir.Append(kPolicyDir); const base::FilePath policy_dir = profile_dir.Append(kPolicyDir);
const base::FilePath policy_cache_file = policy_dir.Append(kPolicyCacheFile); const base::FilePath policy_cache_file = policy_dir.Append(kPolicyCacheFile);
const base::FilePath token_cache_file = policy_dir.Append(kTokenCacheFile); const base::FilePath token_cache_file = policy_dir.Append(kTokenCacheFile);
FilePath policy_key_dir;
PathService::Get(chrome::DIR_USER_POLICY_KEYS, &policy_key_dir);
if (wait_for_policy_fetch) if (wait_for_policy_fetch)
device_management_service_->ScheduleInitialization(0); device_management_service_->ScheduleInitialization(0);
...@@ -270,8 +272,9 @@ void BrowserPolicyConnector::InitializeUserPolicy( ...@@ -270,8 +272,9 @@ void BrowserPolicyConnector::InitializeUserPolicy(
} else if (!IsNonEnterpriseUser(user_name)) { } else if (!IsNonEnterpriseUser(user_name)) {
scoped_ptr<CloudPolicyStore> store( scoped_ptr<CloudPolicyStore> store(
new UserCloudPolicyStoreChromeOS( new UserCloudPolicyStoreChromeOS(
chromeos::DBusThreadManager::Get()->GetCryptohomeClient(),
chromeos::DBusThreadManager::Get()->GetSessionManagerClient(), chromeos::DBusThreadManager::Get()->GetSessionManagerClient(),
user_name, token_cache_file, policy_cache_file)); user_name, policy_key_dir, token_cache_file, policy_cache_file));
user_cloud_policy_manager_.reset( user_cloud_policy_manager_.reset(
new UserCloudPolicyManagerChromeOS(store.Pass(), new UserCloudPolicyManagerChromeOS(store.Pass(),
wait_for_policy_fetch)); wait_for_policy_fetch));
......
...@@ -37,6 +37,10 @@ ...@@ -37,6 +37,10 @@
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/login/user_manager.h" #include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/policy/user_cloud_policy_manager_chromeos.h" #include "chrome/browser/policy/user_cloud_policy_manager_chromeos.h"
#include "chrome/common/chrome_paths.h"
#include "chromeos/dbus/mock_cryptohome_client.h"
#include "chromeos/dbus/mock_dbus_thread_manager.h"
#include "chromeos/dbus/mock_session_manager_client.h"
#else #else
#include "chrome/browser/policy/user_cloud_policy_manager.h" #include "chrome/browser/policy/user_cloud_policy_manager.h"
#include "chrome/browser/policy/user_cloud_policy_manager_factory.h" #include "chrome/browser/policy/user_cloud_policy_manager_factory.h"
...@@ -44,6 +48,7 @@ ...@@ -44,6 +48,7 @@
#include "chrome/browser/signin/signin_manager_factory.h" #include "chrome/browser/signin/signin_manager_factory.h"
#endif #endif
using testing::AnyNumber;
using testing::InvokeWithoutArgs; using testing::InvokeWithoutArgs;
using testing::Mock; using testing::Mock;
using testing::_; using testing::_;
...@@ -64,6 +69,41 @@ class MockCloudPolicyClientObserver : public CloudPolicyClient::Observer { ...@@ -64,6 +69,41 @@ class MockCloudPolicyClientObserver : public CloudPolicyClient::Observer {
MOCK_METHOD1(OnClientError, void(CloudPolicyClient*)); MOCK_METHOD1(OnClientError, void(CloudPolicyClient*));
}; };
#if defined(OS_CHROMEOS)
const char kSanitizedUsername[] = "0123456789ABCDEF0123456789ABCDEF01234567";
ACTION(GetSanitizedUsername) {
MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(arg1, chromeos::DBUS_METHOD_CALL_SUCCESS, kSanitizedUsername));
}
ACTION_P(RetrieveUserPolicy, storage) {
MessageLoop::current()->PostTask(FROM_HERE, base::Bind(arg0, *storage));
}
ACTION_P2(StoreUserPolicy, storage, user_policy_key_file) {
// The session_manager stores a copy of the policy key at
// /var/run/user_policy/$hash/policy.pub. Simulate that behavior here, so
// that the policy signature can be validated.
em::PolicyFetchResponse policy;
ASSERT_TRUE(policy.ParseFromString(arg0));
if (policy.has_new_public_key()) {
ASSERT_TRUE(file_util::CreateDirectory(user_policy_key_file.DirName()));
int result = file_util::WriteFile(
user_policy_key_file,
policy.new_public_key().data(),
policy.new_public_key().size());
ASSERT_EQ(static_cast<int>(policy.new_public_key().size()), result);
}
*storage = arg0;
MessageLoop::current()->PostTask(FROM_HERE, base::Bind(arg1, true));
}
#endif
const char* GetTestUser() { const char* GetTestUser() {
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
return chromeos::UserManager::kStubUser; return chromeos::UserManager::kStubUser;
...@@ -80,14 +120,15 @@ std::string GetEmptyPolicy() { ...@@ -80,14 +120,15 @@ std::string GetEmptyPolicy() {
" \"recommended\": {}" " \"recommended\": {}"
" }," " },"
" \"managed_users\": [ \"*\" ]," " \"managed_users\": [ \"*\" ],"
" \"policy_user\": \"%s\"" " \"policy_user\": \"%s\","
" \"current_key_index\": 0"
"}"; "}";
return base::StringPrintf(kEmptyPolicy, dm_protocol::kChromeUserPolicyType, return base::StringPrintf(
GetTestUser()); kEmptyPolicy, dm_protocol::kChromeUserPolicyType, GetTestUser());
} }
std::string GetTestPolicy() { std::string GetTestPolicy(int key_version) {
const char kTestPolicy[] = const char kTestPolicy[] =
"{" "{"
" \"%s\": {" " \"%s\": {"
...@@ -101,11 +142,30 @@ std::string GetTestPolicy() { ...@@ -101,11 +142,30 @@ std::string GetTestPolicy() {
" }" " }"
" }," " },"
" \"managed_users\": [ \"*\" ]," " \"managed_users\": [ \"*\" ],"
" \"policy_user\": \"%s\"" " \"policy_user\": \"%s\","
" \"current_key_index\": %d"
"}"; "}";
return base::StringPrintf(kTestPolicy, dm_protocol::kChromeUserPolicyType, return base::StringPrintf(kTestPolicy,
GetTestUser()); dm_protocol::kChromeUserPolicyType,
GetTestUser(),
key_version);
}
void GetExpectedTestPolicy(PolicyMap* expected) {
expected->Set(key::kShowHomeButton, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
base::Value::CreateBooleanValue(true));
expected->Set(key::kMaxConnectionsPerProxy, POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_USER, base::Value::CreateIntegerValue(42));
base::ListValue list;
list.AppendString("dev.chromium.org");
list.AppendString("youtube.com");
expected->Set(
key::kURLBlacklist, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
list.DeepCopy());
expected->Set(
key::kHomepageLocation, POLICY_LEVEL_RECOMMENDED,
POLICY_SCOPE_USER, base::Value::CreateStringValue("google.com"));
} }
} // namespace } // namespace
...@@ -127,13 +187,31 @@ class CloudPolicyTest : public InProcessBrowserTest { ...@@ -127,13 +187,31 @@ class CloudPolicyTest : public InProcessBrowserTest {
new net::TestServer( new net::TestServer(
net::TestServer::TYPE_HTTP, net::TestServer::TYPE_HTTP,
net::TestServer::kLocalhost, net::TestServer::kLocalhost,
temp_dir_.path().BaseName())); testserver_relative_docroot()));
ASSERT_TRUE(test_server_->Start()); ASSERT_TRUE(test_server_->Start());
std::string url = test_server_->GetURL("device_management").spec(); std::string url = test_server_->GetURL("device_management").spec();
CommandLine* command_line = CommandLine::ForCurrentProcess(); CommandLine* command_line = CommandLine::ForCurrentProcess();
command_line->AppendSwitchASCII(switches::kDeviceManagementUrl, url); command_line->AppendSwitchASCII(switches::kDeviceManagementUrl, url);
#if defined(OS_CHROMEOS)
PathService::Override(chrome::DIR_USER_POLICY_KEYS, user_policy_key_dir());
mock_dbus_thread_manager_ = new chromeos::MockDBusThreadManager();
chromeos::DBusThreadManager::InitializeForTesting(
mock_dbus_thread_manager_);
EXPECT_CALL(*mock_dbus_thread_manager_->mock_cryptohome_client(),
GetSanitizedUsername(_, _))
.WillRepeatedly(GetSanitizedUsername());
EXPECT_CALL(*mock_dbus_thread_manager_->mock_session_manager_client(),
StoreUserPolicy(_, _))
.WillRepeatedly(StoreUserPolicy(&session_manager_user_policy_,
user_policy_key_file()));
EXPECT_CALL(*mock_dbus_thread_manager_->mock_session_manager_client(),
RetrieveUserPolicy(_))
.WillRepeatedly(RetrieveUserPolicy(&session_manager_user_policy_));
#endif
} }
virtual void SetUpOnMainThread() OVERRIDE { virtual void SetUpOnMainThread() OVERRIDE {
...@@ -190,21 +268,48 @@ class CloudPolicyTest : public InProcessBrowserTest { ...@@ -190,21 +268,48 @@ class CloudPolicyTest : public InProcessBrowserTest {
policy_manager->core()->client()->RemoveObserver(&observer); policy_manager->core()->client()->RemoveObserver(&observer);
} }
FilePath testserver_relative_docroot() {
return temp_dir_.path().BaseName().AppendASCII("testserver");
}
FilePath testserver_device_management_file() {
return temp_dir_.path().AppendASCII("testserver")
.AppendASCII("device_management");
}
#if defined(OS_CHROMEOS)
FilePath user_policy_key_dir() {
return temp_dir_.path().AppendASCII("user_policy");
}
FilePath user_policy_key_file() {
return user_policy_key_dir().AppendASCII(kSanitizedUsername)
.AppendASCII("policy.pub");
}
#endif
void SetServerPolicy(const std::string& policy) { void SetServerPolicy(const std::string& policy) {
ASSERT_TRUE(file_util::CreateDirectory(
testserver_device_management_file().DirName()));
int result = file_util::WriteFile( int result = file_util::WriteFile(
temp_dir_.path().AppendASCII("device_management"), testserver_device_management_file(), policy.data(), policy.size());
policy.data(), policy.size());
ASSERT_EQ(static_cast<int>(policy.size()), result); ASSERT_EQ(static_cast<int>(policy.size()), result);
} }
base::ScopedTempDir temp_dir_; base::ScopedTempDir temp_dir_;
scoped_ptr<net::TestServer> test_server_; scoped_ptr<net::TestServer> test_server_;
#if defined(OS_CHROMEOS)
std::string session_manager_user_policy_;
chromeos::MockDBusThreadManager* mock_dbus_thread_manager_;
#endif
}; };
IN_PROC_BROWSER_TEST_F(CloudPolicyTest, FetchPolicy) { IN_PROC_BROWSER_TEST_F(CloudPolicyTest, FetchPolicy) {
PolicyService* policy_service = browser()->profile()->GetPolicyService(); PolicyService* policy_service = browser()->profile()->GetPolicyService();
{ {
base::RunLoop run_loop; base::RunLoop run_loop;
// This does the initial fetch and stores the initial key.
policy_service->RefreshPolicies(run_loop.QuitClosure()); policy_service->RefreshPolicies(run_loop.QuitClosure());
run_loop.Run(); run_loop.Run();
} }
...@@ -213,23 +318,12 @@ IN_PROC_BROWSER_TEST_F(CloudPolicyTest, FetchPolicy) { ...@@ -213,23 +318,12 @@ IN_PROC_BROWSER_TEST_F(CloudPolicyTest, FetchPolicy) {
EXPECT_TRUE(empty.Equals(policy_service->GetPolicies( EXPECT_TRUE(empty.Equals(policy_service->GetPolicies(
PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())))); PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))));
ASSERT_NO_FATAL_FAILURE(SetServerPolicy(GetTestPolicy())); ASSERT_NO_FATAL_FAILURE(SetServerPolicy(GetTestPolicy(0)));
PolicyMap expected; PolicyMap expected;
expected.Set(key::kShowHomeButton, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, GetExpectedTestPolicy(&expected);
base::Value::CreateBooleanValue(true));
expected.Set(key::kMaxConnectionsPerProxy, POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_USER, base::Value::CreateIntegerValue(42));
base::ListValue list;
list.AppendString("dev.chromium.org");
list.AppendString("youtube.com");
expected.Set(
key::kURLBlacklist, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
list.DeepCopy());
expected.Set(
key::kHomepageLocation, POLICY_LEVEL_RECOMMENDED,
POLICY_SCOPE_USER, base::Value::CreateStringValue("google.com"));
{ {
base::RunLoop run_loop; base::RunLoop run_loop;
// This fetches the new policies, using the same key.
policy_service->RefreshPolicies(run_loop.QuitClosure()); policy_service->RefreshPolicies(run_loop.QuitClosure());
run_loop.Run(); run_loop.Run();
} }
...@@ -237,6 +331,59 @@ IN_PROC_BROWSER_TEST_F(CloudPolicyTest, FetchPolicy) { ...@@ -237,6 +331,59 @@ IN_PROC_BROWSER_TEST_F(CloudPolicyTest, FetchPolicy) {
PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())))); PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))));
} }
#if defined(OS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(CloudPolicyTest, FetchPolicyWithRotatedKey) {
PolicyService* policy_service = browser()->profile()->GetPolicyService();
{
base::RunLoop run_loop;
// This does the initial fetch and stores the initial key.
policy_service->RefreshPolicies(run_loop.QuitClosure());
run_loop.Run();
}
// Read the initial key.
std::string initial_key;
ASSERT_TRUE(
file_util::ReadFileToString(user_policy_key_file(), &initial_key));
PolicyMap empty;
EXPECT_TRUE(empty.Equals(policy_service->GetPolicies(
PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))));
// Set the new policies and a new key at the server.
ASSERT_NO_FATAL_FAILURE(SetServerPolicy(GetTestPolicy(1)));
PolicyMap expected;
GetExpectedTestPolicy(&expected);
{
base::RunLoop run_loop;
// This fetches the new policies and does a key rotation.
policy_service->RefreshPolicies(run_loop.QuitClosure());
run_loop.Run();
}
EXPECT_TRUE(expected.Equals(policy_service->GetPolicies(
PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))));
// Verify that the key was rotated.
std::string rotated_key;
ASSERT_TRUE(
file_util::ReadFileToString(user_policy_key_file(), &rotated_key));
EXPECT_NE(rotated_key, initial_key);
// Another refresh using the same key won't rotate it again.
{
base::RunLoop run_loop;
policy_service->RefreshPolicies(run_loop.QuitClosure());
run_loop.Run();
}
EXPECT_TRUE(expected.Equals(policy_service->GetPolicies(
PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))));
std::string current_key;
ASSERT_TRUE(
file_util::ReadFileToString(user_policy_key_file(), &current_key));
EXPECT_EQ(rotated_key, current_key);
}
#endif
TEST(CloudPolicyProtoTest, VerifyProtobufEquivalence) { TEST(CloudPolicyProtoTest, VerifyProtobufEquivalence) {
// There are 2 protobufs that can be used for user cloud policy: // There are 2 protobufs that can be used for user cloud policy:
// cloud_policy.proto and chrome_settings.proto. chrome_settings.proto is the // cloud_policy.proto and chrome_settings.proto. chrome_settings.proto is the
......
...@@ -4,17 +4,19 @@ ...@@ -4,17 +4,19 @@
#include "chrome/browser/policy/user_cloud_policy_store_chromeos.h" #include "chrome/browser/policy/user_cloud_policy_store_chromeos.h"
#include <string>
#include "base/bind.h" #include "base/bind.h"
#include "base/bind_helpers.h" #include "base/bind_helpers.h"
#include "base/callback.h" #include "base/callback.h"
#include "base/file_util.h" #include "base/file_util.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/stl_util.h"
#include "base/stringprintf.h"
#include "chrome/browser/policy/proto/cloud_policy.pb.h" #include "chrome/browser/policy/proto/cloud_policy.pb.h"
#include "chrome/browser/policy/proto/device_management_local.pb.h" #include "chrome/browser/policy/proto/device_management_local.pb.h"
#include "chrome/browser/policy/user_policy_disk_cache.h" #include "chrome/browser/policy/user_policy_disk_cache.h"
#include "chrome/browser/policy/user_policy_token_loader.h" #include "chrome/browser/policy/user_policy_token_loader.h"
#include "chromeos/dbus/cryptohome_client.h"
#include "chromeos/dbus/session_manager_client.h" #include "chromeos/dbus/session_manager_client.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "google_apis/gaia/gaia_auth_util.h" #include "google_apis/gaia/gaia_auth_util.h"
...@@ -24,15 +26,16 @@ namespace em = enterprise_management; ...@@ -24,15 +26,16 @@ namespace em = enterprise_management;
namespace policy { namespace policy {
namespace { namespace {
// Subdirectory in the user's profile for storing user policies.
const base::FilePath::CharType kPolicyDir[] =
FILE_PATH_LITERAL("Device Management");
// File in the above directory for stroing user policy dmtokens.
const base::FilePath::CharType kTokenCacheFile[] = FILE_PATH_LITERAL("Token");
// File in the above directory for storing user policy data.
const base::FilePath::CharType kPolicyCacheFile[] = FILE_PATH_LITERAL("Policy");
} // namespace
// Path within |user_policy_key_dir_| that contains the policy key.
// "%s" must be substituted with the sanitized username.
const base::FilePath::CharType kPolicyKeyFile[] =
FILE_PATH_LITERAL("%s/policy.pub");
// Maximum key size that will be loaded, in bytes.
const int kKeySizeLimit = 16 * 1024;
} // namespace
// Helper class for loading legacy policy caches. // Helper class for loading legacy policy caches.
class LegacyPolicyCacheLoader : public UserPolicyTokenLoader::Delegate, class LegacyPolicyCacheLoader : public UserPolicyTokenLoader::Delegate,
...@@ -147,17 +150,22 @@ CloudPolicyStore::Status LegacyPolicyCacheLoader::TranslateLoadResult( ...@@ -147,17 +150,22 @@ CloudPolicyStore::Status LegacyPolicyCacheLoader::TranslateLoadResult(
} }
UserCloudPolicyStoreChromeOS::UserCloudPolicyStoreChromeOS( UserCloudPolicyStoreChromeOS::UserCloudPolicyStoreChromeOS(
chromeos::CryptohomeClient* cryptohome_client,
chromeos::SessionManagerClient* session_manager_client, chromeos::SessionManagerClient* session_manager_client,
const std::string& username, const std::string& username,
const base::FilePath& user_policy_key_dir,
const base::FilePath& legacy_token_cache_file, const base::FilePath& legacy_token_cache_file,
const base::FilePath& legacy_policy_cache_file) const base::FilePath& legacy_policy_cache_file)
: session_manager_client_(session_manager_client), : cryptohome_client_(cryptohome_client),
session_manager_client_(session_manager_client),
username_(username), username_(username),
user_policy_key_dir_(user_policy_key_dir),
ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
legacy_cache_dir_(legacy_token_cache_file.DirName()), legacy_cache_dir_(legacy_token_cache_file.DirName()),
legacy_loader_(new LegacyPolicyCacheLoader(legacy_token_cache_file, legacy_loader_(new LegacyPolicyCacheLoader(legacy_token_cache_file,
legacy_policy_cache_file)), legacy_policy_cache_file)),
legacy_caches_loaded_(false) {} legacy_caches_loaded_(false),
policy_key_loaded_(false) {}
UserCloudPolicyStoreChromeOS::~UserCloudPolicyStoreChromeOS() {} UserCloudPolicyStoreChromeOS::~UserCloudPolicyStoreChromeOS() {}
...@@ -165,10 +173,12 @@ void UserCloudPolicyStoreChromeOS::Store( ...@@ -165,10 +173,12 @@ void UserCloudPolicyStoreChromeOS::Store(
const em::PolicyFetchResponse& policy) { const em::PolicyFetchResponse& policy) {
// Cancel all pending requests. // Cancel all pending requests.
weak_factory_.InvalidateWeakPtrs(); weak_factory_.InvalidateWeakPtrs();
Validate( scoped_ptr<em::PolicyFetchResponse> response(
scoped_ptr<em::PolicyFetchResponse>(new em::PolicyFetchResponse(policy)), new em::PolicyFetchResponse(policy));
base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyToStoreValidated, EnsurePolicyKeyLoaded(
weak_factory_.GetWeakPtr())); base::Bind(&UserCloudPolicyStoreChromeOS::ValidatePolicyForStore,
weak_factory_.GetWeakPtr(),
base::Passed(&response)));
} }
void UserCloudPolicyStoreChromeOS::Load() { void UserCloudPolicyStoreChromeOS::Load() {
...@@ -179,6 +189,61 @@ void UserCloudPolicyStoreChromeOS::Load() { ...@@ -179,6 +189,61 @@ void UserCloudPolicyStoreChromeOS::Load() {
weak_factory_.GetWeakPtr())); weak_factory_.GetWeakPtr()));
} }
void UserCloudPolicyStoreChromeOS::ValidatePolicyForStore(
scoped_ptr<em::PolicyFetchResponse> policy) {
// Create and configure a validator.
scoped_ptr<UserCloudPolicyValidator> validator =
CreateValidator(policy.Pass());
validator->ValidateUsername(username_);
if (policy_key_.empty()) {
validator->ValidateInitialKey();
} else {
const bool allow_rotation = true;
validator->ValidateSignature(policy_key_, allow_rotation);
}
// Start validation. The Validator will delete itself once validation is
// complete.
validator.release()->StartValidation(
base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyToStoreValidated,
weak_factory_.GetWeakPtr()));
}
void UserCloudPolicyStoreChromeOS::OnPolicyToStoreValidated(
UserCloudPolicyValidator* validator) {
validation_status_ = validator->status();
if (!validator->success()) {
status_ = STATUS_VALIDATION_ERROR;
NotifyStoreError();
return;
}
std::string policy_blob;
if (!validator->policy()->SerializeToString(&policy_blob)) {
status_ = STATUS_SERIALIZE_ERROR;
NotifyStoreError();
return;
}
session_manager_client_->StoreUserPolicy(
policy_blob,
base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyStored,
weak_factory_.GetWeakPtr()));
}
void UserCloudPolicyStoreChromeOS::OnPolicyStored(bool success) {
if (!success) {
status_ = STATUS_STORE_ERROR;
NotifyStoreError();
} else {
// Load the policy right after storing it, to make sure it was accepted by
// the session manager. An additional validation is performed after the
// load; reload the key for that validation too, in case it was rotated.
ReloadPolicyKey(base::Bind(&UserCloudPolicyStoreChromeOS::Load,
weak_factory_.GetWeakPtr()));
}
}
void UserCloudPolicyStoreChromeOS::OnPolicyRetrieved( void UserCloudPolicyStoreChromeOS::OnPolicyRetrieved(
const std::string& policy_blob) { const std::string& policy_blob) {
if (policy_blob.empty()) { if (policy_blob.empty()) {
...@@ -207,9 +272,26 @@ void UserCloudPolicyStoreChromeOS::OnPolicyRetrieved( ...@@ -207,9 +272,26 @@ void UserCloudPolicyStoreChromeOS::OnPolicyRetrieved(
return; return;
} }
Validate(policy.Pass(), // Load |policy_key_| to verify the loaded policy.
base::Bind(&UserCloudPolicyStoreChromeOS::OnRetrievedPolicyValidated, EnsurePolicyKeyLoaded(
weak_factory_.GetWeakPtr())); base::Bind(&UserCloudPolicyStoreChromeOS::ValidateRetrievedPolicy,
weak_factory_.GetWeakPtr(),
base::Passed(&policy)));
}
void UserCloudPolicyStoreChromeOS::ValidateRetrievedPolicy(
scoped_ptr<em::PolicyFetchResponse> policy) {
// Create and configure a validator for the loaded policy.
scoped_ptr<UserCloudPolicyValidator> validator =
CreateValidator(policy.Pass());
validator->ValidateUsername(username_);
const bool allow_rotation = false;
validator->ValidateSignature(policy_key_, allow_rotation);
// Start validation. The Validator will delete itself once validation is
// complete.
validator.release()->StartValidation(
base::Bind(&UserCloudPolicyStoreChromeOS::OnRetrievedPolicyValidated,
weak_factory_.GetWeakPtr()));
} }
void UserCloudPolicyStoreChromeOS::OnRetrievedPolicyValidated( void UserCloudPolicyStoreChromeOS::OnRetrievedPolicyValidated(
...@@ -236,56 +318,6 @@ void UserCloudPolicyStoreChromeOS::OnRetrievedPolicyValidated( ...@@ -236,56 +318,6 @@ void UserCloudPolicyStoreChromeOS::OnRetrievedPolicyValidated(
NotifyStoreLoaded(); NotifyStoreLoaded();
} }
void UserCloudPolicyStoreChromeOS::OnPolicyToStoreValidated(
UserCloudPolicyValidator* validator) {
validation_status_ = validator->status();
if (!validator->success()) {
status_ = STATUS_VALIDATION_ERROR;
NotifyStoreError();
return;
}
std::string policy_blob;
if (!validator->policy()->SerializeToString(&policy_blob)) {
status_ = STATUS_SERIALIZE_ERROR;
NotifyStoreError();
return;
}
session_manager_client_->StoreUserPolicy(
policy_blob,
base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyStored,
weak_factory_.GetWeakPtr()));
}
void UserCloudPolicyStoreChromeOS::OnPolicyStored(bool success) {
if (!success) {
status_ = STATUS_STORE_ERROR;
NotifyStoreError();
} else {
// TODO(mnissler): Once we do signature verifications, we'll have to reload
// the key at this point to account for key rotations.
Load();
}
}
void UserCloudPolicyStoreChromeOS::Validate(
scoped_ptr<em::PolicyFetchResponse> policy,
const UserCloudPolicyValidator::CompletionCallback& callback) {
// Configure the validator.
scoped_ptr<UserCloudPolicyValidator> validator =
CreateValidator(policy.Pass());
validator->ValidateUsername(username_);
// TODO(mnissler): Do a signature check here as well. The key is stored by
// session_manager in the root-owned cryptohome area, which is currently
// inaccessible to Chrome though.
// Start validation. The Validator will free itself once validation is
// complete.
validator.release()->StartValidation(callback);
}
void UserCloudPolicyStoreChromeOS::OnLegacyLoadFinished( void UserCloudPolicyStoreChromeOS::OnLegacyLoadFinished(
const std::string& dm_token, const std::string& dm_token,
const std::string& device_id, const std::string& device_id,
...@@ -293,10 +325,16 @@ void UserCloudPolicyStoreChromeOS::OnLegacyLoadFinished( ...@@ -293,10 +325,16 @@ void UserCloudPolicyStoreChromeOS::OnLegacyLoadFinished(
scoped_ptr<em::PolicyFetchResponse> policy) { scoped_ptr<em::PolicyFetchResponse> policy) {
status_ = status; status_ = status;
if (policy.get()) { if (policy.get()) {
Validate(policy.Pass(), // Create and configure a validator for the loaded legacy policy. Note that
base::Bind(&UserCloudPolicyStoreChromeOS::OnLegacyPolicyValidated, // the signature on this policy is not verified.
weak_factory_.GetWeakPtr(), scoped_ptr<UserCloudPolicyValidator> validator =
dm_token, device_id)); CreateValidator(policy.Pass());
validator->ValidateUsername(username_);
validator.release()->StartValidation(
base::Bind(&UserCloudPolicyStoreChromeOS::OnLegacyPolicyValidated,
weak_factory_.GetWeakPtr(),
dm_token,
device_id));
} else { } else {
InstallLegacyTokens(dm_token, device_id); InstallLegacyTokens(dm_token, device_id);
} }
...@@ -309,8 +347,7 @@ void UserCloudPolicyStoreChromeOS::OnLegacyPolicyValidated( ...@@ -309,8 +347,7 @@ void UserCloudPolicyStoreChromeOS::OnLegacyPolicyValidated(
validation_status_ = validator->status(); validation_status_ = validator->status();
if (validator->success()) { if (validator->success()) {
status_ = STATUS_OK; status_ = STATUS_OK;
InstallPolicy(validator->policy_data().Pass(), InstallPolicy(validator->policy_data().Pass(), validator->payload().Pass());
validator->payload().Pass());
// Clear the public key version. The public key version field would // Clear the public key version. The public key version field would
// otherwise indicate that we have key installed in the store when in fact // otherwise indicate that we have key installed in the store when in fact
...@@ -348,4 +385,77 @@ void UserCloudPolicyStoreChromeOS::RemoveLegacyCacheDir( ...@@ -348,4 +385,77 @@ void UserCloudPolicyStoreChromeOS::RemoveLegacyCacheDir(
LOG(ERROR) << "Failed to remove cache dir " << dir.value(); LOG(ERROR) << "Failed to remove cache dir " << dir.value();
} }
void UserCloudPolicyStoreChromeOS::ReloadPolicyKey(
const base::Closure& callback) {
std::vector<uint8>* key = new std::vector<uint8>();
content::BrowserThread::PostBlockingPoolTaskAndReply(
FROM_HERE,
base::Bind(&UserCloudPolicyStoreChromeOS::LoadPolicyKey,
policy_key_path_,
key),
base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyKeyReloaded,
weak_factory_.GetWeakPtr(),
base::Owned(key),
callback));
}
// static
void UserCloudPolicyStoreChromeOS::LoadPolicyKey(const base::FilePath& path,
std::vector<uint8>* key) {
if (!file_util::PathExists(path)) {
VLOG(1) << "No key at " << path.value();
return;
}
int64 size;
if (!file_util::GetFileSize(path, &size)) {
LOG(ERROR) << "Could not get size of " << path.value();
} else if (size == 0 || size > kKeySizeLimit) {
LOG(ERROR) << "Key at " << path.value() << " has bad size " << size;
} else {
key->resize(size);
int read_size = file_util::ReadFile(
path, reinterpret_cast<char*>(vector_as_array(key)), size);
if (read_size != size) {
LOG(ERROR) << "Failed to read key at " << path.value();
key->clear();
}
}
}
void UserCloudPolicyStoreChromeOS::OnPolicyKeyReloaded(
std::vector<uint8>* key,
const base::Closure& callback) {
policy_key_.swap(*key);
policy_key_loaded_ = true;
callback.Run();
}
void UserCloudPolicyStoreChromeOS::EnsurePolicyKeyLoaded(
const base::Closure& callback) {
if (policy_key_loaded_) {
callback.Run();
} else {
// Get the hashed username that's part of the key's path, to determine
// |policy_key_path_|.
cryptohome_client_->GetSanitizedUsername(username_,
base::Bind(&UserCloudPolicyStoreChromeOS::OnGetSanitizedUsername,
weak_factory_.GetWeakPtr(),
callback));
}
}
void UserCloudPolicyStoreChromeOS::OnGetSanitizedUsername(
const base::Closure& callback,
chromeos::DBusMethodCallStatus call_status,
const std::string& sanitized_username) {
// The default empty path will always yield an empty key.
if (call_status == chromeos::DBUS_METHOD_CALL_SUCCESS &&
!sanitized_username.empty()) {
policy_key_path_ = user_policy_key_dir_.Append(
base::StringPrintf(kPolicyKeyFile, sanitized_username.c_str()));
}
ReloadPolicyKey(callback);
}
} // namespace policy } // namespace policy
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#define CHROME_BROWSER_POLICY_USER_CLOUD_POLICY_STORE_CHROMEOS_H_ #define CHROME_BROWSER_POLICY_USER_CLOUD_POLICY_STORE_CHROMEOS_H_
#include <string> #include <string>
#include <vector>
#include "base/basictypes.h" #include "base/basictypes.h"
#include "base/compiler_specific.h" #include "base/compiler_specific.h"
...@@ -14,8 +15,10 @@ ...@@ -14,8 +15,10 @@
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "chrome/browser/policy/cloud_policy_validator.h" #include "chrome/browser/policy/cloud_policy_validator.h"
#include "chrome/browser/policy/user_cloud_policy_store_base.h" #include "chrome/browser/policy/user_cloud_policy_store_base.h"
#include "chromeos/dbus/dbus_method_call_status.h"
namespace chromeos { namespace chromeos {
class CryptohomeClient;
class SessionManagerClient; class SessionManagerClient;
} }
...@@ -33,8 +36,10 @@ class LegacyPolicyCacheLoader; ...@@ -33,8 +36,10 @@ class LegacyPolicyCacheLoader;
class UserCloudPolicyStoreChromeOS : public UserCloudPolicyStoreBase { class UserCloudPolicyStoreChromeOS : public UserCloudPolicyStoreBase {
public: public:
UserCloudPolicyStoreChromeOS( UserCloudPolicyStoreChromeOS(
chromeos::CryptohomeClient* cryptohome_client,
chromeos::SessionManagerClient* session_manager_client, chromeos::SessionManagerClient* session_manager_client,
const std::string& username, const std::string& username,
const base::FilePath& user_policy_key_dir,
const base::FilePath& legacy_token_cache_file, const base::FilePath& legacy_token_cache_file,
const base::FilePath& legacy_policy_cache_file); const base::FilePath& legacy_policy_cache_file);
virtual ~UserCloudPolicyStoreChromeOS(); virtual ~UserCloudPolicyStoreChromeOS();
...@@ -45,24 +50,27 @@ class UserCloudPolicyStoreChromeOS : public UserCloudPolicyStoreBase { ...@@ -45,24 +50,27 @@ class UserCloudPolicyStoreChromeOS : public UserCloudPolicyStoreBase {
virtual void Load() OVERRIDE; virtual void Load() OVERRIDE;
private: private:
// Called back from SessionManagerClient for policy load operations. // Starts validation of |policy| before storing it.
void OnPolicyRetrieved(const std::string& policy_blob); void ValidatePolicyForStore(
scoped_ptr<enterprise_management::PolicyFetchResponse> policy);
// Completion handler for policy validation on the Load() path. Installs the
// policy and publishes it if validation succeeded.
void OnRetrievedPolicyValidated(UserCloudPolicyValidator* validator);
// Completion handler for policy validation on the Load() path. Starts a store // Completion handler for policy validation on the Store() path.
// operation if the validation succeeded. // Starts a store operation if the validation succeeded.
void OnPolicyToStoreValidated(UserCloudPolicyValidator* validator); void OnPolicyToStoreValidated(UserCloudPolicyValidator* validator);
// Called back from SessionManagerClient for policy store operations. // Called back from SessionManagerClient for policy store operations.
void OnPolicyStored(bool); void OnPolicyStored(bool);
// Starts policy blob validation. // Called back from SessionManagerClient for policy load operations.
void Validate( void OnPolicyRetrieved(const std::string& policy_blob);
scoped_ptr<enterprise_management::PolicyFetchResponse> policy,
const UserCloudPolicyValidator::CompletionCallback& callback); // Starts validation of the loaded |policy| before installing it.
void ValidateRetrievedPolicy(
scoped_ptr<enterprise_management::PolicyFetchResponse> policy);
// Completion handler for policy validation on the Load() path. Installs the
// policy and publishes it if validation succeeded.
void OnRetrievedPolicyValidated(UserCloudPolicyValidator* validator);
// Callback for loading legacy caches. // Callback for loading legacy caches.
void OnLegacyLoadFinished( void OnLegacyLoadFinished(
...@@ -83,8 +91,29 @@ class UserCloudPolicyStoreChromeOS : public UserCloudPolicyStoreBase { ...@@ -83,8 +91,29 @@ class UserCloudPolicyStoreChromeOS : public UserCloudPolicyStoreBase {
// Removes the passed-in legacy cache directory. // Removes the passed-in legacy cache directory.
static void RemoveLegacyCacheDir(const base::FilePath& dir); static void RemoveLegacyCacheDir(const base::FilePath& dir);
// Invokes |callback| after reloading |policy_key_|.
void ReloadPolicyKey(const base::Closure& callback);
// Reads the contents of |path| into |key|.
static void LoadPolicyKey(const FilePath& path, std::vector<uint8>* key);
// Callback for the key reloading.
void OnPolicyKeyReloaded(std::vector<uint8>* key,
const base::Closure& callback);
// Invokes |callback| after creating |policy_key_|, if it hasn't been created
// yet; otherwise invokes |callback| immediately.
void EnsurePolicyKeyLoaded(const base::Closure& callback);
// Callback for getting the sanitized username from |cryptohome_client_|.
void OnGetSanitizedUsername(const base::Closure& callback,
chromeos::DBusMethodCallStatus call_status,
const std::string& sanitized_username);
chromeos::CryptohomeClient* cryptohome_client_;
chromeos::SessionManagerClient* session_manager_client_; chromeos::SessionManagerClient* session_manager_client_;
const std::string username_; const std::string username_;
base::FilePath user_policy_key_dir_;
base::WeakPtrFactory<UserCloudPolicyStoreChromeOS> weak_factory_; base::WeakPtrFactory<UserCloudPolicyStoreChromeOS> weak_factory_;
...@@ -94,6 +123,10 @@ class UserCloudPolicyStoreChromeOS : public UserCloudPolicyStoreBase { ...@@ -94,6 +123,10 @@ class UserCloudPolicyStoreChromeOS : public UserCloudPolicyStoreBase {
scoped_ptr<LegacyPolicyCacheLoader> legacy_loader_; scoped_ptr<LegacyPolicyCacheLoader> legacy_loader_;
bool legacy_caches_loaded_; bool legacy_caches_loaded_;
bool policy_key_loaded_;
FilePath policy_key_path_;
std::vector<uint8> policy_key_;
DISALLOW_COPY_AND_ASSIGN(UserCloudPolicyStoreChromeOS); DISALLOW_COPY_AND_ASSIGN(UserCloudPolicyStoreChromeOS);
}; };
......
...@@ -4,15 +4,21 @@ ...@@ -4,15 +4,21 @@
#include "chrome/browser/policy/user_cloud_policy_store_chromeos.h" #include "chrome/browser/policy/user_cloud_policy_store_chromeos.h"
#include <vector>
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/file_util.h" #include "base/file_util.h"
#include "base/files/scoped_temp_dir.h" #include "base/files/scoped_temp_dir.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/message_loop.h" #include "base/message_loop.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/policy/cloud_policy_constants.h" #include "chrome/browser/policy/cloud_policy_constants.h"
#include "chrome/browser/policy/mock_cloud_policy_store.h" #include "chrome/browser/policy/mock_cloud_policy_store.h"
#include "chrome/browser/policy/policy_builder.h" #include "chrome/browser/policy/policy_builder.h"
#include "chrome/browser/policy/proto/cloud_policy.pb.h" #include "chrome/browser/policy/proto/cloud_policy.pb.h"
#include "chrome/browser/policy/proto/device_management_local.pb.h" #include "chrome/browser/policy/proto/device_management_local.pb.h"
#include "chromeos/dbus/mock_cryptohome_client.h"
#include "chromeos/dbus/mock_session_manager_client.h" #include "chromeos/dbus/mock_session_manager_client.h"
#include "content/public/test/test_browser_thread.h" #include "content/public/test/test_browser_thread.h"
#include "policy/policy_constants.h" #include "policy/policy_constants.h"
...@@ -22,6 +28,7 @@ ...@@ -22,6 +28,7 @@
namespace em = enterprise_management; namespace em = enterprise_management;
using testing::AllOf; using testing::AllOf;
using testing::AnyNumber;
using testing::Eq; using testing::Eq;
using testing::Mock; using testing::Mock;
using testing::Property; using testing::Property;
...@@ -34,6 +41,13 @@ namespace { ...@@ -34,6 +41,13 @@ namespace {
const char kLegacyDeviceId[] = "legacy-device-id"; const char kLegacyDeviceId[] = "legacy-device-id";
const char kLegacyToken[] = "legacy-token"; const char kLegacyToken[] = "legacy-token";
const char kSanitizedUsername[] = "0123456789ABCDEF0123456789ABCDEF012345678";
const char kDefaultHomepage[] = "http://chromium.org";
ACTION_P2(SendSanitizedUsername, call_status, sanitized_username) {
MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(arg1, call_status, sanitized_username));
}
class UserCloudPolicyStoreChromeOSTest : public testing::Test { class UserCloudPolicyStoreChromeOSTest : public testing::Test {
protected: protected:
...@@ -43,21 +57,36 @@ class UserCloudPolicyStoreChromeOSTest : public testing::Test { ...@@ -43,21 +57,36 @@ class UserCloudPolicyStoreChromeOSTest : public testing::Test {
file_thread_(content::BrowserThread::FILE, &loop_) {} file_thread_(content::BrowserThread::FILE, &loop_) {}
virtual void SetUp() OVERRIDE { virtual void SetUp() OVERRIDE {
EXPECT_CALL(cryptohome_client_,
GetSanitizedUsername(PolicyBuilder::kFakeUsername, _))
.Times(AnyNumber())
.WillRepeatedly(
SendSanitizedUsername(chromeos::DBUS_METHOD_CALL_SUCCESS,
kSanitizedUsername));
ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir()); ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir());
store_.reset(new UserCloudPolicyStoreChromeOS(&session_manager_client_, store_.reset(new UserCloudPolicyStoreChromeOS(&cryptohome_client_,
&session_manager_client_,
PolicyBuilder::kFakeUsername, PolicyBuilder::kFakeUsername,
user_policy_dir(),
token_file(), token_file(),
policy_file())); policy_file()));
store_->AddObserver(&observer_); store_->AddObserver(&observer_);
policy_.payload().mutable_showhomebutton()->set_value(true); // Install the initial public key, so that by default the validation of
// the stored/loaded policy blob succeeds.
std::vector<uint8> public_key;
ASSERT_TRUE(policy_.signing_key()->ExportPublicKey(&public_key));
StoreUserPolicyKey(public_key);
policy_.payload().mutable_homepagelocation()->set_value(kDefaultHomepage);
policy_.Build(); policy_.Build();
} }
virtual void TearDown() OVERRIDE { virtual void TearDown() OVERRIDE {
store_->RemoveObserver(&observer_); store_->RemoveObserver(&observer_);
store_.reset(); store_.reset();
loop_.RunUntilIdle(); RunUntilIdle();
} }
// Install an expectation on |observer_| for an error code. // Install an expectation on |observer_| for an error code.
...@@ -76,22 +105,109 @@ class UserCloudPolicyStoreChromeOSTest : public testing::Test { ...@@ -76,22 +105,109 @@ class UserCloudPolicyStoreChromeOSTest : public testing::Test {
EXPECT_CALL(session_manager_client_, RetrieveUserPolicy(_)) EXPECT_CALL(session_manager_client_, RetrieveUserPolicy(_))
.WillOnce(SaveArg<0>(&retrieve_callback)); .WillOnce(SaveArg<0>(&retrieve_callback));
store_->Load(); store_->Load();
loop_.RunUntilIdle(); RunUntilIdle();
Mock::VerifyAndClearExpectations(&session_manager_client_); Mock::VerifyAndClearExpectations(&session_manager_client_);
ASSERT_FALSE(retrieve_callback.is_null()); ASSERT_FALSE(retrieve_callback.is_null());
// Run the callback. // Run the callback.
retrieve_callback.Run(response); retrieve_callback.Run(response);
loop_.RunUntilIdle(); RunUntilIdle();
} }
// Verifies that store_->policy_map() has the ShowHomeButton entry. // Verifies that store_->policy_map() has the HomepageLocation entry with
void VerifyPolicyMap() { // the |expected_value|.
void VerifyPolicyMap(const char* expected_value) {
EXPECT_EQ(1U, store_->policy_map().size()); EXPECT_EQ(1U, store_->policy_map().size());
const PolicyMap::Entry* entry = const PolicyMap::Entry* entry =
store_->policy_map().Get(key::kShowHomeButton); store_->policy_map().Get(key::kHomepageLocation);
ASSERT_TRUE(entry); ASSERT_TRUE(entry);
EXPECT_TRUE(base::FundamentalValue(true).Equals(entry->value)); EXPECT_TRUE(base::StringValue(expected_value).Equals(entry->value));
}
void StoreUserPolicyKey(const std::vector<uint8>& public_key) {
ASSERT_TRUE(file_util::CreateDirectory(user_policy_key_file().DirName()));
ASSERT_TRUE(
file_util::WriteFile(user_policy_key_file(),
reinterpret_cast<const char*>(public_key.data()),
public_key.size()));
}
// Stores the current |policy_| and verifies that it is published.
// If |new_public_key| is set then it will be persisted after storing but
// before loading the policy, so that the signature validation can succeed.
// If |previous_value| is set then a previously existing policy with that
// value will be expected; otherwise no previous policy is expected.
// If |new_value| is set then a new policy with that value is expected after
// storing the |policy_| blob.
void PerformStorePolicy(const std::vector<uint8>* new_public_key,
const char* previous_value,
const char* new_value) {
chromeos::SessionManagerClient::StorePolicyCallback store_callback;
EXPECT_CALL(session_manager_client_, StoreUserPolicy(policy_.GetBlob(), _))
.WillOnce(SaveArg<1>(&store_callback));
store_->Store(policy_.policy());
RunUntilIdle();
Mock::VerifyAndClearExpectations(&session_manager_client_);
ASSERT_FALSE(store_callback.is_null());
// The new policy shouldn't be present yet.
PolicyMap previous_policy;
EXPECT_EQ(previous_value != NULL, store_->policy() != NULL);
if (previous_value) {
previous_policy.Set(key::kHomepageLocation,
POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_USER,
base::Value::CreateStringValue(previous_value));
}
EXPECT_TRUE(previous_policy.Equals(store_->policy_map()));
EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status());
// Store the new public key so that the validation after the retrieve
// operation completes can verify the signature.
if (new_public_key)
StoreUserPolicyKey(*new_public_key);
// Let the store operation complete.
chromeos::SessionManagerClient::RetrievePolicyCallback retrieve_callback;
EXPECT_CALL(session_manager_client_, RetrieveUserPolicy(_))
.WillOnce(SaveArg<0>(&retrieve_callback));
store_callback.Run(true);
RunUntilIdle();
EXPECT_TRUE(previous_policy.Equals(store_->policy_map()));
EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status());
Mock::VerifyAndClearExpectations(&session_manager_client_);
ASSERT_FALSE(retrieve_callback.is_null());
// Finish the retrieve callback.
EXPECT_CALL(observer_, OnStoreLoaded(store_.get()));
retrieve_callback.Run(policy_.GetBlob());
RunUntilIdle();
ASSERT_TRUE(store_->policy());
EXPECT_EQ(policy_.policy_data().SerializeAsString(),
store_->policy()->SerializeAsString());
VerifyPolicyMap(new_value);
EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status());
}
void VerifyStoreHasValidationError() {
EXPECT_FALSE(store_->policy());
EXPECT_TRUE(store_->policy_map().empty());
EXPECT_EQ(CloudPolicyStore::STATUS_VALIDATION_ERROR, store_->status());
}
void RunUntilIdle() {
loop_.RunUntilIdle();
content::BrowserThread::GetBlockingPool()->FlushForTesting();
loop_.RunUntilIdle();
}
base::FilePath user_policy_dir() {
return tmp_dir_.path().AppendASCII("var_run_user_policy");
}
base::FilePath user_policy_key_file() {
return user_policy_dir().AppendASCII(kSanitizedUsername)
.AppendASCII("policy.pub");
} }
base::FilePath token_file() { base::FilePath token_file() {
...@@ -103,6 +219,7 @@ class UserCloudPolicyStoreChromeOSTest : public testing::Test { ...@@ -103,6 +219,7 @@ class UserCloudPolicyStoreChromeOSTest : public testing::Test {
} }
MessageLoop loop_; MessageLoop loop_;
chromeos::MockCryptohomeClient cryptohome_client_;
chromeos::MockSessionManagerClient session_manager_client_; chromeos::MockSessionManagerClient session_manager_client_;
UserPolicyBuilder policy_; UserPolicyBuilder policy_;
MockCloudPolicyStoreObserver observer_; MockCloudPolicyStoreObserver observer_;
...@@ -116,41 +233,31 @@ class UserCloudPolicyStoreChromeOSTest : public testing::Test { ...@@ -116,41 +233,31 @@ class UserCloudPolicyStoreChromeOSTest : public testing::Test {
DISALLOW_COPY_AND_ASSIGN(UserCloudPolicyStoreChromeOSTest); DISALLOW_COPY_AND_ASSIGN(UserCloudPolicyStoreChromeOSTest);
}; };
TEST_F(UserCloudPolicyStoreChromeOSTest, Store) { TEST_F(UserCloudPolicyStoreChromeOSTest, InitialStore) {
// Store policy. // Start without any public key to trigger the initial key checks.
chromeos::SessionManagerClient::StorePolicyCallback store_callback; ASSERT_TRUE(file_util::Delete(user_policy_key_file(), false));
EXPECT_CALL(session_manager_client_, StoreUserPolicy(policy_.GetBlob(), _)) // Make the policy blob contain a new public key.
.WillOnce(SaveArg<1>(&store_callback)); policy_.set_new_signing_key(PolicyBuilder::CreateTestNewSigningKey());
store_->Store(policy_.policy()); policy_.Build();
loop_.RunUntilIdle(); std::vector<uint8> new_public_key;
Mock::VerifyAndClearExpectations(&session_manager_client_); ASSERT_TRUE(policy_.new_signing_key()->ExportPublicKey(&new_public_key));
ASSERT_FALSE(store_callback.is_null()); ASSERT_NO_FATAL_FAILURE(
PerformStorePolicy(&new_public_key, NULL, kDefaultHomepage));
// No policy should be present yet. }
EXPECT_FALSE(store_->policy());
EXPECT_TRUE(store_->policy_map().empty());
EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status());
// Let the store operation complete. TEST_F(UserCloudPolicyStoreChromeOSTest, StoreWithExistingKey) {
chromeos::SessionManagerClient::RetrievePolicyCallback retrieve_callback; ASSERT_NO_FATAL_FAILURE(
EXPECT_CALL(session_manager_client_, RetrieveUserPolicy(_)) PerformStorePolicy(NULL, NULL, kDefaultHomepage));
.WillOnce(SaveArg<0>(&retrieve_callback)); }
store_callback.Run(true);
loop_.RunUntilIdle();
EXPECT_TRUE(store_->policy_map().empty());
EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status());
Mock::VerifyAndClearExpectations(&session_manager_client_);
ASSERT_FALSE(retrieve_callback.is_null());
// Finish the retrieve callback. TEST_F(UserCloudPolicyStoreChromeOSTest, StoreWithRotation) {
EXPECT_CALL(observer_, OnStoreLoaded(store_.get())); // Make the policy blob contain a new public key.
retrieve_callback.Run(policy_.GetBlob()); policy_.set_new_signing_key(PolicyBuilder::CreateTestNewSigningKey());
loop_.RunUntilIdle(); policy_.Build();
ASSERT_TRUE(store_->policy()); std::vector<uint8> new_public_key;
EXPECT_EQ(policy_.policy_data().SerializeAsString(), ASSERT_TRUE(policy_.new_signing_key()->ExportPublicKey(&new_public_key));
store_->policy()->SerializeAsString()); ASSERT_NO_FATAL_FAILURE(
VerifyPolicyMap(); PerformStorePolicy(&new_public_key, NULL, kDefaultHomepage));
EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status());
} }
TEST_F(UserCloudPolicyStoreChromeOSTest, StoreFail) { TEST_F(UserCloudPolicyStoreChromeOSTest, StoreFail) {
...@@ -159,14 +266,14 @@ TEST_F(UserCloudPolicyStoreChromeOSTest, StoreFail) { ...@@ -159,14 +266,14 @@ TEST_F(UserCloudPolicyStoreChromeOSTest, StoreFail) {
EXPECT_CALL(session_manager_client_, StoreUserPolicy(policy_.GetBlob(), _)) EXPECT_CALL(session_manager_client_, StoreUserPolicy(policy_.GetBlob(), _))
.WillOnce(SaveArg<1>(&store_callback)); .WillOnce(SaveArg<1>(&store_callback));
store_->Store(policy_.policy()); store_->Store(policy_.policy());
loop_.RunUntilIdle(); RunUntilIdle();
Mock::VerifyAndClearExpectations(&session_manager_client_); Mock::VerifyAndClearExpectations(&session_manager_client_);
ASSERT_FALSE(store_callback.is_null()); ASSERT_FALSE(store_callback.is_null());
// Let the store operation complete. // Let the store operation complete.
ExpectError(CloudPolicyStore::STATUS_STORE_ERROR); ExpectError(CloudPolicyStore::STATUS_STORE_ERROR);
store_callback.Run(false); store_callback.Run(false);
loop_.RunUntilIdle(); RunUntilIdle();
EXPECT_FALSE(store_->policy()); EXPECT_FALSE(store_->policy());
EXPECT_TRUE(store_->policy_map().empty()); EXPECT_TRUE(store_->policy_map().empty());
EXPECT_EQ(CloudPolicyStore::STATUS_STORE_ERROR, store_->status()); EXPECT_EQ(CloudPolicyStore::STATUS_STORE_ERROR, store_->status());
...@@ -182,7 +289,40 @@ TEST_F(UserCloudPolicyStoreChromeOSTest, StoreValidationError) { ...@@ -182,7 +289,40 @@ TEST_F(UserCloudPolicyStoreChromeOSTest, StoreValidationError) {
EXPECT_CALL(session_manager_client_, StoreUserPolicy(policy_.GetBlob(), _)) EXPECT_CALL(session_manager_client_, StoreUserPolicy(policy_.GetBlob(), _))
.Times(0); .Times(0);
store_->Store(policy_.policy()); store_->Store(policy_.policy());
loop_.RunUntilIdle(); RunUntilIdle();
Mock::VerifyAndClearExpectations(&session_manager_client_);
}
TEST_F(UserCloudPolicyStoreChromeOSTest, StoreWithoutPolicyKey) {
// Make the dbus call to cryptohome fail.
Mock::VerifyAndClearExpectations(&cryptohome_client_);
EXPECT_CALL(cryptohome_client_,
GetSanitizedUsername(PolicyBuilder::kFakeUsername, _))
.Times(AnyNumber())
.WillRepeatedly(SendSanitizedUsername(chromeos::DBUS_METHOD_CALL_FAILURE,
std::string()));
// Store policy.
chromeos::SessionManagerClient::StorePolicyCallback store_callback;
ExpectError(CloudPolicyStore::STATUS_VALIDATION_ERROR);
EXPECT_CALL(session_manager_client_, StoreUserPolicy(policy_.GetBlob(), _))
.Times(0);
store_->Store(policy_.policy());
RunUntilIdle();
Mock::VerifyAndClearExpectations(&session_manager_client_);
}
TEST_F(UserCloudPolicyStoreChromeOSTest, StoreWithInvalidSignature) {
// Break the signature.
policy_.policy().mutable_policy_data_signature()->append("garbage");
// Store policy.
chromeos::SessionManagerClient::StorePolicyCallback store_callback;
ExpectError(CloudPolicyStore::STATUS_VALIDATION_ERROR);
EXPECT_CALL(session_manager_client_, StoreUserPolicy(policy_.GetBlob(), _))
.Times(0);
store_->Store(policy_.policy());
RunUntilIdle();
Mock::VerifyAndClearExpectations(&session_manager_client_); Mock::VerifyAndClearExpectations(&session_manager_client_);
} }
...@@ -195,7 +335,7 @@ TEST_F(UserCloudPolicyStoreChromeOSTest, Load) { ...@@ -195,7 +335,7 @@ TEST_F(UserCloudPolicyStoreChromeOSTest, Load) {
ASSERT_TRUE(store_->policy()); ASSERT_TRUE(store_->policy());
EXPECT_EQ(policy_.policy_data().SerializeAsString(), EXPECT_EQ(policy_.policy_data().SerializeAsString(),
store_->policy()->SerializeAsString()); store_->policy()->SerializeAsString());
VerifyPolicyMap(); VerifyPolicyMap(kDefaultHomepage);
EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status()); EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status());
} }
...@@ -226,11 +366,23 @@ TEST_F(UserCloudPolicyStoreChromeOSTest, LoadValidationError) { ...@@ -226,11 +366,23 @@ TEST_F(UserCloudPolicyStoreChromeOSTest, LoadValidationError) {
ExpectError(CloudPolicyStore::STATUS_VALIDATION_ERROR); ExpectError(CloudPolicyStore::STATUS_VALIDATION_ERROR);
ASSERT_NO_FATAL_FAILURE(PerformPolicyLoad(policy_.GetBlob())); ASSERT_NO_FATAL_FAILURE(PerformPolicyLoad(policy_.GetBlob()));
VerifyStoreHasValidationError();
}
// Verify no policy has been installed. TEST_F(UserCloudPolicyStoreChromeOSTest, LoadNoKey) {
EXPECT_FALSE(store_->policy()); // The loaded policy can't be verified without the public key.
EXPECT_TRUE(store_->policy_map().empty()); ASSERT_TRUE(file_util::Delete(user_policy_key_file(), false));
EXPECT_EQ(CloudPolicyStore::STATUS_VALIDATION_ERROR, store_->status()); ExpectError(CloudPolicyStore::STATUS_VALIDATION_ERROR);
ASSERT_NO_FATAL_FAILURE(PerformPolicyLoad(policy_.GetBlob()));
VerifyStoreHasValidationError();
}
TEST_F(UserCloudPolicyStoreChromeOSTest, LoadInvalidSignature) {
// Break the signature.
policy_.policy().mutable_policy_data_signature()->append("garbage");
ExpectError(CloudPolicyStore::STATUS_VALIDATION_ERROR);
ASSERT_NO_FATAL_FAILURE(PerformPolicyLoad(policy_.GetBlob()));
VerifyStoreHasValidationError();
} }
TEST_F(UserCloudPolicyStoreChromeOSTest, MigrationFull) { TEST_F(UserCloudPolicyStoreChromeOSTest, MigrationFull) {
...@@ -261,9 +413,9 @@ TEST_F(UserCloudPolicyStoreChromeOSTest, MigrationFull) { ...@@ -261,9 +413,9 @@ TEST_F(UserCloudPolicyStoreChromeOSTest, MigrationFull) {
ASSERT_TRUE(store_->policy()); ASSERT_TRUE(store_->policy());
EXPECT_EQ(expected_policy_data.SerializeAsString(), EXPECT_EQ(expected_policy_data.SerializeAsString(),
store_->policy()->SerializeAsString()); store_->policy()->SerializeAsString());
VerifyPolicyMap(); VerifyPolicyMap(kDefaultHomepage);
EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status()); EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status());
}; }
TEST_F(UserCloudPolicyStoreChromeOSTest, MigrationNoToken) { TEST_F(UserCloudPolicyStoreChromeOSTest, MigrationNoToken) {
std::string data; std::string data;
...@@ -286,9 +438,9 @@ TEST_F(UserCloudPolicyStoreChromeOSTest, MigrationNoToken) { ...@@ -286,9 +438,9 @@ TEST_F(UserCloudPolicyStoreChromeOSTest, MigrationNoToken) {
ASSERT_TRUE(store_->policy()); ASSERT_TRUE(store_->policy());
EXPECT_EQ(expected_policy_data.SerializeAsString(), EXPECT_EQ(expected_policy_data.SerializeAsString(),
store_->policy()->SerializeAsString()); store_->policy()->SerializeAsString());
VerifyPolicyMap(); VerifyPolicyMap(kDefaultHomepage);
EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status()); EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status());
}; }
TEST_F(UserCloudPolicyStoreChromeOSTest, MigrationNoPolicy) { TEST_F(UserCloudPolicyStoreChromeOSTest, MigrationNoPolicy) {
std::string data; std::string data;
...@@ -312,7 +464,48 @@ TEST_F(UserCloudPolicyStoreChromeOSTest, MigrationNoPolicy) { ...@@ -312,7 +464,48 @@ TEST_F(UserCloudPolicyStoreChromeOSTest, MigrationNoPolicy) {
store_->policy()->SerializeAsString()); store_->policy()->SerializeAsString());
EXPECT_TRUE(store_->policy_map().empty()); EXPECT_TRUE(store_->policy_map().empty());
EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status()); EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status());
}; }
TEST_F(UserCloudPolicyStoreChromeOSTest, MigrationAndStoreNew) {
// Start without an existing public key.
ASSERT_TRUE(file_util::Delete(user_policy_key_file(), false));
std::string data;
em::CachedCloudPolicyResponse cached_policy;
cached_policy.mutable_cloud_policy()->CopyFrom(policy_.policy());
ASSERT_TRUE(cached_policy.SerializeToString(&data));
ASSERT_NE(-1, file_util::WriteFile(policy_file(), data.c_str(), data.size()));
EXPECT_CALL(observer_, OnStoreLoaded(store_.get()));
ASSERT_NO_FATAL_FAILURE(PerformPolicyLoad(""));
Mock::VerifyAndClearExpectations(&observer_);
// Verify the legacy cache has been loaded.
em::PolicyData expected_policy_data;
EXPECT_TRUE(expected_policy_data.ParseFromString(
cached_policy.cloud_policy().policy_data()));
expected_policy_data.clear_public_key_version();
ASSERT_TRUE(store_->policy());
EXPECT_EQ(expected_policy_data.SerializeAsString(),
store_->policy()->SerializeAsString());
VerifyPolicyMap(kDefaultHomepage);
EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status());
EXPECT_TRUE(file_util::PathExists(policy_file()));
// Now store a new policy using the new homepage location.
const char kNewHomepage[] = "http://google.com";
policy_.payload().mutable_homepagelocation()->set_value(kNewHomepage);
policy_.set_new_signing_key(PolicyBuilder::CreateTestNewSigningKey());
policy_.Build();
std::vector<uint8> new_public_key;
ASSERT_TRUE(policy_.new_signing_key()->ExportPublicKey(&new_public_key));
ASSERT_NO_FATAL_FAILURE(
PerformStorePolicy(&new_public_key, kDefaultHomepage, kNewHomepage));
VerifyPolicyMap(kNewHomepage);
// Verify that the legacy cache has been removed.
EXPECT_FALSE(file_util::PathExists(policy_file()));
}
} // namespace } // namespace
......
...@@ -93,12 +93,17 @@ const char kFilepathSinglePrefExtensions[] = ...@@ -93,12 +93,17 @@ const char kFilepathSinglePrefExtensions[] =
#endif // defined(OS_LINUX) #endif // defined(OS_LINUX)
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
const char kDefaultAppOrderFileName[] = const char kDefaultAppOrderFileName[] =
#if defined(GOOGLE_CHROME_BUILD) #if defined(GOOGLE_CHROME_BUILD)
FILE_PATH_LITERAL("/usr/share/google-chrome/default_app_order.json"); FILE_PATH_LITERAL("/usr/share/google-chrome/default_app_order.json");
#else #else
FILE_PATH_LITERAL("/usr/share/chromium/default_app_order.json"); FILE_PATH_LITERAL("/usr/share/chromium/default_app_order.json");
#endif // defined(GOOGLE_CHROME_BUILD) #endif // defined(GOOGLE_CHROME_BUILD)
const FilePath::CharType kDefaultUserPolicyKeysDir[] =
FILE_PATH_LITERAL("/var/run/user_policy");
#endif // defined(OS_CHROMEOS) #endif // defined(OS_CHROMEOS)
} // namespace } // namespace
...@@ -405,6 +410,9 @@ bool PathProvider(int key, base::FilePath* result) { ...@@ -405,6 +410,9 @@ bool PathProvider(int key, base::FilePath* result) {
case chrome::FILE_DEFAULT_APP_ORDER: case chrome::FILE_DEFAULT_APP_ORDER:
cur = base::FilePath(FILE_PATH_LITERAL(kDefaultAppOrderFileName)); cur = base::FilePath(FILE_PATH_LITERAL(kDefaultAppOrderFileName));
break; break;
case chrome::DIR_USER_POLICY_KEYS:
cur = FilePath(kDefaultUserPolicyKeysDir);
break;
#endif #endif
// The following are only valid in the development environment, and // The following are only valid in the development environment, and
// will fail if executed from an installed executable (because the // will fail if executed from an installed executable (because the
......
...@@ -104,6 +104,8 @@ enum { ...@@ -104,6 +104,8 @@ enum {
// reside. // reside.
FILE_DEFAULT_APP_ORDER, // Full path to the json file that defines the FILE_DEFAULT_APP_ORDER, // Full path to the json file that defines the
// default app order. // default app order.
DIR_USER_POLICY_KEYS, // Directory where the session_manager stores
// the user policy keys.
#endif #endif
// Valid only in development environment; TODO(darin): move these // Valid only in development environment; TODO(darin): move these
......
...@@ -45,7 +45,8 @@ Example: ...@@ -45,7 +45,8 @@ Example:
}, },
"managed_users" : [ "managed_users" : [
"secret123456" "secret123456"
] ],
"current_key_index": 0
} }
""" """
...@@ -455,18 +456,17 @@ class RequestHandler(object): ...@@ -455,18 +456,17 @@ class RequestHandler(object):
settings = dp.ChromeDeviceSettingsProto() settings = dp.ChromeDeviceSettingsProto()
self.GatherDevicePolicySettings(settings, policy.get(policy_key, {})) self.GatherDevicePolicySettings(settings, policy.get(policy_key, {}))
# Figure out the key we want to use. If multiple keys are configured, the # Sign with 'current_key_index', defaulting to key 0.
# server will rotate through them in a round-robin fashion.
signing_key = None signing_key = None
req_key = None req_key = None
key_version = 1 current_key_index = policy.get('current_key_index', 0)
nkeys = len(self._server.keys) nkeys = len(self._server.keys)
if msg.signature_type == dm.PolicyFetchRequest.SHA1_RSA and nkeys > 0: if (msg.signature_type == dm.PolicyFetchRequest.SHA1_RSA and
current_key_index in range(nkeys)):
signing_key = self._server.keys[current_key_index]
if msg.public_key_version in range(1, nkeys + 1): if msg.public_key_version in range(1, nkeys + 1):
# requested key exists, use for signing and rotate. # requested key exists, use for signing and rotate.
req_key = self._server.keys[msg.public_key_version - 1]['private_key'] req_key = self._server.keys[msg.public_key_version - 1]['private_key']
key_version = (msg.public_key_version % nkeys) + 1
signing_key = self._server.keys[key_version - 1]
# Fill the policy data protobuf. # Fill the policy data protobuf.
policy_data = dm.PolicyData() policy_data = dm.PolicyData()
...@@ -480,7 +480,7 @@ class RequestHandler(object): ...@@ -480,7 +480,7 @@ class RequestHandler(object):
policy_data.settings_entity_id = msg.settings_entity_id policy_data.settings_entity_id = msg.settings_entity_id
if signing_key: if signing_key:
policy_data.public_key_version = key_version policy_data.public_key_version = current_key_index + 1
if msg.policy_type == 'google/chromeos/publicaccount': if msg.policy_type == 'google/chromeos/publicaccount':
policy_data.username = msg.settings_entity_id policy_data.username = msg.settings_entity_id
else: else:
...@@ -498,7 +498,7 @@ class RequestHandler(object): ...@@ -498,7 +498,7 @@ class RequestHandler(object):
if signing_key: if signing_key:
fetch_response.policy_data_signature = ( fetch_response.policy_data_signature = (
signing_key['private_key'].hashAndSign(signed_data).tostring()) signing_key['private_key'].hashAndSign(signed_data).tostring())
if msg.public_key_version != key_version: if msg.public_key_version != current_key_index + 1:
fetch_response.new_public_key = signing_key['public_key'] fetch_response.new_public_key = signing_key['public_key']
if req_key: if req_key:
fetch_response.new_public_key_signature = ( fetch_response.new_public_key_signature = (
...@@ -572,12 +572,13 @@ class TestServer(object): ...@@ -572,12 +572,13 @@ class TestServer(object):
assert key is not None assert key is not None
self.keys.append({ 'private_key' : key }) self.keys.append({ 'private_key' : key })
else: else:
# Generate a key if none were specified. # Generate 2 private keys if none were passed from the command line.
key = tlslite.api.generateRSAKey(1024) for i in range(2):
assert key is not None key = tlslite.api.generateRSAKey(512)
self.keys.append({ 'private_key' : key }) assert key is not None
self.keys.append({ 'private_key' : key })
# Derive the public keys from the loaded private keys. # Derive the public keys from the private keys.
for entry in self.keys: for entry in self.keys:
key = entry['private_key'] key = entry['private_key']
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment