Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / components / policy / core / common / cloud / user_cloud_policy_store.cc
index c800328..b6f2042 100644 (file)
@@ -7,9 +7,12 @@
 #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 "google_apis/gaia/gaia_auth_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 +35,7 @@ enum PolicyLoadStatus {
 struct PolicyLoadResult {
   PolicyLoadStatus status;
   em::PolicyFetchResponse policy;
+  em::PolicySigningKey key;
 };
 
 namespace {
@@ -42,69 +46,132 @@ 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 std::string& verification_key,
     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/key 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());
+    key_info.set_verification_key(verification_key);
+    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 +183,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 +191,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 +211,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 +233,33 @@ 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));
+
+      bool doing_key_rotation = false;
+      const std::string& verification_key = verification_key_;
+      if (!key->has_verification_key() ||
+          key->verification_key() != verification_key_) {
+        // The cached key didn't match our current key, so we're doing a key
+        // rotation - make sure we request a new key from the server on our
+        // next fetch.
+        doing_key_rotation = true;
+        DLOG(WARNING) << "Verification key rotation detected";
+        // TODO(atwilson): Add code to update |verification_key| to point to
+        // the correct key to validate the existing blob (can't do this until
+        // we've done our first key rotation).
+      }
+
       Validate(cloud_policy.Pass(),
+               key.Pass(),
+               verification_key,
                validate_in_background,
                base::Bind(
                    &UserCloudPolicyStore::InstallLoadedPolicyAfterValidation,
-                   weak_factory_.GetWeakPtr()));
+                   weak_factory_.GetWeakPtr(),
+                   doing_key_rotation,
+                   result.key.has_signing_key() ?
+                       result.key.signing_key() : std::string()));
       break;
     }
     default:
@@ -176,6 +268,8 @@ void UserCloudPolicyStore::PolicyLoaded(bool validate_in_background,
 }
 
 void UserCloudPolicyStore::InstallLoadedPolicyAfterValidation(
+    bool doing_key_rotation,
+    const std::string& signing_key,
     UserCloudPolicyValidator* validator) {
   validation_status_ = validator->status();
   if (!validator->success()) {
@@ -189,6 +283,16 @@ void UserCloudPolicyStore::InstallLoadedPolicyAfterValidation(
       validator->policy_data()->request_token();
   DVLOG(1) << "Device ID: " << validator->policy_data()->device_id();
 
+  // If we're doing a key rotation, clear the public key version so a future
+  // policy fetch will force regeneration of the keys.
+  if (doing_key_rotation) {
+    validator->policy_data()->clear_public_key_version();
+    policy_key_.clear();
+  } else {
+    // Policy validation succeeded, so we know the signing key is good.
+    policy_key_ = signing_key;
+  }
+
   InstallPolicy(validator->policy_data().Pass(), validator->payload().Pass());
   status_ = STATUS_OK;
   NotifyStoreLoaded();
@@ -201,6 +305,8 @@ 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>(),
+           verification_key_,
            true,
            base::Bind(&UserCloudPolicyStore::StorePolicyAfterValidation,
                       weak_factory_.GetWeakPtr()));
@@ -208,16 +314,91 @@ void UserCloudPolicyStore::Store(const em::PolicyFetchResponse& policy) {
 
 void UserCloudPolicyStore::Validate(
     scoped_ptr<em::PolicyFetchResponse> policy,
+    scoped_ptr<em::PolicySigningKey> cached_key,
+    const std::string& verification_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())
+  // Extract the owning domain from the signed-in user (if any is set yet).
+  // If there's no owning domain, then the code just ensures that the policy
+  // is self-consistent (that the keys are signed with the same domain that the
+  // username field in the policy contains). UserPolicySigninServerBase will
+  // verify that the username matches the signed in user once profile
+  // initialization is complete (http://crbug.com/342327).
+  std::string owning_domain;
+
+  // Validate the username if the user is signed in. The signin_username_ can
+  // be empty during initial policy load because this happens before the
+  // Prefs subsystem is initialized.
+  if (!signin_username_.empty()) {
+    DVLOG(1) << "Validating username: " << signin_username_;
     validator->ValidateUsername(signin_username_);
+    owning_domain = gaia::ExtractDomainName(
+        gaia::CanonicalizeEmail(gaia::SanitizeEmail(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 - validate the cached
+      // key, then do normal policy data signature validation using the cached
+      // key. We're loading from cache so don't allow key rotation.
+      validator->ValidateCachedKey(cached_key->signing_key(),
+                                   cached_key->signing_key_signature(),
+                                   verification_key,
+                                   owning_domain);
+      const bool no_rotation = false;
+      validator->ValidateSignature(cached_key->signing_key(),
+                                   verification_key,
+                                   owning_domain,
+                                   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 (either this is the initial
+      // policy fetch, or we're doing a key rotation), so this new policy fetch
+      // should include an initial key provision.
+      validator->ValidateInitialKey(verification_key, owning_domain);
+    } 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 need to
+      // verify via ValidateCachedKey().
+      const bool allow_rotation = true;
+      validator->ValidateSignature(
+          policy_key_, verification_key, owning_domain, allow_rotation);
+    }
+  }
 
   if (validate_in_background) {
     // Start validation in the background. The Validator will free itself once
@@ -245,8 +426,13 @@ void UserCloudPolicyStore::StorePolicyAfterValidation(
   background_task_runner()->PostTask(
       FROM_HERE,
       base::Bind(&StorePolicyToDiskOnBackgroundThread,
-                 backing_file_path_, *validator->policy()));
+                 policy_path_, key_path_, verification_key_,
+                 *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();
 }