Implementação de um Cliente SSH
Edans Flavius de Oliveira Sandes
Departamento de Ciência da Computação
Universidade de Brasília
17 de Dezembro de 2003
SSH é um protocolo para login remoto
seguro e outros serviços contextualizados em redes abertas. Esse protocolo é
dividido em três componentes básicos:
·
Camada de Transporte: Provê sigilo, integridade e autenticação do servidor. Opcionalmente,
pode ser oferecido um serviço de compressão de dados. Essa camada geralmente é
rodada em cima de uma conexão TCP/IP, mas é possivel que ela seja usada em cima
de qualquer outro tipo de protocolo confiável e orientado a conexão.
·
Protocolo de Autenticação: Autentica o usuário perante o servidor. Esse
protocolo necessita da camada de transporte para o seu funcionamento.
·
Protocolo de Conexão: Permite a multiplexação da conexão entre vários canais lógicos. Esse
protocolo depende do protocolo de autenticação. Nessa camada encontramos recursos
como shell interativo, tunelamento de portas TCP/IP e conexões X11.
O SSH é bastante flexível no sentido de
que os algoritmos usados em uma conexão são negociados entre o cliente e o
servidor. Isso permite que algoritmos fracos sejam removidos ou novos
algoritmos sejam testados sem que haja uma mudança no protocolo.
A
seguir será mostrado como funciona cada um dos componentes que foram citados
acima.
A camada de tranporte do SSH é um
protocolo seguro de baixo nivel que fornece encriptação, integridade e autenticação
do servidor. Observe que aqui somente é feita a autenticação do servidor, sendo
que a autenticação do usuario é responsabilidade do protocolo de autenticação.
Os pacotes que são recebidos e
enviados por essa camada possuem o seguinte formato:
Tamanho (4
bytes) |
Tamanho do preenchimento (1 byte) |
Conteúdo (n1 bytes) |
Preenchimento (n2 bytes) |
MAC (m
bytes) |
Tamanho: Indica o comprimento do pacote sem
incluir o MAC e o próprio campo Tamanho.
Tamanho = 1 + n1 + n2.
Tamanho
do preenchimento = “n2”: Número de bytes que serão colocados no pacote para
que o tamanho total (sem o MAC) seja
múltiplo de 8 (ou do tamanho do bloco usado na encriptacao). n2 não pode ser
menor que 4.
Conteúdo:
Conteúdo útil do pacote. Se algum algoritmo de compressão estiver sendo usado,
esse campo é compactado.
Preenchimento:
“n2” bytes aleatórios.
MAC:
Código de autenticação de mensagem. O número “m” de bytes varia de acordo com o
algoritmo que está sendo utilizado. O cálculo do MAC é: mac = MAC(key, no_do_pacote
|| pacote_não_encriptado), onde no_do_pacote
é um contador de pacotes enviados (ou recebidos).
0-
Troca de strings de versão: Após a conexão ser estabelecida, cliente e
servidor trocam strings no formato: "SSH-protoversion-softwareversion
comentários\r\n". Essa string é chamada V_S para a string enviada pelo
servidor e V_C para a do cliente. A partir de então, todos os dados são
transmitidos no formato do pacote citado no item anterior.
1-
Negociação dos algoritmos usados: Ambos os lados enviam o pacote SSH_MSG_KEXINIT
a seguir:
byte |
SSH_MSG_KEXINIT |
byte[16] |
cookie (bytes aleatórios) |
string |
Algoritmos de troca de
chaves |
string |
Algoritmos de chave
pública |
string |
Algoritmos de encriptação
cliente-servidor |
string |
Algoritmos de encriptação
servidor-cliente |
string |
Algoritmos MAC cliente-servidor |
string |
Algoritmos MAC servidor-cliente |
string |
Algoritmos compressão cliente-servidor |
string |
Algoritmos compressão servidor-cliente |
string |
Linguagens cliente-servidor |
string |
Linguagens servidor-cliente |
boolean |
first_kex_packet_follows |
uint32 |
0 (reservado) |
Os campos em destaque são
as strings com os nomes dos algoritmos em ordem de preferência. Após os dois
lados enviarem esse pacote, a camada de transporte decide qual o algoritmo que
será usado no protocolo em cada um dos campos. O algoritmo usado é o primeiro
que o cliente possuir que o servidor suporte.
2.
Protocolo
de troca de chaves. A saída dessa etapa é um segredo compartilhado K e o
resultado H de um hash. Esse hash é usado como identificador de secao e como
parte dos dados assinados pelo servidor para ele se autenticar perante o
cliente.
O protocolo de troca de chaves padrão
do SSH é o diffie-hellman-group1-sha1 explicado a seguir:
2.1 Cliente:
2.1.1
gera X randômico
2.1.2
envia
byte |
SSH_MSG_KEXDH_INIT |
mpint |
e = (g^x mod p) |
Onde g=2 e p o seguinte primo(120 bits):
FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1
29024E088A67CC74020BBEA63B139B22514A08798E3404DD
EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245
E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED
EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381
2.2. Servidor:
2.2.1
gera Y randômico
2.2.2
calcula:
·
f = g y mod p
·
K = e y mod p
·
H = SHA-1(V_C || V_S || I_C || I_S || K_S || e || f ||
K)
Onde V_C, V_S são as strings
de versão; I_C e I_S os conteúdos dos pacotes SSH_MSG_KEXINIT; e K_S a chave
pública do servidor.
2.2.3
gera S = assinatura sobre H usando a chave privada
2.2.4
envia:
byte |
SSH_MSG_KEXDH_REPLY |
string |
K_S (chave publica) |
mpint |
f |
string |
S – Assinatura sobre H |
2.3 Cliente:
2.3.1
Verifica se K_S é realmente a chave pública do
servidor, podendo ser feito uma busca em um histórico de conexões. Caso esse
passo não seja executado corretamente, o protocolo está sujeito ao ataque man-in-the-middle.
2.3.2
Calcula:
·
K = f k mod p
·
H = SHA-1(V_C || V_S || I_C || I_S || K_S || e || f ||
K)
2.3.2
Verifica S com K_S
3. Efetivação do uso de novas chaves com os algoritmos negociados.
Cada lado da conexão envia a
seguinte mensagem
byte
|
SSH_MSG_NEWKEYS |
A partir de então, todos os pacotes enviados são
encriptados de acordo com o algoritmo negociado
É recomendável que as
chaves de sessão sejam trocadas a cada 1Gb de dados transmitidos ou de hora em
hora. Para efetuar essa troca basta que um dos lados mande um pacote do tipo SSH_MSG_KEXINIT.
Isso for ç a a execução dos passos 1 ao 3 da
troca de chaves. O Identificador da sessão é o “H” da primeira troca de chave,
e ele mantêm o seu valor até o término da conecção.
Os vetores de
inicializacao e chaves de sessao para os algoritmos de encriptação e MACs são
criados da seguinte forma:
|
cliente - servidor |
servidor - cliente |
VI |
HASH(K
|| H || "A" || ID) |
HASH(K
|| H || "B" || ID) |
Chave
de Encriptação |
HASH(K
|| H || "C" || ID) |
HASH(K
|| H || "D" || ID) |
Chave
do MAC |
HASH(K
|| H || "E" || ID) |
HASH(K
|| H || "F" || ID) |
“HASH” aqui é o algoritmo utilizado
na troca de chaves negociada. No caso do diffie-hellman-group1-sha1 por
exemplo, HASH é o SHA1.
Após o protocolo
terminar a troca de chaves e receber o pacote SSH_MSG_NEWKEYS, o sistema pode tomar vários rumos, onde o
caso mais comum é a autenticação do usuário. Para isso, deve-se requisitar um serviço
através do seguinte pacote:
byte |
SSH_MSG_SERVICE_REQUEST |
string |
|
Se o servidor
reconhecer esse serviço, ele deve retornar o próximo pacote:
byte |
SSH_MSG_SERVICE_ACCEPT |
string |
|
Se o servidor recusar o pedido, o cliente deve
receber SSH_MSG_DISCONNECT e em seguida ser desconectado.
Obs.: Para o caso da autenticação, nome do serviço é “ssh-userauth”.
A seguir temos a tabela com os
algoritmos padronizados que foram definidos no protocolo de transporte do SSH.
Como citado anteriormente, outros algoritmos podem ser acrescentados nessa
lista. Basta utilizar nomes diferente dos citados a seguir, pois estes são
reservados para o protocolo.
Compressão |
none, zlib |
Encriptação |
3des-cbc,blowfish-cbc, twofish256-cbc,twofish-, twofish192-cbc,
twofish128-cbc, aes256-cbc, aes192-cbc, aes128-cbc, serpent256-cbc,
serpent192-cbc, serpent128-cbc, arcfour, idea-cbc, cast128-cbc, none |
MAC |
hmac-sha1, hmac-sha1-96, hmac-md5,
hmac-md5-96, none |
Troca de Chaves |
diffie-hellman-group1-sha1 |
Algoritmos de chave pública |
ssh-dss, ssh-rsa,
x509v3-sign-rsa, x509v3-sign-dss, spki-sign-rsa, spki-sign-dss, pgp-sign-rsa,
pgp-sign-dss |
Os
algoritmos em negrito são obrigatórios.
Aqui são executados os procedimentos
relativos a autenticação do usuário. É assumido que a camada de transporte já
está provendo integridade e sigilo na comunicação.
Para ocorrer a autenticação, o
cliente deve enviar a seguinte mensagem:
byte |
SSH_MSG_USERAUTH_REQUEST |
string |
user name |
string |
|
string |
|
… extra … |
O nome do serviço especifica qual o serviço que será iniciado apóso a
autenticaçao. Se o serviço não estiver disponível, o servidor irá desconectar o
cliente.
O método é o nome do procedimento usado para a autenticação.
O servidor pode responder de duas
formas:
Autenticação confirmada:
byte |
SSH_MSG_USERAUTH_SUCCESS |
Autenticação falhou:
byte |
SSH_MSG_USERAUTH_FAILURE |
string |
Métodos disponíveis |
boolean |
Sucesso parcial |
Onde sucesso parcial indica que o
método de autenticação aceitou o usuário, mas o servidor não aceitou essa
autenticação.
Um exemplo de método de autenticação
é o que utiliza senha. O pacote SSH_MSG_USERAUTH_REQUEST relative a ele é o seguinte:
byte |
SSH_MSG_USERAUTH_REQUEST |
string |
user name |
string |
|
string |
“password” |
boolean |
FALSE |
string |
senha |
Embora a senha é informada “às claras”, o pacote todo é criptografado (ao
contrário do telnet, por exemplo), impedindo assim o vazamento da senha se alguém
estiver ouvindo o tráfego.
O servidor também pode responder com
SSH_MSG_USERAUTH_PASSWD_CHANGEREQ,
informando que a senha
do usuário expirou e que ela deve ser trocada. Para isso, o cliente deve
enviadar o seguinte pacote:
byte |
SSH_MSG_USERAUTH_REQUEST |
string |
user name |
string |
|
string |
“password” |
boolean |
TRUE |
string |
Senha antiga |
string |
Senha nova |
Esta é aparte que provê execução de
commandos remotos, abertura de shells, tunelamento de conexões TCP/IP e de
conexões X11. Todos esses canais são multiplexados em um único tunel
encriptado.
O protocolo de comunicação tem o seu
funcionamento baseado em canais. Cada canal é identificado por um número em
cada uma das pontas. Esse número pode ser diferente em cada um dos lados.
Canais possuem fluxo controlado. Os
dados são transmitidos através deles até quando o espaço disponível em suas
janelas se esgotar. Quando isso ocorrer, o tamanho das janelas deve ser
incrementado com o envio de uma mensagem adequada.
Para abrir um canal na conexão, a próxima
mensagem deve ser transmitida por um dos lados.
byte |
SSH_MSG_CHANNEL_OPEN |
string |
Tipo do canal |
uint32 |
no do canal remetente |
uint32 |
Tamanho inicial da janela |
uint32 |
Tamanho máximo dos pacotes |
… extra … |
O outro lado deve responder o pedido
com um dos dois pacotes a seguir:
byte |
SSH_MSG_CHANNEL_OPEN_CONFIRMATION |
uint32 |
no do canal
destinatário |
uint32 |
no do canal
remetente |
uint32 |
Tamanho inicial da janela |
uint32 |
Tamanho máximo dos pacotes |
… extra … |
byte |
SSH_MSG_CHANNEL_OPEN_FAILURE |
uint32 |
no do canal
destinatário |
uint32 |
código do motivo |
string |
Informaçao adicional |
string |
Tag de lingugagem |
Observe que o no do canal destinatário dessas duas ultimas mensagens é o #do
canal remetente de quem requisitou a abertura do canal (SSH_MSG_CHANNEL_OPEN).
Para
transferir dados pelo canal, utiliza-se a seguinte mensagem:
byte |
SSH_MSG_CHANNEL_DATA |
uint32 |
no do canal
destinatário |
string |
Dados |
ou
byte |
SSH_MSG_CHANNEL_EXTENDED_DATA |
uint32 |
no do canal
destinatário |
uint32 |
Tipo dos dados |
string |
Dados |
O tamanho disponível da janela de
quem receber os dados deve ser decrementada pelo número de bytes do campo dados, e ao se esgotar a janela a
mensagem a seguir deve ser enviada pelo destinatário:
byte |
SSH_MSG_CHANNEL_WINDOW_ADJUST |
uint32 |
no do canal
destinatário |
uint32 |
no de bytes
incremental |
Com isso o a janela disponibiliza o
número de bytes solicitado.
A seguir, foi implementado um protótipo de um cliente que se comunica
com um servidor SSH. Este cliente possui os seguintes recursos:
·
Algoritmo
de encriptação: 3des-cbc, none
·
Algoritmos
MAC: hmac-md5,
hmac-sha1
·
Algoritmo
de troca de chaves: diffie-hellman-group1-sha1
·
Algoritmo
de chave pública: ssh-dss
·
Protocolo
de autenticação: password
·
Tipo
de serviço suportado: shell remoto
·
Visualização
de pacotes
·
Botão
de reexecutar troca de chaves
Interface
Principal
O 1º botão conecta ou disconecta em um servidor.
O 2º botão (reexecutar troca de chaves) funciona da seguinte forma: Ao
clicá-lo, novas chaves são estabelecidas. Como uma funcionalidade didática, os
algoritmos de encriptação (3des-cbc e none) são
alternados em cada nova troca, assim como os algoritmos de MAC (hmac-md5, hmac-sha1). Se o servidor não suportar o hmac-md5 ou o none, o
cliente não funcionará. Logo, não
use o botão de reexecutar troca de chaves se nao se conhecer os algoritmos
suportados pelo servidor.
A listagem que aparece no lado direito são os pacotes que entram e saem
do aplicativo. Ao dar um duplo clique é possível visualizar o conteúdo de cada
pacote.
Alguns exemplos de pacotes (decriptados):
pacote SSH_MSG_USERAUTH_REQUEST
para autenticação
pacote SSH_MSG_CHANNEL_DATA enviando o commando “date” para o shell
resultado de “date” enviado para o shell:”Tue Dec 16 23:52:26 BRST 2003”
Na barra de status são mostrados os algoritmos utilizados para encriptar
e para gerar os MAC’s. Nessa barra existe um botão (“KEX Status”) que mostra um
janela com os dados da sessão (H, K, Pub.Key do Server). O MD5 fingerprint da
chave pública é mostrada diretamente na barra de status.
barra de status com os dados da conexão
janela
informando K, H e a Chave Pública do servidor
Baixe aqui
o programa com fonte incluído: cliente_ssh.jar
IETF -
Internet-Drafts:
[SSH-ARCH]
Ylonen, T., "SSH Protocol
Architecture", I-D
draft-ietf-architecture-15.txt,
Oct 2003.
Ylonen, T., "SSH Transport
Layer Protocol", I-D
draft-ietf-transport-17.txt, Oct
2003.
Ylonen, T., "SSH
Authentication Protocol", I-D
draft-ietf-userauth-18.txt, Oct 2003.
Ylonen, T., "SSH Connection
Protocol", I-D
draft-ietf-connect-18.txt, Oct
2003.
Lehtinen, S. and D. Moffat,
"SSH Protocol Assigned
Numbers", I-D
draft-ietf-secsh-assignednumbers-05.txt, Oct
2003.
Outros Links:
http://bob.marlboro.edu/~msie/2004/it/nov7-ether-crypto/lecture.html
http://www.petitcolas.net/fabien/software/java%2Dssh/
http://3sp.com/download/download.php
http://www.lysator.liu.se/~nisse/lsh/