blob: cb90c22f3566a581946553a3cf07b6ad7e90bf87 [file] [log] [blame]
// 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