#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
#if ULONG_MAX == 0xFFFFFFFFFFFFFFFF
#define PRIX64 "lX"
#else
#define PRIX64 "llX"
#endif
#endif
#endif
#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' };
static const char APPX_SIGNATURE[4] = { 'A', 'P', 'P', 'X' };
static const char AXPC_SIGNATURE[4] = { 'A', 'X', 'P', 'C' };
static const char AXCD_SIGNATURE[4] = { 'A', 'X', 'C', 'D' };
static const char AXCT_SIGNATURE[4] = { 'A', 'X', 'C', 'T' };
static const char AXBM_SIGNATURE[4] = { 'A', 'X', 'B', 'M' };
static const char AXCI_SIGNATURE[4] = { 'A', 'X', 'C', 'I' };
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";
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;
typedef struct {
uint32_t crc32;
uint64_t compressedSize;
uint64_t uncompressedSize;
uint8_t *data;
} ZIP_OVERRIDE_DATA;
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)
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;
typedef struct {
uint32_t diskWithEOCD;
uint64_t eocdOffset;
uint32_t totalNumberOfDisks;
} ZIP64_EOCD_LOCATOR;
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;
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;
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,
};
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 *header);
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);
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);
(void)hash;
(void)outdata;
if (!zip) {
return NULL;
}
if (options->verbose) {
zipPrintCentralDirectory(zip);
}
md = appx_get_md(zip);
if (!md) {
freeZip(zip);
return NULL;
}
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;
}
static const EVP_MD *appx_md_get(FILE_FORMAT_CTX *ctx)
{
return ctx->appx_ctx->md;
}
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;
}
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;
(void)md;
(void)hash;
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;
}
if (!appx_append_ct_signature_entry(ctx->appx_ctx->zip, entry)) {
return NULL;
}
bhash = appx_calculate_hashes(ctx);
if (!bhash) {
return NULL;
}
content = spc_indirect_data_content_get(bhash, ctx);
BIO_free_all(bhash);
return pkcs7_set_content(content);
}
static int appx_hash_length_get(FILE_FORMAT_CTX *ctx)
{
return ctx->appx_ctx->hashlen;
}
static int appx_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7)
{
SpcIndirectDataContent *idc;
BIO *hashes;
idc = pkcs7_get_indirect_data_content(p7);
if (!idc)
return 1;
if (!appx_extract_hashes(ctx, idc)) {
fprintf(stderr, "Failed to extract hashes from the signature\n");
SpcIndirectDataContent_free(idc);
return 0;
}
hashes = appx_calculate_hashes(ctx);
if (!hashes) {
SpcIndirectDataContent_free(idc);
return 0;
}
BIO_free_all(hashes);
if (!appx_compare_hashes(ctx)) {
fprintf(stderr, "Signature hash verification failed\n");
SpcIndirectDataContent_free(idc);
return 0;
}
SpcIndirectDataContent_free(idc);
return 1;
}
static PKCS7 *appx_pkcs7_extract(FILE_FORMAT_CTX *ctx)
{
PKCS7 *p7;
uint8_t *data = NULL;
const u_char *blob;
size_t dataSize;
if (!zipEntryExist(ctx->appx_ctx->zip, APP_SIGNATURE_FILENAME)) {
fprintf(stderr, "%s does not exist\n", APP_SIGNATURE_FILENAME);
return NULL;
}
dataSize = zipReadFileDataByName(&data, ctx->appx_ctx->zip, APP_SIGNATURE_FILENAME);
if (dataSize <= 0) {
return NULL;
}
if (memcmp(data, PKCX_SIGNATURE, 4)) {
fprintf(stderr, "Invalid PKCX header\n");
OPENSSL_free(data);
return NULL;
}
blob = (u_char *)data + 4;
p7 = d2i_PKCS7(NULL, &blob, (int)dataSize - 4);
OPENSSL_free(data);
return p7;
}
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);
(void)hash;
if (!entry) {
fprintf(stderr, "Not a valid .appx file: content types file missing\n");
return 1;
}
dataSize = zipReadFileDataByName(&data, ctx->appx_ctx->zip, APP_SIGNATURE_FILENAME);
if (dataSize <= 0) {
return 1;
}
OPENSSL_free(data);
if (!appx_remove_ct_signature_entry(zip, entry)) {
fprintf(stderr, "Failed to remove signature entry\n");
return 1;
}
for (entry = zip->centralDirectoryHead; entry != NULL; entry = entry->next) {
if (noEntries == zip->centralDirectoryRecordCount) {
fprintf(stderr, "Corrupted central directory structure\n");
return 1;
}
noEntries++;
if (!entry->fileName || (entry->fileNameLen == 0)) {
fprintf(stderr, "Corrupted file name\n");
return 1;
}
if (strcmp(entry->fileName, APP_SIGNATURE_FILENAME)) {
uint64_t dummy;
if (!zipRewriteData(zip, entry, outdata, &dummy)) {
return 1;
}
}
}
if (!get_current_position(outdata, &cdOffset)) {
fprintf(stderr, "Unable to get offset\n");
return 1;
}
if (!appx_write_central_directory(outdata, zip, 1, cdOffset)) {
fprintf(stderr, "Unable to write central directory\n");
return 1;
}
return 0;
}
static int appx_process_data(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata)
{
ZIP_CENTRAL_DIRECTORY_ENTRY *entry;
(void)outdata;
(void)hash;
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;
}
if (!appx_append_ct_signature_entry(ctx->appx_ctx->zip, entry)) {
return 0;
}
return 1;
}
static PKCS7 *appx_pkcs7_signature_new(FILE_FORMAT_CTX *ctx, BIO *hash)
{
ASN1_OCTET_STRING *content;
PKCS7 *p7 = NULL;
BIO *hashes;
(void)hash;
hashes = appx_calculate_hashes(ctx);
if (!hashes) {
return NULL;
}
p7 = pkcs7_create(ctx);
if (!p7) {
fprintf(stderr, "Creating a new signature failed\n");
BIO_free_all(hashes);
return NULL;
}
if (!add_indirect_data_object(p7)) {
fprintf(stderr, "Adding SPC_INDIRECT_DATA_OBJID failed\n");
PKCS7_free(p7);
BIO_free_all(hashes);
return NULL;
}
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;
}
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;
}
ASN1_OCTET_STRING_free(content);
return p7;
}
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;
}
noEntries++;
last = entry;
if (!entry->fileName || (entry->fileNameLen == 0)) {
fprintf(stderr, "Corrupted file name\n");
return 1;
}
if (strcmp(entry->fileName, APP_SIGNATURE_FILENAME)) {
uint64_t dummy = 0;
if (!zipRewriteData(zip, entry, outdata, &dummy)) {
return 1;
}
prev = entry;
entry = entry->next;
} else {
ZIP_CENTRAL_DIRECTORY_ENTRY *current = entry;
entry = entry->next;
if (prev) {
prev->next = entry;
}
freeZipCentralDirectoryEntry(current);
}
}
if (!last) {
return 1;
}
if (((len = i2d_PKCS7(p7, NULL)) <= 0) ||
(der = OPENSSL_malloc((size_t)len)) == NULL)
return 1;
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;
}
OPENSSL_free(der);
OPENSSL_free(blob);
if (!get_current_position(outdata, &cdOffset)) {
fprintf(stderr, "Unable to get offset\n");
return 1;
}
if (!appx_write_central_directory(outdata, zip, 0, cdOffset)) {
fprintf(stderr, "Unable to write central directory\n");
return 1;
}
return 0;
}
static void appx_bio_free(BIO *hash, BIO *outdata)
{
BIO_free_all(outdata);
BIO_free_all(hash);
}
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);
}
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;
}
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;
}
return appx_hash_blob_get(ctx);
}
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;
}
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 defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
if (!BIO_set_md(bhash, md)) {
fprintf(stderr, "Unable to set the message digest of BIO\n");
BIO_free_all(bhash);
return NULL;
}
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
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;
}
mdbuf = OPENSSL_malloc((size_t)EVP_MD_size(md));
BIO_gets(bhash, (char*)mdbuf, EVP_MD_size(md));
BIO_free_all(bhash);
return mdbuf;
}
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) {
uint64_t sizeOnDisk = 0;
if (noEntries > zip->centralDirectoryRecordCount) {
fprintf(stderr, "Corrupted central directory structure\n");
return 0;
}
if (!entry->fileName || (entry->fileNameLen == 0)) {
fprintf(stderr, "Corrupted file name\n");
return 0;
}
if (removeSignature && !strcmp(entry->fileName, APP_SIGNATURE_FILENAME)) {
continue;
}
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) {
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;
}
}
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);
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;
}
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 defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
if (!BIO_set_md(bhash, md)) {
fprintf(stderr, "Unable to set the message digest of BIO\n");
BIO_free_all(bhash);
return NULL;
}
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
BIO_push(bhash, BIO_new(BIO_s_null()));
*cdOffset = 0;
for (entry = zip->centralDirectoryHead; entry != NULL; entry = entry->next) {
uint64_t sizeOnDisk = 0;
if (noEntries >= zip->centralDirectoryRecordCount) {
fprintf(stderr, "Corrupted central directory structure\n");
BIO_free_all(bhash);
return NULL;
}
noEntries++;
if (!entry->fileName || (entry->fileNameLen == 0)) {
fprintf(stderr, "Corrupted file name\n");
BIO_free_all(bhash);
return NULL;
}
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;
}
*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;
}
static int appx_extract_hashes(FILE_FORMAT_CTX *ctx, SpcIndirectDataContent *content)
{
#if 0#endif
int len = ASN1_STRING_length(content->messageDigest->digest);
const uint8_t *data = ASN1_STRING_get0_data(content->messageDigest->digest);
int mdlen = EVP_MD_size(ctx->appx_ctx->md);
int pos = 4;
if (len < 4 * mdlen + 4) {
fprintf(stderr, "Hash too short\n");
return 0;
}
if (memcmp(data, APPX_SIGNATURE, 4)) {
fprintf(stderr, "Hash signature does not match\n");
return 0;
}
while (pos + mdlen + 4 <= len) {
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;
}
pos += mdlen + 4;
}
if (!ctx->appx_ctx->existingDataHash) {
fprintf(stderr, "File hash missing\n");
return 0;
}
if (!ctx->appx_ctx->existingCDHash) {
fprintf(stderr, "Central directory hash missing\n");
return 0;
}
if (!ctx->appx_ctx->existingBMHash) {
fprintf(stderr, "Block map hash missing\n");
return 0;
}
if (!ctx->appx_ctx->existingCTHash) {
fprintf(stderr, "Content types hash missing\n");
return 0;
}
if (zipEntryExist(ctx->appx_ctx->zip, CODE_INTEGRITY_FILENAME) && !ctx->appx_ctx->existingCIHash) {
fprintf(stderr, "Code integrity hash missing\n");
return 0;
}
return 1;
}
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;
}
} else {
fprintf(stderr, "Block map hash missing\n");
return 0;
}
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;
}
} else {
fprintf(stderr, "Content Types hash missing\n");
return 0;
}
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;
}
} else {
fprintf(stderr, "Central Directory hash missing\n");
return 0;
}
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;
}
} else {
fprintf(stderr, "Central Directory hash missing\n");
return 0;
}
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;
}
} else if (!ctx->appx_ctx->calculatedCIHash && !ctx->appx_ctx->existingCIHash) {
} else {
fprintf(stderr, "Code Integrity hash missing\n");
return 0;
}
return 1;
}
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;
}
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;
}
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;
}
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;
}
existingEntry = strstr((const char *)data, SIGNATURE_CONTENT_TYPES_ENTRY);
if (existingEntry) {
OPENSSL_free(data);
return 1;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
OPENSSL_free(data);
return md;
}
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;
}
noEntries++;
if (!entry->fileName || (entry->fileNameLen == 0)) {
fprintf(stderr, "Corrupted file name\n");
return NULL;
}
if (!strcmp(entry->fileName, name)) {
return entry;
}
}
return NULL;
}
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#endif
if (entry->fileCommentLen > 0 && entry->fileComment) {
BIO_write(bio, entry->fileComment, entry->fileCommentLen);
}
*sizeOnDisk = (uint64_t)46 + entry->fileNameLen + entry->extraFieldLen + entry->fileCommentLen;
}
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;
}
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);
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(header.fileName);
header.fileName = NULL;
OPENSSL_free(dataToWrite);
return 0;
}
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(header.fileName);
header.fileName = NULL;
OPENSSL_free(dataToWrite);
return 0;
}
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;
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;
}
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;
}
entry->overrideData->compressedSize = size;
return 1;
}
static int zipRewriteData(ZIP_FILE *zip, ZIP_CENTRAL_DIRECTORY_ENTRY *entry, BIO *bio, uint64_t *sizeOnDisk)
{
size_t check;
ZIP_LOCAL_HEADER header;
int ret = 0;
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;
}
if (fseeko(zip->file, (int64_t)entry->offsetOfLocalHeader, SEEK_SET) < 0) {
return 0;
}
if (!zipReadLocalHeader(&header, zip, entry->compressedSize)) {
goto out;
}
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) {
goto out;
}
if (entry->compressedSize > (uint64_t)zip->fileSize - entry->offsetOfLocalHeader) {
fprintf(stderr, "Corrupted compressedSize : 0x%08" PRIX64 "\n", entry->compressedSize);
goto out;
}
if (fseeko(zip->file, (int64_t)entry->compressedSize, SEEK_CUR) < 0) {
goto out;
}
*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);
goto out;
}
if (!BIO_write_ex(bio, data, toWrite, &check)
|| check != toWrite) {
OPENSSL_free(data);
goto out;
}
*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) {
goto out;
}
*sizeOnDisk += 24;
} else {
if (fseeko(zip->file, 16, SEEK_CUR) < 0) {
goto out;
}
*sizeOnDisk += 16;
}
}
ret = 1;
out:
OPENSSL_free(header.fileName);
OPENSSL_free(header.extraField);
header.fileName = NULL;
header.extraField = NULL;
return ret;
}
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;
}
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;
}
noEntries++;
if (!entry->fileName || (entry->fileNameLen == 0)) {
fprintf(stderr, "Corrupted file name\n");
return 0;
}
if (!strcmp(entry->fileName, name)) {
return 1;
}
}
return 0;
}
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;
}
bhash = BIO_new(BIO_f_md());
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
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;
}
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
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;
}
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;
}
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;
}
noEntries++;
if (!entry->fileName || (entry->fileNameLen == 0)) {
fprintf(stderr, "Corrupted file name\n");
return 0;
}
if (!strcmp(entry->fileName, name)) {
return zipReadFileData(zip, pData, entry);
}
}
return 0;
}
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;
}
if (fseeko(file, (int64_t)entry->offsetOfLocalHeader, SEEK_SET) < 0) {
return 0;
}
if (entry->overrideData) {
compressedSize = entry->overrideData->compressedSize;
if (compressedSize > (uint64_t)(SIZE_MAX - 1)) {
fprintf(stderr, "Corrupted compressedSize : %" PRIu64"\n", compressedSize);
return 0;
}
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)) {
OPENSSL_free(header.fileName);
OPENSSL_free(header.extraField);
header.fileName = NULL;
header.extraField = NULL;
return 0;
}
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");
OPENSSL_free(header.fileName);
OPENSSL_free(header.extraField);
header.fileName = NULL;
header.extraField = NULL;
return 0;
}
OPENSSL_free(header.fileName);
OPENSSL_free(header.extraField);
header.fileName = NULL;
header.extraField = NULL;
if (compressedSize > (uint64_t)(SIZE_MAX - 1)
|| compressedSize > (uint64_t)zip->fileSize - entry->offsetOfLocalHeader) {
fprintf(stderr, "Corrupted compressedSize : %" PRIu64"\n", compressedSize);
return 0;
}
compressedData = OPENSSL_zalloc(compressedSize + 1);
size = fread(compressedData, 1, compressedSize, file);
if (size != compressedSize) {
OPENSSL_free(compressedData);
return 0;
}
compressedData[compressedSize] = 0;
}
if (entry->compression == COMPRESSION_NONE) {
if (compressedSize == 0) {
OPENSSL_free(compressedData);
return 0;
}
*pData = compressedData;
dataSize = compressedSize;
} else if (entry->compression == COMPRESSION_DEFLATE) {
uint8_t *uncompressedData;
uint64_t destLen, sourceLen;
int ret;
if (uncompressedSize > (uint64_t)(SIZE_MAX - 1)) {
fprintf(stderr, "Corrupted uncompressedSize : %" PRIu64"\n", uncompressedSize);
OPENSSL_free(compressedData);
return 0;
}
if (uncompressedSize > 1024 * 1024 && uncompressedSize / 100 >= compressedSize) {
fprintf(stderr, "Error: suspicious compression ratio\n");
OPENSSL_free(compressedData);
return 0;
}
uncompressedData = OPENSSL_zalloc(uncompressedSize + 1);
destLen = uncompressedSize;
sourceLen = compressedSize;
ret = zipInflate(uncompressedData, &destLen, compressedData, (uLong *)&sourceLen);
OPENSSL_free(compressedData);
if (ret != Z_OK) {
fprintf(stderr, "Data decompression failed, zlib error: %d\n", ret);
OPENSSL_free(uncompressedData);
return 0;
} else {
if (destLen == 0) {
OPENSSL_free(uncompressedData);
return 0;
}
*pData = uncompressedData;
dataSize = destLen;
}
} else {
fprintf(stderr, "Unsupported compression mode: %d\n", entry->compression);
OPENSSL_free(compressedData);
return 0;
}
return dataSize;
}
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;
}
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;
}
header->version = fileGetU16(file);
header->flags = fileGetU16(file);
header->compression = fileGetU16(file);
header->modTime = fileGetU16(file);
header->modDate = fileGetU16(file);
header->crc32 = fileGetU32(file);
header->compressedSize = fileGetU32(file);
header->uncompressedSize = fileGetU32(file);
header->fileNameLen = fileGetU16(file);
header->extraFieldLen = fileGetU16(file);
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;
}
header->fileName[header->fileNameLen] = 0;
} else {
header->fileName = NULL;
}
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;
}
header->extraField[header->extraFieldLen] = 0;
} else {
header->extraField = NULL;
}
if (header->flags & DATA_DESCRIPTOR_BIT) {
int64_t offset = ftello(file);
if (offset < 0 || offset >= zip->fileSize) {
return 0;
}
if (compressedSize > (uint64_t)(zip->fileSize - offset)) {
fprintf(stderr, "Corrupted compressedSize : 0x%08" PRIX64 "\n", compressedSize);
return 0;
}
if (fseeko(file, (int64_t)compressedSize, SEEK_CUR) < 0) {
return 0;
}
size = fread(signature, 1, 4, file);
if (size != 4) {
return 0;
}
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);
header->fileName = NULL;
header->extraField = NULL;
return 0;
}
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;
}
}
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;
}
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;
}
}
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;
}
}
} else {
OPENSSL_free(header->fileName);
OPENSSL_free(header->extraField);
header->fileName = NULL;
header->extraField = NULL;
return 0;
}
}
return 1;
}
static int zipInflate(uint8_t *dest, uint64_t *destLen, uint8_t *source, uLong *sourceLen)
{
z_stream stream;
int err;
const uInt max = (uInt)-1;
uLong len, left;
static u_char buf[] = { 0x00 };
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;
}
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;
}
static int zipDeflate(uint8_t *dest, uint64_t *destLen, uint8_t *source, uLong sourceLen)
{
z_stream stream;
int err;
const uInt max = (uInt)-1;
uLong left;
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) {
deflateEnd(&stream);
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;
}
err = deflate(&stream, sourceLen ? Z_NO_FLUSH : Z_FINISH);
} while (err == Z_OK);
#if 0#endif
*destLen = stream.total_out;
deflateEnd(&stream);
return err == Z_STREAM_END ? Z_OK : err;
}
static ZIP_FILE *openZip(const char *filename)
{
ZIP_FILE *zip;
FILE *file = fopen(filename, "rb");
if (!file) {
return NULL;
}
zip = OPENSSL_zalloc(sizeof(ZIP_FILE));
zip->file = file;
if (!readZipEOCDR(&zip->eocdr, file)) {
freeZip(zip);
return NULL;
}
if (fseeko(file, 0, SEEK_END) < 0) {
freeZip(zip);
return NULL;
}
zip->fileSize = ftello(file);
if (zip->fileSize < 0) {
freeZip(zip);
return NULL;
}
if (zip->eocdr.centralDirectoryOffset == UINT32_MAX || zip->eocdr.centralDirectorySize == UINT32_MAX) {
if (!readZip64EOCDLocator(&zip->locator, file)) {
freeZip(zip);
return NULL;
}
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;
}
if (!readZip64EOCDR(&zip->eocdr64, file, zip->locator.eocdOffset)) {
freeZip(zip);
return NULL;
}
zip->isZip64 = 1;
zip->eocdrOffset = zip->locator.eocdOffset;
zip->eocdrLen = zip->fileSize - (int64_t)zip->eocdrOffset;
if (zip->eocdrLen < 0) {
freeZip(zip);
return NULL;
}
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;
}
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;
}
}
if (zip->centralDirectoryOffset >= (uint64_t)zip->fileSize) {
fprintf(stderr, "Corrupted central directory offset : 0x%08" PRIX64 "\n", zip->centralDirectoryOffset);
freeZip(zip);
return NULL;
}
if (!zipReadCentralDirectory(zip, file)) {
freeZip(zip);
return NULL;
}
return zipSortCentralDirectory(zip);
}
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);
}
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;
}
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;
}
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;
}
}
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;
}
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;
}
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);
}
}
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;
}
for (i = 0; i < zip->centralDirectoryRecordCount; i++) {
ZIP_CENTRAL_DIRECTORY_ENTRY *entry = zipReadNextCentralDirectoryEntry(file);
if (!entry) {
return 0;
}
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;
}
prev = entry;
}
return 1;
}
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;
}
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;
}
entry = OPENSSL_zalloc(sizeof(ZIP_CENTRAL_DIRECTORY_ENTRY));
entry->fileOffset = ftello(file) - 4;
if (entry->fileOffset < 0) {
freeZipCentralDirectoryEntry(entry);
return NULL;
}
entry->creatorVersion = fileGetU16(file);
entry->viewerVersion = fileGetU16(file);
entry->flags = fileGetU16(file);
entry->compression = fileGetU16(file);
entry->modTime = fileGetU16(file);
entry->modDate = fileGetU16(file);
entry->crc32 = fileGetU32(file);
entry->compressedSize = fileGetU32(file);
entry->uncompressedSize = fileGetU32(file);
entry->fileNameLen = fileGetU16(file);
entry->extraFieldLen = fileGetU16(file);
entry->fileCommentLen = fileGetU16(file);
entry->diskNoStart = fileGetU16(file);
entry->internalAttr = fileGetU16(file);
entry->externalAttr = fileGetU32(file);
entry->offsetOfLocalHeader = fileGetU32(file);
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;
}
entry->fileName[entry->fileNameLen] = 0;
}
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;
}
entry->extraField[entry->extraFieldLen] = 0;
}
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;
}
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;
}
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;
}
}
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;
}
}
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;
}
}
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;
}
}
} else {
freeZipCentralDirectoryEntry(entry);
return NULL;
}
}
entry->entryLen = ftello(file) - entry->fileOffset;
if (entry->entryLen < 0) {
freeZipCentralDirectoryEntry(entry);
return NULL;
}
return entry;
}
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);
}
static int readZipEOCDR(ZIP_EOCDR *eocdr, FILE *file)
{
char signature[4];
size_t size;
if (fseeko(file, -EOCDR_SIZE, SEEK_END) < 0) {
return 0;
}
size = fread(signature, 1, 4, file);
if (size != 4) {
return 0;
}
if (memcmp(signature, PKZIP_EOCDR_SIGNATURE, 4)) {
return 0;
}
eocdr->diskNumber = fileGetU16(file);
eocdr->centralDirectoryDiskNumber = fileGetU16(file);
eocdr->diskEntries = fileGetU16(file);
eocdr->totalEntries = fileGetU16(file);
eocdr->centralDirectorySize = fileGetU32(file);
eocdr->centralDirectoryOffset = fileGetU32(file);
eocdr->commentLen = fileGetU16(file);
#if 0#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;
}
eocdr->comment[eocdr->commentLen] = 0;
} else {
eocdr->comment = NULL;
}
return 1;
}
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;
}
size = fread(signature, 1, 4, file);
if (size != 4) {
return 0;
}
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;
}
locator->diskWithEOCD = fileGetU32(file);
locator->eocdOffset = fileGetU64(file);
locator->totalNumberOfDisks = fileGetU32(file);
return 1;
}
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;
}
size = fread(signature, 1, 4, file);
if (size != 4) {
return 0;
}
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;
}
eocdr->eocdrSize = fileGetU64(file);
eocdr->creatorVersion = fileGetU16(file);
eocdr->viewerVersion = fileGetU16(file);
eocdr->diskNumber = fileGetU32(file);
eocdr->diskWithCentralDirectory = fileGetU32(file);
eocdr->diskEntries = fileGetU64(file);
eocdr->totalEntries = fileGetU64(file);
eocdr->centralDirectorySize = fileGetU64(file);
eocdr->centralDirectoryOffset = fileGetU64(file);
eocdr->commentLen = eocdr->eocdrSize - 44;
if (eocdr->commentLen > UINT16_MAX) {
fprintf(stderr, "Corrupted file comment length : 0x%08" PRIX64 "\n", eocdr->commentLen);
return 0;
}
if (eocdr->commentLen > 0) {
eocdr->comment = OPENSSL_malloc(eocdr->commentLen);
size = fread(eocdr->comment, 1, eocdr->commentLen, file);
if (size != eocdr->commentLen) {
return 0;
}
}
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;
}
return 1;
}
static int get_current_position(BIO *bio, uint64_t *offset)
{
FILE *file = NULL;
int64_t pos;
if (BIO_get_fp(bio, &file) != 1 || file == NULL) {
fprintf(stderr, "BIO_get_fp() failed\n");
return 0;
}
pos = ftello(file);
if (pos < 0) {
return 0;
}
*offset = (uint64_t)pos;
return 1;
}
static uint64_t fileGetU64(FILE *file)
{
uint64_t l = fileGetU32(file);
uint64_t h = fileGetU32(file);
return h << 32 | l;
}
static uint32_t fileGetU32(FILE *file)
{
uint8_t b[4];
size_t size = fread(b, 1, 4, file);
if (size != 4) {
return 0;
}
return (uint32_t)(b[3] << 24 | b[2] << 16 | b[1] << 8 | b[0]);
}
static uint16_t fileGetU16(FILE *file)
{
uint8_t b[2];
size_t size = fread(b, 1, 2, file);
if (size != 2) {
return 0;
}
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);
}