Professional Documents
Culture Documents
2004
• Introdução
• O protocolo SSL
O protocolo SSL, que foi desenvolvido pela Netscape em 1994, permite aos clientes
(normalmente os navegadores Web) e servidores HTTP comunicarem-se sobre uma
conexão segura. O SSL oferece encriptação, autenticação e integridade dos dados no intuito
de proteger a informação trocada em redes públicas inseguras. Há várias versões do SSL:
SSL 2.0 possui algumas fraquezas de segurança, não sendo muito utilizado atualmente; já o
SSL 3.0 é universalmente suportado; e finalmente o Tranport Layer Security (TLS), que é
um melhoramento do SSL 3.0, tem sido adotado como um padrão na Internet e
amplamente suportado por quase todos os softwares recentes.
A integridade de dados refere-se a uma maneira de assegurar que os dados não foram
modificados durante a tranmissão
Como o próprio nome indica (Camada de Socket Seguro), conexões SSL agem como
sockets conectados por TCP. Portanto, podemos pensar as conexões SSL como conexões
TCP seguras desde que o lugar do SSL na pilha de protocolos é imediatamente acima do
TCP e logo abaixo da camada de aplicação, como mostra a figura 1. Deve-se ressaltar,
entretanto, que o SSL não suporta algumas das habilidades do TCP como dados out-of-
band.
Figura 1 - SSL e a pilha de protocolos TCP/IP
O protocolo SSL possui duas camadas: "SSL Record Protocol", que é responsável por
encapsular outros protocolos de alto nível e a "SSL Handshake Protocol", que recebe os
dados a serem codificados/decodificados. Esta segunda camada é responsável pela
autenticação do cliente e/ou servidor, negociação do algoritmo criptográfico e suas chaves
antes da aplicação receber ou enviar qualquer dado.
Dentre as "facilidades" do SSL que o tornaram o veículo padrão para trnasações seguras
de comércio eletrônico é o seu suporte para a negociação da encriptação e dos algoritmos
de autenticação. Os projetistas do SSL pereceberma que nem todas as partes envolvidas
usam o mesmo software cliente e que, conseqüentemente, nem todos os clientes incluem um
algortimo criptográfico em particular. O mesmo vale para os servidores. O cliente e os
servidores dos dois lados de uma conexão devem, então, ser capazes de negociar os
algoritmos de encriptação e decriptação ("cipher suites") durante o handshake inicial. Se
eles não tiverem algoritmos suficientes em comum, o handshake deve ser interrompido e a
tentativa de conexão irá falhar.
O protocolo SSL permite que tanto o cliente quanto o servidor autentiquem um ao outro,
mas, tipicamente, somente o servidor é autenticado na camada SSL. Clientes são,
costumeiramente, autenticados na camada de aplicação, por meio de uso de passwords
enviados sobre um canal protegido por SSL. Esse padrão é comum em transações
bancárias, dentre outras aplicações Web seguras.
o O cliente envia uma hello message para a qual o servidor deve responder com uma outra hello message, caso
contrário um erro fatal ocorrerá e a conexão falhará. O hello do cliente e o do servidor são usados para
estabelecer capacidades de segurança entre o cliente e o servidor.
o Esses hello's estabelecem os seguintes atributos: versão do protocolo, identificação da sessão, suite de cifras e
métodos de compressão. Adicionalmente, dois valores randômicos são gerados e trocados: ClentHello.random
e ServerHello.random.
o A seguir, o servidor enviará o seu certificado, se este for autenticado. Adicionalmente, uma mensagem de
trocas de chave do servidor pode ser enviada, se necessário (por exemplo, se o servidor não possuir certificado
ou se o seu certificado for apenas para assinatura). Se o servidor está autenticado, ele pode solicitar um
certificado do cliente.
o Agora o servidor enviará uma mensagem hello done, indicando que a fase hello message do handshake está
completa. O servidor irá, então, esperar por uma resposta do cliente.
o Se o servidor tiver enviado uma mensagem de solicitação de certificado, o cliente deve o seu certificado ou
uma mensagem de alerta de que não há certificado (no_certificate alert). A mensagem de troca de chaves do
cliente é, então, enviada, e o conteúdo desta mensagem dependerá do algortimo assimétrico escolhido durante
o hello do cliente e o hello do servidor. Se o cliente enviou um certificado com possibilidade de assinatura, uma
mensagem de verificação digital do certificado (digitally-signed cerfiticate verify message) é enviada para
verificar, explicitamente, o certificado.
o Nesse momento, uma mensagem de mudança de especificação da cifra é enviada pelo cliente, e o cliente copia
a Especificação de Cifra (Cipher Spec) da cifra pendente na Especificação atual. O cliente envia, então,
imediatamente a mensagem de finalização, juntamente com os novos algoritmos, chaves e segredos.
o Em resposta, o servidor enviará sua mensagem de mudança de Cipher Spec, tranferindo a cifra pendente para
a Especificação atual e envia sua mensagem de finalização juntamente com a nova Cipher Spec.
o Nesse momento, o handshake está completo e o cliente e o servidor podem começar a trocar dados na camada
de aplicação (veja a figura abaixo).
• JSSE
O Java Secure Socket Extension (JSSE), um conjunto de pacotes que habilita comunicação
segura na Internet, é um framework que implementa, totalmente em Java, o Secure Soket
Layer. Esses pacotes habilitam a construção de aplicações de rede seguras; isso possibilita a
passagem, de forma segura e confiável, de dados entre um cliente e um servidor executando
qualquer protocolo, como HTTP, FTP, Telnet, entre outros.
o Interface KeyManager
Essa interface é a base para os gerenciadores de chave JSSE. Os gerenciadores de chave são responsáveis pelo
material (as chaves) que é usado para autenticar o socket SSL junto ao seu par (isto é, autenticar o servidor
junto ao cliente e/ou autenticar o cliente junto ao servidor). Se nenhuma chave estiver disponível o socket não
terá a capacidade de se autenticar. KeyManagers são criados ou usando um KeyManagerFactory ou por meio
de uma de suas subclasses.
o Interface SSLSession
Essa interface implementa as sessões SSL, que são usadas para descrever a relação atual entre duas entidades.
Cada conexão SSL envolve uma sessão por vez. Sessões são criadas como parte do protocolo de handshake do
SSL. Esta interface permite que sejam conhecidas as cifras que estão sendo utilizadas na sessão (por meio do
método getCipherSuite()), o ID da sessão (getId()), o protocolo em uso (getProtocol()), os certificados que
foram definidos como parte da definição da sessão (getPeerCertificates()), dentre outras características.
o Classe HandshakeCompletedEvent
Essa classe implementa um evento que indica que o handshake SSL está completo para uma dada conexão
SSL. Todas as informações do resultado do handshake são capturadas por meio de um objeto do tipo
SSLSession.
o Classe KeyManagerFactory
Essa classe funciona como uma "fábrica" de gerenciadores de chaves, baseada em uma fonte das chaves. Cada
gerenciador de chaves gerencia um tipo específico de material para as chaves que serão usadas por um socket
seguro. O material da chave é baseado em um KeyStore e/ou em um provedor específico. Os métodos
comumente utilizados são o init(KeyStore ks, char[] password), onde é inicializado um KeyManagerFactory
para trabalhar com um KeyStore específico e o método getKeyManagers() que retorna um gerenciador para
cada tipo de material de chave.
o Classe SSLContext
Instâncias dessa classe representam uma implementação segura de sockets que age como uma fábrica para a
construção de sockets seguros. Em outras palavras, um SSLContext é um ambiente para a implementação do
JSSE. Podem ser criados por meio do seu método estático getInstance(String protocol), onde se informa um
protocolo (por exemplo, SSLv3) para que se obtenha um SSLContext. Essa classe é inicializada por meio do
método init(KeyManager[] km, TrustManager[] tm, SecureRandom random) .
o Classe SSLSocket
Essa classe estende a classe Socket do pacote javax.net e provê sockets seguros por meio de uso de protocolos
como o SSL ou o TLS. Alguns métodos importantes são: getEnabledCipherSuites() que retorna o nome das
cifras SSL que estão habilitadas para uso na conexão atual, getSession() que retorna a sessão SSL em uso na
conexão atual, setWantClientAuth(boolean auth) que configura o socket para solicitar a autenticação do
cliente, startHandshake() que inicia um handshake SSL para a conexão atual, dentre outros.
o Classe SSLServerSocket
Essa classe estende a classe ServerSocket do pacote javax.net e provê sockets de servidores seguros por meio
de uso de protocolos como o SSL ou o TLS. Intâncias dessas classes podem ser criadas por meio de um
SSLServerSocketFactory. A função primária de um SSLServerSocket é criar sockets SSL aceitando conexões
(por meio do método accept()). Alguns métodos importantes são: getEnabledCipherSuites() que retorna o
nome das cifras SSL que estão habilitadas para uso na conexão atual, getSession() que retorna a sessão SSL
em uso na conexão atual, setNeedClientAuth(boolean auth) que controla se as conexões aceitas pelo servidor
devem incluir autenticação por parte do cliente, dentre outros.
o Classe TrustManagerFactory
Esta classe age como uma fábrica para a criação de gerenciadores confiáveis (TrustManagers). Um
gerenciador confiável gerencia um tipo específico de material para serem usados por sockets seguros. Esse
material é baseado em fonte de Keystores e/ou provedores específicos. Alguns métodos importantes são
getTrustManager() que retorna objetos do tipo TrustManager e init(KeyStore ks) que inicializa um
TrustManagerFactory por meio de um KeyStore.
Por meio dessas e de outras classes que serão discutidas quando necessário, podemos
permitir que aplicações sejam contruídas utilizando o protocolo SSL para o
estabelecimento de conexões seguras.
Criando os certificados
Para a criação dos certificados do cliente e do servidor foi utilizada a ferramenta Keytool,
que já vem instalada na versão 1.4 do Java 2 Second Edition (J2SE). O Keytool é um
utilitário de gerenciamento de certificados e chaves. Possibilita ao usuário administrar seus
pares de chaves públicas e privadas e os certificados associados para uso de auto-
autenticação ou para uso de integridade e autenticação usando assinaturas digitais.
O Keytool fica na pasta bin do diretório do j2sdk.
Esse comando (a primeira linha acima) gera um certificado referenciado pelo alias
servidorsimples e que será armazenado em um arquivo chamado kservidor. O certificado
foi gerado utilizando o algoritmo RSA e foi pedido um password para o armazenamento da
chave no keystore e um password para o alias; optei por informar o valor password para os
dois valores.
Agora vamos mostrar como construir um servidor simples SSL baseado em Java. Nessa
seção serão mostrados os aspectos mais relevantes da criação do servidor, enfocando as
etapas de configuração do socket para a criação de um socket seguro. Para uma referência
completa ao código, veja o Apêndice A.
KeyStore ks = Utils.getKeyStore("JKS");
kmf.init(ks, keypassword);
A primeira linha chama o método getKeyStore("JKS") da classe Utils; esse método retorna
um KeyStore por meio de uma chamada à KeyStore.getInstance(tipo), que cria um
keystore utilizando o provedor JKS da Sun. A segunda linha carrega o arquivo kservidor (o
keystore do servidor), informando o seu password. A terceira linha chama o método
getKMFactory() para criar um KeyManagerFactory que, conforme explcado
anteriormente, é uma fábrica de gerenciadores de chaves. O método
getKMFactory("X509") realiza a sua ação por meio da chamada
KeyManagerFactory.getInstance(algoritmo), que cria um caminho de certificação baseado
em X509. Por fim, a quarta linha inicializa o KeyManagerFactory para trabalhar com o
keystore do servidor.
A primeira linha chama o método estático criaSSLContext() da classe Utils para criar um
objeto SSLContext, onde é informado que se deve utilizar o protocolo SSL versão 3 para a
criação do contexto. O método criaSSLContext() faz isso por meio de uma chamada à
SSLContext.getInstance(protocolo). A segunda linha inicializa o objeto para trabalhar com
o gerenciador de chaves criado previamente. O segundo e terceiros parâmetros marcados
como null indicam que serão utilizados os valores default para o TrustManager e para o
SecureRandom, respectivamente.
if (autCliente){
servidorSSL.setNeedClientAuth(autCliente);
}
O método run() chama o método criaSSLSocket(); esse método é bastante parecido com o
método criaSSLServerSocket de ServidorSimplesSSL, mas, ao invés de criar um
SSLServerSocket, ele cria um SSLSocket. No mais, os passos são praticamente os mesmos
para a criação do socket do servidor: criação de um KeyStore; criação de um
KeyManagerFactory; isso permite que trabalhemos com o keystore do cliente. Em seguida
criamos um objeto do tipo SSLContext, o qual nos irá permitir criar um SSLSocketFactory
(veja que não é um SSLServerSocketFactory), que por sua vez nos permitirá construir o
objeto SSLSocket.
KeyStore ks = Utils.getKeyStore("JKS");
ks.load(new FileInputStream(keystore), password);
Vamos, a seguir, mostrar alguns testes de execução do servidor e do cliente. Os testes foram
realizados utilizando o J2SDK 1.4.1, executando em ambiente MS Windows 98. Contudo,
não deve haver problemas para a execução dos testes em outros Sistemas Operacionais.
Estabelecendo uma conexão SSL com ServidorSimplesSSL por meio de um navegador Web
>javac ServidorSimplesSSL.java
>java ServidorSimplesSSL
Temos então que informar qual o password do keystore do servidor (kservidor). Quando
criamos o keystore e o certificado, informamos o valor password. Então, entramos com esse
valor. A janela abaixo mostra o servidor em execução e aguardando por uma conexão. Vale
ressaltar, que nesse exemplo, o arquivo kservidor está na mesma pasta que o
ServidorSimplesSSL.
Figura 3 - ServidorSimplesSSL em execução
Optamos então por aceitar o certificado temporariamente para essa seção (poderíamos
também ter escolhido qualquer uma das outras opções). Então a conexão SSL é
estabelecida com o servidor e o navegador mostra a página index.html, conforme a seguir.
Veja o destaque no canto inferior direito, informando que a conexão é segura.
Figura 6 - Conexão estabelecida
Agoras vamos fazer a conexão entre o servidor e o cliente. Em uma janela de comando,
colocamos o servidor para executar, conforme fizemos anteriormente. Em outra janela de
comando, executamos o cliente. Para isso devemos compilar o cliente por meio do comando
javac e em seguida executá-lo informando o host em que ele deve se conectar (no caso,
localhost). Quando da execução do cliente, assim como no servidor, nos é solicitado um
password para o keystore kcliente do cliente, onde devemos informar password. Contudo,
se executarmos da forma normal, isto é, fazendo java ClienteSimplesSSL localhost, algo de
interessante acontece. Vejamos a seguir.
Figura 7 - Conexão cliente-servidor recusada: cliente não reconhece certificado
Para resolver este problema, devemos informar ao cliente em que certificado ele deve
confiar, ou seja, qual é a fonte confiável. Isso pode ser feito da seguinte forma:
Veja que o servidor enviou a mensagem null cert chain, significando que não encontrou um
certificado confiável. O cliente recebeu uma mensagem informando que a conexão foi
reiniciada. Para resolver isso, fazemos da mesma forma que fizemos quando queríamos
que o cliente reconhecesse o certificado do servidor: devemos informar ao servidor, por
meio de -Djavax.net.ssl.trustStore=kcliente, qual é o gerenciador confiável para ele. Dessa
forma, a conexão se estabelece corretamente, e o cliente exibe a página enviada pelo
servidor.
Figura 10 - Conexão cliente-servidor estabelecida com sucesso: servidor reconheceu certificado do cliente
Aqui são mostrados os códigos completos dos arquivos necessários para o funcionamento
do servidor e do cliente. São eles: ServidorSimplesSSL.java, ClienteSimplesSSL.java e
Utils.java. Ao final há um arquivo compactado, contendo os arquivos .java, os arquivos
.class, os keystore kservidor e kcliente e o arquivo index.html que é utilizado para o
servidor enviar dados ao cliente.
ServidorSimplesSSL.java
Nota: Essa versão não exige autenticação por parte do cliente.
import java.io.*;
import java.net.*;
import javax.net.*;
import javax.net.ssl.*;
import java.security.*;
import java.util.*;
//construtor
public ServidorSimplesSSL(String nome, boolean autCliente,
String password){
this.nome = nome;
this.autCliente = autCliente;
keystorepass = password.toCharArray();
keypassword = password.toCharArray();
}
KeyStore ks = Utils.getKeyStore("JKS");
ks.load(new FileInputStream(keystore), keystorepass);
showPropSSLContext(contextoSSL);
ServerSocket listen;
try{
//vai criar um SSLServerSocket
listen = criaSSLServerSocket();
System.out.println(this.nome+" executando na porta
"+HTTPS_PORT);
System.out.println("Aguardando conexao...");
//espera por uma conexão do cliente
Socket cliente = listen.accept();
Conexao con = new Conexao(cliente);
}catch(Exception e){
System.out.println("Exception "+e.getMessage());
e.printStackTrace();
}
System.out.println("-------Informaçoes de contexto
SSL-------");
//main
public static void main(String[] args) throws Exception{
//classe interna
class Conexao extends Thread {
Socket cliente;
BufferedReader in;
DataOutputStream out;
public Conexao(Socket s) {
cliente = s;
try {
in = new BufferedReader(new InputStreamReader
(cliente.getInputStream()));
out = new DataOutputStream(cliente.getOutputStream());
}catch (IOException e) {
System.out.println("Excecao lancada: "+e.getMessage());
}
this.start(); // chama o método run
}
public void run(){
try {
String request = in.readLine();
System.out.println( "Request: "+request );
StringTokenizer st = new StringTokenizer(request);
if ((st.countTokens() >= 2) && st.nextToken().equals("GET"))
{
if ((request = st.nextToken()).startsWith("/"))
request = request.substring( 1 );
if (request.equals(""))
request = request + "index.html";
File arq = new File(request);
leDocumento(out, arq);
}
else{
out.writeBytes( "Erro 400: arquivo nao encontrado.");
}
cliente.close();
}catch (Exception e) {
System.out.println("Excecao lancada: " + e.getMessage());
}
}
ClienteSimplesSSL.java
import java.io.*;
import java.net.*;
import java.security.*;
import javax.net.ssl.*;
KeyStore ks = Utils.getKeyStore("JKS");
ks.load(new FileInputStream(keystore), password);
return socket;
}
try{
socket = criaSSLSocket(host); //cria o socket SSL que o
cliente utilizará
}catch(Exception e){
System.out.println("Excecao 1 lancada : "+e.getMessage());
}
try{
BufferedWriter out = new BufferedWriter(new
OutputStreamWriter(socket.getOutputStream()));
BufferedReader in = new BufferedReader(new
InputStreamReader(socket.getInputStream()));
out.write("GET / HTTP/1.0\n\n");
out.flush();
//o trecho a seguir pega o arquivo do servidor, lê esse
arquivo e imprime na tela
String linha;
StringBuffer sb = new StringBuffer();
while((linha = in.readLine()) != null) {
sb.append(linha);
sb.append("\n");
}
out.close();
in.close();
System.out.println(sb.toString());
}catch(Exception e){
System.out.println("Excecao 2 lancada: "+e.getMessage());
}
if (argv.length != 1) {
System.out.println("Deve ser informado o host em que o
cliente deve se conectar.");
System.exit(0);
}
Utils.java
import javax.net.ssl.*;
import java.security.*;
}
}