diff --git a/pynfe/processamento/assinatura.py b/pynfe/processamento/assinatura.py index 44b81a9..2d294ab 100644 --- a/pynfe/processamento/assinatura.py +++ b/pynfe/processamento/assinatura.py @@ -123,44 +123,37 @@ class AssinaturaA1(Assinatura): # Acrescenta a tag de doctype (como o lxml nao suporta alteracao do doctype, # converte para string para faze-lo) xml = etree.tostring(raiz, xml_declaration=True, encoding='utf-8') - pos = xml.find('>') + 1 - xml = xml[:pos] + doctype + xml[pos:] - raiz = etree.parse(StringIO(xml)) - # Ativa funções criptográficas - self._ativa_funcoes_criptograficas() + if xml.find('') + 1 + xml = xml[:pos] + doctype + xml[pos:] + #raiz = etree.parse(StringIO(xml)) - # Colocamos o texto no avaliador XML - #doc_xml = libxml2.parseMemory(xml, len(xml)) + doc_xml, ctxt, noh_assinatura, assinador = self._antes_de_assinar_ou_verificar(raiz) - # Cria o contexto para manipulação do XML via sintaxe XPATH - #ctxt = doc_xml.xpathNewContext() - #ctxt.xpathRegisterNs(u'sig', NAMESPACE_SIG) + # Realiza a assinatura + assinador.sign(noh_assinatura) - # Separa o nó da assinatura - #noh_assinatura = ctxt.xpathEval(u'//*/sig:Signature')[0] + # Coloca na instância Signature os valores calculados + doc.Signature.DigestValue = ctxt.xpathEval(u'//sig:DigestValue')[0].content.replace(u'\n', u'') + doc.Signature.SignatureValue = ctxt.xpathEval(u'//sig:SignatureValue')[0].content.replace(u'\n', u'') - # Buscamos a chave no arquivo do certificado - chave = xmlsec.cryptoAppKeyLoad( - filename=str(self.certificado.caminho_arquivo), - format=xmlsec.KeyDataFormatPkcs12, - pwd=str(self.senha), - pwdCallback=None, - pwdCallbackCtx=None, - ) + # Provavelmente retornarão vários certificados, já que o xmlsec inclui a cadeia inteira + certificados = ctxt.xpathEval(u'//sig:X509Data/sig:X509Certificate') + doc.Signature.X509Certificate = certificados[len(certificados)-1].content.replace(u'\n', u'') - # Cria a variável de chamada (callable) da função de assinatura - assinador = xmlsec.DSigCtx() + resultado = assinador.status == xmlsec.DSigStatusSucceeded - # Atribui a chave ao assinador - assinador.signKey = chave - - # Desativa funções criptográficas - self._desativa_funcoes_criptograficas() + # Limpa objetos da memoria e desativa funções criptográficas + self._depois_de_assinar_ou_verificar(doc_xml, ctxt, assinador) #print etree.tostring(raiz, pretty_print=True, xml_declaration=True, encoding='utf-8') - def _ativa_funcoes_criptograficas(self): + return resultado + + def _ativar_funcoes_criptograficas(self): + # FIXME: descobrir forma de evitar o uso do libxml2 neste processo + # Ativa as funções de análise de arquivos XML FIXME libxml2.initParser() libxml2.substituteEntitiesDefault(1) @@ -170,7 +163,7 @@ class AssinaturaA1(Assinatura): xmlsec.cryptoAppInit(None) xmlsec.cryptoInit() - def _desativa_funcoes_criptograficas(self): + def _desativar_funcoes_criptograficas(self): ''' Desativa as funções criptográficas e de análise XML As funções devem ser chamadas aproximadamente na ordem inversa da ativação ''' @@ -184,6 +177,71 @@ class AssinaturaA1(Assinatura): # Shutdown xmlsec library xmlsec.shutdown() - # Shutdown LibXML2 FIXME + # Shutdown LibXML2 FIXME: descobrir forma de evitar o uso do libxml2 neste processo libxml2.cleanupParser() + def verificar_arquivo(self, caminho_arquivo): + # Carrega o XML do arquivo + raiz = etree.parse(caminho_arquivo) + return self.verificar_etree(raiz) + + def verificar_xml(self, xml): + raiz = etree.parse(StringIO(xml)) + return self.verificar_etree(raiz) + + def verificar_etree(self, raiz): + doc_xml, ctxt, noh_assinatura, assinador = self._antes_de_assinar_ou_verificar(raiz) + + # Verifica a assinatura + assinador.verify(noh_assinatura) + resultado = assinador.status == xmlsec.DSigStatusSucceeded + + # Limpa objetos da memoria e desativa funções criptográficas + self._depois_de_assinar_ou_verificar(doc_xml, ctxt, assinador) + + return resultado + + def _antes_de_assinar_ou_verificar(self, raiz): + # Converte etree para string + xml = etree.tostring(raiz, xml_declaration=True, encoding='utf-8') + + # Ativa funções criptográficas + self._ativar_funcoes_criptograficas() + + # Colocamos o texto no avaliador XML FIXME: descobrir forma de evitar o uso do libxml2 neste processo + doc_xml = libxml2.parseMemory(xml, len(xml)) + + # Cria o contexto para manipulação do XML via sintaxe XPATH + ctxt = doc_xml.xpathNewContext() + ctxt.xpathRegisterNs(u'sig', NAMESPACE_SIG) + + # Separa o nó da assinatura + noh_assinatura = ctxt.xpathEval(u'//*/sig:Signature')[0] + + # Buscamos a chave no arquivo do certificado + chave = xmlsec.cryptoAppKeyLoad( + filename=str(self.certificado.caminho_arquivo), + format=xmlsec.KeyDataFormatPkcs12, + pwd=str(self.senha), + pwdCallback=None, + pwdCallbackCtx=None, + ) + + # Cria a variável de chamada (callable) da função de assinatura + assinador = xmlsec.DSigCtx() + + # Atribui a chave ao assinador + assinador.signKey = chave + + return doc_xml, ctxt, noh_assinatura, assinador + + def _depois_de_assinar_ou_verificar(self, doc_xml, ctxt, assinador): + # Libera a memória do assinador; isso é necessário, pois na verdade foi feita uma chamada + # a uma função em C cujo código não é gerenciado pelo Python + assinador.destroy() + ctxt.xpathFreeContext() + doc_xml.freeDoc() + + # E, por fim, desativa todas as funções ativadas anteriormente + self._desativar_funcoes_criptograficas() + diff --git a/pynfe/processamento/validacao.py b/pynfe/processamento/validacao.py index 405288a..3b9a38d 100644 --- a/pynfe/processamento/validacao.py +++ b/pynfe/processamento/validacao.py @@ -1,5 +1,8 @@ #-*- coding:utf-8 -*- +from os import path +from glob import glob + try: from lxml import etree except ImportError: @@ -21,6 +24,39 @@ except ImportError: except ImportError: raise Exception('Falhou ao importar lxml/ElementTree') -class Validacao(object): - pass +XSD_FOLDER = "data/XSDs/" + +XSD_NFE="nfe_v1.10.xsd" +XSD_NFE_PROCESSADA="procNFe_v1.10.xsd" +XSD_PD_CANCELAR_NFE="procCancNFe_v1.07.xsd" +XSD_PD_INUTILIZAR_NFE="procInutNFe_v1.07.xsd" +class Validacao(object): + '''Valida documentos xml a partir do xsd informado''' + def __init__(self): + self.clear_cache() + + def clear_cache(self): + self.MEM_CACHE = {} + + def validar_xml(self, xml_path, xsd_file): + '''Valida um arquivo xml + Argumentos: + xml_filepath - caminho para arquivo xml + xsd_file - caminho para o arquivo xsd + ''' + return self._validar(etree.parse(xml_path), xsd_file) + + def _validar(self, xml_doc, xsd_file): + xsd_filepath = path.join(XSD_FOLDER, xsd_file) + + try: + xsd_schema = self.MEM_CACHE[xsd_file] + except: + xsd_doc = etree.parse(xsd_file) + xsd_schema = etree.XMLSchema(xsd_doc) + return xsd_schema.validate(xml_doc) + + # alias + validar_etree = _validar + \ No newline at end of file diff --git a/run_fake_soap_server.py b/run_fake_soap_server.py index b93ac58..77a3ad2 100644 --- a/run_fake_soap_server.py +++ b/run_fake_soap_server.py @@ -4,27 +4,20 @@ from soaplib.service import soapmethod class ServidorNFEFalso(SimpleWSGISoapApp): from soaplib.serializers.primitive import String, Integer, Array, Null - @soapmethod(_returns=Null) - def finalizar(self): - import sys - sys.exit(0) - - @soapmethod(_returns=String) - def ping(self): - return 'eu estou aqui' + @soapmethod(String, Integer, _returns=String) + def ping(self, palavra, vezes): + return ','.join([palavra for i in range(vezes)]) if __name__ == '__main__': - porta = 8081 + porta = 8080 # Via Tornado - #import tornado.httpserver - #import tornado.ioloop - #http_server = tornado.httpserver.HTTPServer(ServidorNFEFalso()) - #http_server.listen(porta) - #tornado.ioloop.IOLoop.instance().start() - - # Via CherryPy - from cherrypy.wsgiserver import CherryPyWSGIServer - server = CherryPyWSGIServer(('localhost', porta), ServidorNFEFalso()) - server.start() + import tornado.wsgi + import tornado.httpserver + import tornado.ioloop + application = ServidorNFEFalso() + container = tornado.wsgi.WSGIContainer(application) + http_server = tornado.httpserver.HTTPServer(container) + http_server.listen(porta) + tornado.ioloop.IOLoop.instance().start() diff --git a/tests/03-processamento-03-assinatura.txt b/tests/03-processamento-03-assinatura.txt index 510f2d5..c17ac1f 100644 --- a/tests/03-processamento-03-assinatura.txt +++ b/tests/03-processamento-03-assinatura.txt @@ -22,7 +22,7 @@ processo e a capture. A assinatura deve ser feita em quatro tipos diferentes de origem do XML: -- Arquivos +- Arquivo >>> assinatura.assinar_arquivo('tests/saida/nfe-1.xml') True @@ -45,7 +45,16 @@ A assinatura deve ser feita em quatro tipos diferentes de origem do XML: - Utilizar pyXMLSec para isso - verificar qual eh a integracao do PyXMLSec com o lxml.etree -Validando assinatura --------------------- +Verificando assinatura +---------------------- + +TODO +Da mesma forma que na assinatura, a verificacao deve suportar os seguintes +formatos de dados: + +- Arquivos +- String de XML +- Instancias do PyNFe +- Instancia de lxml.etree