python - xmlsec reference with URI - Stack Overflow

admin2025-04-17  2

I'm using the XMLSec library to sign a file. I need to use the specific algorithms listed below and include the invoice identification number in the reference URI. However, when the URI parameter is filled, the document is not signed, but if I leave it empty, the signature is generated successfully. I've already checked the ID, and everything seems correct.

Example ID: #NFCom43253123827184000163620017659731731076965182

Problem?

ref = xmlsec.template.add_reference(
        signature, xmlsec.constants.TransformSha1, uri=f"#{id_nfcom}"
    )

def assinar_xml(xml_tree, certificado_pem):
    root = xml_tree.getroot()
    
    inf_nfcom = root.find(".//{}infNFCom")
    if inf_nfcom is None:
        raise ValueError("Elemento <infNFCom> não encontrado no XML.")

    id_nfcom = inf_nfcom.get("Id")
    if not id_nfcom:
        raise ValueError("Atributo 'Id' não encontrado no elemento <infNFCom>.")

    signature = xmlsec.template.create(
        root, xmlsec.Transform.C14N, xmlsec.Transform.RSA_SHA1
    )
    
    **ref = xmlsec.template.add_reference(
        signature, xmlsec.constants.TransformSha1, uri=f"#{id_nfcom}"
    )**

    xmlsec.template.add_transform(ref, xmlsec.Transform.ENVELOPED)
    xmlsec.template.add_transform(ref, xmlsec.Transform.C14N)

    key_info = xmlsec.template.ensure_key_info(signature)
    x509_data = xmlsec.template.add_x509_data(key_info)
    xmlsec.template.x509_data_add_certificate(x509_data)

    root.append(signature)
    
    ctx = xmlsec.SignatureContext()
    
    key = None
    try:
        key = xmlsec.Key.from_file(certificado_pem, xmlsec.constants.KeyDataFormatPem)
        print("Chave carregada com sucesso!")
    except Exception as e:
        print(f"Erro ao carregar a chave: {e}")

    if key is None:
        raise ValueError("A chave não foi carregada corretamente.")

    ctx.key = key

    ctx.sign(signature)
    
    return xml_tree

# xml BEFORE SIGNATURE #

<NFCom xmlns=";>
  <infNFCom Id="NFCom43253123827184000163620017659731731076965182" versao="1.00">
    <ide>
      <cUF>43</cUF>
      <tpAmb>2</tpAmb>
      <mod>62</mod>
      <serie>1</serie>
      <nNF>765973173</nNF>
      <cNF>7696518</cNF>
      <cDV>2</cDV>
      <dhEmi>2025-01-31T11:56:18-03:00</dhEmi>
      <tpEmis>1</tpEmis>
      <nSiteAutoriz>0</nSiteAutoriz>
      <cMunFG>3205309</cMunFG>
      <finNFCom>0</finNFCom>
      <tpFat>0</tpFat>
      <verProc>1.00</verProc>
    </ide>
    <emit>
      <CNPJ>23827184005463</CNPJ>
      <IE>9071339235</IE>
      <IEUFDest>321</IEUFDest>
      <CRT>1</CRT>
      <xNome>TELECOM LTDA ME</xNome>
      <xFant>Telecom</xFant>
      <enderEmit>
        <xLgr>AV. PRESIDENTE KENNEDY</xLgr>
        <nro>845</nro>
        <xBairro>Centro</xBairro>
        <cMun>4117909</cMun>
        <xMun>Palotina</xMun>
        <CEP>85950000</CEP>
        <UF>PR</UF>
      </enderEmit>
    </emit>
    <dest>
      <xNome>Cliente de Telecomunicações Ltda</xNome>
      <CNPJ>98765432000187</CNPJ>
      <indIEDest>1</indIEDest>
      <IE></IE>
      <IM>1</IM>
      <enderDest>
        <xLgr>Rua das Comunicações</xLgr>
        <nro>123</nro>
        <xBairro>Centro</xBairro>
        <cMun>3205309</cMun>
        <xMun>Vitória</xMun>
        <CEP>85950000</CEP>
        <UF>ES</UF>
      </enderDest>
    </dest>
    <assinante>
      <iCodAssinante>01</iCodAssinante>
      <tpAssinante>1</tpAssinante>
      <tpServUtil>4</tpServUtil>
    </assinante>
    <det nItem="1">
      <prod>
        <cProd>001</cProd>
        <xProd>Serviço de Internet Banda Larga</xProd>
        <cClass>0400401</cClass>
        <CFOP>5303</CFOP>
        <uMed>2</uMed>
        <qFaturada>100.00</qFaturada>
        <vItem>100.00</vItem>
        <vProd>100.00</vProd>
      </prod>
      <imposto>
        <ICMS00>
          <CST>00</CST>
          <vBC>10.00</vBC>
          <pICMS>18.00</pICMS>
          <vICMS>1.80</vICMS>
        </ICMS00>
      </imposto>
    </det>
    <total>
      <vProd>100.00</vProd>
      <ICMSTot>
        <vBC>10.00</vBC>
        <vICMS>1.80</vICMS>
        <vICMSDeson>1.00</vICMSDeson>
        <vFCP>1.00</vFCP>
      </ICMSTot>
      <vCOFINS>1.00</vCOFINS>
      <vPIS>1.00</vPIS>
      <vFUNTTEL>1.00</vFUNTTEL>
      <vFUST>1.00</vFUST>
      <vRetTribTot>
        <vRetPIS>1.00</vRetPIS>
        <vRetCofins>1.00</vRetCofins>
        <vRetCSLL>1.00</vRetCSLL>
        <vIRRF>1.00</vIRRF>
      </vRetTribTot>
      <vDesc>0.00</vDesc>
      <vOutro>1.00</vOutro>
      <vNF>100.00</vNF>
    </total>
  </infNFCom>
  <infNFComSupl>
    <qrCodNFCom>;amp;tpAmb=2</qrCodNFCom>
  </infNFComSupl>
</NFCom>


I'm using the XMLSec library to sign a file. I need to use the specific algorithms listed below and include the invoice identification number in the reference URI. However, when the URI parameter is filled, the document is not signed, but if I leave it empty, the signature is generated successfully. I've already checked the ID, and everything seems correct.

Example ID: #NFCom43253123827184000163620017659731731076965182

Problem?

ref = xmlsec.template.add_reference(
        signature, xmlsec.constants.TransformSha1, uri=f"#{id_nfcom}"
    )

def assinar_xml(xml_tree, certificado_pem):
    root = xml_tree.getroot()
    
    inf_nfcom = root.find(".//{http://www.portalfiscal.inf.br/NFCom}infNFCom")
    if inf_nfcom is None:
        raise ValueError("Elemento <infNFCom> não encontrado no XML.")

    id_nfcom = inf_nfcom.get("Id")
    if not id_nfcom:
        raise ValueError("Atributo 'Id' não encontrado no elemento <infNFCom>.")

    signature = xmlsec.template.create(
        root, xmlsec.Transform.C14N, xmlsec.Transform.RSA_SHA1
    )
    
    **ref = xmlsec.template.add_reference(
        signature, xmlsec.constants.TransformSha1, uri=f"#{id_nfcom}"
    )**

    xmlsec.template.add_transform(ref, xmlsec.Transform.ENVELOPED)
    xmlsec.template.add_transform(ref, xmlsec.Transform.C14N)

    key_info = xmlsec.template.ensure_key_info(signature)
    x509_data = xmlsec.template.add_x509_data(key_info)
    xmlsec.template.x509_data_add_certificate(x509_data)

    root.append(signature)
    
    ctx = xmlsec.SignatureContext()
    
    key = None
    try:
        key = xmlsec.Key.from_file(certificado_pem, xmlsec.constants.KeyDataFormatPem)
        print("Chave carregada com sucesso!")
    except Exception as e:
        print(f"Erro ao carregar a chave: {e}")

    if key is None:
        raise ValueError("A chave não foi carregada corretamente.")

    ctx.key = key

    ctx.sign(signature)
    
    return xml_tree

# xml BEFORE SIGNATURE #

<NFCom xmlns="http://www.portalfiscal.inf.br/NFCom">
  <infNFCom Id="NFCom43253123827184000163620017659731731076965182" versao="1.00">
    <ide>
      <cUF>43</cUF>
      <tpAmb>2</tpAmb>
      <mod>62</mod>
      <serie>1</serie>
      <nNF>765973173</nNF>
      <cNF>7696518</cNF>
      <cDV>2</cDV>
      <dhEmi>2025-01-31T11:56:18-03:00</dhEmi>
      <tpEmis>1</tpEmis>
      <nSiteAutoriz>0</nSiteAutoriz>
      <cMunFG>3205309</cMunFG>
      <finNFCom>0</finNFCom>
      <tpFat>0</tpFat>
      <verProc>1.00</verProc>
    </ide>
    <emit>
      <CNPJ>23827184005463</CNPJ>
      <IE>9071339235</IE>
      <IEUFDest>321</IEUFDest>
      <CRT>1</CRT>
      <xNome>TELECOM LTDA ME</xNome>
      <xFant>Telecom</xFant>
      <enderEmit>
        <xLgr>AV. PRESIDENTE KENNEDY</xLgr>
        <nro>845</nro>
        <xBairro>Centro</xBairro>
        <cMun>4117909</cMun>
        <xMun>Palotina</xMun>
        <CEP>85950000</CEP>
        <UF>PR</UF>
      </enderEmit>
    </emit>
    <dest>
      <xNome>Cliente de Telecomunicações Ltda</xNome>
      <CNPJ>98765432000187</CNPJ>
      <indIEDest>1</indIEDest>
      <IE></IE>
      <IM>1</IM>
      <enderDest>
        <xLgr>Rua das Comunicações</xLgr>
        <nro>123</nro>
        <xBairro>Centro</xBairro>
        <cMun>3205309</cMun>
        <xMun>Vitória</xMun>
        <CEP>85950000</CEP>
        <UF>ES</UF>
      </enderDest>
    </dest>
    <assinante>
      <iCodAssinante>01</iCodAssinante>
      <tpAssinante>1</tpAssinante>
      <tpServUtil>4</tpServUtil>
    </assinante>
    <det nItem="1">
      <prod>
        <cProd>001</cProd>
        <xProd>Serviço de Internet Banda Larga</xProd>
        <cClass>0400401</cClass>
        <CFOP>5303</CFOP>
        <uMed>2</uMed>
        <qFaturada>100.00</qFaturada>
        <vItem>100.00</vItem>
        <vProd>100.00</vProd>
      </prod>
      <imposto>
        <ICMS00>
          <CST>00</CST>
          <vBC>10.00</vBC>
          <pICMS>18.00</pICMS>
          <vICMS>1.80</vICMS>
        </ICMS00>
      </imposto>
    </det>
    <total>
      <vProd>100.00</vProd>
      <ICMSTot>
        <vBC>10.00</vBC>
        <vICMS>1.80</vICMS>
        <vICMSDeson>1.00</vICMSDeson>
        <vFCP>1.00</vFCP>
      </ICMSTot>
      <vCOFINS>1.00</vCOFINS>
      <vPIS>1.00</vPIS>
      <vFUNTTEL>1.00</vFUNTTEL>
      <vFUST>1.00</vFUST>
      <vRetTribTot>
        <vRetPIS>1.00</vRetPIS>
        <vRetCofins>1.00</vRetCofins>
        <vRetCSLL>1.00</vRetCSLL>
        <vIRRF>1.00</vIRRF>
      </vRetTribTot>
      <vDesc>0.00</vDesc>
      <vOutro>1.00</vOutro>
      <vNF>100.00</vNF>
    </total>
  </infNFCom>
  <infNFComSupl>
    <qrCodNFCom>http://dfe-portal.svrs.rs.gov.br/NFCom/QRCode?chNFCom=43120201234567890123456789012345678901234567&amp;tpAmb=2</qrCodNFCom>
  </infNFComSupl>
</NFCom>


Share Improve this question edited Feb 3 at 11:28 Renan Akira Escribano asked Jan 31 at 16:29 Renan Akira EscribanoRenan Akira Escribano 11 bronze badge 3
  • You should share a minimal working example. Please add your import statements and an xml example. Where do you load the private key? – Hermann12 Commented Jan 31 at 18:51
  • Have you read the doc with the examples? Maybe your xml use namespace? – Hermann12 Commented Feb 1 at 10:15
  • I have added an example of an XML to be signed, already in the required format. – Renan Akira Escribano Commented Feb 3 at 11:29
Add a comment  | 

1 Answer 1

Reset to default 0

If you have such an XML template:

<?xml version="1.0" encoding="utf-8"?>
<NFCom xmlns="http://www.portalfiscal.inf.br/NFCom" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
  <infNFCom Id="NFCom43253123827184000163620017659731731076965182" versao="1.00">
    ...
  </infNFCom>
  <ds:Signature />
  <infNFComSupl>
    <qrCodNFCom>http://dfe-portal.svrs.rs.gov.br/NFCom/QRCode?chNFCom=43120201234567890123456789012345678901234567&amp;tpAmb=2</qrCodNFCom>
  </infNFComSupl>
</NFCom>

And you use xmlsec with pip install xmlsec and OpenSSL with pip install pyOpenSSL you can sign <infNFCom> part with:

import os
from lxml import etree
import xmlsec
from OpenSSL import crypto
import random

PRIVATE_KEY_FILE = "private_key.pem"
PUBLIC_KEY_FILE = "public_key.pem"
CERTIFICATE_FILE = "certificate.pem"
XML_FILE_TO_SIGN = "nfcom_to_sign.xml"
SIGNED_XML_FILE = "signed_invoice.xml"

NS = {
    "nfcom": "http://www.portalfiscal.inf.br/NFCom",
    "ds": "http://www.w3.org/2000/09/xmldsig#"
}

def generate_rsa_key_and_certificate():
    if not os.path.exists(PRIVATE_KEY_FILE) or not os.path.exists(CERTIFICATE_FILE):
        private_key = crypto.PKey()
        private_key.generate_key(crypto.TYPE_RSA, 2048)

        cert = crypto.X509()
        cert.set_version(2)
        cert.set_serial_number(random.getrandbits(64))

        subject = cert.get_subject()
        subject.CN = "example.com"
        subject.O = "Example"
        subject.L = "Los Angeles"
        subject.ST = "California"
        subject.C = "US"

        cert.set_issuer(subject)
        cert.set_notBefore(b'20250301000000Z')
        cert.set_notAfter(b'20260301000000Z')
        cert.set_pubkey(private_key)
        cert.sign(private_key, "sha256")

        with open(PRIVATE_KEY_FILE, "wb") as f:
            f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, private_key))

        with open(PUBLIC_KEY_FILE, "wb") as f:
            f.write(crypto.dump_publickey(crypto.FILETYPE_PEM, private_key))

        with open(CERTIFICATE_FILE, "wb") as f:
            f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))

        print(f"Keys and certificate generated: {PRIVATE_KEY_FILE}, {PUBLIC_KEY_FILE}, {CERTIFICATE_FILE}")
    else:
        print("Keys and certificate already exist.")

def load_private_key():
    with open(PRIVATE_KEY_FILE, "rb") as f:
        return xmlsec.Key.from_file(f, xmlsec.KeyFormat.PEM)

def load_certificate():
    with open(CERTIFICATE_FILE, "rb") as f:
        cert_data = f.read().decode("utf-8")
    return "".join(cert_data.splitlines()[1:-1])  # Remove BEGIN/END lines

def sign_xml(xml_string, add_x509=False):
    etree.register_namespace("ds", NS["ds"])  # Register namespace globally

    parser = etree.XMLParser(remove_blank_text=True)
    root = etree.XML(xml_string.encode("utf-8"), parser)

    inf_nfcom = root.find(".//nfcom:infNFCom", namespaces=NS)
    if inf_nfcom is None:
        raise ValueError("Error: <infNFCom> element not found.")

    # Create Signature template
    signature = xmlsec.template.create(
        root, xmlsec.Transform.EXCL_C14N, xmlsec.Transform.RSA_SHA256, ns="ds"
    )
    inf_nfcom.append(signature)

    ref = xmlsec.template.add_reference(signature, xmlsec.Transform.SHA256, uri="")
    xmlsec.template.add_transform(ref, xmlsec.Transform.ENVELOPED)
    xmlsec.template.add_transform(ref, xmlsec.Transform.EXCL_C14N)

    key_info = xmlsec.template.ensure_key_info(signature)
    if add_x509:
        x509_data = xmlsec.template.add_x509_data(key_info)
        cert_element = etree.SubElement(x509_data, "{http://www.w3.org/2000/09/xmldsig#}X509Certificate")
        cert_element.text = load_certificate()

    # Sign the XML
    signature_context = xmlsec.SignatureContext()
    signature_context.key = load_private_key()
    signature_context.sign(signature)

    return etree.tostring(root, pretty_print=True, xml_declaration=True, encoding="utf-8").decode()

if __name__ == "__main__":
    generate_rsa_key_and_certificate()
    ADD_X509 = True

    if not os.path.exists(XML_FILE_TO_SIGN):
        print(f"Error: XML file '{XML_FILE_TO_SIGN}' not found.")
        exit(1)

    with open(XML_FILE_TO_SIGN, "r", encoding="utf-8") as f:
        xml_to_sign = f.read()

    try:
        signed_xml = sign_xml(xml_to_sign, ADD_X509)
        with open(SIGNED_XML_FILE, "w", encoding="utf-8") as f:
            f.write(signed_xml)
        print(f"Signed XML saved as '{SIGNED_XML_FILE}'")
    except Exception as e:
        print(f"Signing failed: {e}")
转载请注明原文地址:http://anycun.com/QandA/1744857914a88605.html