[go: nahoru, domu]

blob: a3808aa42df9e38e6aaf3a849347c87604b87baa [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_UPDATER_CERTIFICATE_TAG_INTERNAL_H_
#define CHROME_UPDATER_CERTIFICATE_TAG_INTERNAL_H_
#include <cstdint>
#include <cstring>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/containers/span.h"
#include "base/gtest_prod_util.h"
#include "third_party/boringssl/src/include/openssl/bytestring.h"
#include "third_party/boringssl/src/include/openssl/crypto.h"
namespace updater::tagging::internal {
// PEBinary represents a Windows PE binary and provides functions to extract and
// set data outside of the signed area (called a "tag"). This allows a binary to
// contain arbitrary data without invalidating any Authenticode signature.
class PEBinary : public BinaryInterface {
public:
PEBinary(const PEBinary&);
PEBinary();
~PEBinary() override;
// Parse a signed, Windows PE binary. Note that the returned structure
// contains pointers into the given data.
static std::unique_ptr<PEBinary> Parse(base::span<const uint8_t> binary);
// Returns the embedded tag, if any.
std::optional<std::vector<uint8_t>> tag() const override;
// SetTag returns an updated version of the PE binary image that contains the
// given tag, or `nullopt` on error. If the binary already contains a tag then
// it will be replaced.
std::optional<std::vector<uint8_t>> SetTag(
base::span<const uint8_t> tag) override;
private:
// ParseTag attempts to parse out the tag. It returns false on parse error or
// true on success. If successful, it sets `tag_`.
bool ParseTag();
// binary_ contains the whole input binary.
base::span<const uint8_t> binary_;
// content_info_ contains the `WIN_CERTIFICATE` structure.
base::span<const uint8_t> content_info_;
// tag_ contains the embedded tag, or `nullopt` if there isn't one.
std::optional<std::vector<uint8_t>> tag_;
// attr_cert_offset_ is the offset in the file where the `WIN_CERTIFICATE`
// structure appears. (This is the last structure in the file.)
size_t attr_cert_offset_ = 0;
// certs_size_offset_ is the offset in the file where the u32le size of the
// `WIN_CERTIFICATE` structure is embedded in an `IMAGE_DATA_DIRECTORY`.
size_t certs_size_offset_ = 0;
};
#pragma pack(push)
#pragma pack(1)
// SectorFormat represents parameters of an MSI file sector.
struct SectorFormat {
// the size of a sector in bytes; 512 for dll v3 and 4096 for v4.
uint64_t size = 0;
// the number of int32s in a sector.
int ints = 0;
};
// MSIDirEntry represents a parsed MSI directory entry for a stream.
struct MSIDirEntry {
MSIDirEntry(const MSIDirEntry&);
MSIDirEntry();
~MSIDirEntry();
char name[64] = {};
uint16_t num_name_bytes = 0;
uint8_t object_type = 0;
uint8_t color_flag = 0;
uint32_t left = 0;
uint32_t right = 0;
uint32_t child = 0;
uint8_t clsid[16] = {};
uint32_t state_flags = 0;
uint64_t create_time = 0;
uint64_t modify_time = 0;
uint32_t stream_first_sector = 0;
uint64_t stream_size = 0;
};
// MSIHeader represents a parsed MSI header.
struct MSIHeader {
MSIHeader(const MSIHeader&);
MSIHeader();
~MSIHeader();
uint8_t magic[8] = {};
uint8_t clsid[16] = {};
uint16_t minor_version = 0;
uint16_t dll_version = 0;
uint16_t byte_order = 0;
uint16_t sector_shift = 0;
uint16_t mini_sector_shift = 0;
uint8_t reserved[6] = {};
uint32_t num_dir_sectors = 0;
uint32_t num_fat_sectors = 0;
uint32_t first_dir_sector = 0;
uint32_t transaction_signature_number = 0;
uint32_t mini_stream_cutoff_size = 0;
uint32_t first_mini_fat_sector = 0;
uint32_t num_mini_fat_sectors = 0;
uint32_t first_difat_sector = 0;
uint32_t num_difat_sectors = 0;
};
struct SignedDataDir {
MSIDirEntry sig_dir_entry;
uint64_t offset = 0;
bool found = false;
};
#pragma pack(pop)
// MSIBinary represents a Windows MSI binary and provides functions to extract
// and set a "tag" outside of the signed area. This allows the MSI to contain
// arbitrary data without invalidating any Authenticode signature.
class MSIBinary : public BinaryInterface {
public:
MSIBinary(const MSIBinary&);
MSIBinary();
~MSIBinary() override;
// Parses the MSI header, the directory entry for the SignedData, and the
// SignedData itself. If successful, returns a `BinaryInterface` to the
// `MSIBinary` object.
static std::unique_ptr<MSIBinary> Parse(
base::span<const uint8_t> file_contents);
// Returns the embedded tag, if any.
std::optional<std::vector<uint8_t>> tag() const override;
// Returns a complete MSI binary image based on bin, but where the superfluous
// certificate contains the given tag data.
std::optional<std::vector<uint8_t>> SetTag(
base::span<const uint8_t> tag) override;
private:
// Builds an MSI binary based on the current `MSIBinary`, but with the given
// SignedData.
std::vector<uint8_t> BuildBinary(const std::vector<uint8_t>& signed_data);
// Populates the superfluous-cert `tag_` if found. Returns `true` if the
// parsing did not produce any errors, even if a tag was not found.
bool ParseTag();
// Reads the stream starting at the given start sector.
std::vector<uint8_t> ReadStream(const std::string& name,
uint32_t start,
uint64_t stream_size,
bool force_fat,
bool free_data);
// Parse-time functionality is broken out into Populate*() methods for
// clarity.
void PopulateFatEntries();
// Copy the difat entries and make a list of difat sectors, if any.
// The first 109 difat entries must exist and are read from the MSI header,
// the rest come from optional additional sectors.
void PopulateDifatEntries();
// Returns the directory entry for the signedData stream, if it exists in the
// given sector.
SignedDataDir SignedDataDirFromSector(uint64_t dir_sector);
bool PopulateSignatureDirEntry();
void PopulateSignedData();
// Assigns an entry, the sector# of a fat sector, to the end of the difat
// list.
void AssignDifatEntry(uint64_t fat_sector);
// Ensures there is at least one free entry at the end of the difat list.
void EnsureFreeDifatEntry();
// Returns the index of the first free entry at the end of a vector of fat
// entries. It returns one past the end of list if there are no free entries
// at the end.
static uint64_t FirstFreeFatEntry(const std::vector<uint32_t>& entries);
uint64_t FirstFreeFatEntry();
// Ensures there are at least n free entries at the end of the fat list,
// and returns the first free entry.
uint64_t EnsureFreeFatEntries(uint64_t n);
// The header (512 bytes).
std::vector<uint8_t> header_bytes_;
// The parsed msi header.
MSIHeader header_;
// sector parameters.
SectorFormat sector_format_;
// The file contents with no header.
std::vector<uint8_t> contents_;
// The offset of the signedData stream directory in `contents_`.
uint64_t sig_dir_offset_ = 0;
// The parsed contents of the signedData stream directory.
MSIDirEntry sig_dir_entry_;
// The PKCS#7, SignedData in asn1 DER form.
std::vector<uint8_t> signed_data_bytes_;
// A copy of the fat entries in one list.
std::vector<uint32_t> fat_entries_;
// A copy of the difat entries in one list.
std::vector<uint32_t> difat_entries_;
// A list of the dedicated difat sectors, if any, for convenience.
std::vector<uint32_t> difat_sectors_;
// The parsed tag, if any.
std::optional<std::vector<uint8_t>> tag_;
FRIEND_TEST_ALL_PREFIXES(CertificateTagMsiFirstFreeFatEntryTest, TestCases);
FRIEND_TEST_ALL_PREFIXES(CertificateTagMsiEnsureFreeDifatEntryTest,
TestCases);
FRIEND_TEST_ALL_PREFIXES(CertificateTagMsiEnsureFreeFatEntriesTest,
TestCases);
FRIEND_TEST_ALL_PREFIXES(CertificateTagMsiAssignDifatEntryTest, TestCases);
friend MSIBinary GetMsiBinary(const std::vector<uint32_t>& fat_entries,
const std::vector<uint32_t>& difat_entries);
friend void Validate(
const MSIBinary& bin,
std::optional<std::reference_wrapper<const MSIBinary>> other);
};
// CBS is a structure from BoringSSL used for parsing binary and ASN.1-based
// formats. This implementation detail is not exposed in the interface of this
// code so these utility functions convert to/from base::span.
CBS CBSFromSpan(base::span<const uint8_t> span);
base::span<const uint8_t> SpanFromCBS(const CBS* cbs);
// kTagOID contains the DER-serialised form of the extension OID that we stuff
// the tag into: 1.3.6.1.4.1.11129.2.1.9999.
inline constexpr uint8_t kTagOID[] = {0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6,
0x79, 0x02, 0x01, 0xce, 0x0f};
// Certificate constants. See
// http://msdn.microsoft.com/en-us/library/ms920091.aspx.
//
// Despite MSDN claiming that 0x100 is the only, current revision - in
// practice it's 0x200.
inline constexpr uint16_t kAttributeCertificateRevision = 0x200;
inline constexpr uint16_t kAttributeCertificateTypePKCS7SignedData = 2;
// AddName appends an X.500 Name structure to |cbb| containing a single
// commonName with the given value.
bool AddName(CBB* cbb, const char* common_name);
// CopyASN1 copies a single ASN.1 element from |in| to |out|.
bool CopyASN1(CBB* out, CBS* in);
struct ParseResult {
bool success = false;
std::optional<base::span<const uint8_t>> tag;
};
// Parses the `signed_data` PKCS7 object to find the final certificate in the
// list and see whether it has an extension with `kTagOID`, and if so, returns a
// `base::span` of the tag within this `signed_data`. `success` is set to `true`
// if there were no parse errors, even if a tag could not be found.
ParseResult ParseTagImpl(base::span<const uint8_t> signed_data);
// Returns an updated version of the ContentInfo signedData PKCS7 object with
// the given `tag` added, or `nullopt` on error. If the input `signed_data`
// already contains a tag, then it will be replaced with `tag`.
std::optional<std::vector<uint8_t>> SetTagImpl(
base::span<const uint8_t> signed_data,
base::span<const uint8_t> tag);
// Compound file binary format constants.
inline constexpr int kNumHeaderContentBytes = 76;
inline constexpr int kNumHeaderTotalBytes = 512;
inline constexpr int kNumDifatHeaderEntries = 109;
inline constexpr int kNumDirEntryBytes = 128;
inline constexpr int kMiniStreamSectorSize = 64;
inline constexpr int kMiniStreamCutoffSize = 4096;
// An unallocated sector (used in the fat or difat).
inline constexpr uint32_t kFatFreeSector = 0xFFFFFFFF;
// End of a linked chain (in the fat); or end of difat sector chain.
inline constexpr uint32_t kFatEndOfChain = 0xFFFFFFFE;
// Used in the fat table to indicate a fat sector entry.
inline constexpr uint32_t kFatFatSector = 0xFFFFFFFD;
// Used in the fat table to indicate a difat sector entry.
inline constexpr uint32_t kFatDifSector = 0xFFFFFFFC;
// Reserved value.
inline constexpr uint32_t kFatReserved = 0xFFFFFFFB;
inline constexpr uint8_t kMsiHeaderSignature[] = {0xd0, 0xcf, 0x11, 0xe0,
0xa1, 0xb1, 0x1a, 0xe1};
inline constexpr uint8_t kMsiHeaderClsid[16] = {};
// UTF-16 for "\05DigitalSignature".
inline constexpr uint8_t kSignatureName[] = {
0x05, 0x00, 0x44, 0x00, 0x69, 0x00, 0x67, 0x00, 0x69, 0x00, 0x74, 0x00,
0x61, 0x00, 0x6c, 0x00, 0x53, 0x00, 0x69, 0x00, 0x67, 0x00, 0x6e, 0x00,
0x61, 0x00, 0x74, 0x00, 0x75, 0x00, 0x72, 0x00, 0x65, 0x00, 0x00, 0x00};
std::optional<SectorFormat> NewSectorFormat(uint16_t sector_shift);
// Returns whether the index corresponds to the last entry in a sector.
//
// The last entry in each difat sector is a pointer to the next difat sector, or
// is an end-of-chain marker.
// This does not apply to the last entry stored in the MSI header.
bool IsLastInSector(const SectorFormat& format, int index);
} // namespace updater::tagging::internal
#endif // CHROME_UPDATER_CERTIFICATE_TAG_INTERNAL_H_