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.
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 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.
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.
>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): |
>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): |
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); |
ServerSocketFactory ssf = contextoSSL.getServerSocketFactory(); SSLServerSocket servidorSSL = (SSLServerSocket) ssf.createServerSocket(HTTPS_PORT); |
if (autCliente){ servidorSSL.setNeedClientAuth(autCliente); } |
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); |
>javac ServidorSimplesSSL.java |
>java ServidorSimplesSSL |
>java -Djavax.net.ssl.trustStore=kservidor ClienteSimplesSSL localhost |
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(); } } } } |
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(); } } |
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); } } |