install_verifier.cc revision 923bd855a3a49144a9f75d8a8200416a52bae775
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.
4
5#include "chrome/browser/extensions/install_verifier.h"
6
7#include <algorithm>
8#include <string>
9
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/common/manifest.h"
23#include "grit/generated_resources.h"
24#include "ui/base/l10n/l10n_util.h"
25
26namespace {
27
28enum VerifyStatus {
29  NONE = 0,   // Do not request install signatures, and do not enforce them.
30  BOOTSTRAP,  // Request install signatures, but do not enforce them.
31  ENFORCE,    // Request install signatures, and enforce them.
32
33  // This is used in histograms - do not remove or reorder entries above! Also
34  // the "MAX" item below should always be the last element.
35
36  VERIFY_STATUS_MAX
37};
38
39#if defined(GOOGLE_CHROME_BUILD)
40const char kExperimentName[] = "ExtensionInstallVerification";
41#endif  // defined(GOOGLE_CHROME_BUILD)
42
43VerifyStatus GetExperimentStatus() {
44#if defined(GOOGLE_CHROME_BUILD)
45  const std::string group = base::FieldTrialList::FindFullName(
46      kExperimentName);
47
48  std::string forced_trials = CommandLine::ForCurrentProcess()->
49      GetSwitchValueASCII(switches::kForceFieldTrials);
50  if (forced_trials.find(kExperimentName) != std::string::npos) {
51    // We don't want to allow turning off enforcement by forcing the field
52    // trial group to something other than enforcement.
53    return ENFORCE;
54  }
55
56  VerifyStatus default_status = NONE;
57
58  if (group == "Enforce")
59    return ENFORCE;
60  else if (group == "Bootstrap")
61    return BOOTSTRAP;
62  else if (group == "None" || group == "Control")
63    return NONE;
64  else
65    return default_status;
66#endif  // defined(GOOGLE_CHROME_BUILD)
67
68  return NONE;
69}
70
71VerifyStatus GetCommandLineStatus() {
72  const CommandLine* cmdline = CommandLine::ForCurrentProcess();
73  if (!extensions::InstallSigner::GetForcedNotFromWebstore().empty())
74    return ENFORCE;
75
76  if (cmdline->HasSwitch(switches::kExtensionsInstallVerification)) {
77    std::string value = cmdline->GetSwitchValueASCII(
78        switches::kExtensionsInstallVerification);
79    if (value == "bootstrap")
80      return BOOTSTRAP;
81    else
82      return ENFORCE;
83  }
84
85  return NONE;
86}
87
88VerifyStatus GetStatus() {
89  return std::max(GetExperimentStatus(), GetCommandLineStatus());
90}
91
92bool ShouldFetchSignature() {
93  VerifyStatus status = GetStatus();
94  return (status == BOOTSTRAP || status == ENFORCE);
95}
96
97bool ShouldEnforce() {
98  return GetStatus() == ENFORCE;
99}
100
101}  // namespace
102
103namespace extensions {
104
105InstallVerifier::InstallVerifier(ExtensionPrefs* prefs,
106                                 net::URLRequestContextGetter* context_getter)
107    : prefs_(prefs), context_getter_(context_getter) {
108}
109
110InstallVerifier::~InstallVerifier() {}
111
112namespace {
113
114enum InitResult {
115  INIT_NO_PREF = 0,
116  INIT_UNPARSEABLE_PREF,
117  INIT_INVALID_SIGNATURE,
118  INIT_VALID_SIGNATURE,
119
120  // This is used in histograms - do not remove or reorder entries above! Also
121  // the "MAX" item below should always be the last element.
122
123  INIT_RESULT_MAX
124};
125
126void LogInitResultHistogram(InitResult result) {
127  UMA_HISTOGRAM_ENUMERATION("ExtensionInstallVerifier.InitResult",
128                            result, INIT_RESULT_MAX);
129}
130
131bool FromStore(const Extension& extension) {
132  bool updates_from_store = ManifestURL::UpdatesFromGallery(&extension);
133  return extension.from_webstore() || updates_from_store;
134}
135
136bool CanUseExtensionApis(const Extension& extension) {
137  return extension.is_extension() || extension.is_legacy_packaged_app();
138}
139
140}  // namespace
141
142// static
143bool InstallVerifier::NeedsVerification(const Extension& extension) {
144  return FromStore(extension) && CanUseExtensionApis(extension);
145}
146
147void InstallVerifier::Init() {
148  UMA_HISTOGRAM_ENUMERATION("ExtensionInstallVerifier.ExperimentStatus",
149                            GetExperimentStatus(), VERIFY_STATUS_MAX);
150  UMA_HISTOGRAM_ENUMERATION("ExtensionInstallVerifier.ActualStatus",
151                            GetStatus(), VERIFY_STATUS_MAX);
152
153  const DictionaryValue* pref = prefs_->GetInstallSignature();
154  if (pref) {
155    scoped_ptr<InstallSignature> signature_from_prefs =
156        InstallSignature::FromValue(*pref);
157    if (!signature_from_prefs.get()) {
158      LogInitResultHistogram(INIT_UNPARSEABLE_PREF);
159    } else if (!InstallSigner::VerifySignature(*signature_from_prefs.get())) {
160      LogInitResultHistogram(INIT_INVALID_SIGNATURE);
161      DVLOG(1) << "Init - ignoring invalid signature";
162    } else {
163      signature_ = signature_from_prefs.Pass();
164      LogInitResultHistogram(INIT_VALID_SIGNATURE);
165      UMA_HISTOGRAM_COUNTS_100("ExtensionInstallVerifier.InitSignatureCount",
166                               signature_->ids.size());
167      GarbageCollect();
168    }
169  } else {
170    LogInitResultHistogram(INIT_NO_PREF);
171  }
172}
173
174bool InstallVerifier::NeedsBootstrap() {
175  return signature_.get() == NULL && ShouldFetchSignature();
176}
177
178void InstallVerifier::Add(const std::string& id,
179                          const AddResultCallback& callback) {
180  ExtensionIdSet ids;
181  ids.insert(id);
182  AddMany(ids, callback);
183}
184
185void InstallVerifier::AddMany(const ExtensionIdSet& ids,
186                              const AddResultCallback& callback) {
187  if (!ShouldFetchSignature()) {
188    if (!callback.is_null())
189      callback.Run(true);
190    return;
191  }
192
193  if (signature_.get()) {
194    ExtensionIdSet not_allowed_yet =
195        base::STLSetDifference<ExtensionIdSet>(ids, signature_->ids);
196    if (not_allowed_yet.empty()) {
197      if (!callback.is_null())
198        callback.Run(true);
199      return;
200    }
201  }
202
203  InstallVerifier::PendingOperation* operation =
204    new InstallVerifier::PendingOperation();
205  operation->type = InstallVerifier::ADD;
206  operation->ids.insert(ids.begin(), ids.end());
207  operation->callback = callback;
208
209  operation_queue_.push(linked_ptr<PendingOperation>(operation));
210
211  // If there are no ongoing pending requests, we need to kick one off.
212  if (operation_queue_.size() == 1)
213    BeginFetch();
214}
215
216void InstallVerifier::AddProvisional(const ExtensionIdSet& ids) {
217  provisional_.insert(ids.begin(), ids.end());
218  AddMany(ids, AddResultCallback());
219}
220
221void InstallVerifier::Remove(const std::string& id) {
222  ExtensionIdSet ids;
223  ids.insert(id);
224  RemoveMany(ids);
225}
226
227void InstallVerifier::RemoveMany(const ExtensionIdSet& ids) {
228  if (!signature_.get() || !ShouldFetchSignature())
229    return;
230
231  bool found_any = false;
232  for (ExtensionIdSet::const_iterator i = ids.begin(); i != ids.end(); ++i) {
233    if (ContainsKey(signature_->ids, *i)) {
234      found_any = true;
235      break;
236    }
237  }
238  if (!found_any)
239    return;
240
241  InstallVerifier::PendingOperation* operation =
242    new InstallVerifier::PendingOperation();
243  operation->type = InstallVerifier::REMOVE;
244  operation->ids = ids;
245
246  operation_queue_.push(linked_ptr<PendingOperation>(operation));
247  if (operation_queue_.size() == 1)
248    BeginFetch();
249}
250
251std::string InstallVerifier::GetDebugPolicyProviderName() const {
252  return std::string("InstallVerifier");
253}
254
255namespace {
256
257enum MustRemainDisabledOutcome {
258  VERIFIED = 0,
259  NOT_EXTENSION,
260  UNPACKED,
261  ENTERPRISE_POLICY_ALLOWED,
262  FORCED_NOT_VERIFIED,
263  NOT_FROM_STORE,
264  NO_SIGNATURE,
265  NOT_VERIFIED_BUT_NOT_ENFORCING,
266  NOT_VERIFIED,
267
268  // This is used in histograms - do not remove or reorder entries above! Also
269  // the "MAX" item below should always be the last element.
270
271  MUST_REMAIN_DISABLED_OUTCOME_MAX
272};
273
274void MustRemainDisabledHistogram(MustRemainDisabledOutcome outcome) {
275  UMA_HISTOGRAM_ENUMERATION("ExtensionInstallVerifier.MustRemainDisabled",
276                            outcome, MUST_REMAIN_DISABLED_OUTCOME_MAX);
277}
278
279}  // namespace
280
281bool InstallVerifier::MustRemainDisabled(const Extension* extension,
282                                         Extension::DisableReason* reason,
283                                         base::string16* error) const {
284  if (!CanUseExtensionApis(*extension)) {
285    MustRemainDisabledHistogram(NOT_EXTENSION);
286    return false;
287  }
288  if (Manifest::IsUnpackedLocation(extension->location())) {
289    MustRemainDisabledHistogram(UNPACKED);
290    return false;
291  }
292  if (AllowedByEnterprisePolicy(extension->id())) {
293    MustRemainDisabledHistogram(ENTERPRISE_POLICY_ALLOWED);
294    return false;
295  }
296
297  bool verified = true;
298  MustRemainDisabledOutcome outcome = VERIFIED;
299  if (ContainsKey(InstallSigner::GetForcedNotFromWebstore(), extension->id())) {
300    verified = false;
301    outcome = FORCED_NOT_VERIFIED;
302  } else if (!FromStore(*extension)) {
303    verified = false;
304    outcome = NOT_FROM_STORE;
305  } else if (signature_.get() == NULL) {
306    // If we don't have a signature yet, we'll temporarily consider every
307    // extension from the webstore verified to avoid false positives on existing
308    // profiles hitting this code for the first time, and rely on consumers of
309    // this class to check NeedsBootstrap() and schedule a first check so we can
310    // get a signature.
311    outcome = NO_SIGNATURE;
312  } else if (!IsVerified(extension->id())) {
313    verified = false;
314    outcome = NOT_VERIFIED;
315  }
316  if (!verified && !ShouldEnforce()) {
317    verified = true;
318    outcome = NOT_VERIFIED_BUT_NOT_ENFORCING;
319  }
320  MustRemainDisabledHistogram(outcome);
321
322  if (!verified) {
323    if (reason)
324      *reason = Extension::DISABLE_NOT_VERIFIED;
325    if (error)
326      *error = l10n_util::GetStringFUTF16(
327          IDS_EXTENSIONS_ADDED_WITHOUT_KNOWLEDGE,
328          l10n_util::GetStringUTF16(IDS_EXTENSION_WEB_STORE_TITLE));
329  }
330  return !verified;
331}
332
333InstallVerifier::PendingOperation::PendingOperation() {
334  type = InstallVerifier::ADD;
335}
336
337InstallVerifier::PendingOperation::~PendingOperation() {
338}
339
340void InstallVerifier::GarbageCollect() {
341  if (!ShouldFetchSignature()) {
342    return;
343  }
344  CHECK(signature_.get());
345  ExtensionIdSet leftovers = signature_->ids;
346  ExtensionIdList all_ids;
347  prefs_->GetExtensions(&all_ids);
348  for (ExtensionIdList::const_iterator i = all_ids.begin();
349       i != all_ids.end(); ++i) {
350    ExtensionIdSet::iterator found = leftovers.find(*i);
351    if (found != leftovers.end())
352      leftovers.erase(found);
353  }
354  if (!leftovers.empty()) {
355    RemoveMany(leftovers);
356  }
357}
358
359bool InstallVerifier::AllowedByEnterprisePolicy(const std::string& id) const {
360  PrefService* pref_service = prefs_->pref_service();
361  if (pref_service->IsManagedPreference(prefs::kExtensionInstallAllowList)) {
362    const base::ListValue* whitelist =
363        pref_service->GetList(prefs::kExtensionInstallAllowList);
364    base::StringValue id_value(id);
365    if (whitelist && whitelist->Find(id_value) != whitelist->end())
366      return true;
367  }
368  if (pref_service->IsManagedPreference(prefs::kExtensionInstallForceList)) {
369    const base::DictionaryValue* forcelist =
370        pref_service->GetDictionary(prefs::kExtensionInstallForceList);
371    if (forcelist && forcelist->HasKey(id))
372      return true;
373  }
374  return false;
375}
376
377bool InstallVerifier::IsVerified(const std::string& id) const {
378  return ((signature_.get() && ContainsKey(signature_->ids, id)) ||
379          ContainsKey(provisional_, id));
380}
381
382void InstallVerifier::BeginFetch() {
383  DCHECK(ShouldFetchSignature());
384
385  // TODO(asargent) - It would be possible to coalesce all operations in the
386  // queue into one fetch - we'd probably just need to change the queue to
387  // hold (set of ids, list of callbacks) pairs.
388  CHECK(!operation_queue_.empty());
389  const PendingOperation& operation = *operation_queue_.front();
390
391  ExtensionIdSet ids_to_sign;
392  if (signature_.get()) {
393    ids_to_sign.insert(signature_->ids.begin(), signature_->ids.end());
394  }
395  if (operation.type == InstallVerifier::ADD) {
396    ids_to_sign.insert(operation.ids.begin(), operation.ids.end());
397  } else {
398    for (ExtensionIdSet::const_iterator i = operation.ids.begin();
399         i != operation.ids.end(); ++i) {
400      if (ContainsKey(ids_to_sign, *i))
401        ids_to_sign.erase(*i);
402    }
403  }
404
405  signer_.reset(new InstallSigner(context_getter_, ids_to_sign));
406  signer_->GetSignature(base::Bind(&InstallVerifier::SignatureCallback,
407                                   base::Unretained(this)));
408}
409
410void InstallVerifier::SaveToPrefs() {
411  if (signature_.get())
412    DCHECK(InstallSigner::VerifySignature(*signature_));
413
414  if (!signature_.get() || signature_->ids.empty()) {
415    DVLOG(1) << "SaveToPrefs - saving NULL";
416    prefs_->SetInstallSignature(NULL);
417  } else {
418    DictionaryValue pref;
419    signature_->ToValue(&pref);
420    if (VLOG_IS_ON(1)) {
421      DVLOG(1) << "SaveToPrefs - saving";
422
423      DCHECK(InstallSigner::VerifySignature(*signature_.get()));
424      scoped_ptr<InstallSignature> rehydrated =
425          InstallSignature::FromValue(pref);
426      DCHECK(InstallSigner::VerifySignature(*rehydrated.get()));
427    }
428    prefs_->SetInstallSignature(&pref);
429  }
430}
431
432namespace {
433
434enum CallbackResult {
435  CALLBACK_NO_SIGNATURE = 0,
436  CALLBACK_INVALID_SIGNATURE,
437  CALLBACK_VALID_SIGNATURE,
438
439  // This is used in histograms - do not remove or reorder entries above! Also
440  // the "MAX" item below should always be the last element.
441
442  CALLBACK_RESULT_MAX
443};
444
445void GetSignatureResultHistogram(CallbackResult result) {
446  UMA_HISTOGRAM_ENUMERATION("ExtensionInstallVerifier.GetSignatureResult",
447                            result, CALLBACK_RESULT_MAX);
448}
449
450}  // namespace
451
452void InstallVerifier::SignatureCallback(
453    scoped_ptr<InstallSignature> signature) {
454
455  linked_ptr<PendingOperation> operation = operation_queue_.front();
456  operation_queue_.pop();
457
458  bool success = false;
459  if (!signature.get()) {
460    GetSignatureResultHistogram(CALLBACK_NO_SIGNATURE);
461  } else if (!InstallSigner::VerifySignature(*signature)) {
462    GetSignatureResultHistogram(CALLBACK_INVALID_SIGNATURE);
463  } else {
464    GetSignatureResultHistogram(CALLBACK_VALID_SIGNATURE);
465    success = true;
466  }
467
468  if (!success) {
469    if (!operation->callback.is_null())
470      operation->callback.Run(false);
471
472    // TODO(asargent) - if this was something like a network error, we need to
473    // do retries with exponential back off.
474  } else {
475    signature_ = signature.Pass();
476    SaveToPrefs();
477
478    if (!provisional_.empty()) {
479      // Update |provisional_| to remove ids that were successfully signed.
480      provisional_ = base::STLSetDifference<ExtensionIdSet>(
481          provisional_, signature_->ids);
482    }
483
484    if (!operation->callback.is_null())
485      operation->callback.Run(success);
486  }
487
488  if (!operation_queue_.empty())
489    BeginFetch();
490}
491
492
493}  // namespace extensions
494