Browse Source

Trabalhando na assinatura

tags/0.1
Marinho Brandão 16 years ago
parent
commit
602fda7521
  1. 142
      pynfe/processamento/assinatura.py
  2. 4
      tests/01-basico.txt
  3. 6
      tests/03-processamento-01-serializacao-xml.txt
  4. 18
      tests/03-processamento-03-assinatura.txt

142
pynfe/processamento/assinatura.py

@ -1,5 +1,38 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
try:
from lxml import etree
except ImportError:
try:
# Python 2.5 - cElementTree
import xml.etree.cElementTree as etree
except ImportError:
try:
# Python 2.5 - ElementTree
import xml.etree.ElementTree as etree
except ImportError:
try:
# Instalacao normal do cElementTree
import cElementTree as etree
except ImportError:
try:
# Instalacao normal do ElementTree
import elementtree.ElementTree as etree
except ImportError:
raise Exception('Falhou ao importar lxml/ElementTree')
import xmlsec, libxml2 # FIXME: verificar ambiguidade de dependencias: lxml e libxml2
from geraldo.utils import memoize
NAMESPACE_NFE = u'http://www.portalfiscal.inf.br/nfe'
NAMESPACE_SIG = u'http://www.w3.org/2000/09/xmldsig#'
class Assinatura(object): class Assinatura(object):
"""Classe abstrata responsavel por definir os metodos e logica das classes """Classe abstrata responsavel por definir os metodos e logica das classes
de assinatura digital.""" de assinatura digital."""
@ -11,7 +44,7 @@ class Assinatura(object):
self.certificado = certificado self.certificado = certificado
self.senha = senha self.senha = senha
def assinar_arquivos(self, caminho_raiz):
def assinar_arquivo(self, caminho_arquivo):
"""Efetua a assinatura dos arquivos XML informados""" """Efetua a assinatura dos arquivos XML informados"""
pass pass
@ -33,7 +66,7 @@ class Assinatura(object):
"""Efetua a assinatura em instancias do PyNFe""" """Efetua a assinatura em instancias do PyNFe"""
pass pass
def verificar_arquivos(self, caminho_raiz):
def verificar_arquivo(self, caminho_arquivo):
pass pass
def verificar_xml(self, xml): def verificar_xml(self, xml):
@ -45,9 +78,112 @@ class Assinatura(object):
def verificar_objetos(self, objetos): def verificar_objetos(self, objetos):
pass pass
@memoize
def extrair_tag(root):
return root.tag.split('}')[-1]
class AssinaturaA1(Assinatura): class AssinaturaA1(Assinatura):
"""Classe abstrata responsavel por efetuar a assinatura do certificado """Classe abstrata responsavel por efetuar a assinatura do certificado
digital no XML informado.""" digital no XML informado."""
pass
def assinar_arquivo(self, caminho_arquivo):
# Carrega o XML do arquivo
raiz = etree.parse(caminho_arquivo)
return self.assinar_etree(raiz)
def assinar_xml(self, xml):
raiz = etree.parse(StringIO(xml))
return self.assinar_etree(raiz)
def assinar_etree(self, raiz):
# Extrai a tag do elemento raiz
tipo = extrair_tag(raiz.getroot())
# doctype compatível com o tipo da tag raiz
if tipo == u'NFe':
doctype = u'<!DOCTYPE NFe [<!ATTLIST infNFe Id ID #IMPLIED>]>'
elif tipo == u'inutNFe':
doctype = u'<!DOCTYPE inutNFe [<!ATTLIST infInut Id ID #IMPLIED>]>'
elif tipo == u'cancNFe':
doctype = u'<!DOCTYPE cancNFe [<!ATTLIST infCanc Id ID #IMPLIED>]>'
elif tipo == u'DPEC':
doctype = u'<!DOCTYPE DPEC [<!ATTLIST infDPEC Id ID #IMPLIED>]>'
# Tag de assinatura
if raiz.getroot().find('Signature') is None:
signature = etree.Element(
'Signature',
URI=raiz.getroot().getchildren()[0].attrib['Id'],
xmlns=NAMESPACE_SIG,
)
signature.text = ''
raiz.getroot().insert(0, signature)
# 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()
# 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,
)
# Cria a variável de chamada (callable) da função de assinatura
assinador = xmlsec.DSigCtx()
# Atribui a chave ao assinador
assinador.signKey = chave
# Desativa funções criptográficas
self._desativa_funcoes_criptograficas()
#print etree.tostring(raiz, pretty_print=True, xml_declaration=True, encoding='utf-8')
def _ativa_funcoes_criptograficas(self):
# Ativa as funções de análise de arquivos XML FIXME
libxml2.initParser()
libxml2.substituteEntitiesDefault(1)
# Ativa as funções da API de criptografia
xmlsec.init()
xmlsec.cryptoAppInit(None)
xmlsec.cryptoInit()
def _desativa_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
'''
# Shutdown xmlsec-crypto library
xmlsec.cryptoShutdown()
# Shutdown crypto library
xmlsec.cryptoAppShutdown()
# Shutdown xmlsec library
xmlsec.shutdown()
# Shutdown LibXML2 FIXME
libxml2.cleanupParser()

4
tests/01-basico.txt

@ -55,12 +55,12 @@ modelo:
| ---------------------- | consultar_cadastro() | | | ---------------------- | consultar_cadastro() | |
| | Validacao | | inutilizar_faixa_numeracao() | | | | Validacao | | inutilizar_faixa_numeracao() | |
| ---------------------- -------------------------------- | | ---------------------- -------------------------------- |
| | validar_arquivos() | |
| | validar_arquivo() | |
| | validar_xml() | | | | validar_xml() | |
| | validar_etree() | ---------------------- | | | validar_etree() | ---------------------- |
| | validar_objetos() | | Assinatura | | | | validar_objetos() | | Assinatura | |
| ---------------------- ---------------------- | | ---------------------- ---------------------- |
| | assinar_arquivos() | |
| | assinar_arquivo() | |
| | assinar_xml() | | | | assinar_xml() | |
| | assinar_etree() | | | | assinar_etree() | |
| | assinar_objetos() | | | | assinar_objetos() | |

6
tests/03-processamento-01-serializacao-xml.txt

@ -497,6 +497,12 @@ Exportacao completa
>>> xml = serializador.exportar(modelo=55) >>> xml = serializador.exportar(modelo=55)
>>> from lxml import etree
>>> fp = file('tests/saida/nfe-1.xml', 'w')
>>> fp.write(etree.tostring(xml, pretty_print=True, xml_declaration=True, encoding='utf-8'))
>>> fp.close()
- Quando gerados me lote, apenas o primeiro arquivo deve ter o cabecalho - Quando gerados me lote, apenas o primeiro arquivo deve ter o cabecalho
padrao do XML 1.0 padrao do XML 1.0
- <?xml version="1.0" encoding="UTF-8"?> - <?xml version="1.0" encoding="UTF-8"?>

18
tests/03-processamento-03-assinatura.txt

@ -6,7 +6,7 @@ Carregando Certificado Digital tipo A1
>>> from pynfe.entidades import CertificadoA1 >>> from pynfe.entidades import CertificadoA1
>>> certificado = CertificadoA1(caminho_arquivo='tests/certificado.pfx')
>>> certificado = CertificadoA1(caminho_arquivo='tests/certificado.pem')
Assinando NF-e Assinando NF-e
-------------- --------------
@ -24,24 +24,28 @@ A assinatura deve ser feita em quatro tipos diferentes de origem do XML:
- Arquivos - Arquivos
>>> hasattr(AssinaturaA1, 'assinar_arquivos')
>>> assinatura.assinar_arquivo('tests/saida/nfe-1.xml')
True True
- String de XML - String de XML
>>> hasattr(AssinaturaA1, 'assinar_xml')
>>> hasattr(assinatura, 'assinar_xml')
True True
- Instancia de lxml.etree
- Instancias do PyNFe
>>> hasattr(AssinaturaA1, 'assinar_etree')
>>> hasattr(assinatura, 'assinar_objetos')
True True
- Instancias do PyNFe
- Instancia de lxml.etree
>>> hasattr(AssinaturaA1, 'assinar_objetos')
>>> hasattr(assinatura, 'assinar_etree')
True True
- 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
--------------------
Loading…
Cancel
Save