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 +----------------------