diff --git a/pytrustnfe/certificado.py b/pytrustnfe/certificado.py index 1971f15..4e44ae9 100644 --- a/pytrustnfe/certificado.py +++ b/pytrustnfe/certificado.py @@ -31,7 +31,6 @@ def extract_cert_and_key_from_pfx(pfx, password): return cert, key - def save_cert_key(cert, key): cert_temp = '/tmp/' + uuid4().hex key_temp = '/tmp/' + uuid4().hex diff --git a/pytrustnfe/nfe/__init__.py b/pytrustnfe/nfe/__init__.py index 6b75a6a..3516b27 100644 --- a/pytrustnfe/nfe/__init__.py +++ b/pytrustnfe/nfe/__init__.py @@ -48,17 +48,9 @@ def _send(certificado, method, sign, **kwargs): xml_send = render_xml(path, '%s.xml' % method, **kwargs) if sign: - xml_send = ']>' + \ - xml_send - xml_send = xml_send.replace('\n', '') - pfx_path = certificado.save_pfx() - signer = Assinatura(pfx_path, certificado.password) - xml_send = signer.assina_xml_nota( + signer = Assinatura(certificado.pfx, certificado.password) + xml_send = signer.assina_xml( xml_send, kwargs['NFes'][0]['infNFe']['Id']) - xml_send = xml_send.replace( - '\n\n]>\n', '') - xml_send = xml_send.replace('\n', u'') - xml_send = xml_send.replace('', '') url = localizar_url(method, kwargs['estado'], kwargs['ambiente']) cabecalho = _build_header(method, **kwargs) diff --git a/pytrustnfe/nfe/assinatura.py b/pytrustnfe/nfe/assinatura.py index de5044e..d9c1517 100644 --- a/pytrustnfe/nfe/assinatura.py +++ b/pytrustnfe/nfe/assinatura.py @@ -2,11 +2,11 @@ # © 2016 Danimar Ribeiro, Trustcode # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -import xmlsec -import libxml2 -import os.path - -NAMESPACE_SIG = 'http://www.w3.org/2000/09/xmldsig#' +import signxml +from lxml import etree +from pytrustnfe.certificado import extract_cert_and_key_from_pfx +from signxml import XMLSigner +from StringIO import StringIO class Assinatura(object): @@ -15,125 +15,22 @@ class Assinatura(object): self.arquivo = arquivo self.senha = senha - def _checar_certificado(self): - if not os.path.isfile(self.arquivo): - raise Exception('Caminho do certificado não existe.') - - def _inicializar_cripto(self): - libxml2.initParser() - libxml2.substituteEntitiesDefault(1) - - xmlsec.init() - xmlsec.cryptoAppInit(None) - xmlsec.cryptoInit() - - def _finalizar_cripto(self): - xmlsec.cryptoShutdown() - xmlsec.cryptoAppShutdown() - xmlsec.shutdown() - - libxml2.cleanupParser() - def assina_xml(self, xml, reference): - self._checar_certificado() - self._inicializar_cripto() - try: - doc_xml = libxml2.parseMemory( - xml.encode('utf-8'), len(xml.encode('utf-8'))) - - signNode = xmlsec.TmplSignature(doc_xml, - xmlsec.transformInclC14NId(), - xmlsec.transformRsaSha1Id(), None) - - doc_xml.getRootElement().addChild(signNode) - refNode = signNode.addReference(xmlsec.transformSha1Id(), - None, '#' + str(reference), None) - - refNode.addTransform(xmlsec.transformEnvelopedId()) - refNode.addTransform(xmlsec.transformInclC14NId()) - keyInfoNode = signNode.ensureKeyInfo() - keyInfoNode.addX509Data() - - dsig_ctx = xmlsec.DSigCtx() - chave = xmlsec.cryptoAppKeyLoad(filename=str(self.arquivo), - format=xmlsec.KeyDataFormatPkcs12, - pwd=str(self.senha), - pwdCallback=None, - pwdCallbackCtx=None) - - dsig_ctx.signKey = chave - dsig_ctx.sign(signNode) - - status = dsig_ctx.status - dsig_ctx.destroy() - - if status != xmlsec.DSigStatusSucceeded: - raise RuntimeError( - 'Erro ao realizar a assinatura do arquivo; status: "' + - str(status) + - '"') - - xpath = doc_xml.xpathNewContext() - xpath.xpathRegisterNs('sig', NAMESPACE_SIG) - certificados = xpath.xpathEval( - '//sig:X509Data/sig:X509Certificate') - for i in range(len(certificados) - 1): - certificados[i].unlinkNode() - certificados[i].freeNode() - - xml = doc_xml.serialize() - return xml - finally: - doc_xml.freeDoc() - # self._finalizar_cripto() - - def assina_xml_nota(self, xml, reference): - self._checar_certificado() - self._inicializar_cripto() - try: - doc_xml = libxml2.parseMemory( - xml.encode('utf-8'), len(xml.encode('utf-8'))) - signNode = xmlsec.TmplSignature(doc_xml, - xmlsec.transformInclC14NId(), - xmlsec.transformRsaSha1Id(), None) - doc_xml.getRootElement().get_last().addChild(signNode) - refNode = signNode.addReference(xmlsec.transformSha1Id(), - None, '#' + str(reference), None) - - refNode.addTransform(xmlsec.transformEnvelopedId()) - refNode.addTransform(xmlsec.transformInclC14NId()) - keyInfoNode = signNode.ensureKeyInfo() - keyInfoNode.addX509Data() - - dsig_ctx = xmlsec.DSigCtx() - chave = xmlsec.cryptoAppKeyLoad(filename=str(self.arquivo), - format=xmlsec.KeyDataFormatPkcs12, - pwd=str(self.senha), - pwdCallback=None, - pwdCallbackCtx=None) - - dsig_ctx.signKey = chave - dsig_ctx.sign(signNode) - - status = dsig_ctx.status - dsig_ctx.destroy() - - if status != xmlsec.DSigStatusSucceeded: - raise RuntimeError( - 'Erro ao realizar a assinatura do arquivo; status: "' + - str(status) + - '"') - - xpath = doc_xml.xpathNewContext() - xpath.xpathRegisterNs('sig', NAMESPACE_SIG) - certificados = xpath.xpathEval( - '//sig:X509Data/sig:X509Certificate') - for i in range(len(certificados) - 1): - certificados[i].unlinkNode() - certificados[i].freeNode() - - xml = doc_xml.serialize() - return xml - finally: - doc_xml.freeDoc() - # self._finalizar_cripto() + cert, key = extract_cert_and_key_from_pfx(self.arquivo, self.senha) + + parser = etree.XMLParser(remove_blank_text=True, remove_comments=True) + root = etree.parse(StringIO(xml), parser=parser) + for element in root.iter("*"): + if element.text is not None and not element.text.strip(): + element.text = None + + signer = XMLSigner( + method=signxml.methods.enveloped, signature_algorithm="rsa-sha1", + digest_algorithm='sha1', + c14n_algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315') + + signed_root = signer.sign( + root, key=key, cert=cert, reference_only=True, + reference_uri=('#%s' % reference)) + signed_root[2].append(signed_root[3]) + return etree.tostring(signed_root) diff --git a/pytrustnfe/nfe/templates/NfeAutorizacao.xml b/pytrustnfe/nfe/templates/NfeAutorizacao.xml index 28c5905..feb0df0 100644 --- a/pytrustnfe/nfe/templates/NfeAutorizacao.xml +++ b/pytrustnfe/nfe/templates/NfeAutorizacao.xml @@ -32,11 +32,11 @@ {% with emit = NFe.infNFe.emit %} {% if emit.tipo == 'person' -%} - {{ emit.cnpj_cpf }} - {% endif %} - {% if emit.tipo == 'company' -%} - {{ emit.cnpj_cpf }} - {% endif %} + {{ emit.cnpj_cpf }} + {% endif %} + {% if emit.tipo == 'company' -%} + {{ emit.cnpj_cpf }} + {% endif %} {{ emit.xNome }} {{ emit.xFant }} @@ -58,11 +58,11 @@ {% with dest = NFe.infNFe.dest %} {% if dest.tipo == 'person' -%} - {{ dest.cnpj_cpf }} - {% endif %} - {% if dest.tipo == 'company' -%} - {{ dest.cnpj_cpf }} - {% endif %} + {{ dest.cnpj_cpf }} + {% endif %} + {% if dest.tipo == 'company' -%} + {{ dest.cnpj_cpf }} + {% endif %} {{ dest.xNome }} {{ dest.enderDest.xLgr }} @@ -154,6 +154,16 @@ {{ imposto.COFINS.COFINSAliq.vCOFINS }} + + 0.00 + 0.00 + 0.00 + 12.00 + 40 + 0.00 + 0.00 + 0.00 + {% endwith %} diff --git a/pytrustnfe/nfse/assinatura.py b/pytrustnfe/nfse/assinatura.py new file mode 100644 index 0000000..b2a8d28 --- /dev/null +++ b/pytrustnfe/nfse/assinatura.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +# © 2016 Danimar Ribeiro, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import xmlsec +import libxml2 +import os.path + +NAMESPACE_SIG = 'http://www.w3.org/2000/09/xmldsig#' + + +class Assinatura(object): + + def __init__(self, arquivo, senha): + self.arquivo = arquivo + self.senha = senha + + def _checar_certificado(self): + if not os.path.isfile(self.arquivo): + raise Exception('Caminho do certificado não existe.') + + def _inicializar_cripto(self): + libxml2.initParser() + libxml2.substituteEntitiesDefault(1) + + xmlsec.init() + xmlsec.cryptoAppInit(None) + xmlsec.cryptoInit() + + def _finalizar_cripto(self): + xmlsec.cryptoShutdown() + xmlsec.cryptoAppShutdown() + xmlsec.shutdown() + + libxml2.cleanupParser() + + def assina_xml(self, xml, reference): + self._checar_certificado() + self._inicializar_cripto() + try: + doc_xml = libxml2.parseMemory( + xml, len(xml)) + + signNode = xmlsec.TmplSignature(doc_xml, + xmlsec.transformInclC14NId(), + xmlsec.transformRsaSha1Id(), None) + + doc_xml.getRootElement().addChild(signNode) + refNode = signNode.addReference(xmlsec.transformSha1Id(), + None, reference, None) + + refNode.addTransform(xmlsec.transformEnvelopedId()) + refNode.addTransform(xmlsec.transformInclC14NId()) + keyInfoNode = signNode.ensureKeyInfo() + keyInfoNode.addX509Data() + + dsig_ctx = xmlsec.DSigCtx() + chave = xmlsec.cryptoAppKeyLoad(filename=str(self.arquivo), + format=xmlsec.KeyDataFormatPkcs12, + pwd=str(self.senha), + pwdCallback=None, + pwdCallbackCtx=None) + + dsig_ctx.signKey = chave + dsig_ctx.sign(signNode) + + status = dsig_ctx.status + dsig_ctx.destroy() + + if status != xmlsec.DSigStatusSucceeded: + raise RuntimeError( + 'Erro ao realizar a assinatura do arquivo; status: "' + + str(status) + + '"') + + xpath = doc_xml.xpathNewContext() + xpath.xpathRegisterNs('sig', NAMESPACE_SIG) + certificados = xpath.xpathEval( + '//sig:X509Data/sig:X509Certificate') + for i in range(len(certificados) - 1): + certificados[i].unlinkNode() + certificados[i].freeNode() + + xml = doc_xml.serialize() + return xml + finally: + doc_xml.freeDoc() diff --git a/pytrustnfe/nfse/paulistana/__init__.py b/pytrustnfe/nfse/paulistana/__init__.py index fd88892..8a776c5 100644 --- a/pytrustnfe/nfse/paulistana/__init__.py +++ b/pytrustnfe/nfse/paulistana/__init__.py @@ -9,7 +9,7 @@ from base64 import b64encode from pytrustnfe.xml import render_xml, sanitize_response from pytrustnfe.client import get_authenticated_client from pytrustnfe.certificado import extract_cert_and_key_from_pfx, save_cert_key -from pytrustnfe.nfe.assinatura import Assinatura +from pytrustnfe.nfse.assinatura import Assinatura def sign_tag(certificado, **kwargs):