Signing in XMLSecurity based saml2
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)
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.