Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / extensions / install_verifier.cc
blob752ab20c0d29c27017e1a5c2bd8938f02e677b45
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/extensions/install_verifier.h"
7 #include <algorithm>
8 #include <string>
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/metrics/field_trial.h"
13 #include "base/metrics/histogram.h"
14 #include "base/prefs/pref_service.h"
15 #include "base/stl_util.h"
16 #include "chrome/browser/extensions/extension_prefs.h"
17 #include "chrome/browser/extensions/install_signer.h"
18 #include "chrome/common/chrome_switches.h"
19 #include "chrome/common/extensions/manifest_url_handler.h"
20 #include "chrome/common/pref_names.h"
21 #include "content/public/common/content_switches.h"
22 #include "extensions/browser/pref_names.h"
23 #include "extensions/common/manifest.h"
24 #include "grit/generated_resources.h"
25 #include "ui/base/l10n/l10n_util.h"
27 namespace {
29 enum VerifyStatus {
30 NONE = 0, // Do not request install signatures, and do not enforce them.
31 BOOTSTRAP, // Request install signatures, but do not enforce them.
32 ENFORCE, // Request install signatures, and enforce them.
35 #if defined(GOOGLE_CHROME_BUILD)
36 const char kExperimentName[] = "ExtensionInstallVerification";
37 #endif // defined(GOOGLE_CHROME_BUILD)
39 VerifyStatus GetExperimentStatus() {
40 #if defined(GOOGLE_CHROME_BUILD)
41 const std::string group = base::FieldTrialList::FindFullName(
42 kExperimentName);
44 std::string forced_trials = CommandLine::ForCurrentProcess()->
45 GetSwitchValueASCII(switches::kForceFieldTrials);
46 if (forced_trials.find(kExperimentName) != std::string::npos) {
47 // We don't want to allow turning off enforcement by forcing the field
48 // trial group to something other than enforcement.
49 return ENFORCE;
52 VerifyStatus default_status = BOOTSTRAP;
54 if (group == "Enforce")
55 return ENFORCE;
56 else if (group == "Bootstrap")
57 return BOOTSTRAP;
58 else if (group == "None" || group == "Control")
59 return NONE;
60 else
61 return default_status;
62 #endif // defined(GOOGLE_CHROME_BUILD)
64 return NONE;
67 VerifyStatus GetCommandLineStatus() {
68 const CommandLine* cmdline = CommandLine::ForCurrentProcess();
69 if (!extensions::InstallSigner::GetForcedNotFromWebstore().empty())
70 return ENFORCE;
72 if (cmdline->HasSwitch(switches::kExtensionsInstallVerification)) {
73 std::string value = cmdline->GetSwitchValueASCII(
74 switches::kExtensionsInstallVerification);
75 if (value == "bootstrap")
76 return BOOTSTRAP;
77 else
78 return ENFORCE;
81 return NONE;
84 VerifyStatus GetStatus() {
85 return std::max(GetExperimentStatus(), GetCommandLineStatus());
88 bool ShouldFetchSignature() {
89 VerifyStatus status = GetStatus();
90 return (status == BOOTSTRAP || status == ENFORCE);
93 bool ShouldEnforce() {
94 return GetStatus() == ENFORCE;
97 } // namespace
99 namespace extensions {
101 InstallVerifier::InstallVerifier(ExtensionPrefs* prefs,
102 net::URLRequestContextGetter* context_getter)
103 : prefs_(prefs), context_getter_(context_getter) {
106 InstallVerifier::~InstallVerifier() {}
108 namespace {
110 enum InitResult {
111 INIT_NO_PREF = 0,
112 INIT_UNPARSEABLE_PREF,
113 INIT_INVALID_SIGNATURE,
114 INIT_VALID_SIGNATURE,
116 // This is used in histograms - do not remove or reorder entries above! Also
117 // the "MAX" item below should always be the last element.
119 INIT_RESULT_MAX
122 void LogInitResultHistogram(InitResult result) {
123 UMA_HISTOGRAM_ENUMERATION("ExtensionInstallVerifier.InitResult",
124 result, INIT_RESULT_MAX);
127 bool FromStore(const Extension& extension) {
128 bool updates_from_store = ManifestURL::UpdatesFromGallery(&extension);
129 return extension.from_webstore() || updates_from_store;
132 bool CanUseExtensionApis(const Extension& extension) {
133 return extension.is_extension() || extension.is_legacy_packaged_app();
136 } // namespace
138 // static
139 bool InstallVerifier::NeedsVerification(const Extension& extension) {
140 return FromStore(extension) && CanUseExtensionApis(extension);
143 void InstallVerifier::Init() {
144 const base::DictionaryValue* pref = prefs_->GetInstallSignature();
145 if (pref) {
146 scoped_ptr<InstallSignature> signature_from_prefs =
147 InstallSignature::FromValue(*pref);
148 if (!signature_from_prefs.get()) {
149 LogInitResultHistogram(INIT_UNPARSEABLE_PREF);
150 } else if (!InstallSigner::VerifySignature(*signature_from_prefs.get())) {
151 LogInitResultHistogram(INIT_INVALID_SIGNATURE);
152 DVLOG(1) << "Init - ignoring invalid signature";
153 } else {
154 signature_ = signature_from_prefs.Pass();
155 LogInitResultHistogram(INIT_VALID_SIGNATURE);
156 UMA_HISTOGRAM_COUNTS_100("ExtensionInstallVerifier.InitSignatureCount",
157 signature_->ids.size());
158 GarbageCollect();
160 } else {
161 LogInitResultHistogram(INIT_NO_PREF);
165 bool InstallVerifier::NeedsBootstrap() {
166 return signature_.get() == NULL && ShouldFetchSignature();
169 void InstallVerifier::Add(const std::string& id,
170 const AddResultCallback& callback) {
171 ExtensionIdSet ids;
172 ids.insert(id);
173 AddMany(ids, callback);
176 void InstallVerifier::AddMany(const ExtensionIdSet& ids,
177 const AddResultCallback& callback) {
178 if (!ShouldFetchSignature()) {
179 if (!callback.is_null())
180 callback.Run(true);
181 return;
184 if (signature_.get()) {
185 ExtensionIdSet not_allowed_yet =
186 base::STLSetDifference<ExtensionIdSet>(ids, signature_->ids);
187 if (not_allowed_yet.empty()) {
188 if (!callback.is_null())
189 callback.Run(true);
190 return;
194 InstallVerifier::PendingOperation* operation =
195 new InstallVerifier::PendingOperation();
196 operation->type = InstallVerifier::ADD;
197 operation->ids.insert(ids.begin(), ids.end());
198 operation->callback = callback;
200 operation_queue_.push(linked_ptr<PendingOperation>(operation));
202 // If there are no ongoing pending requests, we need to kick one off.
203 if (operation_queue_.size() == 1)
204 BeginFetch();
207 void InstallVerifier::AddProvisional(const ExtensionIdSet& ids) {
208 provisional_.insert(ids.begin(), ids.end());
209 AddMany(ids, AddResultCallback());
212 void InstallVerifier::Remove(const std::string& id) {
213 ExtensionIdSet ids;
214 ids.insert(id);
215 RemoveMany(ids);
218 void InstallVerifier::RemoveMany(const ExtensionIdSet& ids) {
219 if (!signature_.get() || !ShouldFetchSignature())
220 return;
222 bool found_any = false;
223 for (ExtensionIdSet::const_iterator i = ids.begin(); i != ids.end(); ++i) {
224 if (ContainsKey(signature_->ids, *i)) {
225 found_any = true;
226 break;
229 if (!found_any)
230 return;
232 InstallVerifier::PendingOperation* operation =
233 new InstallVerifier::PendingOperation();
234 operation->type = InstallVerifier::REMOVE;
235 operation->ids = ids;
237 operation_queue_.push(linked_ptr<PendingOperation>(operation));
238 if (operation_queue_.size() == 1)
239 BeginFetch();
242 std::string InstallVerifier::GetDebugPolicyProviderName() const {
243 return std::string("InstallVerifier");
246 namespace {
248 enum MustRemainDisabledOutcome {
249 VERIFIED = 0,
250 NOT_EXTENSION,
251 UNPACKED,
252 ENTERPRISE_POLICY_ALLOWED,
253 FORCED_NOT_VERIFIED,
254 NOT_FROM_STORE,
255 NO_SIGNATURE,
256 NOT_VERIFIED_BUT_NOT_ENFORCING,
257 NOT_VERIFIED,
259 // This is used in histograms - do not remove or reorder entries above! Also
260 // the "MAX" item below should always be the last element.
262 MUST_REMAIN_DISABLED_OUTCOME_MAX
265 void MustRemainDisabledHistogram(MustRemainDisabledOutcome outcome) {
266 UMA_HISTOGRAM_ENUMERATION("ExtensionInstallVerifier.MustRemainDisabled",
267 outcome, MUST_REMAIN_DISABLED_OUTCOME_MAX);
270 } // namespace
272 bool InstallVerifier::MustRemainDisabled(const Extension* extension,
273 Extension::DisableReason* reason,
274 base::string16* error) const {
275 CHECK(extension);
276 if (!CanUseExtensionApis(*extension)) {
277 MustRemainDisabledHistogram(NOT_EXTENSION);
278 return false;
280 if (Manifest::IsUnpackedLocation(extension->location())) {
281 MustRemainDisabledHistogram(UNPACKED);
282 return false;
284 if (AllowedByEnterprisePolicy(extension->id())) {
285 MustRemainDisabledHistogram(ENTERPRISE_POLICY_ALLOWED);
286 return false;
289 bool verified = true;
290 MustRemainDisabledOutcome outcome = VERIFIED;
291 if (ContainsKey(InstallSigner::GetForcedNotFromWebstore(), extension->id())) {
292 verified = false;
293 outcome = FORCED_NOT_VERIFIED;
294 } else if (!FromStore(*extension)) {
295 verified = false;
296 outcome = NOT_FROM_STORE;
297 } else if (signature_.get() == NULL) {
298 // If we don't have a signature yet, we'll temporarily consider every
299 // extension from the webstore verified to avoid false positives on existing
300 // profiles hitting this code for the first time, and rely on consumers of
301 // this class to check NeedsBootstrap() and schedule a first check so we can
302 // get a signature.
303 outcome = NO_SIGNATURE;
304 } else if (!IsVerified(extension->id())) {
305 verified = false;
306 outcome = NOT_VERIFIED;
308 if (!verified && !ShouldEnforce()) {
309 verified = true;
310 outcome = NOT_VERIFIED_BUT_NOT_ENFORCING;
312 MustRemainDisabledHistogram(outcome);
314 if (!verified) {
315 if (reason)
316 *reason = Extension::DISABLE_NOT_VERIFIED;
317 if (error)
318 *error = l10n_util::GetStringFUTF16(
319 IDS_EXTENSIONS_ADDED_WITHOUT_KNOWLEDGE,
320 l10n_util::GetStringUTF16(IDS_EXTENSION_WEB_STORE_TITLE));
322 return !verified;
325 InstallVerifier::PendingOperation::PendingOperation() {
326 type = InstallVerifier::ADD;
329 InstallVerifier::PendingOperation::~PendingOperation() {
332 void InstallVerifier::GarbageCollect() {
333 if (!ShouldFetchSignature()) {
334 return;
336 CHECK(signature_.get());
337 ExtensionIdSet leftovers = signature_->ids;
338 ExtensionIdList all_ids;
339 prefs_->GetExtensions(&all_ids);
340 for (ExtensionIdList::const_iterator i = all_ids.begin();
341 i != all_ids.end(); ++i) {
342 ExtensionIdSet::iterator found = leftovers.find(*i);
343 if (found != leftovers.end())
344 leftovers.erase(found);
346 if (!leftovers.empty()) {
347 RemoveMany(leftovers);
351 bool InstallVerifier::AllowedByEnterprisePolicy(const std::string& id) const {
352 PrefService* pref_service = prefs_->pref_service();
353 if (pref_service->IsManagedPreference(pref_names::kInstallAllowList)) {
354 const base::ListValue* whitelist =
355 pref_service->GetList(pref_names::kInstallAllowList);
356 base::StringValue id_value(id);
357 if (whitelist && whitelist->Find(id_value) != whitelist->end())
358 return true;
360 if (pref_service->IsManagedPreference(pref_names::kInstallForceList)) {
361 const base::DictionaryValue* forcelist =
362 pref_service->GetDictionary(pref_names::kInstallForceList);
363 if (forcelist && forcelist->HasKey(id))
364 return true;
366 return false;
369 bool InstallVerifier::IsVerified(const std::string& id) const {
370 return ((signature_.get() && ContainsKey(signature_->ids, id)) ||
371 ContainsKey(provisional_, id));
374 void InstallVerifier::BeginFetch() {
375 DCHECK(ShouldFetchSignature());
377 // TODO(asargent) - It would be possible to coalesce all operations in the
378 // queue into one fetch - we'd probably just need to change the queue to
379 // hold (set of ids, list of callbacks) pairs.
380 CHECK(!operation_queue_.empty());
381 const PendingOperation& operation = *operation_queue_.front();
383 ExtensionIdSet ids_to_sign;
384 if (signature_.get()) {
385 ids_to_sign.insert(signature_->ids.begin(), signature_->ids.end());
387 if (operation.type == InstallVerifier::ADD) {
388 ids_to_sign.insert(operation.ids.begin(), operation.ids.end());
389 } else {
390 for (ExtensionIdSet::const_iterator i = operation.ids.begin();
391 i != operation.ids.end(); ++i) {
392 if (ContainsKey(ids_to_sign, *i))
393 ids_to_sign.erase(*i);
397 signer_.reset(new InstallSigner(context_getter_, ids_to_sign));
398 signer_->GetSignature(base::Bind(&InstallVerifier::SignatureCallback,
399 base::Unretained(this)));
402 void InstallVerifier::SaveToPrefs() {
403 if (signature_.get())
404 DCHECK(InstallSigner::VerifySignature(*signature_));
406 if (!signature_.get() || signature_->ids.empty()) {
407 DVLOG(1) << "SaveToPrefs - saving NULL";
408 prefs_->SetInstallSignature(NULL);
409 } else {
410 base::DictionaryValue pref;
411 signature_->ToValue(&pref);
412 if (VLOG_IS_ON(1)) {
413 DVLOG(1) << "SaveToPrefs - saving";
415 DCHECK(InstallSigner::VerifySignature(*signature_.get()));
416 scoped_ptr<InstallSignature> rehydrated =
417 InstallSignature::FromValue(pref);
418 DCHECK(InstallSigner::VerifySignature(*rehydrated.get()));
420 prefs_->SetInstallSignature(&pref);
424 namespace {
426 enum CallbackResult {
427 CALLBACK_NO_SIGNATURE = 0,
428 CALLBACK_INVALID_SIGNATURE,
429 CALLBACK_VALID_SIGNATURE,
431 // This is used in histograms - do not remove or reorder entries above! Also
432 // the "MAX" item below should always be the last element.
434 CALLBACK_RESULT_MAX
437 void GetSignatureResultHistogram(CallbackResult result) {
438 UMA_HISTOGRAM_ENUMERATION("ExtensionInstallVerifier.GetSignatureResult",
439 result, CALLBACK_RESULT_MAX);
442 } // namespace
444 void InstallVerifier::SignatureCallback(
445 scoped_ptr<InstallSignature> signature) {
447 linked_ptr<PendingOperation> operation = operation_queue_.front();
448 operation_queue_.pop();
450 bool success = false;
451 if (!signature.get()) {
452 GetSignatureResultHistogram(CALLBACK_NO_SIGNATURE);
453 } else if (!InstallSigner::VerifySignature(*signature)) {
454 GetSignatureResultHistogram(CALLBACK_INVALID_SIGNATURE);
455 } else {
456 GetSignatureResultHistogram(CALLBACK_VALID_SIGNATURE);
457 success = true;
460 if (!success) {
461 if (!operation->callback.is_null())
462 operation->callback.Run(false);
464 // TODO(asargent) - if this was something like a network error, we need to
465 // do retries with exponential back off.
466 } else {
467 signature_ = signature.Pass();
468 SaveToPrefs();
470 if (!provisional_.empty()) {
471 // Update |provisional_| to remove ids that were successfully signed.
472 provisional_ = base::STLSetDifference<ExtensionIdSet>(
473 provisional_, signature_->ids);
476 if (!operation->callback.is_null())
477 operation->callback.Run(success);
480 if (!operation_queue_.empty())
481 BeginFetch();
485 } // namespace extensions