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