4

I found Xades Signature for Python GitHub. My plan is to apply Xades-EPES signature to XML files. According to the work from GitHub, it is capable to perform this process, but I could not make this run.

test_xades.py has two methods. I receive a error message when I try to run it. Well, the issue is that I am not sure about if the lib can sign Xades-EPES or how to achieve it.

Thank you in advance


CODE:

import unittest
from datetime import datetime
from os import path

from OpenSSL import crypto

import xmlsig
from xades import XAdESContext, template, utils, ObjectIdentifier
from xades.policy import GenericPolicyId, ImpliedPolicy
from basex import parse_xml, BASE_DIR 

class TestXadesSignature(unittest.TestCase):
    def test_verify(self):
        root = parse_xml('data/sample.xml')
        sign = root.xpath(
            '//ds:Signature', namespaces={'ds': xmlsig.constants.DSigNs}
        )[0]
        ctx = XAdESContext()
        ctx.verify(sign)

    def test_sign(self):
        root = parse_xml('data/unsigned-sample.xml')
        sign = root.xpath(
            '//ds:Signature', namespaces={'ds': xmlsig.constants.DSigNs}
        )[0]
        policy = GenericPolicyId(
            'http://www.facturae.es/politica_de_firma_formato_facturae/'
            'politica_de_firma_formato_facturae_v3_1.pdf',
            u"Politica de Firma FacturaE v3.1",
            xmlsig.constants.TransformSha1
        )
        ctx = XAdESContext(policy)
        with open(path.join(BASE_DIR, "data/keyStore.p12"), "rb") as key_file:
            ctx.load_pkcs12(crypto.load_pkcs12(key_file.read()))
        ctx.sign(sign)
        ctx.verify(sign)

    def test_create(self):
        root = parse_xml('data/free-sample.xml')
        signature = xmlsig.template.create(
            xmlsig.constants.TransformInclC14N,
            xmlsig.constants.TransformRsaSha1,
            "Signature"
        )
        signature_id = utils.get_unique_id()
        ref = xmlsig.template.add_reference(
            signature, xmlsig.constants.TransformSha1, uri="", name="REF"
        )
        xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped)
        xmlsig.template.add_reference(
            signature, xmlsig.constants.TransformSha1, uri="#KI"
        )
        xmlsig.template.add_reference(
            signature, xmlsig.constants.TransformSha1, uri="#" + signature_id
        )
        ki = xmlsig.template.ensure_key_info(signature, name='KI')
        data = xmlsig.template.add_x509_data(ki)
        xmlsig.template.x509_data_add_certificate(data)
        serial = xmlsig.template.x509_data_add_issuer_serial(data)
        xmlsig.template.x509_issuer_serial_add_issuer_name(serial)
        xmlsig.template.x509_issuer_serial_add_serial_number(serial)
        xmlsig.template.add_key_value(ki)
        qualifying = template.create_qualifying_properties(
            signature, name=utils.get_unique_id()
        )
        props = template.create_signed_properties(
            qualifying, name=signature_id
        )
        template.add_claimed_role(props, "Supp2")
        template.add_production_place(props, city='Madrid')
        template.add_production_place(
            props, state='BCN', postal_code='08000', country='ES')
        template.add_claimed_role(props, "Supp")
        policy = GenericPolicyId(
            'http://www.facturae.es/politica_de_firma_formato_facturae/'
            'politica_de_firma_formato_facturae_v3_1.pdf',
            u"Politica de Firma FacturaE v3.1",
            xmlsig.constants.TransformSha1
        )
        root.append(signature)
        ctx = XAdESContext(policy)
        with open(path.join(BASE_DIR, "data/keyStore.p12"), "rb") as key_file:
            ctx.load_pkcs12(crypto.load_pkcs12(key_file.read()))
        ctx.sign(signature)
        ctx.verify(signature)

    def test_create_2(self):
        root = parse_xml('data/free-sample.xml')
        signature = xmlsig.template.create(
            xmlsig.constants.TransformInclC14N,
            xmlsig.constants.TransformRsaSha1,
            "Signature"
        )
        ref = xmlsig.template.add_reference(
            signature, xmlsig.constants.TransformSha1, uri="", name="R1"
        )
        xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped)
        xmlsig.template.add_reference(
            signature, xmlsig.constants.TransformSha1, uri="#KI", name="RKI"
        )
        ki = xmlsig.template.ensure_key_info(signature, name='KI')
        data = xmlsig.template.add_x509_data(ki)
        xmlsig.template.x509_data_add_certificate(data)
        serial = xmlsig.template.x509_data_add_issuer_serial(data)
        xmlsig.template.x509_issuer_serial_add_issuer_name(serial)
        xmlsig.template.x509_issuer_serial_add_serial_number(serial)
        xmlsig.template.add_key_value(ki)
        qualifying = template.create_qualifying_properties(signature)
        utils.ensure_id(qualifying)
        utils.ensure_id(qualifying)
        props = template.create_signed_properties(
            qualifying, datetime=datetime.now()
        )
        template.add_claimed_role(props, "Supp")
        signed_do = template.ensure_signed_data_object_properties(props)
        template.add_data_object_format(
            signed_do,
            "#R1",
            identifier=ObjectIdentifier("Idenfitier0", "Description")
        )
        template.add_commitment_type_indication(
            signed_do,
            ObjectIdentifier("Idenfitier0", "Description"),
            qualifiers_type=["Tipo"]
        )

        template.add_commitment_type_indication(
            signed_do,
            ObjectIdentifier("Idenfitier1", references=["#R1"]),
            references=["#R1"]
        )
        template.add_data_object_format(
            signed_do,
            "#RKI",
            description="Desc",
            mime_type="application/xml",
            encoding='UTF-8'
        )
        root.append(signature)
        ctx = XAdESContext(ImpliedPolicy(xmlsig.constants.TransformSha1))
        with open(path.join(BASE_DIR, "data/keyStore.p12"), "rb") as key_file:
            ctx.load_pkcs12(crypto.load_pkcs12(key_file.read()))
        ctx.sign(signature)
        from lxml import etree
        print(etree.tostring(root))
        ctx.verify(signature)

x= TestXadesSignature()
x.test_create()
x.test_create_2()

TRACEBACK:

Exception has occurred: StopIteration
    exception: no description
      File "/home/sergio/Escritorio/PROYECTOSMAY2018/haciendaPython/src/lxml/lxml.etree.pyx", line 2821, in lxml.etree._ElementMatchIterator.__next__ (src/lxml/lxml.etree.c:75265)
      File "/home/sergio/Escritorio/PROYECTOSMAY2018/haciendaPython/haciendaCode/pythoncorella/test/test_xades.py", line 50, in test_create
        xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped)
      File "/home/sergio/Escritorio/PROYECTOSMAY2018/haciendaPython/haciendaCode/pythoncorella/test/test_xades.py", line 150, in <module>
        x.test_create()
      File "/usr/lib/python3.6/runpy.py", line 85, in _run_code
        exec(code, run_globals)
      File "/usr/lib/python3.6/runpy.py", line 96, in _run_module_code
        mod_name, mod_spec, pkg_name, script_name)
      File "/usr/lib/python3.6/runpy.py", line 263, in run_path
        pkg_name=pkg_name, script_name=fname)
jww
  • 97,681
  • 90
  • 411
  • 885

1 Answers1

5

Use this imports

import xmlsig
from lxml import etree
from OpenSSL import crypto
from xades import XAdESContext, template, utils
from xades.policy import GenericPolicyId

GLOBAL IS THIS

POLICY_ENDPOINT = "politicadefirma/v2/politicadefirmav2.pdf"
SIGN_POLICY = f"http://facturaelectronica.dian.gov.co/{POLICY_ENDPOINT}"
CERTICAMARA_PFX = os.environ.get(
    'CERTICAMARA_PFX',
    '/path/to/certificate.pfx')
  1. you need to parse the xml file you want to sign

    parsed_file = etree.parse(file_path).getroot()

  2. Create Signature template with the corresponding Transform stuff

    signature = xmlsig.template.create(
        xmlsig.constants.TransformInclC14N,
        xmlsig.constants.TransformRsaSha256,
        "Signature",
    )
    
    
  3. create an uuid and the reference to the signature

    signature_id = utils.get_unique_id()
    ref = xmlsig.template.add_reference(
        signature, xmlsig.constants.TransformSha256, uri="", name="REF"
    )
  1. Create transform for the signature reference
    xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped)
  1. Add the other references
   xmlsig.template.add_reference(
        signature, xmlsig.constants.TransformSha256, uri="#" + signature_id
    )

   xmlsig.template.add_reference(
        signature, xmlsig.constants.TransformSha256, uri="#" + signature_id
    )
  1. add the part where the certificate is going to be incorporated
    ki = xmlsig.template.ensure_key_info(signature, name="KI")
    data = xmlsig.template.add_x509_data(ki)
    xmlsig.template.x509_data_add_certificate(data)
    serial = xmlsig.template.x509_data_add_issuer_serial(data)
    xmlsig.template.x509_issuer_serial_add_issuer_name(serial)
    xmlsig.template.x509_issuer_serial_add_serial_number(serial)
    xmlsig.template.add_key_value(ki)
    qualifying = template.create_qualifying_properties(
        signature, name=utils.get_unique_id(), etsi='xades'
    )
  1. Add any additional data por the signature
    props = template.create_signed_properties(qualifying, name=signature_id)
    # Additional data for signature
    template.add_claimed_role(props, "Supp2")
    template.add_production_place(props, city="Bogotá Colombia")
    template.add_production_place(
        props, state="BCN", postal_code="08000", country="CO"
    )
    template.add_claimed_role(props, "SNE")
  1. Add policy info
    policy = GenericPolicyId(
        SIGN_POLICY,
        u"Política de firma para facturas"
        " electrónicas de la República de Colombia",
        xmlsig.constants.TransformSha256,
    )
  1. Append the signature to the parsed document
   parsed_file.append(signature)
  1. Open the .pfx or .pem, this has to contain both key and cert
   with open(CERTICAMARA_ANDES_PFX, "rb") as key_file:
        certificate = crypto.load_pkcs12(key_file.read(), 'filepassword')
  1. Add certificate and policy to ctx for signig in the next step
    ctx = XAdESContext(
        policy,
        certificate.get_certificate().to_cryptography(),
    )
  1. Load the certificate to the ctx and perform the signing
   ctx.load_pkcs12(certificate)
   ctx.sign(signature)
  1. Move the signature to the desired position, you can changed to fit your pourposes.
    parsed_file[0][position][0].append(signature) 
  1. Create the new xml signed file
    et = etree.ElementTree(parsed_file)
    
    nfs_name = 'Name of the signed file'
    et.write(nfs_name, pretty_print=True,
             encoding='utf-8', xml_declaration=True)

I used this for a project for electronic invoice signing in my country, take into account that the source code of Enric Tobella is ahead compared to the package. With the source code you can add multiple certificates whereas the package only let you use one, that was the only difference I found.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Misa K
  • 51
  • 1
  • 5
  • 1
    Thanks friend. You are so gentle. I will give a try to your answer. I worked so hard on this project. I need to resume the development. – Sergio Ramirez May 31 '21 at 02:01