diff options
author | rafaelw@chromium.org <rafaelw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-11 20:16:09 +0000 |
---|---|---|
committer | rafaelw@chromium.org <rafaelw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-11 20:16:09 +0000 |
commit | 928bfd79b3646f46451f439dd13e0871d97cc20c (patch) | |
tree | 9b1d92f4c54e11e288a1ebfdc2252c819f770b4f /chrome/common | |
parent | b17ef85af94338657109d2c85de8c26b88432f89 (diff) | |
download | chromium_src-928bfd79b3646f46451f439dd13e0871d97cc20c.zip chromium_src-928bfd79b3646f46451f439dd13e0871d97cc20c.tar.gz chromium_src-928bfd79b3646f46451f439dd13e0871d97cc20c.tar.bz2 |
BUG=12114
R=erikkay,wtc
Review URL: http://codereview.chromium.org/115682
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@18189 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/common')
-rw-r--r-- | chrome/common/extensions/extension.cc | 78 | ||||
-rw-r--r-- | chrome/common/extensions/extension.h | 39 | ||||
-rw-r--r-- | chrome/common/extensions/extension_unittest.cc | 25 | ||||
-rw-r--r-- | chrome/common/extensions/extension_unpacker.cc | 136 | ||||
-rw-r--r-- | chrome/common/extensions/extension_unpacker.h | 4 |
5 files changed, 105 insertions, 177 deletions
diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc index bf7d3a5..73eb417 100644 --- a/chrome/common/extensions/extension.cc +++ b/chrome/common/extensions/extension.cc @@ -5,10 +5,14 @@ #include "chrome/common/extensions/extension.h" #include "app/resource_bundle.h" +#include "base/basictypes.h" #include "base/file_path.h" #include "base/file_util.h" #include "base/logging.h" #include "base/string_util.h" +#include "base/third_party/nss/blapi.h" +#include "base/third_party/nss/sha256.h" +#include "net/base/base64.h" #include "net/base/net_util.h" #include "chrome/common/extensions/extension_error_reporter.h" #include "chrome/common/extensions/extension_error_utils.h" @@ -33,6 +37,8 @@ namespace { const int kRSAKeySize = 1024; }; +int Extension::id_counter_ = 0; + const char Extension::kManifestFilename[] = "manifest.json"; const wchar_t* Extension::kBackgroundKey = L"background_page"; @@ -40,10 +46,10 @@ const wchar_t* Extension::kContentScriptsKey = L"content_scripts"; const wchar_t* Extension::kCssKey = L"css"; const wchar_t* Extension::kDescriptionKey = L"description"; const wchar_t* Extension::kIconPathKey = L"icon"; -const wchar_t* Extension::kIdKey = L"id"; const wchar_t* Extension::kJsKey = L"js"; const wchar_t* Extension::kMatchesKey = L"matches"; const wchar_t* Extension::kNameKey = L"name"; +const wchar_t* Extension::kPageActionIdKey = L"id"; const wchar_t* Extension::kPageActionsKey = L"page_actions"; const wchar_t* Extension::kPermissionsKey = L"permissions"; const wchar_t* Extension::kPluginsKey = L"plugins"; @@ -71,7 +77,6 @@ const char* Extension::kPageActionTypePermanent = "permanent"; static const wchar_t* kValidThemeKeys[] = { Extension::kDescriptionKey, Extension::kIconPathKey, - Extension::kIdKey, Extension::kNameKey, Extension::kPublicKeyKey, Extension::kSignatureKey, @@ -93,12 +98,12 @@ const char* Extension::kInvalidCssListError = "Required value 'content_scripts[*].css is invalid."; const char* Extension::kInvalidDescriptionError = "Invalid value for 'description'."; -const char* Extension::kInvalidIdError = - "Required value 'id' is missing or invalid."; const char* Extension::kInvalidJsError = "Invalid value for 'content_scripts[*].js[*]'."; const char* Extension::kInvalidJsListError = "Required value 'content_scripts[*].js is invalid."; +const char* Extension::kInvalidKeyError = + "Value 'key' is missing or invalid."; const char* Extension::kInvalidManifestError = "Manifest is missing or invalid."; const char* Extension::kInvalidMatchCountError = @@ -116,6 +121,8 @@ const char* Extension::kInvalidPageActionsListError = "Invalid value for 'page_actions'."; const char* Extension::kInvalidPageActionIconPathError = "Invalid value for 'page_actions[*].icon'."; +const char* Extension::kInvalidPageActionIdError = + "Required value 'id' is missing or invalid."; const char* Extension::kInvalidPageActionTooltipError = "Invalid value for 'page_actions[*].tooltip'."; const char* Extension::kInvalidPageActionTypeValueError = @@ -139,6 +146,8 @@ const char* Extension::kInvalidBackgroundError = "Invalid value for 'background'."; const char* Extension::kInvalidRunAtError = "Invalid value for 'content_scripts[*].run_at'."; +const char* Extension::kInvalidSignatureError = + "Value 'signature' is missing or invalid."; const char* Extension::kInvalidToolstripError = "Invalid value for 'toolstrips[*]'"; const char* Extension::kInvalidToolstripsError = @@ -167,7 +176,8 @@ const char* Extension::kExtensionRegistryPath = "Software\\Google\\Chrome\\Extensions"; #endif -const size_t Extension::kIdSize = 20; // SHA1 (160 bits) == 20 bytes +// first 20 bytes of SHA256 hashed public key. +const size_t Extension::kIdSize = 20; Extension::~Extension() { for (PageActionMap::iterator i = page_actions_.begin(); @@ -230,6 +240,23 @@ Extension::Location Extension::ExternalExtensionInstallType( return Extension::EXTERNAL_PREF; } +bool Extension::GenerateIdFromPublicKey(const std::string& input, + std::string* output) { + CHECK(output); + if (input.length() == 0) + return false; + + const uint8* ubuf = reinterpret_cast<const unsigned char*>(input.data()); + SHA256Context ctx; + SHA256_Begin(&ctx); + SHA256_Update(&ctx, ubuf, input.length()); + uint8 hash[Extension::kIdSize]; + SHA256_End(&ctx, hash, NULL, sizeof(hash)); + *output = StringToLowerASCII(HexEncode(hash, sizeof(hash))); + + return true; +} + // Helper method that loads a UserScript object from a dictionary in the // content_script list of the manifest. bool Extension::LoadUserScriptHelper(const DictionaryValue* content_script, @@ -367,8 +394,8 @@ PageAction* Extension::LoadPageActionHelper( // Read the page action |id|. std::string id; - if (!page_action->GetString(kIdKey, &id)) { - *error = ExtensionErrorUtils::FormatErrorMessage(kInvalidIdError, + if (!page_action->GetString(kPageActionIdKey, &id)) { + *error = ExtensionErrorUtils::FormatErrorMessage(kInvalidPageActionIdError, IntToString(definition_index)); return NULL; } @@ -492,13 +519,14 @@ Extension::Extension(const FilePath& path) { #endif } - // TODO(rafaelw): Move ParsePEMKeyBytes, ProducePEM & FormatPEMForOutput to a // util class in base: // http://code.google.com/p/chromium/issues/detail?id=13572 bool Extension::ParsePEMKeyBytes(const std::string& input, std::string* output) { - CHECK(output); + DCHECK(output); + if (!output) + return false; if (input.length() == 0) return false; @@ -564,33 +592,23 @@ bool Extension::FormatPEMForFileOutput(const std::string input, bool Extension::InitFromValue(const DictionaryValue& source, bool require_id, std::string* error) { - // Initialize id. - if (source.HasKey(kIdKey)) { - if (!source.GetString(kIdKey, &id_)) { - *error = kInvalidIdError; - return false; - } - - // Normalize the string to lowercase, so it can be used as an URL component - // (where GURL will lowercase it). - StringToLowerASCII(&id_); - - // Verify that the id is legal. - if (!IdIsValid(id_)) { - *error = kInvalidIdError; - return false; + if (source.HasKey(kPublicKeyKey)) { + std::string public_key_bytes; + if (!source.GetString(kPublicKeyKey, &public_key_) || + !ParsePEMKeyBytes(public_key_, &public_key_bytes) || + !GenerateIdFromPublicKey(public_key_bytes, &id_)) { + *error = kInvalidKeyError; + return false; } } else if (require_id) { - *error = kInvalidIdError; + *error = kInvalidKeyError; return false; } else { // Generate a random ID - static int counter = 0; - id_ = StringPrintf("%x", counter); - ++counter; + id_ = StringPrintf("%x", NextGeneratedId()); - // pad the string out to 40 chars with zeroes. - id_.insert(0, 40 - id_.length(), '0'); + // pad the string out to kIdSize*2 chars with zeroes. + id_.insert(0, Extension::kIdSize*2 - id_.length(), '0'); } // Initialize the URL. diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h index 9a5bfbf..658b57f 100644 --- a/chrome/common/extensions/extension.h +++ b/chrome/common/extensions/extension.h @@ -38,6 +38,13 @@ class Extension { KILLBIT, // Don't install/upgrade (applies to external extensions only). }; + enum InstallType { + DOWNGRADE, + REINSTALL, + UPGRADE, + NEW_INSTALL + }; + // An NPAPI plugin included in the extension. struct PluginInfo { FilePath path; // Path to the plugin. @@ -53,10 +60,10 @@ class Extension { static const wchar_t* kCssKey; static const wchar_t* kDescriptionKey; static const wchar_t* kIconPathKey; - static const wchar_t* kIdKey; static const wchar_t* kJsKey; static const wchar_t* kMatchesKey; static const wchar_t* kNameKey; + static const wchar_t* kPageActionIdKey; static const wchar_t* kPageActionsKey; static const wchar_t* kPermissionsKey; static const wchar_t* kPluginsKey; @@ -87,9 +94,9 @@ class Extension { static const char* kInvalidCssError; static const char* kInvalidCssListError; static const char* kInvalidDescriptionError; - static const char* kInvalidIdError; static const char* kInvalidJsError; static const char* kInvalidJsListError; + static const char* kInvalidKeyError; static const char* kInvalidManifestError; static const char* kInvalidMatchCountError; static const char* kInvalidMatchError; @@ -101,12 +108,14 @@ class Extension { static const char* kInvalidBackgroundError; static const char* kInvalidRunAtError; + static const char* kInvalidSignatureError; static const char* kInvalidToolstripError; static const char* kInvalidToolstripsError; static const char* kInvalidVersionError; static const char* kInvalidPageActionError; static const char* kInvalidPageActionsListError; static const char* kInvalidPageActionIconPathError; + static const char* kInvalidPageActionIdError; static const char* kInvalidPageActionTooltipError; static const char* kInvalidPageActionTypeValueError; static const char* kInvalidPermissionsError; @@ -133,6 +142,11 @@ class Extension { explicit Extension(const FilePath& path); virtual ~Extension(); + // Resets the id counter. This is only useful for unit tests. + static void ResetGeneratedIdCounter() { + id_counter_ = 0; + } + // Checks to see if the extension has a valid ID. static bool IdIsValid(const std::string& id); @@ -173,6 +187,11 @@ class Extension { // Does a simple base64 encoding of |input| into |output|. static bool ProducePEM(const std::string& input, std::string* output); + // Note: The result is coverted to lower-case because the browser enforces + // hosts to be lower-case in omni-bar. + static bool GenerateIdFromPublicKey(const std::string& input, + std::string* output); + // Expects base64 encoded |input| and formats into |output| including // the appropriate header & footer. static bool FormatPEMForFileOutput(const std::string input, @@ -193,6 +212,7 @@ class Extension { // String representation of the version number. const std::string VersionString() const; const std::string& name() const { return name_; } + const std::string& public_key() const { return public_key_; } const std::string& description() const { return description_; } const UserScriptList& content_scripts() const { return content_scripts_; } const PageActionMap& page_actions() const { return page_actions_; } @@ -222,6 +242,14 @@ class Extension { std::set<FilePath> GetBrowserImages(); private: + // Counter used to assign ids to extensions that are loaded using + // --load-extension. + static int id_counter_; + + // Returns the next counter id. Intentionally post-incrementing so that first + // value is 0. + static int NextGeneratedId() { return id_counter_++; } + // Helper method that loads a UserScript object from a // dictionary in the content_script list of the manifest. bool LoadUserScriptHelper(const DictionaryValue* content_script, @@ -281,10 +309,9 @@ class Extension { // Paths to HTML files to be displayed in the toolbar. std::vector<std::string> toolstrips_; - // A SHA1 hash of the contents of the zip file. Note that this key is only - // present in the manifest that's prepended to the zip. The inner manifest - // will not have this key. - std::string zip_hash_; + // The public key ('key' in the manifest) used to sign the contents of the + // crx package ('signature' in the manifest) + std::string public_key_; // A map of resource id's to relative file paths. scoped_ptr<DictionaryValue> theme_images_; diff --git a/chrome/common/extensions/extension_unittest.cc b/chrome/common/extensions/extension_unittest.cc index 7f0f288..661bcb6 100644 --- a/chrome/common/extensions/extension_unittest.cc +++ b/chrome/common/extensions/extension_unittest.cc @@ -43,17 +43,6 @@ TEST(ExtensionTest, InitFromValueInvalid) { scoped_ptr<DictionaryValue> input_value; - // Test missing and invalid ids - input_value.reset(static_cast<DictionaryValue*>(valid_value->DeepCopy())); - input_value->Remove(Extension::kIdKey, NULL); - EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error)); - EXPECT_EQ(Extension::kInvalidIdError, error); - - input_value.reset(static_cast<DictionaryValue*>(valid_value->DeepCopy())); - input_value->SetInteger(Extension::kIdKey, 42); - EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error)); - EXPECT_EQ(Extension::kInvalidIdError, error); - // Test missing and invalid versions input_value.reset(static_cast<DictionaryValue*>(valid_value->DeepCopy())); input_value->Remove(Extension::kVersionKey, NULL); @@ -204,22 +193,22 @@ TEST(ExtensionTest, InitFromValueValid) { #elif defined(OS_POSIX) FilePath path(FILE_PATH_LITERAL("/foo")); #endif + Extension::ResetGeneratedIdCounter(); + Extension extension(path); std::string error; DictionaryValue input_value; // Test minimal extension - input_value.SetString(Extension::kIdKey, - "00123456789ABCDEF0123456789ABCDEF0123456"); input_value.SetString(Extension::kVersionKey, "1.0.0.0"); input_value.SetString(Extension::kNameKey, "my extension"); - EXPECT_TRUE(extension.InitFromValue(input_value, true, &error)); + EXPECT_TRUE(extension.InitFromValue(input_value, false, &error)); EXPECT_EQ("", error); - EXPECT_EQ("00123456789abcdef0123456789abcdef0123456", extension.id()); + EXPECT_EQ("0000000000000000000000000000000000000000", extension.id()); EXPECT_EQ("1.0.0.0", extension.VersionString()); EXPECT_EQ("my extension", extension.name()); - EXPECT_EQ("chrome-extension://00123456789abcdef0123456789abcdef0123456/", + EXPECT_EQ("chrome-extension://0000000000000000000000000000000000000000/", extension.url().spec()); EXPECT_EQ(path.value(), extension.path().value()); } @@ -232,11 +221,9 @@ TEST(ExtensionTest, GetResourceURLAndPath) { #endif Extension extension(path); DictionaryValue input_value; - input_value.SetString(Extension::kIdKey, - "00123456789ABCDEF0123456789ABCDEF0123456"); input_value.SetString(Extension::kVersionKey, "1.0.0.0"); input_value.SetString(Extension::kNameKey, "my extension"); - EXPECT_TRUE(extension.InitFromValue(input_value, true, NULL)); + EXPECT_TRUE(extension.InitFromValue(input_value, false, NULL)); EXPECT_EQ(extension.url().spec() + "bar/baz.js", Extension::GetResourceURL(extension.url(), "bar/baz.js").spec()); diff --git a/chrome/common/extensions/extension_unpacker.cc b/chrome/common/extensions/extension_unpacker.cc index b5bc919..bae5a7d 100644 --- a/chrome/common/extensions/extension_unpacker.cc +++ b/chrome/common/extensions/extension_unpacker.cc @@ -8,8 +8,6 @@ #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" @@ -23,8 +21,6 @@ #include "webkit/glue/image_decoder.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"; @@ -32,27 +28,14 @@ const char kTempExtensionName[] = "TEMP_INSTALL"; // The file to write our decoded images to, relative to the extension_path. const char kDecodedImagesFilename[] = "DECODED_IMAGES"; -// Chromium Extension magic number -// TODO(aa): This should use the one in ExtensionCreator once we transition this -// to ouptut the same format. -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. +// Errors +const char* kCouldNotCreateDirectoryError = + "Could not create directory for unzipping."; +const char* kCouldNotDecodeImageError = "Could not decode theme image."; +const char* kCouldNotUnzipExtension = "Could not unzip extension."; +const char* kPathNamesMustBeAbsoluteOrLocalError = + "Path names must not be absolute or contain '..'."; -// 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; } // namespace static SkBitmap DecodeImage(const FilePath& path) { @@ -90,74 +73,6 @@ static bool PathContainsParentDirectory(const FilePath& path) { return false; } -// 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::ReadPackageHeader() { - 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()); - - // 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; -} - DictionaryValue* ExtensionUnpacker::ReadManifest() { FilePath manifest_path = temp_install_dir_.AppendASCII(Extension::kManifestFilename); @@ -185,34 +100,16 @@ DictionaryValue* ExtensionUnpacker::ReadManifest() { bool ExtensionUnpacker::Run() { LOG(INFO) << "Installing extension " << extension_path_.value(); - // Read and verify the extension. - scoped_ptr<DictionaryValue> header_manifest(ReadPackageHeader()); - if (!header_manifest.get()) { - // ReadPackageHeader has already reported the extension error. - return false; - } - - // TODO(mpcomplete): it looks like this isn't actually necessary. We don't - // use header_extension, and we check that the unzipped manifest is valid. - Extension header_extension; - std::string error; - if (!header_extension.InitFromValue(*header_manifest, - true, // require ID - &error)) { - SetError(error); - return false; - } - // <profile>/Extensions/INSTALL_TEMP/<version> temp_install_dir_ = extension_path_.DirName().AppendASCII(kTempExtensionName); if (!file_util::CreateDirectory(temp_install_dir_)) { - SetError("Couldn't create directory for unzipping."); + SetError(kCouldNotCreateDirectoryError); return false; } if (!Unzip(extension_path_, temp_install_dir_)) { - SetError("Couldn't unzip extension."); + SetError(kCouldNotUnzipExtension); return false; } @@ -220,16 +117,19 @@ bool ExtensionUnpacker::Run() { parsed_manifest_.reset(ReadManifest()); if (!parsed_manifest_.get()) return false; // Error was already reported. - - // Re-read the actual manifest into our extension struct. + + // NOTE: Since the Unpacker doesn't have the extension's public_id, the + // InitFromValue is allowed to generate a temporary id for the extension. + // ANY CODE THAT FOLLOWS SHOULD NOT DEPEND ON THE CORRECT ID OF THIS + // EXTENSION. Extension extension; + std::string error; if (!extension.InitFromValue(*parsed_manifest_, - true, // require ID + false, &error)) { SetError(error); return false; } - // Decode any images that the browser needs to display. std::set<FilePath> image_paths = extension.GetBrowserImages(); for (std::set<FilePath>::iterator it = image_paths.begin(); @@ -271,13 +171,13 @@ bool ExtensionUnpacker::ReadImagesFromFile(const FilePath& extension_path, bool ExtensionUnpacker::AddDecodedImage(const FilePath& path) { // Make sure it's not referencing a file outside the extension's subdir. if (path.IsAbsolute() || PathContainsParentDirectory(path)) { - SetError("Path names must not be absolute or contain '..'."); + SetError(kPathNamesMustBeAbsoluteOrLocalError); return false; } SkBitmap image_bitmap = DecodeImage(temp_install_dir_.Append(path)); if (image_bitmap.isNull()) { - SetError("Could not decode theme image."); + SetError(kCouldNotDecodeImageError); return false; } diff --git a/chrome/common/extensions/extension_unpacker.h b/chrome/common/extensions/extension_unpacker.h index 65af750..aeb72d9 100644 --- a/chrome/common/extensions/extension_unpacker.h +++ b/chrome/common/extensions/extension_unpacker.h @@ -48,10 +48,6 @@ class ExtensionUnpacker { const DecodedImages& decoded_images() { return decoded_images_; } private: - // Parse the header on the front of the extension file and return the manifest - // inside it. Caller takes ownership of return value. - DictionaryValue* ReadPackageHeader(); - // Parse the manifest.json file inside the extension (not in the header). // Caller takes ownership of return value. DictionaryValue* ReadManifest(); |