265 lines
9.1 KiB
C#
265 lines
9.1 KiB
C#
/**********722871**********
|
|
* Date: 10/4/2024
|
|
* Programmer: Kyle Gilbert
|
|
* Program Name: CentralizedChatRoom
|
|
* Program Description: A chat room system I wrote with a custom packet system.
|
|
* Connections are partially encrypted.
|
|
**************************/
|
|
|
|
using ChatRoom.Centralized.Shared;
|
|
using ChatRoom.Centralized.Shared.ObjectModels;
|
|
using ChatRoom.Centralized.Shared.Packets;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
|
|
namespace ChatRoom.Centralized.Client;
|
|
|
|
public static class ProgramClient
|
|
{
|
|
private static readonly TcpClient client = new();
|
|
|
|
public static void Main()
|
|
{
|
|
Console.Write("Enter the IP address to connect to > ");
|
|
IPAddress ip = IPAddress.Parse(Console.ReadLine()!);
|
|
|
|
Console.Write("Testing connection...");
|
|
client.Connect(new(ip, GlobalServerInfo.Port));
|
|
NetworkStream stream = client.GetStream();
|
|
PacketWriter writer = new(stream);
|
|
PacketReader reader = new(stream);
|
|
|
|
INetworkPacket packet = new ClientHelloPacket()
|
|
{
|
|
KeepOpen = true,
|
|
TimeSent = DateTimeOffset.UtcNow
|
|
};
|
|
writer.Write(packet, null);
|
|
|
|
packet = reader.ReadPacket(null);
|
|
if (packet is not ServerHelloResponsePacket pHello) throw new("Didn't get back hello response, got something else!");
|
|
|
|
Console.WriteLine($" Latency: {pHello.Latency} ms");
|
|
Console.WriteLine($"""
|
|
|
|
Server Name: {pHello.Information.Name}
|
|
Description: {pHello.Information.Description}
|
|
Sign-up Allowed: {(pHello.Information.AllowsSignup ? "Yes" : "No")}
|
|
|
|
""");
|
|
|
|
RSA clientRsa = RSA.Create();
|
|
packet = new ClientRequestServerKeyPacket()
|
|
{
|
|
KeepOpen = true,
|
|
ClientPublicKey = clientRsa.ExportRSAPublicKey()
|
|
};
|
|
writer.Write(packet, null);
|
|
|
|
packet = reader.ReadPacket(null);
|
|
if (packet is not ServerKeyResponsePacket pServerKey) throw new("Didn't get back the server's AES key, got something else!");
|
|
|
|
Aes serverAes = Aes.Create();
|
|
|
|
serverAes.BlockSize = BitConverter.ToInt32(clientRsa.Decrypt(pServerKey.BlockSize, RSAEncryptionPadding.Pkcs1));
|
|
serverAes.FeedbackSize = BitConverter.ToInt32(clientRsa.Decrypt(pServerKey.FeedbackSize, RSAEncryptionPadding.Pkcs1));
|
|
serverAes.IV = clientRsa.Decrypt(pServerKey.IV, RSAEncryptionPadding.Pkcs1);
|
|
serverAes.KeySize = BitConverter.ToInt32(clientRsa.Decrypt(pServerKey.KeySize, RSAEncryptionPadding.Pkcs1));
|
|
serverAes.Key = clientRsa.Decrypt(pServerKey.Key, RSAEncryptionPadding.Pkcs1);
|
|
serverAes.Mode = (CipherMode)BitConverter.ToInt32(clientRsa.Decrypt(pServerKey.Mode, RSAEncryptionPadding.Pkcs1));
|
|
serverAes.Padding = (PaddingMode)BitConverter.ToInt32(clientRsa.Decrypt(pServerKey.Padding, RSAEncryptionPadding.Pkcs1));
|
|
|
|
Console.Write("(C)reate an account or (L)og in? > ");
|
|
_tryChoice:
|
|
string choice = Console.ReadLine()!;
|
|
_tryLogin:
|
|
|
|
string username, password;
|
|
byte[] passwordHash;
|
|
switch (choice.Trim().ToLower())
|
|
{
|
|
case "c":
|
|
if (!pHello.Information.AllowsSignup)
|
|
{
|
|
Console.WriteLine("Cannot create an account on this server.");
|
|
goto case "l";
|
|
}
|
|
Console.Write("Enter a username > ");
|
|
username = Console.ReadLine()!;
|
|
|
|
Console.Write("Enter a password > ");
|
|
password = Console.ReadLine()!;
|
|
passwordHash = SHA256.HashData(Encoding.UTF8.GetBytes(password));
|
|
|
|
packet = new ClientCreateAccountPacket()
|
|
{
|
|
Username = username,
|
|
PasswordHashed = passwordHash
|
|
};
|
|
break;
|
|
case "l":
|
|
Console.Write("Enter your username > ");
|
|
username = Console.ReadLine()!;
|
|
|
|
Console.Write("Enter your password > ");
|
|
password = Console.ReadLine()!;
|
|
passwordHash = SHA256.HashData(Encoding.UTF8.GetBytes(password));
|
|
|
|
packet = new ClientLoginPacket()
|
|
{
|
|
Username = username,
|
|
PasswordHashed = passwordHash
|
|
};
|
|
break;
|
|
|
|
default: goto _tryChoice;
|
|
}
|
|
|
|
Console.Write("Logging in... ");
|
|
writer.Write(packet, serverAes);
|
|
|
|
packet = reader.ReadPacket(serverAes);
|
|
if (packet is ServerErrorResponsePacket pLoginError)
|
|
{
|
|
Console.WriteLine($"Error: {pLoginError.Message}");
|
|
goto _tryLogin;
|
|
}
|
|
Console.WriteLine("Ready!");
|
|
|
|
Thread.Sleep(500);
|
|
|
|
Console.Clear();
|
|
packet = new ClientRequestChatLogPacket()
|
|
{
|
|
Amount = 30,
|
|
Latest = DateTimeOffset.Now,
|
|
};
|
|
writer.Write(packet, serverAes);
|
|
|
|
packet = reader.ReadPacket(serverAes);
|
|
if (packet is not ServerChatLogPacket pChatLog) throw new("Got a packet other than ServerChatLogPacket!");
|
|
|
|
MessageInfo[] messages = pChatLog.Messages;
|
|
foreach (MessageInfo message in messages)
|
|
{
|
|
if (!cachedUsers.TryGetValue(message.UserId, out PublicUserInfo? user))
|
|
{
|
|
packet = new ClientRequestUserInfoPacket()
|
|
{
|
|
UserId = message.UserId,
|
|
Username = null
|
|
};
|
|
writer.Write(packet, serverAes);
|
|
packet = reader.ReadPacket(serverAes);
|
|
|
|
if (packet is ServerUserInformationPacket pUserInfo)
|
|
{
|
|
user = pUserInfo.UserInfo;
|
|
cachedUsers.Add(message.UserId, user);
|
|
}
|
|
else user = new() { Username = "Unknown User" };
|
|
}
|
|
|
|
Console.WriteLine($"""
|
|
{message.SentAt,19:g} {user.Username}
|
|
{message.Contents}
|
|
|
|
""");
|
|
}
|
|
|
|
Task.Run(() => MonitorThread(writer, reader, serverAes, Console.CursorTop));
|
|
|
|
StringBuilder toSend = new();
|
|
ConsoleKeyInfo key;
|
|
while (true)
|
|
{
|
|
// Get key inputs.
|
|
key = Console.ReadKey(true);
|
|
|
|
if (!char.IsControl(key.KeyChar))
|
|
{
|
|
toSend.Append(key.KeyChar);
|
|
lock (Console.Out)
|
|
{
|
|
Console.Write(key.KeyChar);
|
|
}
|
|
}
|
|
else if (key.Key == ConsoleKey.Backspace && toSend.Length > 0)
|
|
{
|
|
toSend.Remove(toSend.Length - 1, 1);
|
|
lock (Console.Out)
|
|
{
|
|
Console.CursorLeft--;
|
|
Console.Write(' ');
|
|
Console.CursorLeft--;
|
|
}
|
|
}
|
|
else if (key.Key == ConsoleKey.Enter)
|
|
{
|
|
// Send message.
|
|
packet = new ClientSendMessagePacket()
|
|
{
|
|
Content = toSend.ToString()
|
|
};
|
|
lock (writer)
|
|
{
|
|
writer.Write(packet, serverAes);
|
|
}
|
|
lock (Console.Out)
|
|
{
|
|
Console.CursorLeft = 0;
|
|
Console.Write(new string(' ', toSend.Length));
|
|
Console.CursorLeft = 0;
|
|
}
|
|
toSend.Clear();
|
|
Thread.Sleep(100);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static readonly Dictionary<Guid, PublicUserInfo> cachedUsers = new();
|
|
private static Task MonitorThread(PacketWriter writer, PacketReader reader, Aes keys, int lastMessagePos)
|
|
{
|
|
while (true)
|
|
{
|
|
INetworkPacket packet = reader.ReadPacket(keys);
|
|
if (packet is ServerMessageAlertPacket pAlert)
|
|
{
|
|
MessageInfo message = pAlert.Message;
|
|
if (!cachedUsers.TryGetValue(message.UserId, out PublicUserInfo? user))
|
|
{
|
|
packet = new ClientRequestUserInfoPacket()
|
|
{
|
|
UserId = message.UserId,
|
|
Username = null
|
|
};
|
|
lock (writer)
|
|
{
|
|
writer.Write(packet, keys);
|
|
packet = reader.ReadPacket(keys);
|
|
}
|
|
|
|
if (packet is ServerUserInformationPacket pUserInfo)
|
|
{
|
|
user = pUserInfo.UserInfo;
|
|
cachedUsers.Add(message.UserId, user);
|
|
}
|
|
else user = new() { Username = "Unknown User" };
|
|
}
|
|
|
|
lock (Console.Out)
|
|
{
|
|
Console.WriteLine($"""
|
|
{message.SentAt,19:g} {user.Username}
|
|
{message.Contents}
|
|
|
|
""");
|
|
lastMessagePos = Console.CursorTop;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|