quarta-feira, 8 de setembro de 2021

🔍 Diagnosticando TLS, SSL e HTTPS em Aplicações Java

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.2
Para 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.2
Disponí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.proxyHost
http.proxyPort
A conexão de proxy a ser usada para conexões HTTP.
Exemplo: -Dhttp.proxyHost=proxy.exemplo.com -Dhttp.proxyPort=8080
https.proxyHost
https.proxyPort
O mesmo que acima, exceto que a configuração é separada entre HTTP e HTTPS.
http.proxyUser
http.proxyPassword
https.proxyUser
https.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();