Como assinar o XML em um nó específico (Nota Fiscal)

Bom dia pessoal, estou com dúvidas sobre assinatura em XML em um nó específico do arquivo.

Estou implementando um sistema PHP para emissão automática de nota fiscal de serviço. Atualmente consigo assinar o arquvio XML e enviar via SOAP para o servidor da prefeitura de Salvador/BA.

Entretanto, ao enviar, recebo o erro “Assinatura digital da NFTS incorreta”. Ao estudar a estrutura do XML modelo, vi que existe um campo no XML onde precisa ter uma assinatura da nota fiscal, e não o Signature que assina o documento.

Segue abaixo o modelo da nota fiscal que peguei no site da prefeitura:

<PedidoEnvioNFTS xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://nfse.salvador.ba.gov.br/nfts">
<Cabecalho Versao="1" xmlns="">
    <Remetente>
        <CPFCNPJ>
            <CNPJ>57529050000188</CNPJ>
        </CPFCNPJ>
    </Remetente>
</Cabecalho>
<NFTS xmlns="">
    <TipoDocumento>01</TipoDocumento>
    <ChaveDocumento>
        <InscricaoMunicipal>13580200127</InscricaoMunicipal>
        <SerieNFTS>A</SerieNFTS>
        <NumeroDocumento>202</NumeroDocumento>
    </ChaveDocumento>
    <DataPrestacao>2014-10-02</DataPrestacao>
    <StatusNFTS>N</StatusNFTS>
    <TributacaoNFTS>T</TributacaoNFTS>
    <ValorServicos>100</ValorServicos>
    <ValorDeducoes>0</ValorDeducoes>
    <CodigoServico>103</CodigoServico>
    <AliquotaServicos>0.03</AliquotaServicos>
    <ISSRetidoTomador>false</ISSRetidoTomador>
    <Prestador>
        <CPFCNPJ>
            <CNPJ>32250824000106</CNPJ>
        </CPFCNPJ>
        <RazaoSocialPrestador>Prestador Teste</RazaoSocialPrestador>
        <Endereco>
            <CEP>44020200</CEP>
        </Endereco>
        <Email>jose@uol.com.br</Email>
    </Prestador>
    <RegimeTributacao>0</RegimeTributacao>
    <Discriminacao>Emissao de NFTS</Discriminacao>
    <TipoNFTS>1</TipoNFTS>
    <Assinatura>ODY+3VzyGzi3hTi+FtXcWYputJtZat4txp8fQBdMPkei1DsWsd02vpubTUyLLYpE/1cXzlbJFEbKdV3uf3n4LP3rWlUgmWDat+NjCakSysqxq42UCqIQ9Se4QWYKV4pyEIX8iAaB5RmBzzRme2bdLDkcVP+G9FYNqlzdrBhhAwqRR+kkio6Htor17a9WspFIHPVMwKAIobqCvSXEuhvCWMLS3oX47VIkHG/iAD/mZgaJiyIvYwl+jylGZOYw3tZ27Jmk/r2n9IQgHegNo6IMch7m7vgTOVEtilOi0Gx/otfwhj5pRIPVB8txHeu4KFGjzs6sHlcxwhpcppO3pTngEA==</Assinatura>
</NFTS>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <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="">
            <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>X6CiD8MsW3rwvIgL1EflzMttwK8=</DigestValue>
        </Reference>
    </SignedInfo>
    <SignatureValue>vI50jrPlWfWUq0ld1X9STxFGDfI327knRq3zg/CNtH+X0aoYzYoOPdSKJ2LDdPrrsQFGTaVPqne1XioPmnJpwguVJ5BSlHZy0ebtm2isMkB1N1yjYSv9eUoFzKGksCwNIeI/KB6d780fOAloQ31Gy1sLSDbIcn7B8ylJT5u3nsPBgPRgjHX6fQVNTBJTF0DrbLAkAhzUYw4oBigZvHSfPZ5kxgH91LBTYJfJxHHMAiFqfj+QoUR3f7W5logC/Qu7lz5m0CoCmCMrYa4YK3qyFAFXXDAsqhNbXM6OomQyBR98RdbsiyRncGiz7AktG/bbu9Yf+Xfd0EzpfDKTNXWtgA==</SignatureValue>
    <KeyInfo>
        <X509Data>
            <X509Certificate>_CERTIFICADO_</X509Certificate>
        </X509Data>
    </KeyInfo>
</Signature>

Logo abaixo do 1 tem um nó chamado com uma assinatura digital da nota fiscal, não do documento.

Já busquei no google e até agora não identifiquei como fazer a assinatura nesse campo específico, só lembrando que segundo a documentação da Prefeitura, não pode ser cópia do nó que tem quando assina o documento xml, ou seja, no caso, é uma assinatura nova apenas para o documento.

Atualmente estou utilizando a biblioteca Greenter (github) para assinar o arquivo xml, e já chequei a validade da assinatura e está tudo normal, e pelo que tudo indica o que está dando erro é a falta de assinatura da nota fiscal no nó .

Alguém saberia como me ajudar nesse ponto?

Verifique esse meu gist para saber como assinar o XML:
https://gist.github.com/luizvaz/6c3e2c1e6bc7d310898107c9f95476bb

A função assinaXML($docxml, $tag) recebe um XML e o nome da TAG para assinar.
Repare que os arquivos do certificado estão referenciados dentro da função.
Eles precisam estar no formato PEM.

Obrigado pela informação, só que agora estou com dúvida, consigo assinar na tag específica, mas na hora de assinar o lote, para enviar, está retornando que a assinatura não confere. Quando gero apenas uma assinatura e checo no site da receita federal, diz que a assinatura está válida, mas quando gero as 2, da erro de que a assinatura não é válida.
O que estou fazendo é gero um xml com a nota assinada na primeira vez (assina o rps), e depois gero outro xml com a nota assinada no lote, pegando já o xml assinado no rps, mas da erro.
Não estou utilizando o seu repositório como exemplo não, porque estou desenvolvendo o meu próprio, mas a lógica é a mesma, teria como me ajudar?

Nesse caso, o que deve ter acontecido é que não está mantendo o conteúdo assinado de forma intacta. Não pode reformatar ou retirar espaços e quebras de linha.

Mas de qualuer forma, consultado o site.
Seu modelo não confere com o deles.
https://nfse.sefaz.salvador.ba.gov.br/OnLine/Documentos/XML.zip
https://nfse.sefaz.salvador.ba.gov.br/OnLine/Documentos/XSD.zip

O envio do RPS é através do EnviarLoteRpsEnvio.

Note que tem o Lote e a Lista de RPS.
O RPS é assinado pela tag InfRps após o conteúdo e a assinatura fica dentro da tag Rps.
A tag InfRps possui um Id que é referenciado na assinatura, <Reference URI="#001">.

A assinatura do LoteRps fica após o conteúdo e dentro da tag EnviarLoteRpsEnvio.
A tag LoteRps possui um Id que também é referenciado na assinatura, exemplo <Reference URI="#002">.

Se você obedecer essas regras, deve funcionar.

Meu código já faz isso.
Basta você passar o conteúdo duas vezes para a função.

Obrigado mais uma vez pela resposta, mas eu estou seguindo exatamente essa ordem, mas quando tento validar, ele da erro.
Se eu assinar apenas uma vez o documento, seja ele onde for, funciona, entretanto quando tento fazer a segunda assinatura que da o erro.
Eu to fazendo um modelo da nota fiscal, e assina ela no espaço Rps. Depois disso, antes de enviar eu uso a mesma função para assinar, só que agora no campo EnviarLoteRpsEnvio.
Minha nota fiscal sai igual ao modelo que o funcionário da SEFAZ Salvador me enviou, mas mesmo assim, não consigo nem enviar e nem validar ela na receita federal.
Vou tentar usar o seu código para ver o que consigo.

Inclusive estou utilizando a mesma biblioteca que você, do robrichards, e não estou reformatando nem nada o xml quando tento assinar ele pela segunda vez.

Estou fazendo assim na função:


$doc = new DOMDocument();
$doc->load('nota.xml');
$objDSig = new XMLSecurityDSig('');
$objDSig->setCanonicalMethod(XMLSecurityDSig::C14N);
$objDSig->addReference(
    $doc, 
    XMLSecurityDSig::SHA1, 
    ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', 
		'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'],
	['force_uri' => true, 'uri' => 'rpsId23253']	
);
$objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type'=>'private'));
$objKey->passphrase = '1234';
$objKey->loadKey(dirname(__FILE__).'/cert.pem', true);
$objDSig->sign($objKey);
$objDSig->add509Cert(file_get_contents(dirname(__FILE__).'/cert2.pem'));
$objDSig->appendSignature($doc->getElementsByTagName('Rps')->item(0));
//$objDSig->appendSignature($doc->documentElement);
$doc->save('notaassinada.xml');

$doc = new DOMDocument();
$doc->load('notaassinada.xml');
$objDSig = new XMLSecurityDSig('');
$objDSig->setCanonicalMethod(XMLSecurityDSig::C14N);
$objDSig->addReference(
    $doc, 
    XMLSecurityDSig::SHA1, 
    ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', 
		'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'],
	['force_uri' => true, 'uri' => 'AGZ001']
);
$objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type'=>'private'));
$objKey->passphrase = '1234';
$objKey->loadKey(dirname(__FILE__).'/cert.pem', true);
$objDSig->sign($objKey);
$objDSig->add509Cert(file_get_contents(dirname(__FILE__).'/cert2.pem'));
$objDSig->appendSignature($doc->getElementsByTagName('EnviarLoteRpsEnvio')->item(0));
//$objDSig->appendSignature($doc->documentElement);
$doc->save('notaassinada.xml');

Sua implementação está quase certa.
Você precisa adicionar ao Pai do Nó após assinado o conteúdo.
Por isso está dando errado.
Você está modificando o conteúdo do mesmo.

É preciso encontrar o node através de uma expressão XPath ou procurando diretamente.
Eu faço isso nesse trecho:

$tagid = $tag;
...
//extrair a tag com os dados a serem assinados
$xpath = new DOMXPath($xmldoc);
$xpath->registerNamespace('ns', "http://www.abrasf.org.br/nfse.xsd");
$node = $xpath->query("//ns:{$tagid}")->item(0);
if (!isset($node)) {
   $msg = "A tag <$tagid> não existe no XML!!";
   throw new Exception($msg);
}

É preciso colocar o NameSpace corretamente, para que o XPath consiga encontrar o elemento.
Caso contrário, não funciona.

Depois de calculada a assinatura, eu faço a referência informando o próprio node, além do Id do mesmo:

// Assina usando SHA-1
$objDSig->addReference(
   $node,
   XMLSecurityDSig::SHA1,
   [
      "http://www.w3.org/2000/09/xmldsig#enveloped-signature",
      "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
   ],
   [
      'id_name' => 'Id',
      'overwrite' => false
   ]
);

No final de tudo, eu adiciono a assinatura no elemento pai:

// Acrescenta a signature para o XML
$objDSig->appendSignature($node->parentNode);

Dessa forma a assinatura fica localizada nos elementos esperados pela aplicação da Prefeitura.

Pegouuuuuuuuu!!! uhuuuuuuu!!! Obrigado meu querido pela ajuda… O erro que deu por ultimo foi porque eu não tinha referenciado o id do lote

1 curtida

Amigo, só mais uma dúvida, desculpa está incomodando, mas você saberia como faço para pegar o código de verificação da nota fiscal, após ela é gerada? Estou tentando pegar para poder gerar a nota em pdf também, pra poder enviar para o cliente

No XML de consulta da nota.

Você envia a consulta:

<ConsultarLoteRpsEnvio xmlns="http://www.abrasf.org.br/ABRASF/arquivos/nfse.xsd">
    <Prestador>
        <Cnpj>12345678000128</Cnpj>
        <InscricaoMunicipal>11111111111</InscricaoMunicipal>
    </Prestador>
    <Protocolo>27230FEADB2F4F87B42E7744EBF8C40E</Protocolo>
</ConsultarLoteRpsEnvio>

E a resposta possui o código:

<ConsultarLoteRpsResposta xmlns="http://www.abrasf.org.br/ABRASF/arquivos/nfse.xsd">
    <ListaNfse>
        <CompNfse>
            <Nfse>
                <InfNfse id="20091">
                    <Numero>20091</Numero>
                    <CodigoVerificacao>AB3DCD21D</CodigoVerificacao>
                    <DataEmissao>2010-03-01T15:12:03</DataEmissao>
                    <IdentificacaoRps>
                        <Numero>1</Numero>
                        <Serie>1</Serie>
                        <Tipo>1</Tipo>
                    </IdentificacaoRps>