pysaml2 icon indicating copy to clipboard operation
pysaml2 copied to clipboard

Signing in XMLSecurity based saml2

Open clabru opened this issue 2 years ago • 1 comments

Code Version

Version 7.4.1

Expected Behavior

We're implementing a SP under Windows. Since using xmlsec1 version under Windows gives us a lot of troubles, we were trying to use, as a crypto backend, "XMLSecurity". At least for signing an AuthnRequest, it should work.

Current Behavior

We're getting

....  
  File "C:\Users\xxxx\Anaconda3\envs\Portal\lib\site-packages\saml2\client.py", line 72, in prepare_for_authenticate
    reqid, negotiated_binding, info = self.prepare_for_negotiated_authenticate(
  File "C:\Users\xxxx\Anaconda3\envs\Portal\lib\site-packages\saml2\client.py", line 167, in prepare_for_negotiated_authenticate
    http_info = self.apply_binding(
  File "C:\Users\xxxx\Anaconda3\envs\Portal\lib\site-packages\saml2\entity.py", line 286, in apply_binding
    info = http_redirect_message(
  File "C:\Users\xxxx\Anaconda3\envs\Portal\lib\site-packages\saml2\pack.py", line 175, in http_redirect_message
    signer = backend.get_signer(sigalg) if sign and sigalg else None
AttributeError: 'NoneType' object has no attribute 'get_signer'

Possible Solution

It seems that the sec_backend of the sec object is not initialized in case of "XMLSecurity"

In sigver.py, in function security_context (line 927 in my version), if we move the block of code:

    _file_name = conf.getattr("key_file", "")
    if _file_name:
        try:
            rsa_key = import_rsa_key_from_file(_file_name)
        except Exception as err:
            logger.error(f"Cannot import key from {_file_name}: {err}")
            raise
        else:
            sec_backend = RSACrypto(rsa_key)

(at lines 962-970 in my version), outside the if-elif-else, the signing works fine.

As a recap, if we change from

    if conf.crypto_backend == "xmlsec1":
        xmlsec_binary = conf.xmlsec_binary

        if not xmlsec_binary:
            try:
                _path = conf.xmlsec_path
            except AttributeError:
                _path = []
            xmlsec_binary = get_xmlsec_binary(_path)

        # verify that xmlsec is where it's supposed to be
        if not os.path.exists(xmlsec_binary):
            # if not os.access(, os.F_OK):
            err_msg = "xmlsec binary not found: {binary}"
            err_msg = err_msg.format(binary=xmlsec_binary)
            raise SigverError(err_msg)

        crypto = _get_xmlsec_cryptobackend(xmlsec_binary, delete_tmpfiles=conf.delete_tmpfiles)

        _file_name = conf.getattr("key_file", "")
        if _file_name:
            try:
                rsa_key = import_rsa_key_from_file(_file_name)
            except Exception as err:
                logger.error(f"Cannot import key from {_file_name}: {err}")
                raise
            else:
                sec_backend = RSACrypto(rsa_key)

    elif conf.crypto_backend == "XMLSecurity":
        # new and somewhat untested pyXMLSecurity crypto backend.
        crypto = CryptoBackendXMLSecurity()

    else:
        err_msg = "Unknown crypto_backend {backend}"
        err_msg = err_msg.format(backend=conf.crypto_backend)
        raise SigverError(err_msg)

to

    if conf.crypto_backend == "xmlsec1":
        xmlsec_binary = conf.xmlsec_binary

        if not xmlsec_binary:
            try:
                _path = conf.xmlsec_path
            except AttributeError:
                _path = []
            xmlsec_binary = get_xmlsec_binary(_path)

        # verify that xmlsec is where it's supposed to be
        if not os.path.exists(xmlsec_binary):
            # if not os.access(, os.F_OK):
            err_msg = "xmlsec binary not found: {binary}"
            err_msg = err_msg.format(binary=xmlsec_binary)
            raise SigverError(err_msg)

        crypto = _get_xmlsec_cryptobackend(xmlsec_binary, delete_tmpfiles=conf.delete_tmpfiles)

    elif conf.crypto_backend == "XMLSecurity":
        # new and somewhat untested pyXMLSecurity crypto backend.
        crypto = CryptoBackendXMLSecurity()

    else:
        err_msg = "Unknown crypto_backend {backend}"
        err_msg = err_msg.format(backend=conf.crypto_backend)
        raise SigverError(err_msg)
    
    _file_name = conf.getattr("key_file", "")
    if _file_name:
        try:
            rsa_key = import_rsa_key_from_file(_file_name)
        except Exception as err:
            logger.error(f"Cannot import key from {_file_name}: {err}")
            raise
        else:
            sec_backend = RSACrypto(rsa_key)

we are able to have XMLSecurity to wotk correctly (for signing, at least)

clabru avatar Mar 30 '23 08:03 clabru

Also, to be able to check signed assertions, I had to:

  • Add
        if not isinstance(signedtext, bytes):
            signedtext = signedtext.encode("utf-8")

in the validate_signature method of the CryptoBackendXMLSecurity object. Those lines are already there in the equivalent method of the CryptoBackendXmlSec1 object

  • Set 'delete_tmpfiles': False in the configuration, otherwise windows was getting a "Permission denied" error when trying to get the temporary PEM file extracted from the assertion.

clabru avatar Mar 30 '23 09:03 clabru