// 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 "extensions/browser/extension_creator.h" #include #include #include #include "base/bind.h" #include "base/callback.h" #include "base/files/file_util.h" #include "base/files/scoped_file.h" #include "base/files/scoped_temp_dir.h" #include "base/strings/string_util.h" #include "components/crx_file/crx_creator.h" #include "components/crx_file/id_util.h" #include "crypto/rsa_private_key.h" #include "crypto/signature_creator.h" #include "extensions/browser/extension_creator_filter.h" #include "extensions/common/extension.h" #include "extensions/common/file_util.h" #include "extensions/strings/grit/extensions_strings.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 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 = crx_file::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; if (run_flags & kBookmarkApp) create_flags |= Extension::FROM_BOOKMARK; scoped_refptr extension(file_util::LoadExtension( extension_dir, extension_id, run_flags & kSystemApp ? mojom::ManifestLocation::kExternalComponent : mojom::ManifestLocation::kInternal, create_flags, &error_message_)); return !!extension.get(); } std::unique_ptr 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 nullptr; } 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 nullptr; } 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 nullptr; } std::unique_ptr private_key = crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(std::vector( private_key_bytes.begin(), private_key_bytes.end())); if (!private_key) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_PRIVATE_KEY_INVALID_FORMAT); return nullptr; } return private_key; } std::unique_ptr ExtensionCreator::GenerateKey( const base::FilePath& output_private_key_path) { std::unique_ptr key_pair( crypto::RSAPrivateKey::Create(kRSAKeySize)); if (!key_pair) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_PRIVATE_KEY_FAILED_TO_GENERATE); return nullptr; } std::vector private_key_vector; if (!key_pair->ExportPrivateKey(&private_key_vector)) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_PRIVATE_KEY_FAILED_TO_EXPORT); return nullptr; } std::string private_key_bytes( reinterpret_cast(&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 nullptr; } 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 nullptr; } 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 nullptr; } } return key_pair; } 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 filter = base::MakeRefCounted(extension_dir); zip::FilterCallback filter_cb = base::BindRepeating(&ExtensionCreatorFilter::ShouldPackageFile, filter); // TODO(crbug.com/862471): Surface a warning to the user for files excluded // from being packed. if (!zip::ZipWithFilterCallback(extension_dir, *zip_path, std::move(filter_cb))) { error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_FAILED_DURING_PACKAGING); return false; } return true; } bool ExtensionCreator::CreateCrx( const base::FilePath& zip_path, crypto::RSAPrivateKey* private_key, const base::FilePath& crx_path, const absl::optional& compressed_verified_contents) { crx_file::CreatorResult result; if (compressed_verified_contents.has_value()) { result = crx_file::CreateCrxWithVerifiedContentsInHeader( crx_path, zip_path, private_key, compressed_verified_contents.value()); } else { result = crx_file::Create(crx_path, zip_path, private_key); } switch (result) { case crx_file::CreatorResult::OK: return true; case crx_file::CreatorResult::ERROR_SIGNING_FAILURE: error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_ERROR_WHILE_SIGNING); return false; case crx_file::CreatorResult::ERROR_FILE_NOT_WRITABLE: error_message_ = l10n_util::GetStringUTF8(IDS_EXTENSION_SHARING_VIOLATION); return false; case crx_file::CreatorResult::ERROR_FILE_NOT_READABLE: case crx_file::CreatorResult::ERROR_FILE_WRITE_FAILURE: return false; } return false; } bool ExtensionCreator::CreateCrxAndPerformCleanup( const base::FilePath& extension_dir, const base::FilePath& crx_path, crypto::RSAPrivateKey* private_key, const absl::optional& compressed_verified_contents) { base::ScopedTempDir temp_dir; if (!temp_dir.CreateUniqueTempDir()) return false; base::FilePath zip_path; bool result = CreateZip(extension_dir, temp_dir.GetPath(), &zip_path) && CreateCrx(zip_path, private_key, crx_path, compressed_verified_contents); base::DeleteFile(zip_path); return result; } bool ExtensionCreator::CreateCrxWithVerifiedContentsInHeaderForTesting( const base::FilePath& extension_dir, const base::FilePath& crx_path, const std::string& compressed_verified_contents, std::string* extension_id) { auto signing_key = crypto::RSAPrivateKey::Create(kRSAKeySize); std::vector public_key; signing_key->ExportPublicKey(&public_key); const std::string public_key_str(public_key.begin(), public_key.end()); *extension_id = crx_file::id_util::GenerateId(public_key_str); return CreateCrxAndPerformCleanup(extension_dir, crx_path, signing_key.get(), compressed_verified_contents); } 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 std::unique_ptr key_pair; if (!private_key_path.value().empty()) key_pair = ReadInputKey(private_key_path); else key_pair = GenerateKey(output_private_key_path); if (!key_pair) { DCHECK(!error_message_.empty()) << "Set proper error message."; 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; return CreateCrxAndPerformCleanup(extension_dir, crx_path, key_pair.get(), absl::nullopt); } } // namespace extensions