Browse Source

Merge branch 'master' of git@github.com:marinho/PyNFe

Conflicts:
	pynfe/processamento/assinatura.py
tags/0.1
Italo Maia 16 years ago
parent
commit
6b5a662c0f
  1. 118
      pynfe/processamento/assinatura.py
  2. 38
      pynfe/processamento/validacao.py
  3. 31
      run_fake_soap_server.py
  4. 15
      tests/03-processamento-03-assinatura.txt

118
pynfe/processamento/assinatura.py

@ -123,44 +123,37 @@ class AssinaturaA1(Assinatura):
# Acrescenta a tag de doctype (como o lxml nao suporta alteracao do doctype, # Acrescenta a tag de doctype (como o lxml nao suporta alteracao do doctype,
# converte para string para faze-lo) # converte para string para faze-lo)
xml = etree.tostring(raiz, xml_declaration=True, encoding='utf-8') 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('<!DOCTYPE ') == -1:
pos = 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') #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 # Ativa as funções de análise de arquivos XML FIXME
libxml2.initParser() libxml2.initParser()
libxml2.substituteEntitiesDefault(1) libxml2.substituteEntitiesDefault(1)
@ -170,7 +163,7 @@ class AssinaturaA1(Assinatura):
xmlsec.cryptoAppInit(None) xmlsec.cryptoAppInit(None)
xmlsec.cryptoInit() xmlsec.cryptoInit()
def _desativa_funcoes_criptograficas(self):
def _desativar_funcoes_criptograficas(self):
''' Desativa as funções criptográficas e de análise XML ''' Desativa as funções criptográficas e de análise XML
As funções devem ser chamadas aproximadamente na ordem inversa da ativação As funções devem ser chamadas aproximadamente na ordem inversa da ativação
''' '''
@ -184,6 +177,71 @@ class AssinaturaA1(Assinatura):
# Shutdown xmlsec library # Shutdown xmlsec library
xmlsec.shutdown() xmlsec.shutdown()
# Shutdown LibXML2 FIXME
# Shutdown LibXML2 FIXME: descobrir forma de evitar o uso do libxml2 neste processo
libxml2.cleanupParser() 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()

38
pynfe/processamento/validacao.py

@ -1,5 +1,8 @@
#-*- coding:utf-8 -*- #-*- coding:utf-8 -*-
from os import path
from glob import glob
try: try:
from lxml import etree from lxml import etree
except ImportError: except ImportError:
@ -21,6 +24,39 @@ except ImportError:
except ImportError: except ImportError:
raise Exception('Falhou ao importar lxml/ElementTree') raise Exception('Falhou ao importar lxml/ElementTree')
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): class Validacao(object):
pass
'''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

31
run_fake_soap_server.py

@ -4,27 +4,20 @@ from soaplib.service import soapmethod
class ServidorNFEFalso(SimpleWSGISoapApp): class ServidorNFEFalso(SimpleWSGISoapApp):
from soaplib.serializers.primitive import String, Integer, Array, Null 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__': if __name__ == '__main__':
porta = 8081
porta = 8080
# Via Tornado # 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()

15
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: A assinatura deve ser feita em quatro tipos diferentes de origem do XML:
- Arquivos
- Arquivo
>>> assinatura.assinar_arquivo('tests/saida/nfe-1.xml') >>> assinatura.assinar_arquivo('tests/saida/nfe-1.xml')
True True
@ -45,7 +45,16 @@ A assinatura deve ser feita em quatro tipos diferentes de origem do XML:
- Utilizar pyXMLSec para isso - Utilizar pyXMLSec para isso
- verificar qual eh a integracao do PyXMLSec com o lxml.etree - 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
Loading…
Cancel
Save