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.