// Copyright 2017 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 "components/crx_file/crx_creator.h"

#include "base/files/file.h"
#include "base/files/file_path.h"
#include "components/crx_file/crx3.pb.h"
#include "components/crx_file/crx_file.h"
#include "crypto/rsa_private_key.h"
#include "crypto/sha2.h"
#include "crypto/signature_creator.h"

namespace crx_file {

namespace {

std::string GetCrxId(const std::string& key) {
  uint8_t hash[16] = {};  // CRX IDs are 16 bytes long.
  crypto::SHA256HashString(key, hash, sizeof(hash));
  static_assert(sizeof(char) == sizeof(uint8_t), "Unsupported char size.");
  return std::string(reinterpret_cast<char*>(hash), sizeof(hash));
}

// Read to the end of the file, updating the signer.
CreatorResult ReadAndSignArchive(base::File* file,
                                 crypto::SignatureCreator* signer,
                                 std::vector<uint8_t>* signature) {
  uint8_t buffer[1 << 12] = {};
  int read = 0;
  static_assert(sizeof(char) == sizeof(uint8_t), "Unsupported char size.");
  while ((read = file->ReadAtCurrentPos(reinterpret_cast<char*>(buffer),
                                        arraysize(buffer))) > 0) {
    if (!signer->Update(buffer, read))
      return CreatorResult::ERROR_SIGNING_FAILURE;
  }
  if (read < 0)
    return CreatorResult::ERROR_SIGNING_FAILURE;
  return signer->Final(signature) ? CreatorResult::OK
                                  : CreatorResult::ERROR_SIGNING_FAILURE;
}

bool WriteBuffer(base::File* file, const char buffer[], int len) {
  return file->WriteAtCurrentPos(buffer, len) == len;
}

bool WriteArchive(base::File* out, base::File* in) {
  char buffer[1 << 12] = {};
  int read = 0;
  in->Seek(base::File::Whence::FROM_BEGIN, 0);
  while ((read = in->ReadAtCurrentPos(buffer, arraysize(buffer))) > 0) {
    if (out->WriteAtCurrentPos(buffer, read) != read)
      return false;
  }
  return read == 0;
}

}  // namespace

CreatorResult Create(const base::FilePath& output_path,
                     const base::FilePath& zip_path,
                     crypto::RSAPrivateKey* signing_key) {
  // Get the public key.
  std::vector<uint8_t> public_key;
  signing_key->ExportPublicKey(&public_key);
  const std::string public_key_str(public_key.begin(), public_key.end());

  // Assemble SignedData section.
  SignedData signed_header_data;
  signed_header_data.set_crx_id(GetCrxId(public_key_str));
  const std::string signed_header_data_str =
      signed_header_data.SerializeAsString();
  const int signed_header_size = signed_header_data_str.size();
  const uint8_t signed_header_size_octets[] = {
      signed_header_size, signed_header_size >> 8, signed_header_size >> 16,
      signed_header_size >> 24};

  // Create a signer, init with purpose, SignedData length, run SignedData
  // through, run ZIP through.
  auto signer = crypto::SignatureCreator::Create(
      signing_key, crypto::SignatureCreator::HashAlgorithm::SHA256);
  signer->Update(kSignatureContext, arraysize(kSignatureContext));
  signer->Update(signed_header_size_octets,
                 arraysize(signed_header_size_octets));
  signer->Update(
      reinterpret_cast<const uint8_t*>(signed_header_data_str.data()),
      signed_header_data_str.size());
  base::File file(zip_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
  if (!file.IsValid())
    return CreatorResult::ERROR_FILE_NOT_READABLE;
  std::vector<uint8_t> signature;
  const CreatorResult signing_result =
      ReadAndSignArchive(&file, signer.get(), &signature);
  if (signing_result != CreatorResult::OK)
    return signing_result;

  // Create CRXFileHeader.
  CrxFileHeader header;
  AsymmetricKeyProof* proof = header.add_sha256_with_rsa();
  proof->set_public_key(public_key_str);
  proof->set_signature(std::string(signature.begin(), signature.end()));
  header.set_signed_header_data(signed_header_data_str);
  const std::string header_str = header.SerializeAsString();
  const int header_size = header_str.size();
  const uint8_t header_size_octets[] = {header_size, header_size >> 8,
                                        header_size >> 16, header_size >> 24};

  // Write CRX.
  const uint8_t format_version_octets[] = {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;
  static_assert(sizeof(char) == sizeof(uint8_t), "Unsupported char size.");
  if (!WriteBuffer(&crx, kCrxFileHeaderMagic, kCrxFileHeaderMagicSize) ||
      !WriteBuffer(&crx, reinterpret_cast<const char*>(format_version_octets),
                   arraysize(format_version_octets)) ||
      !WriteBuffer(&crx, reinterpret_cast<const char*>(header_size_octets),
                   arraysize(header_size_octets)) ||
      !WriteBuffer(&crx, header_str.c_str(), header_str.length()) ||
      !WriteArchive(&crx, &file)) {
    return CreatorResult::ERROR_FILE_WRITE_FAILURE;
  }
  return CreatorResult::OK;
}

}  // namespace crx_file
