| Index: chrome/browser/extensions/extensions_service.cc
|
| diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc
|
| index bf79e78b858e975cd681e351e799b317badf8476..6d99e84f127081516a6d03a947c90220b29315d2 100644
|
| --- a/chrome/browser/extensions/extensions_service.cc
|
| +++ b/chrome/browser/extensions/extensions_service.cc
|
| @@ -6,6 +6,7 @@
|
|
|
| #include "app/l10n_util.h"
|
| #include "base/command_line.h"
|
| +#include "base/crypto/signature_verifier.h"
|
| #include "base/file_util.h"
|
| #include "base/gfx/png_encoder.h"
|
| #include "base/scoped_handle.h"
|
| @@ -20,6 +21,7 @@
|
| #include "chrome/browser/browser_list.h"
|
| #include "chrome/browser/browser_process.h"
|
| #include "chrome/browser/chrome_thread.h"
|
| +#include "chrome/browser/extensions/extension_creator.h"
|
| #include "chrome/browser/extensions/extension_browser_event_router.h"
|
| #include "chrome/browser/extensions/extension_process_manager.h"
|
| #include "chrome/browser/extensions/external_extension_provider.h"
|
| @@ -38,6 +40,7 @@
|
| #include "chrome/common/url_constants.h"
|
| #include "grit/chromium_strings.h"
|
| #include "grit/generated_resources.h"
|
| +#include "net/base/base64.h"
|
| #include "third_party/skia/include/core/SkBitmap.h"
|
|
|
| #if defined(OS_WIN)
|
| @@ -48,6 +51,8 @@
|
|
|
| // ExtensionsService.
|
|
|
| +const char ExtensionsService::kExtensionHeaderMagic[] = "Cr24";
|
| +
|
| const char* ExtensionsService::kInstallDirectoryName = "Extensions";
|
| const char* ExtensionsService::kCurrentVersionFileName = "Current Version";
|
| const char* ExtensionsServiceBackend::kTempExtensionName = "TEMP_INSTALL";
|
| @@ -65,8 +70,17 @@ const wchar_t kState[] = L"state";
|
| // A temporary subdirectory where we unpack extensions.
|
| const char* kUnpackExtensionDir = "TEMP_UNPACK";
|
|
|
| -// The version of the extension package that this code understands.
|
| -const uint32 kExpectedVersion = 1;
|
| +// Unpacking errors
|
| +const char* kBadMagicNumberError = "Bad magic number";
|
| +const char* kBadHeaderSizeError = "Excessively large key or signature";
|
| +const char* kBadVersionNumberError = "Bad version number";
|
| +const char* kInvalidExtensionHeaderError = "Invalid extension header";
|
| +const char* kInvalidPublicKeyError = "Invalid public key";
|
| +const char* kInvalidSignatureError = "Invalid signature";
|
| +const char* kSignatureVerificationFailed = "Signature verification failed";
|
| +const char* kSignatureVerificationInitFailed =
|
| + "Signature verification initialization failed. This is most likely "
|
| + "caused by a public key in the wrong format (should encode algorithm).";
|
| }
|
|
|
| // This class coordinates an extension unpack task which is run in a separate
|
| @@ -77,11 +91,12 @@ class ExtensionsServiceBackend::UnpackerClient
|
| public:
|
| UnpackerClient(ExtensionsServiceBackend* backend,
|
| const FilePath& extension_path,
|
| + const std::string& public_key,
|
| const std::string& expected_id,
|
| bool from_external)
|
| : backend_(backend), extension_path_(extension_path),
|
| - expected_id_(expected_id), from_external_(from_external),
|
| - got_response_(false) {
|
| + public_key_(public_key), expected_id_(expected_id),
|
| + from_external_(from_external), got_response_(false) {
|
| }
|
|
|
| // Starts the unpack task. We call back to the backend when the task is done,
|
| @@ -146,6 +161,14 @@ class ExtensionsServiceBackend::UnpackerClient
|
| void OnUnpackExtensionSucceededImpl(
|
| const DictionaryValue& manifest,
|
| const ExtensionUnpacker::DecodedImages& images) {
|
| + // Add our public key into the parsed manifest. We want it to be saved so
|
| + // that we can later refer to it (eg for generating ids, validating
|
| + // signatures, etc).
|
| + // The const_cast is hacky, but seems like the right thing here, rather than
|
| + // making a full copy just to make this change.
|
| + const_cast<DictionaryValue*>(&manifest)->SetString(
|
| + Extension::kPublicKeyKey, public_key_);
|
| +
|
| // The extension was unpacked to the temp dir inside our unpacking dir.
|
| FilePath extension_dir = temp_extension_path_.DirName().AppendASCII(
|
| ExtensionsServiceBackend::kTempExtensionName);
|
| @@ -182,6 +205,9 @@ class ExtensionsServiceBackend::UnpackerClient
|
| // The path to the crx file that we're installing.
|
| FilePath extension_path_;
|
|
|
| + // The public key of the extension we're installing.
|
| + std::string public_key_;
|
| +
|
| // The path to the copy of the crx file in the temporary directory where we're
|
| // unpacking it.
|
| FilePath temp_extension_path_;
|
| @@ -355,7 +381,7 @@ void ExtensionsService::OnExtensionsLoaded(ExtensionList* new_extensions) {
|
| }
|
|
|
| void ExtensionsService::OnExtensionInstalled(Extension* extension,
|
| - bool update) {
|
| + Extension::InstallType install_type) {
|
| UpdateExtensionPref(ASCIIToWide(extension->id()), kState,
|
| Value::CreateIntegerValue(Extension::ENABLED), false);
|
| UpdateExtensionPref(ASCIIToWide(extension->id()), kLocation,
|
| @@ -385,7 +411,7 @@ void ExtensionsService::OnExternalExtensionInstalled(
|
| Value::CreateIntegerValue(location), true);
|
| }
|
|
|
| -void ExtensionsService::OnExtensionVersionReinstalled(const std::string& id) {
|
| +void ExtensionsService::OnExtensionOverinstallAttempted(const std::string& id) {
|
| Extension* extension = GetExtensionByID(id);
|
| if (extension && extension->IsTheme()) {
|
| NotificationService::current()->Notify(
|
| @@ -630,6 +656,21 @@ void ExtensionsServiceBackend::LoadSingleExtension(
|
| }
|
| }
|
|
|
| +DictionaryValue* ExtensionsServiceBackend::ReadManifest(FilePath manifest_path,
|
| + std::string* error) {
|
| + JSONFileValueSerializer serializer(manifest_path);
|
| + scoped_ptr<Value> root(serializer.Deserialize(error));
|
| + if (!root.get())
|
| + return NULL;
|
| +
|
| + if (!root->IsType(Value::TYPE_DICTIONARY)) {
|
| + *error = Extension::kInvalidManifestError;
|
| + return NULL;
|
| + }
|
| +
|
| + return static_cast<DictionaryValue*>(root.release());
|
| +}
|
| +
|
| Extension* ExtensionsServiceBackend::LoadExtension(
|
| const FilePath& extension_path,
|
| Extension::Location location,
|
| @@ -641,22 +682,15 @@ Extension* ExtensionsServiceBackend::LoadExtension(
|
| return NULL;
|
| }
|
|
|
| - JSONFileValueSerializer serializer(manifest_path);
|
| std::string error;
|
| - scoped_ptr<Value> root(serializer.Deserialize(&error));
|
| + scoped_ptr<DictionaryValue> root(ReadManifest(manifest_path, &error));
|
| if (!root.get()) {
|
| ReportExtensionLoadError(extension_path, error);
|
| return NULL;
|
| }
|
|
|
| - if (!root->IsType(Value::TYPE_DICTIONARY)) {
|
| - ReportExtensionLoadError(extension_path, Extension::kInvalidManifestError);
|
| - return NULL;
|
| - }
|
| -
|
| scoped_ptr<Extension> extension(new Extension(extension_path));
|
| - if (!extension->InitFromValue(*static_cast<DictionaryValue*>(root.get()),
|
| - require_id, &error)) {
|
| + if (!extension->InitFromValue(*root.get(), require_id, &error)) {
|
| ReportExtensionLoadError(extension_path, error);
|
| return NULL;
|
| }
|
| @@ -765,28 +799,36 @@ bool ExtensionsServiceBackend::ReadCurrentVersion(const FilePath& dir,
|
| return false;
|
| }
|
|
|
| -bool ExtensionsServiceBackend::CheckCurrentVersion(
|
| +Extension::InstallType ExtensionsServiceBackend::CompareToInstalledVersion(
|
| + const std::string& id,
|
| const std::string& new_version_str,
|
| - const std::string& current_version_str,
|
| - const FilePath& dest_dir) {
|
| + std::string *current_version_str) {
|
| + CHECK(current_version_str);
|
| + FilePath dir(install_directory_.AppendASCII(id.c_str()));
|
| + if (!ReadCurrentVersion(dir, current_version_str))
|
| + return Extension::NEW_INSTALL;
|
| +
|
| scoped_ptr<Version> current_version(
|
| - Version::GetVersionFromString(current_version_str));
|
| + Version::GetVersionFromString(*current_version_str));
|
| scoped_ptr<Version> new_version(
|
| - Version::GetVersionFromString(new_version_str));
|
| - if (current_version->CompareTo(*new_version) >= 0) {
|
| - // Verify that the directory actually exists. If it doesn't we'll return
|
| - // true so that the install code will repair the broken installation.
|
| - // TODO(erikkay): A further step would be to verify that the extension
|
| - // has actually loaded successfully.
|
| - FilePath version_dir = dest_dir.AppendASCII(current_version_str);
|
| - if (file_util::PathExists(version_dir)) {
|
| - std::string id = WideToASCII(dest_dir.BaseName().ToWStringHack());
|
| - StringToLowerASCII(&id);
|
| - ReportExtensionVersionReinstalled(id);
|
| - return false;
|
| - }
|
| - }
|
| - return true;
|
| + Version::GetVersionFromString(new_version_str));
|
| + int comp = new_version->CompareTo(*current_version);
|
| + if (comp > 0)
|
| + return Extension::UPGRADE;
|
| + else if (comp == 0)
|
| + return Extension::REINSTALL;
|
| + else
|
| + return Extension::DOWNGRADE;
|
| +}
|
| +
|
| +bool ExtensionsServiceBackend::NeedsReinstall(const std::string& id,
|
| + const std::string& current_version) {
|
| + // Verify that the directory actually exists.
|
| + // TODO(erikkay): A further step would be to verify that the extension
|
| + // has actually loaded successfully.
|
| + FilePath dir(install_directory_.AppendASCII(id.c_str()));
|
| + FilePath version_dir(dir.AppendASCII(current_version));
|
| + return !file_util::PathExists(version_dir);
|
| }
|
|
|
| bool ExtensionsServiceBackend::InstallDirSafely(const FilePath& source_dir,
|
| @@ -873,13 +915,107 @@ void ExtensionsServiceBackend::InstallExtension(
|
| }
|
|
|
| void ExtensionsServiceBackend::InstallOrUpdateExtension(
|
| - const FilePath& extension_path, const std::string& expected_id,
|
| + const FilePath& extension_path,
|
| + const std::string& expected_id,
|
| bool from_external) {
|
| - UnpackerClient* client =
|
| - new UnpackerClient(this, extension_path, expected_id, from_external);
|
| + std::string actual_public_key;
|
| + if (!ValidateSignature(extension_path, &actual_public_key))
|
| + return; // Failures reported within ValidateSignature().
|
| +
|
| + UnpackerClient* client = new UnpackerClient(
|
| + this, extension_path, actual_public_key, expected_id, from_external);
|
| client->Start();
|
| }
|
|
|
| +bool ExtensionsServiceBackend::ValidateSignature(const FilePath& extension_path,
|
| + std::string* key_out) {
|
| + ScopedStdioHandle file(file_util::OpenFile(extension_path, "rb"));
|
| + if (!file.get()) {
|
| + ReportExtensionInstallError(extension_path, "Could not open file.");
|
| + return NULL;
|
| + }
|
| +
|
| + // Read and verify the header.
|
| + ExtensionsService::ExtensionHeader header;
|
| + size_t len;
|
| +
|
| + // TODO(erikkay): Yuck. I'm not a big fan of this kind of code, but it
|
| + // appears that we don't have any endian/alignment aware serialization
|
| + // code in the code base. So for now, this assumes that we're running
|
| + // on a little endian machine with 4 byte alignment.
|
| + len = fread(&header, 1, sizeof(ExtensionsService::ExtensionHeader),
|
| + file.get());
|
| + if (len < sizeof(ExtensionsService::ExtensionHeader)) {
|
| + ReportExtensionInstallError(extension_path, kInvalidExtensionHeaderError);
|
| + return false;
|
| + }
|
| + if (strncmp(ExtensionsService::kExtensionHeaderMagic, header.magic,
|
| + sizeof(header.magic))) {
|
| + ReportExtensionInstallError(extension_path, kBadMagicNumberError);
|
| + return false;
|
| + }
|
| + if (header.version != ExtensionsService::kCurrentVersion) {
|
| + ReportExtensionInstallError(extension_path, kBadVersionNumberError);
|
| + return false;
|
| + }
|
| + if (header.key_size > ExtensionsService::kMaxPublicKeySize ||
|
| + header.signature_size > ExtensionsService::kMaxSignatureSize) {
|
| + ReportExtensionInstallError(extension_path, kBadHeaderSizeError);
|
| + return false;
|
| + }
|
| +
|
| + std::vector<uint8> key;
|
| + key.resize(header.key_size);
|
| + len = fread(&key.front(), sizeof(uint8), header.key_size, file.get());
|
| + if (len < header.key_size) {
|
| + ReportExtensionInstallError(extension_path, kInvalidPublicKeyError);
|
| + return false;
|
| + }
|
| +
|
| + std::vector<uint8> signature;
|
| + signature.resize(header.signature_size);
|
| + len = fread(&signature.front(), sizeof(uint8), header.signature_size,
|
| + file.get());
|
| + if (len < header.signature_size) {
|
| + ReportExtensionInstallError(extension_path, kInvalidSignatureError);
|
| + return false;
|
| + }
|
| +
|
| + // Note: this structure is an ASN.1 which encodes the algorithm used
|
| + // with its parameters. This is defined in PKCS #1 v2.1 (RFC 3447).
|
| + // It is encoding: { OID sha1WithRSAEncryption PARAMETERS NULL }
|
| + // TODO(aa): This needs to be factored away someplace common.
|
| + const uint8 signature_algorithm[15] = {
|
| + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
|
| + 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00
|
| + };
|
| +
|
| + base::SignatureVerifier verifier;
|
| + if (!verifier.VerifyInit(signature_algorithm,
|
| + sizeof(signature_algorithm),
|
| + &signature.front(),
|
| + signature.size(),
|
| + &key.front(),
|
| + key.size())) {
|
| + ReportExtensionInstallError(extension_path,
|
| + kSignatureVerificationInitFailed);
|
| + return false;
|
| + }
|
| +
|
| + unsigned char buf[1 << 12];
|
| + while ((len = fread(buf, 1, sizeof(buf), file.get())) > 0)
|
| + verifier.VerifyUpdate(buf, len);
|
| +
|
| + if (!verifier.VerifyFinal()) {
|
| + ReportExtensionInstallError(extension_path, kSignatureVerificationFailed);
|
| + return false;
|
| + }
|
| +
|
| + net::Base64Encode(std::string(reinterpret_cast<char*>(&key.front()),
|
| + key.size()), key_out);
|
| + return true;
|
| +}
|
| +
|
| void ExtensionsServiceBackend::OnExtensionUnpacked(
|
| const FilePath& extension_path,
|
| const FilePath& temp_extension_dir,
|
| @@ -942,11 +1078,25 @@ void ExtensionsServiceBackend::OnExtensionUnpacked(
|
| FilePath dest_dir = install_directory_.AppendASCII(extension.id());
|
| std::string version = extension.VersionString();
|
| std::string current_version;
|
| - bool was_update = false;
|
| - if (ReadCurrentVersion(dest_dir, ¤t_version)) {
|
| - if (!CheckCurrentVersion(version, current_version, dest_dir))
|
| + Extension::InstallType install_type =
|
| + CompareToInstalledVersion(extension.id(), version, ¤t_version);
|
| +
|
| + // Do not allow downgrade.
|
| + if (install_type == Extension::DOWNGRADE) {
|
| + ReportExtensionInstallError(extension_path,
|
| + "Error: Attempt to downgrade extension from more recent version.");
|
| + return;
|
| + }
|
| +
|
| + if (install_type == Extension::REINSTALL) {
|
| + if (NeedsReinstall(extension.id(), current_version)) {
|
| + // Treat corrupted existing installation as new install case.
|
| + install_type = Extension::NEW_INSTALL;
|
| + } else {
|
| + // The client may use this as a signal (to switch themes, for instance).
|
| + ReportExtensionOverinstallAttempted(extension.id());
|
| return;
|
| - was_update = true;
|
| + }
|
| }
|
|
|
| // Write our parsed manifest back to disk, to ensure it doesn't contain an
|
| @@ -1044,7 +1194,7 @@ void ExtensionsServiceBackend::OnExtensionUnpacked(
|
|
|
| frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(
|
| frontend_, &ExtensionsService::OnExtensionInstalled, extension,
|
| - was_update));
|
| + install_type));
|
|
|
| // Only one extension, but ReportExtensionsLoaded can handle multiple,
|
| // so we need to construct a list.
|
| @@ -1073,10 +1223,10 @@ void ExtensionsServiceBackend::ReportExtensionInstallError(
|
| ExtensionErrorReporter::GetInstance()->ReportError(message, alert_on_error_);
|
| }
|
|
|
| -void ExtensionsServiceBackend::ReportExtensionVersionReinstalled(
|
| +void ExtensionsServiceBackend::ReportExtensionOverinstallAttempted(
|
| const std::string& id) {
|
| frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(
|
| - frontend_, &ExtensionsService::OnExtensionVersionReinstalled, id));
|
| + frontend_, &ExtensionsService::OnExtensionOverinstallAttempted, id));
|
| }
|
|
|
| bool ExtensionsServiceBackend::ShouldSkipInstallingExtension(
|
| @@ -1224,9 +1374,14 @@ void ExtensionsServiceBackend::OnExternalExtensionFound(
|
|
|
| bool ExtensionsServiceBackend::ShouldInstall(const std::string& id,
|
| const Version* version) {
|
| - FilePath dir(install_directory_.AppendASCII(id.c_str()));
|
| std::string current_version;
|
| - if (ReadCurrentVersion(dir, ¤t_version))
|
| - return CheckCurrentVersion(version->GetString(), current_version, dir);
|
| - return true;
|
| + Extension::InstallType install_type =
|
| + CompareToInstalledVersion(id, version->GetString(), ¤t_version);
|
| +
|
| + if (install_type == Extension::DOWNGRADE)
|
| + return false;
|
| +
|
| + return (install_type == Extension::UPGRADE ||
|
| + install_type == Extension::NEW_INSTALL ||
|
| + NeedsReinstall(id, current_version));
|
| }
|
|
|