From 5a1d0205e13a794b136184912fa38a08c175e5d4 Mon Sep 17 00:00:00 2001 From: olszomal Date: Mon, 25 May 2026 08:50:06 +0200 Subject: [PATCH 1/3] cmake: add documentation generation, GNU install dirs, and uninstall target --- CMakeLists.txt | 26 +- cmake/CMakeDoc.cmake | 44 +++ cmake/cmake_uninstall.cmake.in | 24 ++ osslsigncode.md | 554 +++++++++++++++++++++++++++++++++ 4 files changed, 645 insertions(+), 3 deletions(-) create mode 100644 cmake/CMakeDoc.cmake create mode 100644 cmake/cmake_uninstall.cmake.in create mode 100644 osslsigncode.md diff --git a/CMakeLists.txt b/CMakeLists.txt index fa2be03..470991f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,14 +97,25 @@ set_target_properties(osslsigncode PROPERTIES INSTALL_RPATH_USE_LINK_PATH TRUE) # testing with CTest include(CMakeTest) +# documentation with Pandoc +include(CMakeDoc) + # installation rules for a project -set(BINDIR "${CMAKE_INSTALL_PREFIX}/bin") -install(TARGETS osslsigncode RUNTIME DESTINATION ${BINDIR}) +include(GNUInstallDirs) + +install(TARGETS osslsigncode RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +install(FILES + "${PROJECT_SOURCE_DIR}/README.md" + "${PROJECT_SOURCE_DIR}/NEWS.md" + DESTINATION "${CMAKE_INSTALL_DOCDIR}") + if(UNIX) include(CMakeDist) else(UNIX) install( - DIRECTORY ${PROJECT_BINARY_DIR}/ DESTINATION ${BINDIR} + DIRECTORY ${PROJECT_BINARY_DIR}/ + DESTINATION ${CMAKE_INSTALL_BINDIR} FILES_MATCHING PATTERN "*.dll" PATTERN "vcpkg_installed" EXCLUDE @@ -112,6 +123,15 @@ else(UNIX) PATTERN "Testing" EXCLUDE) endif(UNIX) +# uninstall target +configure_file( + "${PROJECT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" + "${PROJECT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + +add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P "${PROJECT_BINARY_DIR}/cmake_uninstall.cmake") + #[[ Local Variables: c-basic-offset: 4 diff --git a/cmake/CMakeDoc.cmake b/cmake/CMakeDoc.cmake new file mode 100644 index 0000000..f66c8a6 --- /dev/null +++ b/cmake/CMakeDoc.cmake @@ -0,0 +1,44 @@ +# documentation with Pandoc +# cmake --build . + +find_program(PANDOC pandoc) + +if(NOT PANDOC) + message(WARNING "CMakeDoc: pandoc not found, documentation disabled") + return() +endif(NOT PANDOC) + +set(DOC_MD "${PROJECT_SOURCE_DIR}/osslsigncode.md") + +if(NOT EXISTS "${DOC_MD}") + message(WARNING "CMakeDoc: markdown source not found: ${DOC_MD}") + return() +endif(NOT EXISTS "${DOC_MD}") + +set(MAN_PAGE "${PROJECT_BINARY_DIR}/osslsigncode.1") +set(HTML_PAGE "${PROJECT_BINARY_DIR}/osslsigncode.html") + +add_custom_command( + OUTPUT "${MAN_PAGE}" + COMMAND "${PANDOC}" -s "${DOC_MD}" -t man -o "${MAN_PAGE}" + DEPENDS "${DOC_MD}" + COMMENT "CMakeDoc: generating man page" + VERBATIM) + +add_custom_command( + OUTPUT "${HTML_PAGE}" + COMMAND "${PANDOC}" -s --toc --toc-depth=2 "${DOC_MD}" -t html -o "${HTML_PAGE}" + DEPENDS "${DOC_MD}" + COMMENT "CMakeDoc: generating HTML documentation" + VERBATIM) + +add_custom_target(docs ALL DEPENDS "${MAN_PAGE}" "${HTML_PAGE}") + +#[[ +Local Variables: + c-basic-offset: 4 + tab-width: 4 + indent-tabs-mode: nil +End: + vim: set ts=4 expandtab: +]] diff --git a/cmake/cmake_uninstall.cmake.in b/cmake/cmake_uninstall.cmake.in new file mode 100644 index 0000000..25eb6e7 --- /dev/null +++ b/cmake/cmake_uninstall.cmake.in @@ -0,0 +1,24 @@ +# uninstall target +# +# CMake does not provide a built-in uninstall target. +# This target removes files listed in install_manifest.txt, +# generated by the install step. +# +# cmake --build . --target uninstall + +if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest") +endif() + +file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +string(REPLACE "\n" ";" files "${files}") + +foreach(file ${files}) + message(STATUS "Removing ${file}") + + if(EXISTS "${file}" OR IS_SYMLINK "${file}") + file(REMOVE "${file}") + else() + message(STATUS "File does not exist: ${file}") + endif() +endforeach() diff --git a/osslsigncode.md b/osslsigncode.md new file mode 100644 index 0000000..4d923ec --- /dev/null +++ b/osslsigncode.md @@ -0,0 +1,554 @@ +--- +title: osslsigncode +lang: en-US +--- + +# NAME + +osslsigncode - Authenticode signing, timestamping, extraction, attachment, removal, and verification tool + +# SYNOPSIS + +`osslsigncode` [`--help`] [`--version`] + +`osslsigncode` `sign` +[`-certs` *file* | `-spc` *file* | `-pkcs12` *file*] +[`-key` *file-or-URI*] +[`-ac` *file*] +[`-pass` *password* | `-readpass` *file* | `-askpass`] +[`-pkcs11module` *module*] [`-pkcs11cert` *URI*] +[`-engine` *engine*] [`-provider` *provider*] +[`-login`] [`-engineCtrl` *command*[:*parameter*]] +[`-h` *digest*] +[`-n` *description*] [`-i` *URL*] +[`-jp` `low`] [`-comm`] [`-ph`] +[`-t` *URL* ... | `-ts` *URL* ...] +[`-TSA-certs` *file* `-TSA-key` *file-or-URI* [`-TSA-time` *unix-time*]] +[`-HTTPS-CAfile` *file*] [`-HTTPS-CRLfile` *file*] +[`-time` *unix-time*] +[`-addUnauthenticatedBlob` [`-blobFile` *file*]] +[`-nest`] [`-add-msi-dse`] [`-verbose`] [`-pem`] +`-in` *input* `-out` *output* + +`osslsigncode` `extract-data` +[`-pem`] [`-h` *digest*] [`-ph`] [`-add-msi-dse`] +`-in` *input* `-out` *output* + +`osslsigncode` `add` +[`-addUnauthenticatedBlob` [`-blobFile` *file*]] +[`-t` *URL* ... | `-ts` *URL* ...] +[`-TSA-certs` *file* `-TSA-key` *file-or-URI* [`-TSA-time` *unix-time*]] +[`-HTTPS-CAfile` *file*] [`-HTTPS-CRLfile` *file*] +[`-h` *digest*] [`-index` *n*] [`-verbose`] [`-add-msi-dse`] +`-in` *input* `-out` *output* + +`osslsigncode` `attach-signature` +`-sigin` *signature* +[`-h` *digest*] [`-nest`] [`-add-msi-dse`] +`-in` *input* `-out` *output* + +`osslsigncode` `extract-signature` +[`-pem`] +`-in` *input* `-out` *output* + +`osslsigncode` `remove-signature` +`-in` *input* `-out` *output* + +`osslsigncode` `verify` +`-in` *input* +[`-c` | `-catalog` *catalog-file*] +[`-CAfile` *file*] [`-CRLfile` *file*] +[`-HTTPS-CAfile` *file*] [`-HTTPS-CRLfile` *file*] +[`-TSA-CAfile` *file*] [`-TSA-CRLfile` *file*] +[`-p` *proxy*] [`-index` *n*] +[`-ignore-timestamp`] [`-ignore-cdp`] [`-ignore-crl`] +[`-time` *unix-time*] +[`-require-leaf-hash` *alg*:*hex*] +[`-verbose`] + +# DESCRIPTION + +`osslsigncode` signs and verifies Microsoft Authenticode signatures on +supported file formats. It can also extract data for detached signing, +attach an externally produced signature, add timestamps or unauthenticated +blobs to an existing signature, and remove an embedded signature. + +Supported input formats include PE files such as EXE, DLL, and SYS, CAB, +CAT, MSI, APPX, and several script file types, including `.ps1`, `.ps1xml`, +`.psc1`, `.psd1`, `.psm1`, `.cdxml`, `.mof`, and `.js`. + +The program supports these common workflows: + +- direct signing of an unsigned file +- detached signing via `extract-data`, `sign`, and `attach-signature` +- post-sign timestamping with `add` +- verification of embedded signatures or catalog signatures with `verify` + +If no subcommand is given, `sign` is assumed. + +# FORMATS + +Support is not identical across all file formats. + +In particular, detached-signature workflows, nested signatures, catalog-based +verification, and signature removal are format-dependent features. A command +that is valid for one supported file type may be unsupported for another. + +CAT files are a special case. They are detached catalog containers for +hashes of other files, not ordinary embedded-signature payloads. A CAT +file is itself a PKCS#7 structure containing authenticated entries for one +or more external files. In practice, the catalog signs file digests +recorded in the catalog, rather than embedding a signature into each +covered file. + +Because of this, CAT files behave differently from embedded-signature +formats. They do not support `attach-signature`, `remove-signature`, +`extract-data`, or nested signatures. + +MSI files are also a special case. They support an extended signature mode +controlled by `-add-msi-dse`. In this mode, the MSI signature covers file +metadata as well as file content. Detached-signing workflows and any later +re-signing or nesting operations must use a mode consistent with the MSI +file's existing signature structure. + +# COMMANDS + +## `sign` + +Create a new Authenticode signature. + +This command can sign a normal unsigned file, or it can sign PKCS#7 data +previously produced by `extract-data`. + +## `extract-data` + +Extract the PKCS#7 content to be signed later. This is used for detached +signing workflows. + +## `add` + +Add unauthenticated attributes to an existing signature, typically an +Authenticode timestamp, an RFC 3161 timestamp, or an unauthenticated blob. + +With `-index`, the selected signature in a multi-signature file is updated. + +## `attach-signature` + +Attach a detached PKCS#7 signature to an input file. + +With `-nest`, the new signature is attached as a nested signature instead of +replacing the primary one, if the file format supports nested signatures. + +## `extract-signature` + +Extract the embedded PKCS#7 signature from a signed file. + +## `remove-signature` + +Remove the embedded signature from a signed file. + +## `verify` + +Verify an embedded signature or a catalog signature. + +Verification may include digest consistency, certificate chain validation, +certificate revocation checking, timestamp validation, and optional checking +of the signer's leaf certificate hash. + +When verifying that a file is covered by a catalog, use `verify -catalog +catalog.cat -in file`. Verifying the CAT file by itself validates the +catalog signature; verifying with `-catalog` checks whether the specified +input file is covered by that catalog. + +# OPTIONS + +Some options are available only in particular builds or OpenSSL versions. +In particular, `-askpass` is build-dependent, `-provider` and `-nolegacy` +require OpenSSL 3, and engine-related options depend on engine support in the +build. + +## General options + +`--help` +: Show help text. With a subcommand, show help for that subcommand. + +`-v`, `--version` +: Show version information. + +`-in` *file* +: Input file. + +`-out` *file* +: Output file. Required for all commands except `verify`. + +`-verbose` +: Produce more detailed diagnostic output. + +## Signing material + +`-pkcs12` *file* +: Read the signing certificate and private key from a PKCS#12 container. + +`-certs`, `-spc` *file* +: Read the signing certificate chain. The historical alias `-spc` is accepted. + +`-key` *file-or-URI* +: Read the private key. This may also be a store or PKCS#11 URI. + +`-ac` *file* +: Add extra certificates to the signature block. + +`-pass` *password* +: Password or PIN for the key, token, or PKCS#12 container. + +`-readpass` *file* +: Read the password or PIN from *file*. Use `-` to read from standard input. + +`-askpass` +: Prompt for the password interactively. + +## PKCS#11, engines, and providers + +`-pkcs11module` *module* +: Path to a PKCS#11 module. + +`-pkcs11cert` *URI* +: PKCS#11 URI identifying the certificate object. + +`-provider` *provider* +: OpenSSL 3 provider to load. This is the preferred modern interface for + provider-based PKCS#11 use. + +`-engine`, `-pkcs11engine` *engine* +: OpenSSL engine identifier or path to a dynamic engine module. This + interface is retained for compatibility with builds and deployments that + still support engines. + +`-login` +: Force login to the token for engine-based PKCS#11 use. + +`-engineCtrl` *command*[:*parameter*] +: Pass a control command to the selected engine. + +`-nolegacy` +: On OpenSSL 3 builds, do not automatically load the legacy provider. + +## Signature contents and digest control + +`-h` `md5` | `sha1` | `sha2` | `sha256` | `sha384` | `sha512` +: Select the digest algorithm. The default is `sha256`. `sha2` and + `sha256` are equivalent. + +`-n` *description* +: Description of the signed content. + +`-i` *URL* +: Informational URL associated with the signed content. + +`-comm` +: Use Microsoft Commercial Code Signing purpose instead of the default + individual purpose. + +`-jp` `low` +: Add the Java CAB permission attribute. Only `low` is currently supported. + +`-ph` +: Generate page hashes for executable files. + +`-add-msi-dse` +: For MSI files, enable the `MsiDigitalSignatureEx` signing mode. In this + mode, the signature covers MSI metadata as well as file content. The + metadata portion includes stream names, sizes, and selected timestamps in + the MSI structure. This option changes the MSI signature format and should + be used consistently in any detached-signing workflow involving + `extract-data`, `sign`, `attach-signature`, or `add`. + + For a newly signed MSI, this mode is generally preferred because it extends + signing coverage beyond file content alone. For an already signed MSI, + however, the chosen mode must match the file's existing signature + structure. Switching between basic MSI signing and `MsiDigitalSignatureEx` + during re-signing or nested-signature operations can invalidate the + existing signature. + +`-pem` +: Write PKCS#7 output in PEM format instead of DER. + +## Timestamping and network options + +The following timestamping modes are **mutually exclusive** within a single +`sign` or `add` invocation: + +- Authenticode timestamping with `-t` +- RFC 3161 timestamping with `-ts` +- built-in RFC 3161 timestamp generation with `-TSA-certs` and `-TSA-key` + +`-t` *URL* +: Add an Authenticode timestamp from the specified URL. May be repeated. + +`-ts` *URL* +: Add an RFC 3161 timestamp from the specified URL. May be repeated. + +`-p` *proxy* +: Proxy used for timestamp or CRL retrieval. + +`-noverifypeer` +: Do not verify the TLS certificate of the remote timestamp service. + +`-HTTPS-CAfile` *file* +: PEM bundle used to verify HTTPS peers contacted by `osslsigncode`. + +`-HTTPS-CRLfile` *file* +: PEM CRL file used while verifying HTTPS peers. + +`-TSA-certs` *file* +: PEM certificate chain for locally generated RFC 3161 timestamps. + +`-TSA-key` *file-or-URI* +: Private key for locally generated RFC 3161 timestamps. + +`-TSA-time` *unix-time* +: Timestamp time for locally generated RFC 3161 responses. + +## Nested signatures and indexed operations + +`-nest` +: Add a nested signature instead of replacing the primary signature. + +`-index` *n* +: Select a signature by index for `add` or `verify`. Index 0 is the primary + signature. + +## Unauthenticated blob options + +`-addUnauthenticatedBlob` +: Add an unauthenticated blob to the signature. + +`-blobFile` *file* +: Read blob contents from *file*. If omitted, a placeholder blob is created. + +## Verification options + +`-c`, `-catalog` *file* +: Verify the input file against the specified catalog file. + +`-CAfile` *file* +: PEM bundle of trusted CA certificates for signer validation. + +`-CRLfile` *file* +: PEM file containing CRLs for signer validation. + +`-TSA-CAfile`, `-untrusted` *file* +: PEM bundle of trusted CA certificates for timestamp validation. + +`-TSA-CRLfile`, `-CRLuntrusted` *file* +: PEM file containing CRLs for timestamp validation. + +`-time`, `-st` *unix-time* +: Verification time. If a valid timestamp is present and used, chain + validation is normally performed at the timestamp time. + +`-ignore-timestamp` +: Skip verification of the timestamp signature. + +`-ignore-cdp` +: Do not fetch CRLs from CRL Distribution Points. + +`-ignore-crl` +: Disable CRL retrieval and CRL validation. + +`-require-leaf-hash` *alg*:*hex* +: Require the signer's leaf certificate to hash to the specified value. + The hash is computed over the DER encoding of the leaf certificate. + +# EXIT STATUS + +`0` +: Success. + +non-zero +: Failure. + +# DIAGNOSTICS + +Common causes of failure include: + +missing CA trust bundle +: On Unix-like systems, `verify` expects a readable CA bundle, either from + `-CAfile` or from a detected system default. + +detached-signing mismatch +: `extract-data`, `sign`, and `attach-signature` must use compatible + digest-affecting options such as `-h`, and where relevant `-ph` and + `-add-msi-dse`. + +unsupported format feature +: Some file formats do not support every subcommand or every signature mode. + +missing TSA trust chain +: Timestamp verification may fail unless the appropriate TSA trust anchors + are supplied with `-TSA-CAfile`, and where needed `-TSA-CRLfile`. + +conflicting timestamp modes +: `-t`, `-ts`, and built-in TSA signing cannot be combined in one command. + +MSI signature mode mismatch +: Re-signing or nesting an MSI signature must be consistent with whether the + file already uses `MsiDigitalSignatureEx`. Mixing modes may invalidate the + existing signature. + +# ENVIRONMENT + +`HTTP_PROXY`, `http_proxy` +: Default proxy for HTTP access if `-p` is not given. + +`HTTPS_PROXY`, `https_proxy` +: Default proxy for HTTPS access if `-p` is not given. + +`OPENSSL_ENGINES` +: May help OpenSSL find engine modules. + +# FILES + +On Unix-like systems, `osslsigncode` tries common CA bundle locations for +its default `-CAfile`, including: + +- `/etc/ssl/certs/ca-certificates.crt` +- `/etc/pki/tls/certs/ca-bundle.crt` +- `/usr/share/ssl/certs/ca-bundle.crt` +- `/usr/local/share/certs/ca-root-nss.crt` +- `/etc/ssl/cert.pem` + +If no readable CA bundle is available, `verify` may require an explicit +`-CAfile`. + +# NOTES + +Use `extract-data` when you need to create a new detached signature object. +Use `extract-signature` when you need to copy an existing embedded PKCS#7 +signature out of a file. + +For safer secret handling, prefer `-readpass` or `-askpass` over `-pass`. + +Data added with `-addUnauthenticatedBlob` is not protected by the signature +and must not be treated as trusted. + +For new MSI signatures, `-add-msi-dse` is generally preferred because it +extends signing coverage to MSI metadata as well as file content. However, +it is format-affecting rather than cosmetic, so existing signed MSI files +should be re-signed only in a mode consistent with their current signature +structure. + +Output files are not overwritten. + +# EXAMPLES + +## Sign and verify a file + +```sh +osslsigncode sign \ + -pkcs12 signer.p12 \ + -readpass p12-pass.txt \ + -n "Example Application" \ + -i "https://example.com/" \ + -ts "https://tsa.example.net/" \ + -in app.exe \ + -out app-signed.exe + +osslsigncode verify \ + -CAfile ca-bundle.pem \ + -TSA-CAfile tsa-ca-bundle.pem \ + -in app-signed.exe +``` + +## Detached signing workflow + +```sh +osslsigncode extract-data \ + -h sha384 \ + -ph \ + -in app.exe \ + -out app-data.der + +osslsigncode sign \ + -pkcs12 signer.p12 \ + -readpass p12-pass.txt \ + -h sha384 \ + -in app-data.der \ + -out app-sig.der + +osslsigncode attach-signature \ + -h sha384 \ + -sigin app-sig.der \ + -in app.exe \ + -out app-signed.exe + +osslsigncode verify \ + -CAfile ca-bundle.pem \ + -in app-signed.exe +``` + +## Sign a new MSI with extended MSI metadata coverage + +```sh +osslsigncode sign \ + -pkcs12 signer.p12 \ + -readpass p12-pass.txt \ + -add-msi-dse \ + -in installer.msi \ + -out installer-signed.msi +``` + +## Use a PKCS#11 provider + +```sh +osslsigncode sign \ + -provider /path/to/pkcs11prov.so \ + -pkcs11module /path/to/opensc-pkcs11.so \ + -pkcs11cert 'pkcs11:token=my-token;object=cert' \ + -key 'pkcs11:token=my-token;object=key' \ + -readpass pin.txt \ + -in app.exe \ + -out app-signed.exe +``` + +## Add a timestamp to an already signed file + +```sh +osslsigncode add \ + -ts "https://tsa.example.net/" \ + -in app-signed.exe \ + -out app-signed-ts.exe +``` + +## Verify that a file is covered by a catalog + +```sh +osslsigncode verify \ + -catalog drivers.cat \ + -CAfile ca-bundle.pem \ + -CRLfile ca-crl.pem \ + -in driver.sys +``` + +# REPORTING BUGS + +Report bugs and suspected issues via the project issue tracker: + + + +# AUTHORS + +Originally written by Per Allansson. + +Maintained and extended by Michał Trojnara. + +Major contributions by Małgorzata Olszówka. + +Additional contributions by other project contributors. + +# SEE ALSO + +**OpenSSL** Library + + + From 461f6d9d8692c588d5f3daddbdcfd9b20e5ef509 Mon Sep 17 00:00:00 2001 From: olszomal Date: Mon, 25 May 2026 09:01:22 +0200 Subject: [PATCH 2/3] Use ASN1_STRING and ASN1_INTEGER APIs instead of direct struct access --- appx.c | 13 ++-- cab.c | 6 +- cat.c | 28 +++++--- helpers.c | 19 ++++-- msi.c | 6 +- osslsigncode.c | 174 ++++++++++++++++++++++++++++++------------------- pe.c | 89 ++++++++++++++++++------- script.c | 6 +- 8 files changed, 226 insertions(+), 115 deletions(-) diff --git a/appx.c b/appx.c index 6728f7d..dbc57aa 100644 --- a/appx.c +++ b/appx.c @@ -472,8 +472,9 @@ 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); + const u_char *p = ASN1_STRING_get0_data(content_val); + int len = ASN1_STRING_length(content_val); + SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &p, len); if (idc) { BIO *hashes; @@ -1077,13 +1078,13 @@ static int appx_extract_hashes(FILE_FORMAT_CTX *ctx, SpcIndirectDataContent *con AppxSpcSipInfo_free(si); BIO_free_all(stdbio); #endif - int length = content->messageDigest->digest->length; - uint8_t *data = content->messageDigest->digest->data; + int len = ASN1_STRING_length(content->messageDigest->digest); + const uint8_t *data = ASN1_STRING_get0_data(content->messageDigest->digest); int mdlen = EVP_MD_size(ctx->appx_ctx->md); int pos = 4; /* we are expecting at least 4 hashes + 4 byte header */ - if (length < 4 * mdlen + 4) { + if (len < 4 * mdlen + 4) { fprintf(stderr, "Hash too short\n"); return 0; /* FAILED */ } @@ -1091,7 +1092,7 @@ static int appx_extract_hashes(FILE_FORMAT_CTX *ctx, SpcIndirectDataContent *con fprintf(stderr, "Hash signature does not match\n"); return 0; /* FAILED */ } - while (pos + mdlen + 4 <= length) { + while (pos + mdlen + 4 <= len) { if (!memcmp(data + pos, AXPC_SIGNATURE, 4)) { ctx->appx_ctx->existingDataHash = OPENSSL_malloc((size_t)mdlen); memcpy(ctx->appx_ctx->existingDataHash, data + pos + 4, (size_t)mdlen); diff --git a/cab.c b/cab.c index 257c2f0..3c6ba04 100644 --- a/cab.c +++ b/cab.c @@ -339,8 +339,10 @@ static int cab_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); + const u_char *data = ASN1_STRING_get0_data(content_val); + int len = ASN1_STRING_length(content_val); + + SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &data, len); if (idc) { if (spc_indirect_data_content_get_digest(idc, mdbuf, &mdtype) < 0) { fprintf(stderr, "Failed to extract message digest from signature\n\n"); diff --git a/cat.c b/cat.c index 8967d77..ace8457 100644 --- a/cat.c +++ b/cat.c @@ -297,27 +297,39 @@ static int cat_add_content_type(PKCS7 *p7, PKCS7 *cursig) */ static int cat_sign_content(PKCS7 *p7, PKCS7 *contents) { - u_char *content; - int seqhdrlen, content_length; + const unsigned char *sequence_data; + const unsigned char *content; + ASN1_STRING *sequence; + int seqhdrlen, sequence_len, content_length; - if (!contents->d.other || !contents->d.other->value.sequence - || !contents->d.other->value.sequence->data) { + if (!contents->d.other || !contents->d.other->value.sequence) { fprintf(stderr, "Failed to get content value\n"); return 0; /* FAILED */ } - seqhdrlen = asn1_simple_hdr_len(contents->d.other->value.sequence->data, - contents->d.other->value.sequence->length); - content = contents->d.other->value.sequence->data + seqhdrlen; - content_length = contents->d.other->value.sequence->length - seqhdrlen; + + sequence = contents->d.other->value.sequence; + sequence_data = ASN1_STRING_get0_data(sequence); + sequence_len = ASN1_STRING_length(sequence); + + if (!sequence_data) { + fprintf(stderr, "Failed to get content value\n"); + return 0; /* FAILED */ + } + + seqhdrlen = asn1_simple_hdr_len(sequence_data, sequence_len); + content = (const unsigned char *)sequence_data + seqhdrlen; + content_length = sequence_len - seqhdrlen; if (!pkcs7_sign_content(p7, content, content_length)) { fprintf(stderr, "Failed to sign content\n"); return 0; /* FAILED */ } + if (!PKCS7_set_content(p7, PKCS7_dup(contents))) { fprintf(stderr, "PKCS7_set_content failed\n"); return 0; /* FAILED */ } + return 1; /* OK */ } diff --git a/helpers.c b/helpers.c index cbcbc38..ce945b8 100644 --- a/helpers.c +++ b/helpers.c @@ -575,22 +575,27 @@ int compare_digests(u_char *mdbuf, u_char *cmdbuf, int mdtype) */ int spc_indirect_data_content_get_digest(SpcIndirectDataContent *idc, u_char *mdbuf, int *mdtype) { + ASN1_OCTET_STRING *digest_asn1; + const unsigned char *digest_data; int digest_len; if (!idc || !idc->messageDigest || !idc->messageDigest->digest || !idc->messageDigest->digestAlgorithm) { return -1; /* FAILED */ } - digest_len = idc->messageDigest->digest->length; + digest_asn1 = idc->messageDigest->digest; + digest_len = ASN1_STRING_length((ASN1_STRING *)digest_asn1); /* Validate digest length to prevent buffer overflow */ if (digest_len <= 0 || digest_len > EVP_MAX_MD_SIZE) { fprintf(stderr, "Invalid digest length in signature: %d (expected 1-%d)\n", - digest_len, EVP_MAX_MD_SIZE); + digest_len, EVP_MAX_MD_SIZE); return -1; /* FAILED */ } + + digest_data = ASN1_STRING_get0_data((ASN1_STRING *)digest_asn1); *mdtype = OBJ_obj2nid(idc->messageDigest->digestAlgorithm->algorithm); - memcpy(mdbuf, idc->messageDigest->digest->data, (size_t)digest_len); + memcpy(mdbuf, digest_data, (size_t)digest_len); return digest_len; /* OK */ } @@ -653,8 +658,12 @@ static int spc_indirect_data_content_create(u_char **blob, int *len, FILE_FORMAT SpcIndirectDataContent_free(idc); return 0; /* FAILED */ } - idc->data->value->value.sequence->data = p; - idc->data->value->value.sequence->length = l; + if (!ASN1_STRING_set(idc->data->value->value.sequence, p, l)) { + OPENSSL_free(p); + SpcIndirectDataContent_free(idc); + return 0; /* FAILED */ + } + OPENSSL_free(p); idc->messageDigest->digestAlgorithm->algorithm = OBJ_nid2obj(mdtype); idc->messageDigest->digestAlgorithm->parameters = ASN1_TYPE_new(); idc->messageDigest->digestAlgorithm->parameters->type = V_ASN1_NULL; diff --git a/msi.c b/msi.c index affa65c..aee838b 100644 --- a/msi.c +++ b/msi.c @@ -416,8 +416,10 @@ static int msi_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); + const u_char *p = ASN1_STRING_get0_data(content_val); + int len = ASN1_STRING_length(content_val); + SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &p, len); + if (idc) { if (spc_indirect_data_content_get_digest(idc, mdbuf, &mdtype) < 0) { fprintf(stderr, "Failed to extract message digest from signature\n\n"); diff --git a/osslsigncode.c b/osslsigncode.c index 328fd21..40c4860 100644 --- a/osslsigncode.c +++ b/osslsigncode.c @@ -232,7 +232,7 @@ static ASN1_INTEGER *create_nonce(int bits); static char *clrdp_url_get_x509(X509 *cert); static time_t time_t_get_asn1_time(const ASN1_TIME *s); static time_t time_t_get_si_time(PKCS7_SIGNER_INFO *si); -static ASN1_UTCTIME *asn1_time_get_si_time(PKCS7_SIGNER_INFO *si); +static const ASN1_UTCTIME *asn1_time_get_si_time(PKCS7_SIGNER_INFO *si); static time_t time_t_get_cms_time(CMS_ContentInfo *cms); static CMS_ContentInfo *cms_get_timestamp(PKCS7_SIGNED *p7_signed, PKCS7_SIGNER_INFO *countersignature); @@ -296,7 +296,8 @@ static BIO *bio_encode_rfc3161_request(PKCS7 *p7, const EVP_MD *md) TS_REQ *req = NULL; BIO *bout = NULL, *bhash = NULL; u_char *p; - int len; + const u_char *digest; + int digest_len, len; signer_info = PKCS7_get_signer_info(p7); if (!signer_info) @@ -306,6 +307,9 @@ static BIO *bio_encode_rfc3161_request(PKCS7 *p7, const EVP_MD *md) if (!si) goto out; + digest = ASN1_STRING_get0_data(si->enc_digest); + digest_len = ASN1_STRING_length(si->enc_digest); + bhash = BIO_new(BIO_f_md()); #if defined(__GNUC__) #pragma GCC diagnostic push @@ -319,7 +323,7 @@ static BIO *bio_encode_rfc3161_request(PKCS7 *p7, const EVP_MD *md) #pragma GCC diagnostic pop #endif BIO_push(bhash, BIO_new(BIO_s_null())); - BIO_write(bhash, si->enc_digest->data, si->enc_digest->length); + BIO_write(bhash, digest, digest_len); BIO_gets(bhash, (char*)mdbuf, EVP_MD_size(md)); req = TS_REQ_new(); @@ -377,6 +381,7 @@ static ASN1_INTEGER *create_nonce(int bits) { unsigned char buf[20]; ASN1_INTEGER *nonce = NULL; + BIGNUM *bn = NULL; int len = (bits - 1) / 8 + 1; int i; @@ -391,15 +396,21 @@ static ASN1_INTEGER *create_nonce(int bits) /* Find the first non-zero byte and creating ASN1_INTEGER object. */ for (i = 0; i < len && !buf[i]; ++i) { } - nonce = ASN1_INTEGER_new(); + + bn = BN_bin2bn(buf + i, len - i, NULL); + if (!bn) { + fprintf(stderr, "Could not create nonce BIGNUM\n"); + return NULL; + } + + nonce = BN_to_ASN1_INTEGER(bn, NULL); + BN_free(bn); + if (!nonce) { fprintf(stderr, "Could not create nonce\n"); return NULL; } - OPENSSL_free(nonce->data); - nonce->length = len - i; - nonce->data = OPENSSL_malloc((size_t)nonce->length + 1); - memcpy(nonce->data, buf + i, (size_t)nonce->length); + return nonce; } @@ -1624,7 +1635,7 @@ static int X509_attribute_chain_append_object(STACK_OF(X509_ATTRIBUTE) **unauth_ u_char *p, int len, const char *oid) { X509_ATTRIBUTE *attr = NULL; - ASN1_OBJECT *object; + const ASN1_OBJECT *object; char object_txt[128]; if (*unauth_attr == NULL) { @@ -1635,7 +1646,7 @@ static int X509_attribute_chain_append_object(STACK_OF(X509_ATTRIBUTE) **unauth_ int i; for (i = 0; i < X509at_get_attr_count(*unauth_attr); i++) { attr = X509at_get_attr(*unauth_attr, i); - object = X509_ATTRIBUTE_get0_object(attr); + object = (const ASN1_OBJECT *)X509_ATTRIBUTE_get0_object(attr); if (object == NULL) continue; object_txt[0] = 0x00; @@ -2199,18 +2210,22 @@ static int verify_timestamp_token(PKCS7 *p7, CMS_ContentInfo *timestamp) /* get the embedded content */ pos = CMS_get0_content(timestamp); if (pos != NULL && *pos != NULL) { - const u_char *p = (*pos)->data; - TS_TST_INFO *token = d2i_TS_TST_INFO(NULL, &p, (*pos)->length); + const u_char *p = ASN1_STRING_get0_data(*pos); + int len = ASN1_STRING_length(*pos); + 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; - int md_nid; + const u_char *hash_data; + int hash_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); + const u_char *digest = ASN1_STRING_get0_data(si->enc_digest); + int digest_len = ASN1_STRING_length(si->enc_digest); X509_ALGOR_get0(&aoid, NULL, NULL, alg); md_nid = OBJ_obj2nid(aoid); @@ -2232,17 +2247,19 @@ static int verify_timestamp_token(PKCS7 *p7, CMS_ContentInfo *timestamp) #pragma GCC diagnostic pop #endif BIO_push(bhash, BIO_new(BIO_s_null())); - BIO_write(bhash, si->enc_digest->data, si->enc_digest->length); + 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); - if (memcmp(mdbuf, hash->data, (size_t)hash->length)) { + hash_data = ASN1_STRING_get0_data(hash); + hash_len = ASN1_STRING_length(hash); + if (memcmp(mdbuf, hash_data, (size_t)hash_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("\tReceived message digest", "", hash->data, hash->length); + print_hash("\tReceived message digest", "", hash_data, hash_len); printf("\nFile's message digest verification: failed\n"); TS_TST_INFO_free(token); return 0; /* FAILED */ @@ -2416,7 +2433,7 @@ static int verify_pkcs7_data(PKCS7 *p7, X509_STORE *store) && (contents->d.other->value.sequence->length > 0)) { if (contents->d.other->type == V_ASN1_SEQUENCE) { /* only verify the content of the sequence */ - const unsigned char *data = contents->d.other->value.sequence->data; + const u_char *data = contents->d.other->value.sequence->data; long len; int inf, tag, class; @@ -2688,11 +2705,12 @@ static time_t time_t_timestamp_get_attributes(CMS_ContentInfo **timestamp, PKCS7 { STACK_OF(PKCS7_SIGNER_INFO) *signer_info; PKCS7_SIGNER_INFO *si; - int md_nid, i; + int md_nid, i, len; STACK_OF(X509_ATTRIBUTE) *auth_attr, *unauth_attr; X509_ATTRIBUTE *attr; - ASN1_OBJECT *object; - ASN1_STRING *value; + const ASN1_OBJECT *object; + const ASN1_STRING *value; + const u_char *data; char object_txt[128]; time_t time = INVALID_TIME; @@ -2711,24 +2729,24 @@ static time_t time_t_timestamp_get_attributes(CMS_ContentInfo **timestamp, PKCS7 printf("\nAuthenticated attributes:\n"); for (i=0; imoreInfo && opus->moreInfo->type == 0) { - char *url = OPENSSL_strdup((char *)opus->moreInfo->value.url->data); - printf("\tURL description: %s\n", url); - OPENSSL_free(url); + ASN1_IA5STRING *url_asn1 = opus->moreInfo->value.url; + const u_char *url_data = ASN1_STRING_get0_data((ASN1_STRING *)url_asn1); + int url_length = ASN1_STRING_length((ASN1_STRING *)url_asn1); + + printf("\tURL description: %.*s\n", url_length, url_data); } if (opus->programName) { char *desc = NULL; + if (opus->programName->type == 0) { - u_char *opusdata; - int len = ASN1_STRING_to_UTF8(&opusdata, opus->programName->value.unicode); - if (len >= 0) { - desc = OPENSSL_strndup((char *)opusdata, (size_t)len); - OPENSSL_free(opusdata); + u_char *opus_data; + int opus_len = ASN1_STRING_to_UTF8(&opus_data, opus->programName->value.unicode); + + if (opus_len >= 0) { + desc = OPENSSL_strndup((char *)opus_data, (size_t)opus_len); + OPENSSL_free(opus_data); } } else { - desc = OPENSSL_strdup((char *)opus->programName->value.ascii->data); + ASN1_IA5STRING *desc_asn1 = opus->programName->value.ascii; + const u_char *desc_data = ASN1_STRING_get0_data((ASN1_STRING *)desc_asn1); + int desc_len = ASN1_STRING_length((ASN1_STRING *)desc_asn1); + + desc = OPENSSL_strndup((const char *)desc_data, (size_t)desc_len); } if (desc) { printf("\tText description: %s\n", desc); @@ -2769,31 +2796,31 @@ static time_t time_t_timestamp_get_attributes(CMS_ContentInfo **timestamp, PKCS7 SpcSpOpusInfo_free(opus); } else if (!strcmp(object_txt, SPC_STATEMENT_TYPE_OBJID)) { /* Microsoft OID: 1.3.6.1.4.1.311.2.1.11 */ - const u_char *purpose; - value = X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_SEQUENCE, NULL); + value = (const ASN1_STRING *)X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_SEQUENCE, NULL); if (value == NULL) continue; - purpose = ASN1_STRING_get0_data(value); - if (!memcmp(purpose, purpose_comm, sizeof purpose_comm)) + data = ASN1_STRING_get0_data(value); + if (!memcmp(data, purpose_comm, sizeof purpose_comm)) printf("\tMicrosoft Commercial Code Signing purpose\n"); - else if (!memcmp(purpose, purpose_ind, sizeof purpose_ind)) + else if (!memcmp(data, purpose_ind, sizeof purpose_ind)) printf("\tMicrosoft Individual Code Signing purpose\n"); else printf("\tUnrecognized Code Signing purpose\n"); } else if (!strcmp(object_txt, MS_JAVA_SOMETHING)) { /* Microsoft OID: 1.3.6.1.4.1.311.15.1 */ - const u_char *level; - value = X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_SEQUENCE, NULL); + value = (const ASN1_STRING *)X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_SEQUENCE, NULL); if (value == NULL) continue; - level = ASN1_STRING_get0_data(value); - if (!memcmp(level, java_attrs_low, sizeof java_attrs_low)) + data = ASN1_STRING_get0_data(value); + if (!memcmp(data, java_attrs_low, sizeof java_attrs_low)) printf("\tLow level of permissions in Microsoft Internet Explorer 4.x for CAB files\n"); else printf("\tUnrecognized level of permissions in Microsoft Internet Explorer 4.x for CAB files\n"); } else if (!strcmp(object_txt, PKCS9_SEQUENCE_NUMBER)) { /* PKCS#9 sequence number - Policy OID: 1.2.840.113549.1.9.25.4 */ - ASN1_INTEGER *number = X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_INTEGER, NULL); + const ASN1_INTEGER *number; + + number = (const ASN1_INTEGER *)X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_INTEGER, NULL); if (number == NULL) continue; printf("\tSequence number: %ld\n", ASN1_INTEGER_get(number)); @@ -2804,17 +2831,17 @@ static time_t time_t_timestamp_get_attributes(CMS_ContentInfo **timestamp, PKCS7 unauth_attr = PKCS7_get_attributes(si); /* cont[1] */ for (i=0; idata, blob->length); + char *data_blob; + + data = ASN1_STRING_get0_data(value); + data_blob = OPENSSL_buf2hexstr(data, len); + printf("\nUnauthenticated Data Blob:\n%s\n", data_blob); OPENSSL_free(data_blob); } else { - printf("\nUnauthenticated Data Blob length: %d bytes\n", blob->length); + printf("\nUnauthenticated Data Blob length: %d bytes\n", len); } } } /* Signature */ if (verbose) { + data = ASN1_STRING_get0_data(si->enc_digest); + len = ASN1_STRING_length(si->enc_digest); + md_nid = OBJ_obj2nid(si->digest_enc_alg->algorithm); printf("\nDigest encryption algorithm: %s\n", (md_nid == NID_undef) ? "UNKNOWN" : OBJ_nid2sn(md_nid)); - print_hash("Signature", "", ASN1_STRING_get0_data(si->enc_digest), ASN1_STRING_length(si->enc_digest)); + print_hash("Signature", "", data, len); } return time; @@ -2928,7 +2963,7 @@ static time_t time_t_get_asn1_time(const ASN1_TIME *s) */ static time_t time_t_get_si_time(PKCS7_SIGNER_INFO *si) { - ASN1_UTCTIME *time = asn1_time_get_si_time(si); + const ASN1_UTCTIME *time = asn1_time_get_si_time(si); if (time == NULL) return INVALID_TIME; /* FAILED */ @@ -2940,7 +2975,7 @@ static time_t time_t_get_si_time(PKCS7_SIGNER_INFO *si) * [in] si: PKCS7_SIGNER_INFO structure * [returns] NULL on error or ASN1_UTCTIME on success */ -static ASN1_UTCTIME *asn1_time_get_si_time(PKCS7_SIGNER_INFO *si) +static const ASN1_UTCTIME *asn1_time_get_si_time(PKCS7_SIGNER_INFO *si) { STACK_OF(X509_ATTRIBUTE) *auth_attr = PKCS7_get_signed_attributes(si); if (auth_attr) { @@ -2950,7 +2985,7 @@ static ASN1_UTCTIME *asn1_time_get_si_time(PKCS7_SIGNER_INFO *si) X509_ATTRIBUTE *attr = X509at_get_attr(auth_attr, i); if (OBJ_obj2nid(X509_ATTRIBUTE_get0_object(attr)) == nid) { /* PKCS#9 signing time - Policy OID: 1.2.840.113549.1.9.5 */ - return X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_UTCTIME, NULL); + return (const ASN1_UTCTIME *)X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_UTCTIME, NULL); } } } @@ -2990,10 +3025,13 @@ static time_t time_t_get_cms_time(CMS_ContentInfo *cms) ASN1_OCTET_STRING **pos = CMS_get0_content(cms); if (pos != NULL && *pos != NULL) { - const u_char *p = (*pos)->data; - TS_TST_INFO *token = d2i_TS_TST_INFO(NULL, &p, (*pos)->length); + const u_char *p = ASN1_STRING_get0_data(*pos); + int len = ASN1_STRING_length(*pos); + TS_TST_INFO *token = d2i_TS_TST_INFO(NULL, &p, len); + if (token) { const ASN1_GENERALIZEDTIME *asn1_time = TS_TST_INFO_get_time(token); + posix_time = time_t_get_asn1_time(asn1_time); TS_TST_INFO_free(token); } @@ -3342,11 +3380,11 @@ static STACK_OF(PKCS7) *signature_list_create(PKCS7 *p7) int j; for (j=0; jd.sign->contents->d.other->value.sequence; - const u_char *p = content_val->data; - SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &p, content_val->length); + const u_char *p = ASN1_STRING_get0_data(content_val); + int len = ASN1_STRING_length(content_val); + SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &p, len); + if (idc) { if (!pe_page_hash_get(&ph, &phlen, &phtype, idc->data)) { fprintf(stderr, "Failed to extract a page hash\n\n"); @@ -847,43 +849,71 @@ static BIO *pe_digest_calc_bio(FILE_FORMAT_CTX *ctx, const EVP_MD *md) * [in] obj: SPC_INDIRECT_DATA OID: 1.3.6.1.4.1.311.2.1.4 containing page hash * [returns] 0 on error or 1 on success */ -static int pe_page_hash_get(u_char **ph, int *phlen, int *phtype, SpcAttributeTypeAndOptionalValue *obj) +static int pe_page_hash_get(u_char **ph, int *phlen, int *phtype, + SpcAttributeTypeAndOptionalValue *obj) { - const u_char *blob; + const unsigned char *blob; + const unsigned char *sequence_data; + const unsigned char *classid_data; + const unsigned char *serialized_data; SpcPeImageData *id; SpcSerializedObject *so; - int l, l2; + int sequence_len, classid_len, serialized_len, l, l2; char buf[128]; + /* Validate input object */ if (!obj || !obj->value) return 0; /* FAILED */ - blob = obj->value->value.sequence->data; - id = d2i_SpcPeImageData(NULL, &blob, obj->value->value.sequence->length); - if (!id) { + + /* Decode SpcPeImageData from ASN.1 sequence */ + sequence_data = ASN1_STRING_get0_data(obj->value->value.sequence); + sequence_len = ASN1_STRING_length(obj->value->value.sequence); + + /* d2i_* modifies the input pointer, so use a temporary variable */ + blob = sequence_data; + id = d2i_SpcPeImageData(NULL, &blob, sequence_len); + if (!id) return 0; /* FAILED */ - } + + /* Validate SpcPeImageData contents */ if (!id->file) { SpcPeImageData_free(id); return 0; /* FAILED */ } + + /* Type 1 means SpcSerializedObject */ if (id->file->type != 1) { SpcPeImageData_free(id); - return 1; /* OK - This is not SpcSerializedObject structure that contains page hashes */ + return 1; /* OK - no page hashes present */ } + so = id->file->value.moniker; - if (so->classId->length != sizeof classid_page_hash || - memcmp(so->classId->data, classid_page_hash, sizeof classid_page_hash)) { + + /* Validate serialized object class ID */ + classid_data = ASN1_STRING_get0_data((ASN1_STRING *)so->classId); + classid_len = ASN1_STRING_length((ASN1_STRING *)so->classId); + + if (classid_len != sizeof classid_page_hash || + memcmp(classid_data, classid_page_hash, sizeof classid_page_hash)) { SpcPeImageData_free(id); return 0; /* FAILED */ } - /* skip ASN.1 SET hdr */ - l = asn1_simple_hdr_len(so->serializedData->data, so->serializedData->length); - blob = so->serializedData->data + l; - obj = d2i_SpcAttributeTypeAndOptionalValue(NULL, &blob, so->serializedData->length - l); + + /*Get serialized ASN.1 blob */ + serialized_data = ASN1_STRING_get0_data((ASN1_STRING *)so->serializedData); + serialized_len = ASN1_STRING_length((ASN1_STRING *)so->serializedData); + + /* Skip ASN.1 SET header */ + l = asn1_simple_hdr_len(serialized_data, serialized_len); + blob = serialized_data + l; + + /* Decode nested SpcAttributeTypeAndOptionalValue */ + obj = d2i_SpcAttributeTypeAndOptionalValue(NULL, &blob, serialized_len - l); SpcPeImageData_free(id); if (!obj) return 0; /* FAILED */ + /* Determine page hash algorithm */ *phtype = 0; buf[0] = 0x00; OBJ_obj2txt(buf, sizeof buf, obj->type, 1); @@ -895,15 +925,30 @@ static int pe_page_hash_get(u_char **ph, int *phlen, int *phtype, SpcAttributeTy SpcAttributeTypeAndOptionalValue_free(obj); return 0; /* FAILED */ } - /* Skip ASN.1 SET hdr */ - l2 = asn1_simple_hdr_len(obj->value->value.sequence->data, obj->value->value.sequence->length); - /* Skip ASN.1 OCTET STRING hdr */ - l = asn1_simple_hdr_len(obj->value->value.sequence->data + l2, obj->value->value.sequence->length - l2); + + /* IMPORTANT: + * obj now points to the newly decoded structure, + * so refresh sequence_data/sequence_len */ + sequence_data = ASN1_STRING_get0_data(obj->value->value.sequence); + sequence_len = ASN1_STRING_length(obj->value->value.sequence); + + /* Skip ASN.1 SET header */ + l2 = asn1_simple_hdr_len(sequence_data, sequence_len); + + /* Skip ASN.1 OCTET STRING header */ + l = asn1_simple_hdr_len(sequence_data + l2, sequence_len - l2); l += l2; - *phlen = obj->value->value.sequence->length - l; + + /* Extract raw page hash blob */ + *phlen = sequence_len - l; *ph = OPENSSL_malloc((size_t)*phlen); - memcpy(*ph, obj->value->value.sequence->data + l, (size_t)*phlen); + if (!*ph) { + SpcAttributeTypeAndOptionalValue_free(obj); + return 0; /* FAILED */ + } + memcpy(*ph, sequence_data + l, (size_t)*phlen); SpcAttributeTypeAndOptionalValue_free(obj); + return 1; /* OK */ } diff --git a/script.c b/script.c index 457791b..0b3cf3c 100644 --- a/script.c +++ b/script.c @@ -291,8 +291,10 @@ static int script_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7) /* FIXME: this shared code most likely belongs in osslsigncode.c */ 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); + const u_char *p = ASN1_STRING_get0_data(content_val); + int len = ASN1_STRING_length(content_val); + SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &p, len); + if (idc) { if (spc_indirect_data_content_get_digest(idc, mdbuf, &mdtype) < 0) { fprintf(stderr, "Failed to extract message digest from signature\n\n"); From 96efef1e419b21e269c4d064e1ec2931bd8dc895 Mon Sep 17 00:00:00 2001 From: olszomal Date: Mon, 25 May 2026 10:49:40 +0200 Subject: [PATCH 3/3] Refactor SpcIndirectDataContent parsing helpers --- appx.c | 52 +++++++++++++++++----------------- cab.c | 17 +---------- cat.c | 9 +++--- helpers.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ helpers.h | 3 ++ msi.c | 17 +---------- pe.c | 66 ++++++++++++++++++++++++------------------ script.c | 18 +----------- 8 files changed, 160 insertions(+), 107 deletions(-) diff --git a/appx.c b/appx.c index dbc57aa..4544068 100644 --- a/appx.c +++ b/appx.c @@ -470,33 +470,33 @@ static int appx_hash_length_get(FILE_FORMAT_CTX *ctx) */ 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 = ASN1_STRING_get0_data(content_val); - int len = ASN1_STRING_length(content_val); - SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &p, len); - - if (idc) { - BIO *hashes; - if (!appx_extract_hashes(ctx, idc)) { - fprintf(stderr, "Failed to extract hashes from the signature\n"); - SpcIndirectDataContent_free(idc); - return 0; /* FAILED */ - } - hashes = appx_calculate_hashes(ctx); - if (!hashes) { - SpcIndirectDataContent_free(idc); - return 0; /* FAILED */ - } - BIO_free_all(hashes); - if (!appx_compare_hashes(ctx)) { - fprintf(stderr, "Signature hash verification failed\n"); - SpcIndirectDataContent_free(idc); - return 0; /* FAILED */ - } - SpcIndirectDataContent_free(idc); - } + SpcIndirectDataContent *idc; + BIO *hashes; + + idc = pkcs7_get_indirect_data_content(p7); + if (!idc) + return 1; /* OK - no SpcIndirectDataContent */ + + if (!appx_extract_hashes(ctx, idc)) { + fprintf(stderr, "Failed to extract hashes from the signature\n"); + SpcIndirectDataContent_free(idc); + return 0; /* FAILED */ } + + hashes = appx_calculate_hashes(ctx); + if (!hashes) { + SpcIndirectDataContent_free(idc); + return 0; /* FAILED */ + } + BIO_free_all(hashes); + + if (!appx_compare_hashes(ctx)) { + fprintf(stderr, "Signature hash verification failed\n"); + SpcIndirectDataContent_free(idc); + return 0; /* FAILED */ + } + + SpcIndirectDataContent_free(idc); return 1; /* OK */ } diff --git a/cab.c b/cab.c index 3c6ba04..b9337f5 100644 --- a/cab.c +++ b/cab.c @@ -337,22 +337,7 @@ static int cab_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7) u_char mdbuf[EVP_MAX_MD_SIZE]; u_char *cmdbuf; - if (is_content_type(p7, SPC_INDIRECT_DATA_OBJID)) { - ASN1_STRING *content_val = p7->d.sign->contents->d.other->value.sequence; - const u_char *data = ASN1_STRING_get0_data(content_val); - int len = ASN1_STRING_length(content_val); - - SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &data, len); - if (idc) { - if (spc_indirect_data_content_get_digest(idc, mdbuf, &mdtype) < 0) { - fprintf(stderr, "Failed to extract message digest from signature\n\n"); - SpcIndirectDataContent_free(idc); - return 0; /* FAILED */ - } - SpcIndirectDataContent_free(idc); - } - } - if (mdtype == -1) { + if (!pkcs7_get_content_digest(p7, mdbuf, &mdtype)) { fprintf(stderr, "Failed to extract current message digest\n\n"); return 0; /* FAILED */ } diff --git a/cat.c b/cat.c index ace8457..83bf822 100644 --- a/cat.c +++ b/cat.c @@ -393,23 +393,22 @@ static int cat_print_content_member_digest(ASN1_TYPE *content) { SpcIndirectDataContent *idc; u_char mdbuf[EVP_MAX_MD_SIZE]; - const u_char *data ; int mdtype = -1; - ASN1_STRING *value; - value = content->value.sequence; - data = ASN1_STRING_get0_data(value); - idc = d2i_SpcIndirectDataContent(NULL, &data, ASN1_STRING_length(value)); + idc = asn1_type_get_indirect_data_content(content); if (!idc) return 0; /* FAILED */ + if (spc_indirect_data_content_get_digest(idc, mdbuf, &mdtype) < 0) { fprintf(stderr, "Failed to extract message digest from signature\n\n"); SpcIndirectDataContent_free(idc); return 0; /* FAILED */ } SpcIndirectDataContent_free(idc); + printf("\tHash algorithm: %s\n", OBJ_nid2sn(mdtype)); print_hash("\tMessage digest", "", mdbuf, EVP_MD_size(EVP_get_digestbynid(mdtype))); + return 1; /* OK */ } diff --git a/helpers.c b/helpers.c index ce945b8..ea44d60 100644 --- a/helpers.c +++ b/helpers.c @@ -341,6 +341,91 @@ PKCS7 *pkcs7_set_content(ASN1_OCTET_STRING *content) return p7; } +/* + * Retrieve the message digest and digest algorithm from PKCS7 + * SpcIndirectDataContent. + * + * [in] p7: PKCS7 structure containing SPC_INDIRECT_DATA_OBJID content + * [out] mdbuf: message digest buffer, at least EVP_MAX_MD_SIZE bytes + * [out] mdtype: OpenSSL NID of the digest algorithm + * [returns] 0 on error or 1 on success + */ +int pkcs7_get_content_digest(PKCS7 *p7, u_char *mdbuf, int *mdtype) +{ + SpcIndirectDataContent *idc; + + if (!mdbuf || !mdtype) + return 0; /* FAILED */ + + *mdtype = -1; + + idc = pkcs7_get_indirect_data_content(p7); + if (!idc) { + fprintf(stderr, "Failed to decode SpcIndirectDataContent\n\n"); + return 0; /* FAILED */ + } + if (spc_indirect_data_content_get_digest(idc, mdbuf, mdtype) < 0) { + fprintf(stderr, "Failed to extract message digest from signature\n\n"); + SpcIndirectDataContent_free(idc); + return 0; /* FAILED */ + } + SpcIndirectDataContent_free(idc); + if (*mdtype == -1) { + fprintf(stderr, "Failed to extract current message digest\n\n"); + return 0; /* FAILED */ + } + return 1; /* OK */ +} + +/* + * Decode SpcIndirectDataContent from a PKCS7 signedData content. + * + * [in] p7: PKCS7 structure containing SPC_INDIRECT_DATA_OBJID content + * [returns] newly allocated SpcIndirectDataContent, or NULL on error + * + * The caller is responsible for freeing the returned object with + * SpcIndirectDataContent_free(). + */ +SpcIndirectDataContent *pkcs7_get_indirect_data_content(PKCS7 *p7) +{ + if (!is_content_type(p7, SPC_INDIRECT_DATA_OBJID)) + return NULL; + + if (!p7->d.sign || !p7->d.sign->contents || !p7->d.sign->contents->d.other) + return NULL; + + return asn1_type_get_indirect_data_content(p7->d.sign->contents->d.other); +} + +/* + * Decode SpcIndirectDataContent from an ASN1_TYPE object. + * The ASN1_TYPE is expected to contain a V_ASN1_SEQUENCE value. + * + * [in] content: ASN1_TYPE containing DER-encoded SpcIndirectDataContent + * [returns] newly allocated SpcIndirectDataContent, or NULL on error + * + * The caller is responsible for freeing the returned object with + * SpcIndirectDataContent_free(). + */ +SpcIndirectDataContent *asn1_type_get_indirect_data_content(ASN1_TYPE *content) +{ + ASN1_STRING *value; + const unsigned char *data; + int len; + + if (!content || content->type != V_ASN1_SEQUENCE) + return NULL; + + value = content->value.sequence; + if (!value) + return NULL; + + data = ASN1_STRING_get0_data(value); + len = ASN1_STRING_length(value); + + return d2i_SpcIndirectDataContent(NULL, &data, len); +} + /* * Return spcIndirectDataContent. * [in] hash: message digest BIO diff --git a/helpers.h b/helpers.h index adaf416..ece3e5e 100644 --- a/helpers.h +++ b/helpers.h @@ -15,6 +15,9 @@ PKCS7 *pkcs7_create(FILE_FORMAT_CTX *ctx); int add_indirect_data_object(PKCS7 *p7); int sign_spc_indirect_data_content(PKCS7 *p7, ASN1_OCTET_STRING *content); PKCS7 *pkcs7_set_content(ASN1_OCTET_STRING *content); +int pkcs7_get_content_digest(PKCS7 *p7, u_char *mdbuf, int *mdtype); +SpcIndirectDataContent *pkcs7_get_indirect_data_content(PKCS7 *p7); +SpcIndirectDataContent *asn1_type_get_indirect_data_content(ASN1_TYPE *content); ASN1_OCTET_STRING *spc_indirect_data_content_get(BIO *hash, FILE_FORMAT_CTX *ctx); int pkcs7_sign_content(PKCS7 *p7, const u_char *data, int len); int asn1_simple_hdr_len(const u_char *p, int len); diff --git a/msi.c b/msi.c index aee838b..fb16841 100644 --- a/msi.c +++ b/msi.c @@ -414,22 +414,7 @@ static int msi_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7) const EVP_MD *md; BIO *hash; - 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 = ASN1_STRING_get0_data(content_val); - int len = ASN1_STRING_length(content_val); - SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &p, len); - - if (idc) { - if (spc_indirect_data_content_get_digest(idc, mdbuf, &mdtype) < 0) { - fprintf(stderr, "Failed to extract message digest from signature\n\n"); - SpcIndirectDataContent_free(idc); - return 0; /* FAILED */ - } - SpcIndirectDataContent_free(idc); - } - } - if (mdtype == -1) { + if (!pkcs7_get_content_digest(p7, mdbuf, &mdtype)) { fprintf(stderr, "Failed to extract current message digest\n\n"); return 0; /* FAILED */ } diff --git a/pe.c b/pe.c index f564941..e3ff02e 100644 --- a/pe.c +++ b/pe.c @@ -87,6 +87,7 @@ static uint32_t pe_calc_checksum(BIO *bio, uint32_t header_size); static uint32_t pe_calc_realchecksum(FILE_FORMAT_CTX *ctx); static int pe_modify_header(FILE_FORMAT_CTX *ctx, BIO *hash, BIO *outdata); static BIO *pe_digest_calc_bio(FILE_FORMAT_CTX *ctx, const EVP_MD *md); +static int pkcs7_get_page_hash(PKCS7 *p7, u_char **ph, int *phlen, int *phtype); static int pe_page_hash_get(u_char **ph, int *phlen, int *phtype, SpcAttributeTypeAndOptionalValue *obj); static u_char *pe_page_hash_calc(int *rphlen, FILE_FORMAT_CTX *ctx, int phtype); static int pe_verify_page_hash(FILE_FORMAT_CTX *ctx, u_char *ph, int phlen, int phtype); @@ -247,53 +248,34 @@ static int pe_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7) u_char *cmdbuf = NULL; u_char *ph = NULL; - 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 = ASN1_STRING_get0_data(content_val); - int len = ASN1_STRING_length(content_val); - SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &p, len); - - if (idc) { - if (!pe_page_hash_get(&ph, &phlen, &phtype, idc->data)) { - fprintf(stderr, "Failed to extract a page hash\n\n"); - SpcIndirectDataContent_free(idc); - return 0; /* FAILED */ - } - if (spc_indirect_data_content_get_digest(idc, mdbuf, &mdtype) < 0) { - fprintf(stderr, "Failed to extract message digest from signature\n\n"); - OPENSSL_free(ph); - SpcIndirectDataContent_free(idc); - return 0; /* FAILED */ - } - SpcIndirectDataContent_free(idc); - } - } - if (mdtype == -1) { + if (!pkcs7_get_content_digest(p7, mdbuf, &mdtype)) { fprintf(stderr, "Failed to extract current message digest\n\n"); - OPENSSL_free(ph); return 0; /* FAILED */ } md = EVP_get_digestbynid(mdtype); cmdbuf = pe_digest_calc(ctx, md); if (!cmdbuf) { fprintf(stderr, "Failed to calculate message digest\n\n"); - OPENSSL_free(ph); return 0; /* FAILED */ } if (!compare_digests(mdbuf, cmdbuf, mdtype)) { fprintf(stderr, "Signature verification: failed\n\n"); - OPENSSL_free(ph); OPENSSL_free(cmdbuf); return 0; /* FAILED */ } + OPENSSL_free(cmdbuf); + + if (!pkcs7_get_page_hash(p7, &ph, &phlen, &phtype)) { + fprintf(stderr, "Failed to extract page hash\n\n"); + return 0; /* FAILED */ + } if (!pe_verify_page_hash(ctx, ph, phlen, phtype)) { fprintf(stderr, "Signature verification: failed\n\n"); OPENSSL_free(ph); - OPENSSL_free(cmdbuf); return 0; /* FAILED */ } OPENSSL_free(ph); - OPENSSL_free(cmdbuf); + return 1; /* OK */ } @@ -841,6 +823,36 @@ static BIO *pe_digest_calc_bio(FILE_FORMAT_CTX *ctx, const EVP_MD *md) * Page hash support */ +/* + * Retrieve a page hash from PKCS7 SPC_INDIRECT_DATA structure. + * [in] p7: PKCS7 signature + * [out] ph: page hash + * [out] phlen: page hash length + * [out] phtype: NID_sha1 or NID_sha256 + * [returns] 0 on error or 1 on success + */ +static int pkcs7_get_page_hash(PKCS7 *p7, u_char **ph, int *phlen, int *phtype) +{ + SpcIndirectDataContent *idc = pkcs7_get_indirect_data_content(p7); + + if (!idc) { + fprintf(stderr, "Failed to decode SpcIndirectDataContent\n\n"); + return 0; /* FAILED */ + } + if (!idc->data) { + fprintf(stderr, "Missing SpcIndirectDataContent data\n\n"); + SpcIndirectDataContent_free(idc); + return 0; /* FAILED */ + } + if (!pe_page_hash_get(ph, phlen, phtype, idc->data)) { + fprintf(stderr, "Failed to extract a page hash\n\n"); + SpcIndirectDataContent_free(idc); + return 0; /* FAILED */ + } + SpcIndirectDataContent_free(idc); + return 1; /* OK */ +} + /* * Retrieve a page hash from SPC_INDIRECT_DATA structure. * [out] ph: page hash diff --git a/script.c b/script.c index 0b3cf3c..6148800 100644 --- a/script.c +++ b/script.c @@ -288,23 +288,7 @@ static int script_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7) const EVP_MD *md; BIO *bhash; - /* FIXME: this shared code most likely belongs in osslsigncode.c */ - 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 = ASN1_STRING_get0_data(content_val); - int len = ASN1_STRING_length(content_val); - SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &p, len); - - if (idc) { - if (spc_indirect_data_content_get_digest(idc, mdbuf, &mdtype) < 0) { - fprintf(stderr, "Failed to extract message digest from signature\n\n"); - SpcIndirectDataContent_free(idc); - return 0; /* FAILED */ - } - SpcIndirectDataContent_free(idc); - } - } - if (mdtype == -1) { + if (!pkcs7_get_content_digest(p7, mdbuf, &mdtype)) { fprintf(stderr, "Failed to extract current message digest\n\n"); return 0; /* FAILED */ }