From d29a74996b65282461b95b5b626d52215c5523c1 Mon Sep 17 00:00:00 2001 From: Romain Thomas Date: Tue, 12 Jan 2021 20:44:23 +0100 Subject: [PATCH] Process PKCS #9 counter signature and enhance signature verification --- CMakeLists.txt | 7 + api/python/PE/objects/pyBinary.cpp | 15 +- .../attributes/pyPKCS9CounterSignature.cpp | 7 +- .../PE/objects/signature/pySignature.cpp | 66 +++- .../PE/objects/signature/pySignerInfo.cpp | 32 +- api/python/PE/objects/signature/pyx509.cpp | 32 ++ api/python/PE/pyPE.cpp | 2 +- .../api_example.py} | 0 .../authenticode/authenticode_reader.py | 302 +++++++++++++++++ examples/python/pe_sign/sig_info.py | 62 ---- include/LIEF/BinaryStream/VectorStream.hpp | 8 + include/LIEF/PE/Binary.hpp | 12 +- include/LIEF/PE/signature/Signature.hpp | 47 ++- include/LIEF/PE/signature/SignatureParser.hpp | 2 +- include/LIEF/PE/signature/SignerInfo.hpp | 25 +- .../attributes/PKCS9CounterSignature.hpp | 10 +- include/LIEF/PE/signature/x509.hpp | 42 ++- include/LIEF/enums.hpp | 7 + src/BinaryStream/VectorStream.cpp | 4 +- src/PE/Binary.cpp | 16 +- src/PE/hash.cpp | 3 +- src/PE/json.cpp | 10 +- src/PE/signature/Signature.cpp | 239 +++++++++++-- src/PE/signature/SignatureParser.cpp | 58 ++-- src/PE/signature/SignerInfo.cpp | 28 +- .../attributes/PKCS9CounterSignature.cpp | 11 +- src/PE/signature/x509.cpp | 313 ++++++++++++++++-- tests/pe/test_authenticode.py | 5 +- 28 files changed, 1157 insertions(+), 208 deletions(-) rename examples/python/{pe_sign/authenticode.py => authenticode/api_example.py} (100%) create mode 100644 examples/python/authenticode/authenticode_reader.py delete mode 100644 examples/python/pe_sign/sig_info.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 10502ff..df9a3ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -325,6 +325,9 @@ if(LIEF_FROZEN_ENABLED) add_dependencies(LIB_LIEF lief_frozen) endif() +# ======================================= +# Leaf +# ======================================= add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/include/LIEF/third-party/boost/leaf/all.hpp COMMAND ${CMAKE_COMMAND} -E copy_directory ${LEAF_INCLUDE_DIR}/ @@ -334,6 +337,9 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/include/LIEF/third-party/b target_sources(LIB_LIEF PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include/LIEF/third-party/boost/leaf/all.hpp) +# ======================================= +# utfcpp +# ======================================= add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/include/LIEF/third-party/utfcpp/utf8.h COMMAND ${CMAKE_COMMAND} -E copy_directory ${UTFCPP_INCLUDE_DIR}/ @@ -375,6 +381,7 @@ target_compile_definitions(LIB_LIEF PUBLIC -D_GLIBCXX_USE_CXX11_ABI=1) # extension. add_definitions(-DMBEDTLS_MD2_C -DMBEDTLS_MD4_C -DMBEDTLS_PEM_PARSE_C -DMBEDTLS_X509_CRT_PARSE_C -DMBEDTLS_PEM_WRITE_C + -DMBEDTLS_PKCS1_V15 -DMBEDTLS_PKCS1_V21 -DMBEDTLS_X509_ALLOW_UNSUPPORTED_CRITICAL_EXTENSION) # ASAN - LSAN - TSAN - USAN diff --git a/api/python/PE/objects/pyBinary.cpp b/api/python/PE/objects/pyBinary.cpp index e13d92c..492323d 100644 --- a/api/python/PE/objects/pyBinary.cpp +++ b/api/python/PE/objects/pyBinary.cpp @@ -166,15 +166,22 @@ void create(py::module& m) { "algorithm"_a) .def("verify_signature", - static_cast(&Binary::verify_signature), + static_cast(&Binary::verify_signature), R"delim( Verify the binary against the embedded signature(s) (if any) Firstly, it checks that the embedded signatures are correct (c.f. :meth:`lief.PE.Signature.check`) and then it checks that the authentihash matches :attr:`lief.PE.ContentInfo.digest` - )delim") + + One can tweak the verification process with the :class:`lief.PE.Signature.VERIFICATION_CHECKS` flags + + .. seealso:: + + :meth:`lief.PE.Signature.check` + )delim", + "checks"_a = Signature::VERIFICATION_CHECKS::DEFAULT) .def("verify_signature", - static_cast(&Binary::verify_signature), + static_cast(&Binary::verify_signature), R"delim( Verify the binary with the Signature object provided in the first parameter It can be used to verify a detached signature: @@ -184,7 +191,7 @@ void create(py::module& m) { detached = lief.PE.Signature.parse("sig.pkcs7") binary.verify_signature(detached) )delim", - "signature"_a) + "signature"_a, "checks"_a = Signature::VERIFICATION_CHECKS::DEFAULT) .def_property_readonly("authentihash_md5", [] (const Binary& bin) { diff --git a/api/python/PE/objects/signature/attributes/pyPKCS9CounterSignature.cpp b/api/python/PE/objects/signature/attributes/pyPKCS9CounterSignature.cpp index d43612c..1e8f96e 100644 --- a/api/python/PE/objects/signature/attributes/pyPKCS9CounterSignature.cpp +++ b/api/python/PE/objects/signature/attributes/pyPKCS9CounterSignature.cpp @@ -49,9 +49,10 @@ void create(py::module& m) { } )delim") - .def_property_readonly("signers", - &PKCS9CounterSignature::signers, - "Iterator over the " RST_CLASS_REF(lief.PE.SignerInfo) " as described in the RFC") + .def_property_readonly("signer", + &PKCS9CounterSignature::signer, + "Return the " RST_CLASS_REF(lief.PE.SignerInfo) " as described in the RFC #2985", + py::return_value_policy::reference) .def("__hash__", [] (const PKCS9CounterSignature& obj) { diff --git a/api/python/PE/objects/signature/pySignature.cpp b/api/python/PE/objects/signature/pySignature.cpp index 831b467..373c618 100644 --- a/api/python/PE/objects/signature/pySignature.cpp +++ b/api/python/PE/objects/signature/pySignature.cpp @@ -51,7 +51,24 @@ void create(py::module& m) { .value("MISSING_PKCS9_MESSAGE_DIGEST", Signature::VERIFICATION_FLAGS::MISSING_PKCS9_MESSAGE_DIGEST) .value("BAD_DIGEST", Signature::VERIFICATION_FLAGS::BAD_DIGEST) .value("BAD_SIGNATURE", Signature::VERIFICATION_FLAGS::BAD_SIGNATURE) - .value("NO_SIGNATURE", Signature::VERIFICATION_FLAGS::NO_SIGNATURE); + .value("NO_SIGNATURE", Signature::VERIFICATION_FLAGS::NO_SIGNATURE) + .value("CERT_EXPIRED", Signature::VERIFICATION_FLAGS::CERT_EXPIRED) + .value("CERT_FUTURE", Signature::VERIFICATION_FLAGS::CERT_FUTURE); + + + LIEF::enum_(signature, "VERIFICATION_CHECKS", py::arithmetic(), + R"delim( + Flags to tweak the verification process of the signature + See :meth:`lief.PE.Signature.check` and :meth:`lief.PE.Binary.verify_signature` + )delim") + .value("DEFAULT", Signature::VERIFICATION_CHECKS::DEFAULT, + "Default behavior that tries to follow the Microsoft verification process as close as possible") + .value("HASH_ONLY", Signature::VERIFICATION_CHECKS::HASH_ONLY, + "Only check that :meth:`lief.PE.Binary.authentihash` matches :attr:`lief.PE.ContentInfo.digest` regardless of the signature's validity") + .value("LIFETIME_SIGNING", Signature::VERIFICATION_CHECKS::LIFETIME_SIGNING, + "Same semantic as `WTD_LIFETIME_SIGNING_FLAG `") + .value("SKIP_CERT_TIME", Signature::VERIFICATION_CHECKS::SKIP_CERT_TIME, + "Skip the verification of the certificates time validities so that even though a certificate expired, it returns :attr:`lief.PE.Signature.VERIFICATION_FLAGS.OK`"); signature .def_static("parse", @@ -78,37 +95,65 @@ void create(py::module& m) { .def_property_readonly("version", &Signature::version, - "Should be 1") + "Version of the signature. It should be 1") .def_property_readonly("digest_algorithm", &Signature::digest_algorithm, "Return the algorithm (" RST_CLASS_REF(lief.PE.ALGORITHMS) ") \ used to sign the content of " RST_CLASS_REF(lief.PE.ContentInfo) "") - .def_property_readonly("content_info", &Signature::content_info, "Return the " RST_CLASS_REF(lief.PE.ContentInfo) "", py::return_value_policy::reference) - .def_property_readonly("certificates", &Signature::certificates, "Return an iterator over " RST_CLASS_REF(lief.PE.x509) " certificates", py::return_value_policy::reference) - .def_property_readonly("signers", &Signature::signers, "Return an iterator over the signers: " RST_CLASS_REF(lief.PE.SignerInfo) "", py::return_value_policy::reference) + .def("find_crt", + static_cast&) const>(&Signature::find_crt), + "Find the " RST_CLASS_REF(lief.PE.x509) " certificate according to its serial number", + py::return_value_policy::reference, + "serialno"_a) + + .def("find_crt_subject", + static_cast(&Signature::find_crt_subject), + "Find the " RST_CLASS_REF(lief.PE.x509) " certificate according to its subject", + py::return_value_policy::reference, + "subject"_a) + + .def("find_crt_subject", + static_cast&) const>(&Signature::find_crt_subject), + "Find the " RST_CLASS_REF(lief.PE.x509) " certificate according to its subject **AND** its serial number", + py::return_value_policy::reference, + "subject"_a, "serialno"_a) + + .def("find_crt_issuer", + static_cast(&Signature::find_crt_issuer), + "Find the " RST_CLASS_REF(lief.PE.x509) " certificate according to its issuer", + py::return_value_policy::reference, + "issuer"_a) + + .def("find_crt_issuer", + static_cast&) const>(&Signature::find_crt_issuer), + "Find the " RST_CLASS_REF(lief.PE.x509) " certificate according to its issuer **AND** its serial number", + py::return_value_policy::reference, + "issuer"_a, "serialno"_a) + .def("check", &Signature::check, + // Note: This documentation needs to be sync with LIEF::PE::Signature::check R"delim( Check the integrity of the signature and return a :class:`lief.PE.Signature.VERIFICATION_FLAGS` - It performs the following verifications: + By default, it performs the following verifications: 1. It must contain only **one** signer info (:attr:`~lief.PE.Signature.signers`) 2. :attr:`lief.PE.Signature.digest_algorithm` must match: @@ -125,8 +170,15 @@ void create(py::module& m) { 5. If they are Authenticated attributes, check that a PKCS9_MESSAGE_DIGEST (:class:`lief.PE.PKCS9MessageDigest`) attribute exists and that its value matches hash of ContentInfo + 6. Check the validity of the PKCS #9 counter signature if present + 7. If the signature doesn't embed a signing-time in the counter signature, check the certificate + validity. (See :attr:`lief.PE.Signature.VERIFICATION_CHECKS.LIFETIME_SIGNING` and :attr:`lief.pe.Signature.VERIFICATION_CHECKS.SKIP_CERT_TIME`) - )delim") + See: :class:`lief.PE.Signature.VERIFICATION_CHECKS` to tweak the behavior + + )delim", + "checks"_a = Signature::VERIFICATION_CHECKS::DEFAULT + ) .def_property_readonly("raw_der", [] (const Signature& sig) { diff --git a/api/python/PE/objects/signature/pySignerInfo.cpp b/api/python/PE/objects/signature/pySignerInfo.cpp index 77fd665..84f87d2 100644 --- a/api/python/PE/objects/signature/pySignerInfo.cpp +++ b/api/python/PE/objects/signature/pySignerInfo.cpp @@ -102,10 +102,34 @@ void create(py::module& m) { .def("get_attribute", &SignerInfo::get_attribute, - "Return the authenticated or un-authenticated attribute matching the " - "given " RST_CLASS_REF(lief.PE.SIG_ATTRIBUTE_TYPES) " \n\n" - "It returns **the first** entry that matches the given type. If it can't be " - "found, it returns a nullptr", + R"delim( + Return the authenticated or un-authenticated attribute matching the + given :class:`lief.PE.SIG_ATTRIBUTE_TYPES` + It returns **the first** entry that matches the given type. If it can't be + found, it returns None + )delim", + "type"_a, + py::return_value_policy::reference) + + .def("get_auth_attribute", + &SignerInfo::get_auth_attribute, + R"delim( + Return the authenticated attribute matching the + given :class:`lief.PE.SIG_ATTRIBUTE_TYPES` + It returns **the first** entry that matches the given type. If it can't be + found, it returns None + )delim", + "type"_a, + py::return_value_policy::reference) + + .def("get_unauth_attribute", + &SignerInfo::get_unauth_attribute, + R"delim( + Return the un-authenticated attribute matching the + given :class:`lief.PE.SIG_ATTRIBUTE_TYPES` + It returns **the first** entry that matches the given type. If it can't be + found, it returns a nullptr + )delim", "type"_a, py::return_value_policy::reference) diff --git a/api/python/PE/objects/signature/pyx509.cpp b/api/python/PE/objects/signature/pyx509.cpp index cefaa9d..071dd52 100644 --- a/api/python/PE/objects/signature/pyx509.cpp +++ b/api/python/PE/objects/signature/pyx509.cpp @@ -72,6 +72,17 @@ void create(py::module& m) { .value("RSA_ALT", x509::KEY_TYPES::RSA_ALT, "RSA scheme with an alternative implementation for signing and decrypting") .value("RSASSA_PSS", x509::KEY_TYPES::RSASSA_PSS, "RSA Probabilistic signature scheme"); + LIEF::enum_(cls_x509, "KEY_USAGE", "Key usage as defined in `RFC #5280 - section-4.2.1.3 `_") + .value("DIGITAL_SIGNATURE", x509::KEY_USAGE::DIGITAL_SIGNATURE, "The key is used for digital signature") + .value("NON_REPUDIATION", x509::KEY_USAGE::NON_REPUDIATION, "The key is used for digital signature AND to protects against falsely denying some action") + .value("KEY_ENCIPHERMENT", x509::KEY_USAGE::KEY_ENCIPHERMENT, "The key is used for enciphering private or secret keys") + .value("DATA_ENCIPHERMENT", x509::KEY_USAGE::DATA_ENCIPHERMENT, "The key is used for directly enciphering raw user data without the use of an intermediate symmetric cipher") + .value("KEY_AGREEMENT", x509::KEY_USAGE::KEY_AGREEMENT, "The Key is used for key agreement. (e.g. with Diffie-Hellman)") + .value("KEY_CERT_SIGN", x509::KEY_USAGE::KEY_CERT_SIGN, "The key is used for verifying signatures on public key certificates") + .value("CRL_SIGN", x509::KEY_USAGE::CRL_SIGN, "The key is used for verifying signatures on certificate revocation lists") + .value("ENCIPHER_ONLY", x509::KEY_USAGE::ENCIPHER_ONLY, "In **association with** KEY_AGREEMENT (otherwise the meaning is undefined), the key is only used for enciphering data while performing key agreement") + .value("DECIPHER_ONLY", x509::KEY_USAGE::DECIPHER_ONLY, "In **association with** KEY_AGREEMENT (otherwise the meaning is undefined), the key is only used for deciphering data while performing key agreement"); + cls_x509 .def_static("parse", static_cast(&x509::parse), @@ -137,6 +148,27 @@ void create(py::module& m) { "Otherwise, return None", py::return_value_policy::take_ownership) + .def_property_readonly("key_usage", + &x509::key_usage, + "Purpose of the key contained in the certificate (see " RST_CLASS_REF(lief.PE.x509.KEY_USAGE) ")") + + .def_property_readonly("ext_key_usage", + &x509::ext_key_usage, + "Indicates one or more purposes for which the certified public key may be used (list of OID)") + + .def_property_readonly("certificate_policies", + &x509::certificate_policies, + "Policy information terms as list of OID (see RFC #5280)") + + .def_property_readonly("is_ca", + &x509::is_ca) + + .def_property_readonly("signature", + [] (const x509& cert) -> py::bytes { + const std::vector& sig = cert.signature(); + return py::bytes(reinterpret_cast(sig.data()), sig.size()); + }, "The signature of the certificate") + .def("verify", static_cast(&x509::verify), R"delim( diff --git a/api/python/PE/pyPE.cpp b/api/python/PE/pyPE.cpp index 29a63b7..c50d648 100644 --- a/api/python/PE/pyPE.cpp +++ b/api/python/PE/pyPE.cpp @@ -41,7 +41,6 @@ void init_python_module(py::module& m) { void init_objects(py::module& m) { CREATE(Parser, m); - CREATE(Binary, m); CREATE(DosHeader, m); CREATE(Header, m); CREATE(OptionalHeader, m); @@ -103,6 +102,7 @@ void init_objects(py::module& m) { CREATE(LoadConfigurationV6, m); CREATE(LoadConfigurationV7, m); + CREATE(Binary, m); CREATE(Builder, m); } diff --git a/examples/python/pe_sign/authenticode.py b/examples/python/authenticode/api_example.py similarity index 100% rename from examples/python/pe_sign/authenticode.py rename to examples/python/authenticode/api_example.py diff --git a/examples/python/authenticode/authenticode_reader.py b/examples/python/authenticode/authenticode_reader.py new file mode 100644 index 0000000..f71b5e1 --- /dev/null +++ b/examples/python/authenticode/authenticode_reader.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python +import lief +from lief.PE import oid_to_string +import argparse +import json +import sys +import string +import argparse +import traceback +import pathlib + +try: + from prettyprinter import pprint +except ImportError: + from pprint import pprint + +class exceptions_handler(object): + func = None + + def __init__(self, exceptions, on_except_callback=None): + self.exceptions = exceptions + self.on_except_callback = on_except_callback + + def __call__(self, *args, **kwargs): + if self.func is None: + self.func = args[0] + return self + try: + return self.func(*args, **kwargs) + except self.exceptions as e: + if self.on_except_callback is not None: + self.on_except_callback(e) + else: + print("-" * 60) + print("Exception in {}: {}".format(self.func.__name__, e)) + exc_type, exc_value, exc_traceback = sys.exc_info() + traceback.print_tb(exc_traceback) + print("-" * 60) + +@exceptions_handler(Exception) +def print_attr(indent: int, auth: lief.PE.Attribute): + if auth.type == lief.PE.SIG_ATTRIBUTE_TYPES.CONTENT_TYPE: + print_content_type(indent, auth) + elif auth.type == lief.PE.SIG_ATTRIBUTE_TYPES.PKCS9_SIGNING_TIME: + print_signing_time(indent, auth) + elif auth.type == lief.PE.SIG_ATTRIBUTE_TYPES.MS_SPC_STATEMENT_TYPE: + print_ms_statement_type(indent, auth) + elif auth.type == lief.PE.SIG_ATTRIBUTE_TYPES.PKCS9_MESSAGE_DIGEST: + print_pkcs_msg_dg(indent, auth) + elif auth.type == lief.PE.SIG_ATTRIBUTE_TYPES.PKCS9_COUNTER_SIGNATURE: + print_pkcs_counter_sig(indent, auth) + elif auth.type == lief.PE.SIG_ATTRIBUTE_TYPES.GENERIC_TYPE: + print_generic_type(indent, auth) + elif auth.type == lief.PE.SIG_ATTRIBUTE_TYPES.SPC_SP_OPUS_INFO: + print_spc_sp_opus_info(indent, auth) + elif auth.type == lief.PE.SIG_ATTRIBUTE_TYPES.MS_SPC_NESTED_SIGN: + print_ms_nested_sig(indent, auth) + elif auth.type == lief.PE.SIG_ATTRIBUTE_TYPES.PKCS9_AT_SEQUENCE_NUMBER: + print_pkcs9_at_seq_number(indent, auth) + else: + print(" " * indent, type(auth), auth) + + +@exceptions_handler(Exception) +def print_pkcs9_at_seq_number(indent: int, auth: lief.PE.PKCS9AtSequenceNumber): + print("{} PKCS #9 sequence number: {}".format(" " * indent, auth.number)) + +@exceptions_handler(Exception) +def print_ms_nested_sig(indent: int, auth: lief.PE.MsSpcNestedSignature): + print("{} MS Nested Signature:".format(" " * indent)) + print_all(auth.signature, indent + 2) + +@exceptions_handler(Exception) +def print_spc_sp_opus_info(indent: int, auth: lief.PE.SpcSpOpusInfo): + if len(auth.program_name) > 0 and len(auth.more_info) > 0: + print("{} Info: {} {}".format(" " * indent, auth.program_name, auth.more_info)) + elif len(auth.program_name) > 0 and len(auth.more_info) == 0: + print("{} Info: {}".format(" " * indent, auth.program_name)) + elif len(auth.program_name) == 0 and len(auth.more_info) > 0: + print("{} Info: {}".format(" " * indent, auth.more_info)) + else: + print("{} Info: ".format(" " * indent)) + +@exceptions_handler(Exception) +def print_generic_type(indent: int, auth: lief.PE.GenericType): + print("{} Generic Type {} ({})".format(" " * indent, auth.oid, lief.PE.oid_to_string(auth.oid))) + +@exceptions_handler(Exception) +def print_content_type(indent: int, auth: lief.PE.ContentType): + print("{} Content Type OID: {} ({})".format(" " * indent, auth.oid, lief.PE.oid_to_string(auth.oid))) + +@exceptions_handler(Exception) +def print_signing_time(indent: int, auth: lief.PE.PKCS9SigningTime): + print("{} Signing Time: {}/{:02}/{:02} - {:02}:{:02}:{:02}".format(" " * indent, *auth.time)) + +@exceptions_handler(Exception) +def print_ms_statement_type(indent: int, auth: lief.PE.MsSpcStatementType): + print("{} MS Statement type OID: {} ({})".format(" " * indent, auth.oid, lief.PE.oid_to_string(auth.oid))) + +@exceptions_handler(Exception) +def print_pkcs_msg_dg(indent: int, auth: lief.PE.PKCS9MessageDigest): + print("{} PKCS9 Message Digest: {}".format(" " * indent, auth.digest.hex())) + +@exceptions_handler(Exception) +def print_crt(indent: int, crt: lief.PE.x509): + print("{} Version : {:d}".format(" " * indent, crt.version)) + print("{} Issuer : {}".format(" " * indent, crt.issuer)) + print("{} Subject : {}".format(" " * indent, crt.subject)) + print("{} Serial Number : {}".format(" " * indent, crt.serial_number.hex())) + print("{} Signature Algorithm: {}".format(" " * indent, lief.PE.oid_to_string(crt.signature_algorithm))) + print("{} Valid from : {}/{:02d}/{:02d} - {:02d}:{:02d}:{:02d}".format(" " * indent, *crt.valid_from)) + print("{} Valid to : {}/{:02d}/{:02d} - {:02d}:{:02d}:{:02d}".format(" " * indent, *crt.valid_to)) + if len(crt.key_usage) > 0: + print("{} Key usage : {}".format(" " * indent, " - ".join(str(e).split(".")[-1] for e in crt.key_usage))) + if len(crt.ext_key_usage) > 0: + print("{} Ext key usage : {}".format(" " * indent, " - ".join(lief.PE.oid_to_string(e) for e in crt.ext_key_usage))) + if crt.rsa_info is not None: + rsa_info = crt.rsa_info + print("{} RSA key size : {}".format(" " * indent, rsa_info.key_size)) + print("{} ===========================================".format(" " * indent)) + +@exceptions_handler(Exception) +def print_pkcs_counter_sig(indent: int, auth: lief.PE.PKCS9CounterSignature): + print("{} PKCS9 counter signature".format(" " * indent)) + signer = auth.signer + print("{} Version : {:d}".format(" " * indent, signer.version)) + print("{} Serial Number : {}".format(" " * indent, signer.serial_number.hex())) + print("{} Issuer : {}".format(" " * indent, signer.issuer)) + print("{} Digest Algorithm : {}".format(" " * indent, signer.digest_algorithm)) + print("{} Encryption Algorithm: {}".format(" " * indent, signer.encryption_algorithm)) + print("{} Encrypted Digest : {} ...".format(" " * indent, signer.encrypted_digest.hex()[:20])) + + if len(signer.authenticated_attributes) > 0: + print("{} Authenticated attributes:".format(" " * indent)) + for auth in signer.authenticated_attributes: + print_attr(indent + 4, auth) + + if len(signer.unauthenticated_attributes) > 0: + print("{} Un-Authenticated attributes:".format(" " * indent)) + for auth in signer.unauthenticated_attributes: + print_attr(indent + 4, auth) +@exceptions_handler(Exception) +def print_all(sig: lief.PE.Signature, indent: int = 2): + ci: lief.PE.ContentInfo = sig.content_info + print("{}Signature version : {}".format(" " * indent, sig.version)) + print("{}Digest Algorithm : {!s}".format(" " * indent, sig.digest_algorithm)) + print("{}Content Info:".format(" " * indent)) + print("{} Content Type : {!s} ({})".format(" " * indent, ci.content_type, lief.PE.oid_to_string(ci.content_type))) + print("{} Digest Algorithm: {!s}".format(" " * indent, ci.digest_algorithm)) + print("{} Digest : {!s}".format(" " * indent, ci.digest.hex())) + print("{}Certificates".format(" " * indent)) + for crt in sig.certificates: + print_crt(indent, crt) + print("{}Signer(s)".format(" " * indent)) + for signer in sig.signers: + print("{} Version : {:d}".format(" " * indent, signer.version)) + print("{} Serial Number : {}".format(" " * indent, signer.serial_number.hex())) + print("{} Issuer : {}".format(" " * indent, signer.issuer)) + print("{} Digest Algorithm : {}".format(" " * indent, signer.digest_algorithm)) + print("{} Encryption Algorithm: {}".format(" " * indent, signer.encryption_algorithm)) + print("{} Encrypted Digest : {} ...".format(" " * indent, signer.encrypted_digest.hex()[:20])) + if len(signer.authenticated_attributes) > 0: + print("{} Authenticated attributes:".format(" " * indent)) + for auth in signer.authenticated_attributes: + print_attr(indent + 4, auth) + + if len(signer.unauthenticated_attributes) > 0: + print("{} Un-authenticated attributes:".format(" " * indent)) + for auth in signer.unauthenticated_attributes: + print_attr(indent + 4, auth) + + +@exceptions_handler(Exception) +def show_crts(sig: lief.PE.Signature, args): + for crt in sig.certificates: + print_crt(0, crt) + +@exceptions_handler(Exception) +def process_signature(sig: lief.PE.Signature, args): + if args.show_all: + print_all(sig) + + if args.show_crt: + show_crts(sig, args) + + if args.show_hash: + print("Authentihash: {}".format(sig.content_info.digest.hex())) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("file") + + parser.add_argument('-a', '--all', + action='store_true', dest='show_all', + help='Show all information') + + parser.add_argument('-c', '--crt', + action='store_true', dest='show_crt', + help='Show embedded x509 certificates') + + parser.add_argument('-H', '--hash', + action='store_true', dest='show_hash', + help='Show the autentihash value') + + parser.add_argument('-C', '--check', + action='store_true', dest='check_sig', + help='Check the signature') + + parser.add_argument('-D', '--allow-expired', + action='store_true', dest='allow_expired', + help='Allow expired certificates') + + parser.add_argument('-s', '--save', + dest='ext_file_path', + help='Extract and save the PKCS #7') + + + # Logging setup + logger_group = parser.add_argument_group('Logger') + verbosity = logger_group.add_mutually_exclusive_group() + + verbosity.add_argument('--debug', + dest='main_verbosity', + action='store_const', + const=lief.logging.LOGGING_LEVEL.DEBUG) + + verbosity.add_argument('--trace', + dest='main_verbosity', + action='store_const', + const=lief.logging.LOGGING_LEVEL.TRACE) + + verbosity.add_argument('--info', + dest='main_verbosity', + action='store_const', + const=lief.logging.LOGGING_LEVEL.INFO) + + verbosity.add_argument('--warn', + dest='main_verbosity', + action='store_const', + const=lief.logging.LOGGING_LEVEL.WARNING) + + verbosity.add_argument('--err', + dest='main_verbosity', + action='store_const', + const=lief.logging.LOGGING_LEVEL.ERROR) + + verbosity.add_argument('--critical', + dest='main_verbosity', + action='store_const', + const=lief.logging.LOGGING_LEVEL.CRITICAL) + + parser.set_defaults(main_verbosity=lief.logging.LOGGING_LEVEL.WARNING) + + args = parser.parse_args() + lief.logging.set_level(args.main_verbosity) + + if lief.PE.is_pe(args.file): + binary = None + try: + binary: lief.PE.Binary = lief.PE.parse(args.file) + if binary is None: + print("Error while parsing {}".format(args.file)) + sys.exit(1) + except lief.exception as e: + print(e) + sys.exit(1) + + if args.check_sig: + flags = lief.PE.Signature.VERIFICATION_CHECKS.DEFAULT + if args.allow_expired: + flags = lief.PE.Signature.VERIFICATION_CHECKS.SKIP_CERT_TIME + res = binary.verify_signature(flags) + print(res) + + if args.show_hash: + print("Binary MD5 authentihash: {}".format(binary.authentihash_md5.hex())) + print("Binary SHA-1 authentihash: {}".format(binary.authentihash_sha1.hex())) + print("Binary SHA-256 authentihash: {}".format(binary.authentihash_sha256.hex())) + + for idx, sig in enumerate(binary.signatures): + process_signature(sig, args) + if args.ext_file_path: + path = args.ext_file_path + if idx > 0: + path += str(idx) + if not path.endswith(".p7b"): + path += ".p7b" + outpath = pathlib.Path(path) + outpath.write_bytes(sig.raw_der) + print("Signature saved to {}".format(outpath)) + else: + # Try as a regular p7b signature + sig = lief.PE.Signature.parse(args.file) + if sig is None: + print("Fail to parse the signature") + sys.exit(1) + process_signature(sig, args) + + +if __name__ == "__main__": + main() diff --git a/examples/python/pe_sign/sig_info.py b/examples/python/pe_sign/sig_info.py deleted file mode 100644 index 87e2992..0000000 --- a/examples/python/pe_sign/sig_info.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python -import lief -import argparse -import json -import sys -from prettyprinter import pprint - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("pe_file") - - # Logging setup - logger_group = parser.add_argument_group('Logger') - verbosity = logger_group.add_mutually_exclusive_group() - - verbosity.add_argument('--debug', - dest='main_verbosity', - action='store_const', - const=lief.logging.LOGGING_LEVEL.DEBUG) - - verbosity.add_argument('--trace', - dest='main_verbosity', - action='store_const', - const=lief.logging.LOGGING_LEVEL.TRACE) - - verbosity.add_argument('--info', - dest='main_verbosity', - action='store_const', - const=lief.logging.LOGGING_LEVEL.INFO) - - verbosity.add_argument('--warn', - dest='main_verbosity', - action='store_const', - const=lief.logging.LOGGING_LEVEL.WARNING) - - verbosity.add_argument('--err', - dest='main_verbosity', - action='store_const', - const=lief.logging.LOGGING_LEVEL.ERROR) - - verbosity.add_argument('--critical', - dest='main_verbosity', - action='store_const', - const=lief.logging.LOGGING_LEVEL.CRITICAL) - - parser.set_defaults(main_verbosity=lief.logging.LOGGING_LEVEL.WARNING) - - args = parser.parse_args() - lief.logging.set_level(args.main_verbosity) - - binary = None - try: - binary = lief.PE.parse(args.pe_file) - except lief.exception as e: - print(e) - sys.exit(1) - - for sig in binary.signatures: - print(sig) - -if __name__ == "__main__": - main() diff --git a/include/LIEF/BinaryStream/VectorStream.hpp b/include/LIEF/BinaryStream/VectorStream.hpp index f2b3bed..80f7b3c 100644 --- a/include/LIEF/BinaryStream/VectorStream.hpp +++ b/include/LIEF/BinaryStream/VectorStream.hpp @@ -40,6 +40,14 @@ class VectorStream : public BinaryStream { } + inline uint8_t* start() { + return this->binary_.data(); + } + + inline const uint8_t* start() const { + return this->binary_.data(); + } + inline uint8_t* end() { return this->binary_.data() + this->binary_.size(); } diff --git a/include/LIEF/PE/Binary.hpp b/include/LIEF/PE/Binary.hpp index 8cc806d..023e277 100644 --- a/include/LIEF/PE/Binary.hpp +++ b/include/LIEF/PE/Binary.hpp @@ -145,9 +145,14 @@ class LIEF_API Binary : public LIEF::Binary { it_const_signatures signatures(void) const; //! Verify the binary against the embedded signature(s) (if any) - //! Firstly, it checks that the embedded signatures are correct (c.f. Signature::check) + //! First, it checks that the embedded signatures are correct (c.f. Signature::check) //! and then it checks that the authentihash matches ContentInfo::digest - Signature::VERIFICATION_FLAGS verify_signature() const; + //! + //! One can tweak the verification process with the Signature::VERIFICATION_CHECKS flags + //! + //! @see LIEF::PE::Signature::check + Signature::VERIFICATION_FLAGS verify_signature( + Signature::VERIFICATION_CHECKS checks = Signature::VERIFICATION_CHECKS::DEFAULT) const; //! Verify the binary with the Signature object provided in the first parameter //! It can be used to verify a detached signature: @@ -158,7 +163,8 @@ class LIEF_API Binary : public LIEF::Binary { //! binary->verify_signature(detached.value()); //! } //! \endcode - Signature::VERIFICATION_FLAGS verify_signature(const Signature& sig) const; + Signature::VERIFICATION_FLAGS verify_signature(const Signature& sig, + Signature::VERIFICATION_CHECKS checks = Signature::VERIFICATION_CHECKS::DEFAULT) const; //! Compute the authentihash according to the algorithm provided in the first //! parameter diff --git a/include/LIEF/PE/signature/Signature.hpp b/include/LIEF/PE/signature/Signature.hpp index e3237d5..7d11404 100644 --- a/include/LIEF/PE/signature/Signature.hpp +++ b/include/LIEF/PE/signature/Signature.hpp @@ -45,7 +45,7 @@ class LIEF_API Signature : public Object { static std::vector hash(const std::vector& input, ALGORITHMS algo); public: - //! Flags returned by verification fonctions + //! Flags returned by verification functions enum class VERIFICATION_FLAGS { OK = 0, INVALID_SIGNER = 1 << 0, @@ -58,6 +58,18 @@ class LIEF_API Signature : public Object { BAD_DIGEST = 1 << 7, BAD_SIGNATURE = 1 << 8, NO_SIGNATURE = 1 << 9, + CERT_EXPIRED = 1 << 10, + CERT_FUTURE = 1 << 11, + }; + + //! Flags to tweak the verification process of the signature + //! + //! See Signature::check and LIEF::PE::Binary::verify_signature + enum class VERIFICATION_CHECKS { + DEFAULT = 1 << 0, /**< Default behavior that tries to follow the Microsoft verification process as close as possible */ + HASH_ONLY = 1 << 1, /**< Only check that Binary::authentihash matches ContentInfo::digest regardless of the signature's validity */ + LIFETIME_SIGNING = 1 << 2, /**< Same semantic as [WTD_LIFETIME_SIGNING_FLAG](https://docs.microsoft.com/en-us/windows/win32/api/wintrust/ns-wintrust-wintrust_data#WTD_LIFETIME_SIGNING_FLAG) */ + SKIP_CERT_TIME = 1 << 3, /**< Skip the verification of the certificates time validities so that even though a certificate expired, it returns VERIFICATION_FLAGS::OK */ }; Signature(void); @@ -86,10 +98,25 @@ class LIEF_API Signature : public Object { //! Return the raw original PKCS7 signature const std::vector& raw_der(void) const; - virtual void accept(Visitor& visitor) const override; + //! Find x509 certificate according to its serial number + const x509* find_crt(const std::vector& serialno) const; + + //! Find x509 certificate according to its subject + const x509* find_crt_subject(const std::string& subject) const; + + //! Find x509 certificate according to its subject **AND** serial number + const x509* find_crt_subject(const std::string& subject, const std::vector& serialno) const; + + //! Find x509 certificate according to its issuer + const x509* find_crt_issuer(const std::string& issuer) const; + + //! Find x509 certificate according to its issuer **AND** serial number + const x509* find_crt_issuer(const std::string& issuer, const std::vector& serialno) const; //! Check if this signature is valid according to the Authenticode/PKCS #7 verification scheme //! + //! By default, it performs the following verifications: + //! //! 1. It must contain only **one** signer info //! 2. Signature::digest_algorithm must match: //! * ContentInfo::digest_algorithm @@ -99,16 +126,22 @@ class LIEF_API Signature : public Object { //! 4. Given the x509 certificate, compare SignerInfo::encrypted_digest against either: //! * hash of authenticated attributes if present //! * hash of ContentInfo - //! 5. If they are Authenticated attributes, check that a PKCS9_MESSAGE_DIGEST attribute exists + //! 5. If authenticated attributes are present, check that a PKCS9_MESSAGE_DIGEST attribute exists //! and that its value matches hash of ContentInfo - VERIFICATION_FLAGS check() const; + //! 6. Check the validity of the PKCS #9 counter signature if present + //! 7. If the signature doesn't embed a signing-time in the counter signature, check the certificate + //! validity. (See LIEF::PE::Signature::VERIFICATION_CHECKS::LIFETIME_SIGNING and LIEF::PE::Signature::VERIFICATION_CHECKS::SKIP_CERT_TIME) + //! + //! See: LIEF::PE::Signature::VERIFICATION_CHECKS to tweak the behavior + VERIFICATION_FLAGS check(VERIFICATION_CHECKS checks = VERIFICATION_CHECKS::DEFAULT) const; + + virtual void accept(Visitor& visitor) const override; virtual ~Signature(void); LIEF_API friend std::ostream& operator<<(std::ostream& os, const Signature& signature); private: - uint32_t version_ = 0; ALGORITHMS digest_algorithm_ = ALGORITHMS::UNKNOWN; ContentInfo content_info_; @@ -118,9 +151,6 @@ class LIEF_API Signature : public Object { uint64_t content_info_start_ = 0; uint64_t content_info_end_ = 0; - uint64_t auth_start_ = 0; - uint64_t auth_end_ = 0; - std::vector original_raw_signature_; }; @@ -129,6 +159,7 @@ class LIEF_API Signature : public Object { } ENABLE_BITMASK_OPERATORS(LIEF::PE::Signature::VERIFICATION_FLAGS); +ENABLE_BITMASK_OPERATORS(LIEF::PE::Signature::VERIFICATION_CHECKS); #endif diff --git a/include/LIEF/PE/signature/SignatureParser.hpp b/include/LIEF/PE/signature/SignatureParser.hpp index 0511fb0..9a6c43c 100644 --- a/include/LIEF/PE/signature/SignatureParser.hpp +++ b/include/LIEF/PE/signature/SignatureParser.hpp @@ -68,7 +68,7 @@ class LIEF_API SignatureParser { result parse_content_info(VectorStream& stream, range_t& range); result parse_certificates(VectorStream& stream); - result parse_signer_infos(VectorStream& stream, range_t& auth_attr); + result parse_signer_infos(VectorStream& stream); result parse_attributes(VectorStream& stream); result> parse_content_type(VectorStream& stream); diff --git a/include/LIEF/PE/signature/SignerInfo.hpp b/include/LIEF/PE/signature/SignerInfo.hpp index 82a25c2..7eac60f 100644 --- a/include/LIEF/PE/signature/SignerInfo.hpp +++ b/include/LIEF/PE/signature/SignerInfo.hpp @@ -27,6 +27,7 @@ namespace LIEF { namespace PE { +class Signature; class Attribute; class Parser; class SignatureParser; @@ -52,6 +53,7 @@ class LIEF_API SignerInfo : public Object { friend class Parser; friend class SignatureParser; + friend class Signature; public: using encrypted_digest_t = std::vector; @@ -110,6 +112,18 @@ class LIEF_API SignerInfo : public Object { //! found, it returns a nullptr. const Attribute* get_attribute(PE::SIG_ATTRIBUTE_TYPES type) const; + //! Return the authenticated attribute matching the given PE::SIG_ATTRIBUTE_TYPES. + //! + //! It returns **the first** entry that matches the given type. If it can't be + //! found, it returns a nullptr. + const Attribute* get_auth_attribute(PE::SIG_ATTRIBUTE_TYPES type) const; + + //! Return the un-authenticated attribute matching the given PE::SIG_ATTRIBUTE_TYPES. + //! + //! It returns **the first** entry that matches the given type. If it can't be + //! found, it returns a nullptr. + const Attribute* get_unauth_attribute(PE::SIG_ATTRIBUTE_TYPES type) const; + //! x509 certificate used by this signer. If it can't be found, it returns a nullptr inline const x509* cert() const { return this->cert_.get(); @@ -120,6 +134,11 @@ class LIEF_API SignerInfo : public Object { return this->cert_.get(); } + //! Raw blob that is signed by the signer certificate + const std::vector raw_auth_data() const { + return this->raw_auth_data_; + } + virtual void accept(Visitor& visitor) const override; virtual ~SignerInfo(void); @@ -127,7 +146,7 @@ class LIEF_API SignerInfo : public Object { LIEF_API friend std::ostream& operator<<(std::ostream& os, const SignerInfo& signer_info); private: - uint32_t version_; + uint32_t version_ = 0; std::string issuer_; std::vector serialno_; @@ -135,11 +154,13 @@ class LIEF_API SignerInfo : public Object { ALGORITHMS digest_enc_algorithm_ = ALGORITHMS::UNKNOWN; encrypted_digest_t encrypted_digest_; + + std::vector raw_auth_data_; + std::vector> authenticated_attributes_; std::vector> unauthenticated_attributes_; std::unique_ptr cert_; - }; } diff --git a/include/LIEF/PE/signature/attributes/PKCS9CounterSignature.hpp b/include/LIEF/PE/signature/attributes/PKCS9CounterSignature.hpp index 7a76705..c65e91e 100644 --- a/include/LIEF/PE/signature/attributes/PKCS9CounterSignature.hpp +++ b/include/LIEF/PE/signature/attributes/PKCS9CounterSignature.hpp @@ -48,15 +48,15 @@ class LIEF_API PKCS9CounterSignature : public Attribute { public: PKCS9CounterSignature(); - PKCS9CounterSignature(std::vector signers); + PKCS9CounterSignature(SignerInfo signer); PKCS9CounterSignature(const PKCS9CounterSignature&); PKCS9CounterSignature& operator=(const PKCS9CounterSignature&); virtual std::unique_ptr clone(void) const override; - //! Iterator over the SignerInfo as described in the RFC - inline it_const_signers_t signers() const { - return this->signers_; + //! SignerInfo as described in the RFC #2985 + inline const SignerInfo& signer() const { + return this->signer_; } //! Print information about the attribute @@ -67,7 +67,7 @@ class LIEF_API PKCS9CounterSignature : public Attribute { virtual ~PKCS9CounterSignature(); private: - std::vector signers_; + SignerInfo signer_; }; } diff --git a/include/LIEF/PE/signature/x509.hpp b/include/LIEF/PE/signature/x509.hpp index acca9b1..0a1ee07 100644 --- a/include/LIEF/PE/signature/x509.hpp +++ b/include/LIEF/PE/signature/x509.hpp @@ -34,6 +34,7 @@ namespace PE { class Parser; class SignatureParser; +class Signature; class RsaInfo; @@ -42,6 +43,7 @@ class LIEF_API x509 : public Object { friend class Parser; friend class SignatureParser; + friend class Signature; public: //! Tuple (Year, Month, Day, Hour, Minute, Second) @@ -55,6 +57,15 @@ class LIEF_API x509 : public Object { //! Parse x500 certificate(s) from raw blob static certificates_t parse(const std::vector& content); + //! Return True if ``before`` is *before* than ``after``. False otherwise + static bool check_time(const date_t& before, const date_t& after); + + //! True if the given time is in the **past** according to the clock's system + static bool time_is_past(const date_t& to); + + //! True if the given time is in the future according to the clock's system + static bool time_is_future(const date_t& from); + //! Public key scheme enum class KEY_TYPES { NONE = 0, ///< Unknown scheme @@ -93,6 +104,19 @@ class LIEF_API x509 : public Object { BADCRL_BAD_KEY = 1 << 19, /**< The CRL is signed with an unacceptable key (eg bad curve, RSA too short). */ }; + //! Key usage as defined in [RFC #5280 - section-4.2.1.3](https://tools.ietf.org/html/rfc5280#section-4.2.1.3) + enum class KEY_USAGE { + DIGITAL_SIGNATURE = 0, /**< The key is used for digital signature */ + NON_REPUDIATION, /**< The key is used for digital signature AND to protects against falsely denying some action */ + KEY_ENCIPHERMENT, /**< The key is used for enciphering private or secret keys */ + DATA_ENCIPHERMENT, /**< The key is used for directly enciphering raw user data without the use of an intermediate symmetric cipher */ + KEY_AGREEMENT, /**< The Key is used for key agreement. (e.g. with Diffie-Hellman) */ + KEY_CERT_SIGN, /**< The key is used for verifying signatures on public key certificates */ + CRL_SIGN, /**< The key is used for verifying signatures on certificate revocation lists */ + ENCIPHER_ONLY, /**< In **association with** KEY_AGREEMENT (otherwise the meaning is undefined), the key is only used for enciphering data while performing key agreement */ + DECIPHER_ONLY, /**< In **association with** KEY_AGREEMENT (otherwise the meaning is undefined), the key is only used for deciphering data while performing key agreement */ + }; + x509(mbedtls_x509_crt* ca); x509(const x509& other); x509& operator=(x509 other); @@ -108,10 +132,10 @@ class LIEF_API x509 : public Object { oid_t signature_algorithm(void) const; //! Start time of certificate validity - x509::date_t valid_from(void) const; + date_t valid_from(void) const; //! End time of certificate validity - x509::date_t valid_to(void) const; + date_t valid_to(void) const; //! Issuer informations std::string issuer(void) const; @@ -139,6 +163,20 @@ class LIEF_API x509 : public Object { //! Verify that this certificate **is trusted** by the given CA list VERIFICATION_FLAGS is_trusted_by(const std::vector& ca) const; + //! Policy information terms as OID (see RFC #5280) + std::vector certificate_policies() const; + + //! Purpose of the key contained in the certificate + std::vector key_usage() const; + + //! Indicates one or more purposes for which the certified public key may be used (OID types) + std::vector ext_key_usage() const; + + bool is_ca() const; + + //! The signature of the certificate + std::vector signature() const; + virtual void accept(Visitor& visitor) const override; virtual ~x509(void); diff --git a/include/LIEF/enums.hpp b/include/LIEF/enums.hpp index 52f5e19..3153480 100644 --- a/include/LIEF/enums.hpp +++ b/include/LIEF/enums.hpp @@ -67,4 +67,11 @@ operator &=(Enum& lhs, Enum rhs) return lhs; } +template +typename std::enable_if::bit_mask_enabled, bool>::type +is_true(Enum e) +{ + using underlying = typename std::underlying_type::type; + return static_cast(e) > 0; +} #endif diff --git a/src/BinaryStream/VectorStream.cpp b/src/BinaryStream/VectorStream.cpp index 087490e..2867ac2 100644 --- a/src/BinaryStream/VectorStream.cpp +++ b/src/BinaryStream/VectorStream.cpp @@ -253,7 +253,9 @@ result> VectorStream::asn1_read_cert() { int ret = mbedtls_x509_crt_parse_der(ca.get(), p, /* buff len */ buff_len); if (ret != 0) { - LIEF_DEBUG("asn1_read_cert(): {:x}", ret); + std::string strerr(1024, 0); + mbedtls_strerror(ret, const_cast(strerr.data()), strerr.size()); + LIEF_DEBUG("asn1_read_cert(): {}", strerr); return make_error_code(lief_errors::read_error); } if (ca->raw.len <= 0) { diff --git a/src/PE/Binary.cpp b/src/PE/Binary.cpp index 37296b8..635bf62 100644 --- a/src/PE/Binary.cpp +++ b/src/PE/Binary.cpp @@ -1202,14 +1202,14 @@ std::vector Binary::authentihash(ALGORITHMS algo) const { return hash; } -Signature::VERIFICATION_FLAGS Binary::verify_signature() const { +Signature::VERIFICATION_FLAGS Binary::verify_signature(Signature::VERIFICATION_CHECKS checks) const { if (not this->has_signatures()) { return Signature::VERIFICATION_FLAGS::NO_SIGNATURE; } for (size_t i = 0; i < this->signatures_.size(); ++i) { const Signature& sig = this->signatures_[i]; - Signature::VERIFICATION_FLAGS flags = this->verify_signature(sig); + Signature::VERIFICATION_FLAGS flags = this->verify_signature(sig, checks); if (flags != Signature::VERIFICATION_FLAGS::OK) { LIEF_INFO("Verification failed for signature #{:d} (0b{:b})", i, static_cast(flags)); return flags; @@ -1218,11 +1218,13 @@ Signature::VERIFICATION_FLAGS Binary::verify_signature() const { return Signature::VERIFICATION_FLAGS::OK; } -Signature::VERIFICATION_FLAGS Binary::verify_signature(const Signature& sig) const { - const Signature::VERIFICATION_FLAGS value = sig.check(); - if (value != Signature::VERIFICATION_FLAGS::OK) { - LIEF_INFO("Bad signature (0b{:b})", static_cast(value)); - return value; +Signature::VERIFICATION_FLAGS Binary::verify_signature(const Signature& sig, Signature::VERIFICATION_CHECKS checks) const { + if (not is_true(checks & Signature::VERIFICATION_CHECKS::HASH_ONLY)) { + const Signature::VERIFICATION_FLAGS value = sig.check(checks); + if (value != Signature::VERIFICATION_FLAGS::OK) { + LIEF_INFO("Bad signature (0b{:b})", static_cast(value)); + return value; + } } // Check that the authentihash matches Content Info's digest diff --git a/src/PE/hash.cpp b/src/PE/hash.cpp index 16c8519..9cfd460 100644 --- a/src/PE/hash.cpp +++ b/src/PE/hash.cpp @@ -501,8 +501,7 @@ void Hash::visit(const PKCS9AtSequenceNumber& attr) { } void Hash::visit(const PKCS9CounterSignature& attr) { this->visit(*attr.as()); - it_const_signers_t signers = attr.signers(); - this->process(std::begin(signers), std::end(signers)); + this->process(attr.signer()); } void Hash::visit(const PKCS9MessageDigest& attr) { this->visit(*attr.as()); diff --git a/src/PE/json.cpp b/src/PE/json.cpp index 35d2e94..1702055 100644 --- a/src/PE/json.cpp +++ b/src/PE/json.cpp @@ -864,13 +864,9 @@ void JsonVisitor::visit(const PKCS9AtSequenceNumber& attr) { void JsonVisitor::visit(const PKCS9CounterSignature& attr) { this->visit(*attr.as()); - std::vector signers; - for (const SignerInfo& signer : attr.signers()) { - JsonVisitor visitor; - visitor(signer); - signers.emplace_back(std::move(visitor.get())); - } - this->node_["signers"] = std::move(signers); + JsonVisitor visitor; + visitor(attr.signer()); + this->node_["signer"] = std::move(visitor.get()); } void JsonVisitor::visit(const PKCS9MessageDigest& attr) { diff --git a/src/PE/signature/Signature.cpp b/src/PE/signature/Signature.cpp index 0e2e29c..8c7d802 100644 --- a/src/PE/signature/Signature.cpp +++ b/src/PE/signature/Signature.cpp @@ -27,6 +27,8 @@ #include "LIEF/PE/signature/Attribute.hpp" #include "LIEF/PE/signature/attributes.hpp" +#include + #include #include #include @@ -36,11 +38,107 @@ #include #include "mbedtls/x509_crt.h" +#include "mbedtls/x509.h" namespace LIEF { namespace PE { +inline std::string time_to_string(const x509::date_t& date) { + return fmt::format("{:d}/{:02d}/{:02d} - {:02d}:{:02d}:{:02d}", + date[0], date[1], date[2], + date[3], date[4], date[5]); +} + + +Signature::VERIFICATION_FLAGS verify_ts_counter_signature(const SignerInfo& signer, + const PKCS9CounterSignature& cs, Signature::VERIFICATION_CHECKS checks) { + LIEF_DEBUG("PKCS #9 Counter signature found"); + Signature::VERIFICATION_FLAGS flags = Signature::VERIFICATION_FLAGS::OK; + const SignerInfo& cs_signer = cs.signer(); + if (cs_signer.cert() == nullptr) { + LIEF_WARN("Can't find x509 certificate associated with Counter Signature's signer"); + return flags | Signature::VERIFICATION_FLAGS::CERT_NOT_FOUND; + } + const x509& cs_cert = *cs_signer.cert(); + const SignerInfo::encrypted_digest_t& cs_enc_digest = cs_signer.encrypted_digest(); + + std::vector cs_auth_data = cs_signer.raw_auth_data(); + // According to the RFC: + // + // "[...] The Attributes value's tag is SET OF, and the DER encoding of + // the SET OF tag, rather than of the IMPLICIT [0] tag [...]" + cs_auth_data[0] = /* SET OF */ 0x31; + const ALGORITHMS cs_digest_algo = cs_signer.digest_algorithm(); + const std::vector& cs_hash = Signature::hash(cs_auth_data, cs_digest_algo); + LIEF_DEBUG("Signed data digest: {}", hex_dump(cs_hash)); + bool check_sig = cs_cert.check_signature(cs_hash, cs_enc_digest, cs_digest_algo); + + if (not check_sig) { + LIEF_WARN("Authenticated signature (counter signature) mismatch"); + //return flags | VERIFICATION_FLAGS::BAD_SIGNATURE; + } + + + /* According to Microsoft documentation: + * The Authenticode timestamp SignerInfo structure contains the following authenticated attributes values: + * 1. ContentType (1.2.840.113549.1.9.3) is set to PKCS #7 Data (1.2.840.113549.1.7.1). + * 2. Signing Time (1.2.840.113549.1.9.5) is set to the UTC time of timestamp generation time. + * 3. 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. + */ + + // Verify 1. + const auto* content_type_data = reinterpret_cast(cs_signer.get_auth_attribute(SIG_ATTRIBUTE_TYPES::CONTENT_TYPE)); + if (content_type_data == nullptr) { + LIEF_WARN("Missing ContentType in authenticated attributes in the counter signature's signer"); + return flags | Signature::VERIFICATION_FLAGS::INVALID_SIGNER; + } + + if (content_type_data->oid() != /* PKCS #7 Data */ "1.2.840.113549.1.7.1") { + LIEF_WARN("Bad OID for ContentType in authenticated attributes in the counter signature's signer ({})", + content_type_data->oid()); + return flags | Signature::VERIFICATION_FLAGS::INVALID_SIGNER; + } + + // Verify 3. + const auto* message_dg = reinterpret_cast(cs_signer.get_auth_attribute(SIG_ATTRIBUTE_TYPES::PKCS9_MESSAGE_DIGEST)); + if (message_dg == nullptr) { + LIEF_WARN("Missing MessageDigest in authenticated attributes in the counter signature's signer"); + return flags | Signature::VERIFICATION_FLAGS::INVALID_SIGNER; + } + const std::vector& dg_value = message_dg->digest(); + const std::vector dg_cs_hash = Signature::hash(signer.encrypted_digest(), cs_digest_algo); + if (dg_value != dg_cs_hash) { + LIEF_WARN("MessageDigest mismatch with Hash(signer ED)"); + return flags | Signature::VERIFICATION_FLAGS::INVALID_SIGNER; + } + + /* + * Verify that signing's time is valid within the signer's certificate + * validity window. + */ + const auto* signing_time = reinterpret_cast(cs_signer.get_auth_attribute(SIG_ATTRIBUTE_TYPES::PKCS9_SIGNING_TIME)); + if (signing_time != nullptr and not is_true(checks & Signature::VERIFICATION_CHECKS::SKIP_CERT_TIME)) { + LIEF_DEBUG("PKCS #9 signing time found"); + PKCS9SigningTime::time_t time = signing_time->time(); + if (not x509::check_time(time, cs_cert.valid_to())) { + LIEF_WARN("Signing time: {} is above the certificate validity: {}", + time_to_string(time), time_to_string(cs_cert.valid_to())); + return flags | Signature::VERIFICATION_FLAGS::CERT_EXPIRED; + } + + if (not x509::check_time(cs_cert.valid_from(), time)) { + LIEF_WARN("Signing time: {} is below the certificate validity: {}", + time_to_string(time), time_to_string(cs_cert.valid_to())); + return flags | Signature::VERIFICATION_FLAGS::CERT_FUTURE; + } + } + return flags; +} + Signature::Signature(void) = default; Signature::Signature(const Signature&) = default; @@ -153,7 +251,7 @@ it_const_signers_t Signature::signers(void) const { return this->signers_; } -Signature::VERIFICATION_FLAGS Signature::check() const { +Signature::VERIFICATION_FLAGS Signature::check(VERIFICATION_CHECKS checks) const { // According to the Authenticode documentation, // *SignerInfos contains one SignerInfo structure* const size_t nb_signers = this->signers_.size(); @@ -197,6 +295,18 @@ Signature::VERIFICATION_FLAGS Signature::check() const { const x509& cert = *signer.cert(); const SignerInfo::encrypted_digest_t& enc_digest = signer.encrypted_digest(); + /* + * Check the following condition: + * "The signing certificate must contain either the extended key usage (EKU) + * value for code signing, or the entire certificate chain must contain no + * EKUs. The following is the EKU value for code signing" + */ + //TODO(romain) + + /* + * Verify certificate validity + */ + if (this->content_info_start_ == 0 or this->content_info_end_ == 0) { return flags | VERIFICATION_FLAGS::CORRUPTED_CONTENT_INFO; } @@ -208,19 +318,12 @@ Signature::VERIFICATION_FLAGS Signature::check() const { const std::vector content_info_hash = Signature::hash(std::move(raw_content_info), digest_algo); - if (this->auth_start_ == 0 or this->auth_end_ == 0) { - flags |= VERIFICATION_FLAGS::CORRUPTED_AUTH_DATA; - } // Copy authenticated attributes it_const_attributes_t auth_attrs = signer.authenticated_attributes(); - if (auth_attrs.size() > 0 and - (flags & VERIFICATION_FLAGS::CORRUPTED_AUTH_DATA) != VERIFICATION_FLAGS::CORRUPTED_AUTH_DATA) { - std::vector auth_data = { - std::begin(this->original_raw_signature_) + this->auth_start_, - std::begin(this->original_raw_signature_) + this->auth_end_ - }; + if (auth_attrs.size() > 0) { + std::vector auth_data = signer.raw_auth_data_; // According to the RFC: // // "[...] The Attributes value's tag is SET OF, and the DER encoding of @@ -232,6 +335,7 @@ Signature::VERIFICATION_FLAGS Signature::check() const { bool check_sig = cert.check_signature(auth_attr_hash, enc_digest, digest_algo); if (not check_sig) { + LIEF_WARN("Authenticated signature mismatch"); return flags | VERIFICATION_FLAGS::BAD_SIGNATURE; } @@ -242,6 +346,7 @@ Signature::VERIFICATION_FLAGS Signature::check() const { }); if (it_pkcs9_digest == std::end(auth_attrs)) { + LIEF_WARN("Can't find the authenticated attribute: 'pkcs9-message-digest'"); return flags | VERIFICATION_FLAGS::MISSING_PKCS9_MESSAGE_DIGEST; } @@ -250,14 +355,53 @@ Signature::VERIFICATION_FLAGS Signature::check() const { if (digest_attr.digest() != content_info_hash) { return flags | VERIFICATION_FLAGS::BAD_DIGEST; } - - return flags; + } else { + /* + * If there is no authenticated attributes, then the encrypted digested should match ENC(content_info_hash) + */ + if (not cert.check_signature(content_info_hash, enc_digest, digest_algo)) { + return flags | VERIFICATION_FLAGS::BAD_SIGNATURE; + } } + /* - * If there is no authenticated attributes, then encrypted digested should match ENC(content_info_hash) + * CounterSignature Checks */ - if (not cert.check_signature(content_info_hash, enc_digest, digest_algo)) { - return flags | VERIFICATION_FLAGS::BAD_SIGNATURE; + const auto* counter = reinterpret_cast(signer.get_unauth_attribute(SIG_ATTRIBUTE_TYPES::PKCS9_COUNTER_SIGNATURE)); + bool has_ms_counter_sig = false; + for (const Attribute& attr : signer.unauthenticated_attributes()) { + if (attr.type() == SIG_ATTRIBUTE_TYPES::GENERIC_TYPE) { + if (reinterpret_cast(attr).oid() == /* Ms-CounterSign */ "1.3.6.1.4.1.311.3.3.1") { + has_ms_counter_sig = true; + break; + } + } + } + bool timeless_signature = false; + if (counter != nullptr) { + VERIFICATION_FLAGS cs_flags = verify_ts_counter_signature(signer, *counter, checks); + if (cs_flags == VERIFICATION_FLAGS::OK) { + timeless_signature = true; + } + } else if (not timeless_signature and has_ms_counter_sig) { + timeless_signature = true; + } + bool should_check_cert_time = not timeless_signature or is_true(checks & VERIFICATION_CHECKS::LIFETIME_SIGNING); + if (is_true(checks & VERIFICATION_CHECKS::SKIP_CERT_TIME)) { + should_check_cert_time = false; + } + if (should_check_cert_time) { + /* + * Verify certificate validities + */ + if (x509::time_is_past(cert.valid_to())) { + return flags | VERIFICATION_FLAGS::CERT_EXPIRED; + } + + if (x509::time_is_future(cert.valid_from())) { + return flags | VERIFICATION_FLAGS::CERT_FUTURE; + } + } return flags; } @@ -267,6 +411,63 @@ const std::vector& Signature::raw_der(void) const { return this->original_raw_signature_; } + +const x509* Signature::find_crt(const std::vector& serialno) const { + auto it_cert = std::find_if(std::begin(this->certificates_), std::end(this->certificates_), + [&serialno] (const x509& cert) { + return cert.serial_number() == serialno; + }); + if (it_cert == std::end(this->certificates_)) { + return nullptr; + } + return &(*it_cert); +} + + +const x509* Signature::find_crt_subject(const std::string& subject) const { + auto it_cert = std::find_if(std::begin(this->certificates_), std::end(this->certificates_), + [&subject] (const x509& cert) { + return cert.subject() == subject; + }); + if (it_cert == std::end(this->certificates_)) { + return nullptr; + } + return &(*it_cert); +} + +const x509* Signature::find_crt_subject(const std::string& subject, const std::vector& serialno) const { + auto it_cert = std::find_if(std::begin(this->certificates_), std::end(this->certificates_), + [&subject, &serialno] (const x509& cert) { + return cert.subject() == subject and cert.serial_number() == serialno; + }); + if (it_cert == std::end(this->certificates_)) { + return nullptr; + } + return &(*it_cert); +} + +const x509* Signature::find_crt_issuer(const std::string& issuer) const { + auto it_cert = std::find_if(std::begin(this->certificates_), std::end(this->certificates_), + [&issuer] (const x509& cert) { + return cert.issuer() == issuer; + }); + if (it_cert == std::end(this->certificates_)) { + return nullptr; + } + return &(*it_cert); +} + +const x509* Signature::find_crt_issuer(const std::string& issuer, const std::vector& serialno) const { + auto it_cert = std::find_if(std::begin(this->certificates_), std::end(this->certificates_), + [&issuer, &serialno] (const x509& cert) { + return cert.issuer() == issuer and cert.serial_number() == serialno; + }); + if (it_cert == std::end(this->certificates_)) { + return nullptr; + } + return &(*it_cert); +} + void Signature::accept(Visitor& visitor) const { visitor.visit(*this); } @@ -342,13 +543,7 @@ inline void print_attr(it_const_attributes_t& attrs, std::ostream& os) { case SIG_ATTRIBUTE_TYPES::PKCS9_COUNTER_SIGNATURE: { const auto& ct = reinterpret_cast(attr); - it_const_signers_t signers = ct.signers(); - - if (signers.size() > 1 or signers.size() == 0) { - suffix = std::to_string(signers.size()) + " signers"; - break; - } - const SignerInfo& signer = signers[0]; + const SignerInfo& signer = ct.signer(); suffix = signer.issuer(); break; } diff --git a/src/PE/signature/SignatureParser.cpp b/src/PE/signature/SignatureParser.cpp index bed1723..fe45ab8 100644 --- a/src/PE/signature/SignatureParser.cpp +++ b/src/PE/signature/SignatureParser.cpp @@ -279,11 +279,6 @@ result SignatureParser::parse_signature() { } else { // Makes chain std::vector certs = certificates.value(); - //mbedtls_x509_crt* next = certs.back().x509_cert_; - //for (size_t i = 1; i < certs.size(); ++i) { - // certs[certs.size() - i - 1].x509_cert_->next = next; - // next = certs[certs.size() - i - 1].x509_cert_->next; - //} signature.certificates_ = std::move(certs); } } @@ -304,17 +299,12 @@ result SignatureParser::parse_signature() { // ========================================================= tag = this->stream_->asn1_read_tag(MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SET); if (tag) { - const uint64_t current_pos = this->stream_->pos(); LIEF_DEBUG("Parse pkcs7-signed-data.signer-infos offset: {:d}", this->stream_->pos()); std::vector raw_content = {this->stream_->p(), this->stream_->p() + tag.value()}; VectorStream stream{std::move(raw_content)}; this->stream_->increment_pos(raw_content.size()); - range_t auth_attr; - auto signer_info = this->parse_signer_infos(stream, auth_attr); - signature.auth_start_ = current_pos + auth_attr.start; - signature.auth_end_ = current_pos + auth_attr.end; - LIEF_DEBUG("Authenticated attributes: {} -> {}", signature.auth_start_, signature.auth_end_); + auto signer_info = this->parse_signer_infos(stream); if (not signer_info) { LIEF_INFO("Fail to parse pkcs7-signed-data.signer-infos"); } else { @@ -324,15 +314,22 @@ result SignatureParser::parse_signature() { // Tied signer info with x509 certificates for (SignerInfo& signer : signature.signers_) { - auto it_cert = std::find_if(std::begin(signature.certificates_), std::end(signature.certificates_), - [&signer] (const x509& cert) { - return cert.issuer() == signer.issuer() and cert.serial_number() == signer.serial_number(); - }); - if (it_cert == std::end(signature.certificates_)) { + const x509* crt = signature.find_crt_issuer(signer.issuer(), signer.serial_number()); + if (crt != nullptr) { + signer.cert_ = std::unique_ptr(new x509{*crt}); + } else { LIEF_INFO("Can't find x509 certificate associated with signer '{}'", signer.issuer()); - continue; } - signer.cert_ = std::unique_ptr(new x509{*it_cert}); + const auto* cs = reinterpret_cast(signer.get_attribute(SIG_ATTRIBUTE_TYPES::PKCS9_COUNTER_SIGNATURE)); + if (cs != nullptr) { + SignerInfo& cs_signer = const_cast(cs)->signer_; + const x509* crt = signature.find_crt_issuer(cs_signer.issuer(), cs_signer.serial_number()); + if (crt != nullptr) { + cs_signer.cert_ = std::unique_ptr(new x509{*crt}); + } else { + LIEF_INFO("Can't find x509 certificate associated with signer '{}'", signer.issuer()); + } + } } return signature; @@ -524,7 +521,7 @@ result SignatureParser::parse_certificates } -result SignatureParser::parse_signer_infos(VectorStream& stream, range_t& auth_attr) { +result SignatureParser::parse_signer_infos(VectorStream& stream) { const uintptr_t end_set = stream.size(); signer_infos_t infos; @@ -621,11 +618,11 @@ result SignatureParser::parse_signer_infos(Vect // Authenticated Attributes // ======================================================= { - auth_attr.start = stream.pos(); + const uint64_t auth_attr_start = stream.pos(); tag = stream.asn1_read_tag(/* authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL */ MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED); if (tag) { - auth_attr.end = stream.pos() + tag.value(); + const uint64_t auth_attr_end = stream.pos() + tag.value(); std::vector raw_authenticated_attributes = {stream.p(), stream.p() + tag.value()}; VectorStream auth_stream(std::move(raw_authenticated_attributes)); @@ -634,10 +631,9 @@ result SignatureParser::parse_signer_infos(Vect if (not authenticated_attributes) { LIEF_INFO("Fail to parse pkcs7-signed-data.signer-infos.authenticated-attributes"); } else { + signer.raw_auth_data_ = {stream.start() + auth_attr_start, stream.start() + auth_attr_end}; signer.authenticated_attributes_ = std::move(authenticated_attributes.value()); } - } else { - auth_attr.start = 0; } } @@ -672,7 +668,7 @@ result SignatureParser::parse_signer_infos(Vect stream.pos()); return enc_digest.error(); } - LIEF_DEBUG("pkcs7-signed-data.signer-infos.encrypted-digest: {}", hex_dump(enc_digest.value())); + LIEF_DEBUG("pkcs7-signed-data.signer-infos.encrypted-digest: {}", hex_dump(enc_digest.value()).substr(0, 10)); signer.encrypted_digest_ = enc_digest.value(); } @@ -775,7 +771,14 @@ result SignatureParser::parse_attributes(VectorSt if (not res) { LIEF_INFO("Can't parse pkcs9-counter-sign attribute"); } else { - attributes.emplace_back(new PKCS9CounterSignature(std::move(res.value()))); + const std::vector& signers = res.value(); + if (signers.size() == 0) { + LIEF_INFO("Can't parse signer info associated with the pkcs9-counter-sign"); + } else if (signers.size() > 1) { + LIEF_INFO("More than one signer info associated with the pkcs9-counter-sign"); + } else { + attributes.emplace_back(new PKCS9CounterSignature(std::move(signers.back()))); + } } } @@ -916,8 +919,7 @@ result SignatureParser::parse_pkcs9_counter_sig // ID pkcs-9-at-counterSignature // } LIEF_DEBUG("Parsing pkcs9-CounterSign ({} bytes)", stream.size()); - range_t auth_attr; - auto counter_sig = this->parse_signer_infos(stream, auth_attr); + auto counter_sig = this->parse_signer_infos(stream); if (not counter_sig) { LIEF_INFO("Fail to parse pkcs9-counter-signature"); return counter_sig.error(); @@ -1075,7 +1077,7 @@ result SignatureParser::parse_pkcs9_signing_time(Vector return tm.error(); } std::unique_ptr time = std::move(tm.value()); - LIEF_INFO("pkcs9-signing-time {}/{}/{}", time->day, time->mon, time->year); + LIEF_DEBUG("pkcs9-signing-time {}/{}/{}", time->day, time->mon, time->year); return SignatureParser::time_t{time->year, time->mon, time->day, time->hour, time->min, time->sec}; } diff --git a/src/PE/signature/SignerInfo.cpp b/src/PE/signature/SignerInfo.cpp index bb22656..33c1857 100644 --- a/src/PE/signature/SignerInfo.cpp +++ b/src/PE/signature/SignerInfo.cpp @@ -43,7 +43,8 @@ SignerInfo::SignerInfo(const SignerInfo& other) : serialno_{other.serialno_}, digest_algorithm_{other.digest_algorithm_}, digest_enc_algorithm_{other.digest_enc_algorithm_}, - encrypted_digest_{other.encrypted_digest_} + encrypted_digest_{other.encrypted_digest_}, + raw_auth_data_{other.raw_auth_data_} { for (const std::unique_ptr& attr : other.authenticated_attributes_) { this->authenticated_attributes_.push_back(attr->clone()); @@ -70,6 +71,7 @@ void SignerInfo::swap(SignerInfo& other) { std::swap(this->digest_algorithm_, other.digest_algorithm_); std::swap(this->digest_enc_algorithm_, other.digest_enc_algorithm_); std::swap(this->encrypted_digest_, other.encrypted_digest_); + std::swap(this->raw_auth_data_, other.raw_auth_data_); std::swap(this->authenticated_attributes_, other.authenticated_attributes_); std::swap(this->unauthenticated_attributes_, other.unauthenticated_attributes_); std::swap(this->cert_, other.cert_); @@ -110,7 +112,22 @@ it_const_attributes_t SignerInfo::unauthenticated_attributes() const { const Attribute* SignerInfo::get_attribute(PE::SIG_ATTRIBUTE_TYPES type) const { - // First look for the attribute in the authenticated ones + const Attribute* attr = this->get_auth_attribute(type); + if (attr != nullptr) { + return attr; + } + + attr = this->get_unauth_attribute(type); + + if (attr != nullptr) { + return attr; + } + + // ... not found -> return nullptr + return nullptr; +} + +const Attribute* SignerInfo::get_auth_attribute(PE::SIG_ATTRIBUTE_TYPES type) const { auto it_auth = std::find_if(std::begin(this->authenticated_attributes_), std::end(this->authenticated_attributes_), [type] (const std::unique_ptr& attr) { return attr->type() == type; @@ -118,8 +135,10 @@ const Attribute* SignerInfo::get_attribute(PE::SIG_ATTRIBUTE_TYPES type) const { if (it_auth != std::end(this->authenticated_attributes_)) { return it_auth->get(); } + return nullptr; +} - // Then in the UN-authenticated ones +const Attribute* SignerInfo::get_unauth_attribute(PE::SIG_ATTRIBUTE_TYPES type) const { auto it_uauth = std::find_if(std::begin(this->unauthenticated_attributes_), std::end(this->unauthenticated_attributes_), [type] (const std::unique_ptr& attr) { return attr->type() == type; @@ -127,11 +146,10 @@ const Attribute* SignerInfo::get_attribute(PE::SIG_ATTRIBUTE_TYPES type) const { if (it_uauth != std::end(this->unauthenticated_attributes_)) { return it_uauth->get(); } - - // ... not found -> return nullptr return nullptr; } + void SignerInfo::accept(Visitor& visitor) const { visitor.visit(*this); } diff --git a/src/PE/signature/attributes/PKCS9CounterSignature.cpp b/src/PE/signature/attributes/PKCS9CounterSignature.cpp index 0fc29f2..0de78d5 100644 --- a/src/PE/signature/attributes/PKCS9CounterSignature.cpp +++ b/src/PE/signature/attributes/PKCS9CounterSignature.cpp @@ -10,27 +10,22 @@ PKCS9CounterSignature::PKCS9CounterSignature() : PKCS9CounterSignature::PKCS9CounterSignature(const PKCS9CounterSignature&) = default; PKCS9CounterSignature& PKCS9CounterSignature::operator=(const PKCS9CounterSignature&) = default; -PKCS9CounterSignature::PKCS9CounterSignature(std::vector signers) : +PKCS9CounterSignature::PKCS9CounterSignature(SignerInfo signer) : Attribute(SIG_ATTRIBUTE_TYPES::PKCS9_COUNTER_SIGNATURE), - signers_{std::move(signers)} + signer_{std::move(signer)} {} std::unique_ptr PKCS9CounterSignature::clone(void) const { return std::unique_ptr(new PKCS9CounterSignature{*this}); } - void PKCS9CounterSignature::accept(Visitor& visitor) const { visitor.visit(*this); } std::string PKCS9CounterSignature::print() const { std::ostringstream oss; - it_const_signers_t signers = this->signers(); - oss << std::to_string(signers.size()) << " signer(s): \n"; - for (const SignerInfo& signer : signers) { - oss << signer << "\n"; - } + oss << this->signer() << "\n"; return oss.str(); } diff --git a/src/PE/signature/x509.cpp b/src/PE/signature/x509.cpp index 498caff..a86d966 100644 --- a/src/PE/signature/x509.cpp +++ b/src/PE/signature/x509.cpp @@ -32,9 +32,50 @@ #include "LIEF/PE/signature/RsaInfo.hpp" #include "LIEF/PE/EnumToString.hpp" +#include "LIEF/utils.hpp" + +namespace { + // Copy this function from mbedtls sinc it is not exported + inline int x509_get_current_time( mbedtls_x509_time *now ) + { + struct tm *lt, tm_buf; + mbedtls_time_t tt; + int ret = 0; + + tt = mbedtls_time( NULL ); + lt = mbedtls_platform_gmtime_r( &tt, &tm_buf ); + + if( lt == NULL ) + ret = -1; + else + { + now->year = lt->tm_year + 1900; + now->mon = lt->tm_mon + 1; + now->day = lt->tm_mday; + now->hour = lt->tm_hour; + now->min = lt->tm_min; + now->sec = lt->tm_sec; + } + + return( ret ); + } +} + + namespace LIEF { namespace PE { +inline x509::date_t from_mbedtls(const mbedtls_x509_time& time) { + return { + time.year, + time.mon, + time.day, + time.hour, + time.min, + time.sec + }; +} + x509::certificates_t x509::parse(const std::string& path) { std::ifstream cert_fs(path); @@ -77,6 +118,106 @@ x509::certificates_t x509::parse(const std::vector& content) { return crts; } + +bool x509::check_time(const date_t& before, const date_t& after) { + // Implementation taken + // from https://github.com/ARMmbed/mbedtls/blob/1c54b5410fd48d6bcada97e30cac417c5c7eea67/library/x509.c#L926-L962 + if (before[0] > after[0]) { + LIEF_DEBUG("{} > {}", before[0], after[0]); + return false; + } + + if ( + before[0] == after[0] and + before[1] > after[1] + ) + { + LIEF_DEBUG("{} > {}", before[1], after[1]); + return false; + } + + if ( + before[0] == after[0] and + before[1] == after[1] and + before[2] > after[2] + ) + { + LIEF_DEBUG("{} > {}", before[2], after[2]); + return false; + } + + if ( + before[0] == after[0] and + before[1] == after[1] and + before[2] == after[2] and + before[3] > after[3] + ) + { + LIEF_DEBUG("{} > {}", before[3], after[3]); + return false; + } + + if ( + before[0] == after[0] and + before[1] == after[1] and + before[2] == after[2] and + before[3] == after[3] and + before[4] > after[4] + ) + { + LIEF_DEBUG("{} > {}", before[4], after[4]); + return false; + } + + if ( + before[0] == after[0] and + before[1] == after[1] and + before[2] == after[2] and + before[3] == after[3] and + before[4] == after[4] and + before[5] > after[5] + ) + { + LIEF_DEBUG("{} > {}", before[5], after[5]); + return false; + } + + if ( + before[0] == after[0] and + before[1] == after[1] and + before[2] == after[2] and + before[3] == after[3] and + before[4] == after[4] and + before[5] == after[5] and + before[6] > after[6] + ) + { + LIEF_DEBUG("{} > {}", before[6], after[6]); + return false; + } + + return true; +} + +bool x509::time_is_past(const date_t& to) { + mbedtls_x509_time now; + + if (x509_get_current_time(&now) != 0) { + return true; + } + // check_time(): true if now < to else false + return not check_time(from_mbedtls(now), to); +} + +bool x509::time_is_future(const date_t& from) { + mbedtls_x509_time now; + + if (x509_get_current_time(&now) != 0) { + return true; + } + return check_time(from_mbedtls(now), from); +} + x509::x509() = default; x509::x509(mbedtls_x509_crt* ca) : @@ -119,25 +260,11 @@ oid_t x509::signature_algorithm(void) const { } x509::date_t x509::valid_from(void) const { - return {{ - this->x509_cert_->valid_from.year, - this->x509_cert_->valid_from.mon, - this->x509_cert_->valid_from.day, - this->x509_cert_->valid_from.hour, - this->x509_cert_->valid_from.min, - this->x509_cert_->valid_from.sec - }}; + return from_mbedtls(this->x509_cert_->valid_from); } x509::date_t x509::valid_to(void) const { - return {{ - this->x509_cert_->valid_to.year, - this->x509_cert_->valid_to.mon, - this->x509_cert_->valid_to.day, - this->x509_cert_->valid_to.hour, - this->x509_cert_->valid_to.min, - this->x509_cert_->valid_to.sec - }}; + return from_mbedtls(this->x509_cert_->valid_to); } @@ -206,17 +333,63 @@ bool x509::check_signature(const std::vector& hash, const std::vectorx509_cert_->pk; - int ret = mbedtls_pk_verify(&ctx, /* MD_HASH_ALGO */ it_md->second, /* Input Hash */ hash.data(), hash.size(), /* Signature provided */ signature.data(), signature.size()); + /* If the verification failed with mbedtls_pk_verify it + * does not necessity means that the signatures don't match. + * + * For RSA public-key scheme, mbedtls encodes the hash with rsa_rsassa_pkcs1_v15_encode() so that it expands + * the hash value with encoded data. On some samples, this encoding failed. + * + * In the approach below, we manually decrypt and unpad the output of the DEC(signature) + * as defined in the RFC #2313 + */ if (ret != 0) { - std::string strerr(1024, 0); - mbedtls_strerror(ret, const_cast(strerr.data()), strerr.size()); - LIEF_INFO("decrypt() failed with error: '{}'", strerr); - return false; + if (mbedtls_pk_get_type(&ctx) == MBEDTLS_PK_RSA) { + auto* ctx_rsa = reinterpret_cast(ctx.pk_ctx); + if ((ctx_rsa->len * 8) < 100 or (ctx_rsa->len * 8) > 2048 * 10) { + LIEF_INFO("RSA Key length is not valid ({} bits)", ctx_rsa->len * 8); + return false; + } + std::vector decrypted(ctx_rsa->len); + + int ret_rsa_public = mbedtls_rsa_public(ctx_rsa, signature.data(), decrypted.data()); + if (ret_rsa_public != 0) { + std::string strerr(1024, 0); + mbedtls_strerror(ret_rsa_public, const_cast(strerr.data()), strerr.size()); + LIEF_INFO("RSA public key operation failed: '{}'", strerr); + return false; + } + + // Check padding header + if (decrypted[0] != 0x00 and decrypted[1] != 0x01 and decrypted[2] != 0xff) { + return false; + } + + std::vector unpadded; + for (size_t i = 2; i < decrypted.size(); ++i) { + if (decrypted[i] == 0) { + unpadded = std::vector(std::begin(decrypted) + i + 1, std::end(decrypted)); + break; + } + if (decrypted[i] != 0xFF) { + return false; + } + } + if (unpadded == hash) { + return true; + } + } + if (ret != 0) { + std::string strerr(1024, 0); + mbedtls_strerror(ret, const_cast(strerr.data()), strerr.size()); + LIEF_INFO("decrypt() failed with error: '{}'", strerr); + return false; + } + return true; } return true; } @@ -228,6 +401,7 @@ x509::VERIFICATION_FLAGS x509::is_trusted_by(const std::vector& ca) const ca_list[i].x509_cert_->next = ca_list[i + 1].x509_cert_; } + VERIFICATION_FLAGS result = VERIFICATION_FLAGS::OK; uint32_t flags = 0; mbedtls_x509_crt_profile profile = { MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_MD5) | @@ -257,17 +431,19 @@ x509::VERIFICATION_FLAGS x509::is_trusted_by(const std::vector& ca) const std::string out(1024, 0); mbedtls_x509_crt_verify_info(const_cast(out.data()), out.size(), "", flags); LIEF_WARN("X509 verify failed with: {} (0x{:x})\n{}", strerr, ret, out); + result = VERIFICATION_FLAGS::BADCERT_NOT_TRUSTED; } // Clear the chain since ~x509() will delete each object for (size_t i = 0; i < ca_list.size(); ++i) { ca_list[i].x509_cert_->next = nullptr; } - return static_cast(flags); + return result; } x509::VERIFICATION_FLAGS x509::verify(const x509& ca) const { uint32_t flags = 0; + VERIFICATION_FLAGS result = VERIFICATION_FLAGS::OK; mbedtls_x509_crt_profile profile = { MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA1) | MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA224) | @@ -295,8 +471,99 @@ x509::VERIFICATION_FLAGS x509::verify(const x509& ca) const { std::string out(1024, 0); mbedtls_x509_crt_verify_info(const_cast(out.data()), out.size(), "", flags); LIEF_WARN("X509 verify failed with: {} (0x{:x})\n{}", strerr, ret, out); + result = VERIFICATION_FLAGS::BADCERT_NOT_TRUSTED; } - return static_cast(flags); + return result; +} + +std::vector x509::ext_key_usage() const { + if ((this->x509_cert_->ext_types & MBEDTLS_X509_EXT_EXTENDED_KEY_USAGE) == 0) { + return {}; + } + mbedtls_asn1_sequence* current = &this->x509_cert_->ext_key_usage; + std::vector oids; + while (current != nullptr) { + char oid_str[256] = {0}; + int ret = mbedtls_oid_get_numeric_string(oid_str, sizeof(oid_str), ¤t->buf); + if (ret != MBEDTLS_ERR_OID_BUF_TOO_SMALL) { + LIEF_DEBUG("OID: {}", oid_str); + oids.push_back(oid_str); + } else { + std::string strerr(1024, 0); + mbedtls_strerror(ret, const_cast(strerr.data()), strerr.size()); + LIEF_WARN("{}", strerr); + } + if (current->next == current) { + break; + } + current = current->next; + } + return oids; +} + +std::vector x509::certificate_policies() const { + if ((this->x509_cert_->ext_types & MBEDTLS_OID_X509_EXT_CERTIFICATE_POLICIES) == 0) { + return {}; + } + + mbedtls_x509_sequence& policies = this->x509_cert_->certificate_policies; + mbedtls_asn1_sequence* current = &policies; + std::vector oids; + while (current != nullptr) { + char oid_str[256] = {0}; + int ret = mbedtls_oid_get_numeric_string(oid_str, sizeof(oid_str), ¤t->buf); + if (ret != MBEDTLS_ERR_OID_BUF_TOO_SMALL) { + oids.push_back(oid_str); + } else { + std::string strerr(1024, 0); + mbedtls_strerror(ret, const_cast(strerr.data()), strerr.size()); + LIEF_WARN("{}", strerr); + } + if (current->next == current) { + break; + } + current = current->next; + } + return oids; +} + +bool x509::is_ca() const { + if ((this->x509_cert_->ext_types & MBEDTLS_X509_EXT_BASIC_CONSTRAINTS) == 0) { + return true; + } + return this->x509_cert_->ca_istrue; +} + +std::vector x509::key_usage() const { + static const std::map MBEDTLS_MAP = { + {MBEDTLS_X509_KU_DIGITAL_SIGNATURE, KEY_USAGE::DIGITAL_SIGNATURE}, + {MBEDTLS_X509_KU_NON_REPUDIATION, KEY_USAGE::NON_REPUDIATION}, + {MBEDTLS_X509_KU_KEY_ENCIPHERMENT, KEY_USAGE::KEY_ENCIPHERMENT}, + {MBEDTLS_X509_KU_DATA_ENCIPHERMENT, KEY_USAGE::DATA_ENCIPHERMENT}, + {MBEDTLS_X509_KU_KEY_AGREEMENT, KEY_USAGE::KEY_AGREEMENT}, + {MBEDTLS_X509_KU_KEY_CERT_SIGN, KEY_USAGE::KEY_CERT_SIGN}, + {MBEDTLS_X509_KU_CRL_SIGN, KEY_USAGE::CRL_SIGN}, + {MBEDTLS_X509_KU_ENCIPHER_ONLY, KEY_USAGE::ENCIPHER_ONLY}, + {MBEDTLS_X509_KU_DECIPHER_ONLY, KEY_USAGE::DECIPHER_ONLY}, + }; + + if ((this->x509_cert_->ext_types & MBEDTLS_X509_EXT_KEY_USAGE) == 0) { + return {}; + } + + const uint32_t ku = this->x509_cert_->key_usage; + std::vector usages; + for (const auto& p : MBEDTLS_MAP) { + if ((ku & p.first) > 0) { + usages.push_back(p.second); + } + } + return usages; +} + +std::vector x509::signature() const { + mbedtls_x509_buf sig = this->x509_cert_->sig; + return {sig.p, sig.p + sig.len}; } void x509::accept(Visitor& visitor) const { diff --git a/tests/pe/test_authenticode.py b/tests/pe/test_authenticode.py index e4ab66e..678bf7d 100644 --- a/tests/pe/test_authenticode.py +++ b/tests/pe/test_authenticode.py @@ -14,7 +14,7 @@ from unittest import TestCase import lief from utils import get_sample -lief.logging.set_level(lief.logging.LOGGING_LEVEL.INFO) +lief.logging.set_level(lief.logging.LOGGING_LEVEL.WARNING) def from_hex(x): return bytes.fromhex(x.replace(":", "")) @@ -180,8 +180,7 @@ class TestAuthenticode(TestCase): sig = lief.PE.Signature.parse(get_sample("pkcs7/cert10.p7b")) counter_sign = sig.signers[0].get_attribute(lief.PE.SIG_ATTRIBUTE_TYPES.PKCS9_COUNTER_SIGNATURE) - self.assertEqual(len(counter_sign.signers), 1) - signer = counter_sign.signers[0] + signer = counter_sign.signer self.assertEqual(signer.version, 1) self.assertEqual(signer.serial_number, from_hex("0e:cf:f4:38:c8:fe:bf:35:6e:04:d8:6a:98:1b:1a:50"))