Erro "HTTP 403 - Forbidden: Access is denied." ao consultar NF-e no servidor da SEFAZ BA

DESCRIÇÃO

Ao tentar acessar https://nfe.sefaz.ba.gov.br/webservices/NfeConsulta/NfeConsulta.asmx ocorria o seguinte erro:
HTTP 403 - Forbidden: Access is denied.

O mesmo código que dava erro ao acessar a SEFAZ BA, funcionava para o resto do Brasil.
Pesquisei muito a respeito e até encontrava pessoas com problemas parecidos, mas a solução dada nunca funcionava para mim. Até que encontrei neste fórum o link NFe Erro “403.7 - Forbidden” ao acessar Webservices asmx [RESOLVIDO!] do usuário alexegidio que me deu uma luz para causa do problema. Baseado na solução dele eu consegui encontrar uma solução para o meu problema e como retribuição resolvi publicar aqui para ajudar outros usuários que tiverem problema parecido.

CAUSAS

  1. O certificado original da empresa não tem embutido a cadeia completa de certificados.
  2. O certificado da empresa não estava sendo enviado para o servidor remoto com a sua cadeia de certificados completa. Apenas o certificado da empresa estava sendo enviado, ficando ausentes os certificados das Autoridades Certificadoras Intermediárias e da Autoridade Certificadora Raiz.

INVESTIGAÇÃO

  1. Conseguia acessar a URL pelos browsers Internet Explorer, FireFox e Chrome, mas não conseguia acessar pelo Java.
  2. O mesmo código funcionava para servidores da SEFAZ de outros estados.
  3. Depois que foi descoberto que a cadeia de certificados dentro do certificado da empresa (.PFX) estava incompleta, o arquivo foi atualizado incluindo os certificados ausentes da cadeia de certificados. Os testes usando o SoapUI passaram a funcionar, mas ainda assim não conseguia fazer funcionar no meu código Java.
  4. Ao debugar, foi constatado que durante o handshake, no passo “Certificate chain” que o certificado da empresa não estava sendo enviado com a cadeia completa. Apenas o certificado da empresa estava sendo enviado, faltando os certificados as CA’s.

SOLUÇÃO

Basicamente, a solução foi incluir no arquivo do certificado (.PFX) os certificados das autoridades certificadoras e também garantir que durante o processo de handshake a cadeia completa do certificado fosse enviada.

  1. O certificado da empresa foi importado no Internet Explorer e depois exportado para um novo arquivo incluindo a cadeia completa de certificados, com todos os certificados das autoridades certificadoras da cadeia, conforme descrito no link acima publicado.
  2. Uma classe KeyManager customizada foi utilizada para garantir que a cadeia de certificados seria enviada completa (método getCertificateChain()).
    A partir do momento em que o arquivo PFX ficou completo e a cadeia passou a ser enviada completa para o servidor, o problema foi resolvido.

Código fonte utilizado como exemplo:

Classe que implementa um KeyManager customizado.

import java.net.Socket;

import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;

import javax.net.ssl.X509KeyManager;

/**
 * Implementa X509KeyManager customizado.
 */
public class CustomX509KeyManager  implements X509KeyManager {
    private final X509KeyManager keyManager;
    private final String alias;

    public CustomX509KeyManager(X509KeyManager keyManager, String alias) {
        this.keyManager = keyManager;
        this.alias = alias;        
    }

    public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
        return this.alias;
    }

    public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
        return this.alias;
    }

    public X509Certificate[] getCertificateChain(String alias) {
        return this.keyManager.getCertificateChain(alias);
    }

    public  String[] getClientAliases(String keyType, Principal[] issuers) {
        return this.keyManager.getClientAliases(keyType, issuers);
    }

    public PrivateKey getPrivateKey(String alias) {
        PrivateKey privateKey = this.keyManager.getPrivateKey(alias);
        return privateKey;
    }

    public String[] getServerAliases(String keyType, Principal[] issuers) {
        return this.keyManager.getServerAliases(keyType, issuers);
    }

}

Classe que implementa um Request de teste:

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;

import java.net.HttpURLConnection;
import java.net.URL;

import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.X509Certificate;

import java.util.HashMap;
import java.util.Map;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;

public class TesteNfeConsulta {
        
    private static int TIMEOUT = 30000;
        
    public TesteNfeConsulta() {
        super();
    }

    /**
     * Obtém um X509KeyManager para o KeyStore enviado como parâmetro.
     * @param keyStore KeyStore
     * @param password Senha do KeyStore
     * @return Retorna uma instância de X509KeyManager
     * @throws GeneralSecurityException
     */
    private static X509KeyManager getKeyManagerForKeystore(KeyStore keyStore,
                                                           String password) throws GeneralSecurityException {
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509", "SunJSSE");
        keyManagerFactory.init(keyStore, password.toCharArray());
        for (KeyManager keyManager : keyManagerFactory.getKeyManagers()) {
            if (keyManager instanceof X509KeyManager) {
                return (X509KeyManager) keyManager;
            }
        }
        throw new IllegalStateException("Não foi possível encontrar um X509KeyManager");
    }

    public static void main(String[] args) throws Exception {
        
        // Parametrização
            
        String ufServer = "BA";
        
        Map<String,String> map;
        Map<String,Map<String,String>> urls = new HashMap<String,Map<String,String>>();
        
        map = new HashMap<String,String>();
        map.put("url"         , "https://hnfe.sefaz.ba.gov.br/webservices/NfeConsulta/NfeConsulta.asmx");
        map.put("Content-Type", "text/xml; charset=utf-8");
        map.put("SOAPAction"  , "http://www.portalfiscal.inf.br/nfe/wsdl/NfeConsulta/nfeConsultaNF");
        map.put("SOAPEnvelope", "<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:nfes=\"http://www.portalfiscal.inf.br/nfe/wsdl/NfeConsulta2\"><soap:Header><nfes:nfeCabecMsg><nfes:cUF>29</nfes:cUF><nfes:versaoDados>3.10</nfes:versaoDados></nfes:nfeCabecMsg></soap:Header><soap:Body><nfes:nfeDadosMsg><consSitNFe versao=\"3.10\" xmlns=\"http://www.portalfiscal.inf.br/nfe\"><tpAmb>1</tpAmb><xServ>CONSULTAR</xServ><chNFe>99999999999999999999999999999999999999999999</chNFe></consSitNFe></nfes:nfeDadosMsg></soap:Body></soap:Envelope>");
        urls.put("BA", map);
        
        map = new HashMap<String,String>();
        map.put("url"         , "https://hnfe.fazenda.mg.gov.br/nfe2/services/NfeConsulta2");
        map.put("Content-Type", "application/soap+xml; charset=utf-8");
        map.put("SOAPAction"  , "http://www.portalfiscal.inf.br/nfe/wsdl/NfeConsulta2");
        map.put("SOAPEnvelope", "<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:nfes=\"http://www.portalfiscal.inf.br/nfe/wsdl/NfeConsulta2\"><soap:Header><nfes:nfeCabecMsg><nfes:cUF>31</nfes:cUF><nfes:versaoDados>3.10</nfes:versaoDados></nfes:nfeCabecMsg></soap:Header><soap:Body><nfes:nfeDadosMsg><consSitNFe versao=\"3.10\" xmlns=\"http://www.portalfiscal.inf.br/nfe\"><tpAmb>1</tpAmb><xServ>CONSULTAR</xServ><chNFe>99999999999999999999999999999999999999999999</chNFe></consSitNFe></nfes:nfeDadosMsg></soap:Body></soap:Envelope>");
        urls.put("MG", map);
        
        map = new HashMap<String,String>();
        map.put("url"         , "https://nfe-homologacao.sefazrs.rs.gov.br/ws/NfeConsulta/NfeConsulta2.asmx");
        map.put("Content-Type", "text/xml; charset=utf-8");
        map.put("SOAPAction"  , "http://www.portalfiscal.inf.br/nfe/wsdl/NfeConsulta2/nfeConsultaNF2");
        map.put("SOAPEnvelope", "<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:nfes=\"http://www.portalfiscal.inf.br/nfe/wsdl/NfeConsulta2\"><soap:Header><nfes:nfeCabecMsg><nfes:cUF>29</nfes:cUF><nfes:versaoDados>3.10</nfes:versaoDados></nfes:nfeCabecMsg></soap:Header><soap:Body><nfes:nfeDadosMsg><consSitNFe versao=\"3.10\" xmlns=\"http://www.portalfiscal.inf.br/nfe\"><tpAmb>1</tpAmb><xServ>CONSULTAR</xServ><chNFe>99999999999999999999999999999999999999999999</chNFe></consSitNFe></nfes:nfeDadosMsg></soap:Body></soap:Envelope>");
        urls.put("RS", map);
        
        // Variáveis
        
        String keyStoreFile = "C:/projetos/dsv_dfe/trunk/construcao/certificados/certificado-empresa.pfx";
        String keyStorePassword = "password";
        String trustStoreFile = "C:/projetos/dsv_dfe/trunk/construcao/certificados/cacert.jks";
        String trustStorePassword = "changeit";
        String urlServico = urls.get(ufServer).get("url");
        String soapEnvelope = urls.get(ufServer).get("SOAPEnvelope");
        String soapAction = urls.get(ufServer).get("SOAPAction");
        String contentType = urls.get(ufServer).get("Content-Type");

        // Inicializa o KeyStore
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        keyStore.load(new FileInputStream(keyStoreFile), keyStorePassword.toCharArray());

        // Inicializa o TrustStore
        KeyStore trustStore = KeyStore.getInstance("JKS");
        trustStore.load(new FileInputStream(trustStoreFile), trustStorePassword.toCharArray());

        // Inicializa o TrustManagerFactory
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX", "SunJSSE");
        trustManagerFactory.init(trustStore);

        // Obtém uma instância de X509TrustManager
        X509TrustManager x509TrustManager = null;
        for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
            if (trustManager instanceof X509TrustManager) {
                x509TrustManager = (X509TrustManager) trustManager;
                break;
            }
        }
        if (x509TrustManager == null) {
            throw new Exception("Não foi possível obter o X509TrustManager");
        }
        
        // Obtém o certificado da empresa (somente funcionará se o certificado da empresa for o primeiro a ser recuperado)
        // Pode ser que seja melhor informar o alias em uma constante
        String alias = keyStore.aliases().nextElement();

        // Obtém o certificado da empresa
        X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias);
        
        // Checa se o certificado é válido
        certificate.checkValidity();

        // Obtém uma instância de X509KeyManager, para passar como parâmetro para o KeyManager customizado.
        // O KeyManager customizado irá consumir algumas funcionalidades prontas deste KeyManager.
        X509KeyManager keyManager = getKeyManagerForKeystore(keyStore, keyStorePassword);

        // Cria uma instância de um KeyManager customizado
        CustomX509KeyManager customX509KeyManager = new CustomX509KeyManager(keyManager, alias);

        // Obtém o contexto SSL e o inicializa com as instâncias de keymanager e trustmanager, passando o KeyManager customizado.
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(new X509KeyManager[] { customX509KeyManager }, new TrustManager[] { x509TrustManager }, null);
        
        // Retorna um objeto SocketFactory para o contexto SSL.
        SSLSocketFactory socketFactory = sslContext.getSocketFactory();

        // Informa a URLConnection para usar um SocketFactory do contexto SSL.
        URL url = new URL(urlServico);
        HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
        urlConnection.setConnectTimeout(TIMEOUT);
        urlConnection.setReadTimeout(TIMEOUT);        
        urlConnection.setSSLSocketFactory(socketFactory);

        // Configura o cabeçalho HTTP
        urlConnection.setRequestMethod("POST");
        urlConnection.setRequestProperty("Content-type", contentType);
        urlConnection.setRequestProperty("SOAPAction", soapAction);
        
        // Habilita o envio de um Request Body
        urlConnection.setDoOutput(true);
        
        // Imprime o envelope SOAP da requisição
        System.out.println("REQUEST:");
        System.out.println("-------");
        System.out.println(soapEnvelope);
        
        // Envia a requisição SOAP
        OutputStream reqStream = urlConnection.getOutputStream();
        reqStream.write(soapEnvelope.getBytes());
        
        // Obtém o retorno
        int responseCode = urlConnection.getResponseCode();
        InputStream inputStream;
        if (responseCode == HttpURLConnection.HTTP_OK) {
            inputStream = urlConnection.getInputStream();
        } else {
            inputStream = urlConnection.getErrorStream();
        }

        // Processa o retorno
        System.out.println("");
        System.out.println("RESPONSE:");
        System.out.println("---------");
        BufferedReader reader;
        String line = null;
        reader = new BufferedReader(new InputStreamReader(inputStream));
        while ((line = reader.readLine()) != null) {
            System.out.println(line);
        }

        inputStream.close();
        
    }
}

Existe implementação melhor, mas estava foi a minha primeira versão de teste.
Espero que este post possa ajudar.
Obrigado a todos que publicaram seus problemas e soluções. Foi muito útil para mim.
[ ]s

1 curtida

Eu utilizava um KeyManager customizado. Depois de acertar os arquivos PFX com a cadeia de certificados completa, descobri que não preciso mais de um KeyManager customizado. Está funcionando agora com o KeyManger padrão.