// Copyright (c) 2012 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 <string> #include <vector> #include "base/bind.h" #include "base/callback.h" #include "base/file_util.h" #include "base/files/scoped_file.h" #include "base/files/scoped_temp_dir.h" #include "base/strings/string_util.h" #include "chrome/browser/extensions/extension_creator_filter.h" #include "crypto/rsa_private_key.h" #include "crypto/signature_creator.h" #include "extensions/common/crx_file.h" #include "extensions/common/extension.h" #include "extensions/common/file_util.h" #include "extensions/common/id_util.h" #include "grit/generated_resources.h" #include "third_party/zlib/google/zip.h" #include "ui/base/l10n/l10n_util.h" namespace { const int kRSAKeySize = 2048; }; namespace extensions { ExtensionCreator::ExtensionCreator() : error_type_(kOtherError) { } bool ExtensionCreator::InitializeInput( const base::FilePath& extension_dir, const base::FilePath& crx_path, const base::FilePath& private_key_path, const base::FilePath& private_key_output_path, int run_flags) { // Validate input |extension_dir|. if (extension_dir.value().empty() || !base::DirectoryExists(extension_dir)) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_DIRECTORY_NO_EXISTS); return false; } base::FilePath absolute_extension_dir = base::MakeAbsoluteFilePath(extension_dir); if (absolute_extension_dir.empty()) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_CANT_GET_ABSOLUTE_PATH); return false; } // Validate input |private_key| (if provided). if (!private_key_path.value().empty() && !base::PathExists(private_key_path)) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_PRIVATE_KEY_INVALID_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() && base::PathExists(private_key_output_path)) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_PRIVATE_KEY_EXISTS); return false; } // Check whether crx file already exists. Should be last check, as this is // a warning only. if (!(run_flags & kOverwriteCRX) && base::PathExists(crx_path)) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_CRX_EXISTS); error_type_ = kCRXExists; return false; } return true; } bool ExtensionCreator::ValidateManifest(const base::FilePath& extension_dir, crypto::RSAPrivateKey* key_pair, int run_flags) { std::vector<uint8> public_key_bytes; if (!key_pair->ExportPublicKey(&public_key_bytes)) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_PUBLIC_KEY_FAILED_TO_EXPORT); return false; } std::string public_key; public_key.insert(public_key.begin(), public_key_bytes.begin(), public_key_bytes.end()); std::string extension_id = id_util::GenerateId(public_key); // Load the extension once. We don't really need it, but this does a lot of // useful validation of the structure. int create_flags = Extension::FOLLOW_SYMLINKS_ANYWHERE | Extension::ERROR_ON_PRIVATE_KEY; if (run_flags & kRequireModernManifestVersion) create_flags |= Extension::REQUIRE_MODERN_MANIFEST_VERSION; scoped_refptr<Extension> extension( file_util::LoadExtension(extension_dir, extension_id, Manifest::INTERNAL, create_flags, &error_message_)); return !!extension.get(); } crypto::RSAPrivateKey* ExtensionCreator::ReadInputKey(const base::FilePath& private_key_path) { if (!base::PathExists(private_key_path)) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_PRIVATE_KEY_NO_EXISTS); return NULL; } std::string private_key_contents; if (!base::ReadFileToString(private_key_path, &private_key_contents)) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_PRIVATE_KEY_FAILED_TO_READ); return NULL; } std::string private_key_bytes; if (!Extension::ParsePEMKeyBytes(private_key_contents, &private_key_bytes)) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_PRIVATE_KEY_INVALID); return NULL; } return crypto::RSAPrivateKey::CreateFromPrivateKeyInfo( std::vector<uint8>(private_key_bytes.begin(), private_key_bytes.end())); } crypto::RSAPrivateKey* ExtensionCreator::GenerateKey(const base::FilePath& output_private_key_path) { scoped_ptr<crypto::RSAPrivateKey> key_pair( crypto::RSAPrivateKey::Create(kRSAKeySize)); if (!key_pair) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_PRIVATE_KEY_FAILED_TO_GENERATE); return NULL; } std::vector<uint8> private_key_vector; if (!key_pair->ExportPrivateKey(&private_key_vector)) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_PRIVATE_KEY_FAILED_TO_EXPORT); return NULL; } std::string private_key_bytes( reinterpret_cast<char*>(&private_key_vector.front()), private_key_vector.size()); std::string private_key; if (!Extension::ProducePEM(private_key_bytes, &private_key)) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_PRIVATE_KEY_FAILED_TO_OUTPUT); return NULL; } std::string pem_output; if (!Extension::FormatPEMForFileOutput(private_key, &pem_output, false)) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_PRIVATE_KEY_FAILED_TO_OUTPUT); return NULL; } if (!output_private_key_path.empty()) { if (-1 == base::WriteFile(output_private_key_path, pem_output.c_str(), pem_output.size())) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_PRIVATE_KEY_FAILED_TO_OUTPUT); return NULL; } } return key_pair.release(); } bool ExtensionCreator::CreateZip(const base::FilePath& extension_dir, const base::FilePath& temp_path, base::FilePath* zip_path) { *zip_path = temp_path.Append(FILE_PATH_LITERAL("extension.zip")); scoped_refptr<ExtensionCreatorFilter> filter = new ExtensionCreatorFilter(); const base::Callback<bool(const base::FilePath&)>& filter_cb = base::Bind(&ExtensionCreatorFilter::ShouldPackageFile, filter.get()); if (!zip::ZipWithFilterCallback(extension_dir, *zip_path, filter_cb)) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_FAILED_DURING_PACKAGING); return false; } return true; } bool ExtensionCreator::SignZip(const base::FilePath& zip_path, crypto::RSAPrivateKey* private_key, std::vector<uint8>* signature) { scoped_ptr<crypto::SignatureCreator> signature_creator( crypto::SignatureCreator::Create(private_key)); base::ScopedFILE zip_handle(base::OpenFile(zip_path, "rb")); size_t buffer_size = 1 << 16; scoped_ptr<uint8[]> buffer(new uint8[buffer_size]); int bytes_read = -1; while ((bytes_read = fread(buffer.get(), 1, buffer_size, zip_handle.get())) > 0) { if (!signature_creator->Update(buffer.get(), bytes_read)) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_ERROR_WHILE_SIGNING); return false; } } zip_handle.reset(); if (!signature_creator->Final(signature)) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_ERROR_WHILE_SIGNING); return false; } return true; } bool ExtensionCreator::WriteCRX(const base::FilePath& zip_path, crypto::RSAPrivateKey* private_key, const std::vector<uint8>& signature, const base::FilePath& crx_path) { if (base::PathExists(crx_path)) base::DeleteFile(crx_path, false); base::ScopedFILE crx_handle(base::OpenFile(crx_path, "wb")); if (!crx_handle.get()) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_SHARING_VIOLATION); return false; } std::vector<uint8> public_key; CHECK(private_key->ExportPublicKey(&public_key)); CrxFile::Error error; scoped_ptr<CrxFile> crx( CrxFile::Create(public_key.size(), signature.size(), &error)); if (!crx) { LOG(ERROR) << "cannot create CrxFileHeader: " << error; } const CrxFile::Header header = crx->header(); if (fwrite(&header, sizeof(header), 1, crx_handle.get()) != 1) { PLOG(ERROR) << "fwrite failed to write header"; } if (fwrite(&public_key.front(), sizeof(uint8), public_key.size(), crx_handle.get()) != public_key.size()) { PLOG(ERROR) << "fwrite failed to write public_key.front"; } if (fwrite(&signature.front(), sizeof(uint8), signature.size(), crx_handle.get()) != signature.size()) { PLOG(ERROR) << "fwrite failed to write signature.front"; } size_t buffer_size = 1 << 16; scoped_ptr<uint8[]> buffer(new uint8[buffer_size]); size_t bytes_read = 0; base::ScopedFILE zip_handle(base::OpenFile(zip_path, "rb")); while ((bytes_read = fread(buffer.get(), 1, buffer_size, zip_handle.get())) > 0) { if (fwrite(buffer.get(), sizeof(char), bytes_read, crx_handle.get()) != bytes_read) { PLOG(ERROR) << "fwrite failed to write buffer"; } } return true; } bool ExtensionCreator::Run(const base::FilePath& extension_dir, const base::FilePath& crx_path, const base::FilePath& private_key_path, const base::FilePath& output_private_key_path, int run_flags) { // Check input diretory and read manifest. if (!InitializeInput(extension_dir, crx_path, private_key_path, output_private_key_path, run_flags)) { return false; } // Initialize Key Pair scoped_ptr<crypto::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) return false; // Perform some extra validation by loading the extension. // TODO(aa): Can this go before creating the key pair? This would mean not // passing ID into LoadExtension which seems OK. if (!ValidateManifest(extension_dir, key_pair.get(), run_flags)) return false; base::ScopedTempDir temp_dir; if (!temp_dir.CreateUniqueTempDir()) return false; // Zip up the extension. base::FilePath zip_path; std::vector<uint8> signature; bool result = false; if (CreateZip(extension_dir, temp_dir.path(), &zip_path) && SignZip(zip_path, key_pair.get(), &signature) && WriteCRX(zip_path, key_pair.get(), signature, crx_path)) { result = true; } base::DeleteFile(zip_path, false); return result; } } // namespace extensions