Você está na página 1de 5

UNIVERSIDADE DO MINHO

LICENCIATURA EM ENGENHARIA E GESTÃO DE SISTEMAS DE INFORMAÇÃO


PROJETO 2022/2023:
FUNDAMENTOS DE SISTEMAS DISTRIBUÍDOS
PL1 - Grupo 4

3ª ENTREGA
Relatório do Projeto

A97468 A95144 A95822

Guilherme José de Sousa Inês Capa de Barros Maria Beatriz Garcez Morais
Barbosa
1. Implementação:

PresencesServer

Esta é a classe principal do Servidor.


Esta classe contém uma lista static que vai guardar todas as instancias da class AtendePedidoTCP
criadas para nos permitir enviar e receber, pedidos e respostas, ao cliente. Uma outra lista de strings é
criada contendo as dez mensagens mais recentes.

Começamos por pedir a porta na qual o servidor deseja operar para que qualquer conexão seja
estabelecida. Pedimos também que seja indicado um valor para o parâmetro SESSION_TIMEOUT,
inicializamos as Presences passando como parâmetro o valor inserido e inicializamos o servidor com a porta
que ele previamente selecionou.
O servidor está sempre aberto a aceitar ligações vindas de clientes. Cada vez que temos uma nova
ligação de um cliente ao servidor, criamos e adicionamos uma instância do atendePedidoTCP (passamos
como parâmetro a ligação socket e o Servidor que lhe deu origem) à lista de utilizadores.
De modo que o servidor esteja atualizado e atualize os clientes em relação ao número de
conectados, vamos ter um temporizador que a cada segundo compara os utilizadores que estão ativos
com os utilizadores que estão presentes na lista static que contém todos os objetos do tipo
atendePedidoTCP.
Percorremos a lista static e por cada utilizador que estiver nessa lista, mas não estiver na dos
ativos, adicionamos à lista dos utilizadores inativos.
Por fim, para cada elemento da lista dos inativos enviamos um “SESSION_TIME_OUT”, fechamos
a ligação com o servidor e removemos da lista static de utilizadores.

AtendePedidoTCP

Esta classe gere a ligação de cada cliente com o servidor e é instanciada no PresencesServer cada
vez que uma nova ligação é efetuada.

Cada AtendePedidoTCP tem um nickname do respetivo cliente.

Quando o cliente envia um pedido, este está contido numa só string que contem o tipo de pedido e o
respetivo conteúdo separada por “:”, que é lida pelo atendePedidoTCP o qual os vai separar. Consoante o
cabeçalho da mensagem que pode ser "SESSION_UPDATE_REQUEST" ou "AGENT_POST" o servidor terá
um comportamento diferente:

• Se for do tipo "AGENT_POST", significa que foi uma mensagem que o cliente enviou e vai guardá-
la nas 10 mensagens mais recentes.
• Se for do tipo "SESSION_UPDATE_REQUEST": A string que recebemos devemos parti-la para
obtermos o nickname, o respetivo valor do RMI e a publicKey. Como a publicKey é recebida em
Base64, é necessário voltar a transformá-la no tipo PublicKey. Posteriormente adicionamos estes
valores à lista de utilizadores. Se o AtendePedidoTCP não tiver nickname, ou seja, se ainda não se
tiver conectado, então este passa a ser igual ao nickname enviado no pedido do cliente.

Em ambos os tipos de pedido, o servidor atualiza todos os clientes com o “SESSION_UPDATE” e é


reiniciado o cronometro para o utilizador que realizou o pedido.
No método sessionUpdate() começamos por ir buscar o número de utilizadores que estão presentes no
servidor. Enviamos esse número para o Cliente com o intuito de informar quantos nicknames, respetivo
IP, aceitação de RMI e a publicKey irá receber. Posteriormente enviamos cada nickname, respetivo IP,
aceitação de rmi ou não e a publicKey presentes na lista de utilizadores. De seguida, fazemos o mesmo
procedimento para enviar as mensagens mais recentes, ou seja, enviamos a quantidade e o conteúdo das
mensagens, sendo que o número de mensagens nunca pode ser maior que dez.
IPInfo

Esta classe serve para guardar a informação relativa ao cliente: IP, nickname, regista quando foi a
última vez que este efetuou um pedido ao servidor, regista também se este deseja comunicar
remotamente(RMI) e a publicKey. O método TimeOutPassed() consiste em verificar a diferença do tempo
atual e da última vez que o cliente foi visto. Se este tempo for maior do que o tempo estipulado para o
“TIME_OUT”, então o cliente será visto como inativo.

Presences

Criamos uma hashtable para incluir os dados dos clientes, sendo a chave o IP e para cada chave
tem associado um objeto da classe IPInfo. Estes dados contém a informação relativa aos IP’s, os
respetivos nicknames, a data mais recente na qual o cliente foi visto, ou seja, a última vez que efetuou
um pedido ao servidor, se este deseja comunicar remotamente(RMI) e a publicKey.

Posteriormente apresentamos um método que nos irá retornar os clientes presentes. Se se


tratar da primeira vez que um cliente se tenta conectar com o servidor, criamos um novo objeto IPInfo e
adicionamo-lo à HashTable presentIPs e definimos o tempo do último pedido como sendo o tempo de
pedido de conexão. Caso o cliente já se encontre conectado, então iremos somente atualizar o tempo em
que este foi visto pela última vez. Este método será executado cada vez que um cliente solicitar um
pedido do tipo "SESSION_UPDATE_REQUEST" ou "AGENT_POST" ao servidor.

No método getNickNameList() começamos por percorrer a lista de objetos IPInfo e para cada
elemento vamos verificar se dentro do tempo estipulado como tempo de “TIME_OUT” o cliente realizou
algum pedido, se não tiver realizado não é adicionado novamente à lista, caso contrário o nome dele é
adicionado novamente e o cronometro é reiniciado.

No método getNicknameIpRmiPublicKey () começamos por percorrer a lista de objetos IPInfo e


para cada elemento vamos verificar se dentro do tempo estipulado como tempo de “TIME_OUT” o
cliente realizou algum pedido, se não tiver realizado não é adicionado novamente à lista, caso contrário o
nickname, o IP, o RMI e a publicKey dele é adicionado novamente e o cronometro é reiniciado.

ConfiguracoesCliente

Esta interface será usada com o intuito de receber informações sobre como será estabelecida a
conexão e com quem. Para isso pedimos ao cliente que insira o endereço ao qual se pretende conectar, a
porta do respetivo endereço e, se este pretender comunicar remotamente(RMI), deve selecionar a
checkbox. No caso do cliente não inserir nem o endereço nem a porta aos quais se deseja conectar, será
utilizada a informação default. E se não selecionar a checkbox, o cliente não irá comunicar remotamente
com outros. De seguida inicializamos a interface cliente.

InterfaceCliente

Começamos por inicializar a interface do cliente. Decidimos o modelo para cada uma das tabelas
e criamos o painel que representa toda a nossa interface. É nesta interface que o cliente se pode conectar
com o servidor, para isso criamos um botão “Conectar” que estabelece a ligação com o servidor através do
método iniciar(). Este método cria o Socket, o canal de comunicação com o servidor, que serve para entrada
(BufferedReader) e saída (PrintWriter) de dados, cria também o registo RMI e gera o par de chaves(pública
e privada) de cada cliente. O cliente deve inserir um nickname válido, ou seja, que não seja nulo e,
posteriormente, iniciamos o método iniciarLoop() que nos irá permitirá realizar um
"SESSION_UPDATE_REQUEST” a cada 120 segundos. No "SESSION_UPDATE_REQUEST” enviamos para o
servidor o nickname e no caso deste pretender comunicar através de mensagens privadas, enviamos
também o RMI (true/false) e a respetiva publicKey em Base64.
No método criarRegisto() criamos o registo RMI na porta 1099 e criamos um objeto
remoto(passamos o modelo da tabela, o nickname e um HashMap que contem associados os nicknames
às respetivas publicKeys). Para esse registo, em localhost, precisa de criar um serviço com o nome
“PrivateMessaging” que depois é associado ao objeto remoto.

Ao clicar no botão “Enviar”, o método enviarMensagem(String mensagemEnviar) é chamado e


enviamos para o servidor um pedido do tipo “AGENT_POST” que contém a mensagem em questão.

Para recebermos as mensagens que o servidor envia, criamos uma thread que está sempre em
execução enquanto houver ligação entre o cliente e o servidor. Esta thread é responsável por ver se a
mensagem recebida é do tipo "SESSION_UPDATE”. Caso seja, prepara o cliente para este receber a lista
dos utilizadores presentes e as 10 mensagens mais recentes. A ordem pela qual é recebida a informação é
a mesma pela qual é enviada pelo servidor.

Já no método receberSessionUpDate() vamos guardar as informações dos utilizadores que estão


presentes no servidor e as últimas 10 mensagens. Inicialmente percorremos as presenças para que
possamos listar os clientes presentes na interface do cliente, adicionando assim a quantidade de linhas na
tabela correspondente ao número de utilizadores ativos. Realizamos o mesmo processo para as
mensagens: para cada elemento contido na lista vamos adicionar uma linha na tabela das mensagens com
o respetivo conteúdo (nickname: post). Neste método utilizamos um HashMap que vai associar um
nickname à respetiva publicKey. Para inserirmos um nickname, e uma respetiva publicKey, utilizamos o
método atualizarNicknamePublicKey(String nickname, String publickey). Como a publicKey chega do
Servidor como string(base 64), é necessário voltar a transformá-la em tipo PublicKey. Este método serve
para efetuar esta transformação e adicioná-la ao HashMap.

No método separarNicknameIpRmi (int linha, int coluna) vamos buscar a linha selecionada da
tabela das Presenças e separamos o nickname do Ip da informação relativa ao RMI(true/false). Fazemos
verificações não permitindo que o cliente envie mensagens para si próprio e obrigando-o a selecionar um
dos clientes que pretende comunicar remotamente.

No método enviarMensagemPrivada (String nicknameParaQuemVamosEnviar,String ip,String


mensagemEnviar) adicionamos à tabela das mensagens privadas uma nova linha com a seguinte
informação: “Enviou para:” + nicknameParaQuemVamosEnviar + ": " + mensagemEnviar. De seguida,
acedemos ao registo do IP ao qual vamos enviar a mensagem e fazemos um lookup com o SERVICE_NAME
(“PrivateMessaging”) e obtemos o objeto remoto. Através deste escrevemos a mensagem que
pretendemos enviar.

No método enviarMensagemPrivadaSegura(String nicknameParaQuemVamosEnviar, String ip,


String mensagemEnviar) começamos por criar um sumário(digest) da mensagem a enviar, encriptamos
com a privateKey do emissor e usamos a base64 para transformá-la numa String.

Adicionamos à tabela das mensagens privadas uma nova linha com a seguinte informação: Enviou
uma mensagem segura para:" + nicknameParaQuemVamosEnviar + ": " + mensagemEnviar. De seguida,
acedemos ao registo do IP ao qual vamos enviar a mensagem e fazemos um lookup com o SERVICE_NAME
(“PrivateMessaging”) e obtemos o objeto remoto. Através deste escrevemos a mensagem que
pretendemos enviar.

EnviarMensagem

Nesta classe vamos guardar o nickname, a DefaultTableModel e a listaNickPublicKey, para


posteriormente podermos escrever na tabela.

No método sendMessage (String name, String message) adicionamos uma linha à tabela das
mensagens privadas com o nome do cliente que a envia e o respetivo conteúdo. Retornamos o nickname
de quem recebe a mensagem como pedido no enunciado.
No método sendMessageSecure(String name, String message, String signature), através do
nickname do cliente, vamos buscar a respetiva publicKey contida na lista listaNickPublickey para podermos
decifrar o sumário recebido. Passamos o sumário para bytes e depois deciframos com a publicKey.

Posto isto, criamos o sumário da mensagem utilizando o mesmo algoritmo. Comparando os sumários é
possível concluir se existiram alterações no conteúdo da mensagem. No caso dos sumários serem iguais,
ou seja, da mensagem não ter sido alterada, esta é exibida.

PrivateMessaging

Esta interface serve para declarar o método sendMessage(String name, String message), podendo
ser executado pelo stub. E serve também para declarar o método sendMessageSecure(String name, String
message, String signature).

2. Respostas às Questões Específicas do Enunciado

Q1.

A solução a implementar numa situação em que se pretenda manter confidencial o conteúdo das
mensagens, é a criptografia com Chave Assimétrica. Esta baseia-se na ideia de usar uma das duas chaves
geradas para encriptação e outra para fazer a desencriptação. Conforme o pretendido pelas entidades, o
uso das chaves vai variar.

Uma entidade gera um par de chaves (pública e privada) e distribui a chave pública abertamente
para que todos os interessados em enviar uma mensagem privada, o possam fazer. Se uma entidade
desejar enviar uma mensagem confidencial, deve encriptá-la usando essa chave pública do respetivo
recetor.

A entidade que gerou o par de chaves, ao receber a mensagem encriptada, desencripta-a


utilizando a respetiva chave privada que só ela tem acesso. E se esta tiver sido mantida em segredo, então
a confidencialidade é garantida.

Q2.

A rede permite a comunicação e a troca de mensagens entre diversas entidades e esta apresenta
várias vantagens tais como a emissão ou receção de documentos, o acesso ou a disponibilização de
informações confidenciais, entre outros. No entanto, esta não é um meio totalmente seguro, uma vez que
pode ser usado para cometer atividades maliciosas por parte de pessoas, logo se for pretendido a troca
segura de informação é necessária a utilização dos certificados digitais.

Os certificados digitais atestam a associação entre uma chave pública e um indivíduo ou entidade,
garantindo a integridade, a autenticidade e a confidencialidade aquando da troca de mensagens, evitando
que uma entidade terceira possa interferir.

Uma vez que estes certificados são emitidos por uma Autoridade de Certificação de Confiança,
conseguimos garantir que a entidade a quem nos queremos dirigir é fidedigna e que as mensagens que
recebemos também o são.

Você também pode gostar