15 de maio de 2009

Assinando um documento XML

Agora que tenho assentada uma terminologia XML e métodos para criar um XML (incluindo o cabeçalho) posso mostrar como assinar digitalmente um documento XML e incluir nele essa assinatura.

Antes, uma consideração importante: a Receita Federal só aceita a Nota Fiscal Eletrônica assinada com um Certificado Digital autorizado, isto é, não vale criar um certificado de teste (com o Certificate Creation Tool, por exemplo) e tentar enviar a nota usando ele. Alguns emissores de certificados autorizados: Correios, Serasa e Certisign.

A Receita Federal usou um subconjunto do padrão do W3C para estipular as regras de assinatura digital da NFe. Devem ser assinados todos os elementos infNFe de um lote XML de Notas Fiscais segundo essas regras. Considerando uma variável do tipo XmlDocument de nome doc, já montada com a estrutura correta da NFe, segue um exemplo em C# de como chegar a esse nó:
XmlNodeList ListInfNFe = doc.GetElementsByTagName("infNFe");

Há uma classe no namespace System.Security.Cryptography.Xml do framework .NET chamada SignedXml, que implementa o padrão W3C para assinatura de documentos e verificação de documentos assinados. O trecho de código abaixo exemplifica a configuração básica desta classe:
foreach (XmlElement infNFe in ListInfNFe)
{
string id = infNFe.Attributes.GetNamedItem("Id").Value;
signedXml = new SignedXml(infNFe);
signedXml.SigningKey = ObtemCertificado().PrivateKey;

O id obtido é um código calculado segundo regras estipuladas pela Receita Federal e serve para identificar cada Nota. Esse id é inserido como um atributo no elemento infNFe. Ele será usado mais adiante, quando for preciso referenciar o elemento infNFe na assinatura. A função ObtemCertificado é minha: ela usa os conceitos descritos no post sobre Certificate Store do Windows para obter um certificado válido. A propriedade SigningKey é a chave que será usada para calcular a assinatura; por isso, é atribuido a ela a chave privada do certificado obtido.

Parte da assinatura é dedicada a descrever o nó a partir do qual a assinatura foi feita e quais transformações esse nó e seus filhos sofreram antes da assinatura ser efetivamente calculada. De acordo com a documentação da Receita Federal, as transformações a serem aplicadas são duas:
1) a que indica que a assinatura é "envelopada", ou seja, o trecho assinado é incluído na assinatura. Aqui cabe um esclarecimento: o elemento infNFe não é o elemento assinado; ele na verdade é usado para calcular um valor chamado de DigestValue, este sim inserido no trecho assinado, de forma que qualquer alteração no conteúdo de infNFe ou de seus filhos resultará num DigestValue diferente e, por fim, a assinatura também não será válida.
2) a que indica que o XML deve ser colocado na forma canônica antes do processamento.

A classe Reference cuida dessa parte do processo, incluindo a identificação do nó infNFe e as transformações exigidas:
// Transformações p/ DigestValue da Nota
Reference reference = new Reference("#" + id);
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
reference.AddTransform(new XmlDsigC14NTransform());
signedXml.AddReference(reference);

Antes de computar a assinatura, falta configurar a parte que trata das informações a respeito do certificado digital utilizado. É com base nesses dados que a Receita Federal consegue validar a assinatura e atestar que nenhuma informação da Nota foi modificada depois que o emissor da Nota Fiscal a assinou. Devemos incluir uma cláusula com os dados do certificado:
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(ObtemCertificado()));
signedXml.KeyInfo = keyInfo;

Agora, só falta computar a assinatura:
signedXml.ComputeSignature();

Neste ponto, a variável signedXml contém todos os dados para criar o elemento Signature que deve ser incluído na NFe antes do envio à Receita Federal. Mostro no próximo post como fazer isso.

8 comentários :

Anônimo disse...

Parabéns pelo blog, estou desenvolvendo um sistema que gera nota fiscal eletronica pra Salvador e na hora de assinar seguindo seu exemplo da um erro em signedXml.ComputeSignature(): "Elemento de referência mal formado."
Se puder dar uma ajuda eu agradeço
Valeu

Luís Gustavo Fabbro disse...

Fiz um teste rápido aqui e consegui reproduzir esse erro quando o nó que será assinado não possui um atributo com o nome de Id.

Veja se o nó XML que você passou para o SignedXml possui esse atributo.

Anônimo disse...

Era exatamente isso, muito obrigado, mas pra mim continua o problema pq eu preciso assinar nós sem id. Comparando com seu exemplo de xml eu teria que assinar os nós envieNFe e NFe.
Obrigado mais uma vez

arturcostta@gmail.com

Luís Gustavo Fabbro disse...

Neste caso, sua referência pode ir em branco, isto é, para instanciar o Reference, use algo como :
Reference reference = new Reference("");

Não conheço o formato da NFe de Salvador e não sei se colocar a referência em branco implicará numa assinatura inválida.

Na prática, a inclusão de um valor de referência apenas gera um atributo chamado URI que remete ao ID do nó que está sendo assinado.

Veja um exemplo da aparência da assinatura com o Reference e URI preenchidos neste link.

Unknown disse...

Estou com problemas de assinatura na nota de serviço eletrônica.
Testei para ver se poderia ser o valor de digestValue e atribui o value do attr ID mas da tag infRps, no meu caso. Mesmo assim não valida. Computa certinho e as tags estão em ordem, mas a assinatura está sempre inválida.
Alguma sugestão de um erro tolo que possivelmente pode ser cometido? hahah

Luís Gustavo Fabbro disse...

Aline

O erro é de assinatura inválida? Você seguiu os passos descritos neste outro post para incluir a assinatura no XML?

[]s

Unknown disse...

Então, Luís, acabei de resolver meu problema. Após instanciar Reference e KeyInfo e computar a assinatura, estava separando as tags e definindo os valores para inserir no XML.
Inclusive como você sugere na continuação do post (http://balaiotecnologico.blogspot.com/2009/05/incluindo-assinatura-digital-no-xml.html)
Entretanto, sempre constava como inválida a assinatura. Durante uma semana quebrei a cabeça com este problema. Em todos os fóruns e blogs a solução se mostrava desta forma, mas de uma maneira mais fácil encontrei a solução removendo todo o código:

XmlElement xmlSignature = xmll.CreateElement("Signature", "http://www.w3.org/2000/09/xmldsig#");
XmlElement xmlSignedInfo = arquivoXML.SignedInfo.GetXml();
XmlElement xmlKeyInfo = arquivoXML.KeyInfo.GetXml();
XmlElement xmlSignatureValue = xmll.CreateElement("SignatureValue", xmlSignature.NamespaceURI);
string signBase64 = Convert.ToBase64String(arquivoXML.Signature.SignatureValue);
XmlText text = xmll.CreateTextNode(signBase64);
xmlSignatureValue.AppendChild(text);
xmlSignature.AppendChild(xmlSignatureValue);
xmlSignature.AppendChild(xmll.ImportNode(xmlSignedInfo, true));
xmlSignature.AppendChild(xmll.ImportNode(xmlKeyInfo, true));

por apenas:

XmlElement elemento= arquivoXML.GetXml();
XmlElement xmlElemnt = (XmlElement)xmll.GetElementsByTagName("ListaRps")[0] as XmlElement;
xmlElemnt.LastChild.AppendChild(elemento);

E a assinatura validou na receita federal.
Me autoriza a postar a fonte em seu blog?

Luís Gustavo Fabbro disse...

Que bom que funcionou, Aline.

Publiquei acima o trecho de código que vc enviou. Caso queira publicar algo mais, fique à vontade pra comentar aqui ou enviar para o email do blog.

[]s

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.