diff options
author | mpcomplete@google.com <mpcomplete@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-15 22:23:43 +0000 |
---|---|---|
committer | mpcomplete@google.com <mpcomplete@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-15 22:23:43 +0000 |
commit | 1fca149ca717c64ae05edb534a61a909dc0a6d11 (patch) | |
tree | 6972a9efe49eba842a77cdeb98be8ac2caba8d27 /chrome/common/extensions | |
parent | 20a85780ae0ae9b8467b10146044fec8c1144e77 (diff) | |
download | chromium_src-1fca149ca717c64ae05edb534a61a909dc0a6d11.zip chromium_src-1fca149ca717c64ae05edb534a61a909dc0a6d11.tar.gz chromium_src-1fca149ca717c64ae05edb534a61a909dc0a6d11.tar.bz2 |
Introducing the Utility process, which handles the unpacking and verification
of extension packages.
This is a first pass. In the second pass, I will add support for transcoding
the manifest and any images in the browser process.
BUG=11680
Review URL: http://codereview.chromium.org/114027
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@16198 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/common/extensions')
-rw-r--r-- | chrome/common/extensions/extension_unpacker.cc | 212 | ||||
-rw-r--r-- | chrome/common/extensions/extension_unpacker.h | 45 |
2 files changed, 257 insertions, 0 deletions
diff --git a/chrome/common/extensions/extension_unpacker.cc b/chrome/common/extensions/extension_unpacker.cc new file mode 100644 index 0000000..1849ec8 --- /dev/null +++ b/chrome/common/extensions/extension_unpacker.cc @@ -0,0 +1,212 @@ +// 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/common/extensions/extension_unpacker.h" + +#include "base/file_util.h" +#include "base/scoped_handle.h" +#include "base/scoped_temp_dir.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" +// TODO(mpcomplete): move to common +#include "chrome/browser/extensions/extension.h" +#include "chrome/common/json_value_serializer.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/unzip.h" +#include "chrome/common/url_constants.h" + +namespace { +const char kCurrentVersionFileName[] = "Current Version"; + +// The name of a temporary directory to install an extension into for +// validation before finalizing install. +const char kTempExtensionName[] = "TEMP_INSTALL"; + +// Chromium Extension magic number +const char kExtensionFileMagic[] = "Cr24"; + +struct ExtensionHeader { + char magic[sizeof(kExtensionFileMagic) - 1]; + uint32 version; + size_t header_size; + size_t manifest_size; +}; + +const size_t kZipHashBytes = 32; // SHA-256 +const size_t kZipHashHexBytes = kZipHashBytes * 2; // Hex string is 2x size. + +#if defined(OS_WIN) + +// Registry key where registry defined extension installers live. +const wchar_t kRegistryExtensions[] = L"Software\\Google\\Chrome\\Extensions"; + +// Registry value of of that key that defines the path to the .crx file. +const wchar_t kRegistryExtensionPath[] = L"path"; + +// Registry value of that key that defines the current version of the .crx file. +const wchar_t kRegistryExtensionVersion[] = L"version"; + +#endif + +// A marker file to indicate that an extension was installed from an external +// source. +const char kExternalInstallFile[] = "EXTERNAL_INSTALL"; + +// The version of the extension package that this code understands. +const uint32 kExpectedVersion = 1; +} + +// The extension file format is a header, followed by the manifest, followed +// by the zip file. The header is a magic number, a version, the size of the +// header, and the size of the manifest. These ints are 4 byte little endian. +DictionaryValue* ExtensionUnpacker::ReadManifest() { + ScopedStdioHandle file(file_util::OpenFile(extension_path_, "rb")); + if (!file.get()) { + SetError("no such extension file"); + return NULL; + } + + // 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)) { + SetError("invalid extension header"); + return NULL; + } + if (strncmp(kExtensionFileMagic, header.magic, sizeof(header.magic))) { + SetError("bad magic number"); + return NULL; + } + if (header.version != kExpectedVersion) { + SetError("bad version number"); + return NULL; + } + if (header.header_size > sizeof(ExtensionHeader)) + fseek(file.get(), header.header_size - sizeof(ExtensionHeader), SEEK_CUR); + + char buf[1 << 16]; + std::string manifest_str; + size_t read_size = std::min(sizeof(buf), header.manifest_size); + size_t remainder = header.manifest_size; + while ((len = fread(buf, 1, read_size, file.get())) > 0) { + manifest_str.append(buf, len); + if (len <= remainder) + break; + remainder -= len; + read_size = std::min(sizeof(buf), remainder); + } + + // Verify the JSON + JSONStringValueSerializer json(manifest_str); + std::string error; + scoped_ptr<Value> val(json.Deserialize(&error)); + if (!val.get()) { + SetError(error); + return NULL; + } + if (!val->IsType(Value::TYPE_DICTIONARY)) { + SetError("manifest isn't a JSON dictionary"); + return NULL; + } + DictionaryValue* manifest = static_cast<DictionaryValue*>(val.get()); + + std::string zip_hash; + if (!manifest->GetString(Extension::kZipHashKey, &zip_hash)) { + SetError("missing zip_hash key"); + return NULL; + } + if (zip_hash.size() != kZipHashHexBytes) { + SetError("invalid zip_hash key"); + return NULL; + } + + // Read the rest of the zip file and compute a hash to compare against + // what the manifest claims. Compute the hash incrementally since the + // zip file could be large. + const unsigned char* ubuf = reinterpret_cast<const unsigned char*>(buf); + SHA256Context ctx; + SHA256_Begin(&ctx); + while ((len = fread(buf, 1, sizeof(buf), file.get())) > 0) + SHA256_Update(&ctx, ubuf, len); + uint8 hash[32]; + SHA256_End(&ctx, hash, NULL, sizeof(hash)); + + std::vector<uint8> zip_hash_bytes; + if (!HexStringToBytes(zip_hash, &zip_hash_bytes)) { + SetError("invalid zip_hash key"); + return NULL; + } + if (zip_hash_bytes.size() != kZipHashBytes) { + SetError("invalid zip_hash key"); + return NULL; + } + for (size_t i = 0; i < kZipHashBytes; ++i) { + if (zip_hash_bytes[i] != hash[i]) { + SetError("zip_hash key didn't match zip hash"); + return NULL; + } + } + + // TODO(erikkay): The manifest will also contain a signature of the hash + // (or perhaps the whole manifest) for authentication purposes. + + // The caller owns val (now cast to manifest). + val.release(); + return manifest; +} + +bool ExtensionUnpacker::Run() { + LOG(INFO) << "Installing extension " << extension_path_.value(); + + // Read and verify the extension. + scoped_ptr<DictionaryValue> manifest(ReadManifest()); + if (!manifest.get()) { + // ReadManifest has already reported the extension error. + return false; + } + Extension extension; + std::string error; + if (!extension.InitFromValue(*manifest, + true, // require ID + &error)) { + SetError("Invalid extension manifest."); + return false; + } + + // ID is required for installed extensions. + if (extension.id().empty()) { + SetError("Required value 'id' is missing."); + return false; + } + + // <profile>/Extensions/INSTALL_TEMP/<version> + std::string version = extension.VersionString(); + FilePath temp_install = + extension_path_.DirName().AppendASCII(kTempExtensionName); + if (!file_util::CreateDirectory(temp_install)) { + SetError("Couldn't create directory for unzipping."); + return false; + } + + if (!Unzip(extension_path_, temp_install, NULL)) { + SetError("Couldn't unzip extension."); + return false; + } + + return true; +} + +void ExtensionUnpacker::SetError(const std::string &error) { + error_message_ = error; +} diff --git a/chrome/common/extensions/extension_unpacker.h b/chrome/common/extensions/extension_unpacker.h new file mode 100644 index 0000000..6d5fbf3 --- /dev/null +++ b/chrome/common/extensions/extension_unpacker.h @@ -0,0 +1,45 @@ +// 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_COMMON_EXTENSIONS_EXTENSION_UNPACKER_H_ +#define CHROME_COMMON_EXTENSIONS_EXTENSION_UNPACKER_H_ + +#include <string> + +#include "base/file_path.h" + +class DictionaryValue; + +// Implements IO for the ExtensionsService. +// TODO(aa): Extract an interface out of this for testing the frontend, once the +// frontend has significant logic to test. +class ExtensionUnpacker { + public: + explicit ExtensionUnpacker(const FilePath& extension_path) + : extension_path_(extension_path) {} + + // Install the extension file at |extension_path|. Returns true on success. + // Otherwise, error_message will contain a string explaining what went wrong. + bool Run(); + + const std::string& error_message() { return error_message_; } + + private: + // Read the manifest from the front of the extension file. + // Caller takes ownership of return value. + DictionaryValue* ReadManifest(); + + // Set the error message. + void SetError(const std::string& error); + + // The extension to unpack. + FilePath extension_path_; + + // The last error message that was set. Empty if there were no errors. + std::string error_message_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionUnpacker); +}; + +#endif // CHROME_COMMON_EXTENSIONS_EXTENSION_UNPACKER_H_ |