summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/extensions')
-rw-r--r--chrome/browser/extensions/extension_creator.cc12
-rw-r--r--chrome/browser/extensions/extensions_service.cc398
-rw-r--r--chrome/browser/extensions/extensions_service.h59
-rw-r--r--chrome/browser/extensions/sandboxed_extension_unpacker.cc280
-rw-r--r--chrome/browser/extensions/sandboxed_extension_unpacker.h125
5 files changed, 473 insertions, 401 deletions
diff --git a/chrome/browser/extensions/extension_creator.cc b/chrome/browser/extensions/extension_creator.cc
index 9abd030..7b7c389 100644
--- a/chrome/browser/extensions/extension_creator.cc
+++ b/chrome/browser/extensions/extension_creator.cc
@@ -12,7 +12,7 @@
#include "base/file_util.h"
#include "base/scoped_handle.h"
#include "base/string_util.h"
-#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/browser/extensions/sandboxed_extension_unpacker.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/zip.h"
#include "net/base/base64.h"
@@ -166,14 +166,14 @@ bool ExtensionCreator::WriteCRX(const FilePath& zip_path,
return false;
}
- ExtensionsService::ExtensionHeader header;
- memcpy(&header.magic, ExtensionsService::kExtensionHeaderMagic,
- ExtensionsService::kExtensionHeaderMagicSize);
- header.version = ExtensionsService::kCurrentVersion;
+ SandboxedExtensionUnpacker::ExtensionHeader header;
+ memcpy(&header.magic, SandboxedExtensionUnpacker::kExtensionHeaderMagic,
+ SandboxedExtensionUnpacker::kExtensionHeaderMagicSize);
+ header.version = SandboxedExtensionUnpacker::kCurrentVersion;
header.key_size = public_key.size();
header.signature_size = signature.size();
- fwrite(&header, sizeof(ExtensionsService::ExtensionHeader), 1,
+ fwrite(&header, sizeof(SandboxedExtensionUnpacker::ExtensionHeader), 1,
crx_handle.get());
fwrite(&public_key.front(), sizeof(uint8), public_key.size(),
crx_handle.get());
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc
index 2acc256..1b993e6 100644
--- a/chrome/browser/extensions/extensions_service.cc
+++ b/chrome/browser/extensions/extensions_service.cc
@@ -6,23 +6,16 @@
#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"
#include "base/scoped_temp_dir.h"
#include "base/stl_util-inl.h"
#include "base/string_util.h"
-#include "base/third_party/nss/blapi.h"
-#include "base/third_party/nss/sha256.h"
-#include "base/thread.h"
#include "base/values.h"
#include "net/base/file_stream.h"
#include "chrome/browser/browser.h"
#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/extension_updater.h"
@@ -31,22 +24,17 @@
#include "chrome/browser/extensions/theme_preview_infobar_delegate.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/tab_contents/tab_contents.h"
-#include "chrome/browser/utility_process_host.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_error_reporter.h"
-#include "chrome/common/extensions/extension_unpacker.h"
#include "chrome/common/json_value_serializer.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/pref_service.h"
-#include "chrome/common/zip.h"
#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)
#include "app/win_util.h"
@@ -59,8 +47,6 @@
// ExtensionsService.
-const char ExtensionsService::kExtensionHeaderMagic[] = "Cr24";
-
const char* ExtensionsService::kInstallDirectoryName = "Extensions";
const char* ExtensionsService::kCurrentVersionFileName = "Current Version";
@@ -69,27 +55,6 @@ const char* ExtensionsService::kGalleryDownloadURLPrefix =
const char* ExtensionsService::kGalleryURLPrefix =
"https://tools.google.com/chrome/";
-const char* ExtensionsServiceBackend::kTempExtensionName = "TEMP_INSTALL";
-
-namespace {
-
-// A temporary subdirectory where we unpack extensions.
-const char* kUnpackExtensionDir = "TEMP_UNPACK";
-
-// 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).";
-
-}
-
// static
bool ExtensionsService::IsDownloadFromGallery(const GURL& download_url,
const GURL& referrer_url) {
@@ -101,131 +66,46 @@ bool ExtensionsService::IsDownloadFromGallery(const GURL& download_url,
}
}
-// This class coordinates an extension unpack task which is run in a separate
-// process. Results are sent back to this class, which we route to the
-// ExtensionServiceBackend.
+// This class hosts a SandboxedExtensionUnpacker task and routes the results
+// back to ExtensionsService. The unpack process is started immediately on
+// construction of this object.
class ExtensionsServiceBackend::UnpackerClient
- : public UtilityProcessHost::Client {
+ : public SandboxedExtensionUnpackerClient {
public:
UnpackerClient(ExtensionsServiceBackend* backend,
const FilePath& extension_path,
- const std::string& public_key,
const std::string& expected_id,
bool silent, bool from_gallery)
: backend_(backend), extension_path_(extension_path),
- public_key_(public_key), expected_id_(expected_id), got_response_(false),
- silent_(silent), from_gallery_(from_gallery) {
- }
-
- // Starts the unpack task. We call back to the backend when the task is done,
- // or a problem occurs.
- void Start() {
- AddRef(); // balanced in OnUnpackExtensionReply()
-
- // TODO(mpcomplete): handle multiple installs
- FilePath temp_dir = backend_->install_directory_.AppendASCII(
- kUnpackExtensionDir);
- if (!file_util::CreateDirectory(temp_dir)) {
- backend_->ReportExtensionInstallError(extension_path_,
- "Failed to create temporary directory.");
- return;
- }
-
- temp_extension_path_ = temp_dir.Append(extension_path_.BaseName());
- if (!file_util::CopyFile(extension_path_, temp_extension_path_)) {
- backend_->ReportExtensionInstallError(extension_path_,
- "Failed to copy extension file to temporary directory.");
- return;
- }
-
- if (backend_->resource_dispatcher_host_) {
- ChromeThread::GetMessageLoop(ChromeThread::IO)->PostTask(FROM_HERE,
- NewRunnableMethod(this, &UnpackerClient::StartProcessOnIOThread,
- backend_->resource_dispatcher_host_,
- MessageLoop::current()));
- } else {
- // Cheesy... but if we don't have a ResourceDispatcherHost, assume we're
- // in a unit test and run the unpacker directly in-process.
- ExtensionUnpacker unpacker(temp_extension_path_);
- if (unpacker.Run()) {
- OnUnpackExtensionSucceededImpl(*unpacker.parsed_manifest(),
- unpacker.decoded_images());
- } else {
- OnUnpackExtensionFailed(unpacker.error_message());
- }
- }
+ expected_id_(expected_id), silent_(silent), from_gallery_(from_gallery) {
+ unpacker_ = new SandboxedExtensionUnpacker(extension_path,
+ backend->resource_dispatcher_host_, this);
+ unpacker_->Start();
}
private:
- // UtilityProcessHost::Client
- virtual void OnProcessCrashed() {
- // Don't report crashes if they happen after we got a response.
- if (got_response_)
- return;
-
- OnUnpackExtensionFailed("Chrome crashed while trying to install.");
+ // SandboxedExtensionUnpackerClient
+ virtual void OnUnpackSuccess(const FilePath& temp_dir,
+ const FilePath& extension_dir,
+ Extension* extension) {
+ backend_->OnExtensionUnpacked(extension_path_, extension_dir, extension,
+ expected_id_, silent_, from_gallery_);
+ file_util::Delete(temp_dir, true);
+ delete this;
}
- virtual void OnUnpackExtensionSucceeded(const DictionaryValue& manifest) {
- ExtensionUnpacker::DecodedImages images;
- if (!ExtensionUnpacker::ReadImagesFromFile(temp_extension_path_,
- &images)) {
- OnUnpackExtensionFailed("Couldn't read image data from disk.");
- } else {
- OnUnpackExtensionSucceededImpl(manifest, images);
- }
- }
-
- 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_manifest_keys::kPublicKey, public_key_);
-
- // The extension was unpacked to the temp dir inside our unpacking dir.
- FilePath extension_dir = temp_extension_path_.DirName().AppendASCII(
- ExtensionsServiceBackend::kTempExtensionName);
- backend_->OnExtensionUnpacked(extension_path_, extension_dir,
- expected_id_, manifest, images, silent_,
- from_gallery_);
- Cleanup();
- }
-
- virtual void OnUnpackExtensionFailed(const std::string& error_message) {
+ virtual void OnUnpackFailure(const std::string& error_message) {
backend_->ReportExtensionInstallError(extension_path_, error_message);
- Cleanup();
- }
-
- // Cleans up our temp directory.
- void Cleanup() {
- if (got_response_)
- return;
-
- got_response_ = true;
- file_util::Delete(temp_extension_path_.DirName(), true);
- Release(); // balanced in Run()
+ delete this;
}
- // Starts the utility process that unpacks our extension.
- void StartProcessOnIOThread(ResourceDispatcherHost* rdh,
- MessageLoop* file_loop) {
- UtilityProcessHost* host = new UtilityProcessHost(rdh, this, file_loop);
- host->StartExtensionUnpacker(temp_extension_path_);
- }
+ scoped_refptr<SandboxedExtensionUnpacker> unpacker_;
scoped_refptr<ExtensionsServiceBackend> backend_;
// 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_;
@@ -233,10 +113,6 @@ class ExtensionsServiceBackend::UnpackerClient
// The ID we expect this extension to have, if any.
std::string expected_id_;
- // True if we got a response from the utility process and have cleaned up
- // already.
- bool got_response_;
-
// True if the install should be done with no confirmation dialog.
bool silent_;
@@ -673,11 +549,6 @@ void ExtensionsServiceBackend::GarbageCollectExtensions(
std::string extension_id = WideToASCII(
extension_path.BaseName().ToWStringHack());
- // The utility process might be in the middle of unpacking an extension, so
- // ignore the temp unpacking directory.
- if (extension_id == kUnpackExtensionDir)
- continue;
-
// If there is no Current Version file, just delete the directory and move
// on. This can legitimately happen when an uninstall does not complete, for
// example, when a plugin is in use at uninstall time.
@@ -1038,130 +909,28 @@ void ExtensionsServiceBackend::UpdateExtension(const std::string& id,
void ExtensionsServiceBackend::InstallOrUpdateExtension(
const FilePath& extension_path, bool from_gallery,
const std::string& expected_id, bool silent) {
- 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, silent,
- from_gallery);
- 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;
+ // NOTE: We don't need to keep a reference to this, it deletes itself when it
+ // is done.
+ new UnpackerClient(this, extension_path, expected_id, silent, from_gallery);
}
void ExtensionsServiceBackend::OnExtensionUnpacked(
- const FilePath& extension_path,
- const FilePath& temp_extension_dir,
- const std::string& expected_id,
- const DictionaryValue& manifest,
- const std::vector< Tuple2<SkBitmap, FilePath> >& images,
- bool silent, bool from_gallery) {
- Extension extension;
- std::string error;
- if (!extension.InitFromValue(manifest,
- true, // require ID
- &error)) {
- ReportExtensionInstallError(extension_path, "Invalid extension manifest.");
- return;
- }
+ const FilePath& crx_path, const FilePath& unpacked_path,
+ Extension* extension, const std::string expected_id, bool silent,
+ bool from_gallery) {
+ // Take ownership of the extension object.
+ scoped_ptr<Extension> extension_deleter(extension);
Extension::Location location = Extension::INTERNAL;
- LookupExternalExtension(extension.id(), NULL, &location);
+ LookupExternalExtension(extension->id(), NULL, &location);
+ extension->set_location(location);
bool allow_install = false;
if (extensions_enabled_)
allow_install = true;
// Always allow themes.
- if (extension.IsTheme())
+ if (extension->IsTheme())
allow_install = true;
// Always allow externally installed extensions (partners use this).
@@ -1169,8 +938,7 @@ void ExtensionsServiceBackend::OnExtensionUnpacked(
allow_install = true;
if (!allow_install) {
- ReportExtensionInstallError(extension_path,
- "Extensions are not enabled.");
+ ReportExtensionInstallError(crx_path, "Extensions are not enabled.");
return;
}
@@ -1182,7 +950,7 @@ void ExtensionsServiceBackend::OnExtensionUnpacked(
// - during tests (!frontend->show_extension_prompts())
// - autoupdate (silent).
bool show_dialog = true;
- if (extension.IsTheme())
+ if (extension->IsTheme())
show_dialog = false;
if (Extension::IsExternalLocation(location))
@@ -1215,7 +983,7 @@ void ExtensionsServiceBackend::OnExtensionUnpacked(
NULL, CFSTR("Cancel"), NULL, &response);
if (response == kCFUserNotificationAlternateResponse) {
- ReportExtensionInstallError(extension_path,
+ ReportExtensionInstallError(crx_path,
"User did not allow extension to be installed.");
return;
}
@@ -1223,132 +991,66 @@ void ExtensionsServiceBackend::OnExtensionUnpacked(
}
// If an expected id was provided, make sure it matches.
- if (!expected_id.empty() && expected_id != extension.id()) {
+ if (!expected_id.empty() && expected_id != extension->id()) {
std::string error_msg = "ID in new extension manifest (";
- error_msg += extension.id();
+ error_msg += extension->id();
error_msg += ") does not match expected ID (";
error_msg += expected_id;
error_msg += ")";
- ReportExtensionInstallError(extension_path, error_msg);
+ ReportExtensionInstallError(crx_path, error_msg);
return;
}
// <profile>/Extensions/<id>
- FilePath dest_dir = install_directory_.AppendASCII(extension.id());
- std::string version = extension.VersionString();
+ FilePath dest_dir = install_directory_.AppendASCII(extension->id());
+ std::string version = extension->VersionString();
std::string current_version;
Extension::InstallType install_type =
- CompareToInstalledVersion(extension.id(), version, &current_version);
+ CompareToInstalledVersion(extension->id(), version, &current_version);
// Do not allow downgrade.
if (install_type == Extension::DOWNGRADE) {
- ReportExtensionInstallError(extension_path,
+ ReportExtensionInstallError(crx_path,
"Error: Attempt to downgrade extension from more recent version.");
return;
}
if (install_type == Extension::REINSTALL) {
- if (NeedsReinstall(extension.id(), current_version)) {
+ 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(), extension_path);
- return;
- }
- }
-
- // Write our parsed manifest back to disk, to ensure it doesn't contain an
- // exploitable bug that can be used to compromise the browser.
- std::string manifest_json;
- JSONStringValueSerializer serializer(&manifest_json);
- serializer.set_pretty_print(true);
- if (!serializer.Serialize(manifest)) {
- ReportExtensionInstallError(extension_path,
- "Error serializing manifest.json.");
- return;
- }
-
- FilePath manifest_path =
- temp_extension_dir.AppendASCII(Extension::kManifestFilename);
- if (!file_util::WriteFile(manifest_path,
- manifest_json.data(), manifest_json.size())) {
- ReportExtensionInstallError(extension_path, "Error saving manifest.json.");
- return;
- }
-
- // Delete any images that may be used by the browser. We're going to write
- // out our own versions of the parsed images, and we want to make sure the
- // originals are gone for good.
- std::set<FilePath> image_paths = extension.GetBrowserImages();
- if (image_paths.size() != images.size()) {
- ReportExtensionInstallError(extension_path,
- "Decoded images don't match what's in the manifest.");
- return;
- }
-
- for (std::set<FilePath>::iterator it = image_paths.begin();
- it != image_paths.end(); ++it) {
- if (!file_util::Delete(temp_extension_dir.Append(*it), false)) {
- ReportExtensionInstallError(extension_path,
- "Error removing old image file.");
- return;
- }
- }
-
- // Write our parsed images back to disk as well.
- for (size_t i = 0; i < images.size(); ++i) {
- const SkBitmap& image = images[i].a;
- FilePath path = temp_extension_dir.Append(images[i].b);
-
- std::vector<unsigned char> image_data;
- // TODO(mpcomplete): It's lame that we're encoding all images as PNG, even
- // though they may originally be .jpg, etc. Figure something out.
- // http://code.google.com/p/chromium/issues/detail?id=12459
- if (!PNGEncoder::EncodeBGRASkBitmap(image, false, &image_data)) {
- ReportExtensionInstallError(extension_path,
- "Error re-encoding theme image.");
- return;
- }
-
- // Note: we're overwriting existing files that the utility process wrote,
- // so we can be sure the directory exists.
- const char* image_data_ptr = reinterpret_cast<const char*>(&image_data[0]);
- if (!file_util::WriteFile(path, image_data_ptr, image_data.size())) {
- ReportExtensionInstallError(extension_path, "Error saving theme image.");
+ ReportExtensionOverinstallAttempted(extension->id(), crx_path);
return;
}
}
// <profile>/Extensions/<dir_name>/<version>
FilePath version_dir = dest_dir.AppendASCII(version);
+ extension->set_path(version_dir);
// If anything fails after this, we want to delete the extension dir.
ScopedTempDir scoped_version_dir;
scoped_version_dir.Set(version_dir);
- if (!InstallDirSafely(temp_extension_dir, version_dir))
+ if (!InstallDirSafely(unpacked_path, version_dir))
return;
if (!SetCurrentVersion(dest_dir, version))
return;
- Extension* loaded = LoadExtension(version_dir,
- location,
- true); // require id
- CHECK(loaded);
-
frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(
- frontend_, &ExtensionsService::OnExtensionInstalled, extension_path,
- loaded, install_type));
+ frontend_, &ExtensionsService::OnExtensionInstalled, crx_path,
+ extension, install_type));
// Only one extension, but ReportExtensionsLoaded can handle multiple,
// so we need to construct a list.
- scoped_ptr<ExtensionList> extensions(new ExtensionList);
- extensions->push_back(loaded);
+ ExtensionList* extensions = new ExtensionList;
- // Hand off ownership of the loaded extensions to the frontend.
- ReportExtensionsLoaded(extensions.release());
+ // Hand off ownership of the extension to the frontend.
+ extensions->push_back(extension_deleter.release());
+ ReportExtensionsLoaded(extensions);
scoped_version_dir.Take();
}
diff --git a/chrome/browser/extensions/extensions_service.h b/chrome/browser/extensions/extensions_service.h
index b664b22..5fc036b 100644
--- a/chrome/browser/extensions/extensions_service.h
+++ b/chrome/browser/extensions/extensions_service.h
@@ -20,6 +20,7 @@
#include "chrome/browser/extensions/extension_prefs.h"
#include "chrome/browser/extensions/extension_process_manager.h"
#include "chrome/browser/extensions/external_extension_provider.h"
+#include "chrome/browser/extensions/sandboxed_extension_unpacker.h"
#include "chrome/common/extensions/extension.h"
class Browser;
@@ -32,7 +33,6 @@ class MessageLoop;
class PrefService;
class Profile;
class ResourceDispatcherHost;
-class SkBitmap;
class SiteInstance;
typedef std::vector<Extension*> ExtensionList;
@@ -60,37 +60,6 @@ class ExtensionsService
public base::RefCountedThreadSafe<ExtensionsService> {
public:
- // TODO(port): Move Crx package definitions to ExtentionCreator. They are
- // currently here because ExtensionCreator is excluded on linux & mac.
-
- // The size of the magic character sequence at the beginning of each crx
- // file, in bytes. This should be a multiple of 4.
- static const size_t kExtensionHeaderMagicSize = 4;
-
- // This header is the first data at the beginning of an extension. Its
- // contents are purposely 32-bit aligned so that it can just be slurped into
- // a struct without manual parsing.
- struct ExtensionHeader {
- char magic[kExtensionHeaderMagicSize];
- uint32 version;
- size_t key_size; // The size of the public key, in bytes.
- size_t signature_size; // The size of the signature, in bytes.
- // An ASN.1-encoded PublicKeyInfo structure follows.
- // The signature follows.
- };
-
- // The maximum size the crx parser will tolerate for a public key.
- static const size_t kMaxPublicKeySize = 1 << 16;
-
- // The maximum size the crx parser will tolerate for a signature.
- static const size_t kMaxSignatureSize = 1 << 16;
-
- // The magic character sequence at the beginning of each crx file.
- static const char kExtensionHeaderMagic[];
-
- // The current version of the crx format.
- static const uint32 kCurrentVersion = 2;
-
// The name of the directory inside the profile where extensions are
// installed to.
static const char* kInstallDirectoryName;
@@ -395,22 +364,18 @@ class ExtensionsServiceBackend
bool from_gallery,
const std::string& expected_id, bool silent);
- // Validates the signature of the extension in |extension_path|. Returns true
- // and the public key (in |key|) if the signature validates, false otherwise.
- bool ValidateSignature(const FilePath& extension_path, std::string* key_out);
-
- // Finish installing an extension after it has been unpacked to
- // |temp_extension_dir| by our utility process. If |expected_id| is not
- // empty, it's verified against the extension's manifest before installation.
- // |manifest| and |images| are parsed information from the extension that
- // we want to write to disk in the browser process. If |silent| is true, there
- // will be no install confirmation dialog.
+ // Finish installing the extension in |crx_path| after it has been unpacked to
+ // |unpacked_path|. If |expected_id| is not empty, it's verified against the
+ // extension's manifest before installation. If |silent| is true, there will
+ // be no install confirmation dialog. |from_gallery| indicates whether the
+ // crx was installed from our gallery, which results in different UI.
+ //
+ // Note: We take ownership of |extension|.
void OnExtensionUnpacked(
- const FilePath& extension_path,
- const FilePath& temp_extension_dir,
- const std::string& expected_id,
- const DictionaryValue& manifest,
- const std::vector< Tuple2<SkBitmap, FilePath> >& images,
+ const FilePath& crx_path,
+ const FilePath& unpacked_path,
+ Extension* extension,
+ const std::string expected_id,
bool silent,
bool from_gallery);
diff --git a/chrome/browser/extensions/sandboxed_extension_unpacker.cc b/chrome/browser/extensions/sandboxed_extension_unpacker.cc
new file mode 100644
index 0000000..28b3b2b
--- /dev/null
+++ b/chrome/browser/extensions/sandboxed_extension_unpacker.cc
@@ -0,0 +1,280 @@
+// Copyright (c) 2009 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/sandboxed_extension_unpacker.h"
+
+#include <set>
+
+#include "base/crypto/signature_verifier.h"
+#include "base/file_util.h"
+#include "base/gfx/png_encoder.h"
+#include "base/message_loop.h"
+#include "base/scoped_handle.h"
+#include "base/task.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/browser/renderer_host/resource_dispatcher_host.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/extensions/extension_unpacker.h"
+#include "chrome/common/json_value_serializer.h"
+#include "net/base/base64.h"
+
+#include "third_party/skia/include/core/SkBitmap.h"
+
+const char SandboxedExtensionUnpacker::kExtensionHeaderMagic[] = "Cr24";
+
+SandboxedExtensionUnpacker::SandboxedExtensionUnpacker(
+ const FilePath& crx_path, ResourceDispatcherHost* rdh,
+ SandboxedExtensionUnpackerClient* client)
+ : crx_path_(crx_path), client_loop_(MessageLoop::current()), rdh_(rdh),
+ client_(client), got_response_(false) {
+
+ AddRef();
+}
+
+void SandboxedExtensionUnpacker::Start() {
+ // Create a temporary directory to work in.
+ if (!temp_dir_.CreateUniqueTempDir()) {
+ ReportFailure("Could not create temporary directory.");
+ return;
+ }
+
+ // Initialize the path that will eventually contain the unpacked extension.
+ extension_root_ = temp_dir_.path().AppendASCII("TEMP_INSTALL");
+
+ // Extract the public key and validate the package.
+ if (!ValidateSignature())
+ return; // ValidateSignature() already reported the error.
+
+ // Copy the crx file into our working directory.
+ FilePath temp_crx_path = temp_dir_.path().Append(crx_path_.BaseName());
+ if (!file_util::CopyFile(crx_path_, temp_crx_path)) {
+ ReportFailure("Failed to copy extension file to temporary directory.");
+ return;
+ }
+
+ // If we are supposed to use a subprocess, copy the crx to the temp directory
+ // and kick off the subprocess.
+ if (rdh_) {
+ ChromeThread::GetMessageLoop(ChromeThread::IO)->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &SandboxedExtensionUnpacker::StartProcessOnIOThread,
+ temp_crx_path));
+ } else {
+ // Otherwise, unpack the extension in this process.
+ ExtensionUnpacker unpacker(temp_crx_path);
+ if (unpacker.Run() && unpacker.DumpImagesToFile())
+ OnUnpackExtensionSucceeded(*unpacker.parsed_manifest());
+ else
+ OnUnpackExtensionFailed(unpacker.error_message());
+ }
+}
+
+void SandboxedExtensionUnpacker::StartProcessOnIOThread(
+ const FilePath& temp_crx_path) {
+ UtilityProcessHost* host = new UtilityProcessHost(rdh_, this,
+ MessageLoop::current());
+ host->StartExtensionUnpacker(temp_crx_path);
+}
+
+void SandboxedExtensionUnpacker::OnUnpackExtensionSucceeded(
+ const DictionaryValue& manifest) {
+ got_response_ = true;
+
+ ExtensionUnpacker::DecodedImages images;
+ if (!ExtensionUnpacker::ReadImagesFromFile(temp_dir_.path(), &images)) {
+ ReportFailure("Couldn't read image data from disk.");
+ return;
+ }
+
+ // Add the public key extracted earlier to the parsed manifest and overwrite
+ // the original manifest. We do this to ensure the manifest doesn't contain an
+ // exploitable bug that could be used to compromise the browser.
+ scoped_ptr<DictionaryValue> final_manifest(
+ static_cast<DictionaryValue*>(manifest.DeepCopy()));
+ final_manifest->SetString(extension_manifest_keys::kPublicKey, public_key_);
+
+ std::string manifest_json;
+ JSONStringValueSerializer serializer(&manifest_json);
+ serializer.set_pretty_print(true);
+ if (!serializer.Serialize(*final_manifest)) {
+ ReportFailure("Error serializing manifest.json.");
+ return;
+ }
+
+ FilePath manifest_path =
+ extension_root_.AppendASCII(Extension::kManifestFilename);
+ if (!file_util::WriteFile(manifest_path,
+ manifest_json.data(), manifest_json.size())) {
+ ReportFailure("Error saving manifest.json.");
+ return;
+ }
+
+ // Delete any images that may be used by the browser. We're going to write
+ // out our own versions of the parsed images, and we want to make sure the
+ // originals are gone for good.
+ extension_.reset(new Extension);
+ std::string manifest_error;
+ if (!extension_->InitFromValue(*final_manifest, true, // require id
+ &manifest_error)) {
+ ReportFailure(std::string("Manifest is invalid: ") +
+ manifest_error);
+ return;
+ }
+
+ std::set<FilePath> image_paths = extension_->GetBrowserImages();
+ if (image_paths.size() != images.size()) {
+ ReportFailure("Decoded images don't match what's in the manifest.");
+ return;
+ }
+
+ for (std::set<FilePath>::iterator it = image_paths.begin();
+ it != image_paths.end(); ++it) {
+ if (!file_util::Delete(extension_root_.Append(*it), false)) {
+ ReportFailure("Error removing old image file.");
+ return;
+ }
+ }
+
+ // Write our parsed images back to disk as well.
+ for (size_t i = 0; i < images.size(); ++i) {
+ const SkBitmap& image = images[i].a;
+ FilePath path = extension_root_.Append(images[i].b);
+
+ std::vector<unsigned char> image_data;
+ // TODO(mpcomplete): It's lame that we're encoding all images as PNG, even
+ // though they may originally be .jpg, etc. Figure something out.
+ // http://code.google.com/p/chromium/issues/detail?id=12459
+ if (!PNGEncoder::EncodeBGRASkBitmap(image, false, &image_data)) {
+ ReportFailure("Error re-encoding theme image.");
+ return;
+ }
+
+ // Note: we're overwriting existing files that the utility process wrote,
+ // so we can be sure the directory exists.
+ const char* image_data_ptr = reinterpret_cast<const char*>(&image_data[0]);
+ if (!file_util::WriteFile(path, image_data_ptr, image_data.size())) {
+ ReportFailure("Error saving theme image.");
+ return;
+ }
+ }
+
+ ReportSuccess();
+}
+
+void SandboxedExtensionUnpacker::OnUnpackExtensionFailed(
+ const std::string& error) {
+ got_response_ = true;
+ ReportFailure(error);
+}
+
+void SandboxedExtensionUnpacker::OnProcessCrashed() {
+ // Don't report crashes if they happen after we got a response.
+ if (got_response_)
+ return;
+
+ ReportFailure("Utility process crashed while trying to install.");
+}
+
+bool SandboxedExtensionUnpacker::ValidateSignature() {
+ ScopedStdioHandle file(file_util::OpenFile(crx_path_, "rb"));
+ if (!file.get()) {
+ ReportFailure("Could not open crx file for reading");
+ return false;
+ }
+
+ // Read and verify the header.
+ 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(ExtensionHeader),
+ file.get());
+ if (len < sizeof(ExtensionHeader)) {
+ ReportFailure("Invalid crx header");
+ return false;
+ }
+ if (strncmp(kExtensionHeaderMagic, header.magic,
+ sizeof(header.magic))) {
+ ReportFailure("Bad magic number");
+ return false;
+ }
+ if (header.version != kCurrentVersion) {
+ ReportFailure("Bad version number");
+ return false;
+ }
+ if (header.key_size > kMaxPublicKeySize ||
+ header.signature_size > kMaxSignatureSize) {
+ ReportFailure("Excessively large key or signature");
+ 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) {
+ ReportFailure("Invalid public key");
+ 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) {
+ ReportFailure("Invalid signature");
+ 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())) {
+ ReportFailure("Signature verification initialization failed. "
+ "This is most likely caused by a public key in "
+ "the wrong format (should encode algorithm).");
+ return false;
+ }
+
+ unsigned char buf[1 << 12];
+ while ((len = fread(buf, 1, sizeof(buf), file.get())) > 0)
+ verifier.VerifyUpdate(buf, len);
+
+ if (!verifier.VerifyFinal()) {
+ ReportFailure("Signature verification failed");
+ return false;
+ }
+
+ net::Base64Encode(std::string(reinterpret_cast<char*>(&key.front()),
+ key.size()), &public_key_);
+ return true;
+}
+
+void SandboxedExtensionUnpacker::ReportFailure(const std::string& error) {
+ client_->OnUnpackFailure(error);
+ Release();
+}
+
+void SandboxedExtensionUnpacker::ReportSuccess() {
+ // Client takes ownership of temporary directory and extension.
+ client_->OnUnpackSuccess(temp_dir_.Take(), extension_root_,
+ extension_.release());
+ Release();
+}
diff --git a/chrome/browser/extensions/sandboxed_extension_unpacker.h b/chrome/browser/extensions/sandboxed_extension_unpacker.h
new file mode 100644
index 0000000..4023ea9
--- /dev/null
+++ b/chrome/browser/extensions/sandboxed_extension_unpacker.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2009 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_SANDBOXED_EXTENSION_UNPACKER_H_
+#define CHROME_BROWSER_EXTENSIONS_SANDBOXED_EXTENSION_UNPACKER_H_
+
+#include <string>
+
+#include "base/file_path.h"
+#include "base/ref_counted.h"
+#include "base/scoped_temp_dir.h"
+#include "base/values.h"
+#include "chrome/browser/utility_process_host.h"
+
+class Extension;
+class MessageLoop;
+class ResourceDispatcherHost;
+
+class SandboxedExtensionUnpackerClient {
+ public:
+ // temp_dir - A temporary directoy containing the results of the extension
+ // unpacking. The client is responsible for deleting this directory.
+ //
+ // extension_root - The path to the extension root inside of temp_dir.
+ //
+ // extension - The extension that was unpacked. The client is responsible
+ // for deleting this memory.
+ virtual void OnUnpackSuccess(const FilePath& temp_dir,
+ const FilePath& extension_root,
+ Extension* extension) = 0;
+ virtual void OnUnpackFailure(const std::string& error) = 0;
+};
+
+// SandboxedExtensionUnpacker unpacks extensions from the CRX format into a
+// directory. This is done in a sandboxed subprocess to protect the browser
+// process from parsing complex formats like JPEG or JSON from untrusted
+// sources.
+//
+// Unpacking an extension using this class makes minor changes to its source,
+// such as transcoding all images to PNG and rewriting the manifest JSON. As
+// such, it should not be used when the output is not intended to be given back
+// to the author.
+//
+// NOTE: This class should only be used on the file thread.
+
+
+class SandboxedExtensionUnpacker : public UtilityProcessHost::Client {
+ public:
+ // The size of the magic character sequence at the beginning of each crx
+ // file, in bytes. This should be a multiple of 4.
+ static const size_t kExtensionHeaderMagicSize = 4;
+
+ // This header is the first data at the beginning of an extension. Its
+ // contents are purposely 32-bit aligned so that it can just be slurped into
+ // a struct without manual parsing.
+ struct ExtensionHeader {
+ char magic[kExtensionHeaderMagicSize];
+ uint32 version;
+ size_t key_size; // The size of the public key, in bytes.
+ size_t signature_size; // The size of the signature, in bytes.
+ // An ASN.1-encoded PublicKeyInfo structure follows.
+ // The signature follows.
+ };
+
+ // The maximum size the crx parser will tolerate for a public key.
+ static const size_t kMaxPublicKeySize = 1 << 16;
+
+ // The maximum size the crx parser will tolerate for a signature.
+ static const size_t kMaxSignatureSize = 1 << 16;
+
+ // The magic character sequence at the beginning of each crx file.
+ static const char kExtensionHeaderMagic[];
+
+ // The current version of the crx format.
+ static const uint32 kCurrentVersion = 2;
+
+ // Unpacks the extension in |crx_path| into a temporary directory and calls
+ // |client| with the result. If |rdh| is provided, unpacking is done in a
+ // sandboxed subprocess. Otherwise, it is done in-process.
+ SandboxedExtensionUnpacker(const FilePath& crx_path,
+ ResourceDispatcherHost* rdh,
+ SandboxedExtensionUnpackerClient* cilent);
+
+ // Start unpacking the extension. The client is called with the results.
+ void Start();
+
+ private:
+ class ProcessHostClient;
+ friend class ProcessHostClient;
+
+ // Validates the signature of the extension and extract the key to
+ // |public_key_|. Returns true if the signature validates, false otherwise.
+ //
+ // NOTE: Having this method here is a bit ugly. This code should really live
+ // in ExtensionUnpacker as it is not specific to sandboxed unpacking. It was
+ // put here because we cannot run windows crypto code in the sandbox. But we
+ // could still have this method statically on ExtensionUnpacker so that code
+ // just for unpacking is there and code just for sandboxing of unpacking is
+ // here.
+ bool ValidateSignature();
+
+ // Starts the utility process that unpacks our extension.
+ void StartProcessOnIOThread(const FilePath& temp_crx_path);
+
+ // SandboxedExtensionUnpacker
+ void OnUnpackExtensionSucceeded(const DictionaryValue& manifest);
+ void OnUnpackExtensionFailed(const std::string& error_message);
+ void OnProcessCrashed();
+
+ void ReportFailure(const std::string& message);
+ void ReportSuccess();
+
+ FilePath crx_path_;
+ MessageLoop* client_loop_;
+ ResourceDispatcherHost* rdh_;
+ SandboxedExtensionUnpackerClient* client_;
+ ScopedTempDir temp_dir_;
+ FilePath extension_root_;
+ scoped_ptr<Extension> extension_;
+ bool got_response_;
+ std::string public_key_;
+};
+
+#endif // CHROME_BROWSER_EXTENSIONS_SANDBOXED_EXTENSION_UNPACKER_H_