/* * APPX file support library * https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT * * Copyright (C) Maciej Panek * Copyright (C) 2023 Michał Trojnara * Author: Małgorzata Olszówka * * APPX files do not support nesting (multiple signature) */ #define _FILE_OFFSET_BITS 64 #include "osslsigncode.h" #include "helpers.h" #include #include #if defined(_MSC_VER) #define fseeko _fseeki64 #define ftello _ftelli64 #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 = ""; static const char *SIGNATURE_CONTENT_TYPES_CLOSING_TAG = ""; 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; /* 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_check_file(FILE_FORMAT_CTX *ctx, int detached); 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 BIO *appx_bio_free(BIO *hash, BIO *outdata); static void appx_ctx_cleanup(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata); 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, .check_file = appx_check_file, .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 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) { printf("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; } /* * Check if the signature exists. * [in] ctx: structure holds input and output data * [in] detached: embedded/detached PKCS#7 signature switch * [returns] 0 on error or 1 on success */ static int appx_check_file(FILE_FORMAT_CTX *ctx, int detached) { if (detached) { printf("APPX format does not support detached PKCS#7 signature\n"); return 0; /* FAILED */ } if (!zipEntryExist(ctx->appx_ctx->zip, APP_SIGNATURE_FILENAME)) { printf("%s does not exist\n", APP_SIGNATURE_FILENAME); return 0; /* FAILED */ } return 1; /* OK */ } /* * 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)) { printf("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)) { printf("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; 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)) { printf("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) { 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) { printf("Not a valid .appx file: content types file missing\n"); return 1; /* FAILED */ } if (!appx_remove_ct_signature_entry(zip, entry)) { printf("Failed to remove signature entry\n"); return 1; /* FAILED */ } for (entry = zip->centralDirectoryHead; entry != NULL; entry = entry->next) { if (noEntries == zip->centralDirectoryRecordCount) { printf("Warning: Corrupted central directory structure\n"); return 1; /* FAILED */ } noEntries++; if (!entry->fileName || (entry->fileNameLen == 0)) { printf("Warning: 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)) { printf("Unable to get offset\n"); return 1; /* FAILED */ } if (!appx_write_central_directory(outdata, zip, 1, cdOffset)) { printf("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) { printf("Not a valid .appx file: content types file missing\n"); return 1; /* FAILED */ } if (!appx_append_ct_signature_entry(ctx->appx_ctx->zip, entry)) { return 1; /* FAILED */ } return 0; /* 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) { printf("Creating a new signature failed\n"); BIO_free_all(hashes); return NULL; /* FAILED */ } if (!add_indirect_data_object(p7)) { printf("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) { printf("Failed to get spcIndirectDataContent\n"); PKCS7_free(p7); return NULL; /* FAILED */ } if (!sign_spc_indirect_data_content(p7, content)) { printf("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) { printf("Warning: Corrupted central directory structure\n"); return 1; /* FAILED */ } noEntries++; last = entry; if (!entry->fileName || (entry->fileNameLen == 0)) { printf("Warning: 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); printf("Failed to append zip file\n"); return 1; /* FAILED */ } OPENSSL_free(der); OPENSSL_free(blob); if (!get_current_position(outdata, &cdOffset)) { printf("Unable to get offset\n"); return 1; /* FAILED */ } if (!appx_write_central_directory(outdata, zip, 0, cdOffset)) { printf("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 BIO *appx_bio_free(BIO *hash, BIO *outdata) { BIO_free_all(outdata); BIO_free_all(hash); return NULL; /* OK */ } /* * 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, BIO *hash, BIO *outdata) { if (outdata) { BIO_free_all(hash); BIO_free_all(outdata); } 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) { printf("One or more hashes calculation failed\n"); return NULL; /* FAILED */ } if (zipEntryExist(ctx->appx_ctx->zip, CODE_INTEGRITY_FILENAME) && !ctx->appx_ctx->calculatedCIHash) { printf("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)) { printf("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)) { printf("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) { printf("Warning: Corrupted central directory structure\n"); return 0; /* FAILED */ } if (!entry->fileName || (entry->fileNameLen == 0)) { printf("Warning: 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)) { printf("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) { printf("Warning: Corrupted central directory structure\n"); BIO_free_all(bhash); return NULL; /* FAILED */ } noEntries++; if (!entry->fileName || (entry->fileNameLen == 0)) { printf("Warning: 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)) { printf("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(stdout, 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) { printf("Hash too short\n"); return 0; /* FAILED */ } if (memcmp(data, APPX_SIGNATURE, 4)) { printf("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 { printf("Invalid hash signature\n"); return 0; /* FAILED */ } pos += mdlen + 4; } if (!ctx->appx_ctx->existingDataHash) { printf("File hash missing\n"); return 0; /* FAILED */ } if (!ctx->appx_ctx->existingCDHash) { printf("Central directory hash missing\n"); return 0; /* FAILED */ } if (!ctx->appx_ctx->existingBMHash) { printf("Block map hash missing\n"); return 0; /* FAILED */ } if (!ctx->appx_ctx->existingCTHash) { printf("Content types hash missing\n"); return 0; /* FAILED */ } if (zipEntryExist(ctx->appx_ctx->zip, CODE_INTEGRITY_FILENAME) && !ctx->appx_ctx->existingCIHash) { printf("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 { printf("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 { printf("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 { printf("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 { printf("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 { printf("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("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) { printf("%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) { printf("Could not read: %s\n", BLOCK_MAP_FILENAME); return NULL; /* FAILED */ } start = strstr((const char *)data, HASH_METHOD_TAG); if (!start) { printf("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) { printf("Parse error: data too short in %s\n", BLOCK_MAP_FILENAME); OPENSSL_free(data); return NULL; /* FAILED */ } end = strstr((const char *)start, ">"); if (!end) { printf("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) { printf("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 { printf("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) { printf("Warning: Corrupted central directory structure\n"); return NULL; /* FAILED */ } noEntries++; if (!entry->fileName || (entry->fileNameLen == 0)) { printf("Warning: 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) { printf("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)) { printf("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) { printf("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) { printf("Corrupted relative offset of local header : 0x%08lX\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) { printf("Corrupted compressedSize : 0x%08lX\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) { printf("Warning: Corrupted central directory structure\n"); return 0; /* FAILED */ } noEntries++; if (!entry->fileName || (entry->fileNameLen == 0)) { printf("Warning: 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)) { printf("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) { printf("Warning: Corrupted central directory structure\n"); return 0; /* FAILED */ } noEntries++; if (!entry->fileName || (entry->fileNameLen == 0)) { printf("Warning: 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) { printf("Corrupted relative offset of local header : 0x%08lX\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) { printf("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) { printf("Corrupted compressedSize : 0x%08lX\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) { printf("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 { printf("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)) { printf("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)) { printf("Corrupted compressedSize : 0x%08lX\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)) { printf("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) { printf("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 { printf("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 { printf("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) { printf("Corrupted end of central directory locator offset : 0x%08lX\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) { printf("Corrupted total number of entries in the central directory : 0x%08lX\n", zip->centralDirectoryRecordCount); freeZip(zip); return NULL; /* FAILED */ } } if (zip->centralDirectoryOffset >= (uint64_t)zip->fileSize) { printf("Corrupted central directory offset : 0x%08lX\n", zip->centralDirectoryOffset); freeZip(zip); return NULL; /* FAILED */ } if (!zipReadCentralDirectory(zip, file)) { freeZip(zip); return NULL; /* FAILED */ } return 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); } /* * 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 { printf("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)) { printf("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) { printf("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 { printf("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 { printf("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 { printf("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 { printf("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) { printf("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)) { printf("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)) { printf("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) { printf("Corrupted file comment length : 0x%08lX\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) { printf("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: */