18 de julho de 2012

Assinando documentos XML com CAPICOM e Delphi

A Microsoft disponibiliza para o Windows uma biblioteca com tecnologia COM para tratar a criação e manipulação de arquivos XML. A biblioteca, chamada MSXML, está atualmente na versão 6 e suporta também transformações XSLT e validação através de esquemas XSD. Em apenas uma das versões (o MSXML5), foram incluídos ainda recursos para fazer assinatura digital de XMLs.

A versão 5 foi distribuída exclusivamente com o Microsoft Office para que os desenvolvedores dessa plataforma pudessem assinar XMLs. No entanto, uma busca na internet revela que a biblioteca é amplamente utilizada fora desse contexto.

Isto é uma boa notícia para quem usa Delphi (ou outra linguagem que suporte COM) pois o MSXML5, em associação com o CAPICOM, facilita bastante a tarefa de assinar XMLs. Neste post eu mostro como realizar este processo, considerando que você já tem montado o XML que quer assinar. O quadro abaixo mostra parte do XML de uma Nota Fiscal que vou usar como exemplo:
<?xml version="1.0" encoding="UTF-8" ?>
<enviNFe versao="2.00" xmlns="http://www.portalfiscal.inf.br/nfe">
<idLote>71</idLote>
<NFe>
<infNFe Id="NFe3508059978" versao="2.00">
<cUF>35</cUF>
<cNF>518005127</cNF>
<natOp>Venda a vista</natOp>
<mod>55</mod>
<serie>1</serie>
<dEmi>2012-05-06</dEmi>
<tpAmb>2</tpAmb>
</infNFe>
</NFe>
</enviNFe>

A primeira providência é importar os fontes dos ActiveX MSXML5 e CAPICOM para podermos utilizá-los no projeto Delphi. Há um resumo de como fazer essa importação neste endereço; para o MSXML5, a descrição da Type Library é Microsoft XML, v5.0. Ambos os fontes gerados devem ser incluídos na cláusula uses da unit que for fazer a assinatura.

Obviamente, vamos precisar de um certificado digital para realizar a assinatura. Utilizando o CAPICOM, podemos acessar o Certificate Store do Windows e localizar um apropriado, que tenha sido gerado para sua empresa. No entanto, as interfaces disponíveis no MSXML5 exigem que você informe um Certificate Store com o certificado que será utilizado, bem como sua cadeia de validação, se for necessário. Este passo extra é mostrado no quadro abaixo.
var store : IStore3;
cert : TCertificate;
lKey : IPrivateKey;
begin
cert := getCert;
lKey := Cert.PrivateKey;

{ Monta um Store em memória com o Certificado obtido antes }
store := CoStore.Create;
store.Open(CAPICOM_CURRENT_USER_STORE, 'My', CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED);
store.Add(cert.DefaultInterface);
{ ... }

A função GetCert utilizada acima é minha e segue as instruções descritas no post sobre acesso ao Certificate Store com CAPICOM. De resto, o código apenas cria um Store em memória e adiciona a ele o certificado encontrado. O trecho também salva a chave privada deste certificado, informação que será usada mais adiante para gerar a assinatura em si.

A seguir, precisamos preparar o XML do exemplo para receber a assinatura. É que a interface do MSXML5 responsável por essa etapa se baseia num nó Signature do XML. Esse nó descreve as transformações pelas quais o XML deve ser submetido antes de ser de fato assinado. O nó Signature deve ser criado como mostrado abaixo:
<?xml version="1.0" encoding="UTF-8" ?>
<enviNFe versao="2.00" xmlns="http://www.portalfiscal.inf.br/nfe">
<idLote>71</idLote>
<NFe>
{ ... }
<Signature>
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="#NFe3508059978">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue></DigestValue>
</Reference>
</SignedInfo>
<SignatureValue></SignatureValue>
</Signature>
</NFe>
</enviNFe>

Como podemos ver acima, o nó também deixa reservado um local para receber o hash (DigestValue) e outro para a própria assinatura (SignatureValue), que serão preenchidos automaticamente. A única parte variável do bloco preparado acima é o atributo URI da tag Reference, valor que reflete a identificação da Nota Fiscal contida no XML. Ou seja, deve-se montar o valor desse atributo a partir do valor do atributo Id da tag infNFe, precedendo este valor com o sinal #. Como o próprio nome diz, essa parte do XML faz referência ao nó (e seus filhos) que será resguardado pela assinatura digital.

Uma vez que o XML está preparado, podemos partir para a assinatura propriamente dita, o que é conseguido através da interface IXMLDigitalSignature do MSXML5. Então, precisamos criar uma instância dela e lhe fornecedor os dados para a assinatura:
var {...}
xmlDoc: IXmlDomDocument3;
noSignature : IXMLDOMElement;
xmlSign : IXMLDigitalSignature;
xmlDsigKey: IXMLDSigKey;
begin
{...}
{ Monta o XML e prepara a tag Signature }
xmlDoc := PreparaXML ();
noSignature := EncontraNodeSignature (xmlDoc);

if (noSignature <> Nil) then begin
xmlSign := CoMXDigitalSignature50.Create;
xmlSign.signature := noSignature;
xmlSign.store := store;

{ Monta a chave com o tipo exigido pelo método Sign }
xmlDsigKey := xmlSign.createKeyFromCSP(lKey.ProviderType, lKey.ProviderName, lKey.ContainerName, 0);
{ Assina o XML }
xmlSign.sign(xmlDsigKey, NOKEYINFO);
{...}
end;
end;

Como podemos ver no trecho de código acima, o IXMLDigitalSignature precisa que indiquemos qual é o nó Signature do nosso XmlDomDocument e o Certificate Store que construímos no início do post. Agora, temos que chamar o método sign para completar o serviço. Para funcionar, ele precisa de algumas informações baseadas na chave privada do certificado. No exemplo acima, utilizei a chave recuperada a partir do certificado para criar uma instância da interface IXMLDSigKey contendo as informações necessárias.

O segundo parâmetro da função sign determina como os dados da chave serão incluídos na estrutura do XML. No exemplo, usei o valor NOKEYINFO para que tais dados não sejam incluídos automaticamente.

Após chamar a função sign, as tags que deixamos reservadas no nó Signature são automaticamente calculadas e preenchidas. Finalizando, para que o XML fique no padrão exigido para Notas Fiscais pela Receita Federal, só falta incluir um nó com os dados do certificado usado para assinar. Mostrei como fazer isso no post Certificado Digital para inclusão no XML com CAPICOM e Delphi.

85 comentários :

Luiz Sodré disse...

Bom dia! Estou com problemas para assinar o xml, pois o método "sign" só aceita a referência da RPS com o "Id" (I mmaiúsculo). Porem meu xsd exigido o "id" (i minúsculo) e desta forma o método "sign" retorna erro não especificado.
Tem alguma forma de carregar o xsd antes para o método saber que o identificador será minúsculo???

Luís Gustavo Fabbro disse...

Luiz

Que tipo de XML você está assinando que exige o id em minúsculas ? A recomendação da W3C é que o identificador seja Id (veja o xsd aqui).

Desconheço que haja como forçar a classe de assinatura a considerar o id em minúsculas.

[]s

Luiz Sodré disse...

É devido ao XSD do schema da prefeitura. Já realizei testes e assinei com o Id, mas na validação do schema ele não passa por ser id no schema.
Já retirei a validação para comunicação direta com a prefeitura, mas através do webservice recebo a mesma crítica de validação.
Também testei não informar o id, as assinaturas das RPS's funcionam na boa mas quando assino o lote inteiro, o método que assina não gera a tag .
Já não sei o que fazer, tentei várias coisas.
Poderia me ajudar nessa???

Luís Gustavo Fabbro disse...

Luiz

Vc diz que consegue fazer a assinatura em algumas situações mas que ela falha qdo tentar assinar um lote. Pelo método descrito no post, o nó com as informações básicas da assinatura deve estar preparado com antecedência, marcando o espaço onde a assinatura será incluída. No caso de um lote, cada elemento que constitui o lote deve ser preparado e assinado de forma independente e depois acrescentado ao lote. A sua tentativa de assinar um lote está seguindo essa premissa ?

PS: Vc tem o XSD que exige o Id em minúsculas ? Envie para o email do Blog; gostaria de dar uma olhada nele.

[]s

Luiz Sodré disse...

Luís, boa noite.

Lhe enviei no email com os xsd. Agradeço a ajuda.

Luiz Sodré disse...

Luís, não conheço bem como buscar da assinatura (CAPICOM) as informações para as TAGs DigestValue , SignatureValue , X509SubjectName e X509Certificate (este ultimo você já utilizou em exemplo em que fiz e consegui obter o valor para a tag atraves da enumeração CAPICOM_ENCODE_BASE64).
Você teria os outros enumerados para as outras TAGs, visualizei no site da MSDN mais não entendi qual seria para as demais TAGs.
Pode me ajudar novamente???

Luís Gustavo Fabbro disse...

Luiz

O SubjectName é uma propriedade do Certificado (veja a documentação da classe Certificate). Para a tag DigestValue, você deve calcular um hash usando a classe HashedData ao qual vc passa o XML montado.

Não fiz testes com o signatureValue em CAPICOM mas creio que vc possa se basear no post Assinando documentos com CAPICOM para assinar o hash calculado antes.

[]s

Anônimo disse...

Bom Dia Pessoal,

Estou desenvolvendo NFS-e para blumenau. Preciso assinar uma tag chamada "Assinatura" de cada RPS que envio. O conteúdo dessa tag é uma string com determinada informações do RPS. Tentei montar um XML com o padrão dessa string, assinar e enviar. Mas o webservice me retornar que a tag "Assinatura" esta inválida. Utilizo a CAPICOM para fazer assinatura. Gostaria de saber se vcs sabem como assinar uma string através da CAPICOM e pegar apenas o valor referente a tag SignatureValue de um XML?

Desde já agradeço a atenção !

Att,

ERG

Luís Gustavo Fabbro disse...

Como foi que vc tentou gerar a assinatura ? Seguir as instruções do post, com o XML preparado como descrito, deveria ser suficiente pois a assinatura é calculada automaticamente e inserida na área reservada a ela.

Vc pode tb exportar o XML depois de assiná-lo e verificar se ele está correto, já que o problema pode estar em outra parte. O envio pelo Web Service pode estar fazendo algum tratamento que vc não percebeu, por exemplo.

[]s.

Anônimo disse...

Boa Tarde Luís,

Montei um XML como o abaixo:

00000000U00000000000120120809TNN00000000000004800000000000000012345100000000000000

Carreguei o tamplate da assinatura e mandei assinar. Peguei o valor da tag SIGNATUREVALUE e montei novamente meu RPS jogando pra dentro dessa tag ASSINATURA o valor da tag SIGNATUREVALUE anteriormente. Montei o Lote de RPS validei e enviei, mas mesmo assim me retornou erro na tag ASSINATURA do RPS.

Desde já agradeço a atenção !

Att,

ERG

Luís Gustavo Fabbro disse...

ERG

Se está fazendo a assinatura como descrito no post, não é preciso montar um novo XML pra jogar a tag de assinatura nele pois a função que assina já faz isso automático.

Um dos objetivos de se assinar um documento é justamente garantir que o contéudo assinado não foi modificado depois disso. Como você assinou um XML e transferiu a assinatura pra outro, dificilmente a assinatura será válida.

[]s

Anônimo disse...

Boa Tarde Luís,

Desculpe, Talvez eu não esteja sendo claro o suficiente. Vou tentar explicar melhor.

Este é o link do manual de desenvolvimento da NFS-e de Blumenau: http://www.blumenau.sc.gov.br/gxpsites/agxppdwn.aspx?1,1,556,O,P,0,6124%3bP%3b1%3b20.

Na página 39, mostra como deve ser gerado a tag Assinatura e assinada. O Fato é, que não estou conseguindo achar uma maneira de assinar uma string, sem ser no formato XML, utilizando meu certificado digital, através da dll CAPICOM.

Gostaria de saber se alguém tem alguma idéia de como fazer isso, ou conhece alguma unit ou dll que faça isso.

Desde já agradeço a atenção !

Att,

ERG

Luís Gustavo Fabbro disse...

ERG

Para assinar a string você pode usar o método descrito no post Assinando documentos com CAPICOM. O exemplo nele lê o conteúdo a partir de um arquivo mas nada impede que se passe a string diretamente.

O método SignedData.Sign usado lá calcula o hash do conteúdo com o algorítimo SHA1 e em seguida assina esse hash com a chave privada do certificado estipulado. O resultado é a assinatura já codificada em BASE64. Se não me engano, a chave pública também é incluída no resultado; então, terá que se certificar se isso é permitido.

Uma alternativa seria criar um serviço seu de assinatura em .NET pois as classes do namespace System.Security.Cryptography oferecem maior flexibilidade nas tarefas envolvidas numa assinatura.

[]s

Unknown disse...

Bom dia Luis, primeiramente parabéns pelo post pois me ajudou muito no desenvolvimento da NFSe, porem agora estou com um problema e gostaria de ver se você já teve alguma situação parecido, criei uma função para assinar o xml onde passo o xml como uma string e atribuo para o componente da seguinte maneira xmlDoc.loadXML(sXml) porem esse xml está com o cabeçalho da seguinte maneuira "" porem após assinar quando carrego o xml assinado para uma variável através do comando sXmlTemp := xmlDoc.xml ele retornar com o cabeçalho sem o encoding , tentei alterar o cabeçalho depois de assinado mas o webservice considera o cabeçalho para a verificação da assinatura.
Alguma dica para que possa gerar o xml assinado com o encoding correto?
Muito obrigado!

Luís Gustavo Fabbro disse...

Sua mensagem saiu truncada, sem o trecho do XML de exemplo. De qualquer modo, você pode ajustar o encoding do XML com a função createProcessingInstruction.

Veja um exemplo no link http://msdn.microsoft.com/en-us/library/aa468560.aspx.

[]s

Unknown disse...

Estou com o mesmo problema, provedor Abaco, prefeitura de Canoas, o id (assim mesmo minusculo) o que ocasiona erra na hora de assinar, ninguém conseguiu nada?
Vlw

Anônimo disse...

Luís. Primeiramente quero agradecer pelos post's sobre assinatura digital.

Estou implementando um sistema para NFSe, mas sem os esquemas de validação de servidor. O que preciso mesmo é gerar o xml, assiná-lo e efetuar o envio. Seus post's me ajudaram muito a compreender o processo. Entretanto, estou com dificuldades na implementação da assinatura do xml. Tentei de várias formas utilizar os códigos que você postou, mas não está dando certo. Já gerei o xml no padrão correto, mas estou quebrando a cabeça para fazer a assinatura. Também já converti as dlls CAPICOM e MSXML para o Delphi 2010.

Se não for pedir muito, gostaria, se possível, que você me enviasse o código completo da assinatura do xml, meu e-mail é senajen@bol.com.br.

Obrigado.

Luís Gustavo Fabbro disse...

Que parte do processo não funcionou ? Quais são os sintomas ? Há alguma mensagem de erro ?

[]s

Anônimo disse...

Luís.

Sim, existem as seguintes mensagens de erro:

[DCC Error] Unit_assinarxml.pas(154): E2003 Undeclared identifier: 'PreparaXML'
[DCC Error] Unit_assinarxml.pas(155): E2003 Undeclared identifier: 'EncontraNodeSignature'
[DCC Error] Unit_assinarxml.pas(150): E2003 Undeclared identifier: 'getCert'

Luís Gustavo Fabbro disse...

O código dessas funções que o compilador reclamou não aparece no post; elas devem ser implementadas de acordo com suas necessidades. o GetCert, por exemplo, pode ser montado com base no post sobre o Certificate Store. No caso do PreparaXML e EncontraNodeSignature, você pode usar as interfaces do MSXML ou montar o XML correto com suas próprias ferramentas e carregá-lo com o IXmlDomDocument3.

[]s

marcosalles disse...

Então Luiz eu criei uma função EncontraNodeSignature e utizai o seu Xml deste exemplo . Porém quando tento atribuir o me da um erro que o no {http://portalfiscal.inf.br/nfe}Signature forncedo não corresponde ao nó esperado {http:/www.w3.org/2000/09xmldsig#}Signature

este erro ocorre neste ponto

NodePai := FindNodeURI( XMLDoc.ChildNodes, 'Signature', URIs.Strings[C]);
XMLDSig.signature := NodePai;

[]sds emuito obrigado

Anônimo disse...

Ok!

Obrigado pela dica Luís. Agora ficou entendido.

Bom trabalho e sucesso.
-------------------
João Sena

Luís Gustavo Fabbro disse...

Marcos

Mostre sua implementação do FindNodeURI. Pela mensagem de erro, essa função retornou o nó errado. Ela deveria retornar o nó Signature de dentro do nó NFe mas parece que, ao invés disso, encontrou a raiz do XML.

[]s

Anônimo disse...

Olá...

Consegui realizar alguns testes com a MSXML5 em Windows 7, Vista e XP, porém, no Windows 8 tive alguns problemas.

Sabes dizer se há alguma incompatibilidade entre MSXML5 e Windows 8 (32 bits)?

Abraços

Luís Gustavo Fabbro disse...

Apesar de a Microsoft não dar mais suporte a essa biblioteca, a tecnologia utilizada nela ainda é válida. Isto é, a Msxml5 deveria funcionar tb no windows8. Que tipo de falha acontece? Há mensagem de erro? Dependendo do cenário, pode ser falta de privilégio do usuário para realizar alguma tarefa - acessar o Registry, por exemplo.

[]s

marcelo bertolani disse...

Ola Luis,
existe alguma forma de calcular o hash do conteúdo com o algorítimo SHA-256 no método SignedData.Sign ? Estou precisando para assinar arquivos binarios.
atc

Luís Gustavo Fabbro disse...

Marcelo

Você pode usar o objeto HashedData do CAPICOM para calcular hashes manualmente usando o algoritmo SHA_256, dentre outros.

[]s

marcelo bertolani disse...

Luis,
eu estou usando o HashedData conforme exemplo abaixo, mas acredito que nao esta certo, o metodo Sign aplica outro hash (sha1) sobre a mensagem durante a assinatura, e o certo seria substituir o algoritimo que Sign usa. Obrigado pela atencao.

hash256 := CoHashedData.Create;
hash256.Algorithm := CAPICOM_HASH_ALGORITHM_SHA_256;
hash256.Hash(Content); // Content é a string
content := hash256.Value;

lSignedData := TSignedData.Create(nil);
lSignedData.Content := content;

msg := lSignedData.Sign(lSigner.DefaultInterface, True, CAPICOM_ENCODE_BINARY);

Luís Gustavo Fabbro disse...

Marcelo

Com o THashedData vc calcula o hash manualmente mas, ao passá-lo para o TSignedData, vc estará assinando esse hash com o algoritmo padrão do SignedData - que é o SHA1. Pelo que entendi, vc precisa apenas usar o TSignedData com o SHA256, sem passar pelos 2 passos. No entanto, não encontrei um meio de alterar o algoritmo padrão da classe de assinatura - creio que não seja possível com o CAPICOM.

Se o hash manual não for suficiente para seus propósitos, terá que encontrar outra ferramenta para assinar seu binário - talvez usando .NET.

[]s

marcelo bertolani disse...

Obrigado Luis, tb achei que era uma limitação do CAPICOM, obrigado pela confirmação.

Talles disse...

Luis, voce poderia dar uma idéia de como criar esse EncontraNodeSignature (xmlDoc); o noSignature pede um IXMLDOmElement e nao consigo retornar um objeto desse valor. consigo somente ixmldomnodes

Luís Gustavo Fabbro disse...

Talles

A maioria das funções trabalha com IXmlDomNode porque essa interface é a base para todos os tipos de nó dentro do XML. No entanto, os "filhos" de uma tag XML são tb tags de modo que vc pode convertê-los para IXmlDomElement sem problema.

Há algumas formas de você navegar pela estrutura DOM do XML. Partindo do nó raiz, você pode acessar os nós filhos até encontrar o desejado:

lista := xmlDoc.documentElement.childNodes;
for I := 0 to lista.length - 1 do
if (lista.item[i].nodeName = 'Signature') then
begin
noSign := lista.item[i] As IXMLDOMElement;
{...}
end
{else
ver se este nó tem filhos e analisá-los tb }

Outro meio seria levantar as tags com um determinar nome e analisá-las:

lista := xmlDoc.getElementsByTagName('Signature');

[]s

Talles disse...

Muito obrigado luis. consegui. Na verdade eu nao tava verificando os filhos. Obrigado

Unknown disse...

Luís, segui todos os passos e estou enfrentando o seguinte erro quando eu executo o xmlSign.signature := SignatureNode;

Project Project1.exe raised exception class EOleException with message 'O nó 'Signature' fornecido não corresponde ao nó esperado '{http://www.w3.org/2000/09/xmldsig#}Signature''.

Será que você poderia me dar uma ajuda para tentar descobrir o que é ?

O meu XmlPreparado() esta aqui : http://pastebin.com/78GKXCww

Grato

Luís Gustavo Fabbro disse...

A causa mais comum desse erro é a atribuição errada do nó signature à propriedade signature do objeto assinador. Certifique-se de que a variável SignatureNode é mesmo uma referência ao nó onde a assinatura residirá.

[]s

Unknown disse...

Luis, muito obrigado pelo sua disposição em ajudar, mas o erro persiste. Alguma outra idéia?

Para encontrar o node estou utilizando:

doc := XmlPreparado(edFileName.Text);
SignatureNode := doc.getElementsByTagName('ds:Signature').nextNode as IXMLDOMElement;

Depurando o SignatureNode.xml esta assim:

















Grato

Luís Gustavo Fabbro disse...

Vc consegue depurar o nó SignatureNode recuperado em seu código? Certifique-se de que ele é mesmo o nó esperado.
Ps: creio que vc tentou publicar no comentário o XML completo exportado por seu programa; infelizmente,o blogger truncou a mensagem e removeu essa parte...

Unknown disse...

Luis, eu postei o conteúdo do ctrl+f7 no pastebin.

http://pastebin.com/14U5tEFE

obs: eu postei ontem, mas acho que não atualizou corretamente.

Luís Gustavo Fabbro disse...

Duas coisas me chamam a atenção no trecho de XML postado por vc: está faltando a URI na tag de referência (veja no post como preparar essa tag); as tags do XML estão montadas como parte do namespace ds mas acredito que isso atrapalhe o processo de assinatura - experimente criar esse trecho como está no post, sem o namespace.

Ricardo Pascoal disse...

Luiz, montei uma classe através do xml data biding do delphi com base no schema nfe_v2.00.xsd e estou usando as interfaces para trabalhar.

Nessa classe tenho uma interface IXMLSignatureType_ds que é a que estou usando.

Me deparei com um problema que não estou conseguindo montar o nó e para depois partir para assinatura.
As demais informações acredito estar certo.

meu xml ficou assim :















Sabe como posso incluir essas tags ?
Abraço

Thairony G. Holz disse...

Olá, estou criando uma rotina completa para envio de NFe e CTe, toda feita em Delphi. Porém na parte da assinatura estou com grandes problemas.

Já fiz diversos testes, peguei muitos manuais e exemplos, e até agora nada. No exemplo que voce tem aqui, existe uma variavel do tipo "TCertificate". Eu importei o Capicom, e não existe esse tipo de variavel pra mim, apenas ICertificate e ICertificate2.
Porém, sendo muito parecidas, consegui implementar da seguinte forma:
http://pastebin.com/07bGYxhp

Consigo pegar o certificado, e todos dados dele (tanto que, ja estou com todas rotinas que nao dependem de assinatura digital funcionando).

Mas o meu real problema está na parte de selecionar o node Signature.
Estou tentando o seguinte:
xmlDoc := CoDOMDocument50.Create;
xmlDoc.loadXML(XML_Linha);

noSignature := xmlDoc.DocumentoDOM.selectSingleNode('Signature');

showmessage(noSignature.xml);

O unico "node" que consigo carregar é o node principal do XML..
o XML que tenho na variavel XML_Linha é:
http://pastebin.com/tBzv3k1w

ja tentei utilizar :
noSignature := xmlDoc.DocumentoDOM.selectSingleNode('Signature');
noSignature := xmlDoc.DocumentoDOM.selectSingleNode('ds:Signature');
e varias formas...

Poderia dar alguma luz do meu problema ?

Luís Gustavo Fabbro disse...

Ricardo

O blogger truncou sua mensagem e não é possível ver o XML.

PS: Vou tentar olhar os fontes do subversion que vc mandou no email do blog.

[]s

Ricardo Pascoal disse...

segue trecho do codigo : http://pastebin.com/1d6PmFk2

Luís Gustavo Fabbro disse...

Thairony

Você terá que navegar pela estrutura DOM do XML para encontrar o nó. Partindo do nó raiz, você pode acessar os nós filhos até encontrar o desejado, mais ou menos como no trecho abaixo:

lista := xmlDoc.documentElement.childNodes;
for I := 0 to lista.length - 1 do
if (lista.item[i].nodeName = 'Signature') then
begin
noSign := lista.item[i] As IXMLDOMElement;
{...}
end
else begin
{ ver se este nó tem filhos e analisá-los tb, até encontrar }
end;


[]s

Luís Gustavo Fabbro disse...

Ricardo

Que problema vc encontrou com esse código? Inclui-o nos fontes que vc me enviou e os nós reservados para a assinatura foram gerados conforme esperado.

Notei apenas 2 detalhes :
1) a referência ao ID da nota não precisa ser informada entre aspas; basta fazer a atribuição:

with xmlNFe.Signature do
SignedInfo.Reference.URI := '#NFe' + pChaveAcessoNFe;

2) Como vc não informou nada em contrário, o nó Signature acabou incluído no namespace padrão do XML, que é o da receita federal. No entanto, ele deve estar no namespace da W3C (http://www.w3.org/2000/09/xmldsig#). Por isso, esse atributo deve ser setado manualmente nesse nó e em seus filhos; algo como:

xmlNFe.Signature.Attributes['xmlns'] := 'http://www.w3.org/2000/09/xmldsig#';


[]s

Ricardo Pascoal disse...

Obrigado por responder !

Luis, estou com 2 problemas !

Quando tento gerar o nó vazio através do xmlNFe.Signature.AddChild( 'SignatureValue' )
ele está criando dessa forma


No seu exemplo acima, você encontra o nó da assinatura...
e no meu caso eu já tenho essa informação representada por xmlNFe.Signature imagino eu, correto ?!

Nesse caso não estou conseguindo atribui o nó a variavel xmlSign.signature := ??

veja exemplo : http://pastebin.com/1d6PmFk2

Grato

Luís Gustavo Fabbro disse...

Ricardo

Eu usei no exemplo outra técnica para criar os nós pois não gerei código pelo wizard do XML Binding; vc pode manter seu código sem problemas, com o mesmo resultado.

No seu caso, basta acessar a propriedade xmlNFe.Signature.SignatureValue para criar a tag automaticamente. Isso vale pra todas as propriedades que representam um nó da estrutura do XML

PS: nas classes geradas via XML Binding, o namespace padrão dos nós é o da Receita Federal. Mas, o Signature e seus filhos deverão ser transferidos manualmente para o namespace http://www.w3.org/2000/09/xmldsig#. Isto é, todos eles deverão ter o atributo xmlns atribuídos manualmente para que estejam no escopo correto.

[]s

Ricardo Pascoal disse...

Luís, tive que dar uma parada na NFe mas estou voltando. Bem você me disse que tenho que setar manualmente o namespace
http://www.w3.org/2000/09/xmldsig# para todos os filhos do nó Signature, até aqui blz... a unica coisa que não consegui fazer e tentar declarar o namespace uma unica vez para todos os filhos ao invés de ter que setar manualmente todos... sabe se tem algum metodo que resolveria isso... tentei alguns e não funcionou.
Grato

Ricardo Pascoal disse...

Luis, acho que consegui resolver o problema de setar o namespace somente do nó da assinatura, assim sendo os nós filhos devem ser reconhecidos com esse namespace.

Estou tendo problema com a mensagem de erro :
// The provided node '{http://www.portalfiscal.inf.br/nfe}Signature' does not match to expected node
// '{http://www.w3.org/2000/09/xmldsig#}Signature'

veja trecho do código : http://pastebin.com/rudy3jS4

Luís Gustavo Fabbro disse...

Ricardo

O problema é que usando o método SetAttributeNS, o Delphi está criando errado o nome de atributo para o namespace. Ao invés de ter somente xmlns há um preâmbulo ds:.
Veja:
<Signature ds:xmlns="http://www.w3.org/2000/09/xmldsig#">

O correto é:
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">

Eu já tinha conseguido gerar corretamente o XML usando uma função para forçar um novo namespace em um nó e todos os seus filhos, evitando ter que setar tudo na mão:

procedure ChangeNS (no:IXMLNode; ANovoNS: string);
var i : integer;
lista: IXMLNodeList;
begin
if (no.NodeType = ntElement) then begin
no.Attributes['xmlns'] := ANovoNS;
lista := no.ChildNodes;
for i := 0 to lista.Count - 1 do
ChangeNS(lista.Get(i), ANovoNS);
end;
end;

[]s

Ricardo Pascoal disse...

Grato pela resposta !
Fiz a procedimento acima e funcionou legal !

Thiago Balbino disse...

TBM tenho o mesmo problema como ABACO para Cachoeiro do Itapemirim - ES, uso o ACBR-NFSe mas para esse servidor que usa o "Id" com i maiúsculo dá erro ao assinar. alguém tem solução ?

Ps. para não ficar parado fiz um dll em C# e integro ela com o Delphi para assinar, só assim que deu certo, mas queria fazer via Delphi.

fenxflash disse...

Luís Gustavo Fabbro, o conhecimento em Delphi que contem no seu site é excelente.

Me ajude, eu não consigo usar o comando sign pois dá "erro não especificado"

signedKey := xmldsig.sign(dsigKey, $00000002);

antes não acontecia este erro mas passou a acontecer quando o webservice pediu que as tags na NFse ID ficassem em minúsculas "id". aí passou a dar este erro. isso se deve ao fato do certificado ser programado para não aceitar assinar xml com "id" e sim "Id" (com o i em maíusculo)?

Luís Gustavo Fabbro disse...

Fenxflash

Esse método de assinatura de XMLs leva em conta o XSD da W3C, que considera o atributo Id assim, com o i maiúsculo (veja aqui).

A tecnologia CAPICOM não tem mais suporte e, portanto, não se deve esperar mudanças em seu funcionamento. Por isso, se o seu XML deve mesmo ter o nome id só com minúsculas, você terá que procurar outro mecanismo para assiná-lo. Dá pra usar o .NET ou diretamente a API do Windows (função CryptSignMessage).

[]s

Volmir Lauermann disse...

Boa tarde Luís.

Esto encontrando um sério problema em inicializar o documento xmlDoc em c++. No seu exemplo ele está definido assim:
xmlDoc: IXmlDomDocument3

No c++ eu criei assim:
Msxml2_tlb::IXMLDOMDocument3 *xmlDoc;

então utilizo o seguinte código:
CoDOMDocument50::Create(&xmldoc);

Se eu dou um Build na unit, passa. No entanto, se dou um Build no projeto ocorre o seguinte erro:
[ilink32 Error] Error: Unresolved external 'Msxml2_tlb::CLSID_DOMDocument50' referenced from C:\PROJECTS\UTIL\NFSE_C\WIN32\DEBUG\NFSE.OBJ

Poderia me dar uma ideia do que pode estar ocorrendo, ou se eu estou fazendo a coisa pela forma correta?

Obrigado :)

Luís Gustavo Fabbro disse...

Volmir

Qdo vc importou a type library do MSXML, um arquivo fonte foi gerado. Esse arquivo deve ser adicionado ao projeto que está usando o MSXML para ser compilado e linkado junto.

[]s

Volmir Lauermann disse...

Luís, você estava certo, muito obrigado.

E quanto ao fato de CoDOMDocument50::Create(&xmldoc); sempre retornar NULL, o que pode ser?

IXMLDOMDocument3 *xmldoc;

Ex: eu testo o valor retornado com HRESULT hr = CoDOMDocument50::Create(&xmldoc);

e sempre fica negativo...

então testo da seguinte forma:

HRESULT hr = CoCreateInstance(CLSID_DOMDocument50,NULL, CLSCTX_INPROC_SERVER, IID_IXMLDOMDocument3, (void**)&xmldoc);

...continua falhando :/

Modificando o tipo para IXMLDOMDocument3Ptr xmldoc;

e instanciando da seguinte forma:

CoDOMDocument50::Create(&xmldocc)

falha... e utilizando

xmldoc.CreateInstance(CLSID_DOMDocument50,NULL,CLSCTX_INPROC_SERVER)

...continua não inicializando o objeto.

Falta realmente pouco para conseguir assinar um documento XML utilizando o C++ Builder, no entanto estes pequenos detalhes já me fizeram perder quase uma semana do meu tempo.

Alguma ideia?

Desde já agradeço seu feedback.

Luís Gustavo Fabbro disse...

Volmir

A melhor forma é a primeira indicada em seu comentário, usando o Create. Valores negativos retornados nessa função indicam um erro na criação da instância; recupere o valor e procure na internet o significado dele. Pode ser falta de privilégio de acesso ou outro erro associado ao seu ambiente.

[]s

Volmir Lauermann disse...

Luís, te agradeço de coração pela ajuda que você me deu até o momento, aprendi muito nesses últimos dias e com certeza sem sua ajuda não teria avançado até o ponto em que estou neste projeto.

Os problemas que te apresentei estava relacionado à falta de registro das dll 'msxml5.dll' no windows. Nem sonhando eu imaginaria que para utilizar algum recurso oferecido por uma dll ela deveria estar registrada no sistema operacional.

Outra coisa que gostaria de comentar é que o sistema de cadastro em seu blog pelo login do gmail não está funcionando.

Forte abraço e sucesso em seus projetos.

Luís Gustavo Fabbro disse...

.Obrigado, Volmir

Parece que o Google desativou o FriendConnect.... Vou ver o que pode ser usado para substituí-lo; talvez uma página no Facebook.

[]s

Volmir Lauermann disse...

Boa tarde Luís.

Estou com uma dúvida aqui e posto ela para ver se você faz ideia do que pode estar acontecendo.

Eu tenho no arquivo *.xml que quero assinar o node "Signature" e dois dos nodes filhos são <\X509Data><\X509Certificate>.

Quando eu executo a assinatura do documento, utilizando o seguinte método:
signedKey = xmldsig->sign(dsigKey, _XMLDSIG_WRITEKEYINFO::CERTIFICATES);

e o resultado é o documento acrescido das chaves pertinentes. No entanto, ao invés de inserir o valor de no local já criado no documento, o método cria duas vezes estes nodes, <\X509Data><\X509Certificate> com os dados criptografados, fato que me obrigou a remover via programação os nodes excedentes.

Tem alguma forma de evitar esta reinclusão dos nodes no documento durante a assinatura dele?

Obrigado pela atenção.

Luís Gustavo Fabbro disse...

Volmir

Não consegui reproduzir o efeito descrito por vc. É possível que haja algum outro tratamento em seu código duplicando o nó Verifique trechos onde se faça importação ou criação de nós, até mesmo exportando o XML a cada passo.

[]s

Volmir Lauermann disse...

Obrigado Luís... o problema estava na definição do corpo do XML. O node signature estava sendo criado manualmente antes da assinatura.

Uma pergunta: Você já enviou alguma vez uma NFSe para os servidores da Betha?

Abraços.

Luís Gustavo Fabbro disse...

Na ABC71 nós não tratamos automatização das Notas de Serviço. Como cada cidade define regras próprias, acaba sendo complicado incluir isso num software com Clientes em tantos locais distintos.

[]s

Egon Klipstein disse...

Egon Klipstein

Estou desenvolvendo um software para CRONOTACOGRAFO, o envio do XML é para o INMETRO, método muito parecido com NFe. Tenho que assinar o arquivo XML nos mesmos padrões da NFe. Não tenho experiência com assinatura, sempre usei ACBR, que faz tudo internamente. Encontrei alguns exemplos, mas me pareceu grego pelo meu desconhecimento, retorna umas mensagens que não faço ideia de o que significa. Existe alguma função, componente que faça a assinatura desse documento ? Ou alguém que possa me fornecer esse código ?
Meu email é samadruga@hotmail.com

Luís Gustavo Fabbro disse...

Egon

Há uma série de posts aqui no blog mostrando como fazer a assinatura de XML, tento em C# quanto em Delphi. Vc encontrou problemas ao aplicá-los? Que mensagem é reportada?

[]s

Egon Klipstein disse...

A seleção do certificado digital está OK, consigo deixa-lo em uma variável do tipo ICertificate2, porém o que ainda não entendi é o processo de assinatura. Com base nos POSTs criei uma função para fazer a assinatura do conteúdo:function SA_Assinar_Conteudo(content : WideString; cert : ICertificate2):WideString;
var
lSigner : TSigner;
lSignedData : TSignedData;
msg : WideString;
begin
//---------------------------------------------------------------------------
// Configura o objeto responsável por fazer a assinatura, informando qual é o
// certificado a ser usado e o conteúdo a ser assinado
//---------------------------------------------------------------------------
lSigner := TSigner.Create(nil);
lSigner.Certificate := cert;
lSignedData := TSignedData.Create(nil);
lSignedData.Content := content;
//---------------------------------------------------------------------------
// Efetivamente assina o conteúdo
//---------------------------------------------------------------------------
msg := lSignedData.Sign(lSigner.DefaultInterface, true, CAPICOM_ENCODE_BASE64);
//---------------------------------------------------------------------------
lSignedData.Free;
lSigner.Free;
result := msg;
end;

Esta função me devolve um conteúdo que presumo deverá ser colocado na TAG "SignatureValue", outro valor extraído da seguinte forma
cert.Export(CAPICOM_ENCODE_BASE64) deve ser colocado na TAG "X509Certificate", está faltando dados para a TAG "DigestValue", não faço idéia de onde tirar este valor. Tentei usar o exemplo deste POST, que pelo que entendi, não se faz necessário fazer no DEDÃO, porém pelo meu desconhecimento, fica muita lacuna no meu código. Obs,: As informações deste BLOG são fantásticas, eu é que não estou a altura, mas estou tentando.

Luís Gustavo Fabbro disse...

Egon

Experimente usar a interface IXMLDigitalSignature, como no post. Ela não só calculará a assinatura mas também preencherá os valores de digest, entre outros.

[]s

Egon Klipstein disse...

Desculpe a insistência. Tenho uma dúvida com a assinatura, olhando o exemplo fiquei um pouco confuso, a linha xmlDoc := PreparaXML (); e noSignature := EncontraNodeSignature (xmlDoc);
eu não consegui localizar dentro do BLOG. A partir deste ponto, não consegui encontrar qual a relação entre a variável XMLDOC e a instrução xmlSign.sign(xmlDsigKey, NOKEYINFO);, ou seja, onde eu informo para esta função o conteúdo a ser assinado ? Obs.: vou seguir pesquisando.Segue abaixo o código exemplo que peguei do BLOG.

var {...}
xmlDoc: IXmlDomDocument3;
noSignature : IXMLDOMElement;
xmlSign : IXMLDigitalSignature;
xmlDsigKey: IXMLDSigKey;
begin
{...}
{ Monta o XML e prepara a tag Signature }
xmlDoc := PreparaXML ();
noSignature := EncontraNodeSignature (xmlDoc);

if (noSignature <> Nil) then begin
xmlSign := CoMXDigitalSignature50.Create;
xmlSign.signature := noSignature;
xmlSign.store := store;

{ Monta a chave com o tipo exigido pelo método Sign }
xmlDsigKey := xmlSign.createKeyFromCSP(lKey.ProviderType, lKey.ProviderName, lKey.ContainerName, 0);
{ Assina o XML }
xmlSign.sign(xmlDsigKey, NOKEYINFO);
{...}
end;
end;

Luís Gustavo Fabbro disse...

Egon

A função PreparaXML monta manualmente o documento XML completo para ser assinado, incluindo a tag Signature e suas filhas (sem os valores, que serão calculados). Ela retorna um IXmlDomDocument3 com essa estrutura pronta pra receber a assinatura.

Então, a função EncontraNodeSignature percorre a estrutura para localizar o ponto (IXMLDOMElement) correspondente ao início do trecho que receberá a assinatura (nó Signature). Esse nó é repassado ao xmlSign junto com o certificado. Só então a assinatura é calculada, preenchendo os valores nos nós correspondentes do XML preparado.

[]s

Egon Klipstein disse...

Muchas gracias pela atenção, ótimo conteúdo. Consegui fazer a assinatura.

Enio Maxson disse...

Olá Luís!
Estou utilizando seus posts sobre nota fiscal como base para adicionar ao nosso sistema essa funcionalidade.
Meu problema é está na hora de recuperar privateKey do objeto cert, que é atribuído ao lkey.
Você poderia verificar o meu código, https://gist.github.com/eniomaxson/71e82562449f6b0ea14a.

Muito bom seus posts!

Luís Gustavo Fabbro disse...

Enio

Que problema ocorre quando vc recupera o private key? Dá alguma mensagem de erro?

[]s

Enio Maxson disse...

Acess Violation na linha que atribuo o Private Key ao lkey
(lKey := cert.PrivateKey), como se o objeto cert estivesse nulo.

Luís Gustavo Fabbro disse...

Enio

Testei seu código com uma nota minha e meu certificado digital e funcionou corretamente. Verifique se a sua instalação do certificado não está corrompida; pode ser que a cadeia de validação do seu certificado não esteja presente. Instalar a cadeia toda pode resolver o problema.

[]s

Enio Maxson disse...

Estou verificando se tem alguma coisa errada com a instalação do certificado.

Enio Maxson disse...

Luís,

consegui resolver o problema da assinatura, pesquisando em fóruns encontrei uma dica para trocar o tipo dos objetos Cert, Certs, Store para OleVariant.
Agora estou querendo remover quebras de linhas do XML, deixando tudo em um unica linha do arquivo, com TxmlDocument é apenas uma configuração que passo e ele já faz, você sabe como faço isso com CoDOMDocument50 do msxml?

Enio Maxson disse...

Luís,

um outro detalhe é que o valor do SignatureValue esta com quebra de linha e espaço em branco, existe algum parâmetro ou coisa do tipo, que eu possa informar na hora de chamar o método que assina para remover esses caracteres?

Luís Gustavo Fabbro disse...

Enio

Você pode usar a interface IMXWriter para controlar a formatação do XML exportado. Há um exemplo em C++ no endereço http://en.verysource.com/code/5902731_1/maindlg.cpp.html.

[]s

Enio Maxson disse...

Luís,

muito obrigado, já deu pra fazer muito com seu tutorial!

rodrigo e nunes disse...

Olá Luis,

Estou tentando assinar um LoteRps para a NFSe de PoA e portal acusa erro de assinatura digital inválida. Este pacote possui uma Rps. Este erro de assinatura só surge qdo o documento interno do Lote é assinado, ou seja, parece-me que o surge quando tenta-se assinar um documento que já tenha assinatura. Você sabe algo a respeito de algum problema desta natureza?
No aguardo, desde já agradeço
Rodrigo

Luís Gustavo Fabbro disse...

Rodrigo

O problema é só quando assina de novo um documento já assinado? Não conheço as regras para NFSe; será necessário assinar os dois pontos? Isto é, acredito que a assinatura deva ser ou para cada documento interno ou apenas para o lote com os documentos incluídos.

O que o manual diz a respeito?

[]s

rodrigo e nunes disse...

Olá Gustavo,

Obrigado pelo retorno, mas sim, o manual explica que é necessário ter duas assinaturas. Uma para o Rps e outra para o Lote. Como o lote contempla pelo menos uma Rps, então são necessárias, infelizmente, duas assinaturas no arquivo XML.
Aguardo por algum retorno. Desde já agradeço.

Rodrigo

Luís Gustavo Fabbro disse...

Rodrigo

A mensagem que te reportaram diz que a assinatura está inválida. Verifique no manual como deve ser a assinatura que engloba o documento todo.

Qual (ou quais) tags devem ser consideradas na assinatura; onde a tag com a assinatura deve ser incluída?

[]s

rodrigo e nunes disse...

Olá Gustavo. Obrigado mais uma vez pelo seu retorno. O problema anunciado foi resolvido. Não estava assinando pq o XML interno, dentro do XML maior, deveria ter, em uma de suas tags, a declaração do atributo do namespace. Feito isto, deixado normalizados os namespaces dos XMLs, aí sim, foi possível fazer a assinatura sem erros. Mais uma vez, obrigado pela atenção. Rodrigo.

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.