menu

SHARKLABS

Assinatura Digital: Como assinar um XML (nota fiscal eletrônica) com Ruby

/
/
Assinatura Digital: Como assinar um XML (nota fiscal eletrônica) com Ruby
bookmark Ruby access_time

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.

Autor
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." Martin Fowler