Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
- fixed heap out-of-bounds reads when parsing malformed APPX ZIP64 extra fields
- fixed heap out-of-bounds reads when verifying malformed PKCS#7 signature attributes
- fixed undefined behavior when copying an empty MSI signature blob
- fixed timestamp verification of signatures with more than one timestamp
attribute
- fixed RFC 3161 timestamp verification to require a full-length message
imprint
- fixed a crash with more than 256 "-t" or "-ts" options

### 2.14 (2026.07.20)

Expand Down
38 changes: 38 additions & 0 deletions cmake/CMakeTest.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ if(Python3_FOUND)
"${CMAKE_CURRENT_SOURCE_DIR}/tests/client_http.py"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/make_certificates.py"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/make_large_pe.py"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/make_crafted_timestamps.py"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/check_pe_checksum.py"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/start_server.py"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/exec.py"
Expand Down Expand Up @@ -696,6 +697,43 @@ if(Python3_FOUND AND NOT cryptography_error)
message(STATUS "CTest skips some tests")
endif(OPENSSL_VERSION VERSION_GREATER_EQUAL "3.0.0" OR CURL_FOUND)

### Verify crafted timestamps ###

# The signer certificate is valid only during 2018; tokens are signed with
# the test TSA key and need no timestamping server
add_test(NAME "make_crafted_timestamps"
COMMAND ${Python3_EXECUTABLE} "${TEST_DIR}/make_crafted_timestamps.py"
"--osslsigncode" ${OSSLSIGNCODE}
"--certs" "${CERTS}"
"--in" "${FILES}/unsigned.exe"
"--out" "${FILES}")
set_tests_properties("make_crafted_timestamps" PROPERTIES
DEPENDS "start_server")
list(APPEND ALL_TESTS "make_crafted_timestamps")

# ok: a valid 2018 timestamp; the others are expected to fail:
# late: a valid 2030 timestamp, when the certificate had expired
# empty, short: an empty or 1-byte message imprint
# two_attrs: the 2030 timestamp and a second, unverifiable timestamp attribute
foreach(crafted "ok" "late" "empty" "short" "two_attrs")
add_test(NAME "verify_crafted_ts_${crafted}"
COMMAND ${Python3_EXECUTABLE} ${EXEC} ${OSSLSIGNCODE} "verify"
"-CAfile" "${CERTS}/CACert.pem"
"-CRLfile" "${CERTS}/CACertCRL.pem"
"-TSA-CAfile" "${CERTS}/TSACA.pem"
"-TSA-CRLfile" "${CERTS}/TSACertCRL.pem"
"-ignore-cdp"
"-in" "${FILES}/ts_crafted_${crafted}.exe")
set_tests_properties("verify_crafted_ts_${crafted}" PROPERTIES
DEPENDS "make_crafted_timestamps")
if(NOT crafted STREQUAL "ok")
set_tests_properties("verify_crafted_ts_${crafted}" PROPERTIES
WILL_FAIL TRUE)
endif(NOT crafted STREQUAL "ok")
list(APPEND ALL_TESTS "verify_crafted_ts_${crafted}")
set(OUTPUT_FILES ${OUTPUT_FILES} "${FILES}/ts_crafted_${crafted}.exe")
endforeach(crafted "ok" "late" "empty" "short" "two_attrs")

# Delete test files
set(names "signed" "nested" "revoked" "removed" "added")
foreach(ext ${extensions_all})
Expand Down
70 changes: 41 additions & 29 deletions osslsigncode.c
Original file line number Diff line number Diff line change
Expand Up @@ -2215,12 +2215,11 @@ static int verify_timestamp_token(PKCS7 *p7, CMS_ContentInfo *timestamp)
TS_TST_INFO *token = d2i_TS_TST_INFO(NULL, &p, len);

if (token) {
BIO *bhash;
u_char mdbuf[EVP_MAX_MD_SIZE];
ASN1_OCTET_STRING *hash;
const ASN1_OBJECT *aoid;
const u_char *hash_data;
int hash_len, md_nid;
int hash_len, md_len, md_nid;
const EVP_MD *md;
TS_MSG_IMPRINT *msg_imprint = TS_TST_INFO_get_msg_imprint(token);
const X509_ALGOR *alg = TS_MSG_IMPRINT_get_algo(msg_imprint);
Expand All @@ -2230,35 +2229,29 @@ static int verify_timestamp_token(PKCS7 *p7, CMS_ContentInfo *timestamp)
X509_ALGOR_get0(&aoid, NULL, NULL, alg);
md_nid = OBJ_obj2nid(aoid);
md = EVP_get_digestbynid(md_nid);
md_len = md ? EVP_MD_size(md) : 0;

/* compute a hash from the encrypted message digest value of the file */
bhash = BIO_new(BIO_f_md());
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
if (!BIO_set_md(bhash, md)) {
fprintf(stderr, "Unable to set the message digest of BIO\n");
BIO_free_all(bhash);
if (md_len <= 0 || digest_len < 0
|| !EVP_Digest(digest, (size_t)digest_len, mdbuf, NULL, md, NULL)) {
fprintf(stderr, "Unable to compute the timestamp message imprint: %s\n",
(md_nid == NID_undef) ? "UNKNOWN" : OBJ_nid2ln(md_nid));
TS_TST_INFO_free(token);
return 0; /* FAILED */
}
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
BIO_push(bhash, BIO_new(BIO_s_null()));
BIO_write(bhash, digest, digest_len);
BIO_gets(bhash, (char*)mdbuf, EVP_MD_size(md));
BIO_free_all(bhash);

/* compare the provided hash against the computed hash */
hash =TS_MSG_IMPRINT_get_msg(msg_imprint);
/*
* Compare the provided hash against the computed hash.
* A shorter imprint would only bind a prefix of the digest,
* and an empty one would match any signature.
*/
hash = TS_MSG_IMPRINT_get_msg(msg_imprint);
hash_data = ASN1_STRING_get0_data(hash);
hash_len = ASN1_STRING_length(hash);
if (memcmp(mdbuf, hash_data, (size_t)hash_len)) {
if (hash_len != md_len || memcmp(mdbuf, hash_data, (size_t)md_len)) {
printf("Hash value mismatch:\n\tMessage digest algorithm: %s\n",
(md_nid == NID_undef) ? "UNKNOWN" : OBJ_nid2ln(md_nid));
print_hash("\tComputed message digest", "", mdbuf, EVP_MD_size(md));
print_hash("\tComputed message digest", "", mdbuf, md_len);
print_hash("\tReceived message digest", "", hash_data, hash_len);
printf("\nFile's message digest verification: failed\n");
TS_TST_INFO_free(token);
Expand Down Expand Up @@ -2845,6 +2838,7 @@ static time_t time_t_timestamp_get_attributes(CMS_ContentInfo **timestamp, PKCS7
/* Authenticode Timestamp - Policy OID: 1.2.840.113549.1.9.6 */
CMS_ContentInfo *cms;
PKCS7_SIGNER_INFO *countersi;
time_t ts_time;

value = (const ASN1_STRING *)X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_SEQUENCE, NULL);
if (value == NULL)
Expand All @@ -2856,17 +2850,21 @@ static time_t time_t_timestamp_get_attributes(CMS_ContentInfo **timestamp, PKCS7
ERR_print_errors_fp(stderr);
continue;
}
time = time_t_get_si_time(countersi);
if (time != INVALID_TIME) {
ts_time = time_t_get_si_time(countersi);
if (ts_time != INVALID_TIME) {
/* cms_get_timestamp() takes ownership of countersi */
cms = cms_get_timestamp(p7->d.sign, countersi);
if (cms) {
if (!print_cms_timestamp(cms, time)) {
if (!print_cms_timestamp(cms, ts_time)) {
CMS_ContentInfo_free(cms);
printf("Warning: Authenticode Timestamp could not be decoded correctly\n");
ERR_print_errors_fp(stderr);
continue;
}
/* Only take the time together with the timestamp it came from */
CMS_ContentInfo_free(*timestamp);
*timestamp = cms;
time = ts_time;
} else {
printf("Warning: Corrupt Authenticode Timestamp embedded content\n");
}
Expand All @@ -2877,6 +2875,7 @@ static time_t time_t_timestamp_get_attributes(CMS_ContentInfo **timestamp, PKCS7
} else if (!strcmp(object_txt, SPC_RFC3161_OBJID)) {
/* RFC3161 Timestamp - Policy OID: 1.3.6.1.4.1.311.3.3.1 */
CMS_ContentInfo *cms;
time_t ts_time;

value = (const ASN1_STRING *)X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_SEQUENCE, NULL);
if (value == NULL)
Expand All @@ -2888,15 +2887,18 @@ static time_t time_t_timestamp_get_attributes(CMS_ContentInfo **timestamp, PKCS7
ERR_print_errors_fp(stderr);
continue;
}
time = time_t_get_cms_time(cms);
if (time != INVALID_TIME) {
if (!print_cms_timestamp(cms, time)) {
ts_time = time_t_get_cms_time(cms);
if (ts_time != INVALID_TIME) {
if (!print_cms_timestamp(cms, ts_time)) {
CMS_ContentInfo_free(cms);
printf("Warning: RFC3161 Timestamp could not be decoded correctly\n");
ERR_print_errors_fp(stderr);
continue;
}
/* Only take the time together with the timestamp it came from */
CMS_ContentInfo_free(*timestamp);
*timestamp = cms;
time = ts_time;
} else {
printf("Warning: Corrupt RFC3161 Timestamp embedded content\n");
CMS_ContentInfo_free(cms);
Expand Down Expand Up @@ -3048,7 +3050,7 @@ static time_t time_t_get_cms_time(CMS_ContentInfo *cms)
* Create new CMS_ContentInfo struct for Authenticode Timestamp.
* This struct does not contain any TS_TST_INFO as specified in RFC 3161.
* [in] p7_signed: PKCS#7 signedData structure
* [in] countersignature: Authenticode Timestamp decoded to PKCS7_SIGNER_INFO
* [in] countersignature: Authenticode Timestamp decoded to PKCS7_SIGNER_INFO (consumed)
* [returns] pointer to CMS_ContentInfo structure
*/
static CMS_ContentInfo *cms_get_timestamp(PKCS7_SIGNED *p7_signed,
Expand All @@ -3063,14 +3065,15 @@ static CMS_ContentInfo *cms_get_timestamp(PKCS7_SIGNED *p7_signed,

p7 = PKCS7_new();
si = sk_PKCS7_SIGNER_INFO_value(p7_signed->signer_info, 0);
if (si == NULL)
if (!p7 || !si)
goto out;

/* Create new signed PKCS7 timestamp structure. */
if (!PKCS7_set_type(p7, NID_pkcs7_signed))
goto out;
if (!PKCS7_add_signer(p7, countersignature))
goto out;
countersignature = NULL; /* owned by p7 */
for (i = 0; i < sk_X509_num(p7_signed->cert); i++) {
if (!PKCS7_add_certificate(p7, sk_X509_value(p7_signed->cert, i)))
goto out;
Expand Down Expand Up @@ -3100,6 +3103,7 @@ static CMS_ContentInfo *cms_get_timestamp(PKCS7_SIGNED *p7_signed,
out:
if (!cms)
ERR_print_errors_fp(stderr);
PKCS7_SIGNER_INFO_free(countersignature);
PKCS7_free(p7);
return cms;
}
Expand Down Expand Up @@ -4910,12 +4914,20 @@ static int main_configure(int argc, char **argv, GLOBAL_OPTIONS *options)
usage(argv0, "all");
return 0; /* FAILED */
}
if (options->nturl >= MAX_TS_SERVERS) {
fprintf(stderr, "Too many -t options (maximum %d)\n", MAX_TS_SERVERS);
return 0; /* FAILED */
}
options->turl[options->nturl++] = *(++argv);
} else if ((cmd == CMD_SIGN || cmd == CMD_ADD) && !strcmp(*argv, "-ts")) {
if (--argc < 1) {
usage(argv0, "all");
return 0; /* FAILED */
}
if (options->ntsurl >= MAX_TS_SERVERS) {
fprintf(stderr, "Too many -ts options (maximum %d)\n", MAX_TS_SERVERS);
return 0; /* FAILED */
}
options->tsurl[options->ntsurl++] = *(++argv);
} else if ((cmd == CMD_SIGN || cmd == CMD_ADD || cmd == CMD_VERIFY) && !strcmp(*argv, "-p")) {
if (--argc < 1) {
Expand Down
Loading
Loading