| Index: components/policy/core/common/cloud/user_cloud_policy_store.cc
|
| diff --git a/components/policy/core/common/cloud/user_cloud_policy_store.cc b/components/policy/core/common/cloud/user_cloud_policy_store.cc
|
| index c800328c93ad48816f6fec9e752b3ac3203e6fa7..7b4d735a0f2a0619a6e80018bb4c5eb6b51d15fb 100644
|
| --- a/components/policy/core/common/cloud/user_cloud_policy_store.cc
|
| +++ b/components/policy/core/common/cloud/user_cloud_policy_store.cc
|
| @@ -7,9 +7,11 @@
|
| #include "base/bind.h"
|
| #include "base/file_util.h"
|
| #include "base/location.h"
|
| +#include "base/metrics/histogram.h"
|
| #include "base/task_runner_util.h"
|
| #include "policy/proto/cloud_policy.pb.h"
|
| #include "policy/proto/device_management_backend.pb.h"
|
| +#include "policy/proto/policy_signing_key.pb.h"
|
|
|
| namespace em = enterprise_management;
|
|
|
| @@ -32,6 +34,7 @@ enum PolicyLoadStatus {
|
| struct PolicyLoadResult {
|
| PolicyLoadStatus status;
|
| em::PolicyFetchResponse policy;
|
| + em::PolicySigningKey key;
|
| };
|
|
|
| namespace {
|
| @@ -42,69 +45,130 @@ const base::FilePath::CharType kPolicyDir[] = FILE_PATH_LITERAL("Policy");
|
| const base::FilePath::CharType kPolicyCacheFile[] =
|
| FILE_PATH_LITERAL("User Policy");
|
|
|
| +// File in the above directory for storing policy signing key data.
|
| +const base::FilePath::CharType kKeyCacheFile[] =
|
| + FILE_PATH_LITERAL("Signing Key");
|
| +
|
| +const char kMetricPolicyHasVerifiedCachedKey[] =
|
| + "Enterprise.PolicyHasVerifiedCachedKey";
|
| +
|
| // Loads policy from the backing file. Returns a PolicyLoadResult with the
|
| // results of the fetch.
|
| -policy::PolicyLoadResult LoadPolicyFromDisk(const base::FilePath& path) {
|
| +policy::PolicyLoadResult LoadPolicyFromDisk(
|
| + const base::FilePath& policy_path,
|
| + const base::FilePath& key_path) {
|
| policy::PolicyLoadResult result;
|
| - // If the backing file does not exist, just return.
|
| - if (!base::PathExists(path)) {
|
| + // If the backing file does not exist, just return. We don't verify the key
|
| + // path here, because the key is optional (the validation code will fail if
|
| + // the key does not exist but the loaded policy is unsigned).
|
| + if (!base::PathExists(policy_path)) {
|
| result.status = policy::LOAD_RESULT_NO_POLICY_FILE;
|
| return result;
|
| }
|
| std::string data;
|
| - if (!base::ReadFileToString(path, &data) ||
|
| - !result.policy.ParseFromArray(data.c_str(), data.size())) {
|
| - LOG(WARNING) << "Failed to read or parse policy data from " << path.value();
|
| + // TODO(atwilson): Enforce a policy/key maxsize when ReadFileToString() can
|
| + // accept a max_size (http://crbug.com/339417).
|
| + if (!base::ReadFileToString(policy_path, &data) ||
|
| + !result.policy.ParseFromString(data)) {
|
| + LOG(WARNING) << "Failed to read or parse policy data from "
|
| + << policy_path.value();
|
| result.status = policy::LOAD_RESULT_LOAD_ERROR;
|
| return result;
|
| }
|
|
|
| + if (!base::ReadFileToString(key_path, &data) ||
|
| + !result.key.ParseFromString(data)) {
|
| + // Log an error on missing key data, but do not trigger a load failure
|
| + // for now since there are still old unsigned cached policy blobs in the
|
| + // wild with no associated key (see kMetricPolicyHasVerifiedCachedKey UMA
|
| + // stat below).
|
| + LOG(ERROR) << "Failed to read or parse key data from " << key_path.value();
|
| + result.key.clear_signing_key();
|
| + }
|
| +
|
| + // Track the occurrence of valid cached keys - when this ratio gets high
|
| + // enough, we can update the code to reject unsigned policy or unverified
|
| + // keys.
|
| + UMA_HISTOGRAM_BOOLEAN(kMetricPolicyHasVerifiedCachedKey,
|
| + result.key.has_signing_key());
|
| +
|
| result.status = policy::LOAD_RESULT_SUCCESS;
|
| return result;
|
| }
|
|
|
| +bool WriteStringToFile(const base::FilePath path, const std::string& data) {
|
| + if (!base::CreateDirectory(path.DirName())) {
|
| + DLOG(WARNING) << "Failed to create directory " << path.DirName().value();
|
| + return false;
|
| + }
|
| +
|
| + int size = data.size();
|
| + if (file_util::WriteFile(path, data.c_str(), size) != size) {
|
| + DLOG(WARNING) << "Failed to write " << path.value();
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| // Stores policy to the backing file (must be called via a task on
|
| // the background thread).
|
| void StorePolicyToDiskOnBackgroundThread(
|
| - const base::FilePath& path,
|
| + const base::FilePath& policy_path,
|
| + const base::FilePath& key_path,
|
| const em::PolicyFetchResponse& policy) {
|
| - DVLOG(1) << "Storing policy to " << path.value();
|
| + DVLOG(1) << "Storing policy to " << policy_path.value();
|
| std::string data;
|
| if (!policy.SerializeToString(&data)) {
|
| DLOG(WARNING) << "Failed to serialize policy data";
|
| return;
|
| }
|
|
|
| - if (!base::CreateDirectory(path.DirName())) {
|
| - DLOG(WARNING) << "Failed to create directory " << path.DirName().value();
|
| + if (!WriteStringToFile(policy_path, data))
|
| return;
|
| - }
|
|
|
| - int size = data.size();
|
| - if (file_util::WriteFile(path, data.c_str(), size) != size) {
|
| - DLOG(WARNING) << "Failed to write " << path.value();
|
| + if (policy.has_new_public_key()) {
|
| + // Write the new public key and its verification signature to a file.
|
| + em::PolicySigningKey key_info;
|
| + key_info.set_signing_key(policy.new_public_key());
|
| + key_info.set_signing_key_signature(
|
| + policy.new_public_key_verification_signature());
|
| + std::string key_data;
|
| + if (!key_info.SerializeToString(&key_data)) {
|
| + DLOG(WARNING) << "Failed to serialize policy signing key";
|
| + return;
|
| + }
|
| +
|
| + WriteStringToFile(key_path, key_data);
|
| }
|
| }
|
|
|
| } // namespace
|
|
|
| UserCloudPolicyStore::UserCloudPolicyStore(
|
| - const base::FilePath& path,
|
| + const base::FilePath& policy_path,
|
| + const base::FilePath& key_path,
|
| + const std::string& verification_key,
|
| scoped_refptr<base::SequencedTaskRunner> background_task_runner)
|
| : UserCloudPolicyStoreBase(background_task_runner),
|
| weak_factory_(this),
|
| - backing_file_path_(path) {}
|
| + policy_path_(policy_path),
|
| + key_path_(key_path),
|
| + verification_key_(verification_key) {}
|
|
|
| UserCloudPolicyStore::~UserCloudPolicyStore() {}
|
|
|
| // static
|
| scoped_ptr<UserCloudPolicyStore> UserCloudPolicyStore::Create(
|
| const base::FilePath& profile_path,
|
| + const std::string& verification_key,
|
| scoped_refptr<base::SequencedTaskRunner> background_task_runner) {
|
| - base::FilePath path =
|
| + base::FilePath policy_path =
|
| profile_path.Append(kPolicyDir).Append(kPolicyCacheFile);
|
| - return make_scoped_ptr(
|
| - new UserCloudPolicyStore(path, background_task_runner));
|
| + base::FilePath key_path =
|
| + profile_path.Append(kPolicyDir).Append(kKeyCacheFile);
|
| + return make_scoped_ptr(new UserCloudPolicyStore(
|
| + policy_path, key_path, verification_key, background_task_runner));
|
| }
|
|
|
| void UserCloudPolicyStore::SetSigninUsername(const std::string& username) {
|
| @@ -116,7 +180,7 @@ void UserCloudPolicyStore::LoadImmediately() {
|
| // Cancel any pending Load/Store/Validate operations.
|
| weak_factory_.InvalidateWeakPtrs();
|
| // Load the policy from disk...
|
| - PolicyLoadResult result = LoadPolicyFromDisk(backing_file_path_);
|
| + PolicyLoadResult result = LoadPolicyFromDisk(policy_path_, key_path_);
|
| // ...and install it, reporting success/failure to any observers.
|
| PolicyLoaded(false, result);
|
| }
|
| @@ -124,10 +188,13 @@ void UserCloudPolicyStore::LoadImmediately() {
|
| void UserCloudPolicyStore::Clear() {
|
| background_task_runner()->PostTask(
|
| FROM_HERE,
|
| - base::Bind(
|
| - base::IgnoreResult(&base::DeleteFile), backing_file_path_, false));
|
| + base::Bind(base::IgnoreResult(&base::DeleteFile), policy_path_, false));
|
| + background_task_runner()->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(base::IgnoreResult(&base::DeleteFile), key_path_, false));
|
| policy_.reset();
|
| policy_map_.Clear();
|
| + policy_key_.clear();
|
| NotifyStoreLoaded();
|
| }
|
|
|
| @@ -141,7 +208,7 @@ void UserCloudPolicyStore::Load() {
|
| base::PostTaskAndReplyWithResult(
|
| background_task_runner(),
|
| FROM_HERE,
|
| - base::Bind(&LoadPolicyFromDisk, backing_file_path_),
|
| + base::Bind(&LoadPolicyFromDisk, policy_path_, key_path_),
|
| base::Bind(&UserCloudPolicyStore::PolicyLoaded,
|
| weak_factory_.GetWeakPtr(), true));
|
| }
|
| @@ -163,11 +230,16 @@ void UserCloudPolicyStore::PolicyLoaded(bool validate_in_background,
|
| // Found policy on disk - need to validate it before it can be used.
|
| scoped_ptr<em::PolicyFetchResponse> cloud_policy(
|
| new em::PolicyFetchResponse(result.policy));
|
| + scoped_ptr<em::PolicySigningKey> key(
|
| + new em::PolicySigningKey(result.key));
|
| Validate(cloud_policy.Pass(),
|
| + key.Pass(),
|
| validate_in_background,
|
| base::Bind(
|
| &UserCloudPolicyStore::InstallLoadedPolicyAfterValidation,
|
| - weak_factory_.GetWeakPtr()));
|
| + weak_factory_.GetWeakPtr(),
|
| + result.key.has_signing_key() ?
|
| + result.key.signing_key() : std::string()));
|
| break;
|
| }
|
| default:
|
| @@ -176,6 +248,7 @@ void UserCloudPolicyStore::PolicyLoaded(bool validate_in_background,
|
| }
|
|
|
| void UserCloudPolicyStore::InstallLoadedPolicyAfterValidation(
|
| + const std::string& signing_key,
|
| UserCloudPolicyValidator* validator) {
|
| validation_status_ = validator->status();
|
| if (!validator->success()) {
|
| @@ -190,6 +263,8 @@ void UserCloudPolicyStore::InstallLoadedPolicyAfterValidation(
|
| DVLOG(1) << "Device ID: " << validator->policy_data()->device_id();
|
|
|
| InstallPolicy(validator->policy_data().Pass(), validator->payload().Pass());
|
| + // Policy validation succeeded, so we know the signing key is good.
|
| + policy_key_ = signing_key;
|
| status_ = STATUS_OK;
|
| NotifyStoreLoaded();
|
| }
|
| @@ -201,6 +276,7 @@ void UserCloudPolicyStore::Store(const em::PolicyFetchResponse& policy) {
|
| scoped_ptr<em::PolicyFetchResponse> policy_copy(
|
| new em::PolicyFetchResponse(policy));
|
| Validate(policy_copy.Pass(),
|
| + scoped_ptr<em::PolicySigningKey>(),
|
| true,
|
| base::Bind(&UserCloudPolicyStore::StorePolicyAfterValidation,
|
| weak_factory_.GetWeakPtr()));
|
| @@ -208,16 +284,73 @@ void UserCloudPolicyStore::Store(const em::PolicyFetchResponse& policy) {
|
|
|
| void UserCloudPolicyStore::Validate(
|
| scoped_ptr<em::PolicyFetchResponse> policy,
|
| + scoped_ptr<em::PolicySigningKey> cached_key,
|
| bool validate_in_background,
|
| const UserCloudPolicyValidator::CompletionCallback& callback) {
|
| +
|
| + const bool signed_policy = policy->has_policy_data_signature();
|
| +
|
| // Configure the validator.
|
| scoped_ptr<UserCloudPolicyValidator> validator = CreateValidator(
|
| policy.Pass(),
|
| CloudPolicyValidatorBase::TIMESTAMP_NOT_BEFORE);
|
|
|
| // Validate the username if the user is signed in.
|
| - if (!signin_username_.empty())
|
| + if (!signin_username_.empty()) {
|
| + DVLOG(1) << "Validating username: " << signin_username_;
|
| validator->ValidateUsername(signin_username_);
|
| + }
|
| +
|
| + // There are 4 cases:
|
| + //
|
| + // 1) Validation after loading from cache with no cached key.
|
| + // Action: Don't validate signature (migration from previously cached
|
| + // unsigned blob).
|
| + //
|
| + // 2) Validation after loading from cache with a cached key
|
| + // Action: Validate signature on policy blob but don't allow key rotation.
|
| + //
|
| + // 3) Validation after loading new policy from the server with no cached key
|
| + // Action: Validate as initial key provisioning (case where we are migrating
|
| + // from unsigned policy)
|
| + //
|
| + // 4) Validation after loading new policy from the server with a cached key
|
| + // Action: Validate as normal, and allow key rotation.
|
| + if (cached_key) {
|
| + // Loading from cache should not change the cached keys.
|
| + DCHECK(policy_key_.empty() || policy_key_ == cached_key->signing_key());
|
| + if (!signed_policy || !cached_key->has_signing_key()) {
|
| + // Case #1 - loading from cache with no signing key.
|
| + // TODO(atwilson): Reject policy with no cached key once
|
| + // kMetricPolicyHasVerifiedCachedKey rises to a high enough level.
|
| + DLOG(WARNING) << "Allowing unsigned cached blob for migration";
|
| + } else {
|
| + // Case #2 - loading from cache with a cached key - just do normal
|
| + // signature validation using this key. We're loading from cache so don't
|
| + // allow key rotation.
|
| + const bool no_rotation = false;
|
| + validator->ValidateSignature(cached_key->signing_key(),
|
| + verification_key_,
|
| + cached_key->signing_key_signature(),
|
| + no_rotation);
|
| + }
|
| + } else {
|
| + // No passed cached_key - this is not validating the initial policy load
|
| + // from cache, but rather an update from the server.
|
| + if (policy_key_.empty()) {
|
| + // Case #3 - no valid existing policy key, so this new policy fetch should
|
| + // include an initial key provision.
|
| + validator->ValidateInitialKey(verification_key_);
|
| + } else {
|
| + // Case #4 - verify new policy with existing key. We always allow key
|
| + // rotation - the verification key will prevent invalid policy from being
|
| + // injected. |policy_key_| is already known to be valid, so no
|
| + // verification signature is passed in.
|
| + const bool allow_rotation = true;
|
| + validator->ValidateSignature(
|
| + policy_key_, verification_key_, std::string(), allow_rotation);
|
| + }
|
| + }
|
|
|
| if (validate_in_background) {
|
| // Start validation in the background. The Validator will free itself once
|
| @@ -245,8 +378,12 @@ void UserCloudPolicyStore::StorePolicyAfterValidation(
|
| background_task_runner()->PostTask(
|
| FROM_HERE,
|
| base::Bind(&StorePolicyToDiskOnBackgroundThread,
|
| - backing_file_path_, *validator->policy()));
|
| + policy_path_, key_path_, *validator->policy()));
|
| InstallPolicy(validator->policy_data().Pass(), validator->payload().Pass());
|
| +
|
| + // If the key was rotated, update our local cache of the key.
|
| + if (validator->policy()->has_new_public_key())
|
| + policy_key_ = validator->policy()->new_public_key();
|
| status_ = STATUS_OK;
|
| NotifyStoreLoaded();
|
| }
|
|
|