blob: 47af320d9cbf93963ab0f76aa1acc6376415de5f [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// 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 <utility>
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_macros.h"
#include "base/one_shot_event.h"
#include "base/ranges/algorithm.h"
#include "base/stl_util.h"
#include "base/trace_event/trace_event.h"
#include "base/values.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_management.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/install_signer.h"
#include "chrome/browser/extensions/install_verifier_factory.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/grit/generated_resources.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.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.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_url_handlers.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "ui/base/l10n/l10n_util.h"
namespace extensions {
namespace {
// This should only be set during tests.
ScopedInstallVerifierBypassForTest::ForceType* g_bypass_for_test = nullptr;
enum class 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.
ENFORCE_STRICT, // Same as ENFORCE, but hard fail if we can't fetch
// signatures.
// 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
};
VerifyStatus GetExperimentStatus() {
#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && \
(BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC))
return VerifyStatus::ENFORCE;
#else
return VerifyStatus::NONE;
#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
}
VerifyStatus GetCommandLineStatus() {
const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
if (!InstallSigner::GetForcedNotFromWebstore().empty())
return VerifyStatus::ENFORCE;
if (cmdline->HasSwitch(::switches::kExtensionsInstallVerification)) {
std::string value = cmdline->GetSwitchValueASCII(
::switches::kExtensionsInstallVerification);
if (value == "bootstrap")
return VerifyStatus::BOOTSTRAP;
else if (value == "enforce_strict")
return VerifyStatus::ENFORCE_STRICT;
else
return VerifyStatus::ENFORCE;
}
return VerifyStatus::NONE;
}
VerifyStatus GetStatus() {
if (g_bypass_for_test) {
switch (*g_bypass_for_test) {
case ScopedInstallVerifierBypassForTest::kForceOn:
return VerifyStatus::ENFORCE_STRICT;
case ScopedInstallVerifierBypassForTest::kForceOff:
return VerifyStatus::NONE;
}
}
return std::max(GetExperimentStatus(), GetCommandLineStatus());
}
bool ShouldFetchSignature() {
return GetStatus() >= VerifyStatus::BOOTSTRAP;
}
bool CanUseExtensionApis(const Extension& extension) {
return extension.is_extension() || extension.is_legacy_packaged_app();
}
} // namespace
InstallVerifier::InstallVerifier(ExtensionPrefs* prefs,
content::BrowserContext* context)
: prefs_(prefs), context_(context), bootstrap_check_complete_(false) {}
InstallVerifier::~InstallVerifier() = default;
// static
InstallVerifier* InstallVerifier::Get(
content::BrowserContext* browser_context) {
return InstallVerifierFactory::GetForBrowserContext(browser_context);
}
// static
bool InstallVerifier::ShouldEnforce() {
return GetStatus() >= VerifyStatus::ENFORCE;
}
// static
bool InstallVerifier::NeedsVerification(const Extension& extension,
content::BrowserContext* context) {
return IsFromStore(extension, context) && CanUseExtensionApis(extension);
}
// static
bool InstallVerifier::IsFromStore(const Extension& extension,
content::BrowserContext* context) {
return extension.from_webstore() ||
ExtensionManagementFactory::GetForBrowserContext(context)
->UpdatesFromWebstore(extension);
}
void InstallVerifier::Init() {
TRACE_EVENT0("browser,startup", "extensions::InstallVerifier::Init");
std::unique_ptr<InstallSignature> signature_from_prefs =
InstallSignature::FromDict(prefs_->GetInstallSignature());
if (signature_from_prefs.get()) {
if (!InstallSigner::VerifySignature(*signature_from_prefs)) {
DVLOG(1) << "Init - ignoring invalid signature";
} else {
signature_ = std::move(signature_from_prefs);
GarbageCollect();
}
}
ExtensionSystem::Get(context_)->ready().Post(
FROM_HERE, base::BindOnce(&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) const {
return signature_.get() && (base::Contains(signature_->ids, id) ||
base::Contains(signature_->invalid_ids, id));
}
bool InstallVerifier::IsInvalid(const std::string& id) const {
return ((signature_.get() && base::Contains(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;
}
}
std::unique_ptr<InstallVerifier::PendingOperation> operation(
new InstallVerifier::PendingOperation(type));
operation->ids.insert(ids.begin(), ids.end());
operation_queue_.push(std::move(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;
if (base::ranges::any_of(ids, [this](const std::string& id) {
return base::Contains(signature_->ids, id) ||
base::Contains(signature_->invalid_ids, id);
})) {
return;
}
std::unique_ptr<InstallVerifier::PendingOperation> operation(
new InstallVerifier::PendingOperation(InstallVerifier::REMOVE));
operation->ids = ids;
operation_queue_.push(std::move(operation));
if (operation_queue_.size() == 1)
BeginFetch();
}
bool InstallVerifier::AllowedByEnterprisePolicy(const std::string& id) const {
return ExtensionManagementFactory::GetForBrowserContext(context_)
->IsInstallationExplicitlyAllowed(id);
}
std::string InstallVerifier::GetDebugPolicyProviderName() const {
return std::string("InstallVerifier");
}
bool InstallVerifier::MustRemainDisabled(const Extension* extension,
disable_reason::DisableReason* reason,
std::u16string* error) const {
CHECK(extension);
if (!CanUseExtensionApis(*extension))
return false;
if (Manifest::IsUnpackedLocation(extension->location()))
return false;
if (extension->location() == mojom::ManifestLocation::kComponent)
return false;
if (AllowedByEnterprisePolicy(extension->id()))
return false;
bool verified = true;
if (base::Contains(InstallSigner::GetForcedNotFromWebstore(),
extension->id())) {
verified = false;
} else if (!IsFromStore(*extension, context_)) {
verified = false;
} else if (!signature_ && (!bootstrap_check_complete_ ||
GetStatus() < VerifyStatus::ENFORCE_STRICT)) {
// 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.
// |verified| is already set to true.
} else if (!IsVerified(extension->id())) {
// Transient network failures can create a stale signature missing recently
// added extension ids. To avoid false positives, consider all extensions to
// be from the webstore unless the signature explicitly lists the extension
// as invalid.
if (!signature_ ||
base::Contains(signature_->invalid_ids, extension->id()) ||
GetStatus() >= VerifyStatus::ENFORCE_STRICT) {
verified = false;
}
}
if (!verified && !ShouldEnforce())
verified = true;
if (!verified) {
DLOG(WARNING) << "Disabling extension " << extension->id() << " ('"
<< extension->name()
<< "') due to install verification failure. In tests you "
<< "might want to use a ScopedInstallVerifierBypassForTest "
<< "instance to prevent this.";
if (reason)
*reason = disable_reason::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() = default;
ExtensionIdSet InstallVerifier::GetExtensionsToVerify() const {
ExtensionIdSet result;
const ExtensionSet extensions =
ExtensionRegistry::Get(context_)->GenerateInstalledExtensionsSet();
for (const auto& extension : extensions) {
if (NeedsVerification(*extension, context_)) {
result.insert(extension->id());
}
}
return result;
}
void InstallVerifier::MaybeBootstrapSelf() {
ExtensionIdSet extension_ids = GetExtensionsToVerify();
if ((signature_.get() == nullptr && ShouldFetchSignature()) ||
base::ranges::any_of(extension_ids, [this](const std::string& id) {
return !IsKnownId(id);
})) {
AddMany(extension_ids, ADD_ALL_BOOTSTRAP);
} else {
bootstrap_check_complete_ = true;
}
}
void InstallVerifier::OnVerificationComplete(bool success, OperationType type) {
switch (type) {
case ADD_ALL:
case ADD_ALL_BOOTSTRAP:
bootstrap_check_complete_ = true;
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 & disable_reason::DISABLE_NOT_VERIFIED &&
!MustRemainDisabled(iter->get(), nullptr, nullptr)) {
prefs_->RemoveDisableReason((*iter)->id(),
disable_reason::DISABLE_NOT_VERIFIED);
}
}
}
if (success || GetStatus() == VerifyStatus::ENFORCE_STRICT) {
ExtensionSystem::Get(context_)
->extension_service()
->CheckManagementPolicy();
}
break;
// We don't need to check disable reasons for provisional adds or removals.
case ADD_PROVISIONAL:
case ADD_SINGLE:
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());
for (const auto& extension_id : prefs_->GetExtensions()) {
leftovers.erase(extension_id);
}
if (!leftovers.empty()) {
RemoveMany(leftovers);
}
}
bool InstallVerifier::IsVerified(const std::string& id) const {
return ((signature_.get() && base::Contains(signature_->ids, id)) ||
base::Contains(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 (const std::string& id : operation.ids) {
if (base::Contains(ids_to_sign, id))
ids_to_sign.erase(id);
}
} else { // All other operation types are some form of "ADD".
ids_to_sign.insert(operation.ids.begin(), operation.ids.end());
}
auto url_loader_factory = context_->GetDefaultStoragePartition()
->GetURLLoaderFactoryForBrowserProcess();
signer_ = std::make_unique<InstallSigner>(url_loader_factory, ids_to_sign);
signer_->GetSignature(base::BindOnce(&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(nullptr);
} else {
base::Value::Dict pref = signature_->ToDict();
if (VLOG_IS_ON(1)) {
DVLOG(1) << "SaveToPrefs - saving";
DCHECK(InstallSigner::VerifySignature(*signature_));
std::unique_ptr<InstallSignature> rehydrated =
InstallSignature::FromDict(pref);
DCHECK(InstallSigner::VerifySignature(*rehydrated));
}
prefs_->SetInstallSignature(&pref);
}
}
void InstallVerifier::SignatureCallback(
std::unique_ptr<InstallSignature> signature) {
std::unique_ptr<PendingOperation> operation =
std::move(operation_queue_.front());
operation_queue_.pop();
bool success = signature.get() && InstallSigner::VerifySignature(*signature);
if (success) {
signature_ = std::move(signature);
SaveToPrefs();
if (!provisional_.empty()) {
// Update |provisional_| to remove ids that were successfully signed.
provisional_ =
base::STLSetDifference<ExtensionIdSet>(provisional_, signature_->ids);
}
}
// TODO(asargent) - if this was something like a network error, we need to
// do retries with exponential back off.
OnVerificationComplete(success, operation->type);
if (!operation_queue_.empty())
BeginFetch();
}
ScopedInstallVerifierBypassForTest::ScopedInstallVerifierBypassForTest(
ForceType force_type)
: value_(force_type), old_value_(g_bypass_for_test) {
g_bypass_for_test = &value_;
}
ScopedInstallVerifierBypassForTest::~ScopedInstallVerifierBypassForTest() {
g_bypass_for_test = old_value_;
}
} // namespace extensions