• Aucun résultat trouvé

List of Figures

Dans le document R apid M ineR (Page 44-54)

Existem três entidades diferentes nessa arquitetura: o usuário, o dispositivo e o serviço. Cada um deles usa uma forma de autenticação diferente, conforme o tipo de conexão feita.

Para realizar qualquer ação, tanto o usuário, quanto os dis- positivos e os serviços devem estar autenticados. Os dois primeiros autenticam-se através do envio de um token pelo cabeçalho da requisi- ção, que nesse caso é JWT (Json Web Token), que será explicado logo em seguida.

No caso do serviço, como ele realiza uma conexão persistente com

WebSocket, é feita apenas uma autenticação inicial, com credenciais

cadastradas no servidor.

Além disso, foi usado o protocolo HTTPS, para garantir a crip- tografia dos dados. Desse modo, os NDCs não podem ser lidos por qualquer outra entidade.

3.3.1 Json Web Token

É um padrão aberto, definido no RFC 7519 por Bradley, Saki- mura e Jones (2015), em que consiste uma forma compacta e segura para transmissão de informação entre duas partes em um objeto do tipo JSON. Pode ser verificada, pois é assinada digitalmente, ou usando al- gum segredo com um algoritmo HMAC (Hash-based Message Authen- tication Code) ou com um par de chaves assimétricas RSA.

Além de possuir um tamanho pequeno, permitindo sua trans- missão em requisições HTTP no header ou na URL (Uniform Resource Locator), o token possui informações do usuário. Isso evita a necessi- dade de certas consultas no banco de dados, após a autenticação.

Sua estrutura é uma cadeia de caracteres, dividida em três par- tes, separadas por um ponto (.):

• Header • Payload • Signature

O header consiste em um objeto com duas informações: o tipo de token, que no caso é JWT e o algoritmo usado, que pode ser um HMAC 256 ou RSA. Esse objeto é codificado em Base64Url, gerando a primeira parte do token. Neste trabalho, foi usado o HMAC 256.

O payload contém um pedaço de informação sobre algum as- sunto, que pode ser por exemplo, o identificador de um usuário. Ele nunca deve conter informação sensível em tokens que são somente as- sinados, pois pode ser lida por qualquer um. Da mesma forma, essa segunda parte também é codificada em Base64Url.

E por último, o signature é formado pela concatenação do header codificado em Base64Url, com o payload também codificado, mais o segredo que é definido pelo servidor.

O JWT pode ser usado tanto para autenticação, como para ga- rantir o não-repudio de mensagens. No segundo caso, basta trocar o algoritmo HMAC pelo RSA. Para este projeto, ele foi utilizado apenas para autenticação, pois a aplicação está hospedada em um domínio do LABSEC (Laboratório de Segurança em Computação). Além de garan- tir a transmissão criptografada dos dados, existe uma CA (Certificate Authority) que assegura a origem do servidor, que será explicado na seção a seguir.

3.3.2 HTTPS

Ao usar o protocolo HTTP padrão, para realizar uma comuni- cação do cliente com o servidor, toda informação encontra-se em plain

text. Qualquer entidade pode interceptar a comunicação, e ler o que

está sendo enviado entre as partes.

Dessa forma, o HTTPS fornece uma conexão criptograda entre o cliente e o servidor. Sua implementação usa o SSL ou o TLS para cifrar a comunicação. O princípio do protocolo é moldado em cima da criptografia de chave assimétrica.

Essa criptografia utiliza dois tipos de chave: uma privada, e uma pública. Ao cifrar a mensagem com uma das chaves, só é pos- sível decifrá-la com a outra. A chave privada permanece no servidor, devidamente protegida, enquanto que a chave pública, é acessível por qualquer pessoa, que queira enviar uma mensagem ao servidor.

No entanto, da maneira que foi apresentado, ainda é possível forjar um novo par de chaves, e induzir o usuário que ele está comuni- cando com o servidor correto. Esse tipo de ataque é conhecido como

man in the middle. Para isso, deve haver uma forma de identificar de

modo seguro, que uma chave pública está associada àquele servidor em questão.

Isso é feito por uma entidade denominada autoridade certifica- dora. Dentre várias funcionalidades, ela é responsável por emitir, distri- buir, renovar, revogar e gerenciar certificados digitais. Um certificado digital é um meio de identificar de modo seguro, que uma chave pública pertence ao servidor em questão.

Observando um exemplo simples, em que o navegador acessa um site, o servidor envia o certificado, e verifica se a autoridade que assinou o certificado, encontra-se em uma lista de autoridades certificadoras. Caso esteja, a comunicação pode ser estabelecida.

Com o intuito de realizar este trabalho, o LABSEC forneceu um servidor com o domínio labsec.ufsc.br, para a que a aplicação fosse hospedada. Junto disso, foi gerado um par de chaves RSA, de modo que a chave privada encontra-se no servidor com acesso restrito ao root. O uso de HTTPS mostra-se importante em dois aspectos. Sob o nível de privacidade, pois seria possível ler qualquer geração de NDCs dos dispositivos ao servidor, assim como ler os valores que os serviços recebem efetivamente. E outra questão, é reforçar a autenticação das partes. Como é utilizado um token nessa parte, seria possível, mesmo que por um período curto de tempo, um atacante obter o token e se passar pelo usuário, afim de obter acesso ao sistema. Criptografando a

4 IMPLEMENTAÇÃO

Nesta parte do trabalho será descrita detalhes de como a ar- quitetura proposta foi implementada. Primeiro será mostrado alguns

frameworks e tecnologias utilizadas, que facilitaram muito o processo

de desenvolvimento, e depois como o servidor foi construído.

Tanto no servidor central, como nos serviços que consomem esses valores, foi utilizada a linguagem JavaScript. Ela possui várias bibliote- cas que facilitam o desenvolvimento, e que recebem feedbacks constantes pela comunidade. Além disso, é pouco verbosa, gerando menos código e facilitando a manutenabilidade do mesmo para trabalhos futuros.

Já nos dispositivos foi escolhida a plataforma Android. Sua es- colha permitiu mostrar de forma clara, uma aplicação simples do mid- dleware. Ela também possui uma comunidade grande de desenvolve- dores, que ajudam a mantê-lo com qualidade.

4.0.1 Android

O Android1é um sistema operacional usado em diversos disposi-

tivos, desde celulares e tablets, até relógios e televisores. Teve um forte crescimento quando os smartphones tornaram-se bastante populares ao redor do mundo. Possui uma ótima documentação para construção dos mais diversos tipos de aplicativos.

A IDE Android Studio e sua SDK foram criadas para o desenvol- vimento rápido e eficiente das aplicações. Neste trabalho foi feita uma aplicação para usufruir da autenticação do middleware. Ela é respon- sável em controlar o bloqueio de tela, caso os níveis de confiabilidade não estejam de acordo, com um threshold pré-estabelecido.

4.0.2 Node.js

O Node2 é um runtime environment que permite rodar código

Javascript, como uma linguagem de propósito geral. Embora seja pos- sível construir uma aplicação local, criando scripts como em Ruby ou Python, seu uso foi popularizado na criação de servidores Web.

Sua arquitetura é baseada em eventos, com um modelo de I/O

1

https://developer.android.com/ 2

não bloqueante, permitindo um grande volume de requisições. O mo- delo tradicional aloca uma nova thread, para cada cliente conectado, consumindo mais recurso computacional. No caso do Node, assim que uma requisição é feita, a thread é liberada no momento que ocorrer um pedido de I/O. Isso permite que novas requisições sejam atendidas mais rápido. Quando o I/O acabar, um novo evento é gerado, contendo o resultado desejado e então a requisição retornar o dado.

No desenvolvimento deste projeto, o Node possui um ecossistema grande de bibliotecas, que são gerenciadas pelo NPM (Node Package Module). Através dele, foram baixados módulos prontos, que permiti- ram estabelecer um servidor REST (Representational State Transfer), conexão com um banco de dados e o uso de WebSocket. Junto disso, foi incluído um módulo de testes, para automatizar as verificações dos fluxos mais comuns do projeto.

4.0.3 Express

O Express3 é um framework desenvolvido para construir apli-

cações WEB com poucas linhas de código. O Node possui módulos internos para se comunicar com o sistema operacional, como leitura e escrita de arquivos, e protocolo HTTP para troca de dados. O Express simplesmente encapsula um conjunto de comportamentos desses módu- los, que permitem criar endpoints simples, com um conjunto de opções pré-definidas.

Através dele, será montada a API (Application Programming Interface) do servidor, para que algumas ações possam acontecer. Den- tre elas tem a autenticação do usuário, registro de um dispositivo para gerar NDCs novos, assim como registro de um serviço novo, para que ele possa receber os NDCs dos dispositivos que lhe interessam.

4.0.4 Socket.io

O Socket.io4 é uma implementação baseada na tecnologia de WebSocket, em que é estabelecida uma conexão bidirecional em um ca-

nal Full-duplex sobre um socket TCP. Esse modelo é apropriado para situações orientadas a eventos, em que o cliente deseja notificar o ser- vidor caso algo aconteça, ou vice-versa.

3

http://expressjs.com/ 4

Todos os serviços que desejam ser notificados sobre a mudança de valores em algum dispositivo, conectam-se ao servidor via WebSocket. No momento que estão autenticados, eles enviam um evento ao servidor, informando quais dispositivos deseja ouvir, ou por exemplo, quais não deseja mais escutar novas atualizações.

4.0.5 MongoDB

Devido a carência dos banco de dados relacionais, foram pro- postos novos modelos voltados a escalabilidade horizontal. Enquanto que nos primeiros as melhorias de software/hardware ocorrem no sis- tema, nesse novo a expansão ocorre por meio da adição de novas infra- estruturas. Esses modelos são conhecidos como NoSQL.

O MongoDB5é um banco de dados orientado a documento, que

pertence a essa classe de novos modelos. É organizado em um con- junto de coleções, que podem ser entendidas como tabelas do modelo relacional. Cada coleção é formada por diversos documentos, com cada documento possuindo sua própria estrutura de atributos e valores. A representação de um documento possui um formato parecido com a notação de um objeto JSON.

A integração do MongoDB com o Node.js, além da escabilidade, e mapeamento do modelo de documento para os objetos na aplicação foram fatores que ajudaram na escolha. Visto que à medida que novos usuários podem ser adicionados em trabalhos futuros, mais dados são gerados ao longo do tempo.

4.0.6 Mongoose

É uma biblioteca6 que simplifica a escrita de código de manipu-

lação do banco MongoDB. Em um cenário comum, para se comunicar com o banco, basta ter o drive instalado, para fazer a comunicação e assim realizar as operações nele. Isso tanto quanto num banco de dados relacional ou não-relacional.

Porém, isso pode gerar boilerplate code na hora de criar regras de validação e de negócio. O Mongoose vem como uma solução, para evitar isso (DRY principle) e deixar o código mais limpo e permitir localizar trechos específicos.

5

https://www.mongodb.com/ 6

4.0.7 Jest

Para poder realizar os testes unitários, foi utilizado o framework Jest7, o qual é mantido pelo Facebook. Junto com ele, vem diversas

funções prontas, que facilitam a escrita de asserções. Possui também suporte para testes assíncronos, que ocorrem em requisições na API REST ou quando forem acessar o banco de dados.

Com ela, foi possível escrever os mais variados casos de forma simples e descritiva. Isso permite uma futura refatoração do código, protegida contra a inserção de possíveis novos bugs.

4.0.8 Supertest

Como a maioria dos casos de teste necessitam a chamada de requisições para a API, o Supertest8permite que isso seja simulado via

código. Possui também uma boa integração o Jest na hora da validação. O uso dele também torna o código menos verboso e legível, na hora de verificar como cada teste foi implementado.

4.0.9 Jsonwebtoken

O Jsonwebtoken9 permite a criação de um JWT, seguindo a es-

pecificação detalhada no capítulo anterior. É possível escolher qual dos dois algoritmos utilizar e definir o tempo de expiração do token.

O usuário e o dispositivo possuem segredos diferentes na hora de gerar o token. Caso ocorra o vazamento de um, isso não compromete a segurança do outro.

4.0.10 Bcrypt

Com o intuito de armazenar as senhas das entidades de forma segura, o Bcrypt10possui funções de hash, inclusve de forma assíncrona.

É possível também fornecer um parâmetro, denominado salt. É um dado aleatório usado como entrada adicional junto à senha, para

7 https://facebook.github.io/jest/ 8 https://github.com/visionmedia/supertest 9 https://github.com/auth0/node-jsonwebtoken 10 https://github.com/dcodeIO/bcrypt.js

gerar o hash. Dessa forma, evita-se um ataque de dicionário ou um ataque pre-computado, como o rainbow table.

4.1 DESENVOLVIMENTO

4.1.1 Dispositivos

O principal, se não o único, objetivo do dispositivo, é gerar o NDC, de tempos em tempos, e enviá-lo ao servidor. Este fará o trata- mento adequado, independente do tipo de dispositivo que for.

No entanto, para que isso seja possível, o dispositivo precisa estar em conformidade com estes três pontos:

• O usuário já o cadastrou no sistema • Deve estar autenticado via JWT

• Para enviar os dados, deve chamar a requisição HTTP adequada Assumindo que o usuário esteja autenticado, ele pode cadastrar um novo dispositivo, através de uma requisição POST na url /device. O corpo da requisição deve conter os valores name e password. O valor

name referencia unicamente o dispositivo no servidor para o usuário

em questão.

Com o dispositivo cadastrado, basta ele se conectar fazendo uma requisição, conforme descrito no trecho de código:

1

2 // Aqui vem as c r e d e n c i a i s para r e a l i z a r o login

3 var data = { 4 name : name , 5 p a s s w o r d : p a s s w o r d 6 }; 7 8 $ . ajax ({

9 url : ’/ device / login ’,

10 type : ’post ’,

11 data : JSON . s t r i n g i f y ( data ) , 12 h e a d e r s : {

13 ’Content - Type ’: ’a p p l i c a t i o n / json ’

14 } ,

15 s u c c e s s : f u n c t i o n ( data ) { 16 alert ("S u c c e s s in login ! ") ;

17 } ,

18 error : f u n c t i o n ( data ) {

20 }

21 }) ;

Listing 4.1 – Login do dispositivo

Essa requisição, seguindo o padrão JWT, irá retornar um token, que deve ser usado nas requisições futuras. O próximo passo, para enviar um novo NDC, é fazer uma requisição conforme o outro trecho a seguir. 1 var data = { 2 value : value 3 }; 4 5 $ . ajax ({

6 url : ’/ device / send ’,

7 type : ’post ’,

8 data : JSON . s t r i n g i f y ( data ) , 9 h e a d e r s : {

10 ’Content - Type ’: ’a p p l i c a t i o n / json ’, 11 ’x - auth ’: token

12 } ,

13 s u c c e s s : f u n c t i o n ( data ) { 14 alert ("Data has been sent ! ") ;

15 } ,

16 error : f u n c t i o n ( data ) {

17 alert ("Error s e n d i n g data : " + data ) ;

18 }

19

20 }) ;

Listing 4.2 – Requisição para enviar NDC novo

Note que o token aparece ali no cabeçalho da requisição. E para enviar o valor apropriado, basta o dispositivo gerá-lo, de acordo com o método de autenticação.

Como forma de teste manual, foi criado uma página html está- tica, que pode ser acessada em /device.html. Ela contém dois campos para realizar o login e um outro para enviar um novo NDC referente ao dispositivo.

4.1.2 Serviços

O serviço funciona de forma semelhante ao dispositivo. Para que ele funcione, deve estar em conformidade novamente com alguns pontos:

• Deve estar autenticado via WebSocket

• Para enviar os dados, deve emitir um evento adequado

Diferente do dispositivo, toda comunicação ocorre apenas via

WebSocket. Assim é garantido que os serviços interessados em um dis-

positivo qualquer, obtenham o novo valor gerado em tempo real. Assume-se que o usuário tenha já cadastrado o serviço, através de uma requisição POST na URL /service, com o body possuindo os valores name e password. Assim como o dispositivo, name referencia o service unicamente para aquele usuário.

Nesse ponto, tem uma diferença importante entre os dois. Como no caso de WebSocket o canal de comunicação fica aberto, a autentica- ção só ocorre uma vez, conforme o código abaixo.

1 2 // D e f i n i r a URL do s e r v i d o r 3 _ s o c k e t = io . co n n e c t (’l o c a l h o s t :8080 ’) ; 4 _ s o c k e t . on (’c o n n e c t ’, f u n c t i o n() { 5 6 _ s o c k e t . emit (’a u t h e n t i c a t i o n ’, { u s e r n a m e : username , 7 p a s s w o r d : p a s s w o r d }) ; 8 9 _ s o c k e t . on (’a u t h e n t i c a t e d ’, f u n c t i o n() { 10 alert ("C o n n e c t e d ! ") ; 11 12 _ s o c k e t . on (’n e w V a l u e ’, ( data ) = > {

13 c o n s o l e . log ( ‘ Got $ { data } from device ! ‘) ;

14 }) ;

15

16 }) ;

17

18 _ s o c k e t . on (’u n a u t h o r i z e d ’, f u n c t i o n( err ) {

19 alert ("There was an error with the a u t h e n t i c a t i o n ! ") ;

20 }) ;

21

22 }) ;

Listing 4.3 – Login do serviço

Para estabelecer um WebSocket entre duas partes, é necessário realizar uma requisição HTTP, com as credenciais adequadas. O RFC não define nenhum tipo de header, em que as credenciais possam ser armazenadas.

Além disso, o uso de dados sensíveis em URLs deve ser evitado. Muitos endereços podem ser armazenados em logs do servidor com a senha em plain text. Dessa forma, uma solução encontrada foi a seguinte:

• Assim que o serviço conectar-se ao socket, deve emitir um evento ’authentication’ contendo as credenciais. Se não o fizer, ele será desconectado por timeout, cujo padrão definido é 1 segundo. • Se as credenciais estiverem corretas, um evento ’authenticated’

será emitido pelo servidor.

• Caso contrário, o evento unauthorized será emitido, contendo o erro em questão.

Todos os eventos que forem de interesse, devem ser escutados dentro do ’authenticated’, que é o caso do evento ’newValue’, onde o serviço receberá um dado contendo o último valor gerado de um dispositivo específico.

Com essa implementação, as credenciais ficam no corpo da requi- sição, todavia, o servidor usa TLS/HTTPS para criptografar os dados durante a transmissão, garantindo o sigilo deles.

4.1.3 Servidor Central

A partir desse ponto, entra a explicação mais detalhada da apli- cação, visto que o servidor é quem controla boa parte do tráfego de informações.

O primeiro ponto a ser descrito é sobre a transmissão dos dados. No arquivo de config.js, além de configurações básicas do servidor, aqui é inserido o protocolo HTTPS. No código é mostrado o carregamento da chave privada e do certificado do LABSEC, junto com a cadeia de certificados intermediários. 1 2 let server ; 3 if ( p r o c e s s . env . N O D E _ E N V === ’p r o d u c t i o n ’) { 4 const fs = r e q u i r e (’fs ’) ; 5 6 const o p t i o n s = {};

7 o p t i o n s . key = fs . r e a d F i l e S y n c (’private - key . pem ’) ; 8 o p t i o n s . cert = fs . r e a d F i l e S y n c (’LABSEC . crt ’) ; 9 o p t i o n s . ca = fs . r e a d F i l e S y n c (’cert - chain . crt ’) ; 10

11 server = r e q u i r e (’https ’) . c r e a t e S e r v e r ( options , app ) ;

12 } else {

13 server = r e q u i r e (’http ’) . c r e a t e S e r v e r ( app ) ; 14 }

Por questões práticas, o protocolo só é ativado, quando o servidor encontra-se em execução na máquina do LABSEC.

Outra ponto a considerar: só existe apenas um usuário. A senha foi salva no banco, através do REPL (Read–Eval–Print Loop) do Node, usando o método save() do Mongoose.

1

2 bcrypt . g e n S al t (10 , ( err , salt ) = > {

3 bcrypt . hash ( P A S S W O R D _ N O T _ H A S H E D , salt , ( err , hash ) = > { 4 user . p a s s w o r d = hash

5 }) ;

6 }) ;

Listing 4.5 – Método de hash

Uma senha padrão foi persistida, depois de aplicado um algo- ritmo de hash, com um salt de 10 caracteres, para prover uma segu- rança maior. Quando o usuário realiza login no servidor, a biblioteca

bcrypt verifica a senha, não esquecendo de utilizar o salt gerado.

Para isso, uma requisição HTTP deve ser feita no endpoint /u- ser/login, com o corpo da requisição contendo as credenciais.

1

2 app . post (’/ user / login ’, async ( req , res ) = > {

3 try {

4 const user = await User . f i n d B y C r e d e n t i a l s ( req . body . username , req . body . p a s s w o r d ) ;

5 const token = await user . g e n e r a t e A u t h T o k e n () ; 6 res . header (’x - auth ’, token ) . send () ;

7 } catch ( e ) {

8 res . status (400) . send ( e ) ;

9 }

10 }) ;

Listing 4.6 – Login do usuário

A função findByCredentials retorna uma promise contendo o JWT gerado, caso as credenciais estejam corretas. Ele então é re- tornado no cabeçalho da requisição de resposta. Se as credenciais es- tiverem incorretas, a requisição de resposta retorna um erro de bad

request.

1

2 U s e r S c h e m a . m e t h o d s . g e n e r a t e A u t h T o k e n = f u n c t i o n() {

3 var user = this;

4 var access = ’auth ’;

5 var token = jwt . sign ({ _id : user . _id . t o H e x S t r i n g () ,

6 access } ,

8 { e x p i r e s I n : ’12 h ’}) . t o S t r i n g () ; 9

10 user . tokens = user . tokens . concat ([{ access , token }]) ; 11

12 return user . save () . then (() = > {

13 return token ;

14 }) ;

15 };

Listing 4.7 – Geração do JWT

Para gerar o JWT, o payload informado é um objeto contendo o id do usuário e uma string contendo o tipo de token. O segredo é gerado no servidor a partir do momento em que é iniciado. No caso do

logout, o objeto token ‘auth’ do usuário é apagado no banco de dados,

usando uma requisição post no endpoint /user/logout.

É necessário para esta e outras requisições, que o usuário esteja autenticado, ou seja, com o token gerado em seu poder. O processo de verificação é feito conforme o código demonstrado. O dispositivo utiliza a mesma ideia que o usuário. No express, os métodos são middlewares, associados ao endpoint, de forma que sua execução sempre ocorre antes do código da requisição.

1 const a u t h e n t i c a t e U s e r = ( req , res , next ) = > { 2 const token = req . header (’x - auth ’) ;

3

4 User . f i n d B y T o k e n ( token ) . then (( user ) = > {

5 if (! user ) {

6 return P r o m i s e . reject ( ‘ No JWT found . ‘) ;

7 }

8

9 req . user = user ;

10 req . token = token ;

11 next () ;

12 }) .catch(( e ) = > {

13 res . status (401) . send ( e ) ;

14 }) ;

15 };

Listing 4.8 – Middleware de autenticação do usuário

Embora não fosse necessário um método para encontrar o token, sabendo que existe apenas um usuário, esta camada a mais permite que essa parte do código cresça. Não há necessidade em saber como é feita a implementação de busca do token, ficando mais fácil codificar para que novos usuários sejam inseridos ao servidor.

1 U s e r S c h e m a . s t a t i c s . f i n d B y T o k e n = f u n c t i o n ( token ) {

3 var d e c o d e d ; 4 5 try { 6 d e c o d e d = jwt . verify ( token , J W T _ S E C R E T _ U S E R ) ; 7 } catch ( e ) { 8 return P r o m i s e . reject ( ‘ I n v a l i d JWT . ‘) ; 9 } 10 11 return User . f i n d O n e ({ 12 ’_id ’: d e c o d e d . _id , 13 ’tokens . token ’: token , 14 ’tokens . access ’: ’auth ’

15 })

16 };

Listing 4.9 – Verificação do JWT

Dans le document R apid M ineR (Page 44-54)