From 4895c63dd62f6f61d097e9a0b2f2b67bb6eede6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marinho=20Brand=C3=A3o?= Date: Sun, 17 Jan 2010 22:26:49 -0200 Subject: [PATCH 1/2] Trabalhando na assinatura com certificado digital tipo A1 --- pynfe/processamento/assinatura.py | 122 +++++++++++++++++++++++-------- tests/03-processamento-03-assinatura.txt | 4 +- 2 files changed, 92 insertions(+), 34 deletions(-) diff --git a/pynfe/processamento/assinatura.py b/pynfe/processamento/assinatura.py index 74fc3b6..5b5c0b9 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)) - - # 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, - ) + doc_xml, ctxt, noh_assinatura, assinador = self._antes_de_assinar_ou_verificar(raiz) + + # Realiza a assinatura + assinador.sign(noh_assinatura) - # Cria a variável de chamada (callable) da função de assinatura - assinador = xmlsec.DSigCtx() + # 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'') + + # 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'') - # Atribui a chave ao assinador - assinador.signKey = chave + resultado = assinador.status == xmlsec.DSigStatusSucceeded - # 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/tests/03-processamento-03-assinatura.txt b/tests/03-processamento-03-assinatura.txt index 510f2d5..c619344 100644 --- a/tests/03-processamento-03-assinatura.txt +++ b/tests/03-processamento-03-assinatura.txt @@ -45,7 +45,7 @@ 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 +---------------------- From bf662a4dfc770ac6221d3d0164d7b5b4c5f9303e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marinho=20Brand=C3=A3o?= Date: Sun, 17 Jan 2010 23:05:01 -0200 Subject: [PATCH 2/2] Trabalhando no servidor de SOAP fake, agora sobre Tornado --- run_fake_soap_server.py | 31 ++++++++++++------------------- tests/03-processamento-03-assinatura.txt | 11 ++++++++++- 2 files changed, 22 insertions(+), 20 deletions(-) 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 c619344..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 @@ -48,4 +48,13 @@ A assinatura deve ser feita em quatro tipos diferentes de origem do XML: 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