I was recently experimenting some more with my iOS MDM server, and found that I needed to verify inbound signatures on the messages the clients send to the server. It took some doing, but eventually I found the right way to handle it at the command line.

I had to take the signature (in this case, provided as a base-64 string in the HTTP header), decode it, and save it to a file. Then I needed a copy of the public key for the certificate used to sign the message, and finally, I had to copy the text of the message itself to another file. Once all that was done, it was something like this:

openssl smime -verify -in <sig> -inform der -content <msg>  -certfile <signer cert>  -CAfile <ca cert>

This then spits out a copy of the text, and the message “Verification successful.” Very simple, though it took a while to get it exactly right. (I may have been using the wrong signer certificate for some time, though….)

So this is all well and good, but how do I do this programatically? In my server? Should be easy, right? Just find a good OpenSSL library for Python, and Bob’s your Uncle!

Turns out, that’s the hard part–finding a good OpenSSL library for Python. PyOpenSSL was abandoned for a while, but recently revived. The other library I found, M2Crypto, was a bit more arcane and also fairly dated. But in the end, I did figure out how to get M2Crypto to work. Here’s the code, in a nutshell:

from M2Crypto import SMIME, X509, BIO

raw_sig = ""
msg = """"""

sm_obj = SMIME.SMIME()
x509 = X509.load_cert('signer.crt') # public key cert used by the remote
                                    # client when signing the message
sk = X509.X509_Stack()
sk.push(x509)
sm_obj.set_x509_stack(sk)

st = X509.X509_Store()
st.load_info('CA.crt')              # Public cert for the CA which signed
                                    # the above certificate
sm_obj.set_x509_store(st)

# re-wrap signature so that it fits base64 standards
cooked_sig = '\n'.join(raw_sig[pos:pos+76] for pos in xrange(0, len(raw_sig), 76))  

# now, wrap the signature in a PKCS7 block
sig = """
-----BEGIN PKCS7-----
%s
-----END PKCS7-----
""" % cooked_sig

# and load it into an SMIME p7 object through the BIO I/O buffer:
buf = BIO.MemoryBuffer(sig)
p7 = SMIME.load_pkcs7_bio(buf)

# do the same for the message text
data_bio = BIO.MemoryBuffer(msg)

# finally, try to verify it:
try:
    v = sm_obj.verify(p7, data_bio)
    if v:
        print "Client signature verified."
except:
    print "*** INVALID CLIENT MESSAGE SIGNATURE ***"

This seemed to work just fine, though it did take a few tweaks to get exactly right. One thing that I found useful was to verify that I was at least getting the same message hash. Adding the following code to my test script (in the appropriate places), I could then see what it thought my message hashed to:

import binascii
from M2Crypto import EVP

md = EVP.MessageDigest('sha1')
md.update(msg)

print binascii.b2a_hex(md.digest())

Then, I’d look at the .der signature for the hash inside it:

openssl asn1parse -in sig.der -inform der

and look for the “HEX DUMP” string right after the “:messageDigest” header. If that matched what the script showed, then I knew I at least had the content properly processed, and any remaining problems were probably with the certificate. If they didn’t match, then I apparently hadn’t properly read the content in the first place, and no amount of fiddling with PKI would yield a positive result.

In the end, though it wasn’t nearly as simple as “verify(sig, text, ca)” I was still able to get it working reliably, and eventually added it to my server’s bag of tricks.