Um módulo que estou adicionando ao nosso grande aplicativo Java precisa conversar com o site protegido por SSL de outra empresa. O problema é que o site usa um certificado autoassinado. Eu tenho uma cópia do certificado para verificar se não estou encontrando um ataque intermediário e preciso incorporar esse certificado ao nosso código de forma que a conexão com o servidor seja bem-sucedida.
Aqui está o código básico:
void sendRequest(String dataPacket) {
String urlStr = "https://host.example.com/";
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setMethod("POST");
conn.setRequestProperty("Content-Length", data.length());
conn.setDoOutput(true);
OutputStreamWriter o = new OutputStreamWriter(conn.getOutputStream());
o.write(data);
o.flush();
}
Sem qualquer manipulação adicional em vigor para o certificado autoassinado, ele morre em conn.getOutputStream () com a seguinte exceção:
Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Idealmente, meu código precisa ensinar Java a aceitar este certificado autoassinado, para este ponto no aplicativo e em nenhum outro lugar.
Eu sei que posso importar o certificado para o armazenamento da autoridade de certificação do JRE e isso permitirá que o Java aceite. Essa não é uma abordagem que quero adotar se puder ajudar; parece muito invasivo em todas as máquinas de nossos clientes para um módulo que eles não podem usar; isso afetaria todos os outros aplicativos Java que usam o mesmo JRE, e eu não gosto disso, embora as chances de qualquer outro aplicativo Java acessar este site sejam nulas. Também não é uma operação trivial: no UNIX, tenho que obter direitos de acesso para modificar o JRE dessa maneira.
Também vi que posso criar uma instância do TrustManager que faz alguma verificação personalizada. Parece que eu posso até criar um TrustManager que delega para o TrustManager real em todas as instâncias, exceto neste certificado. Mas parece que o TrustManager é instalado globalmente, e presumo que isso afetaria todas as outras conexões do nosso aplicativo, e isso também não parece muito bom para mim.
Qual é a maneira preferida, padrão ou melhor de configurar um aplicativo Java para aceitar um certificado autoassinado? Posso cumprir todos os objetivos que tenho em mente acima ou terei que me comprometer? Existe uma opção envolvendo arquivos e diretórios e definições de configuração, e pouco ou nenhum código?
Respostas:
Crie
SSLSocket
você mesmo uma fábrica e defina-aHttpsURLConnection
antes de conectar.Você deseja criar um
SSLSocketFactory
e mantê-lo por perto. Aqui está um esboço de como inicializá-lo:Se precisar de ajuda para criar o armazenamento de chaves, comente.
Aqui está um exemplo de carregamento do armazenamento de chaves:
Para criar o armazenamento de chaves com um certificado no formato PEM, você pode escrever seu próprio código usando
CertificateFactory
ou apenas importá-lokeytool
do JDK (o keytool não funcionará para uma "entrada de chave", mas é adequado para uma "entrada confiável" )fonte
Eu li vários lugares online para resolver isso. Este é o código que escrevi para fazê-lo funcionar:
app.certificateString é uma String que contém o Certificado, por exemplo:
Eu testei que você pode colocar qualquer caractere na cadeia de certificados, se for autoassinado, desde que mantenha a estrutura exata acima. Eu obtive a sequência de certificados com a linha de comando do terminal do meu laptop.
fonte
Se criar
SSLSocketFactory
uma opção não for, apenas importe a chave para a JVMRecupere a chave pública:
$openssl s_client -connect dev-server:443
e crie um arquivo dev-server.pem com a aparênciaImportar a chave:
#keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem
. Senha: changeitReiniciar JVM
Fonte: Como resolver o javax.net.ssl.SSLHandshakeException?
fonte
Copiamos o armazenamento confiável do JRE e adicionamos nossos certificados personalizados a esse armazenamento confiável e, em seguida, instruímos o aplicativo a usar o armazenamento confiável personalizado com uma propriedade do sistema. Dessa forma, deixamos o armazenamento confiável JRE padrão em paz.
A desvantagem é que, quando você atualiza o JRE, seu novo armazenamento confiável não é automaticamente mesclado ao seu personalizado.
Talvez você possa lidar com esse cenário com uma rotina de instalação ou inicialização que verifique o armazenamento confiável / jdk e verifique se há uma incompatibilidade ou atualize automaticamente o armazenamento confiável. Não sei o que acontece se você atualizar o armazenamento confiável enquanto o aplicativo estiver em execução.
Esta solução não é 100% elegante ou infalível, mas é simples, funciona e não requer código.
fonte
Eu tive que fazer algo assim ao usar o commons-httpclient para acessar um servidor https interno com um certificado autoassinado. Sim, nossa solução foi criar um TrustManager personalizado que simplesmente transmitisse tudo (registrando uma mensagem de depuração).
Isso se resume a ter nosso próprio SSLSocketFactory, que cria soquetes SSL a partir do nosso SSLContext local, configurado para ter apenas o TrustManager local associado a ele. Você não precisa chegar nem perto de um keystore / certstore.
Portanto, isso está em nossa LocalSSLSocketFactory:
Juntamente com outros métodos de implementação do SecureProtocolSocketFactory. LocalSSLTrustManager é a implementação do gerenciador de confiança fictícia mencionada acima.
fonte