3

Decoding an incoming email in Python, I have an attachment "smime.p7s". If I write this to a file, then it can be extracted and viewed using

openssl pkcs7 -inform der -print_certs <smime.p7s

I'd like to do that in Python. There's an example here of the inverse process, i.e. how to sign a mail.

Looking at the OpenSSL API documentation there is an entry point PKCS7_get0_signers which seems to do this.

Here's the code snippet I'm trying, based on a naive reworking of the signing code.

with open(fname, 'wb') as p7sfile:
    p7sfile.write(sig)
    pkcs7 = crypto._lib.PKCS7_get0_signers(sig, None, 0)

It doesn't work - giving

pkcs7 = crypto._lib.PKCS7_get0_signers(sig, None, 0)
TypeError: initializer for ctype 'PKCS7 *' must be a cdata pointer, not bytes

The function seems to require three parameters, although maybe flags is optional?

This line of code (from the older M2Crypto library) also suggests that entry point needs three parameters.

I don't understand why it would need a "certs.stack" as an input param when we are trying to extract the certs, and I don't understand what to put in "flags".

I'm pretty sure I need some specially typed buffer declarations to set up the call, and also retrieve the results (like the bio_in = crypto._new_mem_buf(data) preamble in 1). Can someone please suggest how to do it?

Also - the M2Crypto library is not compatible with Python 3.x, hence looking for an alternative.

tuck1s
  • 1,002
  • 9
  • 28
  • Possible duplicate of [Extract userCertificate from PKCS7 envelop in python](https://stackoverflow.com/questions/13379846/extract-usercertificate-from-pkcs7-envelop-in-python) – stovfl Nov 22 '18 at 18:23
  • Yes, that is related. but uses the older `M2Crypto` library which isn't Python 3.x compatible. I found a code snippet using the PyOpenSSL library below which I'll propose as a self-answer. – tuck1s Nov 23 '18 at 12:25

1 Answers1

1

I found a useful code snippet here. This extracts certs from a PKCS7 binary object into a list of OpenSSL.crypto.X509 objects.

The OpenSSL.crypto.X509 object is OK for dumping out the certificate contents (it has a dump_certificate method), but the attributes are hard to work with as they are still ASN.1 encoded and are C types.

Once you've got a list of certs, each can be converted into a cryptography Certificate object which is Python native and more amenable. For example:

class Cert(object):
    """
    Convenient container object for human-readable and output-file friendly certificate contents
    """
    pem = ''
    email_signer = None
    startT = None
    endT = None
    issuer = {}
    algorithm = None


def extract_smime_signature(payload):
    """
    Extract public certificates from the PKCS7 binary payload

    :param payload: bytes
    :return: list of Cert objects
    """
    pkcs7 = crypto.load_pkcs7_data(crypto.FILETYPE_ASN1, payload)
    certs = get_certificates(pkcs7)
    certList = []
    # Collect the following info from the certificates
    all_cert_times_valid = True
    for c in certs:
        # Convert to the modern & easier to use https://cryptography.io library objects
        c2 = crypto.X509.to_cryptography(c)
        c3 = Cert()

        # check each certificate's time validity, ANDing cumulatively across each one
        c3.startT = c2.not_valid_before
        c3.endT = c2.not_valid_after
        now = datetime.now()
        all_cert_times_valid = all_cert_times_valid and (c3.startT <= now) and (now <= c3.endT)

        # get Issuer, unpacking the ASN.1 structure into a dict
        for i in c2.issuer.rdns:
            for j in i:
                c3.issuer[j.oid._name] = j.value

        # get email address from the cert "subject" - consider more than one address in the bundle as an error
        for i in c2.subject.rdns:
            for j in i:
                attrName = j.oid._name
                if attrName == 'emailAddress':
                    c3.email_signer = j.value

        # Get hash alg - just for interest
        c3.algorithm = c2.signature_hash_algorithm.name
        c3.pem = c2.public_bytes(serialization.Encoding.PEM).decode('utf8')
        certList.append(c3)
    return certList
tuck1s
  • 1,002
  • 9
  • 28
  • The checks on the certs made in this code are overly simplistic. With access to a trusted cert bundle (such as the file "ca-bundle.crt" available in many Linuxes) it's possible to do much better than this. It's work in progress, but see https://github.com/tuck1s/sparkySecure/blob/master/readSMIMEsig.py – tuck1s Dec 11 '18 at 22:59