-
-
Notifications
You must be signed in to change notification settings - Fork 111
Description
I'm working on generating a signed XML document where the element (and related elements like , , etc.) must not include the ds: prefix. This is because the legacy application I’m integrating with expects the XML Digital Signature block to be present without any namespace prefixes—only the Signature tag and children in the correct namespace (i.e., http://www.w3.org/2000/09/xmldsig#), but without the ds: prefix.
I’m using signxml to generate the signature. Signing works fine when I override the namespace as:
signer.namespaces = {None: signxml.namespaces.ds}
This gives me the desired XML structure (no ds:), and the output looks valid. However, when I try to verify the signature using signxml.XMLVerifier().verify(...)
, it fails. The same test passes if I don't override the namespace and let SignXML add the ds: prefix.
Test Result
self = <signxml.verifier.XMLVerifier object at 0xffffadce0c50>, element = <Element {http://www.w3.org/2000/09/xmldsig#}Reference at 0xffffacd2e9c0>, query = 'DigestMethod'
require = True, xpath = ''
def _find(self, element, query, require=True, xpath=""):
namespace = "ds"
if ":" in query:
namespace, _, query = query.partition(":")
result = element.find(f"{xpath}{namespace}:{query}", namespaces=namespaces)
if require and result is None:
> raise InvalidInput(f"Expected to find XML element {query} in {element.tag}")
E signxml.exceptions.InvalidInput: Expected to find XML element DigestMethod in {http://www.w3.org/2000/09/xmldsig#}Reference
/py/lib/python3.12/site-packages/signxml/processor.py:98: InvalidInput
================================================================================ short test summary info ================================================================================
FAILED test_signxml.py::test_signed_without_ns_success - signxml.exceptions.InvalidInput: Expected to find XML element DigestMethod in {http://www.w3.org/2000/09/xmldsig#}Reference
============================================================================== 1 failed, 1 passed in 0.21s ==============================================================================
Test Code
"""Test Certificate SetUp
.. code-block:: bash
openssl genrsa -out private.key 4096
openssl req -new -key private.key -out request.csr
openssl x509 -req -days 365 -in request.csr -signkey private.key -out certificate.crt
"""
from xml.etree.ElementTree import Element, SubElement, tostring
import lxml
import signxml
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
PRIVATE_KEY_PATH = "/app/private.key"
CERTIFICATE_PATH = "/app/certificate.crt"
def load_cert_pub(cert_path: str):
with open(cert_path, "rb") as cert_file:
cert_data = cert_file.read()
cert = x509.load_pem_x509_certificate(cert_data, default_backend())
return cert, cert.public_key()
def load_private_key(key_path: str, password=None):
with open(key_path, "rb") as key_file:
key_data = key_file.read()
key = serialization.load_pem_private_key(
key_data, password=password, backend=default_backend()
)
return key
def create_sample_xml():
root = Element("ROOT")
# Section Data
data_sec = SubElement(root, "DATA")
pid_elem = SubElement(data_sec, "ID")
pid_elem.text = "encrypted_some_data_data"
session_key_elem = SubElement(data_sec, "SESSION_KEY")
session_key_elem.text = "encrypted_session_key"
return tostring(root, encoding="unicode")
private_key = load_private_key(PRIVATE_KEY_PATH)
cert, public_key = load_cert_pub(CERTIFICATE_PATH)
def test_signed_with_default_ns_success():
xml_str = create_sample_xml()
root = lxml.etree.fromstring(xml_str.encode("utf-8"))
signer = signxml.XMLSigner(
method=signxml.methods.enveloped,
signature_algorithm="rsa-sha256",
digest_algorithm="sha256",
c14n_algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
)
signed_root = signer.sign(
root,
key=private_key,
exclude_c14n_transform_element=True,
)
signed_xml_str = lxml.etree.tostring(signed_root, encoding="utf-8")
# Verifying
root = lxml.etree.fromstring(signed_xml_str)
result = signxml.XMLVerifier().verify(root, x509_cert=cert)
# No exceptions, so this is okay...
def test_signed_without_ns_success():
# This test is failing...
xml_str = create_sample_xml()
root = lxml.etree.fromstring(xml_str.encode("utf-8"))
signer = signxml.XMLSigner(
method=signxml.methods.enveloped,
signature_algorithm="rsa-sha256",
digest_algorithm="sha256",
c14n_algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
)
signer.namespaces = {None: signxml.namespaces.ds}
# signer.signature_annotators = [] # remove key info from payload
signed_root = signer.sign(
root,
key=private_key,
exclude_c14n_transform_element=True,
)
signed_xml_str = lxml.etree.tostring(signed_root, encoding="utf-8")
# Verifying
root = lxml.etree.fromstring(signed_xml_str)
# This going to raise:
# signxml.exceptions.InvalidInput: Expected to find XML element DigestMethod in {http://www.w3.org/2000/09/xmldsig#}Reference
result = signxml.XMLVerifier().verify(root, x509_cert=cert)
Side Note:
I’m new to XML digital signatures, and trying to get this working based on the requirements of the external (legacy) application. If I’m missing something or doing in a wrong way, I'd appreciate your help on the correct approach.