Se você quiser que sua aplicação web (e-commerce, página estática, back-end, etc) viabilize a comunicação HTTPS com seu cliente, você precisa instalar um certificado TLS/SSL no seu web-server. Dessa forma toda a sessão entre a aplicação web e o cliente poderá ser estabelecida de forma segura, isto é, criptografada por meio de chaves públicas e privadas.
TLS/SSL
Os protocolos TLS (Transport Layer Security) e SSL (Secure Socket Layer) são frequentemente usados para se referir à mesma coisa: comunicação HTTPS. Ambos visam a segurança, integridade e privacidade durante a comunicação cliente-servidor. O SSL porém é considerado depreciado e sua versão moderna é o TLS 1.3.
A espinha dorsal do TLS é uma infraestrutura de chaves públicas que viabiliza a confiança mútua entre duas partes (cliente e servidor) que nunca trabalharam juntas. Isso tudo é realizado por um sistema de Certificate Authorities (CAs) que assinam certificados que passam a servir de credenciais para servidores, atestando sua identidade. Assim, quando um cliente tenta se conectar a esse servidor via HTTPS (TLS), esse servidor apresenta seu certificado ao cliente. Se a autoridade que assinou o certficado desse servidor estiver na lista de autoridades confiáveis do cliente, então um canal de comunicação segura é estabelecido entre esse cliente e o respectivo servidor.
Quando o cliente é uma aplicação java, a lista de autoridades confiáveis é chamada de truststore. Do lado do servidor [quando ele também é uma aplicação java], seu certificado (e as respectivas chaves) fica armazenado em uma keystore.
Geralmente, quando você é um servidor, trabalha mais com o keystore; quando você é cliente, está mais interessado no seu truststore.
O server apresenta seu certificado ao cliente, que confere no seu truststore se o certificado é confiável |
Truststores do JDK
O Truststore na prática é um conjunto de certificados armazenados no JDK que a aplicação utiliza para determinar se confia ou não no servidor TLS que ela está acessando. Todo JDK vem com uma lista de CAs pré-definidos no Truststore.
O keytool é um programa em linha de comando que permite trabalhar com truststores e keystores. Como dissemos, você trabalha mais com o keystore se for um server; ou trabalha mais com o truststore, se for um cliente.
O seguinte comando lista todas as autoridades pré-aceitas no JDK (o password default é changeit para rodar o comando):
keytool -list -keystore $JAVA_HOME/jre/lib/security/cacerts
Como exemplo, no browser Mozila, podemos checar o certificado de docs.amazon.aws clicando no icone do cadeado:
Em seguida clicando em more information:
Na pop-up que aparecer clique em view certficate:
Selecione o fingerprint desse certificado:
E confira se o JDK confia nesse CA:
keytool -list -keystore $JAVA_HOME/jre/lib/security/cacerts | grep ' fingerprint '
Como esse CA consta no truststore do JDK, podemos acessar essa url via código sem problemas:
String hostURL = "https://docs.aws.amazon.com";
URL url = new URL(hostURL);
HttpsURLConnection conn;
conn = (HttpsURLConnection)url.openConnection();
InputStream is = conn.getInputStream();
Experimente trocar por uma url cujo certificado não confiável, como por exemplo https://self-signed.badssl.com, e o código acima lança uma exception:
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
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1946)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:316)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:310)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1639)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:223)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1037)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:965)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1064)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1570)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1498)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:268)
at ssl.Main03.main(Main03.java:17)
javax.net.ssl.SSLHandshakeException Basicamente nos diz que o certificado desse host não é reconhecido pelo truststore default do JDK e que, portanto, não é possível estabelecer uma conexão segura com ele via TLS.
É comum que muitas vezes queiramos ignorar essa advertência e acessar o host mesmo assim (cenário comum em ambientes de teste). Neste caso podemos criar nossa própria truststore e adicionar manualmente qualquer certficado.
O código abaixo cria uma nova TrsutStore e adiciona nela o certificado de https://self-signed.badssl.com, fazendo com que a aplicação consiga acessar a URL via HTTPS sem erros:
public class AdicionaCertificado {
public static void main(String args[]) throws Exception {
String hostURL = "https://self-signed.badssl.com";
//String hostURL = "https://docs.aws.amazon.com";
loadNewCertificate("/home/rafael/Library/Blog/Certs/badssl-com.pem");
URL url = new URL(hostURL);
HttpsURLConnection conn;
conn = (HttpsURLConnection)url.openConnection();
InputStream is = conn.getInputStream();
}
static void loadNewCertificate(String newCertficateFile) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
String trustStorePass = "123456";
String clientTrutStorePath = "/home/rafael/Library/Blog/Certs/myNewTrustStore";
String alias = "badssl.com";
// criando uma nova trustStore (diferente da default cacerts)
KeyStore clientTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
char[] password = trustStorePass.toCharArray();
// para criar uma nova truststore vazia, passe null no 1o argumento...
clientTrustStore.load(null, password);
// ...e crie o arquivo fisico vazio
FileOutputStream fos = new FileOutputStream(clientTrutStorePath);
clientTrustStore.store(fos, password);
fos.close();
// como a keystore já existe, desta vez não passamos null ao metodo load
FileInputStream in = new FileInputStream(clientTrutStorePath);
clientTrustStore.load(in, password);
in.close();
// CertficateFactory é usada para gerar novos certificados
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream serverCertstream = new FileInputStream(newCertficateFile);
// gera um objeto certificado e o inicializa com base nos dados do InputStream
Certificate serverCertificate = cf.generateCertificate(serverCertstream);
// adiciona o novo certificado a nova keystore, com um apelido
clientTrustStore.setCertificateEntry(alias, serverCertificate);
// salvamos as alteracoes na nova keystore
FileOutputStream out = new FileOutputStream(clientTrutStorePath);
clientTrustStore.store(out, password);
out.close();
// dinamicamente alteramos o trsutstore default desta App para a nova trsutStore criada
System.setProperty("javax.net.ssl.trustStore", clientTrutStorePath);
System.setProperty("javax.net.ssl.trustStorePassword", trustStorePass);
}
}
Você pode criar seu próprio certificado para posteriormente apresentá-lo aos clientes de sua aplicação. O comando a seguir cria uma chave privada e um certificado contendo a chave pública:
keytool -genkeypair -alias meu-alias -keyalg RSA -validity 7 -keystore minha-keystore
O próximo comando extrai o certificado do arquivo minha-keystore com a extensão .cer:
keytool -exportcert -alias meu-alias -keystore minha-keystore -rfc -file meu-certificado.cer
Nota: um detalhe para se estar ciente e que pode causar confusão é que, da perspectiva do keytool e do Java, keystores e truststores são arquivos keystore. Eles apenas contêm diferentes tipos de chaves. Tanto que a referência a clientTrustore no código foi representada pela classe java.security.KeyStore.
Lucas 17:20-21