Search code examples
pythonopensslportable-executablepyopensslpython-cryptography

Extract Software Signing Cert using Python from a PE File


When trying to extract the cert from a PE file using cryptography, it fails with ValueError: Unable to load certificate. I am able to properly extract the cert from the same PE file using subprocess and the openssl command line. I want to understand what is going wrong in the version of the code that uses cryptography.

I'm using Python 3.7.1, cryptography 2.4.2, and pefile 2018.8.8

import pefile
from cryptography import x509
from cryptography.hazmat.backends import default_backend

pe = pefile.PE(fname)
pe.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']])
sigoff = 0
siglen = 0
for s in pe.__structures__:
    if s.name == 'IMAGE_DIRECTORY_ENTRY_SECURITY':
        sigoff = s.VirtualAddress
        siglen = s.Size
pe.close()
with open(fname, 'rb') as fh:
    fh.seek(sigoff)
    thesig = fh.read(siglen)
cert = x509.load_der_x509_certificate(thesig[8:], default_backend())

This fails with ValueError: Unable to load certificate


Solution

  • the problem is that the signature is a PKCS7 object. MS has documented it in a Word. I have not found a PDF version yet...

    So you need to parse the PKCS7 object first. I use asn1crypto for this.

    This works for me:

    import pefile
    from cryptography import x509
    from cryptography.hazmat.backends import default_backend
    
    from asn1crypto import cms
    
    pe = pefile.PE(fname)
    sigoff = pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_SECURITY"]].VirtualAddress
    siglen = pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_SECURITY"]].Size
    pe.close()
    
    with open(fname, 'rb') as fh:
        fh.seek(sigoff)
        thesig = fh.read(siglen)
    
    signature = cms.ContentInfo.load(thesig[8:])
    
    for cert in signature["content"]["certificates"]:
        parsed_cert = x509.load_der_x509_certificate(cert.dump(), default_backend())
        print(parsed_cert)