diff options
author | asargent@chromium.org <asargent@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-14 00:11:46 +0000 |
---|---|---|
committer | asargent@chromium.org <asargent@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-14 00:11:46 +0000 |
commit | ffd2f79ed3968c9aff2ea1c29e7ef6e3815f6f7d (patch) | |
tree | 6264db35a93f49dd871ead88734a1532461ac7f6 | |
parent | 09feedf8a47a2430847e566984b137a572969e0c (diff) | |
download | chromium_src-ffd2f79ed3968c9aff2ea1c29e7ef6e3815f6f7d.zip chromium_src-ffd2f79ed3968c9aff2ea1c29e7ef6e3815f6f7d.tar.gz chromium_src-ffd2f79ed3968c9aff2ea1c29e7ef6e3815f6f7d.tar.bz2 |
Base support for verifying extension installs
This code is the start of providing a mechanism for verifying that
extensions are installed from the webstore or via enterprise policy,
and not force-installed by malicious third-parties.
BUG=318479
Review URL: https://codereview.chromium.org/70103003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@234970 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/extensions/extension_prefs.cc | 23 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_prefs.h | 5 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_service.cc | 9 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_system.cc | 15 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_system.h | 7 | ||||
-rw-r--r-- | chrome/browser/extensions/install_signer.cc | 292 | ||||
-rw-r--r-- | chrome/browser/extensions/install_signer.h | 109 | ||||
-rw-r--r-- | chrome/browser/extensions/install_verifier.cc | 353 | ||||
-rw-r--r-- | chrome/browser/extensions/install_verifier.h | 136 | ||||
-rw-r--r-- | chrome/browser/extensions/test_extension_system.cc | 7 | ||||
-rw-r--r-- | chrome/browser/extensions/test_extension_system.h | 2 | ||||
-rw-r--r-- | chrome/browser/extensions/webstore_installer.cc | 8 | ||||
-rw-r--r-- | chrome/chrome_browser_extensions.gypi | 4 | ||||
-rw-r--r-- | chrome/common/chrome_switches.cc | 8 | ||||
-rw-r--r-- | chrome/common/chrome_switches.h | 2 | ||||
-rw-r--r-- | chrome/common/extensions/manifest_url_handler.cc | 8 | ||||
-rw-r--r-- | chrome/common/extensions/manifest_url_handler.h | 5 |
17 files changed, 992 insertions, 1 deletions
diff --git a/chrome/browser/extensions/extension_prefs.cc b/chrome/browser/extensions/extension_prefs.cc index e10ef94..f021de4 100644 --- a/chrome/browser/extensions/extension_prefs.cc +++ b/chrome/browser/extensions/extension_prefs.cc @@ -189,6 +189,9 @@ const char kPrefGeometryCache[] = "geometry_cache"; // A preference that indicates when an extension is last launched. const char kPrefLastLaunchTime[] = "last_launch_time"; +// A list of installed ids and a signature. +const char kInstallSignature[] = "extensions.install_signature"; + // Provider of write access to a dictionary storing extension prefs. class ScopedExtensionPrefUpdate : public DictionaryPrefUpdate { public: @@ -1714,6 +1717,21 @@ void ExtensionPrefs::SetGeometryCache( UpdateExtensionPref(extension_id, kPrefGeometryCache, cache.release()); } +const DictionaryValue* ExtensionPrefs::GetInstallSignature() { + return prefs_->GetDictionary(kInstallSignature); +} + +void ExtensionPrefs::SetInstallSignature(const DictionaryValue* signature) { + if (signature) { + prefs_->Set(kInstallSignature, *signature); + DVLOG(1) << "SetInstallSignature - saving"; + } else { + DVLOG(1) << "SetInstallSignature - clearing"; + prefs_->ClearPref(kInstallSignature); + } +} + + ExtensionPrefs::ExtensionPrefs( PrefService* prefs, const base::FilePath& root_dir, @@ -1787,13 +1805,16 @@ void ExtensionPrefs::RegisterProfilePrefs( user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); registry->RegisterListPref(prefs::kExtensionKnownDisabled, user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); - #if defined(TOOLKIT_VIEWS) registry->RegisterIntegerPref( prefs::kBrowserActionContainerWidth, 0, user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); #endif + registry->RegisterDictionaryPref( + kInstallSignature, + user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); + } template <class ExtensionIdContainer> diff --git a/chrome/browser/extensions/extension_prefs.h b/chrome/browser/extensions/extension_prefs.h index edc98fd..081eb66 100644 --- a/chrome/browser/extensions/extension_prefs.h +++ b/chrome/browser/extensions/extension_prefs.h @@ -510,6 +510,11 @@ class ExtensionPrefs : public ExtensionScopedPrefs, void SetGeometryCache(const std::string& extension_id, scoped_ptr<base::DictionaryValue> cache); + // Used for verification of installed extension ids. For the Set method, pass + // null to remove the preference. + const base::DictionaryValue* GetInstallSignature(); + void SetInstallSignature(const DictionaryValue* signature); + private: friend class ExtensionPrefsBlacklistedExtensions; // Unit test. friend class ExtensionPrefsUninstallExtension; // Unit test. diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc index 9fdefc0..2b39df1 100644 --- a/chrome/browser/extensions/extension_service.cc +++ b/chrome/browser/extensions/extension_service.cc @@ -52,6 +52,7 @@ #include "chrome/browser/extensions/external_install_ui.h" #include "chrome/browser/extensions/external_provider_impl.h" #include "chrome/browser/extensions/external_provider_interface.h" +#include "chrome/browser/extensions/install_verifier.h" #include "chrome/browser/extensions/installed_loader.h" #include "chrome/browser/extensions/management_policy.h" #include "chrome/browser/extensions/pending_extension_manager.h" @@ -120,6 +121,7 @@ using extensions::Extension; using extensions::ExtensionIdSet; using extensions::ExtensionInfo; using extensions::FeatureSwitch; +using extensions::InstallVerifier; using extensions::ManagementPolicy; using extensions::Manifest; using extensions::PermissionMessage; @@ -774,6 +776,9 @@ bool ExtensionService::UninstallExtension( extension.get(), is_ready()); } + extensions::ExtensionSystem::Get(profile_)->install_verifier()->Remove( + extension->id()); + if (IsUnacknowledgedExternalExtension(extension.get())) { UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEvent", EXTERNAL_EXTENSION_UNINSTALLED, @@ -2175,6 +2180,10 @@ void ExtensionService::AddNewOrUpdatedExtension( blacklisted_for_malware, page_ordinal); delayed_installs_.Remove(extension->id()); + if (extensions::ManifestURL::UpdatesFromGallery(extension)) { + extensions::ExtensionSystem::Get(profile_)->install_verifier()->Add( + extension->id(), InstallVerifier::AddResultCallback()); + } FinishInstallation(extension); } diff --git a/chrome/browser/extensions/extension_system.cc b/chrome/browser/extensions/extension_system.cc index 007b1f4..bb18b07 100644 --- a/chrome/browser/extensions/extension_system.cc +++ b/chrome/browser/extensions/extension_system.cc @@ -25,6 +25,7 @@ #include "chrome/browser/extensions/extension_util.h" #include "chrome/browser/extensions/extension_warning_badge_service.h" #include "chrome/browser/extensions/extension_warning_set.h" +#include "chrome/browser/extensions/install_verifier.h" #include "chrome/browser/extensions/management_policy.h" #include "chrome/browser/extensions/navigation_observer.h" #include "chrome/browser/extensions/standard_management_policy_provider.h" @@ -152,6 +153,8 @@ void ExtensionSystemImpl::Shared::RegisterManagementPolicyProviders() { } #endif // defined (OS_CHROMEOS) + management_policy_->RegisterProvider(install_verifier_.get()); + #endif // defined(ENABLE_EXTENSIONS) } @@ -186,6 +189,10 @@ void ExtensionSystemImpl::Shared::Init(bool extensions_enabled) { // These services must be registered before the ExtensionService tries to // load any extensions. { + install_verifier_.reset(new InstallVerifier(ExtensionPrefs::Get(profile_), + profile_->GetRequestContext())); + install_verifier_->Init(); + management_policy_.reset(new ManagementPolicy); RegisterManagementPolicyProviders(); } @@ -304,6 +311,10 @@ ErrorConsole* ExtensionSystemImpl::Shared::error_console() { return error_console_.get(); } +InstallVerifier* ExtensionSystemImpl::Shared::install_verifier() { + return install_verifier_.get(); +} + // // ExtensionSystemImpl // @@ -394,6 +405,10 @@ ErrorConsole* ExtensionSystemImpl::error_console() { return shared_->error_console(); } +InstallVerifier* ExtensionSystemImpl::install_verifier() { + return shared_->install_verifier(); +} + void ExtensionSystemImpl::RegisterExtensionWithRequestContexts( const Extension* extension) { base::Time install_time; diff --git a/chrome/browser/extensions/extension_system.h b/chrome/browser/extensions/extension_system.h index bf2522c..a96cfeb 100644 --- a/chrome/browser/extensions/extension_system.h +++ b/chrome/browser/extensions/extension_system.h @@ -35,6 +35,7 @@ class ExtensionSystemSharedFactory; class ExtensionWarningBadgeService; class ExtensionWarningService; class InfoMap; +class InstallVerifier; class LazyBackgroundTaskQueue; class ManagementPolicy; class NavigationObserver; @@ -111,6 +112,9 @@ class ExtensionSystem : public BrowserContextKeyedService { // The ErrorConsole is created at startup. virtual ErrorConsole* error_console() = 0; + // The InstallVerifier is created at startup. + virtual InstallVerifier* install_verifier() = 0; + // Called by the ExtensionService that lives in this system. Gives the // info map a chance to react to the load event before the EXTENSION_LOADED // notification has fired. The purpose for handling this event first is to @@ -159,6 +163,7 @@ class ExtensionSystemImpl : public ExtensionSystem { virtual ExtensionWarningService* warning_service() OVERRIDE; virtual Blacklist* blacklist() OVERRIDE; // shared virtual ErrorConsole* error_console() OVERRIDE; + virtual InstallVerifier* install_verifier() OVERRIDE; virtual void RegisterExtensionWithRequestContexts( const Extension* extension) OVERRIDE; @@ -199,6 +204,7 @@ class ExtensionSystemImpl : public ExtensionSystem { EventRouter* event_router(); ExtensionWarningService* warning_service(); ErrorConsole* error_console(); + InstallVerifier* install_verifier(); const OneShotEvent& ready() const { return ready_; } private: @@ -226,6 +232,7 @@ class ExtensionSystemImpl : public ExtensionSystem { scoped_ptr<ExtensionWarningService> extension_warning_service_; scoped_ptr<ExtensionWarningBadgeService> extension_warning_badge_service_; scoped_ptr<ErrorConsole> error_console_; + scoped_ptr<InstallVerifier> install_verifier_; #if defined(OS_CHROMEOS) scoped_ptr<chromeos::DeviceLocalAccountManagementPolicyProvider> diff --git a/chrome/browser/extensions/install_signer.cc b/chrome/browser/extensions/install_signer.cc new file mode 100644 index 0000000..a144b99 --- /dev/null +++ b/chrome/browser/extensions/install_signer.cc @@ -0,0 +1,292 @@ +// 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_signer.h" + +#include "base/base64.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/message_loop/message_loop.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/values.h" +#include "chrome/common/chrome_switches.h" +#include "crypto/random.h" +#include "crypto/secure_hash.h" +#include "crypto/sha2.h" +#include "net/url_request/url_fetcher.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "net/url_request/url_request_context_getter.h" +#include "url/gurl.h" + +#if defined(ENABLE_RLZ) +#include "rlz/lib/machine_id.h" +#endif + +namespace { + +using extensions::ExtensionIdSet; + +const char kExpireDateKey[] = "expire_date"; +const char kIdsKey[] = "ids"; +const char kSaltKey[] = "salt"; +const char kSignatureKey[] = "signature"; +const size_t kSaltBytes = 32; + +// Returns a date 12 weeks from now in YYYY-MM-DD format. +std::string GetExpiryString() { + base::Time later = base::Time::Now() + base::TimeDelta::FromDays(7*12); + base::Time::Exploded exploded; + later.LocalExplode(&exploded); + return base::StringPrintf("%d-%02d-%02d", + exploded.year, + exploded.month, + exploded.day_of_month); +} + +void FakeSignData(const ExtensionIdSet& ids, + const std::string& hash_base64, + const std::string& expire_date, + std::string* signature, + ExtensionIdSet* invalid_ids) { + DCHECK(invalid_ids && invalid_ids->empty()); + DCHECK(IsStringASCII(hash_base64)); + + ExtensionIdSet invalid = + extensions::InstallSigner::GetForcedNotFromWebstore(); + + std::string data_to_sign; + for (ExtensionIdSet::const_iterator i = ids.begin(); i != ids.end(); ++i) { + if (ContainsKey(invalid, (*i))) + invalid_ids->insert(*i); + else + data_to_sign.append(*i); + } + data_to_sign.append(hash_base64); + data_to_sign.append(expire_date); + + *signature = std::string("FakeSignature:") + + crypto::SHA256HashString(data_to_sign); +} + +void FakeSignDataWithCallback( + const ExtensionIdSet& ids, + const std::string& hash_base64, + const base::Callback<void(const std::string&, + const std::string&, + const ExtensionIdSet&)>& callback) { + std::string signature; + std::string expire_date = GetExpiryString(); + ExtensionIdSet invalid_ids; + FakeSignData(ids, hash_base64, expire_date, &signature, &invalid_ids); + callback.Run(signature, expire_date, invalid_ids); +} + +bool FakeCheckSignature(const extensions::ExtensionIdSet& ids, + const std::string& hash_base64, + const std::string& signature, + const std::string& expire_date) { + std::string computed_signature; + extensions::ExtensionIdSet invalid_ids; + FakeSignData(ids, hash_base64, expire_date, &computed_signature, + &invalid_ids); + return invalid_ids.empty() && computed_signature == signature; +} + +// Hashes |salt| with the machine id, base64-encodes it and returns it in +// |result|. +bool HashWithMachineId(const std::string& salt, std::string* result) { + std::string machine_id; +#if defined(ENABLE_RLZ) + if (!rlz_lib::GetMachineId(&machine_id)) + return false; +#endif + + scoped_ptr<crypto::SecureHash> hash( + crypto::SecureHash::Create(crypto::SecureHash::SHA256)); + + hash->Update(machine_id.data(), machine_id.size()); + hash->Update(salt.data(), salt.size()); + + std::string result_bytes(crypto::kSHA256Length, 0); + hash->Finish(string_as_array(&result_bytes), result_bytes.size()); + + return base::Base64Encode(result_bytes, result); +} + +} // namespace + +namespace extensions { + +InstallSignature::InstallSignature() { +} +InstallSignature::~InstallSignature() { +} + +void InstallSignature::ToValue(base::DictionaryValue* value) const { + CHECK(value); + DCHECK(!signature.empty()); + DCHECK(!salt.empty()); + + base::ListValue* id_list = new base::ListValue(); + for (ExtensionIdSet::const_iterator i = ids.begin(); i != ids.end(); + ++i) + id_list->AppendString(*i); + + value->Set(kIdsKey, id_list); + value->SetString(kExpireDateKey, expire_date); + std::string salt_base64; + std::string signature_base64; + base::Base64Encode(salt, &salt_base64); + base::Base64Encode(signature, &signature_base64); + value->SetString(kSaltKey, salt_base64); + value->SetString(kSignatureKey, signature_base64); +} + +// static +scoped_ptr<InstallSignature> InstallSignature::FromValue( + const base::DictionaryValue& value) { + + scoped_ptr<InstallSignature> result(new InstallSignature); + + std::string salt_base64; + std::string signature_base64; + if (!value.GetString(kExpireDateKey, &result->expire_date) || + !value.GetString(kSaltKey, &salt_base64) || + !value.GetString(kSignatureKey, &signature_base64) || + !base::Base64Decode(salt_base64, &result->salt) || + !base::Base64Decode(signature_base64, &result->signature)) { + result.reset(); + return result.Pass(); + } + + const base::ListValue* ids = NULL; + if (!value.GetList(kIdsKey, &ids)) { + result.reset(); + return result.Pass(); + } + + for (ListValue::const_iterator i = ids->begin(); i != ids->end(); ++i) { + std::string id; + if (!(*i)->GetAsString(&id)) { + result.reset(); + return result.Pass(); + } + result->ids.insert(id); + } + + return result.Pass(); +} + + +InstallSigner::InstallSigner(net::URLRequestContextGetter* context_getter, + const ExtensionIdSet& ids) + : ids_(ids), context_getter_(context_getter) { +} + +InstallSigner::~InstallSigner() { +} + +// static +bool InstallSigner::VerifySignature(const InstallSignature& signature) { + if (signature.ids.empty()) + return true; + + std::string hash_base64; + if (!HashWithMachineId(signature.salt, &hash_base64)) + return false; + + return FakeCheckSignature(signature.ids, hash_base64, signature.signature, + signature.expire_date); +} + + +class InstallSigner::FetcherDelegate : public net::URLFetcherDelegate { + public: + explicit FetcherDelegate(const base::Closure& callback) + : callback_(callback) { + } + + virtual ~FetcherDelegate() { + } + + virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE { + callback_.Run(); + } + + private: + base::Closure callback_; + DISALLOW_COPY_AND_ASSIGN(FetcherDelegate); +}; + +// static +ExtensionIdSet InstallSigner::GetForcedNotFromWebstore() { + std::string value = CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kExtensionsNotWebstore); + if (value.empty()) + return ExtensionIdSet(); + + std::vector<std::string> ids; + base::SplitString(value, ',', &ids); + return ExtensionIdSet(ids.begin(), ids.end()); +} + +void InstallSigner::GetSignature(const SignatureCallback& callback) { + CHECK(!url_fetcher_.get()); + CHECK(callback_.is_null()); + CHECK(salt_.empty()); + callback_ = callback; + + // If the set of ids is empty, just return an empty signature and skip the + // call to the server. + if (ids_.empty()) { + callback_.Run(scoped_ptr<InstallSignature>(new InstallSignature())); + return; + } + + salt_ = std::string(kSaltBytes, 0); + DCHECK_EQ(kSaltBytes, salt_.size()); + crypto::RandBytes(string_as_array(&salt_), salt_.size()); + + std::string hash_base64; + if (!HashWithMachineId(salt_, &hash_base64)) { + if (!callback_.is_null()) + callback_.Run(scoped_ptr<InstallSignature>()); + return; + } + + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&FakeSignDataWithCallback, + ids_, + hash_base64, + base::Bind(&InstallSigner::HandleSignatureResult, + base::Unretained(this)))); +} + +void InstallSigner::HandleSignatureResult(const std::string& signature, + const std::string& expire_date, + const ExtensionIdSet& invalid_ids) { + ExtensionIdSet valid_ids = + base::STLSetDifference<ExtensionIdSet>(ids_, invalid_ids); + + scoped_ptr<InstallSignature> result; + if (!signature.empty()) { + result.reset(new InstallSignature); + result->ids = valid_ids; + result->salt = salt_; + result->signature = signature; + result->expire_date = expire_date; + DCHECK(VerifySignature(*result)); + } + + if (!callback_.is_null()) + callback_.Run(result.Pass()); +} + + +} // namespace extensions diff --git a/chrome/browser/extensions/install_signer.h b/chrome/browser/extensions/install_signer.h new file mode 100644 index 0000000..b110bef --- /dev/null +++ b/chrome/browser/extensions/install_signer.h @@ -0,0 +1,109 @@ +// 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. + +#ifndef CHROME_BROWSER_EXTENSIONS_INSTALL_SIGNER_H_ +#define CHROME_BROWSER_EXTENSIONS_INSTALL_SIGNER_H_ + +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/common/extensions/extension.h" + +namespace base { +class DictionaryValue; +} + +namespace net { +class URLFetcher; +class URLRequestContextGetter; +} + +namespace extensions { + +// This represents a list of ids signed with a private key using an algorithm +// that includes some salt bytes. +struct InstallSignature { + // The set of ids that have been signed. + ExtensionIdSet ids; + + // Both of these are just arrays of bytes, NOT base64-encoded. + std::string salt; + std::string signature; + + // The date that the signature should expire, in YYYY-MM-DD format. + std::string expire_date; + + InstallSignature(); + ~InstallSignature(); + + // Helper methods for serialization to/from a base::DictionaryValue. + void ToValue(base::DictionaryValue* value) const; + + static scoped_ptr<InstallSignature> FromValue( + const base::DictionaryValue& value); +}; + +// Objects of this class encapsulate an operation to get a signature proving +// that a set of ids are hosted in the webstore. +class InstallSigner { + public: + typedef base::Callback<void(scoped_ptr<InstallSignature>)> SignatureCallback; + + // IMPORTANT NOTE: It is possible that only some, but not all, of the entries + // in |ids| will be successfully signed by the backend. Callers should always + // check the set of ids in the InstallSignature passed to their callback, as + // it may contain only a subset of the ids they passed in. + InstallSigner(net::URLRequestContextGetter* context_getter, + const ExtensionIdSet& ids); + ~InstallSigner(); + + // Returns a set of ids that are forced to be considered not from webstore, + // e.g. by a command line flag used for testing. + static ExtensionIdSet GetForcedNotFromWebstore(); + + // Begins the process of fetching a signature from the backend. This should + // only be called once! If you want to get another signature, make another + // instance of this class. + void GetSignature(const SignatureCallback& callback); + + // Returns whether the signature in InstallSignature is properly signed with a + // known public key. + static bool VerifySignature(const InstallSignature& signature); + + private: + // A very simple delegate just used to call ourself back when a url fetch is + // complete. + class FetcherDelegate; + + // Handles the result from a backend fetch. + void HandleSignatureResult(const std::string& signature, + const std::string& expire_date, + const ExtensionIdSet& invalid_ids); + + // The final callback for when we're done. + SignatureCallback callback_; + + // The current set of ids we're trying to verify. This may contain fewer ids + // than we started with. + ExtensionIdSet ids_; + + // An array of random bytes used as an input to hash with the machine id, + // which will need to be persisted in the eventual InstallSignature we get. + std::string salt_; + + // These are used to make the call to a backend server for a signature. + net::URLRequestContextGetter* context_getter_; + scoped_ptr<net::URLFetcher> url_fetcher_; + scoped_ptr<FetcherDelegate> delegate_; + + DISALLOW_COPY_AND_ASSIGN(InstallSigner); +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_INSTALL_SIGNER_H_ diff --git a/chrome/browser/extensions/install_verifier.cc b/chrome/browser/extensions/install_verifier.cc new file mode 100644 index 0000000..4b7f347 --- /dev/null +++ b/chrome/browser/extensions/install_verifier.cc @@ -0,0 +1,353 @@ +// 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 "base/bind.h" +#include "base/command_line.h" +#include "base/metrics/histogram.h" +#include "base/prefs/pref_service.h" +#include "base/stl_util.h" +#include "chrome/browser/extensions/extension_prefs.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 "extensions/common/manifest.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { + +enum VerifyStatus { + NONE, // 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. +}; + +VerifyStatus GetStatus() { + const CommandLine* cmdline = CommandLine::ForCurrentProcess(); + if (!extensions::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; +} + +bool ShouldFetchSignature() { + VerifyStatus status = GetStatus(); + return (status == BOOTSTRAP || status == ENFORCE); +} + +bool ShouldEnforce() { + return GetStatus() == ENFORCE; +} + +} // namespace + +namespace extensions { + +InstallVerifier::InstallVerifier(ExtensionPrefs* prefs, + net::URLRequestContextGetter* context_getter) + : prefs_(prefs), context_getter_(context_getter) { +} + +InstallVerifier::~InstallVerifier() {} + +void InstallVerifier::Init() { + const DictionaryValue* pref = prefs_->GetInstallSignature(); + if (pref) { + scoped_ptr<InstallSignature> signature_from_prefs = + InstallSignature::FromValue(*pref); + if (!signature_from_prefs.get()) { + UMA_HISTOGRAM_BOOLEAN("InstallVerifier.InitUnparseablePref", true); + } else if (!InstallSigner::VerifySignature(*signature_from_prefs.get())) { + UMA_HISTOGRAM_BOOLEAN("InstallVerifier.InitInvalidSignature", true); + DVLOG(1) << "Init - ignoring invalid signature"; + } else { + signature_ = signature_from_prefs.Pass(); + GarbageCollect(); + } + } + + if (!signature_.get() && ShouldFetchSignature()) { + // We didn't have any signature but are in fetch mode, so do a request for + // a signature if needed. + scoped_ptr<ExtensionPrefs::ExtensionsInfo> all_info = + prefs_->GetInstalledExtensionsInfo(); + ExtensionIdSet to_add; + if (all_info.get()) { + for (ExtensionPrefs::ExtensionsInfo::const_iterator i = all_info->begin(); + i != all_info->end(); ++i) { + const ExtensionInfo& info = **i; + const base::DictionaryValue* manifest = info.extension_manifest.get(); + if (manifest && ManifestURL::UpdatesFromGallery(manifest)) + to_add.insert(info.extension_id); + } + } + if (!to_add.empty()) + AddMany(to_add, AddResultCallback()); + } +} + +void InstallVerifier::Add(const std::string& id, + const AddResultCallback& callback) { + ExtensionIdSet ids; + ids.insert(id); + AddMany(ids, callback); +} + +void InstallVerifier::AddMany(const ExtensionIdSet& ids, + const AddResultCallback& callback) { + if (!ShouldFetchSignature()) + return; + + if (signature_.get()) { + ExtensionIdSet not_allowed_yet = + base::STLSetDifference<ExtensionIdSet>(ids, signature_->ids); + if (not_allowed_yet.empty()) { + if (!callback.is_null()) + callback.Run(true); + return; + } + } + + InstallVerifier::PendingOperation* operation = + new InstallVerifier::PendingOperation(); + operation->type = InstallVerifier::ADD; + operation->ids.insert(ids.begin(), ids.end()); + operation->callback = callback; + + 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, AddResultCallback()); +} + +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)) { + found_any = true; + break; + } + } + if (!found_any) + return; + + InstallVerifier::PendingOperation* operation = + new InstallVerifier::PendingOperation(); + operation->type = 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"); +} + +static bool FromStore(const Extension* extension) { + bool updates_from_store = ManifestURL::UpdatesFromGallery(extension); + return extension->from_webstore() || updates_from_store; +} + +bool InstallVerifier::MustRemainDisabled(const Extension* extension, + Extension::DisableReason* reason, + string16* error) const { + if (!ShouldEnforce() || !extension->is_extension() || + Manifest::IsUnpackedLocation(extension->location()) || + AllowedByEnterprisePolicy(extension->id())) + return false; + + bool verified = + FromStore(extension) && + IsVerified(extension->id()) && + !ContainsKey(InstallSigner::GetForcedNotFromWebstore(), extension->id()); + + 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() { + type = InstallVerifier::ADD; +} + +InstallVerifier::PendingOperation::~PendingOperation() { +} + +void InstallVerifier::GarbageCollect() { + if (!ShouldFetchSignature()) { + return; + } + CHECK(signature_.get()); + ExtensionIdSet leftovers = signature_->ids; + 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(prefs::kExtensionInstallAllowList)) { + const base::ListValue* whitelist = + pref_service->GetList(prefs::kExtensionInstallAllowList); + base::StringValue id_value(id); + if (whitelist && whitelist->Find(id_value) != whitelist->end()) + return true; + } + if (pref_service->IsManagedPreference(prefs::kExtensionInstallForceList)) { + const base::DictionaryValue* forcelist = + pref_service->GetDictionary(prefs::kExtensionInstallForceList); + 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 callbacks) 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::ADD) { + ids_to_sign.insert(operation.ids.begin(), operation.ids.end()); + } else { + for (ExtensionIdSet::const_iterator i = operation.ids.begin(); + i != operation.ids.end(); ++i) { + if (ContainsKey(ids_to_sign, *i)) + ids_to_sign.erase(*i); + } + } + + signer_.reset(new InstallSigner(context_getter_, ids_to_sign)); + signer_->GetSignature(base::Bind(&InstallVerifier::SignatureCallback, + base::Unretained(this))); +} + +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 { + 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); + } +} + +void InstallVerifier::SignatureCallback( + scoped_ptr<InstallSignature> signature) { + + linked_ptr<PendingOperation> operation = operation_queue_.front(); + operation_queue_.pop(); + + bool success = false; + if (!signature.get()) { + UMA_HISTOGRAM_BOOLEAN("InstallVerifier.CallbackNoSignature", true); + } else if (!InstallSigner::VerifySignature(*signature)) { + UMA_HISTOGRAM_BOOLEAN("InstallVerifier.CallbackInvalidSignature", true); + } else { + UMA_HISTOGRAM_BOOLEAN("InstallVerifier.CallbackValidSignature", true); + success = true; + } + + if (!success) { + if (!operation->callback.is_null()) + operation->callback.Run(false); + + // 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); + } + + // See if we were able to sign all of |ids|. + ExtensionIdSet not_allowed = + base::STLSetDifference<ExtensionIdSet>(operation->ids, + signature_->ids); + + UMA_HISTOGRAM_COUNTS_100("InstallVerifier.CallbackNotAllowed", + not_allowed.size()); + + if (!operation->callback.is_null()) + operation->callback.Run(not_allowed.empty()); + } + + if (!operation_queue_.empty()) + BeginFetch(); +} + + +} // namespace extensions diff --git a/chrome/browser/extensions/install_verifier.h b/chrome/browser/extensions/install_verifier.h new file mode 100644 index 0000000..eb5d17c --- /dev/null +++ b/chrome/browser/extensions/install_verifier.h @@ -0,0 +1,136 @@ +// 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. + +#ifndef CHROME_BROWSER_EXTENSIONS_INSTALL_VERIFIER_H_ +#define CHROME_BROWSER_EXTENSIONS_INSTALL_VERIFIER_H_ + +#include <queue> +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/extensions/management_policy.h" +#include "chrome/common/extensions/extension.h" + +namespace net { +class URLRequestContextGetter; +} + +namespace extensions { + +class ExtensionPrefs; +class InstallSigner; +struct InstallSignature; + +// This class implements verification that a set of extensions are either from +// the webstore or are whitelisted by enterprise policy. The webstore +// verification process works by sending a request to a backend server to get a +// signature proving that a set of extensions are verified. This signature is +// written into the extension preferences and is checked for validity when +// being read back again. +// +// This class should be kept notified of runtime changes to the set of +// extensions installed from the webstore. +class InstallVerifier : public ManagementPolicy::Provider { + public: + InstallVerifier(ExtensionPrefs* prefs, + net::URLRequestContextGetter* context_getter); + virtual ~InstallVerifier(); + + // Initializes this object for use, including reading preferences and + // validating the stored signature. + void Init(); + + // A callback for indicating success/failure of adding new ids. + typedef base::Callback<void(bool)> AddResultCallback; + + // Try adding a new |id| (or set of ids) to the list of verified ids. When + // this process is finished |callback| will be run with success/failure. In + // case of success, subsequent calls to IsVerified will begin returning true + // for |id|. + void Add(const std::string& id, const AddResultCallback& callback); + void AddMany(const ExtensionIdSet& ids, + const AddResultCallback& callback); + + // Call this to add a set of ids that will immediately be considered allowed, + // and kick off an aysnchronous request to Add. + void AddProvisional(const ExtensionIdSet& ids); + + // Removes an id or set of ids from the verified list. + void Remove(const std::string& id); + void RemoveMany(const ExtensionIdSet& ids); + + // ManagementPolicy::Provider interface. + virtual std::string GetDebugPolicyProviderName() const OVERRIDE; + virtual bool MustRemainDisabled(const Extension* extension, + Extension::DisableReason* reason, + string16* error) const OVERRIDE; + + private: + // We keep a list of operations to the current set of extensions - either + // additions or removals. + enum OperationType { + ADD, + REMOVE + }; + + // This is an operation we want to apply to the current set of verified ids. + struct PendingOperation { + OperationType type; + + // This is the set of ids being either added or removed. + ExtensionIdSet ids; + + AddResultCallback callback; + + explicit PendingOperation(); + ~PendingOperation(); + }; + + // Removes any no-longer-installed ids, requesting a new signature if needed. + void GarbageCollect(); + + // Returns whether an extension id is allowed by policy. + bool AllowedByEnterprisePolicy(const std::string& id) const; + + // Returns whether the given |id| is included in our verified signature. + bool IsVerified(const std::string& id) const; + + // Begins the process of fetching a new signature, based on applying the + // operation at the head of the queue to the current set of ids in + // |signature_| (if any) and then sending a request to sign that. + void BeginFetch(); + + // Saves the current value of |signature_| to the prefs; + void SaveToPrefs(); + + // Called with the result of a signature request, or NULL on failure. + void SignatureCallback(scoped_ptr<InstallSignature> signature); + + ExtensionPrefs* prefs_; + net::URLRequestContextGetter* context_getter_; + + // This is the most up-to-date signature, read out of |prefs_| during + // initialization and updated anytime we get new id's added. + scoped_ptr<InstallSignature> signature_; + + // The current InstallSigner, if we have a signature request running. + scoped_ptr<InstallSigner> signer_; + + // A queue of operations to apply to the current set of allowed ids. + std::queue<linked_ptr<PendingOperation> > operation_queue_; + + // A set of ids that have been provisionally added, which we're willing to + // consider allowed until we hear back from the server signature request. + ExtensionIdSet provisional_; + + DISALLOW_COPY_AND_ASSIGN(InstallVerifier); +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_INSTALL_VERIFIER_H_ diff --git a/chrome/browser/extensions/test_extension_system.cc b/chrome/browser/extensions/test_extension_system.cc index 3a9cad7..33838b8 100644 --- a/chrome/browser/extensions/test_extension_system.cc +++ b/chrome/browser/extensions/test_extension_system.cc @@ -15,6 +15,7 @@ #include "chrome/browser/extensions/extension_prefs_factory.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_system.h" +#include "chrome/browser/extensions/install_verifier.h" #include "chrome/browser/extensions/management_policy.h" #include "chrome/browser/extensions/standard_management_policy_provider.h" #include "chrome/browser/extensions/state_store.h" @@ -79,6 +80,8 @@ ExtensionService* TestExtensionSystem::CreateExtensionService( bool autoupdate_enabled) { if (!ExtensionPrefs::Get(profile_)) CreateExtensionPrefs(command_line, install_directory); + install_verifier_.reset(new InstallVerifier(ExtensionPrefs::Get(profile_), + NULL)); // The ownership of |value_store_| is immediately transferred to state_store_, // but we keep a naked pointer to the TestingValueStore. scoped_ptr<TestingValueStore> value_store(new TestingValueStore()); @@ -158,6 +161,10 @@ ErrorConsole* TestExtensionSystem::error_console() { return error_console_.get(); } +InstallVerifier* TestExtensionSystem::install_verifier() { + return install_verifier_.get(); +} + // static BrowserContextKeyedService* TestExtensionSystem::Build( content::BrowserContext* profile) { diff --git a/chrome/browser/extensions/test_extension_system.h b/chrome/browser/extensions/test_extension_system.h index 4bf61e8..4f67311 100644 --- a/chrome/browser/extensions/test_extension_system.h +++ b/chrome/browser/extensions/test_extension_system.h @@ -70,6 +70,7 @@ class TestExtensionSystem : public ExtensionSystem { virtual Blacklist* blacklist() OVERRIDE; virtual const OneShotEvent& ready() const OVERRIDE; virtual ErrorConsole* error_console() OVERRIDE; + virtual InstallVerifier* install_verifier() OVERRIDE; void SetReady() { LOG(INFO) << "SetReady()"; @@ -94,6 +95,7 @@ class TestExtensionSystem : public ExtensionSystem { scoped_ptr<ProcessManager> process_manager_; scoped_refptr<InfoMap> info_map_; scoped_ptr<ErrorConsole> error_console_; + scoped_ptr<InstallVerifier> install_verifier_; OneShotEvent ready_; }; diff --git a/chrome/browser/extensions/webstore_installer.cc b/chrome/browser/extensions/webstore_installer.cc index de53ca9..d4c9c2f 100644 --- a/chrome/browser/extensions/webstore_installer.cc +++ b/chrome/browser/extensions/webstore_installer.cc @@ -24,6 +24,7 @@ #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/extensions/install_tracker.h" #include "chrome/browser/extensions/install_tracker_factory.h" +#include "chrome/browser/extensions/install_verifier.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" @@ -283,6 +284,13 @@ void WebstoreInstaller::Start() { total_modules_ = pending_modules_.size(); + std::set<std::string> ids; + std::list<SharedModuleInfo::ImportInfo>::const_iterator i; + for (i = pending_modules_.begin(); i != pending_modules_.end(); ++i) { + ids.insert(i->extension_id); + } + ExtensionSystem::Get(profile_)->install_verifier()->AddProvisional(ids); + // TODO(crbug.com/305343): Query manifest of dependencises before // downloading & installing those dependencies. DownloadNextPendingModule(); diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index c701630..cdf3be2 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -806,10 +806,14 @@ 'browser/extensions/installed_loader.cc', 'browser/extensions/installed_loader.h', 'browser/extensions/install_observer.h', + 'browser/extensions/install_signer.cc', + 'browser/extensions/install_signer.h', 'browser/extensions/install_tracker.cc', 'browser/extensions/install_tracker.h', 'browser/extensions/install_tracker_factory.cc', 'browser/extensions/install_tracker_factory.h', + 'browser/extensions/install_verifier.cc', + 'browser/extensions/install_verifier.h', 'browser/extensions/location_bar_controller.h', 'browser/extensions/management_policy.cc', 'browser/extensions/management_policy.h', diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index 4a8a9c1..c05b8e7 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -812,6 +812,14 @@ const char kExplicitlyAllowedPorts[] = "explicitly-allowed-ports"; // Marks a renderer as extension process. const char kExtensionProcess[] = "extension-process"; +// Turns on extension install verification if it would not otherwise have been +// turned on. +const char kExtensionsInstallVerification[] = "extensions-install-verification"; + +// Specifies a comma-separated list of extension ids that should be forced to +// be treated as not from the webstore when doing install verification. +const char kExtensionsNotWebstore[] = "extensions-not-webstore"; + // Frequency in seconds for Extensions auto-update. const char kExtensionsUpdateFrequency[] = "extensions-update-frequency"; diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index 30e708e..c0f0ba3 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -229,6 +229,8 @@ extern const char kEnableWatchdog[]; extern const char kEnableWebSocketOverSpdy[]; extern const char kExplicitlyAllowedPorts[]; extern const char kExtensionProcess[]; +extern const char kExtensionsInstallVerification[]; +extern const char kExtensionsNotWebstore[]; extern const char kExtensionsUpdateFrequency[]; extern const char kExtraSearchQueryParams[]; extern const char kFakeVariationsChannel[]; diff --git a/chrome/common/extensions/manifest_url_handler.cc b/chrome/common/extensions/manifest_url_handler.cc index 75f3690..52229bd 100644 --- a/chrome/common/extensions/manifest_url_handler.cc +++ b/chrome/common/extensions/manifest_url_handler.cc @@ -72,6 +72,14 @@ bool ManifestURL::UpdatesFromGallery(const Extension* extension) { } // static +bool ManifestURL::UpdatesFromGallery(const base::DictionaryValue* manifest) { + std::string url; + if (!manifest->GetString(keys::kUpdateURL, &url)) + return false; + return extension_urls::IsWebstoreUpdateUrl(GURL(url)); +} + +// static const GURL& ManifestURL::GetOptionsPage(const Extension* extension) { return GetManifestURL(extension, keys::kOptionsPage); } diff --git a/chrome/common/extensions/manifest_url_handler.h b/chrome/common/extensions/manifest_url_handler.h index d11146b..34934dd 100644 --- a/chrome/common/extensions/manifest_url_handler.h +++ b/chrome/common/extensions/manifest_url_handler.h @@ -10,6 +10,10 @@ #include "chrome/common/extensions/extension.h" #include "extensions/common/manifest_handler.h" +namespace base { +class DictionaryValue; +} + namespace extensions { // A structure to hold various URLs like devtools_page, homepage_url, etc @@ -31,6 +35,7 @@ struct ManifestURL : public Extension::ManifestData { // Returns true if this extension's update URL is the extension gallery. static bool UpdatesFromGallery(const Extension* extension); + static bool UpdatesFromGallery(const base::DictionaryValue* manifest); // Returns the Options Page for this extension. static const GURL& GetOptionsPage(const Extension* extension); |