osslsigncode/appx.c

2814 lines
98 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* APPX file support library
* https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
*
* Copyright (C) Maciej Panek <maciej.panek_malpa_punxworks.com>
* Copyright (C) 2023 Michał Trojnara <Michal.Trojnara@stunnel.org>
* Author: Małgorzata Olszówka <Malgorzata.Olszowka@stunnel.org>
*
* APPX files do not support nesting (multiple signature)
*/
#define _FILE_OFFSET_BITS 64
#include "osslsigncode.h"
#include "helpers.h"
#include <zlib.h>
#include <inttypes.h>
#ifndef PRIX64
#if defined(_MSC_VER)
#define PRIX64 "I64X"
#else /* _MSC_VER */
#if ULONG_MAX == 0xFFFFFFFFFFFFFFFF
#define PRIX64 "lX"
#else /* ULONG_MAX == 0xFFFFFFFFFFFFFFFF */
#define PRIX64 "llX"
#endif /* ULONG_MAX == 0xFFFFFFFFFFFFFFFF */
#endif /* _MSC_VER */
#endif /* PRIX64 */
#define EOCDR_SIZE 22
#define ZIP64_EOCD_LOCATOR_SIZE 20
#define ZIP64_HEADER 0x01
#define COMPRESSION_NONE 0
#define COMPRESSION_DEFLATE 8
#define DATA_DESCRIPTOR_BIT (1 << 3)
static const char PKZIP_LH_SIGNATURE[4] = { 'P', 'K', 3, 4 };
static const char PKZIP_CD_SIGNATURE[4] = { 'P', 'K', 1, 2 };
static const char PKZIP_EOCDR_SIGNATURE[4] = { 'P', 'K', 5, 6 };
static const char PKZIP_DATA_DESCRIPTOR_SIGNATURE[4] = { 'P', 'K', 7, 8 };
static const char PKZIP64_EOCD_LOCATOR_SIGNATURE[4] = { 'P', 'K', 6, 7 };
static const char PKZIP64_EOCDR_SIGNATURE[4] = { 'P', 'K', 6, 6 };
static const char *APP_SIGNATURE_FILENAME = "AppxSignature.p7x";
static const char *CONTENT_TYPES_FILENAME = "[Content_Types].xml";
static const char *BLOCK_MAP_FILENAME = "AppxBlockMap.xml";
static const char *APPXBUNDLE_MANIFEST_FILENAME = "AppxMetadata/AppxBundleManifest.xml";
static const char *CODE_INTEGRITY_FILENAME = "AppxMetadata/CodeIntegrity.cat";
static const char *SIGNATURE_CONTENT_TYPES_ENTRY = "<Override PartName=\"/AppxSignature.p7x\" ContentType=\"application/vnd.ms-appx.signature\"/>";
static const char *SIGNATURE_CONTENT_TYPES_CLOSING_TAG = "</Types>";
static const u_char APPX_UUID[] = { 0x4B, 0xDF, 0xC5, 0x0A, 0x07, 0xCE, 0xE2, 0x4D, 0xB7, 0x6E, 0x23, 0xC8, 0x39, 0xA0, 0x9F, 0xD1 };
static const u_char APPXBUNDLE_UUID[] = { 0xB3, 0x58, 0x5F, 0x0F, 0xDE, 0xAA, 0x9A, 0x4B, 0xA4, 0x34, 0x95, 0x74, 0x2D, 0x92, 0xEC, 0xEB };
static const char PKCX_SIGNATURE[4] = { 'P', 'K', 'C', 'X' }; /* P7X format header */
static const char APPX_SIGNATURE[4] = { 'A', 'P', 'P', 'X' }; /* APPX header */
static const char AXPC_SIGNATURE[4] = { 'A', 'X', 'P', 'C' }; /* digest of zip file records */
static const char AXCD_SIGNATURE[4] = { 'A', 'X', 'C', 'D' }; /* digest zip file central directory */
static const char AXCT_SIGNATURE[4] = { 'A', 'X', 'C', 'T' }; /* digest of uncompressed [ContentTypes].xml */
static const char AXBM_SIGNATURE[4] = { 'A', 'X', 'B', 'M' }; /* digest of uncompressed AppxBlockMap.xml */
static const char AXCI_SIGNATURE[4] = { 'A', 'X', 'C', 'I' }; /* digest of uncompressed AppxMetadata/CodeIntegrity.cat (optional) */
static const char *HASH_METHOD_TAG = "HashMethod";
static const char *HASH_METHOD_SHA256 = "http://www.w3.org/2001/04/xmlenc#sha256";
static const char *HASH_METHOD_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384";
static const char *HASH_METHOD_SHA512 = "http://www.w3.org/2001/04/xmlenc#sha512";
/*
* Overall .ZIP file format:
*
* [local file header 1]
* [encryption header 1]
* [file data 1]
* [data descriptor 1]
* .
* .
* .
* [local file header n]
* [encryption header n]
* [file data n]
* [data descriptor n]
* [archive decryption header]
* [archive extra data record]
* [central directory header 1]
* .
* .
* .
* [central directory header n]
* [zip64 end of central directory record]
* [zip64 end of central directory locator]
* [end of central directory record]
*/
/* Local file header */
typedef struct {
uint16_t version;
uint16_t flags;
uint16_t compression;
uint16_t modTime;
uint16_t modDate;
uint32_t crc32;
uint64_t compressedSize;
uint64_t uncompressedSize;
uint16_t fileNameLen;
uint16_t extraFieldLen;
char *fileName;
uint8_t *extraField;
int compressedSizeInZip64;
int uncompressedSizeInZip64;
} ZIP_LOCAL_HEADER;
/* Data descriptor */
typedef struct {
uint32_t crc32;
uint64_t compressedSize;
uint64_t uncompressedSize;
uint8_t *data;
} ZIP_OVERRIDE_DATA;
/* Central directory structure */
typedef struct zipCentralDirectoryEntry_struct {
uint16_t creatorVersion;
uint16_t viewerVersion;
uint16_t flags;
uint16_t compression;
uint16_t modTime;
uint16_t modDate;
uint32_t crc32;
uint64_t compressedSize;
uint64_t uncompressedSize;
uint16_t fileNameLen;
uint16_t extraFieldLen;
uint16_t fileCommentLen;
uint32_t diskNoStart;
uint16_t internalAttr;
uint32_t externalAttr;
uint64_t offsetOfLocalHeader;
char *fileName;
uint8_t *extraField;
char *fileComment;
int64_t fileOffset;
int64_t entryLen;
int compressedSizeInZip64;
int uncompressedSizeInZip64;
int offsetInZip64;
int diskNoInZip64;
ZIP_OVERRIDE_DATA *overrideData;
struct zipCentralDirectoryEntry_struct *next;
} ZIP_CENTRAL_DIRECTORY_ENTRY;
DEFINE_STACK_OF(ZIP_CENTRAL_DIRECTORY_ENTRY)
/* Zip64 end of central directory record */
typedef struct {
uint64_t eocdrSize;
uint16_t creatorVersion;
uint16_t viewerVersion;
uint32_t diskNumber;
uint32_t diskWithCentralDirectory;
uint64_t diskEntries;
uint64_t totalEntries;
uint64_t centralDirectorySize;
uint64_t centralDirectoryOffset;
uint64_t commentLen;
char *comment;
} ZIP64_EOCDR;
/* Zip64 end of central directory locator */
typedef struct {
uint32_t diskWithEOCD;
uint64_t eocdOffset;
uint32_t totalNumberOfDisks;
} ZIP64_EOCD_LOCATOR;
/* End of central directory record */
typedef struct {
uint16_t diskNumber;
uint16_t centralDirectoryDiskNumber;
uint16_t diskEntries;
uint16_t totalEntries;
uint32_t centralDirectorySize;
uint32_t centralDirectoryOffset;
uint16_t commentLen;
char *comment;
} ZIP_EOCDR;
typedef struct {
FILE *file;
ZIP_CENTRAL_DIRECTORY_ENTRY *centralDirectoryHead;
uint64_t centralDirectorySize;
uint64_t centralDirectoryOffset;
uint64_t centralDirectoryRecordCount;
uint64_t eocdrOffset;
int64_t eocdrLen;
int64_t fileSize;
int isZip64;
/* this will come handy to rewrite the eocdr */
ZIP_EOCDR eocdr;
ZIP64_EOCD_LOCATOR locator;
ZIP64_EOCDR eocdr64;
} ZIP_FILE;
typedef struct {
ASN1_INTEGER *a;
ASN1_OCTET_STRING *string;
ASN1_INTEGER *b;
ASN1_INTEGER *c;
ASN1_INTEGER *d;
ASN1_INTEGER *e;
ASN1_INTEGER *f;
} AppxSpcSipInfo;
DECLARE_ASN1_FUNCTIONS(AppxSpcSipInfo)
ASN1_SEQUENCE(AppxSpcSipInfo) = {
ASN1_SIMPLE(AppxSpcSipInfo, a, ASN1_INTEGER),
ASN1_SIMPLE(AppxSpcSipInfo, string, ASN1_OCTET_STRING),
ASN1_SIMPLE(AppxSpcSipInfo, b, ASN1_INTEGER),
ASN1_SIMPLE(AppxSpcSipInfo, c, ASN1_INTEGER),
ASN1_SIMPLE(AppxSpcSipInfo, d, ASN1_INTEGER),
ASN1_SIMPLE(AppxSpcSipInfo, e, ASN1_INTEGER),
ASN1_SIMPLE(AppxSpcSipInfo, f, ASN1_INTEGER),
} ASN1_SEQUENCE_END(AppxSpcSipInfo)
IMPLEMENT_ASN1_FUNCTIONS(AppxSpcSipInfo)
struct appx_ctx_st {
ZIP_FILE *zip;
u_char *calculatedBMHash;
u_char *calculatedCTHash;
u_char *calculatedCDHash;
u_char *calculatedDataHash;
u_char *calculatedCIHash;
u_char *existingBMHash;
u_char *existingCTHash;
u_char *existingCDHash;
u_char *existingDataHash;
u_char *existingCIHash;
int isBundle;
const EVP_MD *md;
int hashlen;
} appx_ctx_t;
/* FILE_FORMAT method prototypes */
static FILE_FORMAT_CTX *appx_ctx_new(GLOBAL_OPTIONS *options, BIO *hash, BIO *outdata);
static const EVP_MD *appx_md_get(FILE_FORMAT_CTX *ctx);
static ASN1_OBJECT *appx_spc_sip_info_get(u_char **p, int *plen, FILE_FORMAT_CTX *ctx);
static PKCS7 *appx_pkcs7_contents_get(FILE_FORMAT_CTX *ctx, BIO *hash, const EVP_MD *md);
static int appx_hash_length_get(FILE_FORMAT_CTX *ctx);
static int appx_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7);
static PKCS7 *appx_pkcs7_extract(FILE_FORMAT_CTX *ctx);
static int appx_remove_pkcs7(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata);
static int appx_process_data(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata);
static PKCS7 *appx_pkcs7_signature_new(FILE_FORMAT_CTX *ctx, BIO *hash);
static int appx_append_pkcs7(FILE_FORMAT_CTX *ctx, BIO *outdata, PKCS7 *p7);
static void appx_bio_free(BIO *hash, BIO *outdata);
static void appx_ctx_cleanup(FILE_FORMAT_CTX *ctx);
FILE_FORMAT file_format_appx = {
.ctx_new = appx_ctx_new,
.md_get = appx_md_get,
.data_blob_get = appx_spc_sip_info_get,
.pkcs7_contents_get = appx_pkcs7_contents_get,
.hash_length_get = appx_hash_length_get,
.verify_digests = appx_verify_digests,
.pkcs7_extract = appx_pkcs7_extract,
.remove_pkcs7 = appx_remove_pkcs7,
.process_data = appx_process_data,
.pkcs7_signature_new = appx_pkcs7_signature_new,
.append_pkcs7 = appx_append_pkcs7,
.bio_free = appx_bio_free,
.ctx_cleanup = appx_ctx_cleanup,
};
/* Prototypes */
static BIO *appx_calculate_hashes(FILE_FORMAT_CTX *ctx);
static BIO *appx_hash_blob_get(FILE_FORMAT_CTX *ctx);
static uint8_t *appx_calc_zip_central_directory_hash(ZIP_FILE *zip, const EVP_MD *md, uint64_t cdOffset);
static int appx_write_central_directory(BIO *bio, ZIP_FILE *zip, int removeSignature, uint64_t cdOffset);
static uint8_t *appx_calc_zip_data_hash(uint64_t *cdOffset, ZIP_FILE *zip, const EVP_MD *md);
static int appx_extract_hashes(FILE_FORMAT_CTX *ctx, SpcIndirectDataContent *content);
static int appx_compare_hashes(FILE_FORMAT_CTX *ctx);
static int appx_remove_ct_signature_entry(ZIP_FILE *zip, ZIP_CENTRAL_DIRECTORY_ENTRY *entry);
static int appx_append_ct_signature_entry(ZIP_FILE *zip, ZIP_CENTRAL_DIRECTORY_ENTRY *entry);
static const EVP_MD *appx_get_md(ZIP_FILE *zip);
static ZIP_CENTRAL_DIRECTORY_ENTRY *zipGetCDEntryByName(ZIP_FILE *zip, const char *name);
static void zipWriteCentralDirectoryEntry(BIO *bio, uint64_t *sizeOnDisk, ZIP_CENTRAL_DIRECTORY_ENTRY *entry, uint64_t offsetDiff);
static int zipAppendSignatureFile(BIO *bio, ZIP_FILE *zip, uint8_t *data, uint64_t dataSize);
static int zipOverrideFileData(ZIP_CENTRAL_DIRECTORY_ENTRY *entry, uint8_t *data, uint64_t dataSize);
static int zipRewriteData(ZIP_FILE *zip, ZIP_CENTRAL_DIRECTORY_ENTRY *entry, BIO *bio, uint64_t *sizeOnDisk);
static void zipWriteLocalHeader(BIO *bio, uint64_t *sizeonDisk, ZIP_LOCAL_HEADER *heade);
static int zipEntryExist(ZIP_FILE *zip, const char *name);
static u_char *zipCalcDigest(ZIP_FILE *zip, const char *fileName, const EVP_MD *md);
static size_t zipReadFileDataByName(uint8_t **pData, ZIP_FILE *zip, const char *name);
static size_t zipReadFileData(ZIP_FILE *zip, uint8_t **pData, ZIP_CENTRAL_DIRECTORY_ENTRY *entry);
static int zipReadLocalHeader(ZIP_LOCAL_HEADER *header, ZIP_FILE *zip, uint64_t compressedSize);
static int zipInflate(uint8_t *dest, uint64_t *destLen, uint8_t *source, uLong *sourceLen);
static int zipDeflate(uint8_t *dest, uint64_t *destLen, uint8_t *source, uLong sourceLen);
static ZIP_FILE *openZip(const char *filename);
static void freeZip(ZIP_FILE *zip);
static ZIP_FILE *zipSortCentralDirectory(ZIP_FILE *zip);
static void zipPrintCentralDirectory(ZIP_FILE *zip);
static int zipReadCentralDirectory(ZIP_FILE *zip, FILE *file);
static ZIP_CENTRAL_DIRECTORY_ENTRY *zipReadNextCentralDirectoryEntry(FILE *file);
static void freeZipCentralDirectoryEntry(ZIP_CENTRAL_DIRECTORY_ENTRY *entry);
static int readZipEOCDR(ZIP_EOCDR *eocdr, FILE *file);
static int readZip64EOCDLocator(ZIP64_EOCD_LOCATOR *locator, FILE *file);
static int readZip64EOCDR(ZIP64_EOCDR *eocdr, FILE *file, uint64_t offset);
static int get_current_position(BIO *bio, uint64_t *offset);
static uint64_t fileGetU64(FILE *file);
static uint32_t fileGetU32(FILE *file);
static uint16_t fileGetU16(FILE *file);
static uint64_t bufferGetU64(uint8_t *buffer, uint64_t *pos);
static uint32_t bufferGetU32(uint8_t *buffer, uint64_t *pos);
static uint16_t bufferGetU16(uint8_t *buffer, uint64_t *pos);
static void bioAddU64(BIO *bio, uint64_t v);
static void bioAddU32(BIO *bio, uint32_t v);
static void bioAddU16(BIO *bio, uint16_t v);
/*
* FILE_FORMAT method definitions
*/
/*
* Allocate and return a PE file format context.
* [in, out] options: structure holds the input data
* [out] hash: message digest BIO
* [in] outdata: outdata file BIO
* [returns] pointer to PE file format context
*/
static FILE_FORMAT_CTX *appx_ctx_new(GLOBAL_OPTIONS *options, BIO *hash, BIO *outdata)
{
FILE_FORMAT_CTX *ctx;
const EVP_MD *md;
ZIP_FILE *zip = openZip(options->infile);
/* squash unused parameter warnings */
(void)hash;
(void)outdata;
if (!zip) {
return NULL; /* FAILED */
}
if (options->verbose) {
zipPrintCentralDirectory(zip);
}
md = appx_get_md(zip);
if (!md) {
freeZip(zip);
return NULL; /* FAILED */
}
ctx = OPENSSL_malloc(sizeof(FILE_FORMAT_CTX));
ctx->appx_ctx = OPENSSL_zalloc(sizeof(appx_ctx_t));
ctx->appx_ctx->zip = zip;
ctx->format = &file_format_appx;
ctx->options = options;
ctx->appx_ctx->md = md;
if (zipGetCDEntryByName(zip, APPXBUNDLE_MANIFEST_FILENAME)) {
ctx->appx_ctx->isBundle = 1;
}
if (options->cmd == CMD_SIGN || options->cmd==CMD_ATTACH
|| options->cmd==CMD_ADD || options->cmd == CMD_EXTRACT_DATA) {
printf("Warning: Ignore -h option, use the hash algorithm specified in AppxBlockMap.xml\n");
}
if (options->pagehash == 1)
printf("Warning: -ph option is only valid for PE files\n");
if (options->jp >= 0)
printf("Warning: -jp option is only valid for CAB files\n");
if (options->add_msi_dse == 1)
printf("Warning: -add-msi-dse option is only valid for MSI files\n");
return ctx;
}
/*
* Return a hash algorithm specified in the AppxBlockMap.xml file.
* [in] ctx: structure holds input and output data
* [returns] hash algorithm
*/
static const EVP_MD *appx_md_get(FILE_FORMAT_CTX *ctx)
{
return ctx->appx_ctx->md;
}
/*
* Allocate and return SpcSipInfo object.
* [out] p: SpcSipInfo data
* [out] plen: SpcSipInfo data length
* [in] ctx: structure holds input and output data
* [returns] pointer to ASN1_OBJECT structure corresponding to SPC_SIPINFO_OBJID
*/
static ASN1_OBJECT *appx_spc_sip_info_get(u_char **p, int *plen, FILE_FORMAT_CTX *ctx)
{
ASN1_OBJECT *dtype;
AppxSpcSipInfo *si = AppxSpcSipInfo_new();
ASN1_INTEGER_set(si->a, 0x01010000);
ASN1_INTEGER_set(si->b, 0);
ASN1_INTEGER_set(si->c, 0);
ASN1_INTEGER_set(si->d, 0);
ASN1_INTEGER_set(si->e, 0);
ASN1_INTEGER_set(si->f, 0);
if (ctx->appx_ctx->isBundle) {
printf("Signing as a bundle\n");
ASN1_OCTET_STRING_set(si->string, APPXBUNDLE_UUID, sizeof(APPXBUNDLE_UUID));
} else {
printf("Signing as a package\n");
ASN1_OCTET_STRING_set(si->string, APPX_UUID, sizeof(APPX_UUID));
}
*plen = i2d_AppxSpcSipInfo(si, NULL);
*p = OPENSSL_malloc((size_t)*plen);
i2d_AppxSpcSipInfo(si, p);
*p -= *plen;
dtype = OBJ_txt2obj(SPC_SIPINFO_OBJID, 1);
AppxSpcSipInfo_free(si);
return dtype; /* OK */
}
/*
* Allocate and return a data content to be signed.
* [in] ctx: structure holds input and output data
* [in] hash: message digest BIO
* [in] md: message digest algorithm
* [returns] data content
*/
static PKCS7 *appx_pkcs7_contents_get(FILE_FORMAT_CTX *ctx, BIO *hash, const EVP_MD *md)
{
ASN1_OCTET_STRING *content;
ZIP_CENTRAL_DIRECTORY_ENTRY *entry;
BIO *bhash;
/* squash unused parameter warnings */
(void)md;
(void)hash;
/* Create and append a new signature content types entry */
entry = zipGetCDEntryByName(ctx->appx_ctx->zip, CONTENT_TYPES_FILENAME);
if (!entry) {
fprintf(stderr, "Not a valid .appx file: content types file missing\n");
return NULL; /* FAILED */
}
if (!appx_append_ct_signature_entry(ctx->appx_ctx->zip, entry)) {
return NULL; /* FAILED */
}
bhash = appx_calculate_hashes(ctx);
if (!bhash) {
return NULL; /* FAILED */
}
content = spc_indirect_data_content_get(bhash, ctx);
BIO_free_all(bhash);
return pkcs7_set_content(content);
}
/*
* Get concatenated hashes length.
* [in] ctx: structure holds input and output data
* [returns] the length of concatenated hashes
*/
static int appx_hash_length_get(FILE_FORMAT_CTX *ctx)
{
return ctx->appx_ctx->hashlen;
}
/*
* Calculate message digest and compare to value retrieved from PKCS#7 signedData.
* [in] ctx: structure holds input and output data
* [in] p7: PKCS#7 signature
* [returns] 0 on error or 1 on success
*/
static int appx_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7)
{
if (is_content_type(p7, SPC_INDIRECT_DATA_OBJID)) {
ASN1_STRING *content_val = p7->d.sign->contents->d.other->value.sequence;
const u_char *p = content_val->data;
SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &p, content_val->length);
if (idc) {
BIO *hashes;
if (!appx_extract_hashes(ctx, idc)) {
fprintf(stderr, "Failed to extract hashes from the signature\n");
SpcIndirectDataContent_free(idc);
return 0; /* FAILED */
}
hashes = appx_calculate_hashes(ctx);
if (!hashes) {
SpcIndirectDataContent_free(idc);
return 0; /* FAILED */
}
BIO_free_all(hashes);
if (!appx_compare_hashes(ctx)) {
fprintf(stderr, "Signature hash verification failed\n");
SpcIndirectDataContent_free(idc);
return 0; /* FAILED */
}
SpcIndirectDataContent_free(idc);
}
}
return 1; /* OK */
}
/*
* Extract existing signature in DER format.
* [in] ctx: structure holds input and output data
* [returns] pointer to PKCS#7 structure
*/
static PKCS7 *appx_pkcs7_extract(FILE_FORMAT_CTX *ctx)
{
PKCS7 *p7;
uint8_t *data = NULL;
const u_char *blob;
size_t dataSize;
/* Check if the signature exists */
if (!zipEntryExist(ctx->appx_ctx->zip, APP_SIGNATURE_FILENAME)) {
fprintf(stderr, "%s does not exist\n", APP_SIGNATURE_FILENAME);
return NULL; /* FAILED */
}
dataSize = zipReadFileDataByName(&data, ctx->appx_ctx->zip, APP_SIGNATURE_FILENAME);
if (dataSize <= 0) {
return NULL; /* FAILED */
}
/* P7X format is just 0x504B4358 (PKCX) followed by PKCS#7 data in the DER format */
if (memcmp(data, PKCX_SIGNATURE, 4)) {
fprintf(stderr, "Invalid PKCX header\n");
OPENSSL_free(data);
return NULL; /* FAILED */
}
blob = (u_char *)data + 4;
p7 = d2i_PKCS7(NULL, &blob, (int)dataSize - 4);
OPENSSL_free(data);
return p7;
}
/*
* Remove existing signature.
* [in, out] ctx: structure holds input and output data
* [out] hash: message digest BIO
* [out] outdata: outdata file BIO
* [returns] 1 on error or 0 on success
*/
static int appx_remove_pkcs7(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata)
{
uint8_t *data = NULL;
size_t dataSize;
uint64_t cdOffset, noEntries = 0;
ZIP_FILE *zip = ctx->appx_ctx->zip;
ZIP_CENTRAL_DIRECTORY_ENTRY *entry = zipGetCDEntryByName(zip, CONTENT_TYPES_FILENAME);
/* squash the unused parameter warning */
(void)hash;
if (!entry) {
fprintf(stderr, "Not a valid .appx file: content types file missing\n");
return 1; /* FAILED */
}
/* read signature data */
dataSize = zipReadFileDataByName(&data, ctx->appx_ctx->zip, APP_SIGNATURE_FILENAME);
if (dataSize <= 0) {
return 1; /* FAILED, no signature */
}
OPENSSL_free(data);
if (!appx_remove_ct_signature_entry(zip, entry)) {
fprintf(stderr, "Failed to remove signature entry\n");
return 1; /* FAILED */
}
for (entry = zip->centralDirectoryHead; entry != NULL; entry = entry->next) {
if (noEntries == zip->centralDirectoryRecordCount) {
fprintf(stderr, "Corrupted central directory structure\n");
return 1; /* FAILED */
}
noEntries++;
if (!entry->fileName || (entry->fileNameLen == 0)) {
fprintf(stderr, "Corrupted file name\n");
return 1; /* FAILED */
}
if (strcmp(entry->fileName, APP_SIGNATURE_FILENAME)) {
uint64_t dummy;
if (!zipRewriteData(zip, entry, outdata, &dummy)) {
return 1; /* FAILED */
}
}
}
if (!get_current_position(outdata, &cdOffset)) {
fprintf(stderr, "Unable to get offset\n");
return 1; /* FAILED */
}
if (!appx_write_central_directory(outdata, zip, 1, cdOffset)) {
fprintf(stderr, "Unable to write central directory\n");
return 1; /* FAILED */
}
return 0; /* OK */
}
/*
* Modify specific type data.
* [in, out] ctx: structure holds input and output data
* [out] hash: message digest BIO (unused)
* [out] outdata: outdata file BIO (unused)
* [returns] 1 on error or 0 on success
*/
static int appx_process_data(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata)
{
ZIP_CENTRAL_DIRECTORY_ENTRY *entry;
/* squash unused parameter warnings */
(void)outdata;
(void)hash;
/* Create and append a new signature content types entry */
entry = zipGetCDEntryByName(ctx->appx_ctx->zip, CONTENT_TYPES_FILENAME);
if (!entry) {
fprintf(stderr, "Not a valid .appx file: content types file missing\n");
return 0; /* FAILED */
}
if (!appx_append_ct_signature_entry(ctx->appx_ctx->zip, entry)) {
return 0; /* FAILED */
}
return 1; /* OK */
}
/*
* Create a new PKCS#7 signature.
* [in, out] ctx: structure holds input and output data
* [out] hash: message digest BIO (unused)
* [returns] pointer to PKCS#7 structure
*/
static PKCS7 *appx_pkcs7_signature_new(FILE_FORMAT_CTX *ctx, BIO *hash)
{
ASN1_OCTET_STRING *content;
PKCS7 *p7 = NULL;
BIO *hashes;
/* squash unused parameter warnings */
(void)hash;
/* Create hash blob from concatenated APPX hashes */
hashes = appx_calculate_hashes(ctx);
if (!hashes) {
return NULL; /* FAILED */
}
p7 = pkcs7_create(ctx);
if (!p7) {
fprintf(stderr, "Creating a new signature failed\n");
BIO_free_all(hashes);
return NULL; /* FAILED */
}
if (!add_indirect_data_object(p7)) {
fprintf(stderr, "Adding SPC_INDIRECT_DATA_OBJID failed\n");
PKCS7_free(p7);
BIO_free_all(hashes);
return NULL; /* FAILED */
}
content = spc_indirect_data_content_get(hashes, ctx);
BIO_free_all(hashes);
if (!content) {
fprintf(stderr, "Failed to get spcIndirectDataContent\n");
PKCS7_free(p7);
return NULL; /* FAILED */
}
if (!sign_spc_indirect_data_content(p7, content)) {
fprintf(stderr, "Failed to set signed content\n");
PKCS7_free(p7);
ASN1_OCTET_STRING_free(content);
return NULL; /* FAILED */
}
ASN1_OCTET_STRING_free(content);
return p7; /* OK */
}
/*
* Append signature to the outfile.
* [in, out] ctx: structure holds input and output data
* [out] outdata: outdata file BIO
* [in] p7: PKCS#7 signature
* [returns] 1 on error or 0 on success
*/
static int appx_append_pkcs7(FILE_FORMAT_CTX *ctx, BIO *outdata, PKCS7 *p7)
{
ZIP_FILE *zip = ctx->appx_ctx->zip;
ZIP_CENTRAL_DIRECTORY_ENTRY *prev = NULL;
ZIP_CENTRAL_DIRECTORY_ENTRY *last = NULL;
ZIP_CENTRAL_DIRECTORY_ENTRY *entry;
u_char *blob, *der = NULL;
int len;
uint64_t cdOffset, noEntries = 0;
for (entry = zip->centralDirectoryHead; entry != NULL;) {
if (noEntries >= zip->centralDirectoryRecordCount) {
fprintf(stderr, "Corrupted central directory structure\n");
return 1; /* FAILED */
}
noEntries++;
last = entry;
if (!entry->fileName || (entry->fileNameLen == 0)) {
fprintf(stderr, "Corrupted file name\n");
return 1; /* FAILED */
}
if (strcmp(entry->fileName, APP_SIGNATURE_FILENAME)) {
uint64_t dummy = 0;
if (!zipRewriteData(zip, entry, outdata, &dummy)) {
return 1; /* FAILED */
}
prev = entry;
entry = entry->next;
} else {
/* remove the entry
* actually this code is pretty naive - if you remove the entry that was not at the end
* everything will go south - the offsets in the CD will not match the local header offsets.
* that can be fixed here or left as is - signtool and this tool always appends the signature file at the end.
* Might be a problem when someone decides to unpack & repack the .appx zip file */
ZIP_CENTRAL_DIRECTORY_ENTRY *current = entry;
entry = entry->next;
if (prev) {
prev->next = entry;
}
freeZipCentralDirectoryEntry(current);
}
}
if (!last) {
/* not really possible unless an empty zip file, but who knows */
return 1; /* FAILED */
}
/* create the signature entry */
if (((len = i2d_PKCS7(p7, NULL)) <= 0) ||
(der = OPENSSL_malloc((size_t)len)) == NULL)
return 1; /* FAILED */
i2d_PKCS7(p7, &der);
der -= len;
blob = OPENSSL_malloc((size_t)(len + 4));
memcpy(blob, PKCX_SIGNATURE, 4);
memcpy(blob + 4, der, (size_t)len);
len += 4;
if (!zipAppendSignatureFile(outdata, zip, blob, (uint64_t)len)) {
OPENSSL_free(blob);
fprintf(stderr, "Failed to append zip file\n");
return 1; /* FAILED */
}
OPENSSL_free(der);
OPENSSL_free(blob);
if (!get_current_position(outdata, &cdOffset)) {
fprintf(stderr, "Unable to get offset\n");
return 1; /* FAILED */
}
if (!appx_write_central_directory(outdata, zip, 0, cdOffset)) {
fprintf(stderr, "Unable to write central directory\n");
return 1; /* FAILED */
}
return 0; /* OK */
}
/*
* Free up an entire message digest BIO chain.
* [out] hash: message digest BIO
* [out] outdata: outdata file BIO
* [returns] none
*/
static void appx_bio_free(BIO *hash, BIO *outdata)
{
BIO_free_all(outdata);
BIO_free_all(hash);
}
/*
* Deallocate a FILE_FORMAT_CTX structure and PE format specific structure.
* [out] ctx: structure holds input and output data
* [out] hash: message digest BIO
* [in] outdata: outdata file BIO
* [returns] none
*/
static void appx_ctx_cleanup(FILE_FORMAT_CTX *ctx)
{
freeZip(ctx->appx_ctx->zip);
OPENSSL_free(ctx->appx_ctx->calculatedBMHash);
OPENSSL_free(ctx->appx_ctx->calculatedCTHash);
OPENSSL_free(ctx->appx_ctx->calculatedCDHash);
OPENSSL_free(ctx->appx_ctx->calculatedDataHash);
OPENSSL_free(ctx->appx_ctx->calculatedCIHash);
OPENSSL_free(ctx->appx_ctx->existingBMHash);
OPENSSL_free(ctx->appx_ctx->existingCTHash);
OPENSSL_free(ctx->appx_ctx->existingCDHash);
OPENSSL_free(ctx->appx_ctx->existingDataHash);
OPENSSL_free(ctx->appx_ctx->existingCIHash);
OPENSSL_free(ctx->appx_ctx);
OPENSSL_free(ctx);
}
/*
* APPX helper functions
*/
/*
* Calculate ZIP hashes.
* [in, out] ctx: structure holds input and output data
* [returns] pointer to BIO with calculated APPX hashes
*/
static BIO *appx_calculate_hashes(FILE_FORMAT_CTX *ctx)
{
uint64_t cdOffset = 0;
ctx->appx_ctx->calculatedBMHash = zipCalcDigest(ctx->appx_ctx->zip, BLOCK_MAP_FILENAME, ctx->appx_ctx->md);
ctx->appx_ctx->calculatedCTHash = zipCalcDigest(ctx->appx_ctx->zip, CONTENT_TYPES_FILENAME, ctx->appx_ctx->md);
ctx->appx_ctx->calculatedDataHash = appx_calc_zip_data_hash(&cdOffset, ctx->appx_ctx->zip, ctx->appx_ctx->md);
ctx->appx_ctx->calculatedCDHash = appx_calc_zip_central_directory_hash(ctx->appx_ctx->zip, ctx->appx_ctx->md, cdOffset);
ctx->appx_ctx->calculatedCIHash = zipCalcDigest(ctx->appx_ctx->zip, CODE_INTEGRITY_FILENAME, ctx->appx_ctx->md);
if (!ctx->appx_ctx->calculatedBMHash || !ctx->appx_ctx->calculatedCTHash
|| !ctx->appx_ctx->calculatedCDHash || !ctx->appx_ctx->calculatedDataHash) {
fprintf(stderr, "One or more hashes calculation failed\n");
return NULL; /* FAILED */
}
if (zipEntryExist(ctx->appx_ctx->zip, CODE_INTEGRITY_FILENAME) && !ctx->appx_ctx->calculatedCIHash) {
fprintf(stderr, "Code integrity file exists, but CI hash calculation failed\n");
return NULL; /* FAILED */
}
return appx_hash_blob_get(ctx);
}
/*
* Create hash blob from concatenated APPX hashes.
* [in] ctx: structure holds input and output data
* [returns] pointer to BIO with calculated APPX hashes
*/
static BIO *appx_hash_blob_get(FILE_FORMAT_CTX *ctx)
{
int mdlen = EVP_MD_size(ctx->appx_ctx->md);
int dataSize = ctx->appx_ctx->calculatedCIHash ? 4 + 5 * (mdlen + 4) : 4 + 4 * (mdlen + 4);
u_char *data = OPENSSL_malloc((size_t)dataSize);
int pos = 0;
BIO *hashes = BIO_new(BIO_s_mem());
memcpy(data + pos, APPX_SIGNATURE, 4);
pos += 4;
memcpy(data + pos, AXPC_SIGNATURE, 4);
pos += 4;
memcpy(data + pos, ctx->appx_ctx->calculatedDataHash, (size_t)mdlen);
pos += mdlen;
memcpy(data + pos, AXCD_SIGNATURE, 4);
pos += 4;
memcpy(data + pos, ctx->appx_ctx->calculatedCDHash, (size_t)mdlen);
pos += mdlen;
memcpy(data + pos, AXCT_SIGNATURE, 4);
pos += 4;
memcpy(data + pos, ctx->appx_ctx->calculatedCTHash, (size_t)mdlen);
pos += mdlen;
memcpy(data + pos, AXBM_SIGNATURE, 4);
pos += 4;
memcpy(data + pos, ctx->appx_ctx->calculatedBMHash, (size_t)mdlen);
pos += mdlen;
if (ctx->appx_ctx->calculatedCIHash) {
memcpy(data + pos, AXCI_SIGNATURE, 4);
pos += 4;
memcpy(data + pos, ctx->appx_ctx->calculatedCIHash, (size_t)mdlen);
pos += mdlen;
}
if (ctx->options->verbose) {
print_hash("Hash of file: ", "\n", data, pos);
}
ctx->appx_ctx->hashlen = BIO_write(hashes, data, pos);
OPENSSL_free(data);
return hashes;
}
/*
* Calculate ZIP central directory hash.
* [in] zip: structure holds specific ZIP data
* [in] md: message digest algorithm type
* [in] cdOffset: central directory offset
* [returns] hash
*/
static uint8_t *appx_calc_zip_central_directory_hash(ZIP_FILE *zip, const EVP_MD *md, uint64_t cdOffset)
{
u_char *mdbuf = NULL;
BIO *bhash = BIO_new(BIO_f_md());
if (!BIO_set_md(bhash, md)) {
fprintf(stderr, "Unable to set the message digest of BIO\n");
BIO_free_all(bhash);
return NULL; /* FAILED */
}
BIO_push(bhash, BIO_new(BIO_s_null()));
if (!appx_write_central_directory(bhash, zip, 1, cdOffset)) {
fprintf(stderr, "Unable to write central directory\n");
BIO_free_all(bhash);
return NULL; /* FAILED */
}
mdbuf = OPENSSL_malloc((size_t)EVP_MD_size(md));
BIO_gets(bhash, (char*)mdbuf, EVP_MD_size(md));
BIO_free_all(bhash);
return mdbuf;
}
/*
* Write central directory structure.
* [out] bio: outdata file BIO
* [in] zip: structure holds specific ZIP data
* [in] removeSignature: remove signature switch
* [in] cdOffset: central directory offset
* [returns] 0 on error or 1 on success
*/
static int appx_write_central_directory(BIO *bio, ZIP_FILE *zip, int removeSignature, uint64_t cdOffset)
{
ZIP_CENTRAL_DIRECTORY_ENTRY *entry;
uint64_t offsetDiff = 0, cdSize = 0;
uint16_t noEntries = 0;
for (entry = zip->centralDirectoryHead; entry != NULL; entry = entry->next) {
/* the signature file is considered non existent for hashing purposes */
uint64_t sizeOnDisk = 0;
if (noEntries > zip->centralDirectoryRecordCount) {
fprintf(stderr, "Corrupted central directory structure\n");
return 0; /* FAILED */
}
if (!entry->fileName || (entry->fileNameLen == 0)) {
fprintf(stderr, "Corrupted file name\n");
return 0; /* FAILED */
}
if (removeSignature && !strcmp(entry->fileName, APP_SIGNATURE_FILENAME)) {
continue;
}
/* APP_SIGNATURE is nt 'tainted' by offset shift after replacing the contents of [content_types] */
zipWriteCentralDirectoryEntry(bio, &sizeOnDisk, entry, strcmp(entry->fileName, APP_SIGNATURE_FILENAME) ? offsetDiff : 0);
cdSize += sizeOnDisk;
if (entry->overrideData) {
offsetDiff += entry->overrideData->compressedSize - entry->compressedSize;
}
noEntries++;
}
if (zip->isZip64) {
/* eocdr */
BIO_write(bio, PKZIP64_EOCDR_SIGNATURE, 4);
bioAddU64(bio, zip->eocdr64.eocdrSize);
bioAddU16(bio, zip->eocdr64.creatorVersion);
bioAddU16(bio, zip->eocdr64.viewerVersion);
bioAddU32(bio, zip->eocdr64.diskNumber);
bioAddU32(bio, zip->eocdr64.diskWithCentralDirectory);
bioAddU64(bio, (uint64_t)noEntries);
bioAddU64(bio, (uint64_t)noEntries);
bioAddU64(bio, cdSize);
bioAddU64(bio, cdOffset);
if (zip->eocdr64.commentLen > 0) {
size_t check;
if (!BIO_write_ex(bio, zip->eocdr64.comment, zip->eocdr64.commentLen, &check)
|| check != zip->eocdr64.commentLen) {
return 0; /* FAILED */
}
}
/* eocdr locator */
BIO_write(bio, PKZIP64_EOCD_LOCATOR_SIGNATURE, 4);
bioAddU32(bio, zip->locator.diskWithEOCD);
bioAddU64(bio, cdOffset + cdSize);
bioAddU32(bio, zip->locator.totalNumberOfDisks);
}
BIO_write(bio, PKZIP_EOCDR_SIGNATURE, 4);
/* those need to be 0s even though packaging tool writes FFFFs here
* it will fail verification if not zeros */
bioAddU16(bio, 0);
bioAddU16(bio, 0);
if (zip->eocdr.diskEntries != UINT16_MAX) {
bioAddU16(bio, noEntries);
} else {
bioAddU16(bio, UINT16_MAX);
}
if (zip->eocdr.totalEntries != UINT16_MAX) {
bioAddU16(bio, noEntries);
} else {
bioAddU16(bio, UINT16_MAX);
}
if (zip->eocdr.centralDirectorySize != UINT32_MAX) {
bioAddU32(bio, (uint32_t)cdSize);
} else {
bioAddU32(bio, UINT32_MAX);
}
if (zip->eocdr.centralDirectoryOffset != UINT32_MAX) {
bioAddU32(bio, (uint32_t)cdOffset);
} else {
bioAddU32(bio, UINT32_MAX);
}
bioAddU16(bio, zip->eocdr.commentLen);
if (zip->eocdr.commentLen > 0) {
BIO_write(bio, zip->eocdr.comment, zip->eocdr.commentLen);
}
return 1; /* OK */
}
/*
* Calculate ZIP data hash.
* [out] cdOffset: central directory offset
* [in] zip: structure holds specific ZIP data
* [in] md: message digest algorithm type
* [returns] hash
*/
static uint8_t *appx_calc_zip_data_hash(uint64_t *cdOffset, ZIP_FILE *zip, const EVP_MD *md)
{
ZIP_CENTRAL_DIRECTORY_ENTRY *entry;
u_char *mdbuf = NULL;
BIO *bhash = BIO_new(BIO_f_md());
uint64_t noEntries = 0;
if (!BIO_set_md(bhash, md)) {
fprintf(stderr, "Unable to set the message digest of BIO\n");
BIO_free_all(bhash);
return NULL; /* FAILED */
}
BIO_push(bhash, BIO_new(BIO_s_null()));
*cdOffset = 0;
for (entry = zip->centralDirectoryHead; entry != NULL; entry = entry->next) {
/* the signature file is considered not existent for hashing purposes */
uint64_t sizeOnDisk = 0;
if (noEntries >= zip->centralDirectoryRecordCount) {
fprintf(stderr, "Corrupted central directory structure\n");
BIO_free_all(bhash);
return NULL; /* FAILED */
}
noEntries++;
if (!entry->fileName || (entry->fileNameLen == 0)) {
fprintf(stderr, "Corrupted file name\n");
BIO_free_all(bhash);
return NULL; /* FAILED */
}
if (!strcmp(entry->fileName, APP_SIGNATURE_FILENAME)) {
continue;
}
if (!zipRewriteData(zip, entry, bhash, &sizeOnDisk)) {
fprintf(stderr, "Rewrite data error\n");
BIO_free_all(bhash);
return NULL; /* FAILED */
}
*cdOffset += sizeOnDisk;
}
mdbuf = OPENSSL_malloc((size_t)EVP_MD_size(md));
BIO_gets(bhash, (char*)mdbuf, EVP_MD_size(md));
BIO_free_all(bhash);
return mdbuf;
}
/*
* Extract hashes from SpcIndirectDataContent.
* [in, out] ctx: structure holds input and output data
* [out] content: SpcIndirectDataContent
* [returns] 0 on error or 1 on success
*/
static int appx_extract_hashes(FILE_FORMAT_CTX *ctx, SpcIndirectDataContent *content)
{
#if 0
AppxSpcSipInfo *si = NULL;
const unsigned char *blob = content->data->value->value.sequence->data;
d2i_AppxSpcSipInfo(&si, &blob, content->data->value->value.sequence->length);
long a = ASN1_INTEGER_get(si->a);
long b = ASN1_INTEGER_get(si->b);
long c = ASN1_INTEGER_get(si->c);
long d = ASN1_INTEGER_get(si->d);
long e = ASN1_INTEGER_get(si->e);
long f = ASN1_INTEGER_get(si->f);
BIO *stdbio = BIO_new_fp(stderr, BIO_NOCLOSE);
printf("a: 0x%lX b: 0x%lX c: 0x%lX d: 0x%lX e: 0x%lX f: 0x%lX\n", a, b, c, d, e, f);
printf("string: ");
ASN1_STRING_print_ex(stdbio, si->string, ASN1_STRFLGS_RFC2253);
printf("\n\n");
AppxSpcSipInfo_free(si);
BIO_free_all(stdbio);
#endif
int length = content->messageDigest->digest->length;
uint8_t *data = content->messageDigest->digest->data;
int mdlen = EVP_MD_size(ctx->appx_ctx->md);
int pos = 4;
/* we are expecting at least 4 hashes + 4 byte header */
if (length < 4 * mdlen + 4) {
fprintf(stderr, "Hash too short\n");
return 0; /* FAILED */
}
if (memcmp(data, APPX_SIGNATURE, 4)) {
fprintf(stderr, "Hash signature does not match\n");
return 0; /* FAILED */
}
while (pos + mdlen + 4 <= length) {
if (!memcmp(data + pos, AXPC_SIGNATURE, 4)) {
ctx->appx_ctx->existingDataHash = OPENSSL_malloc((size_t)mdlen);
memcpy(ctx->appx_ctx->existingDataHash, data + pos + 4, (size_t)mdlen);
} else if (!memcmp(data + pos, AXCD_SIGNATURE, 4)) {
ctx->appx_ctx->existingCDHash = OPENSSL_malloc((size_t)mdlen);
memcpy(ctx->appx_ctx->existingCDHash, data + pos + 4, (size_t)mdlen);
} else if (!memcmp(data + pos, AXCT_SIGNATURE, 4)) {
ctx->appx_ctx->existingCTHash = OPENSSL_malloc((size_t)mdlen);
memcpy(ctx->appx_ctx->existingCTHash, data + pos + 4, (size_t)mdlen);
} else if (!memcmp(data + pos, AXBM_SIGNATURE, 4)) {
ctx->appx_ctx->existingBMHash = OPENSSL_malloc((size_t)mdlen);
memcpy(ctx->appx_ctx->existingBMHash, data + pos + 4, (size_t)mdlen);
} else if (!memcmp(data + pos, AXCI_SIGNATURE, 4)) {
ctx->appx_ctx->existingCIHash = OPENSSL_malloc((size_t)mdlen);
memcpy(ctx->appx_ctx->existingCIHash, data + pos + 4, (size_t)mdlen);
} else {
fprintf(stderr, "Invalid hash signature\n");
return 0; /* FAILED */
}
pos += mdlen + 4;
}
if (!ctx->appx_ctx->existingDataHash) {
fprintf(stderr, "File hash missing\n");
return 0; /* FAILED */
}
if (!ctx->appx_ctx->existingCDHash) {
fprintf(stderr, "Central directory hash missing\n");
return 0; /* FAILED */
}
if (!ctx->appx_ctx->existingBMHash) {
fprintf(stderr, "Block map hash missing\n");
return 0; /* FAILED */
}
if (!ctx->appx_ctx->existingCTHash) {
fprintf(stderr, "Content types hash missing\n");
return 0; /* FAILED */
}
if (zipEntryExist(ctx->appx_ctx->zip, CODE_INTEGRITY_FILENAME) && !ctx->appx_ctx->existingCIHash) {
fprintf(stderr, "Code integrity hash missing\n");
return 0; /* FAILED */
}
return 1; /* OK */
}
/*
* Compare extracted and calculated hashes.
* [in, out] ctx: structure holds input and output data
* [returns] 0 on error or 1 on success
*/
static int appx_compare_hashes(FILE_FORMAT_CTX *ctx)
{
int mdtype = EVP_MD_nid(ctx->appx_ctx->md);
if (ctx->appx_ctx->calculatedBMHash && ctx->appx_ctx->existingBMHash) {
printf("Checking Block Map hashes:\n");
if (!compare_digests(ctx->appx_ctx->existingBMHash, ctx->appx_ctx->calculatedBMHash, mdtype)) {
return 0; /* FAILED */
}
} else {
fprintf(stderr, "Block map hash missing\n");
return 0; /* FAILED */
}
if (ctx->appx_ctx->calculatedCTHash && ctx->appx_ctx->existingCTHash) {
printf("Checking Content Types hashes:\n");
if (!compare_digests(ctx->appx_ctx->existingCTHash, ctx->appx_ctx->calculatedCTHash, mdtype)) {
return 0; /* FAILED */
}
} else {
fprintf(stderr, "Content Types hash missing\n");
return 0; /* FAILED */
}
if (ctx->appx_ctx->calculatedDataHash && ctx->appx_ctx->existingDataHash) {
printf("Checking Data hashes:\n");
if (!compare_digests(ctx->appx_ctx->existingDataHash, ctx->appx_ctx->calculatedDataHash, mdtype)) {
return 0; /* FAILED */
}
} else {
fprintf(stderr, "Central Directory hash missing\n");
return 0; /* FAILED */
}
if (ctx->appx_ctx->calculatedCDHash && ctx->appx_ctx->existingCDHash) {
printf("Checking Central Directory hashes:\n");
if (!compare_digests(ctx->appx_ctx->existingCDHash, ctx->appx_ctx->calculatedCDHash, mdtype)) {
return 0; /* FAILED */
}
} else {
fprintf(stderr, "Central Directory hash missing\n");
return 0; /* FAILED */
}
if (ctx->appx_ctx->calculatedCIHash && ctx->appx_ctx->existingCIHash) {
printf("Checking Code Integrity hashes:\n");
if (!compare_digests(ctx->appx_ctx->existingCIHash, ctx->appx_ctx->calculatedCIHash, mdtype)) {
return 0; /* FAILED */
}
} else if (!ctx->appx_ctx->calculatedCIHash && !ctx->appx_ctx->existingCIHash) {
/* this is fine, CI file is optional -> if it is missing we expect both hashes to be non existent */
} else {
fprintf(stderr, "Code Integrity hash missing\n");
return 0; /* FAILED */
}
return 1; /* OK */
}
/*
* Remove signature content types entry.
* [in] zip: structure holds specific ZIP data
* [in, out] entry: central directory structure
* [returns] 0 on error or 1 on success
*/
static int appx_remove_ct_signature_entry(ZIP_FILE *zip, ZIP_CENTRAL_DIRECTORY_ENTRY *entry)
{
uint8_t *data;
const char *cpos;
size_t dataSize, ipos, len;
int ret;
dataSize = zipReadFileData(zip, &data, entry);
if (dataSize <= 0) {
return 0; /* FAILED */
}
cpos = strstr((const char *)data, SIGNATURE_CONTENT_TYPES_ENTRY);
if (!cpos) {
printf("Warning: Did not find existing signature entry in %s\n", entry->fileName);
OPENSSL_free(data);
return 1; /* do not treat as en error */
}
/* *cpos > *data */
ipos = (size_t)(cpos - (char *)data);
len = strlen(SIGNATURE_CONTENT_TYPES_ENTRY);
memmove(data + ipos, data + ipos + len, dataSize - ipos - len);
dataSize -= len;
ret = zipOverrideFileData(entry, data, (uint64_t)dataSize);
OPENSSL_free(data);
return ret;
}
/*
* Append signature content types entry.
* [in] zip: structure holds specific ZIP data
* [in, out] entry: central directory structure
* [returns] 0 on error or 1 on success
*/
static int appx_append_ct_signature_entry(ZIP_FILE *zip, ZIP_CENTRAL_DIRECTORY_ENTRY *entry)
{
uint8_t *data, *newData;
const char *existingEntry, *cpos;
size_t dataSize, newSize, ipos, len;
int ret;
dataSize = zipReadFileData(zip, &data, entry);
if (dataSize <= 0) {
return 0; /* FAILED */
}
existingEntry = strstr((const char *)data, SIGNATURE_CONTENT_TYPES_ENTRY);
if (existingEntry) {
OPENSSL_free(data);
return 1; /* do not append it twice */
}
cpos = strstr((const char *)data, SIGNATURE_CONTENT_TYPES_CLOSING_TAG);
if (!cpos) {
fprintf(stderr, "%s parsing error\n", entry->fileName);
OPENSSL_free(data);
return 0; /* FAILED */
}
ipos = (size_t)(cpos - (char *)data);
len = strlen(SIGNATURE_CONTENT_TYPES_ENTRY);
newSize = dataSize + len;
newData = OPENSSL_malloc(newSize);
memcpy(newData, data, ipos);
memcpy(newData + ipos, SIGNATURE_CONTENT_TYPES_ENTRY, len);
memcpy(newData + ipos + len, data + ipos, dataSize - ipos);
ret = zipOverrideFileData(entry, newData, (uint64_t)newSize);
OPENSSL_free(data);
OPENSSL_free(newData);
return ret;
}
/*
* Get a hash algorithm specified in the AppxBlockMap.xml file.
* [in] zip: structure holds specific ZIP data
* [returns] one of SHA256/SHA384/SHA512 digest algorithms
*/
static const EVP_MD *appx_get_md(ZIP_FILE *zip)
{
uint8_t *data = NULL;
char *start, *end, *pos;
char *valueStart = NULL, *valueEnd = NULL;
const EVP_MD *md = NULL;
size_t slen, dataSize;
dataSize = zipReadFileDataByName(&data, zip, BLOCK_MAP_FILENAME);
if (dataSize <= 0) {
fprintf(stderr, "Could not read: %s\n", BLOCK_MAP_FILENAME);
return NULL; /* FAILED */
}
start = strstr((const char *)data, HASH_METHOD_TAG);
if (!start) {
fprintf(stderr, "Parse error: tag: %s not found in %s\n", HASH_METHOD_TAG, BLOCK_MAP_FILENAME);
OPENSSL_free(data);
return NULL; /* FAILED */
}
start += strlen(HASH_METHOD_TAG);
if ((uint8_t *)start >= data + dataSize) {
fprintf(stderr, "Parse error: data too short in %s\n", BLOCK_MAP_FILENAME);
OPENSSL_free(data);
return NULL; /* FAILED */
}
end = strstr((const char *)start, ">");
if (!end) {
fprintf(stderr, "Parse error: end of tag not found in %s\n", BLOCK_MAP_FILENAME);
OPENSSL_free(data);
return NULL; /* FAILED */
}
for (pos = start; pos != end; pos++) {
if (*pos == '"') {
if (!valueStart) {
valueStart = pos + 1;
} else {
valueEnd = pos - 1;
}
}
}
if (!valueStart || !valueEnd || valueEnd <= valueStart) {
fprintf(stderr, "Parse error: value parse error in %s\n", BLOCK_MAP_FILENAME);
OPENSSL_free(data);
return NULL; /* FAILED */
}
slen = (size_t)(valueEnd - valueStart + 1);
if (strlen(HASH_METHOD_SHA256) == slen && !memcmp(valueStart, HASH_METHOD_SHA256, slen)) {
printf("Hash method is SHA256\n");
md = EVP_sha256();
} else if (strlen(HASH_METHOD_SHA384) == slen && !memcmp(valueStart, HASH_METHOD_SHA384, slen)) {
printf("Hash method is SHA384\n");
md = EVP_sha384();
} else if (strlen(HASH_METHOD_SHA512) == slen && !memcmp(valueStart, HASH_METHOD_SHA512, slen)) {
printf("Hash method is SHA512\n");
md = EVP_sha512();
} else {
fprintf(stderr, "Unsupported hash method\n");
OPENSSL_free(data);
return NULL; /* FAILED */
}
OPENSSL_free(data);
return md;
}
/*
* Get central directory structure entry.
* [in] zip: structure holds specific ZIP data
* [in] name: APPXBUNDLE_MANIFEST_FILENAME or CONTENT_TYPES_FILENAME
* [returns] pointer to central directory structure
*/
static ZIP_CENTRAL_DIRECTORY_ENTRY *zipGetCDEntryByName(ZIP_FILE *zip, const char *name)
{
ZIP_CENTRAL_DIRECTORY_ENTRY *entry;
uint64_t noEntries = 0;
for (entry = zip->centralDirectoryHead; entry != NULL; entry = entry->next) {
if (noEntries >= zip->centralDirectoryRecordCount) {
fprintf(stderr, "Corrupted central directory structure\n");
return NULL; /* FAILED */
}
noEntries++;
if (!entry->fileName || (entry->fileNameLen == 0)) {
fprintf(stderr, "Corrupted file name\n");
return NULL; /* FAILED */
}
if (!strcmp(entry->fileName, name)) {
return entry;
}
}
return NULL; /* FAILED */
}
/*
* Write central directory entry.
* [out] bio: outdata file BIO
* [out] sizeOnDisk: size of central directory structure
* [in] entry: central directory structure
* [in] offsetDiff: central directory offset
* [returns] none
*/
static void zipWriteCentralDirectoryEntry(BIO *bio, uint64_t *sizeOnDisk, ZIP_CENTRAL_DIRECTORY_ENTRY *entry, uint64_t offsetDiff)
{
uint16_t zip64ChunkSize = 0;
BIO_write(bio, PKZIP_CD_SIGNATURE, 4);
bioAddU16(bio, entry->creatorVersion);
bioAddU16(bio, entry->viewerVersion);
bioAddU16(bio, entry->flags);
bioAddU16(bio, entry->compression);
bioAddU16(bio, entry->modTime);
bioAddU16(bio, entry->modDate);
bioAddU32(bio, entry->overrideData ? entry->overrideData->crc32 : entry->crc32);
bioAddU32(bio, entry->compressedSizeInZip64 ? UINT32_MAX : entry->overrideData ? (uint32_t)entry->overrideData->compressedSize : (uint32_t)entry->compressedSize);
bioAddU32(bio, entry->uncompressedSizeInZip64 ? UINT32_MAX : entry->overrideData ? (uint32_t)entry->overrideData->uncompressedSize : (uint32_t)entry->uncompressedSize);
bioAddU16(bio, entry->fileNameLen);
bioAddU16(bio, entry->extraFieldLen);
bioAddU16(bio, entry->fileCommentLen);
bioAddU16(bio, entry->diskNoInZip64 ? UINT16_MAX : (uint16_t)entry->diskNoStart);
bioAddU16(bio, entry->internalAttr);
bioAddU32(bio, entry->externalAttr);
bioAddU32(bio, entry->offsetInZip64 ? UINT32_MAX : (uint32_t)(entry->offsetOfLocalHeader + offsetDiff));
if (entry->fileNameLen > 0 && entry->fileName) {
BIO_write(bio, entry->fileName, entry->fileNameLen);
}
if (entry->uncompressedSizeInZip64) {
zip64ChunkSize += 8;
}
if (entry->compressedSizeInZip64) {
zip64ChunkSize += 8;
}
if (entry->offsetInZip64) {
zip64ChunkSize += 8;
}
if (entry->diskNoInZip64) {
zip64ChunkSize += 4;
}
if (zip64ChunkSize > 0) {
bioAddU16(bio, ZIP64_HEADER);
bioAddU16(bio, zip64ChunkSize);
if (entry->uncompressedSizeInZip64) {
bioAddU64(bio, entry->overrideData ? entry->overrideData->uncompressedSize : entry->uncompressedSize);
}
if (entry->compressedSizeInZip64) {
bioAddU64(bio, entry->overrideData ? entry->overrideData->compressedSize : entry->compressedSize);
}
if (entry->offsetInZip64) {
bioAddU64(bio, entry->offsetOfLocalHeader + offsetDiff);
}
if (entry->diskNoInZip64) {
bioAddU32(bio, entry->diskNoStart);
}
}
#if 0
if (entry->extraFieldLen > 0 && entry->extraField)
{
/* TODO, if override daata, need to rewrite the extra field */
BIO_write(bio, entry->extraField, entry->extraFieldLen);
}
#endif
if (entry->fileCommentLen > 0 && entry->fileComment) {
BIO_write(bio, entry->fileComment, entry->fileCommentLen);
}
*sizeOnDisk = (uint64_t)46 + entry->fileNameLen + entry->extraFieldLen + entry->fileCommentLen;
}
/*
* Append signature file blob to outdata bio.
* [out] bio: outdata file BIO
* [in] zip: structure holds specific ZIP data
* [in] data: pointer to signature file blob
* [in] dataSize: signature file blob length
* [returns] 0 on error or 1 on success
*/
static int zipAppendSignatureFile(BIO *bio, ZIP_FILE *zip, uint8_t *data, uint64_t dataSize)
{
ZIP_CENTRAL_DIRECTORY_ENTRY *entry;
ZIP_LOCAL_HEADER header;
time_t tim;
struct tm *timeinfo;
uint64_t offset, crc, len, pos = 0, dummy = 0, written = 0;
uint64_t size = dataSize, sizeToWrite = dataSize;
uint8_t *dataToWrite = data;
int ret;
memset(&header, 0, sizeof(ZIP_LOCAL_HEADER));
dataToWrite = OPENSSL_malloc(dataSize);
ret = zipDeflate(dataToWrite, &size, data, dataSize);
if (ret != Z_OK) {
fprintf(stderr, "Zip deflate failed: %d\n", ret);
OPENSSL_free(dataToWrite);
return 0; /* FAILED */
}
sizeToWrite = size;
time(&tim);
timeinfo = localtime(&tim);
header.version = 0x14;
header.flags = 0;
header.compression = COMPRESSION_DEFLATE;
header.modTime = (uint16_t)(timeinfo->tm_hour << 11 | \
timeinfo->tm_min << 5 | \
timeinfo->tm_sec >> 1);
header.modDate = (uint16_t)((timeinfo->tm_year - 80) << 9 | \
(timeinfo->tm_mon + 1) << 5 | \
timeinfo->tm_mday);
size = dataSize;
crc = crc32(0L, Z_NULL, 0);
while (size > 0) {
len = MIN(size, UINT32_MAX);
crc = crc32(crc, data + pos, (uint32_t)len);
pos += len;
size -= len;
}
header.crc32 = (uint32_t)crc;
header.uncompressedSize = dataSize;
header.compressedSize = sizeToWrite;
header.fileNameLen = (uint16_t)strlen(APP_SIGNATURE_FILENAME);
/* this will be reassigned to CD entry and freed there */
header.fileName = OPENSSL_zalloc(header.fileNameLen + 1);
memcpy(header.fileName, APP_SIGNATURE_FILENAME, header.fileNameLen);
header.extraField = NULL;
header.extraFieldLen = 0;
if (!get_current_position(bio, &offset)) {
fprintf(stderr, "Unable to get offset\n");
OPENSSL_free(dataToWrite);
return 0; /* FAILED */
}
zipWriteLocalHeader(bio, &dummy, &header);
while (sizeToWrite > 0) {
uint64_t toWrite = sizeToWrite < SIZE_64K ? sizeToWrite : SIZE_64K;
size_t check;
if (!BIO_write_ex(bio, dataToWrite + written, toWrite, &check)
|| check != toWrite) {
OPENSSL_free(dataToWrite);
return 0; /* FAILED */
}
sizeToWrite -= toWrite;
written += toWrite;
}
OPENSSL_free(dataToWrite);
entry = OPENSSL_zalloc(sizeof(ZIP_CENTRAL_DIRECTORY_ENTRY));
entry->creatorVersion = 0x2D;
entry->viewerVersion = header.version;
entry->flags = header.flags;
entry->compression = header.compression;
entry->modTime = header.modTime;
entry->modDate = header.modDate;
entry->crc32 = header.crc32;
entry->uncompressedSize = header.uncompressedSize;
entry->compressedSize = header.compressedSize;
/* take ownership of the fileName pointer */
entry->fileName = header.fileName;
entry->fileNameLen = header.fileNameLen;
entry->extraField = header.extraField;
entry->extraFieldLen = header.extraFieldLen;
entry->fileCommentLen = 0;
entry->fileComment = NULL;
entry->diskNoStart = 0;
entry->offsetOfLocalHeader = offset;
entry->next = NULL;
entry->entryLen = entry->fileNameLen + entry->extraFieldLen + entry->fileCommentLen + 46;
if (!zip->centralDirectoryHead) {
zip->centralDirectoryHead = entry;
} else {
ZIP_CENTRAL_DIRECTORY_ENTRY *last = zip->centralDirectoryHead;
while (last->next) {
last = last->next;
}
last->next = entry;
}
return 1; /* OK */
}
/*
* Override file data.
* [out] entry: central directory structure
* [in] data: pointer to data
* [in] dataSize: data size
* [returns] 0 on error or 1 on success
*/
static int zipOverrideFileData(ZIP_CENTRAL_DIRECTORY_ENTRY *entry, uint8_t *data, uint64_t dataSize)
{
uint64_t crc, len, pos = 0, size = dataSize;
int ret;
if (entry->overrideData) {
OPENSSL_free(entry->overrideData->data);
OPENSSL_free(entry->overrideData);
entry->overrideData = NULL;
}
entry->overrideData = OPENSSL_malloc(sizeof(ZIP_OVERRIDE_DATA));
entry->overrideData->data = OPENSSL_malloc(dataSize);
crc = crc32(0L, Z_NULL, 0);
while (size > 0) {
len = MIN(size, UINT32_MAX);
crc = crc32(crc, data + pos, (uint32_t)len);
pos += len;
size -= len;
}
entry->overrideData->crc32 = (uint32_t)crc;
entry->overrideData->uncompressedSize = dataSize;
size = dataSize;
ret = zipDeflate(entry->overrideData->data, &size, data, dataSize);
if (ret != Z_OK) {
fprintf(stderr, "Zip deflate failed: %d\n", ret);
return 0; /* FAILED */
}
entry->overrideData->compressedSize = size;
return 1; /* OK */
}
/*
* Rewrite data to outdata bio.
* [in, out] zip: structure holds specific ZIP data
* [out] entry: central directory structure
* [out] bio: outdata file BIO
* [out] sizeOnDisk: outdata size
* [returns] 0 on error or 1 on success
*/
static int zipRewriteData(ZIP_FILE *zip, ZIP_CENTRAL_DIRECTORY_ENTRY *entry, BIO *bio, uint64_t *sizeOnDisk)
{
size_t check;
ZIP_LOCAL_HEADER header;
memset(&header, 0, sizeof(header));
if (entry->offsetOfLocalHeader >= (uint64_t)zip->fileSize) {
fprintf(stderr, "Corrupted relative offset of local header : 0x%08" PRIX64 "\n", entry->offsetOfLocalHeader);
return 0; /* FAILED */
}
if (fseeko(zip->file, (int64_t)entry->offsetOfLocalHeader, SEEK_SET) < 0) {
return 0; /* FAILED */
}
if (!zipReadLocalHeader(&header, zip, entry->compressedSize)) {
return 0; /* FAILED */
}
if (entry->overrideData) {
header.compressedSize = entry->overrideData->compressedSize;
header.uncompressedSize = entry->overrideData->uncompressedSize;
header.crc32 = entry->overrideData->crc32;
}
zipWriteLocalHeader(bio, sizeOnDisk, &header);
if (entry->overrideData) {
if (!BIO_write_ex(bio, entry->overrideData->data, entry->overrideData->compressedSize, &check)
|| check != entry->overrideData->compressedSize) {
return 0; /* FAILED */
}
if (entry->compressedSize > (uint64_t)zip->fileSize - entry->offsetOfLocalHeader) {
fprintf(stderr, "Corrupted compressedSize : 0x%08" PRIX64 "\n", entry->compressedSize);
return 0; /* FAILED */
}
if (fseeko(zip->file, (int64_t)entry->compressedSize, SEEK_CUR) < 0) {
return 0; /* FAILED */
}
*sizeOnDisk += entry->overrideData->compressedSize;
} else {
uint64_t len = entry->compressedSize;
uint8_t *data = OPENSSL_malloc(SIZE_64K);
while (len > 0) {
uint64_t toWrite = len < SIZE_64K ? len : SIZE_64K;
size_t size = fread(data, 1, toWrite, zip->file);
if (size != toWrite) {
OPENSSL_free(data);
return 0; /* FAILED */
}
if (!BIO_write_ex(bio, data, toWrite, &check)
|| check != toWrite) {
OPENSSL_free(data);
return 0; /* FAILED */
}
*sizeOnDisk += toWrite;
len -= toWrite;
}
OPENSSL_free(data);
}
if (header.flags & DATA_DESCRIPTOR_BIT) {
BIO_write(bio, PKZIP_DATA_DESCRIPTOR_SIGNATURE, 4);
bioAddU32(bio, header.crc32);
if (zip->isZip64) {
bioAddU64(bio, header.compressedSize);
bioAddU64(bio, header.uncompressedSize);
} else {
bioAddU32(bio, (uint32_t)header.compressedSize);
bioAddU32(bio, (uint32_t)header.uncompressedSize);
}
if (zip->isZip64) {
if (fseeko(zip->file, 24, SEEK_CUR) < 0) {
return 0; /* FAILED */
}
*sizeOnDisk += 24;
} else {
if (fseeko(zip->file, 16, SEEK_CUR) < 0) {
return 0; /* FAILED */
}
*sizeOnDisk += 16;
}
}
OPENSSL_free(header.fileName);
OPENSSL_free(header.extraField);
return 1; /* OK */
}
/*
* Write local file header to outdata bio.
* [out] bio: outdata file BIO
* [out] sizeonDisk: data size
* [in] header: local file header structure
* [returns] none
*/
static void zipWriteLocalHeader(BIO *bio, uint64_t *sizeonDisk, ZIP_LOCAL_HEADER *header)
{
BIO_write(bio, PKZIP_LH_SIGNATURE, 4);
bioAddU16(bio, header->version);
bioAddU16(bio, header->flags);
bioAddU16(bio, header->compression);
bioAddU16(bio, header->modTime);
bioAddU16(bio, header->modDate);
if (header->flags & DATA_DESCRIPTOR_BIT) {
bioAddU32(bio, 0);
bioAddU32(bio, 0);
bioAddU32(bio, 0);
} else {
bioAddU32(bio, header->crc32);
bioAddU32(bio, header->compressedSizeInZip64 ? UINT32_MAX : (uint32_t)header->compressedSize);
bioAddU32(bio, header->uncompressedSizeInZip64 ? UINT32_MAX : (uint32_t)header->uncompressedSize);
}
bioAddU16(bio, header->fileNameLen);
bioAddU16(bio, header->extraFieldLen);
if (header->fileNameLen > 0) {
BIO_write(bio, header->fileName, header->fileNameLen);
}
if (header->extraFieldLen > 0) {
BIO_write(bio, header->extraField, header->extraFieldLen);
}
*sizeonDisk = (uint64_t)30 + header->fileNameLen + header->extraFieldLen;
}
/*
* Check if a given ZIP file exists.
* [in] zip: structure holds specific ZIP data
* [in] name: APP_SIGNATURE_FILENAME or CODE_INTEGRITY_FILENAME
* [returns] 0 on error or 1 on success
*/
static int zipEntryExist(ZIP_FILE *zip, const char *name)
{
ZIP_CENTRAL_DIRECTORY_ENTRY *entry;
uint64_t noEntries = 0;
for (entry = zip->centralDirectoryHead; entry != NULL; entry = entry->next) {
if (noEntries >= zip->centralDirectoryRecordCount) {
fprintf(stderr, "Corrupted central directory structure\n");
return 0; /* FAILED */
}
noEntries++;
if (!entry->fileName || (entry->fileNameLen == 0)) {
fprintf(stderr, "Corrupted file name\n");
return 0; /* FAILED */
}
if (!strcmp(entry->fileName, name)) {
return 1; /* OK */
}
}
return 0; /* FAILED */
}
/*
* Calculate ZIP container file hash.
* [in] zip: structure holds specific ZIP data
* [in] fileName: one of ZIP container file
* [in] md: message digest algorithm type
* [returns] hash
*/
static u_char *zipCalcDigest(ZIP_FILE *zip, const char *fileName, const EVP_MD *md)
{
uint8_t *data = NULL;
size_t dataSize;
u_char *mdbuf = NULL;
BIO *bhash;
dataSize = zipReadFileDataByName(&data, zip, fileName);
if (dataSize <= 0) {
return NULL; /* FAILED */
}
bhash = BIO_new(BIO_f_md());
if (!BIO_set_md(bhash, md)) {
fprintf(stderr, "Unable to set the message digest of BIO\n");
OPENSSL_free(data);
BIO_free_all(bhash);
return NULL; /* FAILED */
}
BIO_push(bhash, BIO_new(BIO_s_null()));
if (!bio_hash_data(bhash, (char *)data, 0, dataSize)) {
OPENSSL_free(data);
BIO_free_all(bhash);
return NULL; /* FAILED */
}
mdbuf = OPENSSL_malloc((size_t)EVP_MD_size(md));
BIO_gets(bhash, (char*)mdbuf, EVP_MD_size(md));
OPENSSL_free(data);
BIO_free_all(bhash);
return mdbuf;
}
/*
* Read file data by name.
* [out] pData: pointer to data
* [in] zip: structure holds specific ZIP data
* [in] name: one of ZIP container file
* [returns] 0 on error or data size on success
*/
static size_t zipReadFileDataByName(uint8_t **pData, ZIP_FILE *zip, const char *name)
{
ZIP_CENTRAL_DIRECTORY_ENTRY *entry;
uint64_t noEntries = 0;
for (entry = zip->centralDirectoryHead; entry != NULL; entry = entry->next) {
if (noEntries >= zip->centralDirectoryRecordCount) {
fprintf(stderr, "Corrupted central directory structure\n");
return 0; /* FAILED */
}
noEntries++;
if (!entry->fileName || (entry->fileNameLen == 0)) {
fprintf(stderr, "Corrupted file name\n");
return 0; /* FAILED */
}
if (!strcmp(entry->fileName, name)) {
return zipReadFileData(zip, pData, entry);
}
}
return 0; /* FAILED */
}
/*
* Read file data.
* [in, out] zip: structure holds specific ZIP data
* [out] pData: pointer to data
* [in] entry: central directory structure
* [returns] 0 on error or data size on success
*/
static size_t zipReadFileData(ZIP_FILE *zip, uint8_t **pData, ZIP_CENTRAL_DIRECTORY_ENTRY *entry)
{
FILE *file = zip->file;
uint8_t *compressedData = NULL;
uint64_t compressedSize = 0;
uint64_t uncompressedSize = 0;
size_t size, dataSize = 0;
if (entry->offsetOfLocalHeader >= (uint64_t)zip->fileSize) {
fprintf(stderr, "Corrupted relative offset of local header : 0x%08" PRIX64 "\n", entry->offsetOfLocalHeader);
return 0; /* FAILED */
}
if (fseeko(file, (int64_t)entry->offsetOfLocalHeader, SEEK_SET) < 0) {
return 0; /* FAILED */
}
if (entry->overrideData) {
compressedSize = entry->overrideData->compressedSize;
uncompressedSize = entry->overrideData->uncompressedSize;
compressedData = OPENSSL_zalloc(compressedSize + 1);
memcpy(compressedData, entry->overrideData->data, compressedSize);
} else {
ZIP_LOCAL_HEADER header;
compressedSize = entry->compressedSize;
uncompressedSize = entry->uncompressedSize;
memset(&header, 0, sizeof(header));
if (!zipReadLocalHeader(&header, zip, compressedSize)) {
return 0; /* FAILED */
}
if (header.fileNameLen != entry->fileNameLen
|| memcmp(header.fileName, entry->fileName, header.fileNameLen)
|| header.compressedSize != compressedSize
|| header.uncompressedSize != uncompressedSize
|| header.compression != entry->compression) {
fprintf(stderr, "Local header does not match central directory entry\n");
return 0; /* FAILED */
}
/* we don't really need those */
OPENSSL_free(header.fileName);
OPENSSL_free(header.extraField);
if (compressedSize > (uint64_t)zip->fileSize - entry->offsetOfLocalHeader) {
fprintf(stderr, "Corrupted compressedSize : 0x%08" PRIX64 "\n", entry->compressedSize);
return 0; /* FAILED */
}
compressedData = OPENSSL_zalloc(compressedSize + 1);
size = fread(compressedData, 1, compressedSize, file);
if (size != compressedSize) {
OPENSSL_free(compressedData);
return 0; /* FAILED */
}
compressedData[compressedSize] = 0;
}
if (entry->compression == COMPRESSION_NONE) {
if (compressedSize == 0) {
OPENSSL_free(compressedData);
return 0; /* FAILED */
}
*pData = compressedData;
dataSize = compressedSize;
} else if (entry->compression == COMPRESSION_DEFLATE) {
uint8_t *uncompressedData = OPENSSL_zalloc(uncompressedSize + 1);
uint64_t destLen = uncompressedSize;
uint64_t sourceLen = compressedSize;
int ret;
ret = zipInflate(uncompressedData, &destLen, compressedData, (uLong *)&sourceLen);
OPENSSL_free(compressedData);
if (ret != Z_OK) {
fprintf(stderr, "Data decompresssion failed, zlib error: %d\n", ret);
OPENSSL_free(uncompressedData);
return 0; /* FAILED */
} else {
if (destLen == 0) {
OPENSSL_free(uncompressedData);
return 0; /* FAILED */
}
*pData = uncompressedData;
dataSize = destLen;
}
} else {
fprintf(stderr, "Unsupported compression mode: %d\n", entry->compression);
OPENSSL_free(compressedData);
return 0; /* FAILED */
}
return dataSize;
}
/*
* Read local file header from a ZIP file.
* [out] header: local file header
* [in, out] zip: structure holds specific ZIP data
* [in] compressedSize: compressed size
* [returns] 0 on error or 1 on success
*/
static int zipReadLocalHeader(ZIP_LOCAL_HEADER *header, ZIP_FILE *zip, uint64_t compressedSize)
{
char signature[4];
size_t size;
FILE *file = zip->file;
size = fread(signature, 1, 4, file);
if (size != 4) {
return 0; /* FAILED */
}
if (memcmp(signature, PKZIP_LH_SIGNATURE, 4)) {
fprintf(stderr, "The input file is not a valid zip file - local header signature does not match\n");
return 0; /* FAILED */
}
/* version needed to extract (2 bytes) */
header->version = fileGetU16(file);
/* general purpose bit flag (2 bytes) */
header->flags = fileGetU16(file);
/* compression method (2 bytes) */
header->compression = fileGetU16(file);
/* last mod file time (2 bytes) */
header->modTime = fileGetU16(file);
/* last mod file date (2 bytes) */
header->modDate = fileGetU16(file);
/* crc-32 (4 bytes) */
header->crc32 = fileGetU32(file);
/* compressed size (4 bytes) */
header->compressedSize = fileGetU32(file);
/* uncompressed size (4 bytes) */
header->uncompressedSize = fileGetU32(file);
/* file name length (2 bytes) */
header->fileNameLen = fileGetU16(file);
/* extra file name length (2 bytes) */
header->extraFieldLen = fileGetU16(file);
/* file name (variable size) */
if (header->fileNameLen > 0) {
header->fileName = OPENSSL_zalloc(header->fileNameLen + 1);
size = fread(header->fileName, 1, header->fileNameLen, file);
if (size != header->fileNameLen) {
return 0; /* FAILED */
}
header->fileName[header->fileNameLen] = 0;
} else {
header->fileName = NULL;
}
/* extra field (variable size) */
if (header->extraFieldLen > 0) {
header->extraField = OPENSSL_zalloc(header->extraFieldLen + 1);
size = fread(header->extraField, 1, header->extraFieldLen, file);
if (size != header->extraFieldLen) {
return 0; /* FAILED */
}
header->extraField[header->extraFieldLen] = 0;
} else {
header->extraField = NULL;
}
if (header->flags & DATA_DESCRIPTOR_BIT) {
/* Read data descriptor */
int64_t offset = ftello(file);
if (offset < 0 || offset >= zip->fileSize) {
return 0; /* FAILED */
}
if (compressedSize > (uint64_t)(zip->fileSize - offset)) {
fprintf(stderr, "Corrupted compressedSize : 0x%08" PRIX64 "\n", compressedSize);
return 0; /* FAILED */
}
if (fseeko(file, (int64_t)compressedSize, SEEK_CUR) < 0) {
return 0; /* FAILED */
}
size = fread(signature, 1, 4, file);
if (size != 4) {
return 0; /* FAILED */
}
if (memcmp(signature, PKZIP_DATA_DESCRIPTOR_SIGNATURE, 4)) {
fprintf(stderr, "The input file is not a valid zip file - flags indicate data descriptor, but data descriptor signature does not match\n");
OPENSSL_free(header->fileName);
OPENSSL_free(header->extraField);
return 0; /* FAILED */
}
header->crc32 = fileGetU32(file);
if (zip->isZip64) {
header->compressedSize = fileGetU64(file);
header->uncompressedSize = fileGetU64(file);
} else {
header->compressedSize = fileGetU32(file);
header->uncompressedSize = fileGetU32(file);
}
if (fseeko(file, offset, SEEK_SET) < 0) {
return 0; /* FAILED */
}
}
if (header->uncompressedSize == UINT32_MAX || header->compressedSize == UINT32_MAX) {
if (header->extraFieldLen > 4) {
uint64_t pos = 0;
uint16_t len;
uint16_t op = bufferGetU16(header->extraField, &pos);
if (op != ZIP64_HEADER) {
fprintf(stderr, "Expected zip64 header in local header extra field, got : 0x%X\n", op);
OPENSSL_free(header->fileName);
OPENSSL_free(header->extraField);
header->fileName = NULL;
header->extraField = NULL;
return 0; /* FAILED */
}
len = bufferGetU16(header->extraField, &pos);
if (header->uncompressedSize == UINT32_MAX) {
if (len >= 8) {
header->uncompressedSize = bufferGetU64(header->extraField, &pos);
header->uncompressedSizeInZip64 = 1;
} else {
fprintf(stderr, "Invalid zip64 local header entry\n");
OPENSSL_free(header->fileName);
OPENSSL_free(header->extraField);
header->fileName = NULL;
header->extraField = NULL;
return 0; /* FAILED */
}
}
if (header->compressedSize == UINT32_MAX) {
if (len >= 16) {
header->compressedSize = bufferGetU64(header->extraField, &pos);
header->compressedSizeInZip64 = 1;
} else {
fprintf(stderr, "Invalid zip64 local header entry\n");
OPENSSL_free(header->fileName);
OPENSSL_free(header->extraField);
header->fileName = NULL;
header->extraField = NULL;
return 0; /* FAILED */
}
}
} else {
OPENSSL_free(header->fileName);
OPENSSL_free(header->extraField);
header->fileName = NULL;
header->extraField = NULL;
return 0; /* FAILED */
}
}
return 1; /* OK */
}
/*
* Decompresses the source buffer into the destination buffer.
* see: uncompress2(), but windowBits is set to 15 for raw inflate
* https://github.com/madler/zlib/blob/09155eaa2f9270dc4ed1fa13e2b4b2613e6e4851/uncompr.c#L27
* [out] dest: destination buffer
* [out] destLen: size of the decompressed data
* [in] source: source buffer
* [in] sourceLen: length of the source buffer
* [returns] returns ZIP error or Z_OK if success
*/
static int zipInflate(uint8_t *dest, uint64_t *destLen, uint8_t *source, uLong *sourceLen)
{
z_stream stream;
int err;
const uInt max = (uInt)-1; /* 0xFFFFFFFF */
uLong len, left;
/* for detection of incomplete stream when *destLen == 0 */
static u_char buf[] = { 0x00 };
/* reset stream */
memset(&stream, 0, sizeof stream);
len = *sourceLen;
if (*destLen) {
left = *destLen;
*destLen = 0;
} else {
left = 1;
dest = buf;
}
stream.next_in = source;
stream.avail_in = 0;
stream.zalloc = (alloc_func)0;
stream.zfree = (free_func)0;
stream.opaque = (voidpf)0;
err = inflateInit2(&stream, -MAX_WBITS);
if (err != Z_OK) {
return err;
}
stream.next_out = dest;
stream.avail_out = 0;
do {
if (stream.avail_out == 0) {
stream.avail_out = left > (uLong)max ? max : (uInt)left;
left -= stream.avail_out;
}
if (stream.avail_in == 0) {
stream.avail_in = len > (uLong)max ? max : (uInt)len;
len -= stream.avail_in;
}
/* coverity[overrun-buffer-arg] max value 0xFFFFFFFF is intended */
err = inflate(&stream, Z_NO_FLUSH);
} while (err == Z_OK);
*sourceLen -= len + stream.avail_in;
if (dest != buf) {
*destLen = stream.total_out;
} else if (stream.total_out && err == Z_BUF_ERROR) {
left = 1;
}
inflateEnd(&stream);
return err == Z_STREAM_END ? Z_OK :
err == Z_NEED_DICT ? Z_DATA_ERROR :
err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR :
err;
}
/*
* Compresses the source buffer into the destination buffer.
* see: compress2(), but windowBits is set to -15 for raw deflate
* https://github.com/madler/zlib/blob/09155eaa2f9270dc4ed1fa13e2b4b2613e6e4851/compress.c#L22
* [out] dest: destination buffer
* [out] destLen: actual size of the compressed buffer
* [in] source: source buffer
* [in] sourceLen: length of the source buffer
* [in] level: deflateInit2 parameter (8)
* [returns] returns ZIP error or Z_OK if success
*/
static int zipDeflate(uint8_t *dest, uint64_t *destLen, uint8_t *source, uLong sourceLen)
{
z_stream stream;
int err;
const uInt max = (uInt)-1; /* 0xFFFFFFFF */
uLong left;
/* reset stream */
memset(&stream, 0, sizeof stream);
left = *destLen;
*destLen = 0;
stream.zalloc = (alloc_func)0;
stream.zfree = (free_func)0;
stream.opaque = (voidpf)0;
err = deflateInit2(&stream, 8, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY);
if (err != Z_OK) {
return err;
}
stream.next_out = dest;
stream.avail_out = 0;
stream.next_in = source;
stream.avail_in = 0;
do {
if (stream.avail_out == 0) {
stream.avail_out = left > (uLong)max ? max : (uInt)left;
left -= stream.avail_out;
}
if (stream.avail_in == 0) {
stream.avail_in = sourceLen > (uLong)max ? max : (uInt)sourceLen;
sourceLen -= stream.avail_in;
}
/* coverity[overrun-buffer-arg] max value 0xFFFFFFFF is intended */
err = deflate(&stream, sourceLen ? Z_NO_FLUSH : Z_FINISH);
} while (err == Z_OK);
#if 0
deflate(&stream, Z_SYNC_FLUSH);
#endif
*destLen = stream.total_out;
deflateEnd(&stream);
return err == Z_STREAM_END ? Z_OK : err;
}
/*
* Open input file and create ZIP_FILE structure.
* [in] filename: input file
* [returns] pointer to ZIP_FILE structure
*/
static ZIP_FILE *openZip(const char *filename)
{
ZIP_FILE *zip;
FILE *file = fopen(filename, "rb");
if (!file) {
return NULL; /* FAILED */
}
/* oncde we read eocdr, comment might be allocated and we need to take care of it
-> create the ZIP_FILE structure */
zip = OPENSSL_zalloc(sizeof(ZIP_FILE));
zip->file = file;
if (!readZipEOCDR(&zip->eocdr, file)) {
freeZip(zip);
return NULL; /* FAILED */
}
if (fseeko(file, 0, SEEK_END) < 0) {
freeZip(zip);
return NULL; /* FAILED */
}
zip->fileSize = ftello(file);
if (zip->fileSize < 0) {
freeZip(zip);
return NULL; /* FAILED */
}
if (zip->eocdr.centralDirectoryOffset == UINT32_MAX || zip->eocdr.centralDirectorySize == UINT32_MAX) {
/* probably a zip64 file */
if (!readZip64EOCDLocator(&zip->locator, file)) {
freeZip(zip);
return NULL; /* FAILED */
}
if (zip->locator.eocdOffset >= (uint64_t)zip->fileSize) {
fprintf(stderr, "Corrupted end of central directory locator offset : 0x%08" PRIX64 "\n", zip->locator.eocdOffset);
freeZip(zip);
return 0; /* FAILED */
}
if (!readZip64EOCDR(&zip->eocdr64, file, zip->locator.eocdOffset)) {
freeZip(zip);
return NULL; /* FAILED */
}
zip->isZip64 = 1;
zip->eocdrOffset = zip->locator.eocdOffset;
zip->eocdrLen = zip->fileSize - (int64_t)zip->eocdrOffset;
if (zip->eocdrLen < 0) {
freeZip(zip);
return NULL; /* FAILED */
}
zip->centralDirectoryOffset = zip->eocdr64.centralDirectoryOffset;
zip->centralDirectorySize = zip->eocdr64.centralDirectorySize;
zip->centralDirectoryRecordCount = zip->eocdr64.totalEntries;
} else {
if (zip->fileSize < EOCDR_SIZE) {
freeZip(zip);
return NULL; /* FAILED */
}
zip->eocdrOffset = (uint64_t)zip->fileSize - EOCDR_SIZE;
zip->eocdrLen = EOCDR_SIZE;
zip->centralDirectoryOffset = zip->eocdr.centralDirectoryOffset;
zip->centralDirectorySize = zip->eocdr.centralDirectorySize;
zip->centralDirectoryRecordCount = (uint64_t)zip->eocdr.totalEntries;
if (zip->centralDirectoryRecordCount > UINT16_MAX) {
fprintf(stderr, "Corrupted total number of entries in the central directory : 0x%08" PRIX64 "\n", zip->centralDirectoryRecordCount);
freeZip(zip);
return NULL; /* FAILED */
}
}
if (zip->centralDirectoryOffset >= (uint64_t)zip->fileSize) {
fprintf(stderr, "Corrupted central directory offset : 0x%08" PRIX64 "\n", zip->centralDirectoryOffset);
freeZip(zip);
return NULL; /* FAILED */
}
if (!zipReadCentralDirectory(zip, file)) {
freeZip(zip);
return NULL; /* FAILED */
}
return zipSortCentralDirectory(zip);
}
/*
* Free up ZIP_FILE structure.
* [in] ZIP_FILE structure
* [returns] none
*/
static void freeZip(ZIP_FILE *zip)
{
ZIP_CENTRAL_DIRECTORY_ENTRY *entry, *next = NULL;
uint64_t noEntries = 0;
fclose(zip->file);
OPENSSL_free(zip->eocdr.comment);
OPENSSL_free(zip->eocdr64.comment);
for (entry = zip->centralDirectoryHead; entry != NULL; entry = next) {
if (noEntries > zip->centralDirectoryRecordCount) {
printf("Warning: Corrupted central directory structure\n");
freeZipCentralDirectoryEntry(entry);
return;
}
noEntries++;
next = entry->next;
freeZipCentralDirectoryEntry(entry);
}
OPENSSL_free(zip);
}
/*
* Offset comparison function.
* [in] a_ptr, b_ptr: pointers to ZIP_CENTRAL_DIRECTORY_ENTRY structure
* [returns] entries order
*/
static int entry_compare(const ZIP_CENTRAL_DIRECTORY_ENTRY *const *a, const ZIP_CENTRAL_DIRECTORY_ENTRY *const *b)
{
return (*a)->offsetOfLocalHeader < (*b)->offsetOfLocalHeader ? -1 : 1;
}
/*
* Sort central directory entries in ascending order by offset.
* [in] zip: ZIP_FILE structure
* [returns] pointer to sorted ZIP_FILE structure
*/
static ZIP_FILE *zipSortCentralDirectory(ZIP_FILE *zip)
{
uint64_t noEntries = 0;
int i;
ZIP_CENTRAL_DIRECTORY_ENTRY *entry;
STACK_OF(ZIP_CENTRAL_DIRECTORY_ENTRY) *chain = sk_ZIP_CENTRAL_DIRECTORY_ENTRY_new(entry_compare);
for (entry = zip->centralDirectoryHead; entry != NULL; entry = entry->next) {
if (noEntries >= zip->centralDirectoryRecordCount) {
fprintf(stderr, "Corrupted central directory structure\n");
sk_ZIP_CENTRAL_DIRECTORY_ENTRY_free(chain);
freeZip(zip);
return NULL; /* FAILED */
}
noEntries++;
if (!sk_ZIP_CENTRAL_DIRECTORY_ENTRY_push(chain, entry)) {
fprintf(stderr, "Failed to add central directory entry\n");
sk_ZIP_CENTRAL_DIRECTORY_ENTRY_free(chain);
freeZip(zip);
return NULL; /* FAILED */
}
}
sk_ZIP_CENTRAL_DIRECTORY_ENTRY_sort(chain);
zip->centralDirectoryHead = entry = sk_ZIP_CENTRAL_DIRECTORY_ENTRY_value(chain, 0);
if (!entry) {
fprintf(stderr, "Failed to get sorted central directory entry\n");
sk_ZIP_CENTRAL_DIRECTORY_ENTRY_free(chain);
freeZip(zip);
return NULL; /* FAILED */
}
for (i=1; i<sk_ZIP_CENTRAL_DIRECTORY_ENTRY_num(chain); i++) {
entry->next = sk_ZIP_CENTRAL_DIRECTORY_ENTRY_value(chain, i);
entry = entry->next;
}
entry->next = NULL;
sk_ZIP_CENTRAL_DIRECTORY_ENTRY_free(chain);
return zip;
}
/*
* Log additional output.
* [in] ZIP_FILE structure
* [returns] none
*/
static void zipPrintCentralDirectory(ZIP_FILE *zip)
{
ZIP_CENTRAL_DIRECTORY_ENTRY *entry;
uint64_t noEntries = 0;
printf("Central directory entry count: %" PRIu64"\n", zip->centralDirectoryRecordCount);
for (entry = zip->centralDirectoryHead; entry != NULL; entry = entry->next) {
if (noEntries >= zip->centralDirectoryRecordCount) {
printf("Warning: Corrupted central directory structure\n");
}
noEntries++;
printf("Name: %s Compressed: %" PRIu64" Uncompressed: %" PRIu64" Offset: %" PRIu64"\n", entry->fileName,
entry->compressedSize, entry->uncompressedSize, entry->offsetOfLocalHeader);
}
}
/*
* Read central directory.
* [in, out] zip: structure holds specific ZIP data
* [in, out] file: FILE pointer to the input file
* [returns] 0 on error or 1 on success
*/
static int zipReadCentralDirectory(ZIP_FILE *zip, FILE *file)
{
ZIP_CENTRAL_DIRECTORY_ENTRY *prev = NULL;
uint64_t i;
if (fseeko(file, (int64_t)zip->centralDirectoryOffset, SEEK_SET) < 0) {
return 0; /* FAILED */
}
for (i = 0; i < zip->centralDirectoryRecordCount; i++) {
ZIP_CENTRAL_DIRECTORY_ENTRY *entry = zipReadNextCentralDirectoryEntry(file);
if (!entry) {
return 0; /* FAILED */
}
if (prev) {
prev->next = entry;
} else if (!zip->centralDirectoryHead) {
zip->centralDirectoryHead = entry;
} else {
fprintf(stderr, "Corrupted central directory structure\n");
OPENSSL_free(entry);
return 0; /* FAILED */
}
prev = entry;
}
return 1; /* OK */
}
/*
* Initialize central directory structure.
* [in] file: FILE pointer to the input file
* [returns] pointer to the central directory structure
*/
static ZIP_CENTRAL_DIRECTORY_ENTRY *zipReadNextCentralDirectoryEntry(FILE *file)
{
ZIP_CENTRAL_DIRECTORY_ENTRY *entry;
char signature[4];
size_t size = fread(signature, 1, 4, file);
if (size != 4) {
return NULL; /* FAILED */
}
if (memcmp(signature, PKZIP_CD_SIGNATURE, 4)) {
fprintf(stderr, "The input file is not a valid zip file - could not find Central Directory record\n");
return NULL; /* FAILED */
}
entry = OPENSSL_zalloc(sizeof(ZIP_CENTRAL_DIRECTORY_ENTRY));
entry->fileOffset = ftello(file) - 4;
if (entry->fileOffset < 0) {
freeZipCentralDirectoryEntry(entry);
return NULL; /* FAILED */
}
/* version made by (2 bytes) */
entry->creatorVersion = fileGetU16(file);
/* version needed to extract (2 bytes) */
entry->viewerVersion = fileGetU16(file);
/* general purpose bit flag (2 bytes) */
entry->flags = fileGetU16(file);
/* compression method (2 bytes) */
entry->compression = fileGetU16(file);
/* last mod file time (2 bytes) */
entry->modTime = fileGetU16(file);
/* last mod file date (2 bytes) */
entry->modDate = fileGetU16(file);
/* crc-32 (4 bytes) */
entry->crc32 = fileGetU32(file);
/* compressed size (4 bytes), 0xFFFFFFFF for ZIP64 format */
entry->compressedSize = fileGetU32(file);
/* uncompressed size (4 bytes), 0xFFFFFFFF for ZIP64 format */
entry->uncompressedSize = fileGetU32(file);
/* file name length (2 bytes) */
entry->fileNameLen = fileGetU16(file);
/* extra field length (2 bytes) */
entry->extraFieldLen = fileGetU16(file);
/* file comment length (2 bytes) */
entry->fileCommentLen = fileGetU16(file);
/* disk number start (2 bytes), 0xFFFFFFFF for ZIP64 format */
entry->diskNoStart = fileGetU16(file);
/* internal file attributes (2 bytes) */
entry->internalAttr = fileGetU16(file);
/* external file attributes (4 bytes) */
entry->externalAttr = fileGetU32(file);
/* relative offset of local header (4 bytes), 0xFFFFFFFF for ZIP64 format */
entry->offsetOfLocalHeader = fileGetU32(file);
/* file name (variable size) */
if (entry->fileNameLen > 0) {
entry->fileName = OPENSSL_zalloc(entry->fileNameLen + 1);
size = fread(entry->fileName, 1, entry->fileNameLen, file);
if (size != entry->fileNameLen) {
freeZipCentralDirectoryEntry(entry);
return NULL; /* FAILED */
}
entry->fileName[entry->fileNameLen] = 0;
}
/* extra field (variable size) */
if (entry->extraFieldLen > 0) {
entry->extraField = OPENSSL_zalloc(entry->extraFieldLen + 1);
size = fread(entry->extraField, 1, entry->extraFieldLen, file);
if (size != entry->extraFieldLen) {
freeZipCentralDirectoryEntry(entry);
return NULL; /* FAILED */
}
entry->extraField[entry->extraFieldLen] = 0;
}
/* file comment (variable size) */
if (entry->fileCommentLen > 0) {
entry->fileComment = OPENSSL_zalloc(entry->fileCommentLen + 1);
size = fread(entry->fileComment, 1, entry->fileCommentLen, file);
if (size != entry->fileCommentLen) {
freeZipCentralDirectoryEntry(entry);
return NULL; /* FAILED */
}
entry->fileComment[entry->fileCommentLen] = 0;
}
if (entry->uncompressedSize == UINT32_MAX || entry->compressedSize == UINT32_MAX ||
entry->offsetOfLocalHeader == UINT32_MAX || entry->diskNoStart == UINT16_MAX) {
if (entry->extraFieldLen > 4) {
uint64_t pos = 0;
uint64_t len;
uint16_t header = bufferGetU16(entry->extraField, &pos);
if (header != ZIP64_HEADER) {
fprintf(stderr, "Expected zip64 header in central directory extra field, got : 0x%X\n", header);
freeZipCentralDirectoryEntry(entry);
return NULL; /* FAILED */
}
len = bufferGetU16(entry->extraField, &pos);
if (entry->uncompressedSize == UINT32_MAX) {
if (len >= 8) {
entry->uncompressedSize = bufferGetU64(entry->extraField, &pos);
entry->uncompressedSizeInZip64 = 1;
} else {
fprintf(stderr, "Invalid zip64 central directory entry\n");
freeZipCentralDirectoryEntry(entry);
return NULL; /* FAILED */
}
}
if (entry->compressedSize == UINT32_MAX) {
if (len >= 16) {
entry->compressedSize = bufferGetU64(entry->extraField, &pos);
entry->compressedSizeInZip64 = 1;
} else {
fprintf(stderr, "Invalid zip64 central directory entry\n");
freeZipCentralDirectoryEntry(entry);
return NULL; /* FAILED */
}
}
if (entry->offsetOfLocalHeader == UINT32_MAX) {
if (len >= 24) {
entry->offsetOfLocalHeader = bufferGetU64(entry->extraField, &pos);
entry->offsetInZip64 = 1;
} else {
fprintf(stderr, "Invalid zip64 central directory entry\n");
freeZipCentralDirectoryEntry(entry);
return NULL; /* FAILED */
}
}
if (entry->diskNoStart == UINT16_MAX) {
if (len >= 28) {
entry->diskNoStart = bufferGetU32(entry->extraField, &pos);
entry->diskNoInZip64 = 1;
} else {
fprintf(stderr, "Invalid zip64 central directory entry\n");
freeZipCentralDirectoryEntry(entry);
return NULL; /* FAILED */
}
}
} else {
freeZipCentralDirectoryEntry(entry);
return NULL; /* FAILED */
}
}
entry->entryLen = ftello(file) - entry->fileOffset;
if (entry->entryLen < 0) {
freeZipCentralDirectoryEntry(entry);
return NULL; /* FAILED */
}
return entry;
}
/*
* Free up central directory structure.
* [in] central directory structure
* [returns] none
*/
static void freeZipCentralDirectoryEntry(ZIP_CENTRAL_DIRECTORY_ENTRY *entry)
{
OPENSSL_free(entry->fileName);
OPENSSL_free(entry->extraField);
OPENSSL_free(entry->fileComment);
if (entry->overrideData) {
OPENSSL_free(entry->overrideData->data);
}
OPENSSL_free(entry->overrideData);
OPENSSL_free(entry);
}
/*
* Read Zip end of central directory record.
* [out] eocdr: end of central directory record
* [in, out] file: FILE pointer to the input file
* [returns] 0 on error or 1 on success
*/
static int readZipEOCDR(ZIP_EOCDR *eocdr, FILE *file)
{
char signature[4];
size_t size;
if (fseeko(file, -EOCDR_SIZE, SEEK_END) < 0) {
return 0; /* FAILED */
}
size = fread(signature, 1, 4, file);
if (size != 4) {
return 0; /* FAILED */
}
if (memcmp(signature, PKZIP_EOCDR_SIGNATURE, 4)) {
/* Not a valid ZIP file - could not find End of Central Directory record */
return 0; /* FAILED */
}
/* number of this disk (2 bytes) */
eocdr->diskNumber = fileGetU16(file);
/* number of the disk with the start of the central directory (2 bytes) */
eocdr->centralDirectoryDiskNumber = fileGetU16(file);
/* total number of entries in the central directory on this disk (2 bytes) */
eocdr->diskEntries = fileGetU16(file);
/* total number of entries in the central directory (2 bytes) */
eocdr->totalEntries = fileGetU16(file);
/* size of the central directory (4 bytes) */
eocdr->centralDirectorySize = fileGetU32(file);
/* offset of start of central directory with respect
* to the starting disk number (4 bytes) */
eocdr->centralDirectoryOffset = fileGetU32(file);
/* .ZIP file comment length (2 bytes) */
eocdr->commentLen = fileGetU16(file);
#if 0
if (eocdr->centralDirectoryDiskNumber > 1 || eocdr->diskNumber > 1 ||
eocdr->centralDirectoryDiskNumber != eocdr->diskNumber ||
eocdr->diskEntries != eocdr->totalEntries)
{
fprintf(stderr, "The input file is a multipart archive - not supported\n");
return 0; /* FAILED */
}
#endif
if (eocdr->commentLen > 0) {
eocdr->comment = OPENSSL_zalloc(eocdr->commentLen + 1);
size = fread(eocdr->comment, 1, eocdr->commentLen, file);
if (size != eocdr->commentLen) {
return 0; /* FAILED */
}
eocdr->comment[eocdr->commentLen] = 0;
} else {
eocdr->comment = NULL;
}
return 1; /* OK */
}
/*
* Read Zip64 end of central directory locator.
* [out] locator: Zip64 end of central directory locator
* [in, out] file: FILE pointer to the input file
* [returns] 0 on error or 1 on success
*/
static int readZip64EOCDLocator(ZIP64_EOCD_LOCATOR *locator, FILE *file)
{
char signature[4];
size_t size;
if (fseeko(file, -(EOCDR_SIZE + ZIP64_EOCD_LOCATOR_SIZE), SEEK_END) < 0) {
return 0; /* FAILED */
}
size = fread(signature, 1, 4, file);
if (size != 4) {
return 0; /* FAILED */
}
if (memcmp(signature, PKZIP64_EOCD_LOCATOR_SIGNATURE, 4)) {
fprintf(stderr, "The input file is not a valid zip file - could not find zip64 EOCD locator\n");
return 0; /* FAILED */
}
locator->diskWithEOCD = fileGetU32(file);
locator->eocdOffset = fileGetU64(file);
locator->totalNumberOfDisks = fileGetU32(file);
return 1; /* OK */
}
/*
* Read Zip64 end of central directory record
* [out] eocdr: Zip64 end of central directory record
* [in, out] file: FILE pointer to the input file
* [in] offset: eocdr struct offset in the file
* [returns] 0 on error or 1 on success
*/
static int readZip64EOCDR(ZIP64_EOCDR *eocdr, FILE *file, uint64_t offset)
{
char signature[4];
size_t size;
if (fseeko(file, (int64_t)offset, SEEK_SET) < 0) {
return 0; /* FAILED */
}
size = fread(signature, 1, 4, file);
if (size != 4) {
return 0; /* FAILED */
}
if (memcmp(signature, PKZIP64_EOCDR_SIGNATURE, 4)) {
fprintf(stderr, "The input file is not a valid zip file - could not find zip64 End of Central Directory record\n");
return 0; /* FAILED */
}
/* size of zip64 end of central directory record (8 bytes) */
eocdr->eocdrSize = fileGetU64(file);
/* version made by (2 bytes) */
eocdr->creatorVersion = fileGetU16(file);
/* version needed to extract (2 bytes) */
eocdr->viewerVersion = fileGetU16(file);
/* number of this disk (4 bytes) */
eocdr->diskNumber = fileGetU32(file);
/* number of the disk with the start of the central directory (4 bytes) */
eocdr->diskWithCentralDirectory = fileGetU32(file);
/* total number of entries in the central directory on this disk (8 bytes) */
eocdr->diskEntries = fileGetU64(file);
/* total number of entries in the central directory (8 bytes) */
eocdr->totalEntries = fileGetU64(file);
/* size of the central directory (8 bytes) */
eocdr->centralDirectorySize = fileGetU64(file);
/* offset of start of central directory with respect
* to the starting disk number (8 bytes) */
eocdr->centralDirectoryOffset = fileGetU64(file);
/* zip64 extensible data sector (comment) */
eocdr->commentLen = eocdr->eocdrSize - 44;
if (eocdr->commentLen > UINT16_MAX) {
fprintf(stderr, "Corrupted file comment length : 0x%08" PRIX64 "\n", eocdr->commentLen);
return 0; /* FAILED */
}
if (eocdr->commentLen > 0) {
eocdr->comment = OPENSSL_malloc(eocdr->commentLen);
size = fread(eocdr->comment, 1, eocdr->commentLen, file);
if (size != eocdr->commentLen) {
return 0; /* FAILED */
}
}
if (eocdr->diskWithCentralDirectory > 1 || eocdr->diskNumber > 1 ||
eocdr->diskWithCentralDirectory != eocdr->diskNumber ||
eocdr->totalEntries != eocdr->diskEntries) {
fprintf(stderr, "The input file is a multipart archive - not supported\n");
return 0; /* FAILED */
}
return 1; /* OK */
}
static int get_current_position(BIO *bio, uint64_t *offset)
{
FILE *file = NULL;
int64_t pos;
BIO_get_fp(bio, &file);
pos = ftello(file);
if (pos < 0) {
return 0; /* FAILED */
}
*offset = (uint64_t)pos;
return 1; /* OK */
}
static uint64_t fileGetU64(FILE *file)
{
uint64_t l = fileGetU32(file);
uint64_t h = fileGetU32(file);
/* coverity[byte_swapping] */
return h << 32 | l;
}
/* coverity[ -tainted_data_return ] */
static uint32_t fileGetU32(FILE *file)
{
uint8_t b[4];
size_t size = fread(b, 1, 4, file);
if (size != 4) {
return 0; /* FAILED */
}
return (uint32_t)(b[3] << 24 | b[2] << 16 | b[1] << 8 | b[0]);
}
/* coverity[ -tainted_data_return ] */
static uint16_t fileGetU16(FILE *file)
{
uint8_t b[2];
size_t size = fread(b, 1, 2, file);
if (size != 2) {
return 0; /* FAILED */
}
return (uint16_t)(b[1] << 8 | b[0]);
}
static uint64_t bufferGetU64(uint8_t *buffer, uint64_t *pos)
{
uint64_t l = bufferGetU32(buffer, pos);
uint64_t h = bufferGetU32(buffer, pos);
return h << 32 | l;
}
static uint32_t bufferGetU32(uint8_t *buffer, uint64_t *pos)
{
uint32_t ret = (uint32_t)(buffer[*pos + 3] << 24 | \
buffer[*pos + 2] << 16 | \
buffer[*pos + 1] << 8 | \
buffer[*pos]);
*pos += 4;
return ret;
}
static uint16_t bufferGetU16(uint8_t *buffer, uint64_t *pos)
{
uint16_t ret = (uint16_t)(buffer[*pos + 1] << 8 | buffer[*pos]);
*pos += 2;
return ret;
}
void bioAddU64(BIO *bio, uint64_t v)
{
uint32_t l = v & UINT32_MAX;
uint32_t h = (uint32_t)(v >> 32);
bioAddU32(bio, l);
bioAddU32(bio, h);
}
static void bioAddU32(BIO *bio, uint32_t v)
{
uint8_t b[4];
b[0] = (u_char)((v) & UINT8_MAX);
b[1] = (u_char)(((v) >> 8) & UINT8_MAX);
b[2] = (u_char)(((v) >> 16) & UINT8_MAX);
b[3] = (u_char)(((v) >> 24) & UINT8_MAX);
BIO_write(bio, b, 4);
}
static void bioAddU16(BIO *bio, uint16_t v)
{
uint8_t b[2];
b[0] = (u_char)((v) & UINT8_MAX);
b[1] = (u_char)(((v) >> 8) & UINT8_MAX);
BIO_write(bio, b, 2);
}
/*
Local Variables:
c-basic-offset: 4
tab-width: 4
indent-tabs-mode: nil
End:
vim: set ts=4 expandtab:
*/