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
- O certificado original da empresa não tem embutido a cadeia completa de certificados.
- 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
- Conseguia acessar a URL pelos browsers Internet Explorer, FireFox e Chrome, mas não conseguia acessar pelo Java.
- O mesmo código funcionava para servidores da SEFAZ de outros estados.
- 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.
- 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.
- 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.
- 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