// 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 #include #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_management.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/install_signer.h" #include "chrome/common/chrome_switches.h" #include "chrome/grit/generated_resources.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/manifest_url_handlers.h" #include "extensions/common/one_shot_event.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. 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 }; #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 = base::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_STRICT; } VerifyStatus default_status = NONE; if (group == "EnforceStrict") return ENFORCE_STRICT; else 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 base::CommandLine* cmdline = base::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 if (value == "enforce_strict") return ENFORCE_STRICT; else return ENFORCE; } return NONE; } VerifyStatus GetStatus() { return std::max(GetExperimentStatus(), GetCommandLineStatus()); } bool ShouldFetchSignature() { return GetStatus() >= BOOTSTRAP; } 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 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), bootstrap_check_complete_(false), weak_factory_(this) { } InstallVerifier::~InstallVerifier() {} // static bool InstallVerifier::NeedsVerification(const Extension& extension) { return IsFromStore(extension) && CanUseExtensionApis(extension); } // static bool InstallVerifier::IsFromStore(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())); } 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 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) const { return signature_.get() && (ContainsKey(signature_->ids, id) || ContainsKey(signature_->invalid_ids, id)); } bool InstallVerifier::IsInvalid(const std::string& id) const { return ((signature_.get() && 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(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(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(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"); } 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 (!IsFromStore(*extension)) { verified = false; outcome = NOT_FROM_STORE; } else if (signature_.get() == NULL && (!bootstrap_check_complete_ || GetStatus() < 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. outcome = NO_SIGNATURE; } else if (!IsVerified(extension->id())) { if (signature_.get() && !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 extensions = ExtensionRegistry::Get(context_)->GenerateInstalledExtensionsSet(); for (ExtensionSet::const_iterator iter = extensions->begin(); iter != extensions->end(); ++iter) { if (NeedsVerification(*iter->get())) 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); else bootstrap_check_complete_ = true; } void InstallVerifier::OnVerificationComplete(bool success, OperationType type) { switch (type) { case ADD_SINGLE: LogAddVerifiedSuccess(success); break; case ADD_ALL: case ADD_ALL_BOOTSTRAP: LogVerifyAllSuccessHistogram(type == ADD_ALL_BOOTSTRAP, success); 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 & Extension::DISABLE_NOT_VERIFIED && !MustRemainDisabled(iter->get(), NULL, NULL)) { prefs_->RemoveDisableReason((*iter)->id(), Extension::DISABLE_NOT_VERIFIED); } } } if (success || GetStatus() == ENFORCE_STRICT) { 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::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 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 signature) { linked_ptr 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( provisional_, signature_->ids); } OnVerificationComplete(success, operation->type); } if (!operation_queue_.empty()) BeginFetch(); } } // namespace extensions