Problem generating and verifying signature with signxml in python (namespaces and signature)

623 Views Asked by At

sorry for my english firstly. I'm taking my first steps with signxml in Python, and from the articles I've seen it looks quite easy to use, but I haven't been able to correctly sign the document as I need it.

I was looking at the documentation and some articles on the web, and among the problems I was having when signing a document with signxml, I found an article at https://technotes.shemyak.com/posts/xml-signatures-with-python-elementtree/. So I tried to use my data with this structure.

ET.register_namespace("ds", "http://www.w3.org/2000/09/xmldsig#")

xml_con_semilla = ET.fromstring("<getToken><item><Semilla>000009574333</Semilla></item><ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" Id=\"placeholder\"></ds:Signature></getToken>")

Here they explain some of the difficulties at the time of signing and verification. Using the method on this website I got the correct signature and verification even of the serialized data at the end of the steps, but when I send the xml to the service I need, it gives me problems. I suppose it must be because it does not have the structure that is requested.

The structure that the service is waiting for is the following

<?xml version="1.0"?>
<getToken>
    <item>
        <Semilla>000009574333</Semilla>
    </item>
    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
            <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
            <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
            <Reference URI="">
                <Transforms>
                    <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                </Transforms>
                <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                <DigestValue>XX...</DigestValue>
            </Reference>
        </SignedInfo>
        <SignatureValue>XX...</SignatureValue>
        <KeyInfo>
            <KeyValue>
                <RSAKeyValue>
                    <Modulus>XX...</Modulus>
                    <Exponent>AQAB</Exponent>
                </RSAKeyValue>
            </KeyValue>
            <X509Data>
                <X509Certificate>XX...</X509Certificate>
            </X509Data>
        </KeyInfo>
    </Signature>
</getToken>

So I'm doing a clean try, from an XML generated with etree, since I think the above solution doesn't satisfy what I need.

import xml.etree.ElementTree as ET
import signxml

semilla = '000009574333'
xml_semilla = ET.Element('getToken')
item = ET.SubElement(xml_semilla, 'item')
ET.SubElement(item, 'Semilla').text = semilla

>>> <getToken><item><Semilla>000009574333</Semilla></item></getToken>

semilla_xml_firmada = FirmarXMLSemilla(xml_semilla)
def FirmarXMLSemilla(xml_semilla):
 
    pem_cert, pem_key = [open(f, "rb").read() for f in ("cert.pem", "key.pem")]

    signer = XMLSigner(method=signxml.methods.enveloped,
                       signature_algorithm='rsa-sha1',
                       c14n_algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315',
                       digest_algorithm="sha1")

    signed_root = signer.sign(xml_semilla, key=pem_key, cert=pem_cert, always_add_key_value=True)

    try:
        verified_data = XMLVerifier().verify(signed_root, x509_cert=pem_cert)
        print('Documento Firmado Correctamente')
    except Exception as e:
        logging.error('Error de Verificación en la Firma o Respuesta SAML: {}'.format(e))

Up to this point, without serializing, the signature is correct

VerifyResult(signed_data=b'<getToken><item><Semilla>000009574333</Semilla></item></getToken>', signed_xml=<Element getToken at 0x24cd69e65c0>, signature_xml=<Element {http://www.w3.org/2000/09/xmldsig#}Signature at 0x24cd69e6640>)
    data_serialized = ET.tostring(signed_root)
data_serialized =
<getToken xmlns:ns0="http://www.w3.org/2000/09/xmldsig#">
    <item>
        <Semilla>000009574333</Semilla>
    </item>
    <ns0:Signature>
        <ns0:SignedInfo>
            <ns0:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
            <ns0:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
            <ns0:Reference URI="">
                <ns0:Transforms>
                    <ns0:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
                </ns0:Transforms>
                <ns0:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
                <ns0:DigestValue>XX...</ns0:DigestValue>
            </ns0:Reference>
        </ns0:SignedInfo>
        <ns0:SignatureValue>XX...</ns0:SignatureValue>
        <ns0:KeyInfo>
            <ns0:KeyValue>
                <ns0:RSAKeyValue>
                    <ns0:Modulus>XX...</ns0:Modulus>
                    <ns0:Exponent>AQAB</ns0:Exponent>
                </ns0:RSAKeyValue>
            </ns0:KeyValue>
            <ns0:X509Data>
                <ns0:X509Certificate>XX...</ns0:X509Certificate>
            </ns0:X509Data>
        </ns0:KeyInfo>
    </ns0:Signature>
</getToken>
    # Sending the data...
    data_parsed = ET.fromstring(data_serialized)

    try:
        verified_data = XMLVerifier().verify(data_parsed, x509_cert=pem_cert)
        print('Documento Firmado Correctamente')
        logging.debug(verified_data)
    except Exception as e:
        logging.error('Error de Verificación en la Firma o Respuesta SAML: {}'.format(e))
ERROR:root:Error de Verificación en la Firma o Respuesta SAML: Signature verification failed: bad signature

As you will notice, when converting to a string, namespaces are added to the data, which breaks the signature. In addition, the "ns0:" prefixes are added.

I need xmlns="http://www.w3.org/2000/09/xmldsig#" to be added to Signature, not getToken

I also tried Python signxml XML signature package. How to add xml placehoder for Signature tag? but it also gives me problems.

I am using signxml version 3.0.1, so that it will allow me to use SHA-1.

Thanks!

1

There are 1 best solutions below

0
ignacio Muriel On

I had the same problem. It seems like the issue is the way you are saving your XML. If you use etree it should do the trick.

        with open('key.pem', 'rb') as key_file:
            private_key_data = key_file.read()

        private_key = serialization.load_pem_private_key(
            private_key_data,
            password=None,  # Add your passphrase if the key is encrypted
            backend=default_backend()
        )
        with open('cer.rec.pem', 'rb') as cert_file:
            certificate = cert_file.read()
        tree = ET.parse('path_to_xml.xml')
        root = tree.getroot()

        signer = XMLSigner(
            method=methods.enveloped,
            c14n_algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments'
        )
        # signer.sign_alg = SignatureMethod('http://www.w3.org/2000/09/xmldsig#rsa-sha1')
        signer.namespaces = {None: namespaces.ds}

        # Pass the private key object and certificate bytes to the signer
        signed_xml = signer.sign(
            root,
            key=private_key,
            # cert=certificate,
            always_add_key_value=True,
        )
        # XMLVerifier().verify(signed_xml)
        
        final_signed_xml = etree.ElementTree(signed_xml)
        final_signed_xml.write('signed_xml.xml', encoding='UTF-8', xml_declaration=False)

also, with the signer.namespaces = {None: namespaces.ds} you should be able to eliminate namespaces.