root/components/policy/core/common/cloud/cloud_policy_validator.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ValidateTimestamp
  2. ValidateUsername
  3. ValidateDomain
  4. ValidateDMToken
  5. ValidatePolicyType
  6. ValidateSettingsEntityId
  7. ValidatePayload
  8. ValidateCachedKey
  9. ValidateSignature
  10. ValidateInitialKey
  11. ValidateAgainstCurrentPolicy
  12. background_task_runner_
  13. PostValidationTask
  14. PerformValidation
  15. ReportCompletion
  16. RunValidation
  17. RunChecks
  18. CheckNewPublicKeyVerificationSignature
  19. CheckVerificationKeySignature
  20. ExtractDomainFromPolicy
  21. set_verification_key_and_domain
  22. CheckSignature
  23. CheckInitialKey
  24. CheckCachedKey
  25. CheckPolicyType
  26. CheckEntityId
  27. CheckTimestamp
  28. CheckToken
  29. CheckUsername
  30. CheckDomain
  31. CheckPayload
  32. VerifySignature

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/policy/core/common/cloud/cloud_policy_validator.h"

#include "base/bind_helpers.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/sequenced_task_runner.h"
#include "base/stl_util.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "crypto/signature_verifier.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "policy/proto/device_management_backend.pb.h"

namespace em = enterprise_management;

namespace policy {

namespace {

// Grace interval for policy timestamp checks, in seconds.
const int kTimestampGraceIntervalSeconds = 60;

// DER-encoded ASN.1 object identifier for the SHA1-RSA signature algorithm.
const uint8 kSHA1SignatureAlgorithm[] = {
    0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
    0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00
};

// DER-encoded ASN.1 object identifier for the SHA256-RSA signature algorithm
// (source: http://tools.ietf.org/html/rfc5754 section 3.2).
const uint8 kSHA256SignatureAlgorithm[] = {
    0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
    0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00
};

COMPILE_ASSERT(sizeof(kSHA256SignatureAlgorithm) ==
               sizeof(kSHA1SignatureAlgorithm), invalid_algorithm_size);

const int kSignatureAlgorithmSize = sizeof(kSHA1SignatureAlgorithm);

const char kMetricPolicyKeyVerification[] = "Enterprise.PolicyKeyVerification";

enum MetricPolicyKeyVerification {
  // UMA metric recorded when the client has no verification key.
  METRIC_POLICY_KEY_VERIFICATION_KEY_MISSING,
  // Recorded when the policy being verified has no key signature (e.g. policy
  // fetched before the server supported the verification key).
  METRIC_POLICY_KEY_VERIFICATION_SIGNATURE_MISSING,
  // Recorded when the key signature did not match the expected value (in
  // theory, this should only happen after key rotation or if the policy cached
  // on disk has been modified).
  METRIC_POLICY_KEY_VERIFICATION_FAILED,
  // Recorded when key verification succeeded.
  METRIC_POLICY_KEY_VERIFICATION_SUCCEEDED,
  METRIC_POLICY_KEY_VERIFICATION_SIZE  // Must be the last.
};

}  // namespace

CloudPolicyValidatorBase::~CloudPolicyValidatorBase() {}

void CloudPolicyValidatorBase::ValidateTimestamp(
    base::Time not_before,
    base::Time now,
    ValidateTimestampOption timestamp_option) {
  // Timestamp should be from the past. We allow for a 1-minute grace interval
  // to cover clock drift.
  validation_flags_ |= VALIDATE_TIMESTAMP;
  timestamp_not_before_ =
      (not_before - base::Time::UnixEpoch()).InMilliseconds();
  timestamp_not_after_ =
      ((now + base::TimeDelta::FromSeconds(kTimestampGraceIntervalSeconds)) -
          base::Time::UnixEpoch()).InMillisecondsRoundedUp();
  timestamp_option_ = timestamp_option;
}

void CloudPolicyValidatorBase::ValidateUsername(
    const std::string& expected_user,
    bool canonicalize) {
  validation_flags_ |= VALIDATE_USERNAME;
  user_ = expected_user;
  canonicalize_user_ = canonicalize;
}

void CloudPolicyValidatorBase::ValidateDomain(
    const std::string& expected_domain) {
  validation_flags_ |= VALIDATE_DOMAIN;
  domain_ = gaia::CanonicalizeDomain(expected_domain);
}

void CloudPolicyValidatorBase::ValidateDMToken(
    const std::string& token,
    ValidateDMTokenOption dm_token_option) {
  validation_flags_ |= VALIDATE_TOKEN;
  token_ = token;
  dm_token_option_ = dm_token_option;
}

void CloudPolicyValidatorBase::ValidatePolicyType(
    const std::string& policy_type) {
  validation_flags_ |= VALIDATE_POLICY_TYPE;
  policy_type_ = policy_type;
}

void CloudPolicyValidatorBase::ValidateSettingsEntityId(
    const std::string& settings_entity_id) {
  validation_flags_ |= VALIDATE_ENTITY_ID;
  settings_entity_id_ = settings_entity_id;
}

void CloudPolicyValidatorBase::ValidatePayload() {
  validation_flags_ |= VALIDATE_PAYLOAD;
}


void CloudPolicyValidatorBase::ValidateCachedKey(
    const std::string& cached_key,
    const std::string& cached_key_signature,
    const std::string& verification_key,
    const std::string& owning_domain) {
  validation_flags_ |= VALIDATE_CACHED_KEY;
  set_verification_key_and_domain(verification_key, owning_domain);
  cached_key_ = cached_key;
  cached_key_signature_ = cached_key_signature;
}

void CloudPolicyValidatorBase::ValidateSignature(
    const std::string& key,
    const std::string& verification_key,
    const std::string& owning_domain,
    bool allow_key_rotation) {
  validation_flags_ |= VALIDATE_SIGNATURE;
  set_verification_key_and_domain(verification_key, owning_domain);
  key_ = key;
  allow_key_rotation_ = allow_key_rotation;
}

void CloudPolicyValidatorBase::ValidateInitialKey(
    const std::string& verification_key,
    const std::string& owning_domain) {
  validation_flags_ |= VALIDATE_INITIAL_KEY;
  set_verification_key_and_domain(verification_key, owning_domain);
}

void CloudPolicyValidatorBase::ValidateAgainstCurrentPolicy(
    const em::PolicyData* policy_data,
    ValidateTimestampOption timestamp_option,
    ValidateDMTokenOption dm_token_option) {
  base::Time last_policy_timestamp;
  std::string expected_dm_token;
  if (policy_data) {
    last_policy_timestamp =
        base::Time::UnixEpoch() +
        base::TimeDelta::FromMilliseconds(policy_data->timestamp());
    expected_dm_token = policy_data->request_token();
  }
  ValidateTimestamp(last_policy_timestamp, base::Time::NowFromSystemTime(),
                    timestamp_option);
  ValidateDMToken(expected_dm_token, dm_token_option);
}

CloudPolicyValidatorBase::CloudPolicyValidatorBase(
    scoped_ptr<em::PolicyFetchResponse> policy_response,
    google::protobuf::MessageLite* payload,
    scoped_refptr<base::SequencedTaskRunner> background_task_runner)
    : status_(VALIDATION_OK),
      policy_(policy_response.Pass()),
      payload_(payload),
      validation_flags_(0),
      timestamp_not_before_(0),
      timestamp_not_after_(0),
      timestamp_option_(TIMESTAMP_REQUIRED),
      dm_token_option_(DM_TOKEN_REQUIRED),
      canonicalize_user_(false),
      allow_key_rotation_(false),
      background_task_runner_(background_task_runner) {}

void CloudPolicyValidatorBase::PostValidationTask(
    const base::Closure& completion_callback) {
  background_task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&CloudPolicyValidatorBase::PerformValidation,
                 base::Passed(scoped_ptr<CloudPolicyValidatorBase>(this)),
                 base::MessageLoop::current()->message_loop_proxy(),
                 completion_callback));
}

// static
void CloudPolicyValidatorBase::PerformValidation(
    scoped_ptr<CloudPolicyValidatorBase> self,
    scoped_refptr<base::MessageLoopProxy> message_loop,
    const base::Closure& completion_callback) {
  // Run the validation activities on this thread.
  self->RunValidation();

  // Report completion on |message_loop|.
  message_loop->PostTask(
      FROM_HERE,
      base::Bind(&CloudPolicyValidatorBase::ReportCompletion,
                 base::Passed(&self),
                 completion_callback));
}

// static
void CloudPolicyValidatorBase::ReportCompletion(
    scoped_ptr<CloudPolicyValidatorBase> self,
    const base::Closure& completion_callback) {
  completion_callback.Run();
}

void CloudPolicyValidatorBase::RunValidation() {
  policy_data_.reset(new em::PolicyData());
  RunChecks();
}

void CloudPolicyValidatorBase::RunChecks() {
  status_ = VALIDATION_OK;
  if ((policy_->has_error_code() && policy_->error_code() != 200) ||
      (policy_->has_error_message() && !policy_->error_message().empty())) {
    LOG(ERROR) << "Error in policy blob."
               << " code: " << policy_->error_code()
               << " message: " << policy_->error_message();
    status_ = VALIDATION_ERROR_CODE_PRESENT;
    return;
  }

  // Parse policy data.
  if (!policy_data_->ParseFromString(policy_->policy_data()) ||
      !policy_data_->IsInitialized()) {
    LOG(ERROR) << "Failed to parse policy response";
    status_ = VALIDATION_PAYLOAD_PARSE_ERROR;
    return;
  }

  // Table of checks we run. These are sorted by descending severity of the
  // error, s.t. the most severe check will determine the validation status.
  static const struct {
    int flag;
    Status (CloudPolicyValidatorBase::* checkFunction)();
  } kCheckFunctions[] = {
    { VALIDATE_SIGNATURE,   &CloudPolicyValidatorBase::CheckSignature },
    { VALIDATE_INITIAL_KEY, &CloudPolicyValidatorBase::CheckInitialKey },
    { VALIDATE_CACHED_KEY,  &CloudPolicyValidatorBase::CheckCachedKey },
    { VALIDATE_POLICY_TYPE, &CloudPolicyValidatorBase::CheckPolicyType },
    { VALIDATE_ENTITY_ID,   &CloudPolicyValidatorBase::CheckEntityId },
    { VALIDATE_TOKEN,       &CloudPolicyValidatorBase::CheckToken },
    { VALIDATE_USERNAME,    &CloudPolicyValidatorBase::CheckUsername },
    { VALIDATE_DOMAIN,      &CloudPolicyValidatorBase::CheckDomain },
    { VALIDATE_TIMESTAMP,   &CloudPolicyValidatorBase::CheckTimestamp },
    { VALIDATE_PAYLOAD,     &CloudPolicyValidatorBase::CheckPayload },
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kCheckFunctions); ++i) {
    if (validation_flags_ & kCheckFunctions[i].flag) {
      status_ = (this->*(kCheckFunctions[i].checkFunction))();
      if (status_ != VALIDATION_OK)
        break;
    }
  }
}

// Verifies the |new_public_key_verification_signature| for the |new_public_key|
// in the policy blob.
bool CloudPolicyValidatorBase::CheckNewPublicKeyVerificationSignature() {
  // If there's no local verification key, then just return true (no
  // validation possible).
  if (verification_key_.empty()) {
    UMA_HISTOGRAM_ENUMERATION(kMetricPolicyKeyVerification,
                              METRIC_POLICY_KEY_VERIFICATION_KEY_MISSING,
                              METRIC_POLICY_KEY_VERIFICATION_SIZE);
    return true;
  }

  if (!policy_->has_new_public_key_verification_signature()) {
    // Policy does not contain a verification signature, so log an error.
    LOG(ERROR) << "Policy is missing public_key_verification_signature";
    UMA_HISTOGRAM_ENUMERATION(kMetricPolicyKeyVerification,
                              METRIC_POLICY_KEY_VERIFICATION_SIGNATURE_MISSING,
                              METRIC_POLICY_KEY_VERIFICATION_SIZE);
    return false;
  }

  if (!CheckVerificationKeySignature(
          policy_->new_public_key(),
          verification_key_,
          policy_->new_public_key_verification_signature())) {
    LOG(ERROR) << "Signature verification failed";
    UMA_HISTOGRAM_ENUMERATION(kMetricPolicyKeyVerification,
                              METRIC_POLICY_KEY_VERIFICATION_FAILED,
                              METRIC_POLICY_KEY_VERIFICATION_SIZE);
    return false;
  }
  // Signature verification succeeded - return success to the caller.
  DVLOG(1) << "Signature verification succeeded";
  UMA_HISTOGRAM_ENUMERATION(kMetricPolicyKeyVerification,
                            METRIC_POLICY_KEY_VERIFICATION_SUCCEEDED,
                            METRIC_POLICY_KEY_VERIFICATION_SIZE);
  return true;
}

bool CloudPolicyValidatorBase::CheckVerificationKeySignature(
    const std::string& key,
    const std::string& verification_key,
    const std::string& signature) {
  DCHECK(!verification_key.empty());
  em::PolicyPublicKeyAndDomain signed_data;
  signed_data.set_new_public_key(key);

  // If no owning_domain_ supplied, try extracting the domain from the policy
  // itself (this happens on certain platforms during startup, when we validate
  // cached policy before prefs are loaded).
  std::string domain = owning_domain_.empty() ?
      ExtractDomainFromPolicy() : owning_domain_;
  if (domain.empty()) {
    LOG(ERROR) << "Policy does not contain a domain";
    return false;
  }
  signed_data.set_domain(domain);
  std::string signed_data_as_string;
  if (!signed_data.SerializeToString(&signed_data_as_string)) {
    DLOG(ERROR) << "Could not serialize verification key to string";
    return false;
  }
  return VerifySignature(signed_data_as_string, verification_key, signature,
                         SHA256);
}

std::string CloudPolicyValidatorBase::ExtractDomainFromPolicy() {
  std::string domain;
  if (policy_data_->has_username()) {
    domain = gaia::ExtractDomainName(
        gaia::CanonicalizeEmail(
            gaia::SanitizeEmail(policy_data_->username())));
  }
  return domain;
}

void CloudPolicyValidatorBase::set_verification_key_and_domain(
    const std::string& verification_key, const std::string& owning_domain) {
  // Make sure we aren't overwriting the verification key with a different key.
  DCHECK(verification_key_.empty() || verification_key_ == verification_key);
  DCHECK(owning_domain_.empty() || owning_domain_ == owning_domain);
  verification_key_ = verification_key;
  owning_domain_ = owning_domain;
}

CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckSignature() {
  const std::string* signature_key = &key_;
  if (policy_->has_new_public_key() && allow_key_rotation_) {
    signature_key = &policy_->new_public_key();
    if (!policy_->has_new_public_key_signature() ||
        !VerifySignature(policy_->new_public_key(), key_,
                         policy_->new_public_key_signature(), SHA1)) {
      LOG(ERROR) << "New public key rotation signature verification failed";
      return VALIDATION_BAD_SIGNATURE;
    }

    if (!CheckNewPublicKeyVerificationSignature()) {
      LOG(ERROR) << "New public key root verification failed";
      return VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE;
    }
  }

  if (!policy_->has_policy_data_signature() ||
      !VerifySignature(policy_->policy_data(), *signature_key,
                       policy_->policy_data_signature(), SHA1)) {
    LOG(ERROR) << "Policy signature validation failed";
    return VALIDATION_BAD_SIGNATURE;
  }

  return VALIDATION_OK;
}

CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckInitialKey() {
  if (!policy_->has_new_public_key() ||
      !policy_->has_policy_data_signature() ||
      !VerifySignature(policy_->policy_data(), policy_->new_public_key(),
                       policy_->policy_data_signature(), SHA1)) {
    LOG(ERROR) << "Initial policy signature validation failed";
    return VALIDATION_BAD_INITIAL_SIGNATURE;
  }

  if (!CheckNewPublicKeyVerificationSignature()) {
    LOG(ERROR) << "Initial policy root signature validation failed";
    return VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE;
  }
  return VALIDATION_OK;
}

CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckCachedKey() {
  if (!verification_key_.empty() &&
      !CheckVerificationKeySignature(cached_key_, verification_key_,
                                     cached_key_signature_)) {
    LOG(ERROR) << "Cached key signature verification failed";
    return VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE;
  } else {
    DVLOG(1) << "Cached key signature verification succeeded";
  }
  return VALIDATION_OK;
}

CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckPolicyType() {
  if (!policy_data_->has_policy_type() ||
       policy_data_->policy_type() != policy_type_) {
    LOG(ERROR) << "Wrong policy type " << policy_data_->policy_type();
    return VALIDATION_WRONG_POLICY_TYPE;
  }

  return VALIDATION_OK;
}

CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckEntityId() {
  if (!policy_data_->has_settings_entity_id() ||
      policy_data_->settings_entity_id() != settings_entity_id_) {
    LOG(ERROR) << "Wrong settings_entity_id "
               << policy_data_->settings_entity_id() << ", expected "
               << settings_entity_id_;
    return VALIDATION_WRONG_SETTINGS_ENTITY_ID;
  }

  return VALIDATION_OK;
}

CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckTimestamp() {
  if (!policy_data_->has_timestamp()) {
    if (timestamp_option_ == TIMESTAMP_NOT_REQUIRED) {
      return VALIDATION_OK;
    } else {
      LOG(ERROR) << "Policy timestamp missing";
      return VALIDATION_BAD_TIMESTAMP;
    }
  }

  if (timestamp_option_ != TIMESTAMP_NOT_REQUIRED &&
      policy_data_->timestamp() < timestamp_not_before_) {
    // If |timestamp_option_| is TIMESTAMP_REQUIRED or TIMESTAMP_NOT_BEFORE
    // then this is a failure.
    LOG(ERROR) << "Policy too old: " << policy_data_->timestamp();
    return VALIDATION_BAD_TIMESTAMP;
  }
  if (timestamp_option_ == TIMESTAMP_REQUIRED &&
      policy_data_->timestamp() > timestamp_not_after_) {
    LOG(ERROR) << "Policy from the future: " << policy_data_->timestamp();
    return VALIDATION_BAD_TIMESTAMP;
  }

  return VALIDATION_OK;
}

CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckToken() {
  // Make sure the token matches the expected token (if any) and also
  // make sure the token itself is valid (non-empty if DM_TOKEN_REQUIRED).
  if (dm_token_option_ == DM_TOKEN_REQUIRED &&
      (!policy_data_->has_request_token() ||
       policy_data_->request_token().empty())) {
    LOG(ERROR) << "Empty DM token encountered - expected: " << token_;
    return VALIDATION_WRONG_TOKEN;
  }
  if (!token_.empty() && policy_data_->request_token() != token_) {
    LOG(ERROR) << "Invalid DM token: " << policy_data_->request_token()
               << " - expected: " << token_;
    return VALIDATION_WRONG_TOKEN;
  }

  return VALIDATION_OK;
}

CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckUsername() {
  if (!policy_data_->has_username()) {
    LOG(ERROR) << "Policy is missing user name";
    return VALIDATION_BAD_USERNAME;
  }

  std::string expected = user_;
  std::string actual = policy_data_->username();
  if (canonicalize_user_) {
    expected = gaia::CanonicalizeEmail(gaia::SanitizeEmail(expected));
    actual = gaia::CanonicalizeEmail(gaia::SanitizeEmail(actual));
  }

  if (expected != actual) {
    LOG(ERROR) << "Invalid user name " << policy_data_->username();
    return VALIDATION_BAD_USERNAME;
  }

  return VALIDATION_OK;
}

CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckDomain() {
  std::string policy_domain = ExtractDomainFromPolicy();
  if (policy_domain.empty()) {
    LOG(ERROR) << "Policy is missing user name";
    return VALIDATION_BAD_USERNAME;
  }

  if (domain_ != policy_domain) {
    LOG(ERROR) << "Invalid user name " << policy_data_->username();
    return VALIDATION_BAD_USERNAME;
  }

  return VALIDATION_OK;
}

CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckPayload() {
  if (!policy_data_->has_policy_value() ||
      !payload_->ParseFromString(policy_data_->policy_value()) ||
      !payload_->IsInitialized()) {
    LOG(ERROR) << "Failed to decode policy payload protobuf";
    return VALIDATION_POLICY_PARSE_ERROR;
  }

  return VALIDATION_OK;
}

// static
bool CloudPolicyValidatorBase::VerifySignature(const std::string& data,
                                               const std::string& key,
                                               const std::string& signature,
                                               SignatureType signature_type) {
  crypto::SignatureVerifier verifier;
  const uint8* algorithm = NULL;
  switch (signature_type) {
    case SHA1:
      algorithm = kSHA1SignatureAlgorithm;
      break;
    case SHA256:
      algorithm = kSHA256SignatureAlgorithm;
      break;
    default:
      NOTREACHED() << "Invalid signature type: " << signature_type;
      return false;
  }

  if (!verifier.VerifyInit(algorithm, kSignatureAlgorithmSize,
                           reinterpret_cast<const uint8*>(signature.c_str()),
                           signature.size(),
                           reinterpret_cast<const uint8*>(key.c_str()),
                           key.size())) {
    DLOG(ERROR) << "Invalid verification signature/key format";
    return false;
  }
  verifier.VerifyUpdate(reinterpret_cast<const uint8*>(data.c_str()),
                        data.size());
  return verifier.VerifyFinal();
}

template class CloudPolicyValidator<em::CloudPolicySettings>;

#if !defined(OS_ANDROID) && !defined(OS_IOS)
template class CloudPolicyValidator<em::ExternalPolicyData>;
#endif

}  // namespace policy

/* [<][>][^][v][top][bottom][index][help] */