Problemas com PIN A3 + Assinatura de XML - ACBrNFe - Projeto ACBr
Problemas com PIN A3 + Assinatura de XML
August 17, 2017 in ACBrNFe
Go to solution Solved by Daniel Simoes, August 21, 2017
Este tópico foi criado há 1726 dias atrás.
Prezados, devido nossa aplicação ser em 3 camadas, a parte de assinatura do XML fica em uma aplicação externa em outra linguagem de programação, e no que diz respeito a parte de ENVIO basta mencionar o excelente trabalho que o projeto fez com a WinCrypt.
obs: se você não sabe o porque de fazermos assim, segue o tópico EXCELENTE do Daniel
Essa semana obtivemos um certificado A3 da Perto (SmartCard) e ele está pedindo PIN no servidor, e no DEBUG descobri que não era na nossa rotina de assinatura mas sim no momento do envio pela ACBr.
Na unit "ACBrNFeNotasFiscais.pas", na linha 252, onde temos:
TACBrNFe(TNotasFiscais(Collection).ACBrNFe).SSL.ValidarCNPJCertificado( NFe.Emit.CNPJCPF );
1º Ele acaba inicializando todo contexto e enviando o PIN para o SmartCard (ou Token) previamente, antes da assinatura e antes do envio.
2º Quando o evento "OnAntesDeAssinar" é disparado nosso código externo realiza a assinatura do XML nessa biblioteca externa.
3º (Não tenho certeza) É provável que ao adquirir o contexto do certificado A3 nessa aplicação nossa, e enviar o PIN tem removido o contexto da ACBR ou algo semelhante, isso já vai um pouco além do que conheço do assunto.
4º Quando o código volta para a ACBR realizar o Envio, a janela de PIN aparece.
5º Quando debugando eu faço um Jump para a próxima linha pulando a linha 252 conforme destacada acima, tudo funciona normalmente, porque não houve um contexto anteriormente adquirido acima, então no momento do envio o código para obter a chave privada funciona como se fosse executado a primeira vez.
Obs: CertFreeCertificateContext também é chamado em nossa aplicação depois da assinatura do XML ser concluída, e o Store dos certificados é fechada.
Aqui que venho pedir ajuda ao pessoal responsável pelo projeto para não fazer alterações desnecessária ou que não serão aceitas.
De imediato pensei em para as pessoas que assinam os XMLs em aplicações externas fica a responsabilidade da validação do CNPJ e o certificado, assim adicionando uma verificação se o evento OnAntesDeAssinar está "Assigned" antes linha 252 e resolvendo esse problema.
Ou então a rotina que verifica se o CNPJ é o mesmo do certificado não realizar o envio do PIN quando o tipo for A3 por exemplo
// Talvez adicionar um parametro padrão como
TDFeWinCrypt.CarregarCertificado;
TDFeWinCrypt.CarregarCertificado(const EnviarPinSeExistir: Boolean = True);
E aqui seguindo a lógica de enviar o parâmetro falso para o CNPJ, até chegar na unit que verifica se os dois CNPJ são os mesmos.
function TDFeSSLCryptClass.GetCertCNPJ: String;
CarregarCertificadoSeVazio(False);
Result := FpDadosCertificado.CNPJ;
Desculpe se ficou muito extenso.
Segue sugestão de alteração, dessa forma mesmo usando assinatura de XML por biblioteca externa funciona sem problemas.
Revisão: 13736
A opção 1, parece fazer mais sentido... (verificar se há um evento externo para assinar)
procedure NotaFiscal.Assinar;
SSL.ValidarCNPJCertificado( NFe.Emit.CNPJCPF );
mas isso não apenas adiaria o problema ? Pois ambas aplicações precisam usar o certificado.. e acho que depois do ACBr atribuir o PIN, para efetuar o envio... provavelmente sua aplicação tenha problemas para usar o certificado, na próxima assinatura...
Em 18/08/2017 at 13:43, Daniel Simoes disse:
Olá Daniel, obrigado pela atenção, e realmente, com duas notas seguidas o problema acaba acontecendo, você tem alguma sugestão?
Talvez adicionar uma propriedade TDFeSSL e em TDFeSSLCryptClass para definir se mantem o certificado carregado, ou não, então sempre que pedir o carregamento e após fazer o uso liberar.
Ou então tentar manter o mesmo contexto em duas aplicações diferentes usando o mesmo handle (não sei se é possível).
Primeiro, descarregando o certificado após a verificação do CNPJ
if Assigned(SSL.AntesDeAssinar) then
SSL.DescarregarCertificado;
Quando descarrega o contexto, remove a trava do pin
procedure TDFeWinCrypt.DescarregarCertificado;
PosA3Pin: integer;
// Limpando objetos da MS CryptoAPI //
if Assigned(FpCertContext) then
if Assigned(FpDadosCertificado) then
PosA3Pin := pos(FpDadosCertificado.NumeroSerie, CertificadosA3ComPin);
if PosA3Pin > 0 then
Delete(CertificadosA3ComPin, PosA3Pin, Length(FpDadosCertificado.NumeroSerie + ','));
CertFreeCertificateContext(FpCertContext);
Desta forma, duas nota seguidas foram enviadas sem pedir o PIN ou apresentar o erro, é claro que é apenas um teste, e de forma nenhum trata todas as possibilidades. Nesse caso foi testado com um Leitor Perto (SafeSign) e com um SafeNet Token 1500.
Eu vi também no MSDN que é possível liberar o cache do pin enviando nulo para NCryptSetProperty ou mesmo CryptSetProvParam.
No objeto DFeSSL há métodos para carregar e descarregar o certificado...
Deve funcionar, se aplicarmos a modificação sugerida ( não validar o CNPJ, para quem programou o evento ) e você descarregar o certificado de DFeSSL, após o envio...
Fiz a reversão de todos os fontes da ACBR na nossa cópia de trabalho.
Fiz a seguinte alteração postada por você
Então em nosso código adicionei
Result := FACBR.Enviar(Lote, False);
FACBR.SSL.DescarregarCertificado;
Ao enviar duas notas fiscais, a segunda travou e pediu o PIN.
Então ainda no nosso código adicionei mais uma linha
// Identifica a posição do certificado na lista
vPosCertPinCache := pos(FACBR.Configuracoes.Certificados.NumeroSerie, ACBrDFeWinCrypt.CertificadosA3ComPin);
// Se foi identificado, então remove apenas ele.
if vPosCertPinCache > 0 then
Delete(CertificadosA3ComPin, vPosCertPinCache, Length(FACBR.Configuracoes.Certificados.NumeroSerie + ','));
Enviei 3 notas e nenhuma delas deu erro ou pediu o PIN, tanto no Token quando no SmartCard.
Se quiser, posso fazer a alteração em todos os valida CNPJ de todas as classes que herdam a DFe e modificar de acordo com seu código e postar aqui para subir.
Edited August 21, 2017 by Data Lider
Hum.. então o que faltou, foi que ao Descarregar o Certificado, devemos remover ele da Lista de Pins salvos... correto ?
ok.. já no SVN...
-- ACBrDFeWinCrypt --
[-] Correção para remover o numero de Série do Certificado, de "CertificadosA3ComPin",
quando Descarregar o Certificado.
http://www.projetoacbr.com.br/forum/topic/38349-problemas-com-pin-a3-assinatura-de-xml/
Fiz o update e os seguintes testes:
Emitir duas NF-e com A1 sem aplicativo externo, Tudo OK
Emitir duas NF-e com A3 Token sem aplicativo externo, Tudo OK.
Emitir duas NF-e com A3 SmartCard com aplicativo externo, Tudo OK.
Emitir uma MDF-e com A3 SmartCard com aplicativo externo, Tudo OK.
@Data Lider,
Após analisar o tópico abaixo, notei que a modificação que efetuamos aqui.. ocasionou efeitos colaterais...
Pensando nisso, aplique modificações na Unit ACBrDFeWinCrypt.pas, a fim de tentar resolver o problema... porém gostaria que você verificasse se essas modificações funcionarão para o caso relatado aqui nesse post.
Por favor teste seus fontes, com a Unit em anexo...
Lucas L. 20
Olá @Daniel Simoes, tomei a liberdade de testar o arquivo com a modificação. Ao consultar o status por exemplo não retorna mais o erro de canais seguros mas retorna o "Falha ao Definir PIN do Certificado." vide imagem.
1 hora atrás, Lucas L. disse:
parece ser uma exceção controlada... quando ele vai descarregar o certificado, ele tenta atribuir "Nul" a senha previamente definida... Observe que está entre um bloco try/except