Skip to content

Commit ce42171

Browse files
committed
Added support for Excel macro-enabled workbook files (XLSM)
1 parent 5ac11e9 commit ce42171

File tree

5 files changed

+91
-40
lines changed

5 files changed

+91
-40
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
### 2.11 (unreleased)
44

5+
- added support for Excel macro-enabled workbook files (XLSM)
6+
57
### 2.10 (2025.06.23)
68

79
- added JavaScript signing

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ tool would fail. And, so, osslsigncode was born.
2323

2424
## WHAT CAN IT DO?
2525

26-
It can sign and timestamp PE (EXE/SYS/DLL/etc), CAB, CAT, MSI and APPX files,
26+
It can sign and timestamp PE (EXE/SYS/DLL/etc), CAB, CAT, MSI, APPX and XLSM files,
2727
as well as script files with extensions `.ps1`, `.ps1xml`, `.psc1`, `.psd1`,
2828
`.psm1`, `.cdxml`, `.mof`, and `.js`.
2929
It supports the equivalent of signtool.exe's "-j javasign.dll -jp low",

appx.c

Lines changed: 86 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ struct appx_ctx_st {
237237
u_char *existingDataHash;
238238
u_char *existingCIHash;
239239
int isBundle;
240+
int isAppxBlockMap;
240241
const EVP_MD *md;
241242
int hashlen;
242243
} appx_ctx_t;
@@ -282,7 +283,8 @@ static int appx_extract_hashes(FILE_FORMAT_CTX *ctx, SpcIndirectDataContent *con
282283
static int appx_compare_hashes(FILE_FORMAT_CTX *ctx);
283284
static int appx_remove_ct_signature_entry(ZIP_FILE *zip, ZIP_CENTRAL_DIRECTORY_ENTRY *entry);
284285
static int appx_append_ct_signature_entry(ZIP_FILE *zip, ZIP_CENTRAL_DIRECTORY_ENTRY *entry);
285-
static const EVP_MD *appx_get_md(ZIP_FILE *zip);
286+
static const EVP_MD *appx_get_md(FILE_FORMAT_CTX *ctx, const EVP_MD *md, ZIP_FILE *zip);
287+
static const EVP_MD *appx_get_md_from_signature(FILE_FORMAT_CTX *ctx);
286288
static ZIP_CENTRAL_DIRECTORY_ENTRY *zipGetCDEntryByName(ZIP_FILE *zip, const char *name);
287289
static void zipWriteCentralDirectoryEntry(BIO *bio, uint64_t *sizeOnDisk, ZIP_CENTRAL_DIRECTORY_ENTRY *entry, uint64_t offsetDiff);
288290
static int zipAppendSignatureFile(BIO *bio, ZIP_FILE *zip, uint8_t *data, uint64_t dataSize);
@@ -332,7 +334,6 @@ static void bioAddU16(BIO *bio, uint16_t v);
332334
static FILE_FORMAT_CTX *appx_ctx_new(GLOBAL_OPTIONS *options, BIO *hash, BIO *outdata)
333335
{
334336
FILE_FORMAT_CTX *ctx;
335-
const EVP_MD *md;
336337
ZIP_FILE *zip = openZip(options->infile);
337338

338339
/* squash unused parameter warnings */
@@ -345,22 +346,18 @@ static FILE_FORMAT_CTX *appx_ctx_new(GLOBAL_OPTIONS *options, BIO *hash, BIO *ou
345346
if (options->verbose) {
346347
zipPrintCentralDirectory(zip);
347348
}
348-
md = appx_get_md(zip);
349-
if (!md) {
350-
freeZip(zip);
351-
return NULL; /* FAILED */
352-
}
353349
ctx = OPENSSL_malloc(sizeof(FILE_FORMAT_CTX));
354350
ctx->appx_ctx = OPENSSL_zalloc(sizeof(appx_ctx_t));
355351
ctx->appx_ctx->zip = zip;
356352
ctx->format = &file_format_appx;
357353
ctx->options = options;
358-
ctx->appx_ctx->md = md;
354+
ctx->appx_ctx->md = appx_get_md(ctx, options->md, zip);
359355
if (zipGetCDEntryByName(zip, APPXBUNDLE_MANIFEST_FILENAME)) {
360356
ctx->appx_ctx->isBundle = 1;
361357
}
362-
if (options->cmd == CMD_SIGN || options->cmd==CMD_ATTACH
363-
|| options->cmd==CMD_ADD || options->cmd == CMD_EXTRACT_DATA) {
358+
if (ctx->appx_ctx->isAppxBlockMap &&
359+
(options->cmd == CMD_SIGN || options->cmd==CMD_ATTACH
360+
|| options->cmd==CMD_ADD || options->cmd == CMD_EXTRACT_DATA)) {
364361
printf("Warning: Ignore -h option, use the hash algorithm specified in AppxBlockMap.xml\n");
365362
}
366363
if (options->pagehash == 1)
@@ -513,7 +510,6 @@ static PKCS7 *appx_pkcs7_extract(FILE_FORMAT_CTX *ctx)
513510

514511
/* Check if the signature exists */
515512
if (!zipEntryExist(ctx->appx_ctx->zip, APP_SIGNATURE_FILENAME)) {
516-
fprintf(stderr, "%s does not exist\n", APP_SIGNATURE_FILENAME);
517513
return NULL; /* FAILED */
518514
}
519515
dataSize = zipReadFileDataByName(&data, ctx->appx_ctx->zip, APP_SIGNATURE_FILENAME);
@@ -798,14 +794,17 @@ static BIO *appx_calculate_hashes(FILE_FORMAT_CTX *ctx)
798794
{
799795
uint64_t cdOffset = 0;
800796

801-
ctx->appx_ctx->calculatedBMHash = zipCalcDigest(ctx->appx_ctx->zip, BLOCK_MAP_FILENAME, ctx->appx_ctx->md);
797+
if (ctx->appx_ctx->isAppxBlockMap) {
798+
ctx->appx_ctx->calculatedBMHash = zipCalcDigest(ctx->appx_ctx->zip, BLOCK_MAP_FILENAME, ctx->appx_ctx->md);
799+
}
802800
ctx->appx_ctx->calculatedCTHash = zipCalcDigest(ctx->appx_ctx->zip, CONTENT_TYPES_FILENAME, ctx->appx_ctx->md);
803801
ctx->appx_ctx->calculatedDataHash = appx_calc_zip_data_hash(&cdOffset, ctx->appx_ctx->zip, ctx->appx_ctx->md);
804802
ctx->appx_ctx->calculatedCDHash = appx_calc_zip_central_directory_hash(ctx->appx_ctx->zip, ctx->appx_ctx->md, cdOffset);
805803
ctx->appx_ctx->calculatedCIHash = zipCalcDigest(ctx->appx_ctx->zip, CODE_INTEGRITY_FILENAME, ctx->appx_ctx->md);
806804

807-
if (!ctx->appx_ctx->calculatedBMHash || !ctx->appx_ctx->calculatedCTHash
808-
|| !ctx->appx_ctx->calculatedCDHash || !ctx->appx_ctx->calculatedDataHash) {
805+
if ((ctx->appx_ctx->isAppxBlockMap && !ctx->appx_ctx->calculatedBMHash)
806+
|| !ctx->appx_ctx->calculatedCTHash || !ctx->appx_ctx->calculatedCDHash
807+
|| !ctx->appx_ctx->calculatedDataHash) {
809808
fprintf(stderr, "One or more hashes calculation failed\n");
810809
return NULL; /* FAILED */
811810
}
@@ -843,18 +842,20 @@ static BIO *appx_hash_blob_get(FILE_FORMAT_CTX *ctx)
843842
pos += 4;
844843
memcpy(data + pos, ctx->appx_ctx->calculatedCTHash, (size_t)mdlen);
845844
pos += mdlen;
846-
memcpy(data + pos, AXBM_SIGNATURE, 4);
847-
pos += 4;
848-
memcpy(data + pos, ctx->appx_ctx->calculatedBMHash, (size_t)mdlen);
849-
pos += mdlen;
845+
if (ctx->appx_ctx->calculatedBMHash) {
846+
memcpy(data + pos, AXBM_SIGNATURE, 4);
847+
pos += 4;
848+
memcpy(data + pos, ctx->appx_ctx->calculatedBMHash, (size_t)mdlen);
849+
pos += mdlen;
850+
}
850851
if (ctx->appx_ctx->calculatedCIHash) {
851852
memcpy(data + pos, AXCI_SIGNATURE, 4);
852853
pos += 4;
853854
memcpy(data + pos, ctx->appx_ctx->calculatedCIHash, (size_t)mdlen);
854855
pos += mdlen;
855856
}
856857
if (ctx->options->verbose) {
857-
print_hash("Hash of file: ", "\n", data, pos);
858+
print_hash("Hash of file ", "\n", data, pos);
858859
}
859860
ctx->appx_ctx->hashlen = BIO_write(hashes, data, pos);
860861
OPENSSL_free(data);
@@ -1081,9 +1082,11 @@ static int appx_extract_hashes(FILE_FORMAT_CTX *ctx, SpcIndirectDataContent *con
10811082
uint8_t *data = content->messageDigest->digest->data;
10821083
int mdlen = EVP_MD_size(ctx->appx_ctx->md);
10831084
int pos = 4;
1085+
int expected_hashes = ctx->appx_ctx->isAppxBlockMap ? 4 : 3;
10841086

1085-
/* we are expecting at least 4 hashes + 4 byte header */
1086-
if (length < 4 * mdlen + 4) {
1087+
/* Expecting 4 hashes + 4-byte header if isAppxBlockMap is set,
1088+
* otherwise 3 hashes + 4-byte header */
1089+
if (length < expected_hashes * mdlen + 4) {
10871090
fprintf(stderr, "Hash too short\n");
10881091
return 0; /* FAILED */
10891092
}
@@ -1121,7 +1124,7 @@ static int appx_extract_hashes(FILE_FORMAT_CTX *ctx, SpcIndirectDataContent *con
11211124
fprintf(stderr, "Central directory hash missing\n");
11221125
return 0; /* FAILED */
11231126
}
1124-
if (!ctx->appx_ctx->existingBMHash) {
1127+
if (ctx->appx_ctx->isAppxBlockMap && !ctx->appx_ctx->existingBMHash) {
11251128
fprintf(stderr, "Block map hash missing\n");
11261129
return 0; /* FAILED */
11271130
}
@@ -1145,14 +1148,16 @@ static int appx_compare_hashes(FILE_FORMAT_CTX *ctx)
11451148
{
11461149
int mdtype = EVP_MD_nid(ctx->appx_ctx->md);
11471150

1148-
if (ctx->appx_ctx->calculatedBMHash && ctx->appx_ctx->existingBMHash) {
1149-
printf("Checking Block Map hashes:\n");
1150-
if (!compare_digests(ctx->appx_ctx->existingBMHash, ctx->appx_ctx->calculatedBMHash, mdtype)) {
1151+
if (ctx->appx_ctx->isAppxBlockMap) {
1152+
if (ctx->appx_ctx->calculatedBMHash && ctx->appx_ctx->existingBMHash) {
1153+
printf("Checking Block Map hashes:\n");
1154+
if (!compare_digests(ctx->appx_ctx->existingBMHash, ctx->appx_ctx->calculatedBMHash, mdtype)) {
1155+
return 0; /* FAILED */
1156+
}
1157+
} else {
1158+
fprintf(stderr, "Block map hash missing ctx->appx_ctx->isAppxBlockMap=%d\n", ctx->appx_ctx->isAppxBlockMap);
11511159
return 0; /* FAILED */
11521160
}
1153-
} else {
1154-
fprintf(stderr, "Block map hash missing\n");
1155-
return 0; /* FAILED */
11561161
}
11571162
if (ctx->appx_ctx->calculatedCTHash && ctx->appx_ctx->existingCTHash) {
11581163
printf("Checking Content Types hashes:\n");
@@ -1270,23 +1275,35 @@ static int appx_append_ct_signature_entry(ZIP_FILE *zip, ZIP_CENTRAL_DIRECTORY_E
12701275
}
12711276

12721277
/*
1273-
* Get a hash algorithm specified in the AppxBlockMap.xml file.
1278+
* Determine the message digest algorithm to use when verifying or signing
1279+
* 1. From the AppxBlockMap.xml (if available)
1280+
* 2. From the PKCS7 signature (if present)
1281+
* 3. Fall back to the provided default digest
1282+
* [in, out] ctx: structure holds input and output data
1283+
* [in] md: default digest algorithm to use if none found elsewhere
12741284
* [in] zip: structure holds specific ZIP data
12751285
* [returns] one of SHA256/SHA384/SHA512 digest algorithms
12761286
*/
1277-
static const EVP_MD *appx_get_md(ZIP_FILE *zip)
1287+
static const EVP_MD *appx_get_md(FILE_FORMAT_CTX *ctx, const EVP_MD *md, ZIP_FILE *zip)
12781288
{
12791289
uint8_t *data = NULL;
12801290
char *start, *end, *pos;
12811291
char *valueStart = NULL, *valueEnd = NULL;
1282-
const EVP_MD *md = NULL;
1292+
const EVP_MD *appx_md = NULL;
12831293
size_t slen, dataSize;
12841294

1295+
/* Try to get a hash algorithm specified in the AppxBlockMap.xml file */
12851296
dataSize = zipReadFileDataByName(&data, zip, BLOCK_MAP_FILENAME);
12861297
if (dataSize <= 0) {
1287-
fprintf(stderr, "Could not read: %s\n", BLOCK_MAP_FILENAME);
1288-
return NULL; /* FAILED */
1298+
/* Excel Spreadsheet Macro-enabled (XLSM) file */
1299+
appx_md = appx_get_md_from_signature(ctx); /* signed XLSM file */
1300+
if (!appx_md)
1301+
appx_md = md; /* unsigned, use default message digest algorithm */
1302+
printf("Message digest algorithm: %s\n", EVP_MD_get0_name(appx_md));
1303+
return appx_md;
12891304
}
1305+
ctx->appx_ctx->isAppxBlockMap = 1; /* AppxBlockMap.xml detected */
1306+
12901307
start = strstr((const char *)data, HASH_METHOD_TAG);
12911308
if (!start) {
12921309
fprintf(stderr, "Parse error: tag: %s not found in %s\n", HASH_METHOD_TAG, BLOCK_MAP_FILENAME);
@@ -1322,20 +1339,52 @@ static const EVP_MD *appx_get_md(ZIP_FILE *zip)
13221339
slen = (size_t)(valueEnd - valueStart + 1);
13231340
if (strlen(HASH_METHOD_SHA256) == slen && !memcmp(valueStart, HASH_METHOD_SHA256, slen)) {
13241341
printf("Hash method is SHA256\n");
1325-
md = EVP_sha256();
1342+
appx_md = EVP_sha256();
13261343
} else if (strlen(HASH_METHOD_SHA384) == slen && !memcmp(valueStart, HASH_METHOD_SHA384, slen)) {
13271344
printf("Hash method is SHA384\n");
1328-
md = EVP_sha384();
1345+
appx_md = EVP_sha384();
13291346
} else if (strlen(HASH_METHOD_SHA512) == slen && !memcmp(valueStart, HASH_METHOD_SHA512, slen)) {
13301347
printf("Hash method is SHA512\n");
1331-
md = EVP_sha512();
1348+
appx_md = EVP_sha512();
13321349
} else {
13331350
fprintf(stderr, "Unsupported hash method\n");
13341351
OPENSSL_free(data);
13351352
return NULL; /* FAILED */
13361353
}
13371354
OPENSSL_free(data);
1338-
return md;
1355+
return appx_md;
1356+
}
1357+
1358+
/*
1359+
* Extract the message digest algorithm used in the PKCS7 signature
1360+
* [in] ctx: structure holds input and output data
1361+
*/
1362+
static const EVP_MD *appx_get_md_from_signature(FILE_FORMAT_CTX *ctx)
1363+
{
1364+
1365+
int mdtype = -1;
1366+
PKCS7 *p7 = appx_pkcs7_extract(ctx);
1367+
1368+
if (!p7)
1369+
return NULL; /* FAILED */
1370+
1371+
if (is_content_type(p7, SPC_INDIRECT_DATA_OBJID)) {
1372+
ASN1_STRING *content_val = p7->d.sign->contents->d.other->value.sequence;
1373+
const u_char *p = content_val->data;
1374+
SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &p, content_val->length);
1375+
1376+
if (idc) {
1377+
if (idc->messageDigest && idc->messageDigest->digest && idc->messageDigest->digestAlgorithm) {
1378+
mdtype = OBJ_obj2nid(idc->messageDigest->digestAlgorithm->algorithm);
1379+
}
1380+
SpcIndirectDataContent_free(idc);
1381+
}
1382+
}
1383+
if (mdtype == -1) {
1384+
fprintf(stderr, "Failed to extract current message digest\n");
1385+
return NULL; /* FAILED */
1386+
}
1387+
return EVP_get_digestbynid(mdtype);
13391388
}
13401389

13411390
/*

cmake/CMakeTest.cmake

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ if(Python3_FOUND)
7575
endif(NOT client_result)
7676
endif(EXISTS "${LOGS}/url.log")
7777

78-
set(extensions_all "exe" "ex_" "msi" "256appx" "512appx" "cat" "ps1" "psc1" "mof" "js")
79-
set(extensions_nocat "exe" "ex_" "msi" "256appx" "512appx" "ps1" "psc1" "mof" "js")
78+
set(extensions_all "exe" "ex_" "msi" "256appx" "512appx" "xlsm" "cat" "ps1" "psc1" "mof" "js")
79+
set(extensions_nocat "exe" "ex_" "msi" "256appx" "512appx" "xlsm" "ps1" "psc1" "mof" "js")
8080
set(extensions_nocatappx "exe" "ex_" "msi" "ps1" "psc1" "mof" "js")
8181
set(formats "pem" "der")
8282

tests/files/unsigned.xlsm

8.45 KB
Binary file not shown.

0 commit comments

Comments
 (0)