Sessões laboratoriais de CA (2007/2008)
Aula 1 - Ambiente de Desenvolvimento
O objectivo principal desta aula é o de escolher/instalar o ambiente de desenvolvimento
Java que será utilizado durante o curso.
Como actividade de programação (para experimentar o ambiente escolhido), deve desenvolver uma pequena aplicação que leia um ficheiro de texto (nome do ficheiro passado como argumento), e escreva para
stdout
o seu conteúdo com todas as letras maiúsculas.
Aula 2 - Cifra de ficheiro utilizando JCA/JCE
Pretende-se cifrar o conteudo de um ficheiro. Para tal far-se-á uso da funcionalidade oferecida pela JCA/JCE, em particular a implementação de cifras simétricas.
O objectivo é então o de definir um pequeno programa Java que permita cifrar/decifrar um ficheiro utilizando uma cifra simétrica (e.g. RC4). A sua forma de utilização pode ser análoga a:
prog -genkey <keyfile>
prog -enc <keyfile> <infile> <outfile>
prog -dec <keyfile> <infile> <outfile>
Sugestões:
- Para simplificar, pode começar por utilizar uma chave secreta fixa, definida no código na forma de um array de bytes. Nesse caso, deverá utilizar a classe SecretKeySpec para a converter para o formato adequado.
- É também interessante verificar que o criptograma gerado é compatível com outras plataformas que implementam a mesma cifra. O comando seguinte utiliza o openssl para decifrar um ficheiro cifrado com RC4 (a chave tem de ser fornecida em hexadecimal).
openssl enc -d -rc4 -in <infile> -out <outfile> -K <chave>
Algumas classes relevantes:
Aula 3 - Confidencialidade na comunicação Cliente-Servidor
As classes
Cliente,
Servidor e
TServidorimplementam uma aplicação que permite a um número arbitrário de
clientes comunicar com
um servidor que escuta num dado
port (e.g. 4567). O servidor atribui um número de ordem a cada cliente, e simplesmente faz o
dump do texto enviado por cada cliente (prefixando cada linha com o respectivo número de ordem).
Quando um cliente fecha a ligação, o servidor assinala o facto (e.g. imprimindo
[n]
, onde
n é o número do cliente).
Exemplo da execução do servidor (que comunica com 3 clientes):
$ java Servidor
1 : daskj djdhs slfghfjs askj
1 : asdkdh fdhss
1 : sjd
2 : iidhs
2 : asdjhf sdga
2 : sadjjd d dhhsj
3 : djsh
1 : sh dh d d
3 : jdhd kasjdh as
2 : dsaj dasjh
3 : asdj dhdhsjsh
[3]
2 : sjdh
1 : dhgd ss
[1]
2 : djdj
[2]
Pretende-se nesta aula modificar as respectivas classes por forma a garantir a confidencialidade nas comunicações estabelecidas. Escolha para o efeito a cifra/modo que considerar mais apropriado.
Algumas classes relevantes:
Aula 4 - Confidencialidade na comunicação Cliente-Servidor (continuação)
Pretende-se nesta aula experimentar o impacto da escolha da cifra/modo na comunicação entre o cliente/servidor. Para tal é conveniente
reforçar a natureza interactiva da comunicação modificando os ciclos de leitura/escrita para operarem sobre um byte de cada vez:
Cliente:
CipherOutputStream cos = ...
int test;
while((test=System.in.read())!=-1) {
cos.write((byte)test);
cos.flush();
}
Servidor:
CipherInputStream cis = ...
int test;
while ((test=cis.read()) != -1) {
System.out.print((char) test);
}
Experimente agora as seguintes cifras (e modos) e verifique qual o respectivo impacto nas questões de
buffering e
sincronização:
-
RC4
-
AES/CBC/NoPadding
-
AES/CBC/PKCS5Padding
-
AES/CFB8/PKCS5Padding
-
AES/CFB8/NoPadding
-
AES/CFB/NoPadding
Procure explicar a diferenças detectadas na execução da aplicação.
Obs: Note que em muitos dos modos sugeridos necessita de considerar um IV. Considere para o efeito que o IV é gerado pelo cliente e enviado
em claro para o servidor (no início da comunicação).
Algumas classes relevantes (para além das já estudadas...):
Aula 5 - Acordo de Chaves Diffie-Hellman
O objectivo desta aula é implementar o acordo de chaves
Diffie-Hellman. Algumas sugestões para atacar o problema:
- Comece por utilizar a classe
BigInteger
e codifique cada passo do protocolo explicitamente. Pode começar por utilizar os seguintes parâmetros para o grupo (P tem 1024 bit):
P = 99494096650139337106186933977618513974146274831566768179581759037259788798151499814653951492724365471316253651463342255785311748602922458795201382445323499931625451272600173180136123245441204133515800495917242011863558721723303661523372572477211620144038809673692512025566673746993593384600667047373692203583
G = 44157404837960328768872680677686802650999163226766694797650810379076416463147265401084491113667624054557335394761604876882446924929840681990106974314935015501571333024773172440352475358750668213444607353872754650805031912866692119819377041901642732455911509867728218394542745330014071040326856846990119719675
- No JCA, podemos gerar valores apropriados para os parâmetros necessários através de uma instância apropriada da classe
AlgorithmParameterGenerator
.
- Em vez de trabalharmos directamente com a classe
BigInteger
, pode-se fazer uso da classe KeyAgreement
.
- No JCE Reference Guide está disponível um exemplo de codificação do protocolo.
Novas classes:
Aula 6 - Continuação da codificação do protocolo Diffie-Hellman
Nesta aula pretende-se concluir o protocolo de acordo de chaves
Diffie-Hellman. Sugere-se que agora façam exclusivamente uso das funcionalidades oferecidas pela JCA, nomeadamente:
- Classe
AlgorithmParameterGenerator
para gerar os parâmetros P
e G
do algoritmo;
- Classe
KeyPairGenerator
para gerar os pares de chaves ( (x, g^x) e (y,_g^y_) para cada um dos intervenientes);
- Classe
KeyAgreement
que implementa o protocolo propriamente dito.
APIs:
Aula 7 - Teste intermédio
Aula 8 - Codificação do protocolo Station-to-Station.
Pretende-se complementar o programa com o acordo de chaves
Diffie-Hellman para incluir a funcionalidade do protocolo
Station to Station. Recorde que nesse protocolo é adicionado uma troca de assinaturas (cifrada com a chave de sessão negociada), i.e.:
- Alice -> Bob : g^x
- Alice <- Bob : g^y, EK(SB(g^y, g^x))
- Alice -> Bob : EK(SA(g^x, g^y))
Um requisito adicional neste protocolo é a manipulação de pares de chaves de cifras assimétricas (e.g.
RSA). Para tal deve produzir um pequeno programa que gere os pares de chaves para cada um dos intervenientes e os guarde em ficheiros que serão lidos pela aplicação
Cliente/Servidor.
Novas Classes:
Aula 9 - Continuação da aula anterior.
Aula 10 - Utilização de certificados X509 em Java
Pretende-se certificar as chaves públicas utilizadas no protocolo
Station-to-Station com base em certificados X509. Para tal, disponibiliza-se:
- Certificado de chave pública do Servidor: servidor.crt
- Chave privada do Servidor (codificada em PKCS8): servidor.pk8
- Certificado de chave pública do Cliente: cliente.crt
- Chave privada do Cliente (codificada em PKCS8): cliente.pk8
- Certificado auto-assinado da autoridade de certificação: ca.crt
A utilização de certificados pressupõe a sua validação. O Java disponibiliza uma API específica que deverá utilizar para o efeito (documentação abaixo). Para facilitar esse estudo recomenda-se o estudo/adaptação de um programa de exemplo que verifica a validade de uma cadeia de certificação:
ValidateCertPath.java. Utilizando esse programa, podemos verificar a validade do certificado do servidor através da linha de comando:
java ValidateCertPath ca.crt servidor.crt
Uma segunda questão que surgirá neste trabalho é a manipulação dos formatos das chaves: as chaves privadas correspondentes aos certificados fornecidos estão codificadas num formato standard PKCS8. Para se converter esse formato num objecto Java apropriado terá de se utilizar uma instância da classe
KeyFactory
. O fragmento de código que se apresenta ilustra esse processo:
byte[] encodedKey; // read from file
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPrivateKey privKey =
(RSAPrivateKey)keyFactory.generatePrivate(keySpec);
Classes requeridas:
Outra documentação relevante:
Aula 11 - Continuação da aula anterior.
Aula 12 - Geração de Certificados X509.
Nesta sessão iremos fazer uso do
openssl para construir uma pequena
autoridade de certificação.
O processo de emissão de certificados X509 passa pelos seguintes passos:
- - O utilizador gera um par de chaves;
- - O utilizador produz um "pedido de certificado" que contém a sua identificação e a sua chave pública, que é assinado com a sua chave privada (o que certifica que quem solicita o certificado está na posse da respectiva chave privada);
- - A autoridade de certificação valida os dados contidos no certificado e, no caso de certificar positivamente esses dados, emite o certificado de chave pública correspondente.
- - O certificado é enviado ao utilizador e, eventualmente, publicado por outros meios (e.g. serviço de directoria).
Para cada um destes passos iremos fazer uso dos comandos respectivos do
openssl.
Geração de chaves:
openssl genrsa -out grupoXPTO.key
Para utilizar a chave privada no
Java é conveniente converter o seu formato para PKCS8
openssl pkcs8 -topk8 -nocrypt -in grupoXPTO.key -outform der -out grupoXptoPrivKey.der
Geração do pedido de certificado:
openssl req -new -key grupoXPTO.key -out grupoXPTO.csr
Emissão do certificado: A emissão de certificados é normalmente realizada com o auxílio de
scripts que invocam o comando
openssl com os argumentos apropriados. Existem duas
scripts normalmente utilizadas para este efeito:
- CA.pl - distribuída com o "openssl".
- sign.sh - distribuída com o "mod_ssl". Espera encontrar ficheiros
ca.key
e ca.crt
.
Produção de PKCS12: Para certas utilizações (e.g.
browsers, leitores de
email, etc.) é conveniente encapsularmos o certificado e respectiva chave privada num PKCS12.
openssl pkcs12 -export -chain -CAfile cacert.pem -name GrupoXPTO -aes128 -inkey grupoXPTO.key -in grupoXPTO.crt -out GrupoXPTO.p12
Verificação dos certificados:
openssl verify -CAfile cacert.pem cert1.crt cert2.crt ...
utilizando este
Certificado da autoridade de certificação de raiz.
Apontadores úteis: