diff options
Diffstat (limited to 'chrome/browser/extensions')
-rwxr-xr-x | chrome/browser/extensions/extension_creator.cc | 275 | ||||
-rwxr-xr-x | chrome/browser/extensions/extension_creator.h | 69 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_service.cc | 14 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_service.h | 10 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_service_unittest.cc | 65 |
5 files changed, 421 insertions, 12 deletions
diff --git a/chrome/browser/extensions/extension_creator.cc b/chrome/browser/extensions/extension_creator.cc new file mode 100755 index 0000000..d5400e0 --- /dev/null +++ b/chrome/browser/extensions/extension_creator.cc @@ -0,0 +1,275 @@ +// 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/extension_creator.h" + +#include <vector> +#include <string> + +#include "base/crypto/rsa_private_key.h" +#include "base/crypto/signature_creator.h" +#include "base/file_util.h" +#include "base/scoped_handle.h" +#include "base/string_util.h" +#include "chrome/browser/extensions/extensions_service.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_error_reporter.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/json_value_serializer.h" +#include "chrome/common/zip.h" +#include "net/base/base64.h" + +namespace { + const int kRSAKeySize = 1024; +}; + +DictionaryValue* ExtensionCreator::InitializeInput( + const FilePath& extension_dir, + const FilePath& private_key_path, + const FilePath& private_key_output_path) { + // Validate input |extension_dir|. + if (extension_dir.value().empty() || + !file_util::DirectoryExists(extension_dir)) { + error_message_ = "Input directory must exist."; + return false; + } + + // Validate input |private_key| (if provided). + if (!private_key_path.value().empty() && + !file_util::PathExists(private_key_path)) { + error_message_ = "Input value for private key must be a valid path."; + return false; + } + + // If an |output_private_key| path is given, make sure it doesn't over-write + // an existing private key. + if (private_key_path.value().empty() && + !private_key_output_path.value().empty() && + file_util::PathExists(private_key_output_path)) { + error_message_ = "Private key exists next to input directory. Try using " + "--pack-extension-key"; + return false; + } + + // Read the manifest. + FilePath manifest_path = extension_dir.AppendASCII( + Extension::kManifestFilename); + if (!file_util::PathExists(manifest_path)) { + error_message_ = "Extension must contain '"; + error_message_.append(Extension::kManifestFilename); + error_message_.append("'."); + return false; + } + + JSONFileValueSerializer serializer(manifest_path); + std::string serialization_error; + Value* input_manifest = (serializer.Deserialize(&serialization_error)); + if (!input_manifest) { + error_message_ = "Invalid manifest.json: "; + error_message_.append(serialization_error); + return false; + } + + if (!input_manifest->IsType(Value::TYPE_DICTIONARY)) { + error_message_ = "Invalid manifest.json"; + return false; + } + + return static_cast<DictionaryValue*>(input_manifest); +} + +base::RSAPrivateKey* ExtensionCreator::ReadInputKey(const FilePath& + private_key_path) { + if (!file_util::PathExists(private_key_path)) { + error_message_ = "Input value for private key must exist."; + return false; + } + + std::string private_key_contents; + if (!file_util::ReadFileToString(private_key_path, + &private_key_contents)) { + error_message_ = "Failed to read private key."; + return false; + } + + std::string private_key_bytes; + if (!Extension::ParsePEMKeyBytes(private_key_contents, + &private_key_bytes)) { + error_message_ = "Invalid private key."; + return false; + } + + return base::RSAPrivateKey::CreateFromPrivateKeyInfo( + std::vector<uint8>(private_key_bytes.begin(), private_key_bytes.end())); +} + +base::RSAPrivateKey* ExtensionCreator::GenerateKey(const FilePath& + output_private_key_path) { + scoped_ptr<base::RSAPrivateKey> key_pair( + base::RSAPrivateKey::Create(kRSAKeySize)); + if (!key_pair.get()) { + error_message_ = "Yikes! Failed to generate random RSA private key."; + return NULL; + } + + std::vector<uint8> private_key_vector; + if (!key_pair->ExportPrivateKey(&private_key_vector)) { + error_message_ = "Failed to export private key."; + return NULL; + } + std::string private_key_bytes(private_key_vector.begin(), + private_key_vector.end()); + + std::string private_key; + if (!Extension::ProducePEM(private_key_bytes, &private_key)) { + error_message_ = "Failed to output private key."; + return NULL; + } + std::string pem_output; + if (!Extension::FormatPEMForFileOutput(private_key, &pem_output, + false)) { + error_message_ = "Failed to output private key."; + return NULL; + } + + if (!output_private_key_path.empty()) { + if (-1 == file_util::WriteFile(output_private_key_path, + pem_output.c_str(), pem_output.size())) { + error_message_ = "Failed to write private key."; + return NULL; + } + } + + return key_pair.release(); +} + +bool ExtensionCreator::CreateAndSignZip(const FilePath& extension_dir, + base::RSAPrivateKey *key_pair, + FilePath* zip_path, + std::string* signature) { + file_util::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome_"), zip_path); + *zip_path = zip_path->Append(FILE_PATH_LITERAL("extension.zip")); + + if (!Zip(extension_dir, *zip_path)) { + error_message_ = "Failed to create temporary zip file during packaging."; + return false; + } + + scoped_ptr<base::SignatureCreator> signature_creator( + base::SignatureCreator::Create(key_pair)); + ScopedStdioHandle zip_handle(file_util::OpenFile(*zip_path, "rb")); + uint8 buffer[1 << 16]; + int bytes_read = -1; + while ((bytes_read = fread(buffer, 1, sizeof(buffer), + zip_handle.get())) > 0) { + if (!signature_creator->Update(buffer, bytes_read)) { + error_message_ = "Error while signing extension."; + return false; + } + } + zip_handle.Close(); + + std::vector<uint8> signature_vector; + signature_creator->Final(&signature_vector); + std::string signature_bytes(signature_vector.begin(), signature_vector.end()); + bool result = net::Base64Encode(signature_bytes, signature); + DCHECK(result); + return true; +} + +bool ExtensionCreator::PrepareManifestForExport(base::RSAPrivateKey *key_pair, + const std::string& signature, + DictionaryValue* manifest) { + std::vector<uint8> public_key_vector; + if (!key_pair->ExportPublicKey(&public_key_vector)) { + error_message_ = "Failed to export public key."; + return false; + } + + std::string public_key_bytes(public_key_vector.begin(), + public_key_vector.end()); + std::string public_key; + if (!net::Base64Encode(public_key_bytes, &public_key)) { + error_message_ = "Error while signing extension."; + return false; + } + + manifest->SetString(Extension::kSignatureKey, signature); + manifest->SetString(Extension::kPublicKeyKey, public_key); + + return true; +} + +bool ExtensionCreator::WriteCRX(const FilePath& crx_path, + DictionaryValue* manifest, + const FilePath& zip_path) { + std::string manifest_string; + JSONStringValueSerializer serializer(&manifest_string); + if (!serializer.Serialize(*manifest)) { + error_message_ = "Failed to write crx."; + return false; + } + + if (file_util::PathExists(crx_path)) + file_util::Delete(crx_path, false); + ScopedStdioHandle crx_handle(file_util::OpenFile(crx_path, "wb")); + + ExtensionsService::ExtensionHeader header; + memcpy(&header.magic, ExtensionsService::kExtensionFileMagic, + sizeof(ExtensionsService::kExtensionFileMagic)); + header.version = 1; // kExpectedVersion + header.header_size = sizeof(ExtensionsService::ExtensionHeader); + header.manifest_size = manifest_string.size(); + + fwrite(&header, sizeof(ExtensionsService::ExtensionHeader), 1, + crx_handle.get()); + fwrite(manifest_string.c_str(), sizeof(char), manifest_string.size(), + crx_handle.get()); + + uint8 buffer[1 << 16]; + int bytes_read = -1; + ScopedStdioHandle zip_handle(file_util::OpenFile(zip_path, "rb")); + while ((bytes_read = fread(buffer, 1, sizeof(buffer), + zip_handle.get())) > 0) { + fwrite(buffer, sizeof(char), bytes_read, crx_handle.get()); + } + + return true; +} + +bool ExtensionCreator::Run(const FilePath& extension_dir, + const FilePath& crx_path, + const FilePath& private_key_path, + const FilePath& output_private_key_path) { + // Check input diretory and read manifest. + scoped_ptr<DictionaryValue> manifest(InitializeInput(extension_dir, + private_key_path, output_private_key_path)); + if (!manifest.get()) + return false; + + // Initialize Key Pair + scoped_ptr<base::RSAPrivateKey> key_pair; + if (!private_key_path.value().empty()) + key_pair.reset(ReadInputKey(private_key_path)); + else + key_pair.reset(GenerateKey(output_private_key_path)); + if (!key_pair.get()) + return false; + + // Zip up the extension. + FilePath zip_path; + std::string signature; + if (!CreateAndSignZip(extension_dir, key_pair.get(), &zip_path, &signature)) + return false; + + if (!PrepareManifestForExport(key_pair.get(), signature, manifest.get())) + return false; + + // Write the final crx out to disk. + if (!WriteCRX(crx_path, manifest.get(), zip_path)) + return false; + + return true; +} diff --git a/chrome/browser/extensions/extension_creator.h b/chrome/browser/extensions/extension_creator.h new file mode 100755 index 0000000..6cdb6bc --- /dev/null +++ b/chrome/browser/extensions/extension_creator.h @@ -0,0 +1,69 @@ +// 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_CREATOR_H_ +#define CHROME_COMMON_EXTENSIONS_EXTENSION_CREATOR_H_ + +#include "base/command_line.h" +#include "base/crypto/rsa_private_key.h" +#include "base/file_path.h" +#include "base/values.h" + +// This class create an installable extension (.crx file) given an input +// directory that contains a valid manifest.json and the extension's resources +// contained within that directory. The output .crx file is always signed with a +// private key that is either provided in |private_key_path| or is internal +// generated randomly (and optionally written to |output_private_key_path|. +class ExtensionCreator { + public: + ExtensionCreator() {} + + bool Run(const FilePath& extension_dir, + const FilePath& crx_path, + const FilePath& private_key_path, + const FilePath& private_key_output_path); + + // Returns the error message that will be present if Run(...) returned false. + std::string error_message() { return error_message_; } + + private: + // Verifies input directory's existance, and reads manifest. |extension_dir| + // Is the source directory that should contain all the extension resources. + // |private_key_path| is the optional path to an existing private key to sign + // the extension. If not provided, a random key will be created (in which case + // it is written to |private_key_output_path| -- if provided). + DictionaryValue* InitializeInput(const FilePath& extension_dir, + const FilePath& private_key_path, + const FilePath& private_key_output_path); + + // Reads private key from |private_key_path|. + base::RSAPrivateKey* ReadInputKey(const FilePath& private_key_path); + + // Generates a key pair and writes the private key to |private_key_path| + // if provided. + base::RSAPrivateKey* GenerateKey(const FilePath& private_key_path); + + // Creates temporary zip file and generates a signature for it. + bool CreateAndSignZip(const FilePath& extension_dir, + base::RSAPrivateKey* key_pair, + FilePath* zip_path, + std::string* signature); + + // Inserts generated keys (signature, public_key) into manifest. + bool PrepareManifestForExport(base::RSAPrivateKey* key_pair, + const std::string& signature, + DictionaryValue* manifest); + + // Export installable .crx to |crx_path|. + bool WriteCRX(const FilePath& crx_path, + DictionaryValue *manifest, + const FilePath& zip_path); + + // Holds a message for any error that is raised during Run(...). + std::string error_message_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionCreator); +}; + +#endif // CHROME_COMMON_EXTENSIONS_EXTENSION_CREATOR_H_ diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc index 388bd71..9fce244 100644 --- a/chrome/browser/extensions/extensions_service.cc +++ b/chrome/browser/extensions/extensions_service.cc @@ -50,19 +50,9 @@ const char* ExtensionsService::kInstallDirectoryName = "Extensions"; const char* ExtensionsService::kCurrentVersionFileName = "Current Version"; const char* ExtensionsServiceBackend::kTempExtensionName = "TEMP_INSTALL"; -namespace { -// 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 char* ExtensionsService::kExtensionFileMagic = "Cr24"; -const size_t kZipHashBytes = 32; // SHA-256 -const size_t kZipHashHexBytes = kZipHashBytes * 2; // Hex string is 2x size. +namespace { // A preference that keeps track of extension settings. This is a dictionary // object read from the Preferences file, keyed off of extension id's. diff --git a/chrome/browser/extensions/extensions_service.h b/chrome/browser/extensions/extensions_service.h index 2d149f1..47578bc 100644 --- a/chrome/browser/extensions/extensions_service.h +++ b/chrome/browser/extensions/extensions_service.h @@ -35,6 +35,16 @@ typedef std::vector<Extension*> ExtensionList; class ExtensionsService : public base::RefCountedThreadSafe<ExtensionsService> { public: + static const int kExtensionFileMagicSize = 4; + static const char* kExtensionFileMagic; + + struct ExtensionHeader { + char magic[kExtensionFileMagicSize]; + uint32 version; + size_t header_size; + size_t manifest_size; + }; + ExtensionsService(Profile* profile, MessageLoop* frontend_loop, MessageLoop* backend_loop, diff --git a/chrome/browser/extensions/extensions_service_unittest.cc b/chrome/browser/extensions/extensions_service_unittest.cc index 630e71f..720b3a6 100644 --- a/chrome/browser/extensions/extensions_service_unittest.cc +++ b/chrome/browser/extensions/extensions_service_unittest.cc @@ -12,6 +12,7 @@ #include "base/path_service.h" #include "base/string_util.h" #include "base/time.h" +#include "chrome/browser/extensions/extension_creator.h" #include "chrome/browser/extensions/extensions_service.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/url_pattern.h" @@ -420,6 +421,70 @@ TEST_F(ExtensionsServiceTest, InstallExtension) { // TODO(erikkay): add tests for upgrade cases. } +#if defined(OS_WIN) // TODO(port) +// Test Packaging and installing an extension. +// TODO(aa): add a test that uses an openssl-generate private key. +// TODO(rafaelw): add more tests for failure cases. +TEST_F(ExtensionsServiceTest, PackExtension) { + SetExtensionsEnabled(true); + + FilePath extensions_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path)); + extensions_path = extensions_path.AppendASCII("extensions"); + FilePath input_directory = extensions_path.AppendASCII("good") + .AppendASCII("extension1").AppendASCII("1"); + + FilePath output_directory; + file_util::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome_"), + &output_directory); + FilePath crx_path(output_directory.AppendASCII("ex1.crx")); + FilePath privkey_path(output_directory.AppendASCII("privkey.pem")); + + scoped_ptr<ExtensionCreator> creator(new ExtensionCreator()); + ASSERT_TRUE(creator->Run(input_directory, crx_path, FilePath(), + privkey_path)); + + InstallExtension(crx_path, true); + + ASSERT_TRUE(file_util::PathExists(privkey_path)); + + file_util::Delete(crx_path, false); + file_util::Delete(privkey_path, false); +} + +// Test Packaging and installing an extension using an openssl generated key. +// The openssl is generated with the following: +// > openssl genrsa -out privkey.pem 1024
+// > openssl pkcs8 -topk8 -nocrypt -in privkey.pem -out privkey_asn1.pem
+// The privkey.pem is a PrivateKey, and the pcks8 -topk8 creates a +// PrivateKeyInfo ASN.1 structure, we our RSAPrivateKey expects. +TEST_F(ExtensionsServiceTest, PackExtensionOpenSSLKey) { + SetExtensionsEnabled(true); + + FilePath extensions_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path)); + extensions_path = extensions_path.AppendASCII("extensions"); + FilePath input_directory = extensions_path.AppendASCII("good") + .AppendASCII("extension1").AppendASCII("1"); + FilePath privkey_path(extensions_path.AppendASCII( + "openssl_privkey_asn1.pem")); + ASSERT_TRUE(file_util::PathExists(privkey_path)); + + FilePath output_directory; + file_util::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome_"), + &output_directory); + FilePath crx_path(output_directory.AppendASCII("ex1.crx")); + + scoped_ptr<ExtensionCreator> creator(new ExtensionCreator()); + ASSERT_TRUE(creator->Run(input_directory, crx_path, privkey_path, + FilePath())); + + InstallExtension(crx_path, true); + + file_util::Delete(crx_path, false); +} +#endif // defined(OS_WIN) + TEST_F(ExtensionsServiceTest, InstallTheme) { FilePath extensions_path; ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path)); |