summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions/sandboxed_extension_unpacker.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/extensions/sandboxed_extension_unpacker.cc')
-rw-r--r--chrome/browser/extensions/sandboxed_extension_unpacker.cc280
1 files changed, 280 insertions, 0 deletions
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();
+}