yo, i only need to do login (and printing stuff) and im done!

This commit is contained in:
Rusty Striker 2025-01-01 19:42:20 +02:00
parent 145e77e437
commit 04a4f7ece8
Signed by: RustyStriker
GPG key ID: 87E4D691632DFF15
5 changed files with 124 additions and 54 deletions

View file

@ -1,30 +1,29 @@
# Project - TODO: # Project - TODO:
[ ] implement SendMessage at the server [x] implement SendMessage at the server
[ ] implement Login [ ] implement Login
[ ] client [ ] client
[ ] server [ ] server
[ ] Figure out how to do the messages themselves [x] Figure out how to do the messages themselves
[ ] implement sending messages properly [x] Figure out how to pass the signature (i think it must be as a second packet)
[ ] implement message acks [x] implement sending messages properly
[x] implement message acks
## Protocol todo: ## Protocol todo:
[ ] Figure out how a message and message ack payload will look [x] Figure out how a message and message ack payload will look
[ ] Figure out server responses (hopefully manages to be stuck in a 512 bit block as well)
## Misc todo: ## Misc todo:
[ ] Create a Request to String function for easy printing and debugging [x] Create a Request to String function for easy printing and debugging
## client todo: ## client todo:
[ ] Check for key when turned on [ ] Check for key when turned on
[x] generate key and register if no key is preset, and save it after registration is done [x] generate key and register if no key is preset, and save it after registration is done
[ ] if key is present, start by establishing connection (which makes sure we are signed in) [ ] if key is present, start by establishing connection (which makes sure we are signed in)
[ ] use AES to get basic packets from the server [x] use AES to get basic packets from the server
[ ] use RSA private key to read normal messages [x] use RSA private key to read normal messages
## Server todo: ## Server todo:
@ -32,9 +31,9 @@
[x] use RSA key to get first message and extract AES key [x] use RSA key to get first message and extract AES key
[ ] verify the user using its public RSA key [ ] verify the user using its public RSA key
[x] if it was a register session save the key into the BIG DATA STRUCTURE [x] if it was a register session save the key into the BIG DATA STRUCTURE
[ ] Keep lists of incoming messages [x] Keep lists of incoming messages
(doesnt need to know from who, they are just big blobs of shlomp) (doesnt need to know from who, they are just big blobs of shlomp)
[ ] When user asks for incoming messages, make basic packet and append the incoming messages [x] When user asks for incoming messages, make basic packet and append the incoming messages
- last byte is the "how many messages are left" byte - last byte is the "how many messages are left" byte
- each byte in the extra data will be the length of the next message, so - each byte in the extra data will be the length of the next message, so
if there are 3 messages of length 128, 200, 300 bytes it will be if there are 3 messages of length 128, 200, 300 bytes it will be

View file

@ -27,8 +27,8 @@ public class Message
Id = BitConverter.ToUInt32(bytes, 0); Id = BitConverter.ToUInt32(bytes, 0);
Sender = Utils.BytesToNumber(bytes[4..12]); Sender = Utils.BytesToNumber(bytes[4..12]);
IsAck = bytes[12] != 0; IsAck = bytes[12] != 0;
Content = Encoding.UTF8.GetString(bytes[13..(bytes.Length - 32)]); Content = Encoding.UTF8.GetString(bytes[13..bytes.Length]);
Signature = bytes[(bytes.Length - 32)..]; Signature = [];
} }
public Message(uint Id, string Sender, bool IsAck, string Content) public Message(uint Id, string Sender, bool IsAck, string Content)
@ -42,15 +42,15 @@ public class Message
public void CalculateSignature(RSA key) public void CalculateSignature(RSA key)
{ {
Signature = key.SignData(Bytes(false), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); Signature = key.SignData(Bytes(), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
} }
public bool IsSignatureValid(RSA key) public bool IsSignatureValid(RSA key)
{ {
return key.VerifyData(Bytes(false), Signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); return key.VerifyData(Bytes(), Signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
} }
public byte[] Bytes(bool IncludeSignature = true) public byte[] Bytes()
{ {
List<byte> msg = []; List<byte> msg = [];
// 0..4 // 0..4
@ -61,11 +61,6 @@ public class Message
msg.Add((byte)(IsAck ? 1 : 0)); msg.Add((byte)(IsAck ? 1 : 0));
// 13..(len - 32) // 13..(len - 32)
msg.AddRange(Encoding.UTF8.GetBytes(Content)); msg.AddRange(Encoding.UTF8.GetBytes(Content));
// (len - 32)..
if (IncludeSignature) // we dont want to include the signature when we sign/verify the data
{
msg.AddRange(Signature);
}
return [0, .. msg]; return [0, .. msg];
} }

View file

@ -1,12 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Sockets; using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using client;
using lib; using lib;
namespace Client; namespace Client;
@ -14,10 +16,12 @@ namespace Client;
public class Program public class Program
{ {
static byte counter = 0; static byte counter = 0;
static string User = "";
static readonly Random Rand = new();
static async Task Main(string[] args) static async Task Main(string[] args)
{ {
string user = args User = args
.SkipWhile(a => !new[] { "-p", "--phone" }.Contains(a)) // search for the option .SkipWhile(a => !new[] { "-p", "--phone" }.Contains(a)) // search for the option
.Skip(1) // skip the `-u/--user` itself to get the value .Skip(1) // skip the `-u/--user` itself to get the value
.FirstOrDefault() ?? "0000"; // get the value or deafult if it doesnt exist .FirstOrDefault() ?? "0000"; // get the value or deafult if it doesnt exist
@ -25,10 +29,10 @@ public class Program
// On boot, check if a key is available // On boot, check if a key is available
RSA? serverKey = LoadRSAFromFile("server_key.pem"); RSA? serverKey = LoadRSAFromFile("server_key.pem");
if (serverKey == null) { Console.WriteLine("Could not find server key, please run server before clients!"); return; } if (serverKey == null) { Console.WriteLine("Could not find server key, please run server before clients!"); return; }
RSA pubKey = LoadRSAFromFile($"pubkey_{user}.pem") ?? RSA.Create(2048); RSA pubKey = LoadRSAFromFile($"pubkey_{User}.pem") ?? RSA.Create(2048);
RSA privKey = LoadRSAFromFile($"privkey_{user}.pem") ?? pubKey; RSA privKey = LoadRSAFromFile($"privkey_{User}.pem") ?? pubKey;
SaveRSAKeys(user, pubKey, privKey); SaveRSAKeys(User, pubKey, privKey);
using TcpClient client = new("127.0.0.1", 12345); using TcpClient client = new("127.0.0.1", 12345);
var stream = client.GetStream(); var stream = client.GetStream();
@ -40,7 +44,7 @@ public class Program
{ {
try try
{ {
await RegisterClient(user, pubKey, privKey, serverKey, sk, stream); await RegisterClient(User, pubKey, privKey, serverKey, sk, stream);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -212,8 +216,10 @@ public class Program
} }
else if (publicKeys.TryGetValue(currentChat!, out RSA? key)) else if (publicKeys.TryGetValue(currentChat!, out RSA? key))
{ {
// TODO: add signature and origin please yes thank you // Pick the id as a random uint, good enough for the sake of testing
byte[] userMsg = key.Encrypt(Encoding.UTF8.GetBytes(input), RSAEncryptionPadding.OaepSHA256); Message m = new((uint)Rand.Next(), User, false, input);
m.CalculateSignature(privKey);
byte[] userMsg = [.. key.Encrypt(m.Bytes(), RSAEncryptionPadding.OaepSHA256), .. m.Signature];
byte[] req = Request.CreateRequest( byte[] req = Request.CreateRequest(
RequestType.SendMessage, RequestType.SendMessage,
ref counter, ref counter,
@ -257,31 +263,64 @@ public class Program
byte[] req = Request.CreateRequest(RequestType.GetMessages, ref counter, []); byte[] req = Request.CreateRequest(RequestType.GetMessages, ref counter, []);
req = sk.EncryptCfb(req, sk.IV, PaddingMode.None); // no need for padding this is exactly 128 bytes req = sk.EncryptCfb(req, sk.IV, PaddingMode.None); // no need for padding this is exactly 128 bytes
await stream.WriteAsync(req); await stream.WriteAsync(req);
byte[] buffer = new byte[1024]; byte[] buffer = new byte[4096];
int len = await stream.ReadAsync(buffer); int len = await stream.ReadAsync(buffer);
byte[] lengths = buffer[..16]; byte[] lengths = buffer[..16];
lengths = sk.DecryptCfb(lengths, sk.IV, PaddingMode.None); lengths = sk.DecryptCfb(lengths, sk.IV, PaddingMode.None);
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"Got messages: {string.Join(", ", lengths)}");
Console.ResetColor();
byte[] msg = new byte[1024]; // msg buffer byte[] msg = new byte[1024]; // msg buffer
int start = 16; // skip the first 16 bytes since its the lengths message int start = 16; // skip the first 16 bytes since its the lengths message
foreach (byte l in lengths.Take(15)) for (int i = 0; i < lengths.Length - 1; i += 2)
{ {
int l = (lengths[i] << 8) | (lengths[i + 1]);
Console.WriteLine($"got message of length: {l}");
if (l == 0) { break; } // a 0 means we are done actually, as empty messages shouldn't be allowed if (l == 0) { break; } // a 0 means we are done actually, as empty messages shouldn't be allowed
// get the msg // get the msg
int end = start + l; int end = start + l;
if (end > len) if (end > len)
{ {
// we need to read more as there was use of more than 1024 overall // TODO: properly handle it as a buffered stream and properly buffer it
// todo for now
// TODO: read more incoming bytes when messages exceed the 1024 buffer
} }
Console.WriteLine($"got ecnryped message: {Convert.ToBase64String(buffer[start..end])}");
WriteColor($"got ecnryped message: {Convert.ToBase64String(buffer[start..end])}", ConsoleColor.Green);
// decrypt the message // decrypt the message
if (privKey.TryDecrypt(buffer.AsSpan()[start..end], msg, RSAEncryptionPadding.OaepSHA256, out int written)) int msgLen = privKey.KeySize / 8;
if (privKey.TryDecrypt(buffer.AsSpan()[start..(msgLen + start)], msg, RSAEncryptionPadding.OaepSHA256, out int written))
{ {
byte[] dec = msg[..written]; byte[] dec = msg[..written];
Console.WriteLine($"decrypted message: {Convert.ToBase64String(dec)}"); Message m = new(dec)
Console.WriteLine($"Message: {Encoding.UTF8.GetString(dec)}"); {
Signature = buffer[(start + msgLen)..end]
};
bool sigValid = false;
if (publicKeys.TryGetValue(m.Sender, out RSA? pk) || (pk = await GetPublicKey(stream, sk, m.Sender)) != null)
{
publicKeys[m.Sender] = pk; // update the key value stored in case it was requested for in GetPublicKey
sigValid = m.IsSignatureValid(pk);
}
else
{
Console.WriteLine("Failed getting sender's public key");
}
WriteColor($"decrypted message: {Convert.ToBase64String(dec)}", ConsoleColor.Green);
Console.WriteLine($"Got message from {m.Sender} (id: {m.Id}) (valid: {sigValid}): " + (m.IsAck ? "ACK" : m.Content));
// if the signature is valid we also want to send back a message ack for the same id :)
if (sigValid)
{
Message mAck = new(m.Id, User, true, "");
mAck.CalculateSignature(privKey);
byte[] userMsg = [.. publicKeys[m.Sender].Encrypt(mAck.Bytes(), RSAEncryptionPadding.OaepSHA256), .. mAck.Signature];
byte[] reqAck = Request.CreateRequest(
RequestType.SendMessage,
ref counter,
[.. Utils.NumberToBytes(m.Sender), .. BitConverter.GetBytes(userMsg.Length)]);
reqAck = sk.EncryptCfb(reqAck, sk.IV);
stream.Write([.. reqAck, .. userMsg]);
}
} }
else else
{ {
@ -292,9 +331,19 @@ public class Program
// supposedly the sender will notice the lack of response and send it again // supposedly the sender will notice the lack of response and send it again
Console.WriteLine("Incoming message failed to decrypt, unknown sender"); Console.WriteLine("Incoming message failed to decrypt, unknown sender");
} }
start = end;
} }
bool hasMoreMessages = lengths[15] != 0; bool hasMoreMessages = lengths[15] != 0;
Console.WriteLine($"hasMoreMessages: {hasMoreMessages}");
} }
}
static void WriteColor(string Line, ConsoleColor Color, ConsoleColor Background = ConsoleColor.Black)
{
Console.ForegroundColor = Color;
Console.BackgroundColor = Background;
Console.WriteLine(Line);
Console.ResetColor();
}
}

View file

@ -37,6 +37,26 @@ public class Data
} }
} }
public bool AddMessage(string Phone, byte[] Message)
{
if (Keys.ContainsKey(Phone))
{
if (Messages.TryGetValue(Phone, out var value))
{
value.Enqueue(Message);
}
else
{
Messages[Phone] = new Queue<byte[]>([Message]);
}
return true;
}
else
{
return false;
}
}
public bool PeekMessages(string Phone) public bool PeekMessages(string Phone)
{ {
return Messages.TryGetValue(Phone, out Queue<byte[]>? value) && value.TryPeek(out var _); return Messages.TryGetValue(Phone, out Queue<byte[]>? value) && value.TryPeek(out var _);

View file

@ -200,22 +200,22 @@ public class Program
switch ((RequestType)msg[1]) switch ((RequestType)msg[1])
{ {
case RequestType.GetMessages: case RequestType.GetMessages:
byte[] msgsLens = new byte[16]; // 128 bits byte[] msgsLens = Enumerable.Repeat<byte>(0, 16).ToArray(); // 128 bits
// get 15 messages, last byte will indicate if there are more // get 15 messages, last byte will indicate if there are more
List<byte[]> msgs = Data.GetMessages(clientPhone, 15) ?? []; List<byte[]> msgs = Data.GetMessages(clientPhone, 7) ?? [];
Write(id, $"Got {msgs.Count} messages");
byte[] msgsBytes = new byte[msgs.Select(m => m.Length).Sum()]; byte[] msgsBytes = new byte[msgs.Select(m => m.Length).Sum()];
int msgsbytesIndex = 0; int msgsbytesIndex = 0;
for (int i = 0; i < msgsLens.Length - 1; i += 1) for (int i = 0; i < msgs.Count; i += 1)
{ {
// it is expected that all messages will be less than 255 bytes, hence a single byte to // messages are encrypted blocks of (currently) 1024 RSA keys, so it would be 256 bytes
// denote length is sufficient, but a simple update to the protocol can allow up to 7 messages // meaning we need a short at least (technically we need 9 bytes, but using a full short will allow for
// per request (instead of 15), and use an ushort (u16) instead // bigger key sizes without much hassle, until a certain length)
msgsLens[i] = (byte)(msgs.Count > i ? msgs[i].Length : 0); msgsLens[2 * i] = (byte)(msgs[i].Length >> 8);
if (i < msgs.Count) msgsLens[(2 * i) + 1] = (byte)msgs[i].Length;
{ // copy the message to the msgsBytes array
// copy the message to the msgsBytes array Array.Copy(msgs[i], 0, msgsBytes, msgsbytesIndex, msgs[i].Length);
Array.Copy(msgs[i], 0, msgsBytes, msgsbytesIndex, msgs[i].Length); msgsbytesIndex += msgs[i].Length;
}
} }
msgsLens[15] = Data.PeekMessages(clientPhone) ? (byte)1 : (byte)0; msgsLens[15] = Data.PeekMessages(clientPhone) ? (byte)1 : (byte)0;
// only need to encrypt the lengths of the messages, as the messages themselves are encrypted // only need to encrypt the lengths of the messages, as the messages themselves are encrypted
@ -242,7 +242,14 @@ public class Program
case RequestType.SendMessage: case RequestType.SendMessage:
string recv = Utils.BytesToNumber(msg[3..11]); string recv = Utils.BytesToNumber(msg[3..11]);
int msgLen = BitConverter.ToInt32(msg, 11); int msgLen = BitConverter.ToInt32(msg, 11);
if (msgLen != (len - MSG_LEN))
{
Write(id, $"Got message to {recv} of length {len - MSG_LEN} but expected {msgLen}");
}
byte[] clientMsg = buffer[MSG_LEN..(msgLen + MSG_LEN)];
// simply add the clientMsg to the "Data"
bool added = Data.AddMessage(recv, clientMsg);
Write(id, $"Added message to {recv} of length {msgLen}: {added}");
break; break;
default: default:
msg = sk.EncryptCfb(Encoding.UTF8.GetBytes("INVALID REQUEST"), sk.IV, PaddingMode.PKCS7); msg = sk.EncryptCfb(Encoding.UTF8.GetBytes("INVALID REQUEST"), sk.IV, PaddingMode.PKCS7);