Diagnosticando TLS, SSL e HTTPS
Ao construir aplicações interconectadas, desenvolvedores frequentemente interagem com protocolos habilitados para TLS, como HTTPS. Com a recente ênfase em comunicações criptografadas, este conteúdo aborda a forma como o JDK evolui em relação a protocolos, algoritmos e alterações, bem como alguns diagnósticos avançados para entender melhor conexões TLS como HTTPS.
A maioria dos desenvolvedores não precisará fazer esse nível de diagnóstico no processo de escrita ou execução de aplicações. No entanto, se necessário, as informações a seguir devem fornecer base suficiente para entender o que está acontecendo dentro de conexões seguras.
Evolução de protocolos e algoritmos
Nos últimos 15 anos, a plataforma Java evoluiu por meio do Java Community Process, onde empresas, organizações e indivíduos dedicados desenvolvem e votam em especificações para determinar o que compõe a Plataforma Java. Grande parte dos esforços está centrada na compatibilidade, como o TCK, garantindo que diferentes implementações sejam compatíveis entre si e que os desenvolvedores possam prever como suas aplicações serão executadas. Opções padrão críticas (como o protocolo TLS) não são alteradas dentro de versões menores.
A tabela a seguir descreve os protocolos e algoritmos suportados em cada versão do JDK:
| JDK 8 (Março 2014 - presente) | JDK 7 (Julho 2011 - presente) | JDK 6 (2006 até fim das atualizações públicas 2013) |
|---|---|---|
| Protocolos TLS: TLSv1.2 (padrão) TLSv1.1 TLSv1 SSLv3 |
Protocolos TLS: TLSv1.2 TLSv1.1 TLSv1 (padrão) SSLv3 |
Protocolos TLS: TLSv1.2 (atualização 111+) TLSv1.1 (atualização 111+) TLSv1 (padrão) SSLv3 |
| Cifras JSSE: Cifras no JDK 8 | Cifras JSSE: Cifras no JDK 7 | Cifras JSSE: Cifras no JDK 6 |
📝 Código Java de exemplo para fazer uma conexão HTTPS
Fazer uma conexão HTTPS em Java é relativamente simples. O código é apresentado com foco no ajuste e na compreensão das capacidades subjacentes.
Código de back-end de exemplo para fazer uma conexão SSL:
final URL url = new URL("https://example.com");
try(final InputStream in = url.openStream()){
//…
}
A conexão também pode ser ajustada através de um cast:
final HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
//opera em conn
conn.connect();
try(final InputStream in = conn.getInputStream()){
//…
}
Exemplo: Página "View My Client" do Qualys SSL Labs
O Qualys SSL Labs mantém uma coleção de ferramentas úteis para entender conexões SSL/TLS. Uma em particular é a página "View My Client", que exibe informações sobre a conexão do cliente. Ao integrar com essa página, é possível controlar a implementação enquanto se usam diferentes parâmetros de ajuste do Java.
Para testar o ajuste de parâmetros, foi implementada uma pequena aplicação JavaFX em JavaScript. Ela exibe essa página em um WebView, mostrando informações sobre a conexão cliente SSL/TLS subjacente do Java. O código pode ser encontrado no apêndice.
🔧 Parâmetros de ajuste do JSSE
Ao diagnosticar problemas relacionados a TLS, há várias propriedades do sistema úteis. Elas geralmente são abordadas em suas seções relevantes do JSSE, mas esta coleção única pode ajudar quem busca entender a flexibilidade da implementação do Java ou diagnosticar detalhes de conexão.
| Parâmetro | Descrição |
|---|---|
javax.net.debug |
Imprime detalhes de depuração para conexões feitas. Exemplo: -Djavax.net.debug=all ou -Djavax.net.debug=ssl:handshake:verbose |
https.protocols |
Controla a versão do protocolo usada por clientes Java que obtêm conexões https através do uso da classe HttpsURLConnection ou via operações URL.openStream(). Para versões mais antigas, isso pode atualizar o padrão, caso seu cliente Java 7 queira usar TLS 1.2 como padrão.Exemplo: -Dhttps.protocols=TLSv1,TLSv1.1,TLSv1.2Para protocolos não HTTP, isso pode ser controlado através do SSLContext do SocketFactory. |
jdk.tls.client.protocols |
Controla a implementação TLS subjacente da plataforma. Mais informações estão disponíveis no Guia de Referência do JSSE. Exemplo: -Djdk.tls.client.protocols=TLSv1.1,TLSv1.2Disponível em todas as versões do JDK 8, ou após Java 7 update 95 (Janeiro 2016) e Java 6 update 121 (Julho 2016). |
http.agent |
Ao iniciar conexões, o Java aplica isso como sua string de agente do usuário. Modificar isso resolve casos onde a parte receptora responde de forma diferente com base no agente do usuário. Exemplo: -Dhttp.agent=“agente conhecido” |
java.net.useSystemProxies |
Usa detalhes de proxy do próprio sistema operacional. Exemplo: -Djava.net.useSystemProxies=true |
http.proxyHosthttp.proxyPort |
A conexão de proxy a ser usada para conexões HTTP. Exemplo: -Dhttp.proxyHost=proxy.exemplo.com -Dhttp.proxyPort=8080 |
https.proxyHosthttps.proxyPort |
O mesmo que acima, exceto que a configuração é separada entre HTTP e HTTPS. |
http.proxyUserhttp.proxyPasswordhttps.proxyUserhttps.proxyPassword |
Credenciais baseadas em senha para os proxies acima. |
🔎 Exemplo de diagnóstico de um problema
Ao fazer uma conexão HTTPS, suponha que o cliente lançou a seguinte exceção devido a um handshake falho com o servidor:
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
SSLHandshakeException é uma subclasse de IOException, então não é necessário capturá-la explicitamente. A maioria dos desenvolvedores não precisará de uma captura explícita, mas isso pode ajudar a diagnosticar mais facilmente a causa de qualquer IOException.
Ao aplicar a propriedade -Djavax.net.debug=all, a falha associada a esta SSLHandshakeException apareceria logo após a negociação de algoritmo nos logs.
| JDK 7 (falha em algoritmo não suportado) | JDK 8 (funciona bem) |
|---|---|
Cipher Suites: […Lista longa de cifras…]
Compression Methods: { 0 }
Extension elliptic_curves, curve names: {…}
Extension ec_point_formats, formats: [uncompressed]
Extension server_name, server_name: [host_name: HOST]
main, WRITE: TLSv1 Handshake, length = 168
main, READ: TLSv1 Alert, length = 2
main, RECV TLSv1 ALERT: fatal, handshake_failure
main, called closeSocket()
main, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure |
Cipher Suites: […Lista longa de cifras…]
Compression Methods: { 0 }
Extension elliptic_curves, curve names: {…}
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: …
Extension server_name, server_name: [type=host_name (0), value=HOST]
main, WRITE: TLSv1.2 Handshake, length = 226
main, READ: TLSv1.2 Handshake, length = 89
*** ServerHello, TLSv1.2
RandomCookie: GMT: -1809079139 bytes = { …}
Session ID: {…}
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
Compression Method: 0
Extension renegotiation_info, renegotiated_connection: <empty>
Extension ec_point_formats, formats: [uncompressed, ansiX962_compressed_prime, ansiX962_compressed_char2]
%% Initialized: [Session-1, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256]
** TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
main, READ: TLSv1.2 Handshake, length = 2308 |
No caso acima, a falha ocorreu durante o handshake. A causa mais provável para isso é o suporte de algoritmo. O JDK fornece um pacote separado chamado JCE Força Ilimitada, projetado para adicionar suporte a algoritmos mais fortes do que o disponível por padrão. O Qualys SSL Labs fornece um teste de servidor SSL diferente que enumerará quais algoritmos um servidor suporta.
Adicionando algoritmos mais fortes: JCE Força Ilimitada
Em um ambiente de alta segurança, uma forma de fortalecer os algoritmos no JDK é através dos arquivos de política JCE Força Ilimitada. Neste caso específico, substituir esses arquivos de política dentro do JDK 7 permite que ele use as variantes mais fortes dos algoritmos existentes e se conecte com sucesso.
Apêndice
O código a seguir abrirá a página "View My Client" do Qualys SSL Labs dentro de um cliente Java. Para testar configurações, execute assim:
jjs -fx viewmyclient.js
jjs -fx -Dhttps.protocols=TLSv1 viewmyclient.js
var Scene = javafx.scene.Scene;
var WebView = javafx.scene.web.WebView;
var browser = new WebView();
browser.getEngine().load("https://ssllabs.com/ssltest/viewMyClient.html");
$STAGE.scene = new Scene(browser);
$STAGE.show();