Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign up| #include "uthenticode.h" | |
| #include <openssl/crypto.h> | |
| #include <openssl/err.h> | |
| #include <openssl/x509.h> | |
| #include <algorithm> | |
| #include <array> | |
| #include <cstring> | |
| #include <iomanip> | |
| #include <iostream> | |
| #include <memory> | |
| #include <sstream> | |
| namespace uthenticode { | |
| namespace impl { | |
| // clang-format off | |
| ASN1_SEQUENCE(Authenticode_SpcAttributeTypeAndOptionalValue) = { | |
| ASN1_SIMPLE(Authenticode_SpcAttributeTypeAndOptionalValue, type, ASN1_OBJECT), | |
| ASN1_OPT(Authenticode_SpcAttributeTypeAndOptionalValue, value, ASN1_ANY) | |
| } ASN1_SEQUENCE_END(Authenticode_SpcAttributeTypeAndOptionalValue) | |
| IMPLEMENT_ASN1_FUNCTIONS(Authenticode_SpcAttributeTypeAndOptionalValue) | |
| ASN1_SEQUENCE(Authenticode_DigestInfo) = { | |
| ASN1_SIMPLE(Authenticode_DigestInfo, digestAlgorithm, X509_ALGOR), | |
| ASN1_SIMPLE(Authenticode_DigestInfo, digest, ASN1_OCTET_STRING) | |
| } ASN1_SEQUENCE_END(Authenticode_DigestInfo) | |
| IMPLEMENT_ASN1_FUNCTIONS(Authenticode_DigestInfo) | |
| ASN1_SEQUENCE(Authenticode_SpcIndirectDataContent) = { | |
| ASN1_SIMPLE(Authenticode_SpcIndirectDataContent, data, Authenticode_SpcAttributeTypeAndOptionalValue), | |
| ASN1_SIMPLE(Authenticode_SpcIndirectDataContent, messageDigest, Authenticode_DigestInfo) | |
| } ASN1_SEQUENCE_END(Authenticode_SpcIndirectDataContent) | |
| IMPLEMENT_ASN1_FUNCTIONS(Authenticode_SpcIndirectDataContent) | |
| /* OpenSSL defines OPENSSL_free as a macro, which we can't use with decltype. | |
| * So we wrap it here for use with unique_ptr. | |
| */ | |
| void OpenSSL_free(void *ptr) { | |
| OPENSSL_free(ptr); | |
| } | |
| // clang-format on | |
| } // namespace impl | |
| /** | |
| * Rounds `x` up to the nearest multiple of `factor`. | |
| * | |
| * @param x the number to round | |
| * @param factor the factor to round by | |
| * @return the rounded number | |
| */ | |
| static inline std::size_t round(std::size_t x, std::size_t factor) { | |
| auto rem = x % factor; | |
| if (rem == 0) { | |
| return x; | |
| } | |
| return x + factor - rem; | |
| } | |
| /** | |
| * Converts the given buffer into a hexadecimal string representation. | |
| * | |
| * @param buf the input buffer | |
| * @param len the buffer's size | |
| * @return the hex string | |
| */ | |
| static inline std::string tohex(std::uint8_t *buf, std::size_t len) { | |
| if (buf == nullptr) { | |
| return {}; | |
| } | |
| std::stringstream ss; | |
| ss << std::hex << std::setw(2) << std::setfill('0'); | |
| for (std::size_t i = 0; i < len; ++i) { | |
| ss << static_cast<int>(buf[i]); | |
| } | |
| return ss.str(); | |
| } | |
| /** | |
| * Convert the given checksum_kind to an OpenSSL NID. | |
| * | |
| * @param kind the checksum_kind to convert | |
| * @return an integer representing the corresponding OpenSSL NID | |
| */ | |
| static inline int checksum_type_to_nid(checksum_kind kind) { | |
| switch (kind) { | |
| default: | |
| return NID_undef; | |
| case checksum_kind::MD5: | |
| return NID_md5; | |
| case checksum_kind::SHA1: | |
| return NID_sha1; | |
| case checksum_kind::SHA256: | |
| return NID_sha256; | |
| } | |
| } | |
| /** | |
| * Convert the given OpenSSL NID to a checksum_kind. | |
| * | |
| * @param nid the NID to convert | |
| * @return the corresponding checksum_kind | |
| */ | |
| static inline checksum_kind nid_to_checksum_kind(int nid) { | |
| switch (nid) { | |
| default: | |
| return checksum_kind::UNKNOWN; | |
| case NID_md5: | |
| return checksum_kind::MD5; | |
| case NID_sha1: | |
| return checksum_kind::SHA1; | |
| case NID_sha256: | |
| return checksum_kind::SHA256; | |
| } | |
| } | |
| std::ostream &operator<<(std::ostream &os, checksum_kind kind) { | |
| switch (kind) { | |
| default: | |
| return os << static_cast<std::uint16_t>(kind); | |
| case checksum_kind::MD5: | |
| return os << "MD5"; | |
| case checksum_kind::SHA1: | |
| return os << "SHA1"; | |
| case checksum_kind::SHA256: | |
| return os << "SHA256"; | |
| } | |
| } | |
| Certificate::Certificate(X509 *cert) { | |
| auto subject = impl::OpenSSL_ptr(X509_NAME_oneline(X509_get_subject_name(cert), nullptr, 0), | |
| impl::OpenSSL_free); | |
| auto issuer = impl::OpenSSL_ptr(X509_NAME_oneline(X509_get_issuer_name(cert), nullptr, 0), | |
| impl::OpenSSL_free); | |
| auto serial_bn = impl::BN_ptr(ASN1_INTEGER_to_BN(X509_get_serialNumber(cert), nullptr), BN_free); | |
| auto serial_number = impl::OpenSSL_ptr(BN_bn2hex(serial_bn.get()), impl::OpenSSL_free); | |
| subject_ = std::string(subject.get()); | |
| issuer_ = std::string(issuer.get()); | |
| serial_number_ = std::string(serial_number.get()); | |
| } | |
| SignedData::SignedData(std::vector<std::uint8_t> cert_buf) : cert_buf_(cert_buf) { | |
| auto *buf_ptr = BIO_new_mem_buf(cert_buf_.data(), static_cast<int>(cert_buf_.size())); | |
| if (buf_ptr == nullptr) { | |
| throw std::bad_alloc{}; | |
| } | |
| impl::BIO_ptr buf(buf_ptr, BIO_free); | |
| p7_ = d2i_PKCS7_bio(buf.get(), nullptr); | |
| if (p7_ == nullptr) { | |
| throw FormatError{"Couldn't parse PKCS#7 SignedData"}; | |
| } | |
| /* NOTE(ww): This call is safe within the constructor, since get_indirect_data | |
| * only requires p7_ to be initialized (which happens immediately above). | |
| */ | |
| indirect_data_ = get_indirect_data(); | |
| if (indirect_data_ == nullptr) { | |
| throw FormatError{"Couldn't parse SpcIndirectDataContent"}; | |
| } | |
| } | |
| SignedData::SignedData(SignedData &&s) noexcept | |
| : cert_buf_(std::move(s.cert_buf_)), | |
| p7_(std::exchange(s.p7_, nullptr)), | |
| indirect_data_(std::exchange(s.indirect_data_, nullptr)) { | |
| } | |
| SignedData::~SignedData() { | |
| if (p7_ != nullptr) { | |
| PKCS7_free(p7_); | |
| } | |
| if (indirect_data_ != nullptr) { | |
| impl::Authenticode_SpcIndirectDataContent_free(indirect_data_); | |
| } | |
| } | |
| bool SignedData::verify_signature() const { | |
| STACK_OF(X509) *certs = nullptr; | |
| switch (OBJ_obj2nid(p7_->type)) { | |
| case NID_pkcs7_signed: { | |
| certs = p7_->d.sign->cert; | |
| break; | |
| } | |
| /* NOTE(ww): I'm pretty sure Authenticode signatures are always SignedData and never | |
| * SignedAndEnvelopedData, but it doesn't hurt us to handle the latter as well. | |
| */ | |
| case NID_pkcs7_signedAndEnveloped: { | |
| certs = p7_->d.signed_and_enveloped->cert; | |
| break; | |
| } | |
| } | |
| if (certs == nullptr) { | |
| return false; | |
| } | |
| /* NOTE(ww): What happens below is a bit dumb: we convert our SpcIndirectDataContent back | |
| * into DER form so that we can unwrap its ASN.1 sequence and pass the underlying data | |
| * to PKCS7_verify for verification. This displays our intent a little more clearly than | |
| * our previous approach, which was to walk the PKCS#7 structure manually. | |
| */ | |
| std::uint8_t *indirect_data_buf = nullptr; | |
| auto buf_size = impl::i2d_Authenticode_SpcIndirectDataContent(indirect_data_, &indirect_data_buf); | |
| if (buf_size < 0 || indirect_data_buf == nullptr) { | |
| return false; | |
| } | |
| auto indirect_data_ptr = | |
| impl::OpenSSL_ptr(reinterpret_cast<char *>(indirect_data_buf), impl::OpenSSL_free); | |
| const auto *signed_data_seq = reinterpret_cast<std::uint8_t *>(indirect_data_ptr.get()); | |
| long length = 0; | |
| int tag = 0, tag_class = 0; | |
| ASN1_get_object(&signed_data_seq, &length, &tag, &tag_class, buf_size); | |
| if (tag != V_ASN1_SEQUENCE) { | |
| return false; | |
| } | |
| auto *signed_data_ptr = BIO_new_mem_buf(signed_data_seq, length); | |
| if (signed_data_ptr == nullptr) { | |
| return false; | |
| } | |
| impl::BIO_ptr signed_data(signed_data_ptr, BIO_free); | |
| /* Our actual verification happens here. | |
| * | |
| * We pass `certs` explicitly, but (experimentally) we don't have to -- the function correctly | |
| * extracts then from the SignedData in `p7_`. | |
| * | |
| * We pass `nullptr` for the X509_STORE, since we don't do full-chain verification | |
| * (we can't, since we don't have access to Windows's Trusted Publishers store on non-Windows). | |
| */ | |
| auto status = PKCS7_verify(p7_, certs, nullptr, signed_data.get(), nullptr, PKCS7_NOVERIFY); | |
| return status == 1; | |
| } | |
| Checksum SignedData::get_checksum() const { | |
| auto nid = OBJ_obj2nid(indirect_data_->messageDigest->digestAlgorithm->algorithm); | |
| auto digest = tohex(indirect_data_->messageDigest->digest->data, | |
| indirect_data_->messageDigest->digest->length); | |
| return std::make_tuple(nid_to_checksum_kind(nid), digest); | |
| } | |
| std::vector<Certificate> SignedData::get_signers() const { | |
| auto *signers_stack_ptr = PKCS7_get0_signers(p7_, nullptr, 0); | |
| if (signers_stack_ptr == nullptr) { | |
| return {}; | |
| } | |
| auto signers_stack = impl::STACK_OF_X509_ptr(signers_stack_ptr, sk_X509_free); | |
| std::vector<Certificate> signers; | |
| for (auto i = 0; i < sk_X509_num(signers_stack.get()); ++i) { | |
| /* Frustrating: ideally we'd use emplace_back here, but apparently | |
| * it gets exploded into some std::allocator scope that makes the private Certificate | |
| * constructor flip out (since it's only friends with SignedData). | |
| * | |
| * Instead, push_back and hope for copy elision (or the default move constructor, maybe). | |
| */ | |
| signers.push_back(Certificate(sk_X509_value(signers_stack.get(), i))); | |
| } | |
| return signers; | |
| } | |
| std::vector<Certificate> SignedData::get_certificates() const { | |
| auto *certs_stack_ptr = p7_->d.sign->cert; | |
| if (certs_stack_ptr == nullptr) { | |
| return {}; | |
| } | |
| std::vector<Certificate> certs; | |
| for (auto i = 0; i < sk_X509_num(certs_stack_ptr); ++i) { | |
| /* Like above: ideally we'd use emplace_back, but C++ gets in the way. | |
| */ | |
| certs.push_back(Certificate(sk_X509_value(certs_stack_ptr, i))); | |
| } | |
| return certs; | |
| } | |
| std::optional<SignedData> SignedData::get_nested_signed_data() const { | |
| PKCS7_SIGNER_INFO *signer_info = sk_PKCS7_SIGNER_INFO_value(p7_->d.sign->signer_info, 0); | |
| /* NOTE(ww): OpenSSL stupidity: you actually need to call OBJ_create before | |
| * OBJ_txt2obj; the latter won't do it for you. Luckily (?) OpenSSL 1.1.0+ | |
| * auto-frees these, so they're not totally impossible to use in leakless C++. | |
| */ | |
| OBJ_create(impl::SPC_NESTED_SIGNATURE_OID, NULL, NULL); | |
| auto *spc_nested_sig_oid_ptr = OBJ_txt2obj(impl::SPC_NESTED_SIGNATURE_OID, 1); | |
| if (spc_nested_sig_oid_ptr == nullptr) { | |
| return std::nullopt; | |
| } | |
| impl::ASN1_OBJECT_ptr spc_nested_sig_oid(spc_nested_sig_oid_ptr, ASN1_OBJECT_free); | |
| auto *nested_signed_data = | |
| PKCS7_get_attribute(signer_info, OBJ_obj2nid(spc_nested_sig_oid.get())); | |
| if (nested_signed_data == nullptr) { | |
| return std::nullopt; | |
| } | |
| if (ASN1_TYPE_get(nested_signed_data) != V_ASN1_SEQUENCE) { | |
| return std::nullopt; | |
| } | |
| auto *nested_signed_data_seq = nested_signed_data->value.sequence; | |
| std::vector<std::uint8_t> cert_buf(nested_signed_data_seq->data, | |
| nested_signed_data_seq->data + nested_signed_data_seq->length); | |
| return std::make_optional<SignedData>(cert_buf); | |
| } | |
| impl::Authenticode_SpcIndirectDataContent *SignedData::get_indirect_data() const { | |
| auto *contents = p7_->d.sign->contents; | |
| if (contents == nullptr) { | |
| return nullptr; | |
| } | |
| /* We're expecting a sequence whose type is SPC_INDIRECT_DATA_OID. | |
| */ | |
| OBJ_create(impl::SPC_INDIRECT_DATA_OID, NULL, NULL); | |
| auto *spc_indir_oid_ptr = OBJ_txt2obj(impl::SPC_INDIRECT_DATA_OID, 1); | |
| if (spc_indir_oid_ptr == nullptr) { | |
| return nullptr; | |
| } | |
| impl::ASN1_OBJECT_ptr spc_indir_oid(spc_indir_oid_ptr, ASN1_OBJECT_free); | |
| if (ASN1_TYPE_get(contents->d.other) != V_ASN1_SEQUENCE || | |
| OBJ_cmp(contents->type, spc_indir_oid.get())) { | |
| return nullptr; | |
| } | |
| /* TODO: Could probably use a BIO here instead of the direct-from-buffer API. | |
| */ | |
| const auto *indir_data_inc_ptr = contents->d.other->value.sequence->data; | |
| auto *indir_data = impl::d2i_Authenticode_SpcIndirectDataContent( | |
| nullptr, &indir_data_inc_ptr, contents->d.other->value.sequence->length); | |
| if (indir_data == nullptr) { | |
| return nullptr; | |
| } | |
| /* Sanity checks against SpcIndirectDataContent. It's not clear to me | |
| * whether a non-nullptr above guarantees any of these fields, so check | |
| * them manually. | |
| */ | |
| if (indir_data->messageDigest->digest->data == nullptr || | |
| indir_data->messageDigest->digest->length >= contents->d.other->value.sequence->length) { | |
| return nullptr; | |
| } | |
| return indir_data; | |
| } | |
| std::optional<SignedData> WinCert::as_signed_data() const { | |
| if (type_ != certificate_type::CERT_TYPE_PKCS_SIGNED_DATA) { | |
| return std::nullopt; | |
| } | |
| try { | |
| return std::make_optional<SignedData>(cert_buf_); | |
| } catch (FormatError) { | |
| return std::nullopt; | |
| } | |
| } | |
| std::vector<WinCert> read_certs(peparse::parsed_pe *pe) { | |
| if (pe == nullptr) { | |
| return {}; | |
| } | |
| std::vector<std::uint8_t> raw_cert_table; | |
| if (!peparse::GetDataDirectoryEntry(pe, peparse::DIR_SECURITY, raw_cert_table)) { | |
| return {}; | |
| } | |
| /* The certificate table is composed of 8-byte aligned entries, each of which looks like this: | |
| * | |
| * dwLength (uint32_t): length of the raw certificate data | |
| * wRevision (uint16_t): the certificate revision number | |
| * wCertificateType (uint16_t): the kind of data in this certificate entry | |
| * bCertificate (uint8_t[dwLength - 8]): the raw certificate data in this entry | |
| */ | |
| std::vector<WinCert> certs; | |
| size_t offset = 0; | |
| while (offset < raw_cert_table.size()) { | |
| std::uint32_t length = *reinterpret_cast<std::uint32_t *>(raw_cert_table.data() + offset); | |
| offset += sizeof(length); | |
| std::uint16_t revision = *reinterpret_cast<std::uint16_t *>(raw_cert_table.data() + offset); | |
| offset += sizeof(revision); | |
| std::uint16_t type = *reinterpret_cast<std::uint16_t *>(raw_cert_table.data() + offset); | |
| offset += sizeof(type); | |
| std::vector<std::uint8_t> cert_data(raw_cert_table.data() + offset, | |
| raw_cert_table.data() + length); | |
| certs.emplace_back(static_cast<certificate_revision>(revision), | |
| static_cast<certificate_type>(type), | |
| cert_data); | |
| offset += round(length, 8); | |
| } | |
| return certs; | |
| } | |
| std::vector<Checksum> get_checksums(peparse::parsed_pe *pe) { | |
| std::vector<Checksum> checksums; | |
| const auto &certs = read_certs(pe); | |
| if (certs.empty()) { | |
| return checksums; | |
| } | |
| for (const auto &cert : certs) { | |
| auto signed_data = cert.as_signed_data(); | |
| if (!signed_data) { | |
| continue; | |
| } | |
| checksums.push_back(signed_data->get_checksum()); | |
| } | |
| return checksums; | |
| } | |
| std::string calculate_checksum(peparse::parsed_pe *pe, checksum_kind kind) { | |
| auto nid = checksum_type_to_nid(kind); | |
| if (nid == NID_undef) { | |
| return {}; | |
| } | |
| /* We'll stash the bits of the PE that we need to hash in this buffer. | |
| * Reserve the original PE's size upfront, since we expect the hashed data | |
| * to be only slightly smaller. | |
| */ | |
| std::vector<std::uint8_t> pe_bits; | |
| pe_bits.reserve(pe->fileBuffer->bufLen); | |
| /* In both PEs and PE32+s, the PE checksum is 64 bytes into the optional header, | |
| * which itself is 24 bytes after the PE magic and COFF header from the offset | |
| * specified in the DOS header. | |
| */ | |
| auto pe_checksum_offset = pe->peHeader.dos.e_lfanew + 24 + 64; | |
| /* The certificate table directory entry offset is also in the optional header, | |
| * albeit at different offsets for PE32 and PE32+. See each of the cases below. | |
| */ | |
| std::size_t cert_table_offset = pe->peHeader.dos.e_lfanew + 24; | |
| std::uint32_t size_of_headers = 0; | |
| peparse::data_directory security_dir; | |
| if (pe->peHeader.nt.OptionalMagic == peparse::NT_OPTIONAL_32_MAGIC) { | |
| security_dir = pe->peHeader.nt.OptionalHeader.DataDirectory[peparse::DIR_SECURITY]; | |
| size_of_headers = pe->peHeader.nt.OptionalHeader.SizeOfHeaders; | |
| cert_table_offset += 128; | |
| } else if (pe->peHeader.nt.OptionalMagic == peparse::NT_OPTIONAL_64_MAGIC) { | |
| security_dir = pe->peHeader.nt.OptionalHeader64.DataDirectory[peparse::DIR_SECURITY]; | |
| size_of_headers = pe->peHeader.nt.OptionalHeader64.SizeOfHeaders; | |
| cert_table_offset += 144; | |
| } else { | |
| /* Mystical future PE version? | |
| */ | |
| return {}; | |
| } | |
| /* "VirtualAddress" here is really an offset; an invalid one indicates a tampered input. | |
| * Similarly, a cert_table_offset beyond size_of_headers indicates a tampered input | |
| * (we get the pe_checksum_offset check for free, since it's always smaller). | |
| */ | |
| if (security_dir.VirtualAddress + security_dir.Size > pe->fileBuffer->bufLen || | |
| cert_table_offset + 8 > size_of_headers) { | |
| return {}; | |
| } | |
| /* Copy everything up to the end of the headers into pe_bits. | |
| * Use a bounded_buffer to handle the range checks for us. | |
| */ | |
| auto *header_buf = peparse::splitBuffer(pe->fileBuffer, 0, size_of_headers); | |
| if (header_buf == nullptr) { | |
| return {}; | |
| } | |
| pe_bits.insert(pe_bits.begin(), header_buf->buf, header_buf->buf + header_buf->bufLen); | |
| delete header_buf; | |
| /* Erase the PE checksum and certificate table entry from pe_bits. | |
| * Do the certificate table entry first, so that we don't have to rescale the checksum's offset. | |
| */ | |
| pe_bits.erase(pe_bits.begin() + cert_table_offset, pe_bits.begin() + cert_table_offset + 8); | |
| pe_bits.erase(pe_bits.begin() + pe_checksum_offset, pe_bits.begin() + pe_checksum_offset + 4); | |
| /* Build up the list of sections in the PE, in ascending order by PointerToRawData | |
| * (i.e., by file offset). | |
| * | |
| * NOTE(ww): Ideally we'd use a capture with the C++ lambda here, but C++ lambdas can't be | |
| * used within C callbacks unless they're captureless. | |
| */ | |
| impl::SectionList sections; | |
| peparse::IterSec( | |
| pe, | |
| [](void *cbd, | |
| [[maybe_unused]] const peparse::VA &secBase, | |
| [[maybe_unused]] const std::string §ionName, | |
| [[maybe_unused]] const peparse::image_section_header &sec, | |
| const peparse::bounded_buffer *b) -> int { | |
| auto §ions = *static_cast<impl::SectionList *>(cbd); | |
| sections.emplace_back(b); | |
| return 0; | |
| }, | |
| §ions); | |
| /* Copy each section's data into pe_bits, in ascending order. | |
| */ | |
| for (const auto §ion : sections) { | |
| pe_bits.insert(pe_bits.end(), section->buf, section->buf + section->bufLen); | |
| } | |
| /* Also copy any data that happens to be trailing the certificate table into pe_bits. | |
| * Most PEs won't have any trailing data but the Authenticode specification is explicit about | |
| * hashing any if it exists. | |
| */ | |
| pe_bits.insert(pe_bits.end(), | |
| pe->fileBuffer->buf + security_dir.VirtualAddress + security_dir.Size, | |
| pe->fileBuffer->buf + pe->fileBuffer->bufLen); | |
| /* This won't happen under normal conditions, but could with a tampered input. | |
| * We don't have to check pe_checksum_offset here since it'll always be strictly less | |
| * than cert_table_offset. | |
| */ | |
| if (pe_bits.size() <= cert_table_offset + 8) { | |
| return {}; | |
| } | |
| /* Finally, hash the damn thing. | |
| * | |
| * NOTE(ww): Instead of building up pe_bits and hashing it in one pass, we | |
| * could hash it incrementally with each section. This would also solve | |
| * the capture problem with the C++ callback above and would reduce | |
| * the number of needed allocations. | |
| */ | |
| std::array<std::uint8_t, EVP_MAX_MD_SIZE> md_buf; | |
| const auto *md = EVP_get_digestbynid(nid); | |
| auto *md_ctx = EVP_MD_CTX_new(); | |
| EVP_DigestInit(md_ctx, md); | |
| EVP_DigestUpdate(md_ctx, pe_bits.data(), pe_bits.size()); | |
| EVP_DigestFinal(md_ctx, md_buf.data(), nullptr); | |
| EVP_MD_CTX_free(md_ctx); | |
| return tohex(md_buf.data(), EVP_MD_size(md)); | |
| } | |
| bool verify(peparse::parsed_pe *pe) { | |
| /* Our verification state. | |
| * A PE is said to be verified if all three are true. | |
| * | |
| * We start verified_signed_data and verified_checksum in the true state | |
| * so that we can AND them against each SignedData's results. | |
| */ | |
| bool has_signed_data = false, verified_signed_data = true, verified_checksum = true; | |
| auto certs = read_certs(pe); | |
| for (const auto &cert : certs) { | |
| const auto signed_data = cert.as_signed_data(); | |
| if (!signed_data) { | |
| continue; | |
| } | |
| auto embedded_checksum = signed_data->get_checksum(); | |
| has_signed_data = true; | |
| verified_signed_data = verified_signed_data && signed_data->verify_signature(); | |
| verified_checksum = | |
| verified_checksum && std::get<std::string>(embedded_checksum) == | |
| calculate_checksum(pe, std::get<checksum_kind>(embedded_checksum)); | |
| const auto nested_signed_data = signed_data->get_nested_signed_data(); | |
| if (!nested_signed_data) { | |
| continue; | |
| } | |
| auto nested_embedded_checksum = nested_signed_data->get_checksum(); | |
| verified_signed_data = verified_signed_data && nested_signed_data->verify_signature(); | |
| verified_checksum = | |
| verified_checksum && | |
| std::get<std::string>(nested_embedded_checksum) == | |
| calculate_checksum(pe, std::get<checksum_kind>(nested_embedded_checksum)); | |
| } | |
| return has_signed_data && verified_signed_data && verified_checksum; | |
| } | |
| } // namespace uthenticode |