Validating SAML signature in python
Question: I need to implement authentication in python from a 3rd party by using SAML2.
I have looked into [pysaml2](https://pypi.python.org/pypi/pysaml2/1.1.0) and
found that to be quite confusing, and decided to give
[M2Crypto](https://pypi.python.org/pypi/M2Crypto) a chance after I found [this
question](http://stackoverflow.com/questions/9704919/saml-signature-
verification-using-python-m2crypto) by
[Ennael](http://stackoverflow.com/users/518959/ennael).

The SAML token I receive [can be found here](http://pastebin.com/vehrUdkB). I
have already extracted all the information I need from the `Assertion` tag
(the user's SSN, IP and the SAML tokens expiration window) but I can't get the
`verify_signature` function from Ennael (and the [revised
code](http://stackoverflow.com/questions/9704919/saml-signature-verification-
using-python-m2crypto/17372895#17372895) from [Ezra
Nugroho](http://stackoverflow.com/users/2464942/ezra-nugroho)) to return True.
I have also tried to change `verify_EVP.reset_context(md='sha1')` to
`verify_EVP.reset_context(md='sha256')` but that didn't work either.

I think my mistake must be in the signed_info part. What do I pass to
`verify_signature` for that part? Do I have to preprocess it in any way? I
have been looking into the Transform tag but don't know where too look next.

Any help will be greatly appreciated. If someone needs the XML before the
obfuscation to test and help me just PM me.

**EDIT** This is my code (very similar to the things i linked to. The main
function is at the bottom):

    
    
    def verify_signature(signed_info, cert, signature):
        from M2Crypto import EVP, RSA, X509, m2
        x509 = X509.load_cert_string(base64.decodestring(cert), X509.FORMAT_DER)
        pubkey = x509.get_pubkey().get_rsa()
        verify_EVP = EVP.PKey()
        verify_EVP.assign_rsa(pubkey)
        verify_EVP.reset_context(md='sha1')
        verify_EVP.verify_init()
        verify_EVP.verify_update(signed_info)
    
        return verify_EVP.verify_final(signature.decode('base64'))
    
    def decode_response(resp):
        return base64.b64decode(resp)
    
    def get_xmldoc(xmlstring):
        return XML(xmlstring)
    
    
    def get_signature(doc):
        return doc.find('{http://www.w3.org/2000/09/xmldsig#}Signature')
    
    
    def get_signed_info(signature):
        signed_info = signature.find(
            '{http://www.w3.org/2000/09/xmldsig#}SignedInfo')
        signed_info_str = tostring(signed_info)
        # return parse(StringIO(signed_info_str))
        return signed_info_str
    
    
    def get_cert(signature):
        ns = '{http://www.w3.org/2000/09/xmldsig#}'
        keyinfo = signature.find('{}KeyInfo'.format(ns))
        keydata = keyinfo.find('{}X509Data'.format(ns))
        certelem = keydata.find('{}X509Certificate'.format(ns))
        return certelem.text
    
    
    def get_signature_value(signature):
        return signature.find(
            '{http://www.w3.org/2000/09/xmldsig#}SignatureValue').text
    
    def parse_saml(saml):
        dec_resp = decode_response(saml)
        xml = get_xmldoc(dec_resp)
    
        signature = get_signature(xml)
        signed_info = get_signed_info(signature)
        cert = get_cert(signature)
        signature_value = get_signature_value(signature)
    
        is_valid = verify_signature(signed_info, cert, signature_value)
    

**UPDATE** : Is it possible I need some more information from the 3rd party
authentication provider? Do I need a private key for any of this?


Answer: I faced the same problem, and had to develop a module for it:
<https://github.com/kislyuk/signxml>. I chose to rely only on PyCrypto and
pyOpenSSL, since M2Crypto is less popular and not well-maintained, which is a
hazard from both compatibility (e.g. PyPy) and security perspectives. I also
use lxml for the canonicalization (c14n). From the signxml docs:

    
    
    from signxml import xmldsig
    
    cert = open("example.pem").read()
    key = open("example.key").read()
    root = ElementTree.fromstring(data)
    xmldsig(root).verify()
    

