| // Copyright 2012 The Chromium Authors | 
 | // 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 <stddef.h> | 
 |  | 
 | #include <string> | 
 | #include <vector> | 
 |  | 
 | #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<uint8_t> 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> 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<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 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<crypto::RSAPrivateKey> private_key = | 
 |       crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(std::vector<uint8_t>( | 
 |           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<crypto::RSAPrivateKey> ExtensionCreator::GenerateKey( | 
 |     const base::FilePath& output_private_key_path) { | 
 |   std::unique_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 nullptr; | 
 |   } | 
 |  | 
 |   std::vector<uint8_t> 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<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 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<ExtensionCreatorFilter> filter = | 
 |       base::MakeRefCounted<ExtensionCreatorFilter>(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<std::string>& 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<std::string>& 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<uint8_t> 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<crypto::RSAPrivateKey> 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 |