diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 8e25d58a3..fd73cf804 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -14,6 +14,7 @@ endif() set(MSVC_GE $) set(MSVC_CONFIG $<${MSVC_GE}:$/>) +add_subdirectory(authenticode-parser) cond_add_subdirectory(capstone RETDEC_ENABLE_CAPSTONE) cond_add_subdirectory(elfio RETDEC_ENABLE_ELFIO) cond_add_subdirectory(googletest RETDEC_ENABLE_GOOGLETEST) diff --git a/deps/authenticode-parser/CMakeLists.txt b/deps/authenticode-parser/CMakeLists.txt new file mode 100644 index 000000000..d36a41bee --- /dev/null +++ b/deps/authenticode-parser/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.14) + +project(authenticode_parser VERSION 1.0.0 LANGUAGES C) + +find_package(OpenSSL 1.1.1 REQUIRED) + +include(GNUInstallDirs) + +add_library(authenticode STATIC + src/authenticode.c + src/helper.c + src/structs.c + src/countersignature.c + src/certificate.c +) + +add_library(retdec::deps::authenticode ALIAS authenticode) + +include (TestBigEndian) +TEST_BIG_ENDIAN(IS_BIG_ENDIAN) +if(IS_BIG_ENDIAN) + target_compile_definitions(-DWORDS_BIGENDIAN) +endif() + +target_compile_options(authenticode PRIVATE -Wall) +target_compile_features(authenticode PRIVATE c_std_11) + +target_include_directories(authenticode + PUBLIC + $ + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +target_link_libraries(authenticode + PRIVATE + OpenSSL::Crypto +) + +install( + DIRECTORY ${RETDEC_DEPS_DIR}/authenticode-parser/include/ + DESTINATION ${RETDEC_INSTALL_DEPS_INCLUDE_DIR} +) + +install(TARGETS authenticode + EXPORT authenticode-targets + ARCHIVE DESTINATION ${RETDEC_INSTALL_LIB_DIR} + LIBRARY DESTINATION ${RETDEC_INSTALL_LIB_DIR} +) + +install(EXPORT authenticode-targets + FILE "retdec-authenticode-targets.cmake" + NAMESPACE retdec::deps:: + DESTINATION ${RETDEC_INSTALL_CMAKE_DIR} +) + +configure_file( + "retdec-authenticode-config.cmake" + "${CMAKE_CURRENT_LIST_DIR}/retdec-authenticode-config.cmake" + @ONLY +) +install( + FILES "${CMAKE_CURRENT_LIST_DIR}/retdec-authenticode-config.cmake" + DESTINATION ${RETDEC_INSTALL_CMAKE_DIR} +) \ No newline at end of file diff --git a/deps/authenticode-parser/include/authenticode-parser/authenticode.h b/deps/authenticode-parser/include/authenticode-parser/authenticode.h new file mode 100644 index 000000000..98fbeede9 --- /dev/null +++ b/deps/authenticode-parser/include/authenticode-parser/authenticode.h @@ -0,0 +1,198 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef AUTHENTICODE_PARSER_AUTHENTICODE_H +#define AUTHENTICODE_PARSER_AUTHENTICODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/* Signature is valid */ +#define AUTHENTICODE_VFY_VALID 0 +/* Parsing error (from OpenSSL functions) */ +#define AUTHENTICODE_VFY_CANT_PARSE 1 +/* Signers certificate is missing */ +#define AUTHENTICODE_VFY_NO_SIGNER_CERT 2 +/* No digest saved inside the signature */ +#define AUTHENTICODE_VFY_DIGEST_MISSING 3 +/* Non verification errors - allocations etc. */ +#define AUTHENTICODE_VFY_INTERNAL_ERROR 4 +/* SignerInfo part of PKCS7 is missing */ +#define AUTHENTICODE_VFY_NO_SIGNER_INFO 5 +/* PKCS7 doesn't have type of SignedData, can't proceed */ +#define AUTHENTICODE_VFY_WRONG_PKCS7_TYPE 6 +/* PKCS7 doesn't have corrent content, can't proceed */ +#define AUTHENTICODE_VFY_BAD_CONTENT 7 +/* Contained and calculated digest don't match */ +#define AUTHENTICODE_VFY_INVALID 8 +/* Signature hash and file hash doesn't match */ +#define AUTHENTICODE_VFY_WRONG_FILE_DIGEST 9 +/* Unknown algorithm, can't proceed with verification */ +#define AUTHENTICODE_VFY_UNKNOWN_ALGORITHM 10 + +/* Countersignature is valid */ +#define COUNTERSIGNATURE_VFY_VALID 0 +/* Parsing error (from OpenSSL functions) */ +#define COUNTERSIGNATURE_VFY_CANT_PARSE 1 +/* Signers certificate is missing */ +#define COUNTERSIGNATURE_VFY_NO_SIGNER_CERT 2 +/* Unknown algorithm, can't proceed with verification */ +#define COUNTERSIGNATURE_VFY_UNKNOWN_ALGORITHM 3 +/* Verification failed, digest mismatch */ +#define COUNTERSIGNATURE_VFY_INVALID 4 +/* Failed to decrypt countersignature enc_digest for verification */ +#define COUNTERSIGNATURE_VFY_CANT_DECRYPT_DIGEST 5 +/* No digest saved inside the countersignature */ +#define COUNTERSIGNATURE_VFY_DIGEST_MISSING 6 +/* Message digest inside countersignature doesn't match signature it countersigns */ +#define COUNTERSIGNATURE_VFY_DOESNT_MATCH_SIGNATURE 7 +/* Non verification errors - allocations etc. */ +#define COUNTERSIGNATURE_VFY_INTERNAL_ERROR 8 +/* Time is missing in the timestamp signature */ +#define COUNTERSIGNATURE_VFY_TIME_MISSING 9 + +typedef struct { + uint8_t* data; + int len; +} ByteArray; + +typedef struct { /* Various X509 attributes parsed out in raw bytes*/ + ByteArray country; + ByteArray organization; + ByteArray organizationalUnit; + ByteArray nameQualifier; + ByteArray state; + ByteArray commonName; + ByteArray serialNumber; + ByteArray locality; + ByteArray title; + ByteArray surname; + ByteArray givenName; + ByteArray initials; + ByteArray pseudonym; + ByteArray generationQualifier; + ByteArray emailAddress; +} Attributes; + +typedef struct { + long version; /* Raw version of X509 */ + char* issuer; /* Oneline name of Issuer */ + char* subject; /* Oneline name of Subject */ + char* serial; /* Serial number in format 00:01:02:03:04... */ + ByteArray sha1; /* SHA1 of the DER representation of the cert */ + ByteArray sha256; /* SHA256 of the DER representation of the cert */ + char* key_alg; /* Name of the key algorithm */ + char* sig_alg; /* Name of the signature algorithm */ + time_t not_before; /* NotBefore validity */ + time_t not_after; /* NotAfter validity */ + char* key; /* PEM encoded public key */ + Attributes issuer_attrs; /* Parsed X509 Attributes of Issuer */ + Attributes subject_attrs; /* Parsed X509 Attributes of Subject */ +} Certificate; + +typedef struct { + Certificate** certs; + size_t count; +} CertificateArray; + +typedef struct { + int verify_flags; /* COUNTERISGNATURE_VFY_ flag */ + time_t sign_time; /* Signing time of the timestamp countersignature */ + char* digest_alg; /* Name of the digest algorithm used */ + ByteArray digest; /* Stored message digest */ + CertificateArray* chain; /* Certificate chain of the signer */ +} Countersignature; + +typedef struct { + Countersignature** counters; + size_t count; +} CountersignatureArray; + +typedef struct { /* Represents SignerInfo structure */ + ByteArray digest; /* Message Digest of the SignerInfo */ + char* digest_alg; /* name of the digest algorithm */ + char* program_name; /* Program name stored in SpcOpusInfo structure of Authenticode */ + CertificateArray* chain; /* Certificate chain of the signer */ +} Signer; + +typedef struct { + int verify_flags; /* AUTHENTICODE_VFY_ flag */ + int version; /* Raw PKCS7 version */ + char* digest_alg; /* name of the digest algorithm */ + ByteArray digest; /* File Digest stored in the Signature */ + ByteArray file_digest; /* Actual calculated file digest */ + Signer* signer; /* SignerInfo information of the Authenticode */ + CertificateArray* certs; /* All certificates in the Signature including the ones in timestamp + countersignatures */ + CountersignatureArray* countersigs; /* Array of timestamp countersignatures */ +} Authenticode; + +typedef struct { + Authenticode** signatures; + size_t count; +} AuthenticodeArray; + +/** + * @brief Constructs AuthenticodeArray from PE file data. Authenticode can + * contains nested Authenticode signatures as its unsigned attribute, + * which can also contain nested signatures. For this reason the function returns + * an Array of parsed Authenticode signatures. Any field of the parsed out + * structures can be NULL, depending on the input data. + * Verification result is stored in verify_flags with the first verification error. + * + * @param pe_data PE binary data + * @param pe_len + * @return AuthenticodeArray* + */ +AuthenticodeArray* parse_authenticode(const uint8_t* pe_data, long pe_len); + +/** + * @brief Constructs AuthenticodeArray from binary data containing Authenticode + * signature. Authenticode can contains nested Authenticode signatures + * as its unsigned attribute, which can also contain nested signatures. + * For this reason the function return an Array of parsed Authenticode signatures. + * Any field of the parsed out structures can be NULL, depending on the input data. + * WARNING: in case of this interface, the file and signature digest comparison is + * up to the library user, as there is no pe data to calculate file digest from. + * Verification result is stored in verify_flags with the first verification error + * + * @param data Binary data containing Authenticode signature + * @param len + * @return AuthenticodeArray* + */ +AuthenticodeArray* authenticode_new(const uint8_t* data, long len); + +/** + * @brief Deallocates AuthenticodeArray and all it's allocated members + * + * @param auth + */ +void authenticode_array_free(AuthenticodeArray* auth); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/deps/authenticode-parser/retdec-authenticode-config.cmake b/deps/authenticode-parser/retdec-authenticode-config.cmake new file mode 100644 index 000000000..06ecadcb8 --- /dev/null +++ b/deps/authenticode-parser/retdec-authenticode-config.cmake @@ -0,0 +1,4 @@ + +if(NOT TARGET retdec::deps::authenticode) + include(${CMAKE_CURRENT_LIST_DIR}/retdec-authenticode-targets.cmake) +endif() diff --git a/deps/authenticode-parser/src/authenticode.c b/deps/authenticode-parser/src/authenticode.c new file mode 100644 index 000000000..bd860fe32 --- /dev/null +++ b/deps/authenticode-parser/src/authenticode.c @@ -0,0 +1,640 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "certificate.h" +#include "countersignature.h" +#include "helper.h" +#include "structs.h" + +#define MAX_NESTED_COUNT 16 + +/* Moves signatures from src to dst, returns 0 on success, + * else 1. If error occurs, arguments are unchanged */ +static int authenticode_array_move(AuthenticodeArray* dst, AuthenticodeArray* src) +{ + size_t newCount = dst->count + src->count; + + Authenticode** tmp = (Authenticode**)realloc(dst->signatures, newCount * sizeof(Authenticode*)); + if (!tmp) + return 1; + + dst->signatures = tmp; + + for (size_t i = 0; i < src->count; ++i) + dst->signatures[i + dst->count] = src->signatures[i]; + + dst->count = newCount; + + free(src->signatures); + src->signatures = NULL; + src->count = 0; + + return 0; +} + +static SpcIndirectDataContent* get_content(PKCS7* content) +{ + if (!content) + return NULL; + + if (OBJ_obj2nid(content->type) != OBJ_txt2nid(NID_spc_indirect_data)) + return NULL; + + SpcIndirectDataContent* spcContent = SpcIndirectDataContent_new(); + if (!spcContent) + return NULL; + + int len = content->d.other->value.sequence->length; + const uint8_t* data = content->d.other->value.sequence->data; + + d2i_SpcIndirectDataContent(&spcContent, &data, len); + + return spcContent; +} + +static char* parse_program_name(ASN1_TYPE* spcAttr) +{ + const uint8_t* spcData = spcAttr->value.sequence->data; + int spcLen = spcAttr->value.sequence->length; + SpcSpOpusInfo* spcInfo = d2i_SpcSpOpusInfo(NULL, &spcData, spcLen); + if (!spcInfo) + return NULL; + + char* result = NULL; + + if (spcInfo->programName) { + uint8_t* data = NULL; + /* Should be Windows UTF16..., try to convert it to UTF8 */ + int nameLen = ASN1_STRING_to_UTF8(&data, spcInfo->programName->value.unicode); + if (nameLen >= 0 && nameLen < spcLen) { + result = (char*)malloc(nameLen + 1); + if (result) { + memcpy(result, data, nameLen); + result[nameLen] = 0; + } + OPENSSL_free(data); + } + } + + SpcSpOpusInfo_free(spcInfo); + return result; +} + +/* Parses X509* certs into internal representation and inserts into CertificateArray + * Array is assumed to have enough space to hold all certificates storted in the STACK */ +static void parse_certificates(const STACK_OF(X509) * certs, CertificateArray* result) +{ + int certCount = sk_X509_num(certs); + int i = 0; + for (; i < certCount; ++i) { + Certificate* cert = certificate_new(sk_X509_value(certs, i)); + if (!cert) + break; + + /* Write to the result */ + result->certs[i] = cert; + } + result->count = i; +} + +static void parse_nested_authenticode(PKCS7_SIGNER_INFO* si, AuthenticodeArray* result) +{ + STACK_OF(X509_ATTRIBUTE)* attrs = PKCS7_get_attributes(si); + int idx = X509at_get_attr_by_NID(attrs, OBJ_txt2nid(NID_spc_nested_signature), -1); + X509_ATTRIBUTE* attr = X509at_get_attr(attrs, idx); + + int attrCount = X509_ATTRIBUTE_count(attr); + if (!attrCount) + return; + + /* Limit the maximum amount of nested attributes to be safe from malformed samples */ + attrCount = attrCount > MAX_NESTED_COUNT ? MAX_NESTED_COUNT : attrCount; + + for (int i = 0; i < attrCount; ++i) { + ASN1_TYPE* nested = X509_ATTRIBUTE_get0_type(attr, i); + if (nested == NULL) + break; + int len = nested->value.sequence->length; + const uint8_t* data = nested->value.sequence->data; + AuthenticodeArray* auth = authenticode_new(data, len); + if (!auth) + continue; + + authenticode_array_move(result, auth); + authenticode_array_free(auth); + } +} + +static void parse_pkcs9_countersig(PKCS7* p7, Authenticode* auth) +{ + PKCS7_SIGNER_INFO* si = sk_PKCS7_SIGNER_INFO_value(PKCS7_get_signer_info(p7), 0); + + STACK_OF(X509_ATTRIBUTE)* attrs = PKCS7_get_attributes(si); + + int idx = X509at_get_attr_by_NID(attrs, NID_pkcs9_countersignature, -1); + X509_ATTRIBUTE* attr = X509at_get_attr(attrs, idx); + + int attrCount = X509_ATTRIBUTE_count(attr); + if (!attrCount) + return; + + /* Limit the maximum amount of nested attributes to be safe from malformed samples */ + attrCount = attrCount > MAX_NESTED_COUNT ? MAX_NESTED_COUNT : attrCount; + + for (int i = 0; i < attrCount; ++i) { + ASN1_TYPE* nested = X509_ATTRIBUTE_get0_type(attr, i); + if (nested == NULL) + break; + int len = nested->value.sequence->length; + const uint8_t* data = nested->value.sequence->data; + + Countersignature* sig = pkcs9_countersig_new(data, len, p7->d.sign->cert, si->enc_digest); + if (!sig) + continue; + + countersignature_array_insert(auth->countersigs, sig); + } +} + +/* Extracts X509 certificates from MS countersignature and stores them into result */ +static void extract_ms_counter_certs(const uint8_t* data, int len, CertificateArray* result) +{ + PKCS7* p7 = d2i_PKCS7(NULL, &data, len); + if (!p7) + return; + + STACK_OF(X509)* certs = p7->d.sign->cert; + CertificateArray* certArr = certificate_array_new(sk_X509_num(certs)); + if (!certArr) { + PKCS7_free(p7); + return; + } + parse_certificates(certs, certArr); + certificate_array_move(result, certArr); + certificate_array_free(certArr); + + PKCS7_free(p7); +} + +static void parse_ms_countersig(PKCS7* p7, Authenticode* auth) +{ + PKCS7_SIGNER_INFO* si = sk_PKCS7_SIGNER_INFO_value(PKCS7_get_signer_info(p7), 0); + + STACK_OF(X509_ATTRIBUTE)* attrs = PKCS7_get_attributes(si); + + int idx = X509at_get_attr_by_NID(attrs, OBJ_txt2nid(NID_spc_ms_countersignature), -1); + X509_ATTRIBUTE* attr = X509at_get_attr(attrs, idx); + + int attrCount = X509_ATTRIBUTE_count(attr); + if (!attrCount) + return; + + /* Limit the maximum amount of nested attributes to be safe from malformed samples */ + attrCount = attrCount > MAX_NESTED_COUNT ? MAX_NESTED_COUNT : attrCount; + + for (int i = 0; i < attrCount; ++i) { + ASN1_TYPE* nested = X509_ATTRIBUTE_get0_type(attr, i); + if (nested == NULL) + break; + int len = nested->value.sequence->length; + const uint8_t* data = nested->value.sequence->data; + + Countersignature* sig = ms_countersig_new(data, len, si->enc_digest); + if (!sig) + return; + + /* Because MS TimeStamp countersignature has it's own SET of certificates + * extract it back into parent signature for consistency with PKCS9 */ + countersignature_array_insert(auth->countersigs, sig); + extract_ms_counter_certs(data, len, auth->certs); + } +} + +static bool authenticode_verify(PKCS7* p7, PKCS7_SIGNER_INFO* si, X509* signCert) +{ + const uint8_t* contentData = p7->d.sign->contents->d.other->value.sequence->data; + long contentLen = p7->d.sign->contents->d.other->value.sequence->length; + + uint64_t version = 0; + ASN1_INTEGER_get_uint64(&version, p7->d.sign->version); + if (version == 1) { + /* Move the pointer to the actual contents - skip OID and length */ + int pclass = 0, ptag = 0; + ASN1_get_object(&contentData, &contentLen, &ptag, &pclass, contentLen); + } + + BIO* contentBio = BIO_new_mem_buf(contentData, contentLen); + /* Create `digest` type BIO to calculate content digest for verification */ + BIO* p7bio = PKCS7_dataInit(p7, contentBio); + + char buf[4096]; + /* We now have to 'read' from p7bio to calculate content digest */ + while (BIO_read(p7bio, buf, sizeof(buf)) > 0) + continue; + + /* Pass it to the PKCS7_signatureVerify, to do the hard work for us */ + bool isValid = PKCS7_signatureVerify(p7bio, p7, si, signCert) == 1; + + BIO_free_all(p7bio); + + return isValid; +} + +/* Creates all the Authenticode objects so we can parse them with OpenSSL */ +static void initialize_openssl() +{ + OBJ_create("1.3.6.1.4.1.311.2.1.12", "spcSpOpusInfo", "SPC_SP_OPUS_INFO_OBJID"); + OBJ_create("1.3.6.1.4.1.311.3.3.1", "spcMsCountersignature", "SPC_MICROSOFT_COUNTERSIGNATURE"); + OBJ_create("1.3.6.1.4.1.311.2.4.1", "spcNestedSignature", "SPC_NESTED_SIGNATUREs"); + OBJ_create("1.3.6.1.4.1.311.2.1.4", "spcIndirectData", "SPC_INDIRECT_DATA"); +} + +/* Return array of Authenticode signatures stored in the data, there can be multiple + * of signatures as Authenticode signatures are often nested through unauth attributes */ +AuthenticodeArray* authenticode_new(const uint8_t* data, long len) +{ + if (!data || len == 0) + return NULL; + + /* We need to initialize all the custom objects for further parsing */ + initialize_openssl(); + + AuthenticodeArray* result = (AuthenticodeArray*)calloc(1, sizeof(*result)); + if (!result) + return NULL; + + result->signatures = (Authenticode**)malloc(sizeof(Authenticode*)); + if (!result->signatures) { + free(result); + return NULL; + } + + Authenticode* auth = (Authenticode*)calloc(1, sizeof(*auth)); + if (!auth) { + free(result); + free(result->signatures); + return NULL; + } + + result->count = 1; + result->signatures[0] = auth; + + /* Let openssl parse the PKCS7 structure */ + PKCS7* p7 = d2i_PKCS7(NULL, &data, len); + if (!p7) { + auth->verify_flags = AUTHENTICODE_VFY_CANT_PARSE; + goto end; + } + + /* We expect SignedData type of PKCS7 */ + if (!PKCS7_type_is_signed(p7)) { + auth->verify_flags = AUTHENTICODE_VFY_WRONG_PKCS7_TYPE; + goto end; + } + + PKCS7_SIGNED* p7data = p7->d.sign; + + uint64_t version = 0; + if (ASN1_INTEGER_get_uint64(&version, p7data->version)) + auth->version = version; + + STACK_OF(X509)* certs = p7data->cert; + + auth->certs = certificate_array_new(sk_X509_num(certs)); + if (!auth->certs) { + auth->verify_flags = AUTHENTICODE_VFY_INTERNAL_ERROR; + goto end; + } + parse_certificates(certs, auth->certs); + + /* Get Signature content that contains the message digest and it's algorithm */ + SpcIndirectDataContent* dataContent = get_content(p7data->contents); + if (!dataContent) { + auth->verify_flags = AUTHENTICODE_VFY_BAD_CONTENT; + goto end; + } + + DigestInfo* messageDigest = dataContent->messageDigest; + + int digestnid = OBJ_obj2nid(messageDigest->digestAlgorithm->algorithm); + auth->digest_alg = strdup(OBJ_nid2ln(digestnid)); + + int digestLen = messageDigest->digest->length; + const uint8_t* digestData = messageDigest->digest->data; + byte_array_init(&auth->digest, digestData, digestLen); + + SpcIndirectDataContent_free(dataContent); + + Signer* signer = (Signer*)calloc(1, sizeof(Signer)); + if (!signer) { + auth->verify_flags = AUTHENTICODE_VFY_INTERNAL_ERROR; + goto end; + } + auth->signer = signer; + + /* Authenticode is supposed to have only one SignerInfo value + * that contains all information for actual signing purposes + * and nested signatures or countersignatures */ + PKCS7_SIGNER_INFO* si = sk_PKCS7_SIGNER_INFO_value(PKCS7_get_signer_info(p7), 0); + if (!si) { + auth->verify_flags = AUTHENTICODE_VFY_NO_SIGNER_INFO; + goto end; + } + + auth->countersigs = (CountersignatureArray*)calloc(1, sizeof(CountersignatureArray)); + if (!auth->countersigs) { + auth->verify_flags = AUTHENTICODE_VFY_INTERNAL_ERROR; + goto end; + } + /* Authenticode can contain SET of nested Authenticode signatures + * and countersignatures in unauthenticated attributes */ + parse_nested_authenticode(si, result); + parse_pkcs9_countersig(p7, auth); + parse_ms_countersig(p7, auth); + + /* Get the signing certificate for the first SignerInfo */ + STACK_OF(X509)* signCertStack = PKCS7_get0_signers(p7, certs, 0); + + X509* signCert = sk_X509_value(signCertStack, 0); + if (!signCert) { + auth->verify_flags = AUTHENTICODE_VFY_NO_SIGNER_CERT; + sk_X509_free(signCertStack); + goto end; + } + + sk_X509_free(signCertStack); + + signer->chain = parse_signer_chain(signCert, certs); + + /* Get the Signers digest of Authenticode content */ + ASN1_TYPE* digest = PKCS7_get_signed_attribute(si, NID_pkcs9_messageDigest); + if (!digest) { + auth->verify_flags = AUTHENTICODE_VFY_DIGEST_MISSING; + goto end; + } + + digestnid = OBJ_obj2nid(si->digest_alg->algorithm); + signer->digest_alg = strdup(OBJ_nid2ln(digestnid)); + + digestLen = digest->value.asn1_string->length; + digestData = digest->value.asn1_string->data; + byte_array_init(&signer->digest, digestData, digestLen); + + /* Authenticode stores optional programName in non-optional SpcSpOpusInfo attribute */ + ASN1_TYPE* spcInfo = PKCS7_get_signed_attribute(si, OBJ_txt2nid(NID_spc_info)); + if (spcInfo) + signer->program_name = parse_program_name(spcInfo); + + /* If we got to this point, we got all we need to start verifying */ + bool isValid = authenticode_verify(p7, si, signCert); + if (!isValid) + auth->verify_flags = AUTHENTICODE_VFY_INVALID; + +end: + PKCS7_free(p7); + return result; +} + +static int authenticode_digest( + const EVP_MD* md, + const uint8_t* pe_data, + uint32_t pe_hdr_offset, + bool is_64bit, + uint32_t cert_table_addr, + uint8_t* digest) +{ + uint32_t buffer_size = 0xFFFF; + uint8_t* buffer = (uint8_t*)malloc(buffer_size); + + /* BIO with the file data */ + BIO* bio = BIO_new_mem_buf(pe_data, cert_table_addr); + + EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); + if (!buffer || !bio || !mdctx) + goto error; + + if (!EVP_DigestInit(mdctx, md)) + goto error; + + /* Calculate size of the space between file start and PE header */ + /* Checksum starts at 0x58th byte of the header */ + uint32_t pe_checksum_offset = pe_hdr_offset + 0x58; + /* Space between DOS and PE header could have arbitrary amount of data, read in chunks */ + uint32_t fpos = 0; + while (fpos < pe_checksum_offset) { + uint32_t len_to_read = pe_checksum_offset - fpos; + if (len_to_read > buffer_size) + len_to_read = buffer_size; + + int rlen = BIO_read(bio, buffer, len_to_read); + if (rlen <= 0) + goto error; + + if (!EVP_DigestUpdate(mdctx, buffer, rlen)) + goto error; + + fpos += rlen; + } + + /* Skip the checksum */ + if (BIO_read(bio, buffer, 4) <= 0) + goto error; + + /* 64bit PE file is larger than 32bit */ + uint32_t pe64_extra = is_64bit ? 16 : 0; + + /* Read up to certificate table*/ + uint32_t cert_table_offset = 0x3c + pe64_extra; + + if (BIO_read(bio, buffer, cert_table_offset) <= 0) + goto error; + + if (!EVP_DigestUpdate(mdctx, buffer, cert_table_offset)) + goto error; + + /* Skip certificate table */ + if (BIO_read(bio, buffer, 8) <= 0) + goto error; + + /* PE header with check sum + checksum + cert table offset + cert table len */ + fpos = pe_checksum_offset + 4 + cert_table_offset + 8; + + /* Hash everything up to the signature (assuming signature is stored in the + * end of the file) */ + /* Read chunks of the file in case the file is large */ + while (fpos < cert_table_addr) { + uint32_t len_to_read = cert_table_addr - fpos; + if (len_to_read > buffer_size) + len_to_read = buffer_size; + + int rlen = BIO_read(bio, buffer, len_to_read); + if (rlen <= 0) + goto error; + + if (!EVP_DigestUpdate(mdctx, buffer, rlen)) + goto error; + fpos += rlen; + } + + /* Calculate the digest, write it into digest */ + if (!EVP_DigestFinal(mdctx, digest, NULL)) + goto error; + + EVP_MD_CTX_free(mdctx); + BIO_free_all(bio); + free(buffer); + return 0; + +error: + EVP_MD_CTX_free(mdctx); + BIO_free_all(bio); + free(buffer); + return 1; +} + +AuthenticodeArray* parse_authenticode(const uint8_t* pe_data, long pe_len) +{ + const int dos_hdr_size = 0x40; + if (pe_len < dos_hdr_size) + return NULL; + + /* offset to pointer in DOS header, that points to PE header */ + const int pe_hdr_ptr_offset = 0x3c; + /* Read the PE offset */ + uint32_t pe_offset = letoh32(*(uint32_t*)(pe_data + pe_hdr_ptr_offset)); + /* Offset to Magic, to know the PE class (32/64bit) */ + uint32_t magic_addr = pe_offset + 0x18; + + if (pe_len < magic_addr + sizeof(uint16_t)) + return NULL; + + /* Read the magic and check if we have 64bit PE */ + uint16_t magic = letoh16(*(uint16_t*)(pe_data + magic_addr)); + bool is_64bit = magic == 0x20b; + /* If PE is 64bit, header is 16 bytes larger */ + uint8_t pe64_extra = is_64bit ? 16 : 0; + + /* Calculate offset to certificate table directory */ + uint32_t pe_cert_table_addr = pe_offset + pe64_extra + 0x98; + + if (pe_len < pe_cert_table_addr + 2 * sizeof(uint32_t)) + return NULL; + + uint32_t cert_addr = letoh32(*(uint32_t*)(pe_data + pe_cert_table_addr)); + uint32_t cert_len = letoh32(*(uint32_t*)(pe_data + pe_cert_table_addr + 4)); + + /* we need atleast 8 bytes to read dwLength, revision and certType */ + if (cert_len < 8 || pe_len < cert_addr + cert_len) + return NULL; + + uint32_t dwLength = letoh32(*(uint32_t*)(pe_data + cert_addr)); + if (pe_len < cert_addr + dwLength) + return NULL; + + AuthenticodeArray* auth_array = authenticode_new(pe_data + cert_addr + 0x8, dwLength); + if (!auth_array) + return NULL; + + /* Compare valid signatures file digests to actual file digest, to complete verification */ + for (size_t i = 0; i < auth_array->count; ++i) { + Authenticode* sig = auth_array->signatures[i]; + + const EVP_MD* md = EVP_get_digestbyname(sig->digest_alg); + if (!md || !sig->digest.len || !sig->digest.data) { + /* If there is an verification error, keep the first error */ + if (sig->verify_flags == AUTHENTICODE_VFY_VALID) + sig->verify_flags = AUTHENTICODE_VFY_UNKNOWN_ALGORITHM; + + continue; + } + + int mdlen = EVP_MD_size(md); + sig->file_digest.len = mdlen; + sig->file_digest.data = (uint8_t*)malloc(mdlen); + if (!sig->file_digest.data) + continue; + + if (authenticode_digest( + md, pe_data, pe_offset, is_64bit, cert_addr, sig->file_digest.data)) { + + /* If there is an verification error, keep the first error */ + if (sig->verify_flags == AUTHENTICODE_VFY_VALID) + sig->verify_flags = AUTHENTICODE_VFY_INTERNAL_ERROR; + break; + } + + /* Complete the verification */ + if (memcmp(sig->file_digest.data, sig->digest.data, mdlen) != 0) + sig->verify_flags = AUTHENTICODE_VFY_WRONG_FILE_DIGEST; + } + + return auth_array; +} + +static void signer_free(Signer* si) +{ + if (si) { + free(si->digest.data); + free(si->digest_alg); + free(si->program_name); + certificate_array_free(si->chain); + free(si); + } +} + +static void authenticode_free(Authenticode* auth) +{ + if (auth) { + free(auth->digest.data); + free(auth->file_digest.data); + free(auth->digest_alg); + signer_free(auth->signer); + certificate_array_free(auth->certs); + countersignature_array_free(auth->countersigs); + free(auth); + } +} + +void authenticode_array_free(AuthenticodeArray* arr) +{ + if (arr) { + for (size_t i = 0; i < arr->count; ++i) { + authenticode_free(arr->signatures[i]); + } + free(arr->signatures); + free(arr); + } +} diff --git a/deps/authenticode-parser/src/certificate.c b/deps/authenticode-parser/src/certificate.c new file mode 100644 index 000000000..7686c5161 --- /dev/null +++ b/deps/authenticode-parser/src/certificate.c @@ -0,0 +1,383 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "certificate.h" + +#include +#include +#include +#include +#include +#include + +#include "helper.h" + +static void parse_name_attributes(X509_NAME* raw, Attributes* attr) +{ + if (!raw || !attr) + return; + + int entryCount = X509_NAME_entry_count(raw); + for (int i = entryCount - 1; i >= 0; --i) { + X509_NAME_ENTRY* entryName = X509_NAME_get_entry(raw, i); + ASN1_STRING* asn1String = X509_NAME_ENTRY_get_data(entryName); + + const char* key = OBJ_nid2sn(OBJ_obj2nid(X509_NAME_ENTRY_get_object(entryName))); + + ByteArray array = {0}; + if (byte_array_init(&array, asn1String->data, asn1String->length) == -1) + break; + + if (strcmp(key, "C") == 0 && !attr->country.data) + attr->country = array; + else if (strcmp(key, "O") == 0 && !attr->organization.data) + attr->organization = array; + else if (strcmp(key, "OU") == 0 && !attr->organizationalUnit.data) + attr->organizationalUnit = array; + else if (strcmp(key, "dnQualifier") == 0 && !attr->nameQualifier.data) + attr->nameQualifier = array; + else if (strcmp(key, "ST") == 0 && !attr->state.data) + attr->state = array; + else if (strcmp(key, "CN") == 0 && !attr->commonName.data) + attr->commonName = array; + else if (strcmp(key, "serialNumber") == 0 && !attr->serialNumber.data) + attr->serialNumber = array; + else if (strcmp(key, "L") == 0 && !attr->locality.data) + attr->locality = array; + else if (strcmp(key, "title") == 0 && !attr->title.data) + attr->title = array; + else if (strcmp(key, "SN") == 0 && !attr->surname.data) + attr->surname = array; + else if (strcmp(key, "GN") == 0 && !attr->givenName.data) + attr->givenName = array; + else if (strcmp(key, "initials") == 0 && !attr->initials.data) + attr->initials = array; + else if (strcmp(key, "pseudonym") == 0 && !attr->pseudonym.data) + attr->pseudonym = array; + else if (strcmp(key, "generationQualifier") == 0 && !attr->generationQualifier.data) + attr->generationQualifier = array; + else if (strcmp(key, "emailAddress") == 0 && !attr->emailAddress.data) + attr->emailAddress = array; + else + free(array.data); + } +} + +/* Reconstructs signers certificate chain */ +CertificateArray* parse_signer_chain(X509* signCert, STACK_OF(X509) * certs) +{ + if (!signCert || !certs) + return NULL; + + X509_STORE* store = X509_STORE_new(); + if (!store) + return NULL; + + X509_STORE_CTX* storeCtx = X509_STORE_CTX_new(); + if (!storeCtx) { + X509_STORE_CTX_free(storeCtx); + return NULL; + } + + X509_STORE_CTX_init(storeCtx, store, signCert, certs); + + /* I can't find ability to use this function for static verification with missing trust anchors, + * because roots are generally not part of the PKCS7 signatures, so the return value is + * currently ignored and the function is only used to build the certificate chain */ + X509_verify_cert(storeCtx); + + STACK_OF(X509)* chain = X509_STORE_CTX_get_chain(storeCtx); + + int certCount = sk_X509_num(chain); + + CertificateArray* result = (CertificateArray*)malloc(sizeof(*result)); + if (!result) + goto error; + + result->certs = (Certificate**)malloc(sizeof(Certificate*) * certCount); + if (!result->certs) + goto error; + + result->count = 0; + /* Convert each certificate to internal representation */ + for (int i = 0; i < certCount; ++i) { + Certificate* cert = certificate_new(sk_X509_value(chain, i)); + if (!cert) + goto error; + + result->certs[i] = cert; + result->count++; + } + + X509_STORE_free(store); + X509_STORE_CTX_free(storeCtx); + return result; + +error: /* In case of error, return nothing */ + free(result); + if (result) { + for (size_t i = 0; i < result->count; ++i) { + certificate_free(result->certs[i]); + } + free(result->certs); + } + X509_STORE_free(store); + X509_STORE_CTX_free(storeCtx); + + return NULL; +} + +/* Taken from YARA for compatibility */ +static char* integer_to_serial(ASN1_INTEGER* serial) +{ + int bytes = i2d_ASN1_INTEGER(serial, NULL); + + char* res = NULL; + /* According to X.509 specification the maximum length for the + * serial number is 20 octets. Add two bytes to account for + * DER type and length information. */ + if (bytes < 2 || bytes > 22) + return NULL; + + /* Now that we know the size of the serial number allocate enough + * space to hold it, and use i2d_ASN1_INTEGER() one last time to + * hold it in the allocated buffer. */ + uint8_t* serial_der = (uint8_t*)malloc(bytes); + if (!serial_der) + return NULL; + + uint8_t* serial_bytes; + + bytes = i2d_ASN1_INTEGER(serial, &serial_der); + + /* i2d_ASN1_INTEGER() moves the pointer as it writes into + serial_bytes. Move it back. */ + serial_der -= bytes; + + /* Skip over DER type, length information */ + serial_bytes = serial_der + 2; + bytes -= 2; + + /* Also allocate space to hold the "common" string format: + * 00:01:02:03:04... + * + * For each byte in the serial to convert to hexlified format we + * need three bytes, two for the byte itself and one for colon. + * The last one doesn't have the colon, but the extra byte is used + * for the NULL terminator. */ + res = (char*)malloc(bytes * 3); + if (res) { + for (int i = 0; i < bytes; i++) { + /* Don't put the colon on the last one. */ + if (i < bytes - 1) + snprintf(res + 3 * i, 4, "%02x:", serial_bytes[i]); + else + snprintf(res + 3 * i, 3, "%02x", serial_bytes[i]); + } + } + free(serial_der); + + return (char*)res; +} + +/* Converts the pubkey to pem, which is just + * Base64 encoding of the DER representation */ +static char* pubkey_to_pem(EVP_PKEY* pubkey) +{ + uint8_t* der = NULL; + int len = i2d_PUBKEY(pubkey, &der); /* Convert to DER */ + if (len <= 0) + return NULL; + + /* Approximate the result length (padding, newlines, 4 out bytes for every 3 in) */ + uint8_t* result = (uint8_t*)malloc(len * 3 / 2); + if (!result) { + OPENSSL_free(der); + return NULL; + } + + /* Base64 encode the DER data */ + EVP_ENCODE_CTX* ctx = EVP_ENCODE_CTX_new(); + if (!ctx) { + OPENSSL_free(der); + free(result); + return NULL; + } + + int resultLen = 0; + int tmp = 0; + EVP_EncodeInit(ctx); + EVP_EncodeUpdate(ctx, result, &tmp, der, len); + resultLen += tmp; + EVP_EncodeFinal(ctx, result + resultLen, &tmp); + resultLen += tmp; + + EVP_ENCODE_CTX_free(ctx); + OPENSSL_free(der); + + /* Remove all newlines from the encoded base64 + * resultLen is excluding NULL terminator */ + for (int i = 0; result[i] != 0; i++) { + if (result[i] == '\n') + memmove(result + i, result + i + 1, resultLen - i); + } + + return (char*)result; +} + +Certificate* certificate_new(X509* x509) +{ + Certificate* result = (Certificate*)calloc(1, sizeof(*result)); + if (!result) + return NULL; + + /* Calculate SHA1 and SHA256 digests of the X509 structure */ + result->sha1.data = (uint8_t*)malloc(SHA_DIGEST_LENGTH); + if (result->sha1.data) { + X509_digest(x509, EVP_sha1(), result->sha1.data, NULL); + result->sha1.len = SHA_DIGEST_LENGTH; + } + + result->sha256.data = (uint8_t*)malloc(SHA256_DIGEST_LENGTH); + if (result->sha256.data) { + X509_digest(x509, EVP_sha256(), result->sha256.data, NULL); + result->sha256.len = SHA256_DIGEST_LENGTH; + } + + /* 256 bytes should be enough for any name */ + char buffer[256]; + + /* X509_NAME_online is deprecated and shouldn't be used per OpenSSL docs + * but we want to comply with existing YARA code */ + X509_NAME* issuerName = X509_get_issuer_name(x509); + X509_NAME_oneline(issuerName, buffer, sizeof(buffer)); + result->issuer = strdup(buffer); + + X509_NAME* subjectName = X509_get_subject_name(x509); + X509_NAME_oneline(subjectName, buffer, sizeof(buffer)); + result->subject = strdup(buffer); + + parse_name_attributes(issuerName, &result->issuer_attrs); + parse_name_attributes(subjectName, &result->subject_attrs); + + result->version = X509_get_version(x509); + result->serial = integer_to_serial(X509_get_serialNumber(x509)); + result->not_after = ASN1_TIME_to_time_t(X509_get0_notAfter(x509)); + result->not_before = ASN1_TIME_to_time_t(X509_get0_notBefore(x509)); + result->sig_alg = strdup(OBJ_nid2ln(X509_get_signature_nid(x509))); + + EVP_PKEY* pkey = X509_get0_pubkey(x509); + if (pkey) { + result->key = pubkey_to_pem(pkey); + result->key_alg = strdup(OBJ_nid2sn(EVP_PKEY_base_id(pkey))); + } + + return result; +} + +/* Moves certificates from src to dst, returns 0 on success, + * else 1. If error occurs, arguments are unchanged */ +int certificate_array_move(CertificateArray* dst, CertificateArray* src) +{ + size_t newCount = dst->count + src->count; + + Certificate** tmp = (Certificate**)realloc(dst->certs, newCount * sizeof(Certificate*)); + if (!tmp) + return 1; + + dst->certs = tmp; + + for (size_t i = 0; i < src->count; ++i) + dst->certs[i + dst->count] = src->certs[i]; + + dst->count = newCount; + + free(src->certs); + src->certs = NULL; + src->count = 0; + + return 0; +} + +/* Allocates empty certificate array with reserved space for certCount certs */ +CertificateArray* certificate_array_new(int certCount) +{ + CertificateArray* arr = (CertificateArray*)malloc(sizeof(*arr)); + if (!arr) + return NULL; + + arr->certs = (Certificate**)malloc(sizeof(Certificate*) * certCount); + if (!arr->certs) { + free(arr); + return NULL; + } + + arr->count = certCount; + + return arr; +} + +static void certificate_attributes_free(Attributes attrs) +{ + free(attrs.country.data); + free(attrs.organization.data); + free(attrs.organizationalUnit.data); + free(attrs.nameQualifier.data); + free(attrs.state.data); + free(attrs.commonName.data); + free(attrs.serialNumber.data); + free(attrs.locality.data); + free(attrs.title.data); + free(attrs.surname.data); + free(attrs.givenName.data); + free(attrs.initials.data); + free(attrs.pseudonym.data); + free(attrs.generationQualifier.data); + free(attrs.emailAddress.data); +} + +void certificate_free(Certificate* cert) +{ + if (cert) { + free(cert->issuer); + free(cert->subject); + free(cert->sig_alg); + free(cert->key_alg); + free(cert->key); + free(cert->sha1.data); + free(cert->sha256.data); + free(cert->serial); + certificate_attributes_free(cert->issuer_attrs); + certificate_attributes_free(cert->subject_attrs); + free(cert); + } +} + +void certificate_array_free(CertificateArray* arr) +{ + if (arr) { + for (size_t i = 0; i < arr->count; ++i) { + certificate_free(arr->certs[i]); + } + free(arr->certs); + free(arr); + } +} diff --git a/deps/authenticode-parser/src/certificate.h b/deps/authenticode-parser/src/certificate.h new file mode 100644 index 000000000..aefb797c6 --- /dev/null +++ b/deps/authenticode-parser/src/certificate.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef AUTHENTICODE_PARSER_CERTIFICATE_H +#define AUTHENTICODE_PARSER_CERTIFICATE_H + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +Certificate* certificate_new(X509* x509); +void certificate_free(Certificate* cert); + +CertificateArray* parse_signer_chain(X509* signer_cert, STACK_OF(X509) * certs); +int certificate_array_move(CertificateArray* dst, CertificateArray* src); +CertificateArray* certificate_array_new(int certCount); +void certificate_array_free(CertificateArray* arr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/deps/authenticode-parser/src/countersignature.c b/deps/authenticode-parser/src/countersignature.c new file mode 100644 index 000000000..5bc2c108a --- /dev/null +++ b/deps/authenticode-parser/src/countersignature.c @@ -0,0 +1,352 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "countersignature.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "certificate.h" +#include "helper.h" +#include "structs.h" + +Countersignature* pkcs9_countersig_new( + const uint8_t* data, long size, STACK_OF(X509) * certs, ASN1_STRING* enc_digest) +{ + Countersignature* result = (Countersignature*)calloc(1, sizeof(*result)); + if (!result) + return NULL; + + PKCS7_SIGNER_INFO* si = d2i_PKCS7_SIGNER_INFO(NULL, &data, size); + if (!si) { + result->verify_flags = COUNTERSIGNATURE_VFY_CANT_PARSE; + return result; + } + + int digestnid = OBJ_obj2nid(si->digest_alg->algorithm); + result->digest_alg = strdup(OBJ_nid2ln(digestnid)); + + /* Get digest that corresponds to decrypted encrypted digest in signature */ + ASN1_TYPE* messageDigest = PKCS7_get_signed_attribute(si, NID_pkcs9_messageDigest); + + const ASN1_TYPE* sign_time = PKCS7_get_signed_attribute(si, NID_pkcs9_signingTime); + result->sign_time = ASN1_TIME_to_time_t(sign_time->value.utctime); + + X509* signCert = X509_find_by_issuer_and_serial( + certs, si->issuer_and_serial->issuer, si->issuer_and_serial->serial); + /* PKCS9 stores certificates in the corresponding PKCS7 it countersigns */ + result->chain = parse_signer_chain(signCert, certs); + + if (!sign_time) { + result->verify_flags = COUNTERSIGNATURE_VFY_TIME_MISSING; + goto end; + } + if (!signCert) { + result->verify_flags = COUNTERSIGNATURE_VFY_NO_SIGNER_CERT; + goto end; + } + if (!messageDigest) { + result->verify_flags = COUNTERSIGNATURE_VFY_DIGEST_MISSING; + goto end; + } + + size_t digestLen = messageDigest->value.octet_string->length; + + if (!digestLen) { + result->verify_flags = COUNTERSIGNATURE_VFY_DIGEST_MISSING; + goto end; + } + + const EVP_MD* md = EVP_get_digestbynid(digestnid); + if (!md) { + result->verify_flags = COUNTERSIGNATURE_VFY_UNKNOWN_ALGORITHM; + goto end; + } + + const uint8_t* digestData = messageDigest->value.octet_string->data; + byte_array_init(&result->digest, digestData, digestLen); + + /* By this point we all necessary things for verification + * Get DER representation of the authenticated attributes to calculate its + * digest that should correspond with the one encrypted in SignerInfo */ + uint8_t* authAttrsData = NULL; + int authAttrsLen = ASN1_item_i2d( + (ASN1_VALUE*)si->auth_attr, &authAttrsData, ASN1_ITEM_rptr(PKCS7_ATTR_VERIFY)); + + uint8_t calc_digest[EVP_MAX_MD_SIZE]; + calculate_digest(md, authAttrsData, authAttrsLen, calc_digest); + OPENSSL_free(authAttrsData); + + /* Get public key to decrypt encrypted digest of auth attrs */ + EVP_PKEY* pkey = X509_get0_pubkey(signCert); + EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); + + /* TODO try to get rid of hardcoded length bound */ + size_t decLen = 65536; + uint8_t* decData = (uint8_t*)malloc(decLen); + if (!decData) { + EVP_PKEY_CTX_free(ctx); + result->verify_flags = COUNTERSIGNATURE_VFY_INTERNAL_ERROR; + goto end; + } + + uint8_t* encData = si->enc_digest->data; + size_t encLen = si->enc_digest->length; + + /* Decrypt the encrypted digest */ + EVP_PKEY_verify_recover_init(ctx); + bool isDecrypted = EVP_PKEY_verify_recover(ctx, decData, &decLen, encData, encLen) == 1; + EVP_PKEY_CTX_free(ctx); + + if (!isDecrypted) { + free(decData); + result->verify_flags = COUNTERSIGNATURE_VFY_CANT_DECRYPT_DIGEST; + goto end; + } + + /* compare the encrypted digest and calculated digest */ + bool isValid = false; + /* Sometimes signed data contains DER encoded DigestInfo structure which contains hash of + * authenticated attributes (39c9d136f026a9ad18fb9f41a64f76dd8418e8de625dce5d3a372bd242fc5edd) + * but other times it is just purely and I didn't find another way to distinguish it but only + * based on the length of data we get. Found mention of this in openssl mailing list: + * https://mta.openssl.org/pipermail/openssl-users/2015-September/002054.html */ + size_t mdLen = EVP_MD_size(md); + if (mdLen == decLen) { + isValid = !memcmp(calc_digest, decData, mdLen); + } else { + const uint8_t* data_ptr = decData; + DigestInfo* digest_info = d2i_DigestInfo(NULL, &data_ptr, decLen); + if (digest_info) { + isValid = !memcmp(digest_info->digest->data, calc_digest, mdLen); + DigestInfo_free(digest_info); + } else { + isValid = false; + } + } + free(decData); + + if (!isValid) { + result->verify_flags = COUNTERSIGNATURE_VFY_INVALID; + goto end; + } + + /* Now check the countersignature message-digest that should correspond + * to Signatures encrypted digest it countersigns */ + calculate_digest(md, enc_digest->data, enc_digest->length, calc_digest); + + /* Check if calculated one matches the stored one */ + if (digestLen != mdLen || memcmp(calc_digest, digestData, mdLen) != 0) { + result->verify_flags = COUNTERSIGNATURE_VFY_DOESNT_MATCH_SIGNATURE; + goto end; + } + +end: + PKCS7_SIGNER_INFO_free(si); + return result; +} + +Countersignature* ms_countersig_new(const uint8_t* data, long size, ASN1_STRING* enc_digest) +{ + Countersignature* result = (Countersignature*)calloc(1, sizeof(*result)); + if (!result) + return NULL; + + PKCS7* p7 = d2i_PKCS7(NULL, &data, size); + if (!p7) { + result->verify_flags = COUNTERSIGNATURE_VFY_CANT_PARSE; + return result; + } + + TS_TST_INFO* ts = PKCS7_to_TS_TST_INFO(p7); + if (!ts) { + result->verify_flags = COUNTERSIGNATURE_VFY_CANT_PARSE; + PKCS7_free(p7); + return result; + } + + const ASN1_TIME* rawTime = TS_TST_INFO_get_time(ts); + result->sign_time = ASN1_TIME_to_time_t(rawTime); + + STACK_OF(X509)* sigs = PKCS7_get0_signers(p7, p7->d.sign->cert, 0); + X509* signCert = sk_X509_value(sigs, 0); + result->chain = parse_signer_chain(signCert, p7->d.sign->cert); + + /* Imprint == digest */ + TS_MSG_IMPRINT* imprint = TS_TST_INFO_get_msg_imprint(ts); + + if (!rawTime) { + result->verify_flags = COUNTERSIGNATURE_VFY_TIME_MISSING; + goto end; + } + if (!signCert) { + result->verify_flags = COUNTERSIGNATURE_VFY_NO_SIGNER_CERT; + goto end; + } + if (!imprint) { + result->verify_flags = COUNTERSIGNATURE_VFY_DIGEST_MISSING; + goto end; + } + + X509_ALGOR* digestAlg = TS_MSG_IMPRINT_get_algo(imprint); + int digestnid = OBJ_obj2nid(digestAlg->algorithm); + result->digest_alg = strdup(OBJ_nid2ln(digestnid)); + + ASN1_STRING* rawDigest = TS_MSG_IMPRINT_get_msg(imprint); + + int digestLen = rawDigest->length; + uint8_t* digestData = rawDigest->data; + + byte_array_init(&result->digest, digestData, digestLen); + + if (!digestLen) { + result->verify_flags = COUNTERSIGNATURE_VFY_DIGEST_MISSING; + goto end; + } + + const EVP_MD* md = EVP_get_digestbynid(digestnid); + if (!md) { + result->verify_flags = COUNTERSIGNATURE_VFY_UNKNOWN_ALGORITHM; + goto end; + } + + uint8_t calc_digest[EVP_MAX_MD_SIZE]; + calculate_digest(md, enc_digest->data, enc_digest->length, calc_digest); + int mdLen = EVP_MD_size(md); + + if (digestLen != mdLen || memcmp(calc_digest, digestData, mdLen) != 0) { + result->verify_flags = COUNTERSIGNATURE_VFY_DOESNT_MATCH_SIGNATURE; + goto end; + } + + TS_VERIFY_CTX* ctx = TS_VERIFY_CTX_new(); + X509_STORE* store = X509_STORE_new(); + TS_VERIFY_CTX_init(ctx); + + TS_VERIFY_CTX_set_flags(ctx, TS_VFY_VERSION | TS_VFY_IMPRINT); + TS_VERIFY_CTX_set_store(ctx, store); + TS_VERIFY_CTS_set_certs(ctx, p7->d.sign->cert); + TS_VERIFY_CTX_set_imprint(ctx, calc_digest, mdLen); + + bool isValid = TS_RESP_verify_token(ctx, p7) == 1; + + /* VERIFY_CTX_free tries to free these, we don't want that */ + TS_VERIFY_CTX_set_imprint(ctx, NULL, 0); + TS_VERIFY_CTS_set_certs(ctx, NULL); + + TS_VERIFY_CTX_free(ctx); + + if (!isValid) { + result->verify_flags = COUNTERSIGNATURE_VFY_INVALID; + goto end; + } + + /* Verify signature with PKCS7_signatureVerify + because TS_RESP_verify_token would try to verify + chain and without trust anchors it always fails */ + BIO* p7bio = PKCS7_dataInit(p7, NULL); + + char buf[4096]; + /* We now have to 'read' from p7bio to calculate digests etc. */ + while (BIO_read(p7bio, buf, sizeof(buf)) > 0) + continue; + + PKCS7_SIGNER_INFO* si = sk_PKCS7_SIGNER_INFO_value(PKCS7_get_signer_info(p7), 0); + + isValid = PKCS7_signatureVerify(p7bio, p7, si, signCert) == 1; + + BIO_free_all(p7bio); + + if (!isValid) + result->verify_flags = COUNTERSIGNATURE_VFY_INVALID; + +end: + sk_X509_free(sigs); + PKCS7_free(p7); + TS_TST_INFO_free(ts); + return result; +} + +int countersignature_array_insert(CountersignatureArray* arr, Countersignature* sig) +{ + Countersignature** tmp = + (Countersignature**)realloc(arr->counters, (arr->count + 1) * sizeof(Countersignature*)); + if (!tmp) + return 1; + + arr->counters = tmp; + arr->counters[arr->count] = sig; + arr->count++; + + return 0; +} + +int countersignature_array_move(CountersignatureArray* dst, CountersignatureArray* src) +{ + size_t newCount = dst->count + src->count; + + Countersignature** tmp = + (Countersignature**)realloc(dst->counters, newCount * sizeof(Countersignature*)); + if (!tmp) + return 1; + + dst->counters = tmp; + + for (size_t i = 0; i < src->count; ++i) + dst->counters[i + dst->count] = src->counters[i]; + + dst->count = newCount; + + free(src->counters); + src->counters = NULL; + src->count = 0; + + return 0; +} + +void countersignature_free(Countersignature* sig) +{ + if (sig) { + free(sig->digest_alg); + free(sig->digest.data); + certificate_array_free(sig->chain); + free(sig); + } +} + +void countersignature_array_free(CountersignatureArray* arr) +{ + if (arr) { + for (size_t i = 0; i < arr->count; ++i) { + countersignature_free(arr->counters[i]); + } + free(arr->counters); + free(arr); + } +} diff --git a/deps/authenticode-parser/src/countersignature.h b/deps/authenticode-parser/src/countersignature.h new file mode 100644 index 000000000..294ffed73 --- /dev/null +++ b/deps/authenticode-parser/src/countersignature.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef AUTHENTICODE_PARSER_COUNTERSIGNATURE_H +#define AUTHENTICODE_PARSER_COUNTERSIGNATURE_H + +#include "certificate.h" +#include "helper.h" +#include +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +Countersignature* pkcs9_countersig_new( + const uint8_t* data, long size, STACK_OF(X509) * certs, ASN1_STRING* enc_digest); +Countersignature* ms_countersig_new(const uint8_t* data, long size, ASN1_STRING* enc_digest); + +int countersignature_array_insert(CountersignatureArray* arr, Countersignature* sig); +/* Moves all countersignatures of src and inserts them into dst */ +int countersignature_array_move(CountersignatureArray* dst, CountersignatureArray* src); + +void countersignature_free(Countersignature* sig); +void countersignature_array_free(CountersignatureArray* arr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/deps/authenticode-parser/src/helper.c b/deps/authenticode-parser/src/helper.c new file mode 100644 index 000000000..be846b827 --- /dev/null +++ b/deps/authenticode-parser/src/helper.c @@ -0,0 +1,78 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "helper.h" + +#include +#include +#include +#include +#include +#include + +uint16_t bswap16(uint16_t d) +{ + return (d << 8) | (d >> 8); +} + +uint32_t bswap32(uint32_t d) +{ + return (((d)&0xff000000) >> 24) | (((d)&0x00ff0000) >> 8) | (((d)&0x0000ff00) << 8) | + (((d)&0x000000ff) << 24); +} + +int calculate_digest(const EVP_MD* md, const uint8_t* data, size_t len, uint8_t* digest) +{ + unsigned int outLen = 0; + + EVP_MD_CTX* mdCtx = EVP_MD_CTX_new(); + if (!mdCtx) + goto end; + + if (!EVP_DigestInit_ex(mdCtx, md, NULL) || !EVP_DigestUpdate(mdCtx, data, len) || + !EVP_DigestFinal_ex(mdCtx, digest, &outLen)) + goto end; + +end: + EVP_MD_CTX_free(mdCtx); + return (int)outLen; +} + +int byte_array_init(ByteArray* arr, const uint8_t* data, int len) +{ + arr->data = (uint8_t*)malloc(len); + if (!arr->data) + return -1; + + arr->len = len; + memcpy(arr->data, data, len); + return 0; +} + +time_t ASN1_TIME_to_time_t(const ASN1_TIME* time) +{ + struct tm t = {0}; + if (!time) + return timegm(&t); + + ASN1_TIME_to_tm(time, &t); + return timegm(&t); +} diff --git a/deps/authenticode-parser/src/helper.h b/deps/authenticode-parser/src/helper.h new file mode 100644 index 000000000..e435cbc51 --- /dev/null +++ b/deps/authenticode-parser/src/helper.h @@ -0,0 +1,68 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef AUTHENTICODE_PARSER_HELPER_H +#define AUTHENTICODE_PARSER_HELPER_H + +#include +#include +#include +#include + +#include + +#ifdef _WIN32 +#define timegm _mkgmtime +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Endianity related functions for PE reading */ +uint16_t bswap16(uint16_t d); +uint32_t bswap32(uint32_t d); + +#if defined(WORDS_BIGENDIAN) +#define letoh16(x) bswap16(x) +#define letoh32(x) bswap32(x) +#define betoh16(x) (x) +#define betoh32(x) (x) +#else +#define letoh16(x) (x) +#define letoh32(x) (x) +#define betoh16(x) bswap16(x) +#define betoh32(x) bswap32(x) +#endif + +/* Calculates digest md of data, return bytes written to digest or 0 on error + * Maximum of EVP_MAX_MD_SIZE will be written to digest */ +int calculate_digest(const EVP_MD* md, const uint8_t* data, size_t len, uint8_t* digest); +/* Copies data of length len into already existing arr */ +int byte_array_init(ByteArray* arr, const uint8_t* data, int len); +/* Converts ASN1_TIME string time into a unix timestamp */ +time_t ASN1_TIME_to_time_t(const ASN1_TIME* time); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/fileformat/file_format/pe/authenticode/authenticode_structs.cpp b/deps/authenticode-parser/src/structs.c similarity index 66% rename from src/fileformat/file_format/pe/authenticode/authenticode_structs.cpp rename to deps/authenticode-parser/src/structs.c index 5acb6975d..4eee46b4d 100644 --- a/src/fileformat/file_format/pe/authenticode/authenticode_structs.cpp +++ b/deps/authenticode-parser/src/structs.c @@ -1,20 +1,26 @@ -/** - * @file src/fileformat/file_format/pe/authenticode/authenticode_structs.cpp - * @brief Defines custom OpenSSL objects and functions - * @copyright (c) 2021 Avast Software, licensed under the MIT license - * @author Marek Milkovič - metthal - */ +/* Copyright (c) 2021 Avast Software -/* Author #Metthal */ +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: -#include "authenticode_structs.h" +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -/* - These are types from "Windows Authenticode Portable Executable Signature Format" - https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/Authenticode_PE.docx - Some of them are changed a little bit because the documentation did not reflect the reality +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ +#include "structs.h" + ASN1_CHOICE(SpcString) = { ASN1_IMP_OPT(SpcString, value.unicode, ASN1_BMPSTRING, 0), ASN1_IMP_OPT(SpcString, value.ascii, ASN1_IA5STRING, 1) diff --git a/deps/authenticode-parser/src/structs.h b/deps/authenticode-parser/src/structs.h new file mode 100644 index 000000000..1f90db69b --- /dev/null +++ b/deps/authenticode-parser/src/structs.h @@ -0,0 +1,111 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef AUTHENTICODE_PARSER_STRUCTS_H +#define AUTHENTICODE_PARSER_STRUCTS_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define NID_spc_info "1.3.6.1.4.1.311.2.1.12" +#define NID_spc_ms_countersignature "1.3.6.1.4.1.311.3.3.1" +#define NID_spc_nested_signature "1.3.6.1.4.1.311.2.4.1" +#define NID_spc_indirect_data "1.3.6.1.4.1.311.2.1.4" + +typedef struct { + int type; + union { + ASN1_BMPSTRING *unicode; + ASN1_IA5STRING *ascii; + } value; +} SpcString; + +typedef struct { + ASN1_OCTET_STRING *classId; + ASN1_OCTET_STRING *serializedData; +} SpcSerializedObject; + +typedef struct { + int type; + union { + ASN1_IA5STRING *url; + SpcSerializedObject *moniker; + SpcString *file; + } value; +} SpcLink; + +typedef struct { + ASN1_OBJECT *type; + ASN1_TYPE *value; +} SpcAttributeTypeAndOptionalValue; + +typedef struct { + ASN1_BIT_STRING *flags; + SpcLink *file; +} SpcPeImageData; + +typedef struct { + ASN1_OBJECT *algorithm; + ASN1_TYPE *parameters; +} AlgorithmIdentifier; + +typedef struct { + AlgorithmIdentifier *digestAlgorithm; + ASN1_OCTET_STRING *digest; +} DigestInfo; + +typedef struct { + SpcAttributeTypeAndOptionalValue *data; + DigestInfo *messageDigest; +} SpcIndirectDataContent; + +typedef struct { + ASN1_OBJECT *contentType; + SpcIndirectDataContent *content; +} SpcContentInfo; + +typedef struct { + SpcString *programName; + SpcLink *moreInfo; +} SpcSpOpusInfo; + +DECLARE_ASN1_FUNCTIONS(SpcString) +DECLARE_ASN1_FUNCTIONS(SpcSerializedObject) +DECLARE_ASN1_FUNCTIONS(SpcLink) +DECLARE_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue) +DECLARE_ASN1_FUNCTIONS(SpcPeImageData) +DECLARE_ASN1_FUNCTIONS(AlgorithmIdentifier) +DECLARE_ASN1_FUNCTIONS(DigestInfo) +DECLARE_ASN1_FUNCTIONS(SpcIndirectDataContent) +DECLARE_ASN1_FUNCTIONS(SpcSpOpusInfo) +DECLARE_ASN1_FUNCTIONS(SpcContentInfo) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/fileformat/CMakeLists.txt b/src/fileformat/CMakeLists.txt index 0d192f82b..cac1b4c93 100644 --- a/src/fileformat/CMakeLists.txt +++ b/src/fileformat/CMakeLists.txt @@ -71,13 +71,6 @@ add_library(fileformat STATIC types/tls_info/tls_info.cpp file_format/pe/pe_format.cpp file_format/pe/pe_dll_list.cpp - file_format/pe/authenticode/authenticode.cpp - file_format/pe/authenticode/pkcs9_counter_signature.cpp - file_format/pe/authenticode/ms_counter_signature.cpp - file_format/pe/authenticode/pkcs7_signature.cpp - file_format/pe/authenticode/x509_certificate.cpp - file_format/pe/authenticode/helper.cpp - file_format/pe/authenticode/authenticode_structs.cpp file_format/coff/coff_format.cpp file_format/intel_hex/intel_hex_parser/intel_hex_tokenizer.cpp file_format/intel_hex/intel_hex_parser/intel_hex_parser.cpp @@ -106,6 +99,7 @@ target_link_libraries(fileformat retdec::deps::llvm PRIVATE retdec::deps::tlsh + retdec::deps::authenticode OpenSSL::Crypto ) diff --git a/src/fileformat/file_format/pe/authenticode/authenticode.cpp b/src/fileformat/file_format/pe/authenticode/authenticode.cpp deleted file mode 100644 index 5d64139e1..000000000 --- a/src/fileformat/file_format/pe/authenticode/authenticode.cpp +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @file src/fileformat/file_format/pe/authenticode/authenticode.cpp - * @brief Class that parses PE Authenticode data - * @copyright (c) 2021 Avast Software, licensed under the MIT license - */ - -#include "authenticode.h" - -namespace authenticode { -Authenticode::Authenticode(const std::vector& data) - : pkcs7(data) {} - -std::vector Authenticode::getSignatures(const retdec::fileformat::PeFormat* peFile) const -{ - return pkcs7.getSignatures(peFile); -} - -} // namespace authenticode diff --git a/src/fileformat/file_format/pe/authenticode/authenticode.h b/src/fileformat/file_format/pe/authenticode/authenticode.h deleted file mode 100644 index ba882284a..000000000 --- a/src/fileformat/file_format/pe/authenticode/authenticode.h +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @file src/fileformat/file_format/pe/authenticode/authenticode.h - * @brief Class that parses PE Authenticode data - * @copyright (c) 2021 Avast Software, licensed under the MIT license - */ - -#pragma once - -#include "retdec/fileformat/types/certificate_table/certificate_table.h" - -#include "authenticode_structs.h" -#include "pkcs7_signature.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -using retdec::fileformat::DigitalSignature; - -namespace authenticode { - -class Authenticode -{ -private: - Pkcs7Signature pkcs7; - -public: - Authenticode(const std::vector& data); - std::vector getSignatures(const retdec::fileformat::PeFormat* peFile) const; -}; - -} // namespace authenticode diff --git a/src/fileformat/file_format/pe/authenticode/authenticode_structs.h b/src/fileformat/file_format/pe/authenticode/authenticode_structs.h deleted file mode 100644 index 3fd8104a4..000000000 --- a/src/fileformat/file_format/pe/authenticode/authenticode_structs.h +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @file src/fileformat/file_format/pe/authenticode/authenticode_structs.h - * @brief Declares custom OpenSSL objects and functions - * @copyright (c) 2021 Avast Software, licensed under the MIT license - * @author Marek Milkovič - metthal - */ - -#pragma once - -#include -#include -#include -#include - -/* - These are types from "Windows Authenticode Portable Executable Signature Format" - https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/Authenticode_PE.docx - Some of them are changed a little bit because the documentation did not reflect the reality -*/ -struct SpcString -{ - int type; - union { - ASN1_BMPSTRING* unicode; - ASN1_IA5STRING* ascii; - } value; -}; - -struct SpcSerializedObject -{ - ASN1_OCTET_STRING* classId; - ASN1_OCTET_STRING* serializedData; -}; - -struct SpcLink -{ - int type; - union { - ASN1_IA5STRING* url; - SpcSerializedObject* moniker; - SpcString* file; - } value; -}; - -struct SpcAttributeTypeAndOptionalValue -{ - ASN1_OBJECT* type; - ASN1_TYPE* value; -}; - -struct SpcPeImageData -{ - ASN1_BIT_STRING* flags; - SpcLink* file; -}; - -struct AlgorithmIdentifier -{ - ASN1_OBJECT* algorithm; - ASN1_TYPE* parameters; -}; - -struct DigestInfo -{ - AlgorithmIdentifier* digestAlgorithm; - ASN1_OCTET_STRING* digest; -}; - -struct SpcIndirectDataContent -{ - SpcAttributeTypeAndOptionalValue* data; - DigestInfo* messageDigest; -}; - -struct SpcContentInfo -{ - ASN1_OBJECT* contentType; - SpcIndirectDataContent* content; -}; -struct SpcSpOpusInfo -{ - SpcString* programName; - SpcLink* moreInfo; -}; - -DECLARE_ASN1_FUNCTIONS(SpcString) -DECLARE_ASN1_FUNCTIONS(SpcSerializedObject) -DECLARE_ASN1_FUNCTIONS(SpcLink) -DECLARE_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue) -DECLARE_ASN1_FUNCTIONS(SpcPeImageData) -DECLARE_ASN1_FUNCTIONS(AlgorithmIdentifier) -DECLARE_ASN1_FUNCTIONS(DigestInfo) -DECLARE_ASN1_FUNCTIONS(SpcIndirectDataContent) -DECLARE_ASN1_FUNCTIONS(SpcSpOpusInfo) -DECLARE_ASN1_FUNCTIONS(SpcContentInfo) diff --git a/src/fileformat/file_format/pe/authenticode/helper.cpp b/src/fileformat/file_format/pe/authenticode/helper.cpp deleted file mode 100644 index ecf3e0688..000000000 --- a/src/fileformat/file_format/pe/authenticode/helper.cpp +++ /dev/null @@ -1,113 +0,0 @@ -/** - * @file src/fileformat/file_format/pe/authenticode/helper.cpp - * @brief Helper functions used for Authenticode components - * @copyright (c) 2021 Avast Software, licensed under the MIT license - */ - -#include "helper.h" -#include -namespace authenticode { - -std::string parsePublicKey(BIO* bio) -{ - std::string key; - std::vector tmp(100); - - BIO_gets(bio, tmp.data(), 100); - if (std::string(tmp.data()) != "-----BEGIN PUBLIC KEY-----\n") { - return key; - } - - while (true) { - BIO_gets(bio, tmp.data(), 100); - if (std::string(tmp.data()) == "-----END PUBLIC KEY-----\n") { - break; - } - - key += tmp.data(); - key.erase(key.length() - 1, 1); // Remove last character (whitespace) - } - - return key; -} - -/* Calculates md digest type from data, result is a written into - digest that has to be large enough to accomodate whole digest */ -void calculateDigest(const EVP_MD* md, const std::uint8_t* data, int len, std::uint8_t* digest) -{ - EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); - EVP_DigestInit_ex(mdctx, md, NULL); - EVP_DigestUpdate(mdctx, data, len); - EVP_DigestFinal_ex(mdctx, digest, NULL); - EVP_MD_CTX_free(mdctx); -} - -std::string bytesToHexString(const std::uint8_t* in, int len) -{ - const std::uint8_t* end = in + len; - std::ostringstream oss; - for (; in != end; ++in) - oss << std::hex << std::setw(2) << std::setfill('0') << std::uppercase << static_cast(*in); - return oss.str(); -} - -std::string parseDateTime(const ASN1_TIME* dateTime) -{ - if (ASN1_TIME_check(dateTime) == 0) - return {}; - - BIO* memBio = BIO_new(BIO_s_mem()); - ASN1_TIME_print(memBio, dateTime); - - BUF_MEM* bioMemPtr; - BIO_ctrl(memBio, BIO_C_GET_BUF_MEM_PTR, 0, reinterpret_cast(&bioMemPtr)); - - std::string result(bioMemPtr->data, bioMemPtr->length); - BIO_free_all(memBio); - return result; -} -std::string serialToString(ASN1_INTEGER* serial) -{ - BIGNUM* bignum = ASN1_INTEGER_to_BN(serial, nullptr); - - BIO* bio = BIO_new(BIO_s_mem()); - BN_print(bio, bignum); - auto data_len = BIO_number_written(bio); - - std::vector result(data_len); - BIO_read(bio, static_cast(result.data()), data_len); - - BIO_free_all(bio); - BN_free(bignum); - return { result.begin(), result.end() }; -} - -std::string X509NameToString(X509_NAME* name) -{ - BIO* bio = BIO_new(BIO_s_mem()); - X509_NAME_print_ex(bio, name, 0, XN_FLAG_RFC2253); - auto str_size = BIO_number_written(bio); - - std::string result(str_size, '\0'); - BIO_read(bio, (void*)result.data(), result.size()); - BIO_free_all(bio); - return result; -} - -/* If PKCS7 cannot be created it throws otherwise returns valid pointer */ -PKCS7* getPkcs7(const std::vector& input) -{ - BIO* bio = BIO_new(BIO_s_mem()); - if (!bio || BIO_reset(bio) != 1 || - BIO_write(bio, input.data(), static_cast(input.size())) != static_cast(input.size())) { - BIO_free(bio); - return nullptr; - } - - PKCS7* pkcs7 = d2i_PKCS7_bio(bio, nullptr); - - BIO_free(bio); - return pkcs7; -} - -} // namespace authenticode diff --git a/src/fileformat/file_format/pe/authenticode/helper.h b/src/fileformat/file_format/pe/authenticode/helper.h deleted file mode 100644 index 397b37397..000000000 --- a/src/fileformat/file_format/pe/authenticode/helper.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @file src/fileformat/file_format/pe/authenticode/helper.h - * @brief Helper functions used for Authenticode components - * @copyright (c) 2021 Avast Software, licensed under the MIT license - */ -#pragma once - -#include "authenticode_structs.h" -#include "x509_certificate.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace authenticode { - -std::string bytesToHexString(const std::uint8_t* in, int len); -std::string parsePublicKey(BIO* bio); -std::string serialToString(ASN1_INTEGER* serial); -std::string X509NameToString(X509_NAME* name); -std::string parseDateTime(const ASN1_TIME* dateTime); -PKCS7* getPkcs7(const std::vector& input); -void calculateDigest(const EVP_MD* md, const std::uint8_t* data, int len, std::uint8_t* digest); - -} // namespace authenticode diff --git a/src/fileformat/file_format/pe/authenticode/ms_counter_signature.cpp b/src/fileformat/file_format/pe/authenticode/ms_counter_signature.cpp deleted file mode 100644 index 026880a35..000000000 --- a/src/fileformat/file_format/pe/authenticode/ms_counter_signature.cpp +++ /dev/null @@ -1,121 +0,0 @@ -/** - * @file src/fileformat/file_format/pe/authenticode/ms_counter_signature.cpp - * @brief Representation of MsCounterSignature - * @copyright (c) 2021 Avast Software, licensed under the MIT license - */ - -#include "ms_counter_signature.h" -#include "x509_certificate.h" -#include -#include -#include -#include -#include - -namespace authenticode { - -MsCounterSignature::MsCounterSignature(const std::vector& data) - : pkcs7(nullptr, PKCS7_free), tstInfo(nullptr, TS_TST_INFO_free), signers(nullptr, sk_X509_free) -{ - pkcs7.reset(getPkcs7(data)); - if (!pkcs7) { - return; - } - - tstInfo.reset(PKCS7_to_TS_TST_INFO(pkcs7.get())); - if (!tstInfo) { - return; - } - - const ASN1_TIME* raw_time = TS_TST_INFO_get_time(tstInfo.get()); - - if (raw_time) { - signTime = parseDateTime(raw_time); - } - signers.reset(PKCS7_get0_signers(pkcs7.get(), pkcs7->d.sign->cert, 0)); - auto signerCount = sk_X509_num(signers.get()); - if (signerCount != 1) { - return; - } - certs = pkcs7->d.sign->cert; - signCert = sk_X509_value(signers.get(), 0); - imprint = TS_TST_INFO_get_msg_imprint(tstInfo.get()); - - X509_ALGOR* digest_algo = TS_MSG_IMPRINT_get_algo(imprint); - digestAlgorithm = OBJ_obj2nid(digest_algo->algorithm); - - ASN1_STRING* raw_digest = TS_MSG_IMPRINT_get_msg(imprint); - messageDigest = std::vector(raw_digest->data, raw_digest->data + raw_digest->length); -} - -std::vector MsCounterSignature::verify(const std::vector& sig_enc_content) const -{ - std::vector warnings; - - if (!pkcs7) { - warnings.emplace_back("Couldn't parse signature"); - return warnings; - } - - if (messageDigest.empty()) { - warnings.emplace_back("Failed to verify the counter-signature, no message digest"); - return warnings; - } - - const EVP_MD* md = EVP_get_digestbynid(digestAlgorithm); - if (!md) { - warnings.emplace_back("Unknown digest algorithm"); - return warnings; - } - std::uint8_t digest[EVP_MAX_MD_SIZE] = { 0 }; - calculateDigest(md, sig_enc_content.data(), sig_enc_content.size(), digest); - - int md_len = EVP_MD_size(md); - if (std::memcmp(digest, messageDigest.data(), md_len)) { - warnings.emplace_back("Failed to verify the signature with counter-signature"); - } - - TS_VERIFY_CTX* ctx = TS_VERIFY_CTX_new(); - X509_STORE* store = X509_STORE_new(); - TS_VERIFY_CTX_init(ctx); - - TS_VERIFY_CTX_set_flags(ctx, TS_VFY_VERSION | TS_VFY_IMPRINT); - TS_VERIFY_CTX_set_store(ctx, store); - TS_VERIFY_CTS_set_certs(ctx, pkcs7->d.sign->cert); - TS_VERIFY_CTX_set_imprint(ctx, digest, md_len); - - bool is_valid = TS_RESP_verify_token(ctx, pkcs7.get()) == 1; - - /* VERIFY_CTX_free tries to free these, we don't want that */ - TS_VERIFY_CTX_set_imprint(ctx, nullptr, 0); - TS_VERIFY_CTS_set_certs(ctx, nullptr); - - TS_VERIFY_CTX_free(ctx); - - if (!is_valid) { - warnings.emplace_back("Failed to verify the counter-signature"); - } - - /* Verify signature with PKCS7_signatureVerify - because TS_RESP_verify_token tries to verify - chain and without trust anchors it fails */ - BIO* p7bio = PKCS7_dataInit(pkcs7.get(), NULL); - - char buf[4096]; - /* We now have to 'read' from p7bio to calculate digests etc. */ - while (BIO_read(p7bio, buf, sizeof(buf)) > 0) - continue; - - STACK_OF(PKCS7_SIGNER_INFO)* sinfos = PKCS7_get_signer_info(pkcs7.get()); - PKCS7_SIGNER_INFO* si = sk_PKCS7_SIGNER_INFO_value(sinfos, 0); - - is_valid = PKCS7_signatureVerify(p7bio, pkcs7.get(), si, const_cast(signCert)) == 1; - if (!is_valid) { - warnings.emplace_back("Failed to verify the counter-signature"); - } - - BIO_free_all(p7bio); - return warnings; -} - -} // namespace authenticode diff --git a/src/fileformat/file_format/pe/authenticode/ms_counter_signature.h b/src/fileformat/file_format/pe/authenticode/ms_counter_signature.h deleted file mode 100644 index b97ca8778..000000000 --- a/src/fileformat/file_format/pe/authenticode/ms_counter_signature.h +++ /dev/null @@ -1,43 +0,0 @@ -/** - * @file src/fileformat/file_format/pe/authenticode/ms_counter_signature.h - * @brief Representation of MsCounterSignature - * @copyright (c) 2021 Avast Software, licensed under the MIT license - */ - -#pragma once - -#include "helper.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace authenticode { - -class MsCounterSignature -{ - std::unique_ptr pkcs7; - std::unique_ptr tstInfo; - std::unique_ptr signers; - TS_MSG_IMPRINT* imprint = nullptr; - -public: - const X509* signCert = nullptr; - const STACK_OF(X509)* certs = nullptr; - - std::string signTime; - std::vector messageDigest; - int digestAlgorithm = 0; - - std::vector verify(const std::vector& sig_enc_content) const; - MsCounterSignature(const std::vector& data); -}; - -} // namespace authenticode diff --git a/src/fileformat/file_format/pe/authenticode/pkcs7_signature.cpp b/src/fileformat/file_format/pe/authenticode/pkcs7_signature.cpp deleted file mode 100644 index 791dc7743..000000000 --- a/src/fileformat/file_format/pe/authenticode/pkcs7_signature.cpp +++ /dev/null @@ -1,618 +0,0 @@ -/** - * @file src/fileformat/file_format/pe/authenticode/pkcs7_signature.cpp - * @brief Class wrapper above openssl Pkcs7 - * @copyright (c) 2021 Avast Software, licensed under the MIT license - */ - -#include "pkcs7_signature.h" -#include "helper.h" -#include "x509_certificate.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace retdec::fileformat; - -static const int NID_spc_nested_signature = - OBJ_create("1.3.6.1.4.1.311.2.4.1", "spcNestedSignature", "SPC_NESTED_SIGNATURE (Authenticode)"); -static const int NID_spc_ms_countersignature = - OBJ_create("1.3.6.1.4.1.311.3.3.1", "spcMsCountersignature", "SPC_MICROSOFT_COUNTERSIGNATURE (Authenticode)"); -static const int NID_spc_indirect_data = - OBJ_create("1.3.6.1.4.1.311.2.1.4", "spcIndirectData", "SPC_INDIRECT_DATA (Authenticode)"); -static const int NID_spc_sp_opus_info_objid = - OBJ_create("1.3.6.1.4.1.311.2.1.12)", "SPC_SP_OPUS_INFO_OBJID", "SPC_SP_OPUS_INFO_OBJID (Authenticode)"); - -namespace authenticode { - -/* naming is hard */ -static std::vector convertToFileformatCertChain(std::vector chain) -{ - std::vector fileformat_chain; - for (auto&& cert : chain) { - fileformat_chain.push_back(cert.createCertificate()); - } - return fileformat_chain; -} - -Pkcs7Signature::ContentInfo::ContentInfo(const PKCS7* contents) -{ - /* - ContentInfo ::= SEQUENCE { - contentType ContentType, - content - [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL } - ContentType ::= OBJECT IDENTIFIER - // contentType must be set to SPC_INDIRECT_DATA_OBJID (1.3.6.1.4.1.311.2.1.4) - SpcIndirectDataContent ::= SEQUENCE { - data SpcAttributeTypeAndOptionalValue, - messageDigest DigestInfo - } --#public— - SpcAttributeTypeAndOptionalValue ::= SEQUENCE { - type ObjectID, - value [0] EXPLICIT ANY OPTIONAL - } - DigestInfo ::= SEQUENCE { - digestAlgorithm AlgorithmIdentifier, - digest OCTETSTRING - } - AlgorithmIdentifier ::= SEQUENCE { - algorithm ObjectID, - parameters [0] EXPLICIT ANY OPTIONAL - } - */ - if (!contents) { - return; - } - - contentType = OBJ_obj2nid(contents->type); - - if (contentType != NID_spc_indirect_data) { - return; - } - - size_t len = contents->d.other->value.sequence->length; - const unsigned char* data = contents->d.other->value.sequence->data; - - auto* spcContent = SpcIndirectDataContent_new(); - if (!spcContent) { - return; - } - - d2i_SpcIndirectDataContent(&spcContent, &data, len); - if (!spcContent) { - return; - } - - digest = bytesToHexString(spcContent->messageDigest->digest->data, spcContent->messageDigest->digest->length); - - digestAlgorithm = OBJ_obj2nid(spcContent->messageDigest->digestAlgorithm->algorithm); - - SpcIndirectDataContent_free(spcContent); -} - -/** - * @brief Parses out bytes into a PKCS7 and other objects that are stored inside (countersignatures etc.) - * - * @param input - */ -Pkcs7Signature::Pkcs7Signature(const std::vector& input) noexcept - : pkcs7(nullptr, PKCS7_free) -{ - /* - SignedData ::= SEQUENCE { - version Version, - digestAlgorithms DigestAlgorithmIdentifiers, - contentInfo ContentInfo, - certificates - [0] IMPLICIT ExtendedCertificatesAndCertificates - OPTIONAL, - Crls - [1] IMPLICIT CertificateRevocationLists OPTIONAL, (Not used in AC) - signerInfos SignerInfos } - - DigestAlgorithmIdentifiers ::= (1 structure for each signer) - SET OF DigestAlgorithmIdentifier - - ContentInfo ::= SEQUENCE { - contentType ContentType, - content (Must be SpcIndirectDataContent) - [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL } - - ContentType ::= OBJECT IDENTIFIER - SignerInfos ::= SET OF SignerInfo (Only one signer is supported) - - Source for the parsing constraints is in the MS Authenticode spec - https://www.symbolcrash.com/wp-content/uploads/2019/02/Authenticode_PE-1.pdf - */ - pkcs7.reset(getPkcs7(input)); - - if (!pkcs7) { - return; - } - - /* Authenticode uses SignedData Pkcs7 type, check if that complies */ - if (!PKCS7_type_is_signed(pkcs7)) { - return; - } - - STACK_OF(X509_ALGOR)* algos = pkcs7->d.sign->md_algs; - /* Must be exactly 1 signer and for each signer there is one algorithm */ - int alg_count = sk_X509_ALGOR_num(algos); - for (int i = 0; i < alg_count; i++) { - contentDigestAlgorithms.emplace_back(OBJ_obj2nid(sk_X509_ALGOR_value(algos, i)->algorithm)); - } - - /* Parse the content info */ - contentInfo.emplace(pkcs7->d.sign->contents); - - ASN1_INTEGER_get_uint64(&version, pkcs7->d.sign->version); - - /* Parse the certificate data into internal structures */ - const STACK_OF(X509)* certs = pkcs7->d.sign->cert; - - int cert_count = sk_X509_num(certs); - for (size_t i = 0; i < cert_count; i++) { - X509Certificate cert(sk_X509_value(certs, i)); - certificates.push_back(cert); - } - - STACK_OF(PKCS7_SIGNER_INFO)* signer_infos = PKCS7_get_signer_info(pkcs7.get()); - if (signer_infos && sk_PKCS7_SIGNER_INFO_num(signer_infos) > 0) { - signerInfo.emplace(pkcs7.get(), sk_PKCS7_SIGNER_INFO_value(signer_infos, 0), certs); - } -} - -Pkcs7Signature::SignerInfo::SignerInfo(const PKCS7* pkcs7, const PKCS7_SIGNER_INFO* si_info, const STACK_OF(X509)* raw_certs) - : raw_signers(nullptr, sk_X509_free), sinfo(si_info) -{ - /* - SignerInfo ::= SEQUENCE { - version Version, - issuerAndSerialNumber IssuerAndSerialNumber, - digestAlgorithm DigestAlgorithmIdentifier, - authenticatedAttributes - [0] IMPLICIT Attributes OPTIONAL, - digestEncryptionAlgorithm - DigestEncryptionAlgorithmIdentifier, - encryptedDigest EncryptedDigest, - unauthenticatedAttributes - [1] IMPLICIT Attributes OPTIONAL } - IssuerAndSerialNumber ::= SEQUENCE { - issuer Name, - serialNumber CertificateSerialNumber } - EncryptedDigest ::= OCTET STRING - */ - /* - "Because Authenticode signatures support only one signer," - https://www.symbolcrash.com/wp-content/uploads/2019/02/Authenticode_PE-1.pdf page 7 - */ - - X509_ALGOR* digestAlgo = si_info->digest_alg; - X509_ALGOR* digestEncryptAlgo = si_info->digest_enc_alg; - - digestAlgorithm = OBJ_obj2nid(digestAlgo->algorithm); - digestEncryptAlgorithm = OBJ_obj2nid(digestEncryptAlgo->algorithm); - - encryptDigest = std::vector(si_info->enc_digest->data, - si_info->enc_digest->data + si_info->enc_digest->length); - - ASN1_INTEGER_get_uint64(&version, si_info->version); - - serial = serialToString(si_info->issuer_and_serial->serial); - issuer = X509NameToString(si_info->issuer_and_serial->issuer); - - parseUnauthAttrs(si_info, raw_certs); - parseAuthAttrs(si_info); - - /* Get the signer certificate */ - raw_signers.reset(PKCS7_get0_signers(const_cast(pkcs7), const_cast(raw_certs), 0)); - - if (!raw_signers) { - return; - } - - int signers_count = sk_X509_num(raw_signers.get()); - /* This by logic shouldn't happen as above we established there is single SignerInfo, - but I am not completely sure so I'll keep it here for a while */ - if (signers_count != 1) { - return; - } - - signerCert = sk_X509_value(raw_signers.get(), 0); - if (!signerCert) { - return; - } -} -void Pkcs7Signature::SignerInfo::parseAuthAttrs(const PKCS7_SIGNER_INFO* si_info) -{ - for (int j = 0; j < sk_X509_ATTRIBUTE_num(si_info->auth_attr); ++j) { - X509_ATTRIBUTE* attr = sk_X509_ATTRIBUTE_value(si_info->auth_attr, j); - ASN1_TYPE* attr_type = X509_ATTRIBUTE_get0_type(attr, 0); - ASN1_OBJECT* attr_object = X509_ATTRIBUTE_get0_object(attr); - - if (!attr_object || !attr_type) { - continue; - } - - auto attr_object_nid = OBJ_obj2nid(attr_object); - char buf[100]; /* 100 should be more than enough for any oid - openssl docs */ - if (attr_object_nid == NID_pkcs9_contentType) { - /* - ContentType ::= OBJECT IDENTIFIER - */ - OBJ_obj2txt(buf, 100, attr_type->value.object, 0); - contentType = std::string(buf, buf + strlen(buf)); - } - else if (attr_object_nid == NID_pkcs9_messageDigest) { - /* - MessageDigest ::= OCTET STRING - */ - messageDigest = bytesToHexString(attr_type->value.asn1_string->data, attr_type->value.asn1_string->length); - } - else if (attr_object_nid == NID_spc_sp_opus_info_objid) { - /* - SpcSpOpusInfo ::= SEQUENCE { - programName [0] EXPLICIT SpcString OPTIONAL, - moreInfo [1] EXPLICIT SpcLink OPTIONAL, - } --#public-- - */ - spcInfo = SpcSpOpusInfo((const unsigned char*)attr_type->value.sequence->data, attr_type->value.sequence->length); - } - } -} - -void Pkcs7Signature::SignerInfo::parseUnauthAttrs(const PKCS7_SIGNER_INFO* si_info, const STACK_OF(X509)* raw_certs) -{ - for (int j = 0; j < sk_X509_ATTRIBUTE_num(si_info->unauth_attr); ++j) { - X509_ATTRIBUTE* attr = sk_X509_ATTRIBUTE_value(si_info->unauth_attr, j); - ASN1_TYPE* attr_type = X509_ATTRIBUTE_get0_type(attr, 0); - ASN1_OBJECT* attr_object = X509_ATTRIBUTE_get0_object(attr); - if (!attr_object) { - continue; - } - auto attr_object_nid = OBJ_obj2nid(attr_object); - - if (attr_object_nid == NID_spc_nested_signature) { - std::vector nested_sig_data(attr_type->value.sequence->data, - attr_type->value.sequence->data + attr_type->value.sequence->length); - - nestedSignatures.emplace_back(nested_sig_data); - } - else if (attr_object_nid == NID_pkcs9_countersignature) { - std::vector countersig_data(attr_type->value.sequence->data, - attr_type->value.sequence->data + attr_type->value.sequence->length); - - counterSignatures.emplace_back(countersig_data, raw_certs); - } - else if (attr_object_nid == NID_spc_ms_countersignature) { - std::vector countersig_data(attr_type->value.sequence->data, - attr_type->value.sequence->data + attr_type->value.sequence->length); - - msCounterSignatures.emplace_back(countersig_data); - } - } -} - -const PKCS7_SIGNER_INFO* Pkcs7Signature::SignerInfo::getSignerInfo() const -{ - return sinfo; -} - -Pkcs7Signature::SpcSpOpusInfo::SpcSpOpusInfo(const unsigned char* data, int len) noexcept -{ - /* - SpcSpOpusInfo ::= SEQUENCE { - programName [0] EXPLICIT SpcString OPTIONAL, - moreInfo [1] EXPLICIT SpcLink OPTIONAL, - } --#public-- - - SpcLink ::= CHOICE { - url [0] IMPLICIT IA5STRING, - moniker [1] IMPLICIT SpcSerializedObject, - file [2] EXPLICIT SpcString - } --#public-- - - SpcString ::= CHOICE { - unicode [0] IMPLICIT BMPSTRING, - ascii [1] IMPLICIT IA5STRING - } - */ - ::SpcSpOpusInfo* spcInfo = SpcSpOpusInfo_new(); - if (!spcInfo) { - return; - } - d2i_SpcSpOpusInfo(&spcInfo, &data, len); - if (!spcInfo) { - return; - } - - if (spcInfo->programName) { - /* name is ascii string or utf16 string */ - if (spcInfo->programName->type) { - programName = std::string(spcInfo->programName->value.ascii->data, spcInfo->programName->value.ascii->data + spcInfo->programName->value.ascii->length); - } - else { - unsigned char* data = nullptr; - int len = ASN1_STRING_to_UTF8(&data, spcInfo->programName->value.unicode); - if (len >= 0) { - programName = std::string(data, data + len); - OPENSSL_free(data); - } - } - } - SpcSpOpusInfo_free(spcInfo); -} - -const X509* Pkcs7Signature::SignerInfo::getSignerCert() const -{ - return signerCert; -} - -/* verifies if signature complies with specification rules, - for each broken rule, create a message in this->warnings */ -std::vector Pkcs7Signature::verify(const std::string& fileDigest) const -{ - /* Check if signature is correctly parsed and complies with the spec: - - [x] contentDigestAlgorithms contain single algorithm - - [x] SignedData and SignerInfo digestAlgorithm match - - [x] contentInfo contains PE hash, hashing algorithm and SpcIndirectDataOid - - [x] SignerInfo contains signer cert - - [x] Authenticated attributes contains all the necessary information: - - [x] ContentType with PKCS9 MessageDigest OID value - - [x] MessageDigest contains correct hash value of PKCS7 SignedData - - [x] SpcSpOpusInfo - - [x] Decrypted encryptedDigest math calculated hash of authenticated attributes - - [x] fileDigest match the signature digest - */ - std::vector warnings; - - /* Verification of the signature SignedData contents */ - if (!pkcs7) { // no sense to continue - warnings.emplace_back("Couldn't parse the Pkcs7 signature"); - return warnings; - } - - if (!PKCS7_type_is_signed(pkcs7)) { - warnings.emplace_back("Invalid PKCS#7 type, expected SignedData"); - } - - if (contentDigestAlgorithms.size() != 1) { - warnings.emplace_back("Invalid number of DigestAlgorithmIdentifiers: " + std::to_string(contentDigestAlgorithms.size()) + " - expected 1"); - } - - if (contentInfo) { - if (contentInfo->contentType != NID_spc_indirect_data) { - warnings.emplace_back("Wrong contentInfo contentType"); - } - else if (contentInfo->digest.empty()) { - warnings.emplace_back("Signature digest is missing"); - } - else { - if (fileDigest != contentInfo->digest) { - warnings.emplace_back("Signature digest doesn't match the file digest"); - } - } - } - else { - warnings.emplace_back("Couldn't get contentInfo"); - } - - if (signerInfo) { - if (!signerInfo->getSignerCert()) { - warnings.emplace_back("Signing cert is missing"); - } - if (contentDigestAlgorithms.size() > 0 && signerInfo->digestAlgorithm != contentDigestAlgorithms[0]) { - warnings.emplace_back("SignedData digest algorithm and signerInfo digest algorithm don't match"); - } - if (signerInfo->encryptDigest.empty()) { - warnings.emplace_back("Encrypted digest is empty"); - } - // verify auth attrs existence - if (!signerInfo->spcInfo) { - warnings.emplace_back("Couldn't get SpcSpOpusInfo"); - } - if (signerInfo->messageDigest.empty()) { - warnings.emplace_back("Couldn't get SignerInfo message digest"); - } - if (signerInfo->contentType.empty()) { - warnings.emplace_back("Missing correct SignerInfo contentType"); - } - if (!signerInfo->encryptDigest.empty() && signerInfo->getSignerCert() && contentInfo && pkcs7->d.sign->contents->d.data) { - /* Verify the signer hash and it's encryptedDigest */ - const auto* data_ptr = pkcs7->d.sign->contents->d.other->value.sequence->data; - long data_len = pkcs7->d.sign->contents->d.other->value.sequence->length; - if (version == 1) { - int pclass = 0, ptag = 0; - ASN1_get_object(&data_ptr, &data_len, &ptag, &pclass, data_len); - } - - BIO* content_bio = BIO_new_mem_buf(data_ptr, data_len); - BIO* p7bio = PKCS7_dataInit(pkcs7.get(), content_bio); - - char buf[4096]; - /* We now have to 'read' from p7bio to calculate digests etc. */ - while (BIO_read(p7bio, buf, sizeof(buf)) > 0) - continue; - - bool is_valid = PKCS7_signatureVerify(p7bio, pkcs7.get(), const_cast(signerInfo->getSignerInfo()), const_cast(signerInfo->getSignerCert())) == 1; - if (!is_valid) { - warnings.emplace_back("Signature isn't valid"); - } - - BIO_free_all(p7bio); - } - else { - warnings.emplace_back("Signature isn't valid"); - } - } - else { - warnings.emplace_back("Couldn't get SignerInfo"); - } - - return warnings; -} - -/** - * Calculates the digest using selected hash algorithm. - * @param peFile PE file with the signature. - * @return Hex string of hash. - * - * Function assumes the contentInfo is correctly parsed - */ -std::string Pkcs7Signature::calculateFileDigest(const retdec::fileformat::PeFormat* peFile) const -{ - if (!peFile) { - return {}; - } - - const EVP_MD* algorithm = EVP_get_digestbynid(contentInfo->digestAlgorithm); - if (!algorithm) { - return {}; - } - EVP_MD_CTX* ctx = EVP_MD_CTX_create(); - - if (EVP_DigestInit(ctx, algorithm) != 1) // 1 == success - { - return {}; - } - - auto digestRanges = peFile->getDigestRanges(); - for (const auto& range : digestRanges) { - const std::uint8_t* data = std::get<0>(range); - std::size_t size = std::get<1>(range); - - if (EVP_DigestUpdate(ctx, data, size) != 1) // 1 == success - { - return {}; - } - } - - std::vector hash(EVP_MD_size(algorithm)); - if (EVP_DigestFinal(ctx, hash.data(), nullptr) != 1) { - return {}; - } - EVP_MD_CTX_destroy(ctx); - - return bytesToHexString(hash.data(), EVP_MD_size(algorithm)); -} - -std::vector Pkcs7Signature::getSignatures(const retdec::fileformat::PeFormat* peFile) const -{ - std::vector signatures; - - CertificateProcessor processor; - - std::string fileDigest; - - DigitalSignature signature; - if (contentInfo.has_value()) { - signature.signedDigest = contentInfo->digest; - signature.digestAlgorithm = OBJ_nid2ln(contentInfo->digestAlgorithm); - fileDigest = calculateFileDigest(peFile); - } - - signature.fileDigest = fileDigest; - signature.warnings = verify(fileDigest); - signature.certificates = getAllCertificates(); - - /* No signer would mean, we have pretty much nothing */ - if (!signerInfo.has_value()) { - signatures.push_back(signature); - signature.isValid = false; - return signatures; - } - - if (signerInfo.has_value() && signerInfo->spcInfo.has_value()) { - signature.programName = signerInfo->spcInfo->programName; - } - - const SignerInfo& signInfo = signerInfo.value(); - STACK_OF(X509)* certs = pkcs7->d.sign->cert; - - const X509* signer_cert = signInfo.getSignerCert(); - Signer signer; - if (signer_cert) { - std::vector chain = processor.getChain(signer_cert, certs); - auto fileformat_chain = convertToFileformatCertChain(chain); - signer.chain = fileformat_chain; - } - signer.digest = signInfo.messageDigest; - signer.digestAlgorithm = OBJ_nid2ln(signInfo.digestAlgorithm); - - signature.signer = signer; - - // If there are warnings, then the signature is invalid - // We don't use the countersignature warnings here as - // previous implementation did when verifying - signature.isValid = signature.warnings.empty(); - - for (auto&& counterSig : signInfo.counterSignatures) { - CertificateProcessor processor; - auto certChain = processor.getChain(counterSig.signerCert, certs); - auto fileformatCertChain = convertToFileformatCertChain(certChain); - - Signer counterSigner; - counterSigner.chain = fileformatCertChain; - counterSigner.signingTime = counterSig.signTime; - counterSigner.digest = bytesToHexString(counterSig.messageDigest.data(), - counterSig.messageDigest.size()); - counterSigner.digestAlgorithm = OBJ_nid2ln(counterSig.digestAlgorithm); - counterSigner.warnings = counterSig.verify(signerInfo->encryptDigest); - counterSigner.counterSigners = std::vector(); - - signature.signer.counterSigners.push_back(counterSigner); - } - - for (auto&& counterSig : signInfo.msCounterSignatures) { - CertificateProcessor processor; - auto certChain = processor.getChain(counterSig.signCert, counterSig.certs); - auto fileformatCertChain = convertToFileformatCertChain(certChain); - - Signer counterSigner; - counterSigner.chain = fileformatCertChain; - counterSigner.signingTime = counterSig.signTime; - counterSigner.digest = bytesToHexString(counterSig.messageDigest.data(), - counterSig.messageDigest.size()); - counterSigner.digestAlgorithm = OBJ_nid2ln(counterSig.digestAlgorithm); - counterSigner.warnings = counterSig.verify(signerInfo->encryptDigest); - counterSigner.counterSigners = std::vector(); - - signature.signer.counterSigners.push_back(counterSigner); - } - - signatures.push_back(signature); - - for (auto&& nestedPkcs7 : signInfo.nestedSignatures) { - auto nestedSigs = nestedPkcs7.getSignatures(peFile); - signatures.insert(signatures.end(), nestedSigs.begin(), nestedSigs.end()); - } - - return signatures; -} - -/* Returns all certificate, including counter signature, excluding nested signatures */ -std::vector Pkcs7Signature::getAllCertificates() const -{ - std::vector all_x509certs = certificates; - - if (!signerInfo.has_value()) { - return convertToFileformatCertChain(all_x509certs); - } - - // ms counter signatures have their own collection, add them - for (auto&& counterSig : signerInfo->msCounterSignatures) { - int cert_count = sk_X509_num(counterSig.certs); - for (int i = 0; i < cert_count; i++) { - all_x509certs.emplace_back(sk_X509_value(counterSig.certs, i)); - } - } - - return convertToFileformatCertChain(all_x509certs); -} -} // namespace authenticode diff --git a/src/fileformat/file_format/pe/authenticode/pkcs7_signature.h b/src/fileformat/file_format/pe/authenticode/pkcs7_signature.h deleted file mode 100644 index 42b59b7b1..000000000 --- a/src/fileformat/file_format/pe/authenticode/pkcs7_signature.h +++ /dev/null @@ -1,124 +0,0 @@ -/** - * @file src/fileformat/file_format/pe/authenticode/pkcs7_signature.h - * @brief Class wrapper above openssl Pkcs7 - * @copyright (c) 2021 Avast Software, licensed under the MIT license - */ - -#pragma once - -#include "authenticode_structs.h" -#include "helper.h" -#include "retdec/fileformat/file_format/pe/pe_format.h" -#include "retdec/fileformat/types/certificate_table/certificate.h" -#include "retdec/fileformat/types/certificate_table/certificate_table.h" -#include "x509_certificate.h" -#include "pkcs9_counter_signature.h" -#include "ms_counter_signature.h" - -#include "retdec/fileformat/types/certificate_table/certificate_table.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace authenticode { - -class Pkcs7Signature -{ - struct SpcSpOpusInfo - { - SpcSpOpusInfo(const unsigned char* data, int len) noexcept; - std::string programName; /* utf-8 */ - }; - struct ContentInfo - { - int contentType = 0; - int digestAlgorithm = 0; - std::string digest; - - ContentInfo(const PKCS7* pkcs7); - }; - class SignerInfo - { - private: - void parseUnauthAttrs(const PKCS7_SIGNER_INFO* si_info, const STACK_OF(X509)* raw_certs); - void parseAuthAttrs(const PKCS7_SIGNER_INFO* si_info); - - std::unique_ptr raw_signers; - const X509* signerCert = nullptr; - const PKCS7_SIGNER_INFO* sinfo = nullptr; - - public: - std::uint64_t version = 0; - - std::string serial; - std::string issuer; - std::string contentType; - std::string messageDigest; - std::optional spcInfo; - - int digestAlgorithm = 0; /* Must be identical to SignedData::digestAlgorithm */ - int digestEncryptAlgorithm = 0; - - std::vector encryptDigest; - std::vector nestedSignatures; - std::vector counterSignatures; - std::vector msCounterSignatures; - - const X509* getSignerCert() const; - const PKCS7_SIGNER_INFO* getSignerInfo() const; - auto operator=(const SignerInfo&) = delete; - SignerInfo(const SignerInfo&) = delete; - - SignerInfo& operator=(SignerInfo&&) noexcept = default; - SignerInfo(SignerInfo&&) noexcept = default; - - SignerInfo(const PKCS7* pkcs7, const PKCS7_SIGNER_INFO* si_info, const STACK_OF(X509)* raw_certs); - }; - -private: - std::unique_ptr pkcs7; - - std::string calculateFileDigest(const retdec::fileformat::PeFormat* peFile) const; - std::vector getAllCertificates() const; - std::vector verify(const std::string& fileDigest) const; - -public: - std::uint64_t version = 0; - std::optional contentInfo; - std::optional signerInfo; - - std::vector contentDigestAlgorithms; - std::vector certificates; /* typically no root certificates, timestamp may include root one */ - - std::vector getSignatures(const retdec::fileformat::PeFormat* peFile) const; - - Pkcs7Signature& operator=(const Pkcs7Signature&) = delete; - Pkcs7Signature(const Pkcs7Signature&) = delete; - - Pkcs7Signature& operator=(Pkcs7Signature&&) noexcept = default; - Pkcs7Signature(Pkcs7Signature&&) noexcept = default; - - Pkcs7Signature(const std::vector& input) noexcept; -}; - -} // namespace authenticode diff --git a/src/fileformat/file_format/pe/authenticode/pkcs9_counter_signature.cpp b/src/fileformat/file_format/pe/authenticode/pkcs9_counter_signature.cpp deleted file mode 100644 index 87f368b72..000000000 --- a/src/fileformat/file_format/pe/authenticode/pkcs9_counter_signature.cpp +++ /dev/null @@ -1,158 +0,0 @@ -/** - * @file src/fileformat/file_format/pe/authenticode/pkcs9_counter_signature.cpp - * @brief Class that wraps openssl pkcs9 information. - * @copyright (c) 2020 Avast Software, licensed under the MIT license - */ - -#include "pkcs9_counter_signature.h" -#include "authenticode_structs.h" -#include -namespace authenticode { - -/* PKCS7 stores all certificates for the signer and counter signers, we need to pass the certs */ -Pkcs9CounterSignature::Pkcs9CounterSignature(std::vector& data, const STACK_OF(X509)* certificates) - : sinfo(nullptr, PKCS7_SIGNER_INFO_free) -{ - /* - counterSignature ATTRIBUTE ::= { - WITH SYNTAX SignerInfo - ID pkcs-9-at-counterSignature - } - */ - const unsigned char* data_ptr = data.data(); - sinfo.reset(d2i_PKCS7_SIGNER_INFO(nullptr, &data_ptr, data.size())); - if (!sinfo) { - return; - } - - digestAlgorithm = OBJ_obj2nid(sinfo->digest_alg->algorithm); - - /* get the signer certificate of this counter signatures */ - signerCert = X509_find_by_issuer_and_serial(const_cast(certificates), - sinfo->issuer_and_serial->issuer, sinfo->issuer_and_serial->serial); - - if (!signerCert) { - return; - } - - for (int i = 0; i < sk_X509_ATTRIBUTE_num(sinfo->auth_attr); ++i) { - X509_ATTRIBUTE* attribute = sk_X509_ATTRIBUTE_value(sinfo->auth_attr, i); - ASN1_OBJECT* attribute_object = X509_ATTRIBUTE_get0_object(attribute); - ASN1_TYPE* attr_type = X509_ATTRIBUTE_get0_type(attribute, 0); - /* - Note 2 - A countersignature, since it has type SignerInfo, can itself - contain a countersignature attribute. Thus it is possible to - construct arbitrarily long series of countersignatures. - */ - if (OBJ_obj2nid(attribute_object) == NID_pkcs9_countersignature) { - auto data = std::vector(attr_type->value.octet_string->data, - attr_type->value.octet_string->data + attr_type->value.octet_string->length); - counterSignatures.emplace_back(data, certificates); - } - else if (OBJ_obj2nid(attribute_object) == NID_pkcs9_contentType) { - contentType = OBJ_nid2sn(NID_pkcs9_contentType); - } - - /* Signing Time (1.2.840.113549.1.9.5) is set to the UTC time of timestamp generation time. */ - else if (OBJ_obj2nid(attribute_object) == NID_pkcs9_signingTime) { - signTime = parseDateTime(attr_type->value.utctime); - } - /* - Message Digest (1.2.840.113549.1.9.4) is set to the hash value of the SignerInfo structure's - encryptedDigest value. The hash algorithm that is used to calculate the hash value is the same - as that specified in the SignerInfo structure’s digestAlgorithm value of the timestamp. - - MessageDigest ::= OCTET STRING - */ - else if (OBJ_obj2nid(attribute_object) == NID_pkcs9_messageDigest) { - messageDigest = std::vector(attr_type->value.octet_string->data, - attr_type->value.octet_string->data + attr_type->value.octet_string->length); - } - } -} - -std::vector Pkcs9CounterSignature::verify(const std::vector& sig_enc_content) const -{ - std::vector warnings; - if (!sinfo) { - warnings.emplace_back("Couldn't parse counter-signature."); - return warnings; - } - - if (!signerCert) { - warnings.emplace_back("No counter-signature certificate"); - return warnings; - } - - if (contentType.empty()) { - warnings.emplace_back("Missing pkcs9 contentType"); - } - - std::uint8_t* data = nullptr; - auto len = ASN1_item_i2d((ASN1_VALUE*)sinfo->auth_attr, &data, ASN1_ITEM_rptr(PKCS7_ATTR_VERIFY)); - - const EVP_MD* md = EVP_get_digestbyobj(sinfo->digest_alg->algorithm); - if (!md) { - warnings.emplace_back("Unknown digest algorithm"); - return warnings; - } - std::uint8_t digest[EVP_MAX_MD_SIZE] = { 0 }; - calculateDigest(md, data, len, digest); - free(data); - - std::uint8_t* enc_data = sinfo->enc_digest->data; - int enc_len = sinfo->enc_digest->length; - - auto pkey = X509_get0_pubkey(signerCert); - auto ctx = EVP_PKEY_CTX_new(pkey, nullptr); - - std::size_t dec_len = 65536; - std::vector dec_data(dec_len); - - EVP_PKEY_verify_recover_init(ctx); - bool is_recovered = EVP_PKEY_verify_recover(ctx, dec_data.data(), &dec_len, enc_data, enc_len) == 1; - EVP_PKEY_CTX_free(ctx); - - if (is_recovered) { - int md_len = EVP_MD_size(md); - /* compare the encrypted digest and calculated digest */ - bool is_valid = false; - - /* Sometimes signed data contains DER encoded DigestInfo structure which - contains hash of authenticated attributes but other times it is just purely - hash and I don't think there's other way to distinguish it but only based on - the length of data we get */ - - if (md_len == dec_len) { - is_valid = !std::memcmp(digest, dec_data.data(), md_len); - } - else { - const std::uint8_t* data_ptr = dec_data.data(); - DigestInfo* digest_info = d2i_DigestInfo(nullptr, &data_ptr, dec_len); - is_valid = !std::memcmp(digest_info->digest->data, digest, md_len); - DigestInfo_free(digest_info); - } - if (!is_valid) { - warnings.emplace_back("Failed to verify the counter-signature"); - } - } else { - warnings.emplace_back("Couldn't decrypt the digest"); - } - - /* compare the saved, now verified digest attribute with the signature that it counter signs */ - if (messageDigest.empty()) { - warnings.emplace_back("Message digest is missing"); - } - else { - std::memset(digest, 0, EVP_MAX_MD_SIZE); - - calculateDigest(md, sig_enc_content.data(), sig_enc_content.size(), digest); - if (std::memcmp(digest, messageDigest.data(), messageDigest.size())) { - warnings.emplace_back("Failed to verify the signature with counter-signature"); - } - } - - return warnings; -} - -} // namespace authenticode diff --git a/src/fileformat/file_format/pe/authenticode/pkcs9_counter_signature.h b/src/fileformat/file_format/pe/authenticode/pkcs9_counter_signature.h deleted file mode 100644 index 7fe836def..000000000 --- a/src/fileformat/file_format/pe/authenticode/pkcs9_counter_signature.h +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @file src/fileformat/file_format/pe/authenticode/pkcs9_counter_signature.h - * @brief Class that wraps openssl pkcs9 information. - * @copyright (c) 2020 Avast Software, licensed under the MIT license - */ - -#pragma once - -#include "authenticode_structs.h" -#include "x509_certificate.h" -#include "helper.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace authenticode { - -class Pkcs9CounterSignature -{ -private: - std::unique_ptr sinfo; - -public: - const X509* signerCert = nullptr; - - std::string contentType; - std::string signTime; - std::vector messageDigest; - int digestAlgorithm = 0; - std::vector counterSignatures; - - std::vector verify(const std::vector& sig_enc_content) const; - Pkcs9CounterSignature(std::vector& data, const STACK_OF(X509)* certificates); -}; - -} // namespace authenticode diff --git a/src/fileformat/file_format/pe/authenticode/x509_certificate.cpp b/src/fileformat/file_format/pe/authenticode/x509_certificate.cpp deleted file mode 100644 index 981b8bdea..000000000 --- a/src/fileformat/file_format/pe/authenticode/x509_certificate.cpp +++ /dev/null @@ -1,307 +0,0 @@ -/** - * @file src/fileformat/file_format/pe/authenticode/x509_certificate.h - * @brief Class that wraps openssl x509 certificate information. - * @copyright (c) 2021 Avast Software, licensed under the MIT license - */ - -#include "x509_certificate.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace authenticode { - -X509Certificate::X509Certificate(const X509* cert) - : cert(cert) {} - -std::string X509Certificate::getSerialNumber() const -{ - // https://github.com/VirusTotal/yara/blob/879a6576dd6e544bf9fc7711821029bf842fac54/libyara/modules/pe/pe.c#L1316 - ASN1_INTEGER* serial_number_asn1 = X509_get_serialNumber(const_cast(cert)); - if (!serial_number_asn1) - return {}; - - // ASN1_INTEGER can be negative (serial->type & V_ASN1_NEG_INTEGER), - // in which case the serial number will be stored in 2's complement. - // - // Handle negative serial numbers, which are technically not allowed - // by RFC5280, but do exist. An example binary which has a negative - // serial number is: 4bfe05f182aa273e113db6ed7dae4bb8. - // - // Negative serial numbers are handled by calling i2d_ASN1_INTEGER() - // with a NULL second parameter. This will return the size of the - // buffer necessary to store the proper serial number. - // - // Do this even for positive serial numbers because it makes the code - // cleaner and easier to read. - - int bytes = i2d_ASN1_INTEGER(serial_number_asn1, nullptr); - - // According to X.509 specification the maximum length for the - // serial number is 20 octets. Add two bytes to account for - // DER type and length information. - - if (bytes <= 2 || bytes > 22) - return {}; - - // Now that we know the size of the serial number allocate enough - // space to hold it, and use i2d_ASN1_INTEGER() one last time to - // hold it in the allocated buffer. - - std::vector serial_der(bytes, 0); - auto tmp_pointer = serial_der.data(); - - // First 2 bytes are DER length information - bytes = i2d_ASN1_INTEGER(serial_number_asn1, &tmp_pointer) - 2; - - // For each byte in the serial to convert to hexlified format we - // need three bytes, two for the byte itself and one for colon. - // The last one doesn't have the colon, but the extra byte is used - // for the NULL terminator. - std::vector result(bytes * 3, 0); - for (int j = 0; j < bytes; j++) - { - // Don't put the colon on the last one. - // Skip over DER type, length information (first 2 bytes of serial_der) - if (j < bytes - 1) - snprintf(result.data() + 3 * j, 4, "%02x:", serial_der[j + 2]); - else - snprintf(result.data() + 3 * j, 3, "%02x", serial_der[j + 2]); - } - // Ignore NULL terminator - return {result.begin(), result.end() - 1}; -} - -std::string X509Certificate::getSignatureAlgorithm() const -{ - auto algo = X509_get0_tbs_sigalg(cert); - char name[256] = { '\0' }; - OBJ_obj2txt(name, 255, algo->algorithm, 0); - return name; -} - -std::string X509Certificate::getValidSince() const -{ - return parseDateTime(X509_get_notBefore(cert)); -} - -std::string X509Certificate::getValidUntil() const -{ - return parseDateTime(X509_get_notAfter(cert)); -} - -std::string X509Certificate::getPem() const -{ - BIO* bio = BIO_new(BIO_s_mem()); - PEM_write_bio_X509(bio, const_cast(cert)); - auto data_len = BIO_number_written(bio); - - std::vector result(data_len); - BIO_read(bio, static_cast(result.data()), data_len); - return { result.begin(), result.end() }; -} - -Certificate::Attributes parseAttributes(X509_NAME* raw) -{ - Certificate::Attributes attributes; - - std::size_t numEntries = X509_NAME_entry_count(raw); - for (std::size_t i = 0; i < numEntries; ++i) { - auto nameEntry = X509_NAME_get_entry(raw, int(i)); - auto valueObj = X509_NAME_ENTRY_get_data(nameEntry); - - std::string key = OBJ_nid2sn( - OBJ_obj2nid(X509_NAME_ENTRY_get_object(nameEntry))); - std::string value = std::string( - reinterpret_cast(valueObj->data), - valueObj->length); - - if (key == "C") - attributes.country = value; - else if (key == "O") - attributes.organization = value; - else if (key == "OU") - attributes.organizationalUnit = value; - else if (key == "dnQualifier") - attributes.nameQualifier = value; - else if (key == "ST") - attributes.state = value; - else if (key == "CN") - attributes.commonName = value; - else if (key == "serialNumber") - attributes.serialNumber = value; - else if (key == "L") - attributes.locality = value; - else if (key == "title") - attributes.title = value; - else if (key == "SN") - attributes.surname = value; - else if (key == "GN") - attributes.givenName = value; - else if (key == "initials") - attributes.initials = value; - else if (key == "pseudonym") - attributes.pseudonym = value; - else if (key == "generationQualifier") - attributes.generationQualifier = value; - else if (key == "emailAddress") - attributes.emailAddress = value; - } - - return attributes; -} - -Certificate::Attributes X509Certificate::getSubject() const -{ - return parseAttributes(X509_get_subject_name(cert)); -} -Certificate::Attributes X509Certificate::getIssuer() const -{ - return parseAttributes(X509_get_issuer_name(cert)); -} - -std::string X509Certificate::getPublicKey() const -{ - std::uint8_t* data = nullptr; - EVP_PKEY* pkey = X509_get0_pubkey(cert); - BIO* memBio = BIO_new(BIO_s_mem()); - PEM_write_bio_PUBKEY(memBio, pkey); - - std::string result(parsePublicKey(memBio)); - BIO_free_all(memBio); - - return result; -} - -std::string X509Certificate::getPublicKeyAlgorithm() const -{ - const EVP_PKEY* pkey = X509_get0_pubkey(cert); - if (!pkey) { - return "unknown"; - } - - return OBJ_nid2sn(EVP_PKEY_base_id(pkey)); -} - -std::string X509Certificate::getSha1() const -{ - const int sha1_length = 20; - std::uint8_t sha1_bytes[sha1_length]; - - std::uint8_t* data = nullptr; - int len = i2d_X509(const_cast(cert), &data); - - const EVP_MD* md = EVP_sha1(); - calculateDigest(md, data, len, sha1_bytes); - - free(data); - return bytesToHexString(sha1_bytes, sha1_length); -} -std::string X509Certificate::getSha256() const -{ - const int sha256_length = 32; - std::uint8_t sha256_bytes[sha256_length]; - - std::uint8_t* data = nullptr; - int len = i2d_X509(const_cast(cert), &data); - - const EVP_MD* md = EVP_sha256(); - calculateDigest(md, data, len, sha256_bytes); - - free(data); - return bytesToHexString(sha256_bytes, sha256_length); -} - -int X509Certificate::getVersion() const -{ - return X509_get_version(cert); -} - -std::string X509Certificate::getRawSubject() const -{ - return X509NameToString(X509_get_subject_name(cert)); -} - -std::string X509Certificate::getRawIssuer() const -{ - return X509NameToString(X509_get_issuer_name(cert)); -} - -// Oneline version for YARA compatibility -std::string X509Certificate::getOnelineSubject() const -{ - char buffer[256] = {0}; - X509_NAME_oneline(X509_get_subject_name(cert), buffer, sizeof(buffer)); - return std::string(buffer); -} - -// Oneline version for YARA compatibility -std::string X509Certificate::getOnelineIssuer() const -{ - char buffer[256] = {0}; - X509_NAME_oneline(X509_get_issuer_name(cert), buffer, sizeof(buffer)); - return std::string(buffer); -} - -Certificate X509Certificate::createCertificate() const -{ - Certificate out_cert; - out_cert.issuerRaw = getRawIssuer(); - out_cert.issuerOneline = getOnelineIssuer(); - out_cert.subjectRaw = getRawSubject(); - out_cert.subjectOneline = getOnelineSubject(); - out_cert.issuer = getIssuer(); - out_cert.subject = getSubject(); - out_cert.publicKey = getPublicKey(); - out_cert.publicKeyAlgo = getPublicKeyAlgorithm(); - out_cert.signatureAlgo = getSignatureAlgorithm(); - out_cert.serialNumber = getSerialNumber(); - out_cert.sha1Digest = getSha1(); - out_cert.sha256Digest = getSha256(); - out_cert.validSince = getValidSince(); - out_cert.validUntil = getValidUntil(); - return out_cert; -} - -CertificateProcessor::CertificateProcessor() - : trust_store(nullptr, X509_STORE_free), - ctx(nullptr, X509_STORE_CTX_free) -{ - trust_store.reset(X509_STORE_new()); - ctx.reset(X509_STORE_CTX_new()); -} - -std::vector CertificateProcessor::getChain(const X509* signer, const STACK_OF(X509)* all_certs) -{ - std::vector certificates; - - if (!signer) { - return certificates; - } - - X509_STORE_CTX_init(ctx.get(), trust_store.get(), const_cast(signer), const_cast(all_certs)); - bool is_valid = X509_verify_cert(ctx.get()) == 1; - STACK_OF(X509)* chain = X509_STORE_CTX_get_chain(ctx.get()); - - - int cert_cnt = sk_X509_num(chain); - for (int i = 0; i < cert_cnt; i++) { - certificates.emplace_back(sk_X509_value(chain, i)); - } - - return certificates; -} - -const X509_STORE* CertificateProcessor::getStore() const -{ - return trust_store.get(); -} -} // namespace authenticode diff --git a/src/fileformat/file_format/pe/authenticode/x509_certificate.h b/src/fileformat/file_format/pe/authenticode/x509_certificate.h deleted file mode 100644 index 085c08995..000000000 --- a/src/fileformat/file_format/pe/authenticode/x509_certificate.h +++ /dev/null @@ -1,77 +0,0 @@ -/** - * @file src/fileformat/file_format/pe/authenticode/x509_certificate.h - * @brief Class that wraps openssl x509 certificate information. - * @copyright (c) 2020 Avast Software, licensed under the MIT license - */ - -#pragma once - -#include "retdec/fileformat/types/certificate_table/certificate.h" -#include "retdec/fileformat/types/certificate_table/certificate_table.h" -#include "helper.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -using retdec::fileformat::Certificate; - -namespace authenticode { - -class X509Certificate -{ /* Can't name it X509 due to the collisions with openssl*/ -private: - const X509* cert = nullptr; - -public: - X509Certificate(const X509* cert); - X509Certificate() = default; - - int getVersion() const; - std::string getValidUntil() const; - std::string getValidSince() const; - std::string getRawSubject() const; - std::string getRawIssuer() const; - std::string getOnelineSubject() const; - std::string getOnelineIssuer() const; - std::string getSerialNumber() const; - std::string getSignatureAlgorithm() const; - std::string getPublicKey() const; - std::string getPublicKeyAlgorithm() const; - std::string getPem() const; - std::string getSignature() const; - std::string getSha1() const; - std::string getSha256() const; - Certificate::Attributes getSubject() const; - Certificate::Attributes getIssuer() const; - Certificate createCertificate() const; -}; - -class CertificateProcessor -{ -private: - std::unique_ptr trust_store; - std::unique_ptr ctx; - -public: - std::vector chain; - - CertificateProcessor(); - - std::vector getChain(const X509* cert, const STACK_OF(X509)* all_certs); - const X509_STORE* getStore() const; -}; - -} // namespace authenticode diff --git a/src/fileformat/file_format/pe/pe_format.cpp b/src/fileformat/file_format/pe/pe_format.cpp index 732a49a2d..9fd200fff 100644 --- a/src/fileformat/file_format/pe/pe_format.cpp +++ b/src/fileformat/file_format/pe/pe_format.cpp @@ -7,16 +7,20 @@ #include #include #include +#include #include +#include #include #include #include #include +#include #include #include #include -#include "authenticode/authenticode.h" +#include +#include "retdec/fileformat/types/certificate_table/certificate.h" #include "retdec/fileformat/types/certificate_table/certificate_table.h" #include "retdec/utils/container.h" #include "retdec/utils/conversion.h" @@ -1796,6 +1800,201 @@ void PeFormat::loadResources() } } +static std::string time_to_string(std::time_t time) +{ + std::tm* tm = std::gmtime(&time); + std::stringstream ss; + // "Dec 21 00:00:00 2012 GMT" format + ss << std::put_time(tm, "%b %e %OH:%OM:%OS %Y GMT"); + return ss.str(); +} + +static Certificate::Attributes getX509Attributes(Attributes attrs) +{ + Certificate::Attributes result; + result.country = attrs.country.data ? std::string(reinterpret_cast(attrs.country.data), attrs.country.len) : ""; + result.organization = attrs.organization.data ? std::string(reinterpret_cast(attrs.organization.data), attrs.organization.len) : ""; + result.organizationalUnit = attrs.organizationalUnit.data ? std::string(reinterpret_cast(attrs.organizationalUnit.data), attrs.organizationalUnit.len) : ""; + result.nameQualifier = attrs.nameQualifier.data ? std::string(reinterpret_cast(attrs.nameQualifier.data), attrs.nameQualifier.len) : ""; + result.state = attrs.state.data ? std::string(reinterpret_cast(attrs.state.data), attrs.state.len) : ""; + result.commonName = attrs.commonName.data ? std::string(reinterpret_cast(attrs.commonName.data), attrs.commonName.len) : ""; + result.serialNumber = attrs.serialNumber.data ? std::string(reinterpret_cast(attrs.serialNumber.data), attrs.serialNumber.len) : ""; + result.locality = attrs.locality.data ? std::string(reinterpret_cast(attrs.locality.data), attrs.locality.len) : ""; + result.title = attrs.title.data ? std::string(reinterpret_cast(attrs.title.data), attrs.title.len) : ""; + result.surname = attrs.surname.data ? std::string(reinterpret_cast(attrs.surname.data), attrs.surname.len) : ""; + result.givenName = attrs.givenName.data ? std::string(reinterpret_cast(attrs.givenName.data), attrs.givenName.len) : ""; + result.initials = attrs.initials.data ? std::string(reinterpret_cast(attrs.initials.data), attrs.initials.len) : ""; + result.pseudonym = attrs.pseudonym.data ? std::string(reinterpret_cast(attrs.pseudonym.data), attrs.pseudonym.len) : ""; + result.generationQualifier = attrs.generationQualifier.data ? std::string(reinterpret_cast(attrs.generationQualifier.data), attrs.generationQualifier.len) : ""; + result.emailAddress = attrs.emailAddress.data ? std::string(reinterpret_cast(attrs.emailAddress.data), attrs.emailAddress.len) : ""; + + return result; +} + +static std::vector getCertificates(CertificateArray* arr) +{ + if (!arr) + return {}; + + std::vector result; + + for (std::size_t i = 0; i < arr->count; ++i) + { + ::Certificate* cert = arr->certs[i]; + Certificate new_cert; + new_cert.validSince = cert->not_before ? time_to_string(cert->not_before) : ""; + new_cert.validUntil = cert->not_after ? time_to_string(cert->not_after) : ""; + new_cert.publicKey = cert->key ? cert->key : ""; + new_cert.publicKeyAlgo = cert->key_alg ? cert->key_alg : ""; + new_cert.signatureAlgo = cert->sig_alg ? cert->sig_alg : ""; + new_cert.serialNumber = cert->serial ? cert->serial : ""; + new_cert.subjectRaw = cert->subject ? cert->subject : ""; + new_cert.issuerRaw = cert->issuer ? cert->issuer : ""; + new_cert.issuer = getX509Attributes(cert->issuer_attrs); + new_cert.subject = getX509Attributes(cert->subject_attrs); + if (cert->sha1.data) + bytesToHexString(cert->sha1.data, cert->sha1.len, new_cert.sha1Digest); + if (cert->sha256.data) + bytesToHexString(cert->sha256.data, cert->sha256.len, new_cert.sha256Digest); + + result.emplace_back(new_cert); + } + + return result; +} + +static std::string authenticodeFlagToString(int flag) +{ + switch (flag) + { + case AUTHENTICODE_VFY_CANT_PARSE: + return "Couldn't parse the Pkcs7 signature"; + case AUTHENTICODE_VFY_NO_SIGNER_CERT: + return "Signing cert is missing"; + case AUTHENTICODE_VFY_DIGEST_MISSING: + return "Signature digest is missing"; + case AUTHENTICODE_VFY_INTERNAL_ERROR: + return "Internal error"; + case AUTHENTICODE_VFY_NO_SIGNER_INFO: + return "Couldn't get SignerInfo"; + case AUTHENTICODE_VFY_WRONG_PKCS7_TYPE: + return "Invalid PKCS#7 type, expected SignedData"; + case AUTHENTICODE_VFY_BAD_CONTENT: + return "Couldn't get contentInfo"; + case AUTHENTICODE_VFY_INVALID: + return "Signature isn't valid"; + case AUTHENTICODE_VFY_WRONG_FILE_DIGEST: + return "Signature digest doesn't match the file digest"; + case AUTHENTICODE_VFY_UNKNOWN_ALGORITHM: + return "Unknown digest algorithm"; + default: + return ""; + } +} + +static std::string countersigFlagToString(int flag) +{ + switch (flag) + { + case COUNTERSIGNATURE_VFY_CANT_PARSE: + return "Couldn't parse counter-signature"; + case COUNTERSIGNATURE_VFY_NO_SIGNER_CERT: + return "No counter-signature certificate"; + case COUNTERSIGNATURE_VFY_UNKNOWN_ALGORITHM: + return "Unknown digest algorithm"; + case COUNTERSIGNATURE_VFY_CANT_DECRYPT_DIGEST: + return "Couldn't decrypt the digest"; + case COUNTERSIGNATURE_VFY_DIGEST_MISSING: + return "Message digest is missing"; + case COUNTERSIGNATURE_VFY_INTERNAL_ERROR: + return "Internal error"; + case COUNTERSIGNATURE_VFY_DOESNT_MATCH_SIGNATURE: + return "Failed to verify the signature with counter-signature"; + case COUNTERSIGNATURE_VFY_TIME_MISSING: + return "Timestamp information is missing"; + case COUNTERSIGNATURE_VFY_INVALID: + return "Failed to verify the counter-signature"; + default: + return ""; + } +} + +static void writeSignerInfo(::Signer* signer, DigitalSignature& signature) +{ + if (!signer) + return; + + signature.signer.chain = getCertificates(signer->chain); + signature.programName = signer->program_name ? signer->program_name : ""; + signature.signer.digestAlgorithm = signer->digest_alg ? signer->digest_alg : ""; + if (signer->digest.data) + bytesToHexString(signer->digest.data, signer->digest.len, signature.signer.digest); +} + +static Signer getCountersigner(Countersignature* counter) +{ + if (!counter) + return {}; + + Signer countersigner; + + countersigner.chain = getCertificates(counter->chain); + countersigner.digestAlgorithm = counter->digest_alg ? counter->digest_alg : ""; + countersigner.signingTime = counter->sign_time ? time_to_string(counter->sign_time) : ""; + if (counter->digest.data) + bytesToHexString(counter->digest.data, counter->digest.len, countersigner.digest); + + // If there is any verification error, export it as a proper message + if (counter->verify_flags != COUNTERSIGNATURE_VFY_VALID) + countersigner.warnings.emplace_back(countersigFlagToString(counter->verify_flags)); + + return countersigner; +} + +static std::vector authenticodeToSignatures(AuthenticodeArray* arr, const PeFormat* file) +{ + if (!arr || !file) + return {}; + + std::vector result; + + for (std::size_t i = 0; i < arr->count; ++i) + { + DigitalSignature signature; + Authenticode* auth = arr->signatures[i]; + + if (auth->digest.data) + bytesToHexString(auth->digest.data, auth->digest.len, signature.signedDigest); + if (auth->file_digest.data) + bytesToHexString(auth->file_digest.data, auth->file_digest.len, signature.fileDigest); + + signature.certificates = getCertificates(auth->certs); + signature.isValid = auth->verify_flags == AUTHENTICODE_VFY_VALID; + signature.digestAlgorithm = auth->digest_alg ? auth->digest_alg : ""; + + // If there is any verification error, export it as a proper message + if (auth->verify_flags != AUTHENTICODE_VFY_VALID) + { + signature.warnings.emplace_back(authenticodeFlagToString(auth->verify_flags)); + } + + writeSignerInfo(auth->signer, signature); + + if (auth->countersigs) + { + for (std::size_t j = 0; j < auth->countersigs->count; ++j) + { + Countersignature* counter = auth->countersigs->counters[j]; + signature.signer.counterSigners.emplace_back(getCountersigner(counter)); + } + } + + result.emplace_back(signature); + } + + return result; +} + /** * Load certificates. */ @@ -1810,17 +2009,19 @@ void PeFormat::loadCertificates() // We always take the first one, there might be more WIN_CERT structures tho const std::vector& certBytes = securityDir.getCertificate(0); - authenticode::Authenticode authenticode(certBytes); - - this->certificateTable = new CertificateTable(authenticode.getSignatures(this)); - std::uint64_t dirOffset = securityDir.getOffset(); std::uint64_t dirSize = securityDir.getSize(); std::vector sections = getSections(); + AuthenticodeArray* auth = parse_authenticode(this->getBytesData(), this->getFileLength()); + std::vector sigs = authenticodeToSignatures(auth, this); + authenticode_array_free(auth); + + this->certificateTable = new CertificateTable(sigs); + certificateTable->isOutsideImage = true; - // check if the SecurityDir overlaps with any real part of section + // Check if the SecurityDir overlaps with any real part of section // if it does, Windows ignores the certificates for (const Section* sec : sections) { @@ -1836,6 +2037,7 @@ void PeFormat::loadCertificates() if (dirOffset < realEndOffset && realOffset < dirEndOffset) { certificateTable->isOutsideImage = false; + break; } } }