root/chrome/browser/extensions/install_verifier.cc

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

DEFINITIONS

This source file includes following definitions.
  1. GetExperimentStatus
  2. GetCommandLineStatus
  3. GetStatus
  4. ShouldFetchSignature
  5. ShouldEnforce
  6. LogInitResultHistogram
  7. FromStore
  8. CanUseExtensionApis
  9. LogVerifyAllSuccessHistogram
  10. LogAddVerifiedSuccess
  11. weak_factory_
  12. NeedsVerification
  13. Init
  14. VerifyAllExtensions
  15. SignatureTimestamp
  16. IsKnownId
  17. VerifyExtension
  18. AddMany
  19. AddProvisional
  20. Remove
  21. RemoveMany
  22. GetDebugPolicyProviderName
  23. MustRemainDisabledHistogram
  24. MustRemainDisabled
  25. GetExtensionsToVerify
  26. MaybeBootstrapSelf
  27. OnVerificationComplete
  28. GarbageCollect
  29. AllowedByEnterprisePolicy
  30. IsVerified
  31. BeginFetch
  32. SaveToPrefs
  33. GetSignatureResultHistogram
  34. SignatureCallback

// Copyright 2013 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 "chrome/browser/extensions/install_verifier.h"

#include <algorithm>
#include <string>

#include "base/bind.h"
#include "base/command_line.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/prefs/pref_service.h"
#include "base/stl_util.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/install_signer.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/manifest_url_handler.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/browser_context.h"
#include "content/public/common/content_switches.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/pref_names.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/manifest.h"
#include "extensions/common/one_shot_event.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"

namespace extensions {

namespace {

enum VerifyStatus {
  NONE = 0,   // Do not request install signatures, and do not enforce them.
  BOOTSTRAP,  // Request install signatures, but do not enforce them.
  ENFORCE,    // Request install signatures, and enforce them.

  // This is used in histograms - do not remove or reorder entries above! Also
  // the "MAX" item below should always be the last element.

  VERIFY_STATUS_MAX
};

#if defined(GOOGLE_CHROME_BUILD)
const char kExperimentName[] = "ExtensionInstallVerification";
#endif  // defined(GOOGLE_CHROME_BUILD)

VerifyStatus GetExperimentStatus() {
#if defined(GOOGLE_CHROME_BUILD)
  const std::string group = base::FieldTrialList::FindFullName(
      kExperimentName);

  std::string forced_trials = CommandLine::ForCurrentProcess()->
      GetSwitchValueASCII(switches::kForceFieldTrials);
  if (forced_trials.find(kExperimentName) != std::string::npos) {
    // We don't want to allow turning off enforcement by forcing the field
    // trial group to something other than enforcement.
    return ENFORCE;
  }

  VerifyStatus default_status = NONE;

  if (group == "Enforce")
    return ENFORCE;
  else if (group == "Bootstrap")
    return BOOTSTRAP;
  else if (group == "None" || group == "Control")
    return NONE;
  else
    return default_status;
#endif  // defined(GOOGLE_CHROME_BUILD)

  return NONE;
}

VerifyStatus GetCommandLineStatus() {
  const CommandLine* cmdline = CommandLine::ForCurrentProcess();
  if (!InstallSigner::GetForcedNotFromWebstore().empty())
    return ENFORCE;

  if (cmdline->HasSwitch(switches::kExtensionsInstallVerification)) {
    std::string value = cmdline->GetSwitchValueASCII(
        switches::kExtensionsInstallVerification);
    if (value == "bootstrap")
      return BOOTSTRAP;
    else
      return ENFORCE;
  }

  return NONE;
}

VerifyStatus GetStatus() {
  return std::max(GetExperimentStatus(), GetCommandLineStatus());
}

bool ShouldFetchSignature() {
  VerifyStatus status = GetStatus();
  return (status == BOOTSTRAP || status == ENFORCE);
}

bool ShouldEnforce() {
  return GetStatus() == ENFORCE;
}

enum InitResult {
  INIT_NO_PREF = 0,
  INIT_UNPARSEABLE_PREF,
  INIT_INVALID_SIGNATURE,
  INIT_VALID_SIGNATURE,

  // This is used in histograms - do not remove or reorder entries above! Also
  // the "MAX" item below should always be the last element.

  INIT_RESULT_MAX
};

void LogInitResultHistogram(InitResult result) {
  UMA_HISTOGRAM_ENUMERATION("ExtensionInstallVerifier.InitResult",
                            result, INIT_RESULT_MAX);
}

bool FromStore(const Extension& extension) {
  if (extension.from_webstore() || ManifestURL::UpdatesFromGallery(&extension))
    return true;

  // If an extension has no update url, our autoupdate code will ask the
  // webstore about it (to aid in migrating to the webstore from self-hosting
  // or sideloading based installs). So we want to do verification checks on
  // such extensions too so that we don't accidentally disable old installs of
  // extensions that did migrate to the webstore.
  return (ManifestURL::GetUpdateURL(&extension).is_empty() &&
          Manifest::IsAutoUpdateableLocation(extension.location()));
}

bool CanUseExtensionApis(const Extension& extension) {
  return extension.is_extension() || extension.is_legacy_packaged_app();
}

enum VerifyAllSuccess {
  VERIFY_ALL_BOOTSTRAP_SUCCESS = 0,
  VERIFY_ALL_BOOTSTRAP_FAILURE,
  VERIFY_ALL_NON_BOOTSTRAP_SUCCESS,
  VERIFY_ALL_NON_BOOTSTRAP_FAILURE,

  // Used in histograms. Do not remove/reorder any entries above, and the below
  // MAX entry should always come last.
  VERIFY_ALL_SUCCESS_MAX
};

// Record the success or failure of verifying all extensions, and whether or
// not it was a bootstrapping.
void LogVerifyAllSuccessHistogram(bool bootstrap, bool success) {
  VerifyAllSuccess result;
  if (bootstrap && success)
    result = VERIFY_ALL_BOOTSTRAP_SUCCESS;
  else if (bootstrap && !success)
    result = VERIFY_ALL_BOOTSTRAP_FAILURE;
  else if (!bootstrap && success)
    result = VERIFY_ALL_NON_BOOTSTRAP_SUCCESS;
  else
    result = VERIFY_ALL_NON_BOOTSTRAP_FAILURE;

  // This used to be part of ExtensionService, but moved here. In order to keep
  // our histograms accurate, the name is unchanged.
  UMA_HISTOGRAM_ENUMERATION(
      "ExtensionService.VerifyAllSuccess", result, VERIFY_ALL_SUCCESS_MAX);
}

// Record the success or failure of a single verification.
void LogAddVerifiedSuccess(bool success) {
  // This used to be part of ExtensionService, but moved here. In order to keep
  // our histograms accurate, the name is unchanged.
  UMA_HISTOGRAM_BOOLEAN("ExtensionService.AddVerified", success);
}

}  // namespace

InstallVerifier::InstallVerifier(ExtensionPrefs* prefs,
                                 content::BrowserContext* context)
    : prefs_(prefs), context_(context), weak_factory_(this) {}

InstallVerifier::~InstallVerifier() {}

// static
bool InstallVerifier::NeedsVerification(const Extension& extension) {
  return FromStore(extension) && CanUseExtensionApis(extension);
}

void InstallVerifier::Init() {
  UMA_HISTOGRAM_ENUMERATION("ExtensionInstallVerifier.ExperimentStatus",
                            GetExperimentStatus(), VERIFY_STATUS_MAX);
  UMA_HISTOGRAM_ENUMERATION("ExtensionInstallVerifier.ActualStatus",
                            GetStatus(), VERIFY_STATUS_MAX);

  const base::DictionaryValue* pref = prefs_->GetInstallSignature();
  if (pref) {
    scoped_ptr<InstallSignature> signature_from_prefs =
        InstallSignature::FromValue(*pref);
    if (!signature_from_prefs.get()) {
      LogInitResultHistogram(INIT_UNPARSEABLE_PREF);
    } else if (!InstallSigner::VerifySignature(*signature_from_prefs.get())) {
      LogInitResultHistogram(INIT_INVALID_SIGNATURE);
      DVLOG(1) << "Init - ignoring invalid signature";
    } else {
      signature_ = signature_from_prefs.Pass();
      LogInitResultHistogram(INIT_VALID_SIGNATURE);
      UMA_HISTOGRAM_COUNTS_100("ExtensionInstallVerifier.InitSignatureCount",
                               signature_->ids.size());
      GarbageCollect();
    }
  } else {
    LogInitResultHistogram(INIT_NO_PREF);
  }

  ExtensionSystem::Get(context_)->ready().Post(
      FROM_HERE,
      base::Bind(&InstallVerifier::MaybeBootstrapSelf,
                 weak_factory_.GetWeakPtr()));
}

void InstallVerifier::VerifyAllExtensions() {
  AddMany(GetExtensionsToVerify(), ADD_ALL);
}

base::Time InstallVerifier::SignatureTimestamp() {
  if (signature_.get())
    return signature_->timestamp;
  else
    return base::Time();
}

bool InstallVerifier::IsKnownId(const std::string& id) {
  return signature_.get() && (ContainsKey(signature_->ids, id) ||
                              ContainsKey(signature_->invalid_ids, id));
}

void InstallVerifier::VerifyExtension(const std::string& extension_id) {
  ExtensionIdSet ids;
  ids.insert(extension_id);
  AddMany(ids, ADD_SINGLE);
}

void InstallVerifier::AddMany(const ExtensionIdSet& ids, OperationType type) {
  if (!ShouldFetchSignature()) {
    OnVerificationComplete(true, type);  // considered successful.
    return;
  }

  if (signature_.get()) {
    ExtensionIdSet not_allowed_yet =
        base::STLSetDifference<ExtensionIdSet>(ids, signature_->ids);
    if (not_allowed_yet.empty()) {
      OnVerificationComplete(true, type);  // considered successful.
      return;
    }
  }

  InstallVerifier::PendingOperation* operation =
      new InstallVerifier::PendingOperation(type);
  operation->ids.insert(ids.begin(), ids.end());

  operation_queue_.push(linked_ptr<PendingOperation>(operation));

  // If there are no ongoing pending requests, we need to kick one off.
  if (operation_queue_.size() == 1)
    BeginFetch();
}

void InstallVerifier::AddProvisional(const ExtensionIdSet& ids) {
  provisional_.insert(ids.begin(), ids.end());
  AddMany(ids, ADD_PROVISIONAL);
}

void InstallVerifier::Remove(const std::string& id) {
  ExtensionIdSet ids;
  ids.insert(id);
  RemoveMany(ids);
}

void InstallVerifier::RemoveMany(const ExtensionIdSet& ids) {
  if (!signature_.get() || !ShouldFetchSignature())
    return;

  bool found_any = false;
  for (ExtensionIdSet::const_iterator i = ids.begin(); i != ids.end(); ++i) {
    if (ContainsKey(signature_->ids, *i) ||
        ContainsKey(signature_->invalid_ids, *i)) {
      found_any = true;
      break;
    }
  }
  if (!found_any)
    return;

  InstallVerifier::PendingOperation* operation =
      new InstallVerifier::PendingOperation(InstallVerifier::REMOVE);
  operation->ids = ids;

  operation_queue_.push(linked_ptr<PendingOperation>(operation));
  if (operation_queue_.size() == 1)
    BeginFetch();
}

std::string InstallVerifier::GetDebugPolicyProviderName() const {
  return std::string("InstallVerifier");
}

namespace {

enum MustRemainDisabledOutcome {
  VERIFIED = 0,
  NOT_EXTENSION,
  UNPACKED,
  ENTERPRISE_POLICY_ALLOWED,
  FORCED_NOT_VERIFIED,
  NOT_FROM_STORE,
  NO_SIGNATURE,
  NOT_VERIFIED_BUT_NOT_ENFORCING,
  NOT_VERIFIED,
  NOT_VERIFIED_BUT_INSTALL_TIME_NEWER_THAN_SIGNATURE,
  NOT_VERIFIED_BUT_UNKNOWN_ID,
  COMPONENT,

  // This is used in histograms - do not remove or reorder entries above! Also
  // the "MAX" item below should always be the last element.
  MUST_REMAIN_DISABLED_OUTCOME_MAX
};

void MustRemainDisabledHistogram(MustRemainDisabledOutcome outcome) {
  UMA_HISTOGRAM_ENUMERATION("ExtensionInstallVerifier.MustRemainDisabled",
                            outcome, MUST_REMAIN_DISABLED_OUTCOME_MAX);
}

}  // namespace

bool InstallVerifier::MustRemainDisabled(const Extension* extension,
                                         Extension::DisableReason* reason,
                                         base::string16* error) const {
  CHECK(extension);
  if (!CanUseExtensionApis(*extension)) {
    MustRemainDisabledHistogram(NOT_EXTENSION);
    return false;
  }
  if (Manifest::IsUnpackedLocation(extension->location())) {
    MustRemainDisabledHistogram(UNPACKED);
    return false;
  }
  if (extension->location() == Manifest::COMPONENT) {
    MustRemainDisabledHistogram(COMPONENT);
    return false;
  }
  if (AllowedByEnterprisePolicy(extension->id())) {
    MustRemainDisabledHistogram(ENTERPRISE_POLICY_ALLOWED);
    return false;
  }

  bool verified = true;
  MustRemainDisabledOutcome outcome = VERIFIED;
  if (ContainsKey(InstallSigner::GetForcedNotFromWebstore(), extension->id())) {
    verified = false;
    outcome = FORCED_NOT_VERIFIED;
  } else if (!FromStore(*extension)) {
    verified = false;
    outcome = NOT_FROM_STORE;
  } else if (signature_.get() == NULL) {
    // If we don't have a signature yet, we'll temporarily consider every
    // extension from the webstore verified to avoid false positives on existing
    // profiles hitting this code for the first time. The InstallVerifier
    // will bootstrap itself once the ExtensionsSystem is ready.
    outcome = NO_SIGNATURE;
  } else if (!IsVerified(extension->id())) {
    if (!ContainsKey(signature_->invalid_ids, extension->id())) {
      outcome = NOT_VERIFIED_BUT_UNKNOWN_ID;
    } else {
      verified = false;
      outcome = NOT_VERIFIED;
    }
  }
  if (!verified && !ShouldEnforce()) {
    verified = true;
    outcome = NOT_VERIFIED_BUT_NOT_ENFORCING;
  }
  MustRemainDisabledHistogram(outcome);

  if (!verified) {
    if (reason)
      *reason = Extension::DISABLE_NOT_VERIFIED;
    if (error)
      *error = l10n_util::GetStringFUTF16(
          IDS_EXTENSIONS_ADDED_WITHOUT_KNOWLEDGE,
          l10n_util::GetStringUTF16(IDS_EXTENSION_WEB_STORE_TITLE));
  }
  return !verified;
}

InstallVerifier::PendingOperation::PendingOperation(OperationType type)
    : type(type) {}

InstallVerifier::PendingOperation::~PendingOperation() {
}

ExtensionIdSet InstallVerifier::GetExtensionsToVerify() const {
  ExtensionIdSet result;
  scoped_ptr<ExtensionSet> extensions =
      ExtensionRegistry::Get(context_)->GenerateInstalledExtensionsSet();
  for (ExtensionSet::const_iterator iter = extensions->begin();
       iter != extensions->end();
       ++iter) {
    if (NeedsVerification(**iter))
      result.insert((*iter)->id());
  }
  return result;
}

void InstallVerifier::MaybeBootstrapSelf() {
  bool needs_bootstrap = false;

  ExtensionIdSet extension_ids = GetExtensionsToVerify();
  if (signature_.get() == NULL && ShouldFetchSignature()) {
    needs_bootstrap = true;
  } else {
    for (ExtensionIdSet::const_iterator iter = extension_ids.begin();
         iter != extension_ids.end();
         ++iter) {
      if (!IsKnownId(*iter)) {
        needs_bootstrap = true;
        break;
      }
    }
  }

  if (needs_bootstrap)
    AddMany(extension_ids, ADD_ALL_BOOTSTRAP);
}

void InstallVerifier::OnVerificationComplete(bool success,
                                             OperationType type) const {
  switch (type) {
    case ADD_SINGLE:
      LogAddVerifiedSuccess(success);
      break;
    case ADD_ALL:
    case ADD_ALL_BOOTSTRAP:
      LogVerifyAllSuccessHistogram(type == ADD_ALL_BOOTSTRAP, success);
      if (success) {
        // Iterate through the extensions and, if any are newly-verified and
        // should have the DISABLE_NOT_VERIFIED reason lifted, do so.
        const ExtensionSet& disabled_extensions =
            ExtensionRegistry::Get(context_)->disabled_extensions();
        for (ExtensionSet::const_iterator iter = disabled_extensions.begin();
             iter != disabled_extensions.end();
             ++iter) {
          int disable_reasons = prefs_->GetDisableReasons((*iter)->id());
          if (disable_reasons & Extension::DISABLE_NOT_VERIFIED &&
              !MustRemainDisabled(*iter, NULL, NULL)) {
            prefs_->RemoveDisableReason((*iter)->id(),
                                        Extension::DISABLE_NOT_VERIFIED);
          }
        }

        ExtensionSystem::Get(context_)
            ->extension_service()
            ->CheckManagementPolicy();
      }
      break;
    // We don't need to check disable reasons or report UMA stats for
    // provisional adds or removals.
    case ADD_PROVISIONAL:
    case REMOVE:
      break;
  }
}

void InstallVerifier::GarbageCollect() {
  if (!ShouldFetchSignature()) {
    return;
  }
  CHECK(signature_.get());
  ExtensionIdSet leftovers = signature_->ids;
  leftovers.insert(signature_->invalid_ids.begin(),
                   signature_->invalid_ids.end());
  ExtensionIdList all_ids;
  prefs_->GetExtensions(&all_ids);
  for (ExtensionIdList::const_iterator i = all_ids.begin();
       i != all_ids.end(); ++i) {
    ExtensionIdSet::iterator found = leftovers.find(*i);
    if (found != leftovers.end())
      leftovers.erase(found);
  }
  if (!leftovers.empty()) {
    RemoveMany(leftovers);
  }
}

bool InstallVerifier::AllowedByEnterprisePolicy(const std::string& id) const {
  PrefService* pref_service = prefs_->pref_service();
  if (pref_service->IsManagedPreference(pref_names::kInstallAllowList)) {
    const base::ListValue* whitelist =
        pref_service->GetList(pref_names::kInstallAllowList);
    base::StringValue id_value(id);
    if (whitelist && whitelist->Find(id_value) != whitelist->end())
      return true;
  }
  if (pref_service->IsManagedPreference(pref_names::kInstallForceList)) {
    const base::DictionaryValue* forcelist =
        pref_service->GetDictionary(pref_names::kInstallForceList);
    if (forcelist && forcelist->HasKey(id))
      return true;
  }
  return false;
}

bool InstallVerifier::IsVerified(const std::string& id) const {
  return ((signature_.get() && ContainsKey(signature_->ids, id)) ||
          ContainsKey(provisional_, id));
}

void InstallVerifier::BeginFetch() {
  DCHECK(ShouldFetchSignature());

  // TODO(asargent) - It would be possible to coalesce all operations in the
  // queue into one fetch - we'd probably just need to change the queue to
  // hold (set of ids, list of operation type) pairs.
  CHECK(!operation_queue_.empty());
  const PendingOperation& operation = *operation_queue_.front();

  ExtensionIdSet ids_to_sign;
  if (signature_.get()) {
    ids_to_sign.insert(signature_->ids.begin(), signature_->ids.end());
  }
  if (operation.type == InstallVerifier::REMOVE) {
    for (ExtensionIdSet::const_iterator i = operation.ids.begin();
         i != operation.ids.end(); ++i) {
      if (ContainsKey(ids_to_sign, *i))
        ids_to_sign.erase(*i);
    }
  } else {  // All other operation types are some form of "ADD".
    ids_to_sign.insert(operation.ids.begin(), operation.ids.end());
  }

  signer_.reset(new InstallSigner(context_->GetRequestContext(), ids_to_sign));
  signer_->GetSignature(base::Bind(&InstallVerifier::SignatureCallback,
                                   weak_factory_.GetWeakPtr()));
}

void InstallVerifier::SaveToPrefs() {
  if (signature_.get())
    DCHECK(InstallSigner::VerifySignature(*signature_));

  if (!signature_.get() || signature_->ids.empty()) {
    DVLOG(1) << "SaveToPrefs - saving NULL";
    prefs_->SetInstallSignature(NULL);
  } else {
    base::DictionaryValue pref;
    signature_->ToValue(&pref);
    if (VLOG_IS_ON(1)) {
      DVLOG(1) << "SaveToPrefs - saving";

      DCHECK(InstallSigner::VerifySignature(*signature_.get()));
      scoped_ptr<InstallSignature> rehydrated =
          InstallSignature::FromValue(pref);
      DCHECK(InstallSigner::VerifySignature(*rehydrated.get()));
    }
    prefs_->SetInstallSignature(&pref);
  }
}

namespace {

enum CallbackResult {
  CALLBACK_NO_SIGNATURE = 0,
  CALLBACK_INVALID_SIGNATURE,
  CALLBACK_VALID_SIGNATURE,

  // This is used in histograms - do not remove or reorder entries above! Also
  // the "MAX" item below should always be the last element.

  CALLBACK_RESULT_MAX
};

void GetSignatureResultHistogram(CallbackResult result) {
  UMA_HISTOGRAM_ENUMERATION("ExtensionInstallVerifier.GetSignatureResult",
                            result, CALLBACK_RESULT_MAX);
}

}  // namespace

void InstallVerifier::SignatureCallback(
    scoped_ptr<InstallSignature> signature) {

  linked_ptr<PendingOperation> operation = operation_queue_.front();
  operation_queue_.pop();

  bool success = false;
  if (!signature.get()) {
    GetSignatureResultHistogram(CALLBACK_NO_SIGNATURE);
  } else if (!InstallSigner::VerifySignature(*signature)) {
    GetSignatureResultHistogram(CALLBACK_INVALID_SIGNATURE);
  } else {
    GetSignatureResultHistogram(CALLBACK_VALID_SIGNATURE);
    success = true;
  }

  if (!success) {
    OnVerificationComplete(false, operation->type);

    // TODO(asargent) - if this was something like a network error, we need to
    // do retries with exponential back off.
  } else {
    signature_ = signature.Pass();
    SaveToPrefs();

    if (!provisional_.empty()) {
      // Update |provisional_| to remove ids that were successfully signed.
      provisional_ = base::STLSetDifference<ExtensionIdSet>(
          provisional_, signature_->ids);
    }

    OnVerificationComplete(success, operation->type);
  }

  if (!operation_queue_.empty())
    BeginFetch();
}

}  // namespace extensions

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