done with printing stuff, saving is non trivial due to classes without constructors or something

This commit is contained in:
Rusty Striker 2025-01-06 20:27:54 +02:00
parent 2aa4a86e5f
commit 2a3be450be
Signed by: RustyStriker
GPG key ID: 87E4D691632DFF15
8 changed files with 277 additions and 109 deletions

47
server/Log.cs Normal file
View file

@ -0,0 +1,47 @@
using System;
using lib;
namespace server;
/// Printing and helper class for logging stuff on the server, this is not static so we dont have to keep on adding the id and client at every call
public class Log(int id, string? client)
{
public int Id { set; get; } = id;
public string? Client { set; get; } = client;
public void Encrypted(string message, byte[] payload)
{
Console.ForegroundColor = Utils.C_ENC;
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {Id} [{Client ?? "unknown"}] - {message}: {Convert.ToBase64String(payload)}");
Console.ResetColor();
}
public void Decrypted(string message, byte[] payload)
{
Console.ForegroundColor = Utils.C_DEC;
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {Id} [{Client ?? "unknown"}] - {message}: {Convert.ToBase64String(payload)}");
Console.ResetColor();
}
public void Decrypted(string message)
{
Console.ForegroundColor = Utils.C_DEC;
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {Id} [{Client ?? "unknown"}] - {message}");
Console.ResetColor();
}
public void System(string message)
{
Console.ForegroundColor = Utils.C_SYS;
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {Id} [{Client ?? "unknown"}] - {message}");
Console.ResetColor();
}
public void Message(string message)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {Id} [{Client ?? "unknown"}] - {message}");
}
}

View file

@ -7,6 +7,7 @@ using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using lib;
@ -23,7 +24,7 @@ public class Program
static async Task Main()
{
// Generally this key would be static but since its not production yet we can generate it every time to make sure
// Generally this key would be static but since its not production we can generate it every time to make sure
// the users has the key and could load it from file
RSA key = RSA.Create(1024);
File.WriteAllText("server_key.pem", key.ExportRSAPublicKeyPem());
@ -31,6 +32,14 @@ public class Program
int port = 12345;
TcpListener server = new(IPAddress.Parse("0.0.0.0"), port);
int connectionCounter = 0;
Log log = new(-1, null);
Console.CancelKeyPress += delegate
{
Console.WriteLine("\nEXITING!");
server.Stop();
};
try
{
server.Start();
@ -48,8 +57,8 @@ public class Program
}
catch (Exception ex)
{
Console.WriteLine($"Client crashed: {ex.Message}");
Console.WriteLine(ex.StackTrace);
log.System($"Client crashed: {ex.Message}");
log.System(ex.StackTrace ?? "MISSING STACK TRACE");
}
});
connectionCounter += 1;
@ -57,8 +66,8 @@ public class Program
}
catch (Exception ex)
{
Console.WriteLine($"Server error: {ex.Message}");
Console.WriteLine("Trace: " + ex.StackTrace);
log.System($"Server error: {ex.Message}");
log.System("Trace: " + ex.StackTrace);
}
finally
{
@ -68,31 +77,32 @@ public class Program
static async Task HandleClient(TcpClient client, int id, RSA pubKey)
{
Write(id, "Got a new client");
Log log = new(id, null);
log.System("Got a new client");
string clientPhone = "";
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
byte counter = 0;
// Get AES session key
int len = await stream.ReadAsync(buffer);
Write(id, $"Got {len} bytes");
log.Encrypted("Key + IV", buffer[..len]);
byte[] skBytes = pubKey.Decrypt(buffer[..len], RSAEncryptionPadding.OaepSHA256);
log.Decrypted("Key + IV", skBytes);
Aes sk = Aes.Create();
sk.Key = skBytes[..32]; // just to make sure no one sends a too big to be true key
sk.IV = skBytes[32..];
Write(id, $"key: {string.Join(' ', sk.Key)}");
Write(id, $"IV: {string.Join(' ', sk.IV)}");
await stream.WriteAsync(new byte[] { 0 });
// Get first message (should be either login or register)
len = await stream.ReadAsync(buffer);
Write(id, $"Got {len} bytes");
log.Encrypted("Auth message", buffer[..len]);
byte[] msgDec = sk.DecryptCfb(buffer[..len], sk.IV, PaddingMode.PKCS7);
log.Decrypted("Auth message", msgDec);
byte[] msg = msgDec[..MSG_LEN];
Write(id, Request.RequestToString(msg));
log.Message(Request.RequestToString(msg));
if (msg[0] != 0)
{
Write(id, "Invalid session id!");
log.System("Invalid message version!");
client.Dispose();
return;
}
@ -102,13 +112,13 @@ public class Program
// Do register stuff
// get phone number
string phone = Utils.BytesToNumber(msg[3..11]);
Write(id, $"Client wants to register as {phone}");
log.System($"Client wants to register as {phone}");
clientPhone = phone;
log.Client = phone;
int keyLen = BitConverter.ToInt32(msg, 11);
RSA pub = RSA.Create();
pub.ImportRSAPublicKey(msgDec.AsSpan()[MSG_LEN..], out int bytesRead);
Write(id, $"Imported key len: {bytesRead} while client claims it is {keyLen}");
Write(id, $"Imported key is: \n {pub.ExportRSAPublicKeyPem()}\n");
log.System($"Imported key is: \n {pub.ExportRSAPublicKeyPem()}\n");
// generate the 6 digit code and send it
byte[] code = [
(byte)Rand.Next(10),
@ -118,17 +128,19 @@ public class Program
(byte)Rand.Next(10),
(byte)Rand.Next(10),
];
await Send6DigitCodeInSecureChannel(stream, code);
await SendBySecureChannel(stream, code);
// wait for the code to be back with a key
int tries = 5; // allow 5 tries before closing the connection and forcing a restart
while (tries > 0)
{
tries -= 1;
len = await stream.ReadAsync(buffer);
Write(id, $"Got 6 digit code with sig, len: {len}");
log.Encrypted("ConfirmRegister", buffer[..len]);
msg = sk.DecryptCfb(buffer[..MSG_LEN], sk.IV, PaddingMode.None);
Write(id, Request.RequestToString(msg));
log.Decrypted("ConfirmRegister", msg);
log.Message(Request.RequestToString(msg));
byte[] sig = buffer[MSG_LEN..len];
log.Decrypted("ConfirmRegister (sig)", sig);
if (msg[0] != 0 || msg[1] != (byte)RequestType.ConfirmRegister || msg[2] != counter)
{
// invalid or unexpected req, someone might be sending dups
@ -139,13 +151,16 @@ public class Program
int expectedSigLen = BitConverter.ToInt32(msg, 9);
if (expectedSigLen != len - MSG_LEN)
{
Write(id, $"expected sig len doesnt match read len: {expectedSigLen} / {len - MSG_LEN}");
log.System($"expected sig len doesnt match read len: {expectedSigLen} / {len - MSG_LEN}");
}
// check if the codes are equal
if (code.Zip(gottenCode).Any(a => a.First != a.Second))
{
// codes are not equal, send a nack
// perhaps we should only send a SIG INVALID (or just FAILED) and hide the reason in case someone tries to guess the code
msg = sk.EncryptCfb(Encoding.UTF8.GetBytes("BAD CODE"), sk.IV, PaddingMode.PKCS7);
log.Decrypted("Fail Register", Encoding.UTF8.GetBytes("BAD CODE"));
log.Encrypted("Fail Register", msg);
await stream.WriteAsync(msg);
}
else
@ -155,6 +170,8 @@ public class Program
if (sigValid)
{
msg = sk.EncryptCfb(Encoding.UTF8.GetBytes("OK"), sk.IV, PaddingMode.PKCS7);
log.Decrypted("Register Success", Encoding.UTF8.GetBytes("OK"));
log.Encrypted("Register Success", msg);
await stream.WriteAsync(msg);
Data.Keys[phone] = pub; // save the key
break;
@ -162,6 +179,8 @@ public class Program
else
{
msg = sk.EncryptCfb(Encoding.UTF8.GetBytes("SIG INVALID"), sk.IV, PaddingMode.PKCS7);
log.Decrypted("Fail Register", Encoding.UTF8.GetBytes("SIG INVALID"));
log.Encrypted("Fail Register", msg);
await stream.WriteAsync(msg);
}
}
@ -172,42 +191,51 @@ public class Program
{
// verify login
clientPhone = Utils.BytesToNumber(msg[3..11]);
log.Client = clientPhone;
counter = IncrementCounter(msg[2]);
if (!Data.Keys.TryGetValue(clientPhone, out RSA? clientKey))
{
stream.Close();
client.Close();
Write(id, $"Client claims to be {clientPhone}, but could not find key in records");
log.System($"Client claims to be {clientPhone}, but could not find key in records");
return;
}
byte[] challenge = new byte[16];
Rand.NextBytes(challenge);
Write(id, $"Sending challenge: {Convert.ToBase64String(challenge)}");
log.Decrypted("Challenge", challenge);
byte[] response = sk.EncryptCfb(challenge, sk.IV, PaddingMode.None);
log.Encrypted("Challenge", response);
await stream.WriteAsync(response);
len = await stream.ReadAsync(buffer);
log.Encrypted("Challenge Response", buffer[..len]);
msg = sk.DecryptCfb(buffer[..MSG_LEN], sk.IV, PaddingMode.None);
Write(id, Request.RequestToString(msg));
log.Decrypted("Challenge Message", msg);
log.Message(Request.RequestToString(msg));
if (msg[2] != counter)
{
client.Close();
Write(id, $"Invalid counter in login response, quitting");
log.System($"Invalid counter in login response, quitting");
return;
}
counter = IncrementCounter(counter);
byte[] sig = buffer[MSG_LEN..len];
Write(id, $"Got challenge signature, length: {len - MSG_LEN}, client says: {BitConverter.ToInt32(msg, 3)}");
Write(id, $"Sig: {Convert.ToBase64String(sig)}");
// sig wasnt encrypted so this print is somewhat redundant, but i think its still useful to see the sig alone
log.Decrypted("Challenge Sig", sig);
bool valid = clientKey.VerifyData(challenge, sig, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
if (valid)
{
log.System("Client verification complete");
response = sk.EncryptCfb(Encoding.UTF8.GetBytes("OK"), sk.IV, PaddingMode.PKCS7);
log.Decrypted("Challenge Response", Encoding.UTF8.GetBytes("OK"));
log.Encrypted("Challenge Response", response);
await stream.WriteAsync(response);
}
else
{
Write(id, "Client failed verification, invalid signature");
log.System("Client failed verification, invalid signature");
response = sk.EncryptCfb(Encoding.UTF8.GetBytes("INVALID SIG"), sk.IV, PaddingMode.PKCS7);
log.Decrypted("Challenge Response", Encoding.UTF8.GetBytes("INVALID SIG"));
log.Encrypted("Challenge Response", response);
await stream.WriteAsync(response);
client.Close();
return;
@ -216,7 +244,7 @@ public class Program
else
{
// invalid connection, quit
Write(id, "Client didnt register or login as first message");
log.System("Client didnt register or login as first message");
client.Dispose();
return;
}
@ -228,12 +256,14 @@ public class Program
// while the client is connected, simply read messages from the client and handle accordingly,
// either by getting new messages for other ppl, or sending back keys/pending messages
len = await stream.ReadAsync(buffer);
log.Encrypted("Request", buffer[..len]);
msg = sk.DecryptCfb(buffer[..MSG_LEN], sk.IV, PaddingMode.None);
Write(id, Request.RequestToString(msg));
log.Decrypted("Request message", msg);
log.Message(Request.RequestToString(msg));
// verify that the counter message is correct
if (msg[0] != 0 || msg[2] != counter)
{
msg = sk.EncryptCfb(Encoding.UTF8.GetBytes("DUPLICATE"), sk.IV, PaddingMode.PKCS7);
msg = sk.EncryptCfb(Encoding.UTF8.GetBytes("INVALID REQUEST"), sk.IV, PaddingMode.PKCS7);
await stream.WriteAsync(msg);
continue;
}
@ -244,7 +274,6 @@ public class Program
byte[] msgsLens = Enumerable.Repeat<byte>(0, 16).ToArray(); // 128 bits
// get 15 messages, last byte will indicate if there are more
List<byte[]> msgs = Data.GetMessages(clientPhone, 7) ?? [];
Write(id, $"Got {msgs.Count} messages");
byte[] msgsBytes = new byte[msgs.Select(m => m.Length).Sum()];
int msgsbytesIndex = 0;
for (int i = 0; i < msgs.Count; i += 1)
@ -260,8 +289,11 @@ public class Program
}
msgsLens[15] = Data.PeekMessages(clientPhone) ? (byte)1 : (byte)0;
// only need to encrypt the lengths of the messages, as the messages themselves are encrypted
log.Decrypted("GetMessages", msgsLens);
msgsLens = sk.EncryptCfb(msgsLens, sk.IV, PaddingMode.None);
log.Encrypted("GetMessages", msgsLens);
byte[] finalPayload = [.. msgsLens, .. msgsBytes];
log.Encrypted("GetMessages Final", finalPayload);
await stream.WriteAsync(finalPayload);
break;
case RequestType.GetUserKey:
@ -270,13 +302,17 @@ public class Program
if (key != null)
{
msg = [0, .. key.ExportRSAPublicKey()];
log.Decrypted("GetUserKey", msg);
msg = sk.EncryptCfb(msg, sk.IV, PaddingMode.PKCS7);
log.Encrypted("GetUserKey", msg);
await stream.WriteAsync(msg);
}
else
{
msg = [1, .. Encoding.UTF8.GetBytes("USER DOES NOT EXIST")];
log.Decrypted("GetUserKey", msg);
msg = sk.EncryptCfb(msg, sk.IV, PaddingMode.PKCS7);
log.Encrypted("GetUserKey", msg);
await stream.WriteAsync(msg);
}
break;
@ -285,12 +321,13 @@ public class Program
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}");
log.System($"Got message to {recv} of length {len - MSG_LEN} but expected {msgLen}");
}
byte[] clientMsg = buffer[MSG_LEN..(msgLen + MSG_LEN)];
log.Encrypted("SendMessage message", clientMsg);
// simply add the clientMsg to the "Data"
bool added = Data.AddMessage(recv, clientMsg);
Write(id, $"Added message to {recv} of length {msgLen}: {added}");
log.System($"Added message to {recv} of length {msgLen}: {added}");
break;
default:
msg = sk.EncryptCfb(Encoding.UTF8.GetBytes("INVALID REQUEST"), sk.IV, PaddingMode.PKCS7);
@ -303,8 +340,8 @@ public class Program
}
catch (Exception ex)
{
Write(id, $"Client failed with error {ex.Message}");
Write(id, $"Stack: {ex.StackTrace}");
log.System($"Client failed with error {ex.Message}");
log.System($"Stack: {ex.StackTrace}");
}
client.Dispose();
@ -315,14 +352,8 @@ public class Program
return counter == byte.MaxValue ? (byte)0 : (byte)(counter + 1);
}
static async Task Send6DigitCodeInSecureChannel(NetworkStream stream, byte[] code)
static async Task SendBySecureChannel(NetworkStream stream, byte[] code)
{
await stream.WriteAsync(code);
}
/// Helper log message so it would print both the time and the id
static void Write(int id, string Message)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {id} - {Message}");
}
}