TCP SSL

Neste artigo será demonstrado como implementar esse IPC usando SSL para proteger as mensagens trafegadas.

A ideia aqui não é explicar como funciona a criptografia mas sim apresentar que é possível o seu uso.

Introdução

No artigo anterior sobre TCP foi demonstrado como ocorre o processo de comunicação desse IPC, porém conforme descrito no artigo é possível notar que os dados trafegam de forma legível(plain text), sendo possível realizar a leitura sem maiores problemas. Existem aplicações que a exposição desses dados resulta no comprometimento da aplicação, permitindo que “curiosos” bisbilhotem. Para garantir que os dados não serão capturados e usados de forma ilícita existe uma forma de proteger os dados através da criptografia. Neste artigo será demonstrado como implementar esse IPC usando SSL para proteger as mensagens trafegadas. Esse artigo foi baseado/traduzido de acordo com esse exemplo.

O que é SSL?

SSL significa Secure Sockets Layer que é um protocolo usado para estabelecer uma conexão criptografada entre um servidor e um cliente. Após estabelecer a conexão, o SSL garante que os dados transmitidos entre o cliente e o servidor estão seguros.

Funcionamento SSL

SSL usa algoritmo de criptografia assimétrica e simétrica para proteger a transmissão dos dados, este artigo explica de forma clara como funciona a transição da forma assimétrica para a forma simétrica. Estes algoritmos usam um par de chaves sendo uma pública e outra privada. A chave pública fica disponível e conhecida por qualquer um. A chave privada é conhecida somente por uma das partes neste caso o servidor. Com o SSL a mensagem criptografada pela chave pública pode ser descriptografada somente pela chave privada, para uma explicação mais elaborada clique aqui.

Handshake SSL

O processo de handshake ocorre em alguns passos antes do início da troca de mensagens entre as partes, para melhor saber como ocorre esse processo a IBM possui um artigo bem explicativo podendo ser acessado aqui.

Preparação do Ambiente

Antes de apresentarmos o exemplo, primeiro precisaremos instalar algumas ferramentas para auxiliar na análise da comunicação. As ferramentas necessárias para esse artigo são o tcpdump e o openssl, para instalá-las basta executar os comandos abaixo:

openssl

OpenSSL é uma ferramente de criptografia que implementa os protocolos SSL e TLS(Transport Layer Security). Com essa ferramenta é possível se conectar a servidores que utilizam SSL/TLS.

tcpdump

O tcpdump é uma ferramenta capaz de monitorar o tráfego de dados em uma dada interface como por exemplo eth0, com ele é possível analisar os pacotes que são recebido e enviados

Implementação

Para demonstrar o uso desse IPC, iremos utilizar o modelo Cliente/Servidor, onde o processo Cliente(button_process) vai enviar uma mensagem com comandos pré-determinados para o servidor, e o Servidor(led_process) vai ler as mensagens e verificar se possui o comando cadastrado, assim o executando.
Para melhor isolar as implementações do servidor e do cliente foi criado uma biblioteca, que abstrai a rotina de inicialização e execução do servidor, e a rotina de conexão por parte do cliente.

Biblioteca

A biblioteca criada permite uma fácil criação do servidor, sendo o servidor orientado a eventos, ou seja, fica aguardando as mensagens chegarem.

tcp_interface.h

Primeiramente criamos uma interface resposável por eventos de envio e recebimento, essa funções serão chamadas quando esses eventos ocorrerem.

tcp_server.h

Criamos também um contexto que armazena os parâmetros utilizados pelo servidor, sendo o socket para armazenar a instância criada, port que recebe o número que corresponde onde o serviço será disponibilizado, buffer que aponta para a memória alocada previamente pelo usuário, buffer_size que representa o tamanho do buffer, a interface das funções de callback, ssl_context que receberá a instância do contexto SSL, certificate que recebe o certificado e key que recebe a chave usada na criptografia.

Essa função é responsável pela a inicialização do servidor

Essa função aguarda uma conexão e realiza a comunicação com o cliente.

Essa função libera o contexto alocado pelo SSL

tcp_server.c

No TCP_Server_Init definimos algumas variáveis para auxiliar na inicialização do servidor, sendo uma variável booleana que representa o estado da inicialização do servidor, uma variável do tipo inteiro que recebe o resultado das funções necessárias para a configuração, uma variável do tipo inteiro para habilitar o reuso da porta caso o servidor precise reiniciar e uma estrutura sockaddr_in que é usada para configurar o servidor para se comunicar através da rede.

Para realizar a inicialização é criado um dummy do while, para que quando houver falha em qualquer uma das etapas, irá sair da função com status de erro, nesse ponto verificamos se o contexto e o buffer foi inicializado, que é de responsabilidade do usuário

Inicializamos a biblioteca do SSL e inicializamos o seu contexto

Carregamos o certificado

Criamos um endpoint com o perfil de se conectar via protocolo IPv4(AF_INET), do tipo stream que caracteriza o TCP(SOCK_STREAM), o último parâmetro pode ser 0 nesse caso.

Aqui permitimos o reuso do socket caso necessite reiniciar o serviço

Preenchemos a estrutura com parâmetros fornecidos pelo usuário como em qual porta que o serviço vai rodar.

Aplicamos as configurações ao socket criado

Por fim colocamos o socket para escutar novas conexões

Na função TCP_Server_Exec declaramos algumas variáveis para realizar a conexão e comunicação com o cliente

Quando a conexão é solicitada por parte do cliente, o accept retorna o socket referente a conexão, caso for feita com sucesso

Realizamos o handshake SSL caso ocorrer com sucesso inicia a troca de mensagens

O Servidor aguarda a troca de mensagem, assim que receber realiza a verificação se o callback para recebimento foi preenchido caso sim, passa o conteúdo para o callback realizar o tratamento.

Aqui é verificado se o callback para envio foi configurado, dessa forma o buffer é passado para que a implementação prepare a mensagem a ser enviada, e alteramos o status para true, indicando que a comunicação foi feita com sucesso.

Liberamos o ssl alocado para o cliente e interrompemos qualquer nova transação e fechamos o socket usado, concluindo a comunicação

Na função TCP_Server_Cleanup liberamos o contexto alocado no TCP_Server_Init

tcp_client.h

Criamos também um contexto que armazena os parâmetros utilizados pelo cliente, sendo o socket para armazenar a instância criada, hostname é o ip que da máquina que vai ser conectar, port que recebe o número que corresponde qual o serviço deseja consumir, buffer que aponta para a memória alocada previamente pelo usuário, buffer_size o representa o tamanho do buffer e a interface das funções de callback

Essa função inicializa a biblioteca SSL

Essa função realiza a conexão, envio e recebimento de mensagens para o servidor configurado

tcp_client.c

Na função TCP_Client_Init inicializamos a biblioteca SSL

Na função TCP_Client_Connect definimos algumas variáveis para auxiliar na comunicação com o servidor, sendo uma variável booleana que representa o estado da parametrização do cliente, uma variável do tipo inteiro que recebe o resultado das funções necessárias para a configuração, uma estrutura sockaddr_in que é usada para configurar o servidor no qual será conectado, e duas variáveis de quantidade de dados enviados e recebidos.

Verificamos se o contexto e o buffer do cliente foram inicializados

Inicializamos o contexto SSL

Criamos um endpoint com o perfil de se conectar via protocolo IPv4(AF_INET), do tipo stream que caracteriza o TCP(SOCK_STREAM), o último parâmetro pode ser 0 nesse caso.

Preenchemos a estrutura com o parâmetros pertinentes ao servidor

Convertemos o hostname para o endereço relativo ao servidor

Solicitamos a conexão com o servidor previamente configurado, caso ocorra tudo de forma correta alteramos o status para verdadeiro

Adquirimos uma instância do SSL, aplicamos ao socket e realizamos um connect ao servidor

Aqui verificamos se a inicialização ocorreu com sucesso e se o callback para envio foi preenchido

Em caso de sucesso passamos o contexto para a implementação feita pelo usuário para preparar os dados a ser enviado para o servidor

Se o callback para o recebimento foi preenchido passamos o contexto para a implementação do usuário tratar a resposta

Por fim liberamos a instância do SSL, interrompemos qualquer nova transação, fechamos o socket, e liberamos o contexto SSL e retornamos o status

A aplicação é composta por três executáveis sendo eles:

  • launch_processes – é responsável por lançar os processos button_process e led_process através da combinação fork e exec
  • button_interface – é responsável por ler o GPIO em modo de leitura da Raspberry Pi e se conectar ao servidor para enviar uma mensagem de alteração de estado.
  • led_interface – é responsável por escutar novas conexões, recebendo comandos para aplicar em um GPIO configurado como saída

launch_processes

No main criamos duas variáveis para armazenar o PID do button_process e do led_process, e mais duas variáveis para armazenar o resultado caso o exec venha a falhar.

Em seguida criamos um processo clone, se processo clone for igual a 0, criamos um array de strings com o nome do programa que será usado pelo exec, em caso o exec retorne, o estado do retorno é capturado e será impresso no stdout e aborta a aplicação. Se o exec for executado com sucesso o programa button_process será carregado.

O mesmo procedimento é repetido novamente, porém com a intenção de carregar o led_process.

button_interface

A implementação do Button_Run ficou simples, onde realizamos a inicialização da interface de botão, inicializamos o cliente e ficamos em loop aguardando o pressionamento do botão para alterar o estado da variável e enviar a mensagem para o servidor

led_interface

A implementação do LED_Run ficou simples também, onde realizamos a inicialização da interface de LED, do servidor e ficamos em loop aguardando o recebimento de uma conexão.

button_process

Definimos uma lista de comandos que iremos enviar

A parametrização do cliente fica por conta do processo de botão que inicializa o contexto com o buffer, seu tamanho, o endereço do hostname, o serviço que deseja consumir e os callbacks preenchidos, nesse exemplo usaremos somente o de envio, não estando interessado na recepção, e assim passamos os argumentos para Button_Run iniciar o processo.

A implementação no evento de envio, recuperamos o estado recebido e alteramos e indexamos com a lista de comando para enviar a mensagem

led_process

A parametrização do servidor fica por conta do processo de LED que inicializa o contexto com o buffer, seu tamanho, a porta onde vai servir, os callbacks preenchidos e o caminho do certificado nesse exemplo usaremos somente o de recebimento, e assim passamos os argumentos para LED_Run iniciar o serviço.

A implementação no evento de recebimento da mensagem, compara a mensagem recebida com os comandos internos para o acionamento do LED, caso for igual executa a ação correspondente.

Compilando, Executando e Matando os processos

Para compilar e testar o projeto é necessário instalar a biblioteca de hardware necessária para resolver as dependências de configuração de GPIO da Raspberry Pi.

Resolvendo as dependências

Para compilar o projeto é necessário utilizar a libssl-dev que provê a API pertinente para o uso do SSL, para o Ubuntu 18:04 para instalar execute:

Quanto foi testado no MINT foi usado a seguinte instalação

Gerando o certificado

Para que o exemplo funcione é necessário a criação do certificado, para criá-lo basta executar o seguinte comando e preencher os dados solicitados após o comando:

Obs: O certificado deve estar no mesmo diretório que os binários.

Compilando

Para facilitar a execução do exemplo, o exemplo proposto foi criado baseado em uma interface, onde é possível selecionar se usará o hardware da Raspberry Pi 3, ou se a interação com o exemplo vai ser através de input feito por FIFO e o output visualizado através de LOG.

Clonando o projeto

Pra obter uma cópia do projeto execute os comandos a seguir:

Selecionando o modo

Para selecionar o modo devemos passar para o cmake uma variável de ambiente chamada de ARCH, e pode-se passar os seguintes valores, PC ou RASPBERRY, para o caso de PC o exemplo terá sua interface preenchida com os sources presentes na pasta src/platform/pc, que permite a interação com o exemplo através de FIFO e LOG, caso seja RASPBERRY usará os GPIO’s descritos no artigo.

Modo PC

Modo RASPBERRY

Executando

Para executar a aplicação execute o processo launch_processes para lançar os processos button_process e led_process que foram determinados de acordo com o modo selecionado.

Uma vez executado podemos verificar se os processos estão rodando atráves do comando

O output

Interagindo com o exemplo

Dependendo do modo de compilação selecionado a interação com o exemplo acontece de forma diferente

MODO PC

Para o modo PC, precisamos abrir um terminal e monitorar os LOG’s

Dessa forma o terminal irá apresentar somente os LOG’s referente ao exemplo.

Para simular o botão, o processo em modo PC cria uma FIFO para permitir enviar comandos para a aplicação, dessa forma todas as vezes que for enviado o número 0 irá logar no terminal onde foi configurado para o monitoramento, segue o exemplo

Output do LOG quando enviado o comando algumas vezez

MODO RASPBERRY

Para o modo RASPBERRY a cada vez que o botão for pressionado irá alternar o estado do LED.

Monitorando o tráfego usando o tcpdump

Para monitorar as mensagens que trafegam, precisamos ler uma interface, para saber quais interfaces que o computador possui usamos o comando

Output

Como podemos ver temos 7 interfaces no computador onde o comando foi executado, pode ser que a máquina que esteja usando possa ter mais interfaces ou menos interfaces. Para teste local, iremos usar a interface local denominada lo, que representa a interface de loopback.

O tcpdump possui opções que permite a visualização dos dados, não irei explicar tudo, fica de estudo para quem quiser saber mais sobre a ferramenta. Executando o comando:

Após executar o comando o tcpdump ficará fazendo sniffing da interface, tudo o que for trafegado nessa interface será apresentado, dessa forma enviamos um comando e veremos a seguinte saída:

Podemos ver que há o processo de handshake SSL seguido do envio da mensagem, diferente do artigo TCP não é possível ver a mensagem, pois está criptografada.

Testando conexão com o servidor via openssl

A aplicação realiza a comunicação entre processos locais, para testar uma comunicação remota usaremos o openssl que permite se conectar de forma prática ao servidor que contenha certificado e enviar os comandos. Para se conectar basta usar o seguinte comando:

Como descrito no comando ip usaremos o ip apresentado na interface enp0s31f6 que é o IP 192.168.0.140, então o comando fica

Após a conexão é possível visualizar o envio do certificado

E enviamos o comando LED ON, note que é recebido um retorno, se visualizar no log irá apresentar que o comando foi executado, para monitorar com o tcpdump basta mudar a interface

Matando os processos

Para matar os processos criados execute o script kill_process.sh

Conclusão

Além de ser o melhor IPC por permitir conectar dois processos em máquinas distintas, ainda é capaz de oferecer mecanismos de segurança para a transmissão de mensagens de forma segura entre as máquinas envolvidas. Para aumentar a segurança ainda é possível aplicar criptografia sobre as mensagens. Para saber mais, aqui no embarcados existe um artigo Intel Edison – Princípios básicos de comunicação segura via Socket TCP usando OpenSSL e AES 256 em C escrito pelo Pedro Bertoleti onde ele explica como fazer.

Referência

Outros artigos da série

<< Socket TCPSocket UDP >>
Notificações
Notificar
guest
0 Comentários
Inline Feedbacks
View all comments

WEBINAR

Visão Computacional para a redução de erros em processos manuais

DATA: 23/09 ÀS 17:00 H