summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/extensions')
-rwxr-xr-xchrome/browser/extensions/extension_creator.cc275
-rwxr-xr-xchrome/browser/extensions/extension_creator.h69
-rw-r--r--chrome/browser/extensions/extensions_service.cc14
-rw-r--r--chrome/browser/extensions/extensions_service.h10
-rw-r--r--chrome/browser/extensions/extensions_service_unittest.cc65
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));