Assinatura Digital: Como assinar um XML (nota fiscal eletrônica) com Ruby
Você precisa emitir notas fiscais eletrônicas com Ruby e não sabe como fazer a assinatura digital do XML? Neste artigo vamos esclarecer este assunto.
Nota fiscal eletrônica VS Programador brasileiro
A primeira vez que ouvi falar de nota fiscal eletrônica foi 2008. No primeiro momento, eu e a maioria dos programadores não fazia ideia do que se tratava.
Inicialmente só era obrigatório para alguns segmentos, mas já começava a dar dor de cabeça. Havia pouca documentação e trazia conceitos tecnológicos pouco difundidos no mercado brasileiro.
Embora muita gente não sabia o que era WebServices e XML, isso não foi uma pedra no sapato. O principal problema foi a assinatura digital, algoritmo complexo e com pouco material digital e bibliográfico.
Maturação da nota fiscal eletrônica
Atualmente a nota fiscal eletrônica é um projeto maduro e o mercado brasileiro está totalmente adaptado. A assinatura digital já é um conceito mais difundido e a maioria das linguagens já estão possuem bibliotecas bem estáveis.
Porém recentemente precisei integrar um software Ruby com a nota fiscal eletrônica e percebi que quase não existia documentação. Por isso decidi escrever este artigo.
Antes de continuar gostaria de fazer uma sugestão ao SEFAZ que é o responsável pelo projeto. Na minha opinião o SEFAZ deveria criar uma SDK da nota fiscal eletrônica para as linguagens mais populares com o objetivo de facilitar a integração.
Biblioteca Ruby para assinar o XML
A biblioteca Ruby que vou utilizar para assinar o XML se chama XMLDSIG. No momento que escrevo este artigo ela está na versão 0.6.6 e é considerada estável, embora faltem algumas funcionalidades, mas o que já foi implementado está funcionando bem.
Vale ressaltar que o material de apoio relacionada a esta biblioteca é limitado, porém seu código fonte é pequeno e de fácil leitura.
Seu GEMFILE deve ficar desta maneira:
source 'https://rubygems.org'
gem 'xmldsig', '~> 0.6.6'
gem 'nokogiri', '~> 1.10', '>= 1.10.1'
Certificado digital
Antes de continuar você precisará de um certificado digital no formato PEM e sem criptografia da chave privada.
Se você não entendeu do que estou falando, recomendo que você leia este artigo que escrevi. Nele eu falo sobre os certificados PKCS12 e como converter para o formato PEM utilizando o OpenSSL.
Nota fiscal: Exemplo
Vou utilizar um formato de XML fictício, ou seja, não é o mesmo formato utilizado pelo SEFAZ.
Quando eu testei esta biblioteca, eu testei com a nota fiscal eletrônica de serviço da minha cidade. Lembrando que as notas fiscais eletrônicas de produto seguem outro padrão.
Vale ressaltar que independentemente do formato do XML o algoritmo da assinatura digital é o mesmo.
<?xml version="1.0" encoding="UTF-8"?>
<invoice ID="invoice" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
<company>
<name>My Store</name>
</company>
<customer>
<name>john</name>
<address>
<street>name street</street>
<city>name city</city>
</address>
</customer>
<products>
<product>
<name>T-shirt</name>
<quantity>1</quantity>
<price>10</price>
</product>
</products>
</invoice>
Headers da assinatura digital
Diferentemente de bibliotecas mais estáveis de outras linguagens, você deve adicionar a estrutura (headers) da assinatura digital manualmente. O seu XML deve ficar da seguinte maneira:
<?xml version="1.0" encoding="UTF-8"?>
<invoice ID="invoice" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
<company>
<name>My Store</name>
</company>
<customer>
<name>john</name>
<address>
<street>name street</street>
<city>name city</city>
</address>
</customer>
<products>
<product>
<name>T-shirt</name>
<quantity>1</quantity>
<price>10</price>
</product>
</products>
<ds:Signature>
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#invoice">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
<ds:XPath>not(ancestor-or-self::ds:Signature)</ds:XPath>
</ds:Transform>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces PrefixList="invoice"/>
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue></ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue></ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate></ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</invoice>
Assinando o XML
Basicamente o algoritmo para assinar digitalmente o XML da nota fiscal eletrônica fica assim:
require 'xmldsig'
require 'nokogiri'
def load_xml(xml_input)
Nokogiri::XML(File.open(xml_input)).to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML)
end
def sign_xml(unsigned_xml, pem_file)
private_key = OpenSSL::PKey::RSA.new(File.read(pem_file))
unsigned_document = Xmldsig::SignedDocument.new(unsigned_xml)
unsigned_document.sign(private_key)
end
def add_cert_to_xml(signed_xml, pem_file, path)
signed_xml = Nokogiri::XML(signed_xml)
certificate = '';
OpenSSL::X509::Certificate.new(File.read(pem_file)).to_pem.each_line do |line|
certificate += line unless /^-{5}/.match(line)
end
signed_xml.xpath(path).each do |element|
element.content = certificate
end
signed_xml.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML)
end
def export_xml(xml_text, xml_file)
File.write(xml_file, xml_text)
end
xml = load_xml('invoice-unsigned.xml')
xml = sign_xml(xml, 'file.pem')
xml = add_cert_to_xml(xml, 'file.pem', 'invoice//ds:Signature//ds:KeyInfo//ds:X509Data//ds:X509Certificate')
export_xml(xml, 'invoice-signed.xml')
É um algoritmo relativamente simples, mas eu gostaria de esclarecer um ponto. A biblioteca XMLDSIG somente assina o XML, ou seja, preenche as tags DigestValue e SignatureValue.
Se você olhar o código com mais atenção, verá o que a chave pública do emissor do certificado digital é inserida manualmente no XML (método add_cert_to_xml). Quem sabe futuramente esta funcionalidade seja implementada.
Como falei no início, o meu objetivo com este artigo é difundir o conceito de assinatura digital para nota fiscal eletrônica com Ruby. Espero que outros programadores se engajem com a iniciativa e façam suas contribuições.
Dúvidas ou sugestões é só entrar em contato. Abraço.