Programação segura com a API do OpenSSL, Parte 1: Visão Geral da API

Crie conexões básicas seguras e inseguras

Aprender a usar a API do OpenSSL (a mais conhecida biblioteca aberta para comunicação segura) pode ser desafiador, pois a documentação está incompleta. Preencha as lacunas e domine a API com as dicas deste artigo. Após configurar uma conexão básica, veja como usar a biblioteca BIO do OpenSSL para configurar conexões seguras e inseguras. E saiba um pouco sobre detecção de erro também.

28 jun 2012 - Incluído link funcional para código de amostra no artigo em Download.

Kenneth Ballard, Software Engineer, MediNotes Corp.

Kenneth é engenheiro de software e trabalha para a MediNotes Corp. em West Des Moines, Iowa, EUA. Ele se formou no Peru State College em Peru, Nebraska, em administração. Também possui o grau de associado em programação de computadores pelo Southwestern Community College em Creston, Iowa. Kenneth criou vários aplicativos e bibliotecas de programação.



20/Jul/2012

A documentação da API do OpenSSL é um pouco vaga. Também não existem muitos tutoriais sobre o uso do OpenSSL, portanto, fazê-lo funcionar em aplicativos pode ser trabalhoso para iniciantes. Como se implementa uma conexão segura básica usando OpenSSL? Este guia ajudará você a solucionar esse problema.

Parte da dificuldade ao aprender como implementar OpenSSL é o fato de que a documentação não está completa. Quando uma API possui documentação incompleta, os desenvolvedores geralmente evitam usá-la, o que geralmente significa o fim da API. Mas OpenSSL ainda é bastante usado. Por quê?

OpenSSL é a mais conhecida biblioteca aberta para comunicação segura. Uma busca no Google por "SSL library" ("biblioteca de SSL") retorna OpenSSL no topo da lista. OpenSSL começou sua vida em 1998, derivado da biblioteca SSLeay, desenvolvida por Eric Young e Tim Hudson. Entre os outros kits de ferramentas SSL estão GNU TLS, distribuído sob a Licença Pública Geral GNU, e Mozilla Network Security Services (NSS) (consulte Recursos mais adiante neste artigo para mais informações).

Mas o que torna OpenSSL melhor que GNU TLS, Mozilla NSS ou qualquer outra biblioteca? Uma questão é o licenciamento (consulte Recursos). Além disso, GNS TLS (até agora) suporta apenas os protocolos TLS v1.0 e SSL v3.0 e não muito além.

Mozilla NSS é distribuído sob a Licença Pública Mozilla e a GNU GPL, permitindo que os desenvolvedores escolham. Mas Mozilla NSS é maior que OpenSSL e exige outras bibliotecas externas para ser desenvolvida, enquanto OpenSSL é totalmente autocontido. E, assim como OpenSSL, a maior parte da API do NSS não está documentada. Mozilla NSS suporta PKCS #11, que é usado para símbolos criptográficos, como Smart Cards. OpenSSL não suporta esse recurso.

Pré-requisitos

Para aproveitar ao máximo este artigo, você deve:

  • Ser proficiente em programação em C
  • Estar familiarizado com a comunicação pela Internet e criação de aplicativos que utilizem a Internet

Não é necessário estar familiarizado com SSL, pois uma breve explicação será dada mais adiante. No entanto, consulte a seção Recursos caso queira links para artigos que discutem SSL em maiores detalhes. Conhecimento de criptografia ajuda, mas não é obrigatório.

O que é SSL?

SSL é uma sigla que significa Secure Sockets Layer. É o padrão por trás da comunicação segura na Internet, integrando criptografia de dados ao protocolo. Os dados são criptografados antes mesmo de sair do computador de origem e são decriptografados apenas ao chegarem a seu destino. Certificados e algoritmos criptográficos estão por trás de tudo isso, e, com OpenSSL, é possível experimentar com ambos.

Em tese, caso os dados criptografados sejam interceptados ou observados antes de chegar ao destino, não há possibilidade de quebrar os dados. Mas, com os computadores ficando mais rápidos a cada ano e com os novos avanços na análise criptográfica, as chances de quebrar os protocolos de criptografia usados em SSL começam a aumentar.

SSL e conexões seguras podem ser usadas para qualquer tipo de protocolo na Internet, seja HTTP, POP3 ou FTP. SSL pode ser usado para proteger sessões de Telnet. Embora qualquer conexão possa ser protegida usando SSL, não é necessário usá-lo em todo tipo de conexão. Deve ser usado apenas quando a conexão carrega informações sensíveis.


O que é OpenSSL?

OpenSSL é mais que apenas SSL. Permite trechos de mensagem, criptografia e decriptografia de arquivos, certificados digitais, assinaturas digitais e números aleatórios. Há muito a se dizer sobre a biblioteca OpenSSL, muito mais do que pode ser colocado em um único artigo.

OpenSSL é mais que apenas a API. É também uma ferramenta de linha de comando. Essa ferramenta pode fazer o mesmo que a API, mas vai além, permitindo testar servidores e clientes SSL. Também dá aos desenvolvedores uma ideia da capacidade do OpenSSL. Para informações sobre como usar a ferramenta de linha de comando do OpenSSL, consulte a seção Recursos.


O que será preciso

Primeiro, é necessária a versão mais recente do OpenSSL. Consulte a seção Recursos para ver onde pode conseguir o código fonte mais recente para compilar, ou uma biblioteca binária da versão mais recente caso não queira perder tempo compilando. No entanto, por motivo de segurança, recomendo fazer download do código fonte mais recente e compilar você mesmo. As distribuições binárias são geralmente compiladas e distribuídas por terceiros e não pelos desenvolvedores do OpenSSL.

Algumas distribuições do Linux vêm com uma versão binária do OpenSSL, o que é útil para aprender como usar a biblioteca. Mas não deixe de obter a versão mais recente e mantê-la atualizada se você for realizar trabalho real.

Para distribuições do Linux que usam RPMs para instalação (Red Hat, Mandrake etc.), recomenda-se atualizar a distribuição do OpenSSL através de um pacote RPM disponibilizado pelo criador da distribuição. Por motivos de segurança, também é recomendado ter a versão mais recente da distribuição. Caso a versão mais recente do OpenSSL não esteja disponível para a distribuição, recomenda-se sobrescrever apenas as bibliotecas e não os executáveis. Os detalhes dessa operação estão no documento de FAQ incluído com o OpenSSL.

Deve-se observar também que OpenSSL não é suportado oficialmente em todas as plataformas. Embora tenham sido feitos esforços para torná-lo compatível em várias plataformas, é possível que o OpenSSL não funcione em um computador e/ou sistema operacional específico. O website do OpenSSL (link em Recursos) indica quais plataformas são suportadas.

Para usar OpenSSL para fazer solicitações de certificado e certificados digitais, é necessário criar um arquivo de configuração. Um arquivo de modelo chamado openssl.cnf está disponível na pasta apps do pacote OpenSSL. Não falarei sobre isso, pois o arquivo não é necessário para o escopo deste artigo. No entanto, o arquivo de modelo está bem anotado, e uma procura na Internet levará a vários tutoriais que discutem a modificação desse arquivo.


Cabeçalhos e inicialização

Apenas três cabeçalhos serão usados neste tutorial: ssl.h, bio.h e err.h. Todos estão no subdiretório openssl e serão necessários para o desenvolvimento do projeto. Também são necessárias apenas três linhas para inicializar a biblioteca do OpenSSL. Todas estão na Listagem 1. Outros cabeçalhos e/ou funções de inicialização podem ser necessários para outros recursos.

Listagem 1. Cabeçalhos necessários
/* OpenSSL headers */

#include "openssl/bio.h"
#include "openssl/ssl.h"
#include "openssl/err.h"

/* Initializing OpenSSL */

SSL_load_error_strings();
ERR_load_BIO_strings();
OpenSSL_add_all_algorithms();

Configurando uma conexão insegura

OpenSSL usa uma biblioteca de abstração chamada BIO para lidar com comunicação de vários tipos, incluindo arquivos e soquetes, seguros ou não. Também pode ser configurada como um filtro, como para codificação UU ou Base64.

A biblioteca BIO é um pouco complicada demais para explicar aqui, portanto, apresentarei partes dela quando for necessário. Primeiro, mostrarei como configurar uma conexão do soquete padrão. São necessárias menos linhas que ao usar a biblioteca de soquete BSD.

Antes de configurar uma conexão, segura ou não, é necessário criar um ponteiro para um objeto BIO. Isso é semelhante ao ponteiro FILE para um fluxo de arquivo em C padrão.

Listagem 2. Ponteiro
BIO * bio;

Abrindo uma conexão

Para criar uma conexão, é necessário chamar BIO_new_connect. É possível especificar o nome do host e porta na mesma chamada, como mostra a Listagem 3, que também tentará abrir a conexão. Também é possível separar em duas chamadas separadas: uma para BIO_new_connect para criar a conexão e configurar o nome do host, e outra para BIO_set_conn_port (ou BIO_set_conn_int_port) para configurar o número da porta.

De qualquer forma, quando o nome do host e o número da porta forem especificados à BIO, ela tentará abrir a conexão. Não há como evitar isso. Caso haja um problema na criação do objeto BIO, o ponteiro será NULL. Uma chamada para BIO_do_connect deve ser feita para verificar se a conexão teve êxito.

Listagem 3. Criando e abrindo uma conexão
bio = BIO_new_connect("hostname:port");
if(bio == NULL)
{
    /* Handle the failure */
}

if(BIO_do_connect(bio) <= 0)
{
    /* Handle failed connection */
}

Aqui, a primeira linha cria um objeto BIO com o nome do host e porta especificados, formatados da maneira mostrada. Por exemplo, para conectar-se à porta 80 de www.ibm.com, a sequência seria www.ibm.com:80. A chamada para BIO_do_connect verifica se a conexão teve êxito. Ela retorna 0 ou -1 em caso de erro.

Comunicando com o servidor

A leitura e gravação no objeto BIO, independente de ser um soquete ou arquivo, é sempre feita usando duas funções: BIO_read e BIO_write. Simples, não? E o melhor é que continua simples.

BIO_read tenta ler um certo número de bytes do servidor. Ela retorna o número de bytes lidos, ou 0 ou -1. Em uma conexão de bloqueio, o retorno 0 indica que a conexão foi fechada, enquanto -1 indica que ocorreu um erro. Em uma conexão sem bloqueio, o retorno 0 indica que dados não estavam disponíveis e -1 indica um erro. Para determinar se o erro é recuperável, chame BIO_should_retry.

Listagem 4. Lendo da conexão
int x = BIO_read(bio, buf, len);
if(x == 0)
{
    /* Handle closed connection */
}
else if(x < 0)
{
   if(! BIO_should_retry(bio))
    {
        /* Handle failed read here */
    }

    /* Do something to handle the retry */
}

BIO_write tenta gravar bytes no soquete. Ela retorna o número de bytes efetivamente gravados, ou 0 ou -1. Assim como BIO_read, 0 ou -1 não indica necessariamente um erro. BIO_should_retry é a maneira de descobrir. Para tentar novamente a operação de gravação, é necessário usar os mesmos parâmetros que antes.

Listagem 5. Gravando na conexão
if(BIO_write(bio, buf, len) <= 0)
{
    if(! BIO_should_retry(bio))
    {
        /* Handle failed write here */
    }

    /* Do something to handle the retry */
}

Fechando a conexão

Fechar a conexão também é simples. É possível fechá-la de duas formas: BIO_reset ou BIO_free_all. Caso você precise reutilizar o objeto, use a primeira opção. Caso contrário, use a segunda.

BIO_reset fecha a conexão e reconfigura o estado interno do objeto BIO para que a conexão possa ser reutilizada. Isso é bom para quando você usa o mesmo objeto ao longo do aplicativo, como em um cliente de bate-papo seguro. Ela não retorna valor.

BIO_free_all faz o que seu nome indica: libera a estrutura interna e toda a memória associada e também fecha o soquete associado. Quando a BIO está integrada em uma classe, a função é usada no seu destruidor.

Listagem 6. Fechando a conexão
/* To reuse the connection, use this line */

BIO_reset(bio);

/* To free it from memory, use this line */

BIO_free_all(bio);

Configurando uma conexão segura

Agora é o momento de ver o que é necessário para configurar uma conexão segura. A única parte que muda é configurar e criar a conexão. O resto continua igual.

Conexões seguras exigem um handshake após serem estabelecidas. Durante o handshake, o servidor envia um certificado ao cliente, que este verifica consultando um conjunto de certificados de confiança. Também verifica se o certificado não expirou. Para verificar se o certificado é confiável, é necessário que um armazenamento de certificados seja carregado antes do estabelecimento da conexão.

O cliente envia um certificado ao servidor apenas quando o servidor solicita. Isso é chamado de autenticação de cliente. Usando o(s) certificado(s), parâmetros de cifra são passados entre o cliente e o servidor para configurar a conexão segura. Embora o handshake seja realizado após a conexão ser estabelecida, o cliente ou servidor pode solicitar um novo handshake a qualquer momento.

Handshakes e outros aspectos da configuração de uma conexão segura são discutidos detalhadamente nos artigos do Netscape e RFC 2246, listados na seção Recursos.

Configurando uma conexão segura

A configuração de uma conexão segura exige mais algumas linhas de código. É necessário outro ponteiro do tipo SSL_CTX. Essa é uma estrutura para conter as informações SSL. Também é usada para configurar a conexão SSL através da biblioteca BIO. Para criar essa estrutura, chama-se SSL_CTX_new com uma função de método SSL, geralmente SSLv23_client_method.

Também é necessário outro ponteiro do tipo SSL para conter a estrutura de conexão SSL (isso é necessário para algo que será feito em breve). Esse ponteiro SSL pode ser usado posteriormente para examinar as informações de conexão ou configurar parâmetros SSL adicionais.

Listagem 7. Configurando os ponteiros SSL
SSL_CTX * ctx = SSL_CTX_new(SSLv23_client_method());
SSL * ssl;

Carregando o armazenamento de certificados de confiança

Após a estrutura de contexto ser criada, é necessário carregar um armazenamento de certificados de confiança. Isso é absolutamente necessário para que a verificação do certificado do peer tenha êxito. Caso a confiabilidade do certificado não possa ser verificada, OpenSSL sinaliza o certificado como inválido (mas a conexão pode continuar).

OpenSSL vem com um conjunto de certificados de confiança. Eles se encontram no diretório certs da árvore de origem. Cada certificado é um arquivo separado, o que significa que cada um deve ser carregado separadamente. Também há uma subpasta em certs com certificados expirados. Tentar carregá-los causará erros.

É possível carregar cada arquivo individualmente caso você queira, mas, para maior simplicidade, os certificados de confiança da distribuição mais recente do OpenSSL estão incluídos no archive do código fonte em um único arquivo, chamado "TrustStore.pem". Caso você já tenha um arquivo de armazenamento de confiança que será usado para o projeto, basta substituir "TrustStore.pem" na Listagem 8 com o arquivo em questão (ou carregar ambos com chamadas de função separadas).

Chame SSL_CTX_load_verify_locations para carregar o arquivo de armazenamento de confiança. Essa função recebe três parâmetros: o ponteiro de contexto, o caminho e o nome do arquivo de armazenamento de confiança e um caminho para o diretório dos certificados. É necessário especificar o arquivo de armazenamento de confiança ou o diretório dos certificados. Ela retorna 1 em caso de êxito ou 0 em caso de problema.

Listagem 8. Carregando um armazenamento de confiança
if(! SSL_CTX_load_verify_locations(ctx, "/path/to/TrustStore.pem", NULL))
{
    /* Handle failed load here */
}

Caso você use um diretório para armazenar o armazenamento de confiança, os arquivos devem ter certos nomes específicos. A documentação do OpenSSL detalha esses nomes, mas há uma ferramenta incluída com o OpenSSL, chamada c_rehash, que prepara uma pasta para uso como parâmetro do caminho para SSL_CTX_load_verify_locations.

Listagem 9. Preparando e usando uma pasta de certificado
/* Use this at the command line */

c_rehash /path/to/certfolder

/* Then call this from within the application */

if(! SSL_CTX_load_verify_locations(ctx, NULL, "/path/to/certfolder"))
{
    /* Handle error here */
}

É possível nomear quantos arquivos ou pastas separados forem necessários para especificar todos os certificados de verificação necessários. Também é possível especificar um arquivo e uma pasta ao mesmo tempo.

Criando a conexão

O objeto BIO é criado usando BIO_new_ssl_connect, que toma o ponteiro para o contexto SSL como único parâmetro. Também é necessário recuperar o ponteiro para a estrutura SSL. Neste artigo, esse ponteiro é usado apenas com a função SSL_set_mode. Essa função é usada para configurar o sinalizador SSL_MODE_AUTO_RETRY. Com essa opção configurada, caso o servidor repentinamente queira um novo handshake, OpenSSL cuida disso em segundo plano. Sem essa opção, cada operação de leitura ou gravação retorna um erro caso o servidor queira um novo handshake, configurando ao mesmo tempo o sinalizador de nova tentativa.

Listagem 10. Configurando o objeto BIO
bio = BIO_new_ssl_connect(ctx);
BIO_get_ssl(bio, & ssl);
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);

Com a estrutura de contexto SSL configurada, é possível criar a conexão. O nome do host é configurado com a função BIO_set_conn_hostname. O nome do host e a porta são especificados no mesmo formato acima. Essa função também abre a conexão para o host. Uma chamada para BIO_do_connect ainda precisa ser feita para verificar se a conexão foi aberta. Essa mesma chamada também realiza o handshake para configurar a comunicação segura.

Listagem 11. Abrindo uma conexão segura
/* Attempt to connect */

BIO_set_conn_hostname(bio, "hostname:port");

/* Verify the connection opened and perform the handshake */

if(BIO_do_connect(bio) <= 0)
{
    /* Handle failed connection */
}

Após a conexão ser estabelecida, é necessário verificar se o certificado é válido. Na verdade, OpenSSL faz isso para nós. Caso haja problemas fatais com o certificado (por exemplo, se os valores do hash não forem válidos) a conexão simplesmente não acontece. Mas caso haja problemas não fatais com o certificado (por exemplo, ele expirou ou não está válido ainda), a conexão ainda pode ser usada.

Para descobrir se a verificação do certificado foi aprovada pelo OpenSSL, chame SSL_get_verify_result com a estrutura de SSL como único parâmetro. Caso o certificado seja aprovado nas verificações internas do OpenSSL, incluindo a verificação de confiança, ela retorna X509_V_OK. Caso algo dê errado, ela retorna um código de erro que está documentado na opção verify da ferramenta de linha de comando.

Deve-se notar que uma falha na verificação não significa que a conexão não possa ser usada. A decisão de usar ou não a conexão depende do resultado da verificação e de considerações de segurança. Por exemplo, uma falha na verificação de confiança pode significar simplesmente que o certificado de confiança não está disponível. A conexão ainda pode ser usada, mas com maior segurança em mente.

Listagem 12. Verificando se um certificado é válido
if(SSL_get_verify_result(ssl) != X509_V_OK)
{
    /* Handle the failed verification */
}

E isso é todo o necessário. Qualquer comunicação com o servidor ocorre normalmente usando BIO_read e BIO_write. Para fechar a conexão, basta chamar BIO_free_all ou BIO_reset, dependendo da decisão de reutilizar a BIO.

Em algum momento antes do fim do aplicativo, é necessário liberar a estrutura de contexto SSL. Chame SSL_CTX_free para liberar a estrutura.

Listagem 13. Limpando o contexto SSL
SSL_CTX_free(ctx);

Detecção de erros

Então o OpenSSL exibiu um erro de algum tipo. O que isso significa? Primeiro, é necessário obter o código de erro; ERR_get_error faz isso. Em seguida, é necessário transformar esse código em uma sequência de erro, que é um ponteiro para uma sequência carregada permanentemente na memória por SSL_load_error_strings ou ERR_load_BIO_strings. Isso pode ser feito em uma chamada aninhada.

A Tabela 1 mostra as maneiras de recuperar um erro da pilha de erro. A Listagem 14 mostra como imprimir a última mensagem de erro em uma sequência de texto.

Tabela 1. Recuperando erros da pilha
ERR_reason_error_string Retorna um ponteiro para uma sequência estática, que pode depois ser exibida na tela, gravada em um arquivo ou o que você quiser fazer com ela.
ERR_lib_error_string Indica em qual biblioteca ocorreu o erro.
ERR_func_error_string Retorna a função do OpenSSL que causou o erro.
Listagem 14. Imprimindo o último erro
printf("Error: %s\n", ERR_reason_error_string(ERR_get_error()));

Também é possível fazer com que a biblioteca forneça uma sequência de erro pré-formatada. Chame ERR_error_string para isso. Essa função toma o código de erro e um buffer pré-alocado como parâmetros. O buffer deve ter 256 bytes. Caso esse parâmetro seja NULL, OpenSSL grava a sequência em um buffer estático com 256 bytes e retorna um ponteiro para esse buffer. Caso contrário, retorna o ponteiro fornecido. Caso você escolha a opção do buffer estático, esse buffer será sobrescrito com a próxima chamada para ERR_error_string.

Listagem 15. Recuperando uma sequência de erro pré-formatada
printf("%s\n", ERR_error_string(ERR_get_error(), NULL));

Também é possível passar toda a fila de erros para um arquivo ou a BIO. Isso é feito através de ERR_print_errors ou ERR_print_errors_fp. A fila é transferida em um formato legível. A primeira envia a fila para uma BIO, enquanto a segunda envia para FILE. A sequência é formatada desta maneira (retirado da documentação do OpenSSL):

[pid]:error:[error code]:[library name]:[function name]:[reason string]:[file name]:[line]:[optional text message]

em que [pid] é o ID do processo, [error code] é um código hexadecimal de 8 dígitos, [file name] é o arquivo de código fonte na biblioteca do OpenSSL e [line] é o número da linha nesse arquivo de origem.

Listagem 16. Transferindo a fila de erros
ERR_print_errors_fp(FILE *);
ERR_print_errors(BIO *);

Introdução

Criar uma conexão básica com OpenSSL não é difícil, mas a documentação pode ser um pouco desafiadora quando se tenta descobrir o que fazer. Este artigo apresentou os aspectos básicos, mas ainda há muita flexibilidade a ser descoberta no OpenSSL, além de configurações avançadas que podem ser necessárias para implementar adequadamente a funcionalidade de SSL em projetos.

Há duas amostras incluídas com este artigo. Uma mostra uma conexão insegura para http://www.verisign.com/, enquanto a outra mostra uma conexão SSL segura para https://www.verisign.com/. Ambas conectam-se ao servidor e fazem download da página inicial. Não há verificações de segurança e são usadas todas as configurações padrão na biblioteca. Deve ser usado apenas para fins educacionais como parte deste artigo.

Deve ser possível compilar o código fonte em qualquer sistema suportado, mas recomenda-se que você tenha a versão mais recente do OpenSSL. No momento em que este artigo é escrito, a versão mais recente é 0.9.7d.


Download

DescriçãoNomeTamanho
Source code for this articleintro-openssl.zip9KB

Recursos

Aprender

Obter produtos e tecnologias

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Linux, Software livre
ArticleID=826270
ArticleTitle=Programação segura com a API do OpenSSL, Parte 1: Visão Geral da API
publish-date=07202012