You are on page 1of 21

Uma Implementação SSL Utilizando JSSE

2004

• Introdução

Quaisquer informações transmitidas em redes de computadores, ou na Internet, está


suscetível à intercepção por outros não autorizados. Algumas informações podem ser
sensíveis, como número de cartão de crédito e outras informações pessoais. Para tornar a
Internet mais útil para empresas e mais segura para o comércio eletrônico, aplicações
devem proteger as informações de seus usuários, utilizando encriptação, autenticação e
protocolos seguros de comunicação. O secure Hypertext Transfer Protocol (HTTPs), que é o
HTTP sobre o Secure Socket Layer (SSL), é usado com sucesso em aplicações de comércio
eletrônico.

• 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 encriptação protege dados contra uso não autorizado, utilizando algoritmos


criptográficos, antes da transmissão. O dado é criptografado em um lado (cliente ou
servidor), transmitido, decifrado pelo outro lado e, então, processado.

A autenticação é um método para verificar a indentidade do remetente. A primeira vez que


um navegador Web ou outro cliente tenta se comunicar com um servidor Web sobre uma
conexão segura, o servidor apresenta ao cliente um conjunto de credenciais na forma de
um certificado.

Certificados são emitidos e validados por autoridades confiáveis conhecidas como


Autoridades Certificadoras (CAs). Um certificado representa a identidade da chave
pública de uma pessoa. É um documento assinado que tem por finalidade dizer: "Eu
certifico que a cheve pública presente neste documento pertence a entidade nomeada neste
documento. Assinado CA". Algumas CAs bastante conhecidas são a Verisign e Entrust. Vale
ressaltar que os certificados utilizados com SSL/TLS são certificados X.509.

A integridade de dados refere-se a uma maneira de assegurar que os dados não foram
modificados durante a tranmissão

SSL e a pilha de protocolos TCP/IP

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.

Negociação da Encriptação (Handshake)

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.

Os parâmetros criptográficos do estado de uma sessão são produzidos pelo Handshake do


protocolo SSL, que opera no topo da camada Record Layer do SSL. Quando um cliente SSL
e um servidor SSL iniciam a comunicação, eles concordam em uma versão do protocolo,
escolhem os algortimos criptográficos, autenticam um ao outro (opcionalmente) e usam
criptografia de chave pública para gerar os segredos que compartilham. Esses passos são
realizados pelo protocolo handshake, que pode ser resumido como segue:

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).

Figura 2 - Handshake SSL

• 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.

Além disso, o JSSE abstrai a complexidade inerente a certos algortimos criptográficos e,


portanto, minimiza o risco de criação de vulnerabilidades. Como veremos, o JSSE torna o
desenvolvimento de aplicações habilitadas com SSL bastante simples, nos permitindo
integrar o SSL de forma fácil. O framework JSSE é capaz de suportar muitos protocolos de
comunicação segura como SSL 2.0 e 3.0 e TLS 1.0.

• Programando com JSSE

As APIS JSSE complementam os pacotes java.security e java.net, provendo classes que


implementam sockets de rede, gerenciadores de chaves e um framework que funciona
como uma fábrica para o encapsulamento do comportamento de sockets. Essas classes
estão incluídas nos pacotes javax.net e javax.net.ssl. O principal pacote para a criação de
sockets seguros baseados em SSL é o javax.net.ssl. A seguir, há uma breve descrição de
algumas das classes e interfaces desse pacote.

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.

• Lidando com os Certificados

Conforme mostrado na seção Negociação da Encriptação (Handshake), a troca de


certificados entre cliente e servidor, embora seja opcional, é uma das partes mais
importantes do protocolo SSL. Normalmente, o servidor apresenta o seu certificado como
forma de autenticar-se perante o cliente; procedimento comum em transações comerciais
na Web. A autenticação por parte do cliente, isto é, o cliente ter que apresentar o seu
certificado é mais raro, embora seja possível.

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.

1.Criando o certificado do Servidor

Keytool armazena as chaves e os certificados em um local chamado keystore. O keystore é


normalmente implementado na forma de arquivos, protegendo as chaves privadas com
passwords.
Para a criação do certificado do servidor, foi utilizado o comando abaixo.

>keytool -genkey -keystore kservidor -keyalg rsa -alias


servidorsimples
Enter keystore password: password
What is your first and last name?
[Unknown]: localhost
What is the name of your organizational unit?
[Unknown]: CIC
What is the name of your organization?
[Unknown]: UnB
What is the name of your City or Locality?
[Unknown]: Brasilia
What is the name of your State or Province?
[Unknown]: DF
What is the two-letter country code for this unit?
[Unknown]: BR
Is CN=localhost, OU=CIC, O=UnB, L=Brasilia, ST=DF, C=BR correct?
[no]: yes

Enter key password for <servidorsimples>


(RETURN if same as keystore password):

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.

2.Criando o certificado do Cliente

A geração do certificado para o cliente se dá de forma semelhante. Apenas o keystore


informado foi kcliente e o alias foi clientesimples. O comando abaixo mostra a criação do
certificado para o cliente.

>keytool -genkey -keystore kcliente -keyalg rsa -alias


clientesimples
Enter keystore password: password
What is your first and last name?
[Unknown]: Cliente
What is the name of your organizational unit?
[Unknown]: Desenvolvimento
What is the name of your organization?
[Unknown]: Sistemas de Seguranca
What is the name of your City or Locality?
[Unknown]: Brasilia
What is the name of your State or Province?
[Unknown]: DF
What is the two-letter country code for this unit?
[Unknown]: BR
Is CN=Cliente, OU=Desenvolvimento, O=Sistemas de Seguranca,
L=Brasilia, ST=DF, C
=BR correct?
[no]: yes

Enter key password for <clientesimples>


(RETURN if same as keystore password):

2 Implementando o servidor : ServidorSimplesSSL

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.

O método main() de ServidorSimplesSSL inicia pedindo o password do keystore do


servidor. Em seguida, chama o construtor de ServidorSimplesSSL para criar uma instância
desse objeto. Como argumentos são passados o nome do servidor, o password informado e
um valor booleano, que serve para indicar se o servidor vai ou não requerer a autenticação
por parte do cliente. A seguir é chamado o método run do ServidorSimplesSSL.

Dentro do método run(), é chamado o método criaSSLServerSocket(), que como o seu


nome diz, cria um socket do tipo SSLServerSocket. Este método é o mais importante da
classe ServidorSimplesSSL, pois, é ele quem configura todas as propriedades para que a
conexão seja segura e baseada em SSL. Como vai ser exigido que o servidor se autentique
para o cliente, a primeira coisa que o método faz é carregar o keystore do servidor
kservidor, que foi criado previamente (veja Criando o certificado do Servidor). As quatro
linhas seguintes fazem isso.

KeyStore ks = Utils.getKeyStore("JKS");

ks.load(new FileInputStream(keystore), keystorepass);

KeyManagerFactory kmf = Utils.getKMFactory("SunX509");

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 seguir, precisamos criar um objeto do tipo SSLContext. Conforme explicado


anteriormente, é a partir do SSLContext que poderemos criar sockets seguros SSL. As
duas linhas a seguir mostram como o SSLContext foi criado.
SSLContext contextoSSL = Utils.criaSSLContext("SSLv3");

contextoSSL.init(kmf.getKeyManagers(), null, null);

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.

Agora, estabelecido o objeto SSLContext, podemos criar o socket seguro SSL. O


procedimento é feito em dois passos: primeiro obtemos um ServerSocketFactory a partir
de SSLContext e, em seguida, a partir do ServerSocketFactory, criamos um
SSLSocketServer. As duas linhas a seguir mostram essas operações.

ServerSocketFactory ssf = contextoSSL.getServerSocketFactory();


SSLServerSocket servidorSSL = (SSLServerSocket)
ssf.createServerSocket(HTTPS_PORT);

O método createServerSocket() utiliza a variável de instância HTTPS_PORT definida como


443 (a porta padrão para conexões SSL) para a criação do Socket.

O método criaSSLServerSocket(), antes de devolver o SSLServerSocket criado, verifica se


é requerida autenticação do cliente; se for ele executa o método setNeedClientAuth() para
requerer essa autenticação. O trecho a seguir mostra isso:

if (autCliente){
servidorSSL.setNeedClientAuth(autCliente);
}

3 Implementando o cliente: ClienteSimplesSSL

A implementação do ClienteSimplesSSL é bastante similar a do servidor. A função básica


do cliente é se conectar ao servidor, lê um arquivo e exibi-lo na tela.

Assim como no servidor, o método main() de ClienteSimplesSSL chama o construtor da


classe, passando como argumentos o nome do cliente, o password do seu keystore (lido via
linha de comando) e o host no qual o cliente deve se conectar (um argumento passado via
linha de comando). Em seguida é chamado o método run() de ClienteSimplesSSL.

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.

O trecho a seguir mostra a criação do SSLSocket para o cliente.

KeyStore ks = Utils.getKeyStore("JKS");
ks.load(new FileInputStream(keystore), password);

KeyManagerFactory kmf = Utils.getKMFactory("SunX509");


kmf.init(ks, password);

SSLContext sslcontext = Utils.criaSSLContext("SSLv3");


sslcontext.init(kmf.getKeyManagers(), null, null);

SSLSocketFactory ssf = sslcontext.getSocketFactory();


SSLSocket socket = (SSLSocket)
ssf.createSocket(host,HTTPS_PORT);

Veja que a implementação é realmente bastante similar àquela do servidor.

4 Testando o ServidorSimplesSSL e o ClienteSimplesSSL

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

Neste primeiro teste, vamos nos conectar ao ServidorSimplesSSL utilizando o navegador


Mozilla. Primeiramente, devemos compilar o arquivo ServidorSimplesSSL.java, por meio
do comando javac, conforme mostrado a seguir.

>javac ServidorSimplesSSL.java

A seguir, executamos o ServidorSimplesSSL. Para isso abrimos uma janela de comando e


no prompt digitamos

>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

Agora, na janela do Mozilla e com o servidor em execução, entramos com o endereço


https://localhost. Ao fazermos, o navegador mostra uma informação alertando que o
servidor é certificado por um Autoridade de Certificação desconhecida. Veja abaixo.

Figura 4 - Alerta do navegador Mozilla


Antes de aceitar o certificado, optamos por examiná-lo, para verificar as informações dele.
A janela a seguir mostra essas informações, onde vemos que esse é realmente o certificado
auto-assinado que criamos anteriormente.

Figura 5 - Visualizando o certificado do servidor

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

Fazendo a comunicação cliente-servidor entre ClienteSimplesSSL e ServidorSimplesSSL

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

A janela da esquerda representa a execução do cliente e a da direita a do servidor.


Observemos a última linha de execução de cada um dos componentes; no lado do servidor,
vemos que uma exceção foi lançada e o servidor recebeu a seguinte mensagem: Received
fatal alert: certificate_unknown. Do lado do cliente uma exceção também foi lançada, que
informa: Couldn't find trusted certificate. Isso ocorreu porque o cliente espera que o
servidor se autentique perante ele; contudo, o certificado que o servidor apresenta não é
confiável pelo cliente; logo este envia uma mensagem que não encontrou um certificado
confiável, o servidor recebe esta notificação e a conexão é abortada.

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:

>java -Djavax.net.ssl.trustStore=kservidor ClienteSimplesSSL


localhost

Assim estamos informando, por meio do parâmetro -Djavax.net.ssl.trustStore, que o cliente


deve confiar em kservidor. Dessa forma, a conexão se estabelece normalmente, e o cliente
imprime a página index.html enviada pelo servidor (figura a seguir).
Figura 8 - Conexão cliente-servidor estabelecida com sucesso

Conforme vimos na seção Negociação de Encriptação (Handshake), o servidor também


pode requerer autenticação por parte do cliente. Este último teste enfoca essa questão. Para
isso, no método main() de ServidorSimplesSSL, devemos alterar o segundo parâmetro do
construtor de false para true. Assim, no método criaSSLServerSocket() será requerida a
autenticação do cliente por meio da chamada à setNeedClientAuth(autCliente). Agora,
depois de recompilarmos o servidor, executamo-lo e em seguida solicitamos uma conexão
por parte do cliente. Se fizermos a execução do servidor da forma normal (somente java
ServidorSimplesSSL), o mesmo erro que aconteceu anteriormente na parte do cliente se
repetirá, pois, como o servidor requer a autenticação do cliente, este apresenta o seu
certificado. Contudo, este certificado não é conhecido pelo servidor, que aborta a conexão.
Esta execução é mostrada a seguir.
Figura 9 - Conexão cliente-servidor recusada: servidor não reconheceu certificado

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

5 Apendice A: Código Completo

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.*;

public class ServidorSimplesSSL{

String keystore = "kservidor";


char keystorepass[];
char keypassword[] ;
public static final int HTTPS_PORT = 443;
boolean autCliente;
String nome;
ObjectOutputStream out;
ObjectInputStream in;

//construtor
public ServidorSimplesSSL(String nome, boolean autCliente,
String password){
this.nome = nome;
this.autCliente = autCliente;
keystorepass = password.toCharArray();
keypassword = password.toCharArray();
}

public ServerSocket criaSSLServerSocket() throws Exception{

KeyStore ks = Utils.getKeyStore("JKS");
ks.load(new FileInputStream(keystore), keystorepass);

KeyManagerFactory kmf = Utils.getKMFactory("SunX509");


kmf.init(ks, keypassword);

SSLContext contextoSSL = Utils.criaSSLContext("SSLv3");


contextoSSL.init(kmf.getKeyManagers(), null, null);

showPropSSLContext(contextoSSL);

ServerSocketFactory ssf = contextoSSL.getServerSocketFactory();


SSLServerSocket servidorSSL = (SSLServerSocket)
ssf.createServerSocket(HTTPS_PORT);
//Se necessário, autentica o cliente
if (autCliente){
servidorSSL.setNeedClientAuth(autCliente);
}
return servidorSSL;
}

public void run(){

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();
}

private void showPropSSLContext(SSLContext contextoSSL){

System.out.println("-------Informaçoes de contexto
SSL-------");

String protocol = contextoSSL.getProtocol();


System.out.println("Protocolo : "+protocol);

Provider provider = contextoSSL.getProvider();


System.out.println("Nome do provedor : "+provider.getName());
System.out.println("Versao do provedor :
"+provider.getVersion());
SSLSessionContext sslsessioncontext =
contextoSSL.getServerSessionContext();

//main
public static void main(String[] args) throws Exception{

System.out.print("Informe o password para o keystore do


servidor:");
BufferedReader in = new BufferedReader(new
InputStreamReader(System.in));
String password = in.readLine();
ServidorSimplesSSL servidor = new ServidorSimplesSSL("Servidor
HTTPs", false, password);
servidor.run();

//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());
}
}

// Lê o arquivo e o envia para o cliente


public void leDocumento(DataOutputStream out, File arq) throws
Exception {
try {
DataInputStream in = new DataInputStream(new
FileInputStream(arq));
int tam = (int) arq.length();
byte[] buffer = new byte[tam];
in.readFully(buffer);
in.close();
out.writeBytes("HTTP/1.0 200 OK\r\n");
out.writeBytes("Tamanho do conteúdo: " + tam +"\r\n");
out.writeBytes("Tipo do conteúdo: text/html\r\n\r\n");
out.write(buffer);
out.flush();
}catch (Exception e) {
out.writeBytes("<html><head><title>Erro</title></head><body
>\r\n\r\n");
out.writeBytes("HTTP/1.0 400 " + e.getMessage() + "\r\n");
out.writeBytes("Tipo do conteúdo: text/html\r\n\r\n");
out.writeBytes("</body></html>");
out.flush();
}finally {
out.close();
}
}

ClienteSimplesSSL.java

import java.io.*;
import java.net.*;
import java.security.*;
import javax.net.ssl.*;

public class ClienteSimplesSSL{

private static final int HTTPS_PORT = 443;


private static SSLSocket socket;
private String keystore = "kcliente";
private char[]password;
private String nome;
private String host;

public ClienteSimplesSSL(String nome, String password, String


host){
this.nome = nome;
this.password = password.toCharArray();
this.host = host;
}

private SSLSocket criaSSLSocket(String host) throws Exception{

KeyStore ks = Utils.getKeyStore("JKS");
ks.load(new FileInputStream(keystore), password);

KeyManagerFactory kmf = Utils.getKMFactory("SunX509");


kmf.init(ks, password);

SSLContext sslcontext = Utils.criaSSLContext("SSLv3");


sslcontext.init(kmf.getKeyManagers(), null, null);

SSLSocketFactory ssf = sslcontext.getSocketFactory();


SSLSocket socket = (SSLSocket)
ssf.createSocket(host,HTTPS_PORT);

return socket;
}

public void run(){

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());
}

public static void main(String argv[]) throws IOException{

if (argv.length != 1) {
System.out.println("Deve ser informado o host em que o
cliente deve se conectar.");
System.exit(0);
}

System.out.print("Informe o password para o keystore do


cliente:");
BufferedReader in = new BufferedReader(new
InputStreamReader(System.in));
String password = in.readLine();
ClienteSimplesSSL cliente = new ClienteSimplesSSL("Cliente 1",
password, argv[0]);
cliente.run();
}

Utils.java

import javax.net.ssl.*;
import java.security.*;

public class Utils{

protected static KeyStore getKeyStore(String tipo) throws


KeyStoreException{

//utiliza a implementação do keystore provido pela Sun


return KeyStore.getInstance(tipo);

protected static KeyManagerFactory getKMFactory(String


algoritmo) throws NoSuchAlgorithmException{

//cria um caminho de certificação baseado em X509


return KeyManagerFactory.getInstance(algoritmo);

protected static SSLContext criaSSLContext(String protocolo)


throws NoSuchAlgorithmException{

//cria um SSLContext segundo o protocolo informado


return SSLContext.getInstance(protocolo);

}
}

You might also like