910e62b5创建于 1月15日历史提交
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/crx_file/crx_creator.h"

#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/numerics/byte_conversions.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_view_util.h"
#include "components/crx_file/crx3.pb.h"
#include "components/crx_file/crx_file.h"
#include "crypto/hash.h"
#include "crypto/keypair.h"
#include "crypto/sign.h"

namespace crx_file {

namespace {

std::string GetCrxId(base::span<const uint8_t> key) {
  const auto full_hash = crypto::hash::Sha256(key);
  const auto truncated_hash = base::span(full_hash).first<16>();
  return std::string(base::as_string_view(truncated_hash));
}

constexpr size_t kFileBufferSize = 1 << 12;

// Read to the end of the file, updating the signer.
CreatorResult ReadAndSignArchive(base::File* file,
                                 crypto::sign::Signer& signer,
                                 std::vector<uint8_t>* signature) {
  std::array<uint8_t, kFileBufferSize> buffer;
  std::optional<size_t> read;
  while ((read = file->ReadAtCurrentPos(buffer)).value_or(0) > 0) {
    signer.Update(base::span(buffer).first(*read));
  }
  if (!read.has_value()) {
    return CreatorResult::ERROR_SIGNING_FAILURE;
  }
  *signature = signer.Finish();
  return CreatorResult::OK;
}

bool WriteArchive(base::File* out, base::File* in) {
  std::array<uint8_t, kFileBufferSize> buffer;
  std::optional<size_t> read;
  in->Seek(base::File::Whence::FROM_BEGIN, 0);
  while ((read = in->ReadAtCurrentPos(buffer)).value_or(0) > 0) {
    auto to_write = base::span<const uint8_t>(buffer).first(*read);
    if (!out->WriteAtCurrentPosAndCheck(to_write)) {
      return false;
    }
  }
  // A successful final read at the end of the file is indicated by returning a
  // populated option with a read size of 0. An unpopulated option indicates a
  // read error.
  return read.has_value() && read.value() == 0;
}

CreatorResult SignArchiveAndCreateHeader(
    const base::FilePath& output_path,
    base::File* file,
    const crypto::keypair::PrivateKey& signing_key,
    CrxFileHeader* header) {
  // Get the public key.
  std::vector<uint8_t> public_key = signing_key.ToSubjectPublicKeyInfo();

  // Assemble SignedData section.
  SignedData signed_header_data;
  signed_header_data.set_crx_id(GetCrxId(public_key));
  const std::string signed_header_data_str =
      signed_header_data.SerializeAsString();
  const auto signed_header_size_octets =
      base::I32ToLittleEndian(signed_header_data_str.size());

  // Create a signer, init with purpose, SignedData length, run SignedData
  // through, run ZIP through.
  crypto::sign::Signer signer(crypto::sign::SignatureKind::RSA_PKCS1_SHA256,
                              signing_key);
  signer.Update(base::as_byte_span(kSignatureContext));
  signer.Update(signed_header_size_octets);
  signer.Update(base::as_byte_span(signed_header_data_str));

  if (!file->IsValid()) {
    return CreatorResult::ERROR_FILE_NOT_READABLE;
  }
  std::vector<uint8_t> signature;
  const CreatorResult signing_result =
      ReadAndSignArchive(file, signer, &signature);
  if (signing_result != CreatorResult::OK) {
    return signing_result;
  }
  AsymmetricKeyProof* proof = header->add_sha256_with_rsa();
  proof->set_public_key(base::as_string_view(public_key));
  proof->set_signature(base::as_string_view(signature));
  header->set_signed_header_data(signed_header_data_str);
  return CreatorResult::OK;
}

CreatorResult WriteCRX(const CrxFileHeader& header,
                       const base::FilePath& output_path,
                       base::File* file) {
  const std::string header_str = header.SerializeAsString();
  const auto header_size_octets = base::I32ToLittleEndian(header_str.size());

  const auto format_version_octets = std::to_array<uint8_t>({3, 0, 0, 0});
  base::File crx(output_path,
                 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
  if (!crx.IsValid()) {
    return CreatorResult::ERROR_FILE_NOT_WRITABLE;
  }
  if (!crx.WriteAtCurrentPosAndCheck(kCrxFileHeaderMagic) ||
      !crx.WriteAtCurrentPosAndCheck(format_version_octets) ||
      !crx.WriteAtCurrentPosAndCheck(header_size_octets) ||
      !crx.WriteAtCurrentPosAndCheck(base::as_byte_span(header_str)) ||
      !WriteArchive(&crx, file)) {
    return CreatorResult::ERROR_FILE_WRITE_FAILURE;
  }
  return CreatorResult::OK;
}

}  // namespace

CreatorResult CreateCrxWithVerifiedContentsInHeader(
    const base::FilePath& output_path,
    const base::FilePath& zip_path,
    const crypto::keypair::PrivateKey& signing_key,
    const std::string& verified_contents) {
  CrxFileHeader header;
  base::File file(zip_path, base::File::FLAG_OPEN | base::File::FLAG_READ |
                                base::File::FLAG_WIN_SHARE_DELETE);
  PLOG_IF(ERROR, !file.IsValid())
      << "Failed to open " << zip_path << ": " << file.error_details();
  const CreatorResult signing_result =
      SignArchiveAndCreateHeader(output_path, &file, signing_key, &header);
  if (signing_result != CreatorResult::OK) {
    return signing_result;
  }

  // Inject the verified contents into the header.
  header.set_verified_contents(verified_contents);
  const CreatorResult result = WriteCRX(header, output_path, &file);
  return result;
}

CreatorResult Create(const base::FilePath& output_path,
                     const base::FilePath& zip_path,
                     const crypto::keypair::PrivateKey& signing_key) {
  CrxFileHeader header;
  base::File file(zip_path, base::File::FLAG_OPEN | base::File::FLAG_READ |
                                base::File::FLAG_WIN_SHARE_DELETE);
  PLOG_IF(ERROR, !file.IsValid())
      << "Failed to open " << zip_path << ": " << file.error_details();
  const CreatorResult signing_result =
      SignArchiveAndCreateHeader(output_path, &file, signing_key, &header);
  if (signing_result != CreatorResult::OK) {
    return signing_result;
  }

  const CreatorResult result = WriteCRX(header, output_path, &file);
  return result;
}

}  // namespace crx_file