SAML signature verification using Python/M2Crypto

2019-03-02 18:33发布

I'm attempting to use M2Crypto to verify a signature contained in an XML response returned from my SSO/SAML provider in my django/python app, but I can't seem to get it to work.

My XML response looks sort of like the second example here.

ETA: And here's a pastebin of my actual XML.

I'm using some code like this to attempt the verification:

def verify_signature(signed_info, cert, signature):
    from M2Crypto import EVP, RSA, X509

    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(signature.decode('base64'))
    result = verify_EVP.verify_final(signed_info)

    return result

I can successfully get the NameID from the response, and I know I'm successfully loading the certificate, because I can pull the issuer, etc. out of it.

As for the signature, though, I've tried hashing the passed in XML, encoding/not encoding various pieces, and passing in various bits of XML for the signed_info parameter (the SignedInfo tag, the Response tag, the whole thing), and I've tried using ElementTree/ElementC14N.py to ensure the XML is exclusively canonicalized, as the Transform implies should be done, but I'm not getting a positive result.

What am I missing here? Am I trying to validate against the wrong XML? Something wrong with my verification technique?

2条回答
一纸荒年 Trace。
2楼-- · 2019-03-02 19:17

FYI, I was facing the same problem as you, and found no useful software for validating XML signatures in Python, so I wrote a new library: https://github.com/kislyuk/signxml.

from lxml import etree
from signxml import xmldsig

with open("saml2_idp_metadata.xml", "rb") as fh:
    cert = etree.parse(fh).find("//ds:X509Certificate").text

root = ElementTree.fromstring(signature_data)
xmldsig(root).verify(x509_cert=cert)
查看更多
ら.Afraid
3楼-- · 2019-03-02 19:23

You were so close! You should pass to verify_update the signed_info, and then to verify_final pass the signature.

You do need to make sure that your signed_info is correctly canonicalized before verifying the signature.

Here is the correct method:

def verify_signature(signed_info, cert, signature):
    from M2Crypto import EVP, RSA, X509

    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)
    result = verify_EVP.verify_final(signature.decode('base64'))

    return result
查看更多
登录 后发表回答