Question

I need to implement authentication in python from a 3rd party by using SAML2. I have looked into pysaml2 and found that to be quite confusing, and decided to give M2Crypto a chance after I found this question by Ennael.

The SAML token I receive can be found here. 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 from 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?

Was it helpful?

Solution

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()

OTHER TIPS

You need to canonicalize the signed info before validating the signature. That's what the transformation tag implies. Basically, since the same XML can be formatted differently, one needs to validate an XML signature in a canonical format.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top