From 2aa4a86e5fd49554551aacd8ba8de4a82003a219 Mon Sep 17 00:00:00 2001 From: Rusty Striker Date: Thu, 2 Jan 2025 18:49:58 +0200 Subject: [PATCH] got login working --- README.md | 79 +++-------------------------------------------- client/Program.cs | 52 +++++++++++++++++++++++++------ lib/Request.cs | 14 ++++++--- protocol.md | 6 ++-- server/Program.cs | 57 +++++++++++++++++++++++++++++----- 5 files changed, 108 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index 12cc456..5e234bd 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,6 @@ # Project - TODO: -[x] implement SendMessage at the server -[ ] implement Login - [ ] client - [ ] server -[x] Figure out how to do the messages themselves -[x] Figure out how to pass the signature (i think it must be as a second packet) -[x] implement sending messages properly -[x] implement message acks - -## Protocol todo: - -[x] Figure out how a message and message ack payload will look - -## Misc todo: - -[x] Create a Request to String function for easy printing and debugging - -## client todo: - -[ ] Check for key when turned on - [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) -[x] use AES to get basic packets from the server -[x] use RSA private key to read normal messages - -## Server todo: - -[x] Laucnh task for each new connection -[x] use RSA key to get first message and extract AES 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] Keep lists of incoming messages - (doesnt need to know from who, they are just big blobs of shlomp) -[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 - - 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 - [128, 200, 300, 0 ...] and the actual position in the payload is easy - to calculate ([128, 200+128=328, 300+328=628, ...]) - - -Register process: - -Client Server - -Send AES key (sk) -Send Register(pub key) - Get AES - Get Register - Send 6 digit code -Get 6 digit code -(1) Wait for user to input 6 digit code -Send 6 digit code (signed) - Get 6 digit code and verify (code, then sig) - Send OK/NACK -if NACK goto (1) - -Login process: - -Client Server - -Send AES key (sk) -Send Login message (Phone, AES sig) - Get AES key - Verify AES sig with Phone-pub key - if sig is invalid: close connection - else: send stored messages because why not - -Usual process get messages: - -Client Server - -Send GetMessages - Send back messages -Send GotMessages(amount) \ No newline at end of file +[ ] Do print stuff for EVERYTHING (with colors preferably) +[ ] rewrite the protocol.md +[ ] export everything and upload it +[ ] get a nice drink to celebrate \ No newline at end of file diff --git a/client/Program.cs b/client/Program.cs index ec82ba3..7452eb5 100644 --- a/client/Program.cs +++ b/client/Program.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -15,9 +16,9 @@ namespace Client; public class Program { - static byte counter = 0; static string User = ""; - static readonly Random Rand = new(); + static readonly Random Rand = new((int)DateTime.Now.Ticks); + static byte counter = (byte)Rand.Next(); static async Task Main(string[] args) { @@ -40,11 +41,17 @@ public class Program // First contact init bool needsRegister = pubKey == privKey || args.Any(a => new[] { "-fr", "--force-register" }.Contains(a)); Aes sk = Aes.Create(); // creates an AES-256 key + // establish secure connection + byte[] skEnc = serverKey.Encrypt([.. sk.Key, .. sk.IV], RSAEncryptionPadding.OaepSHA256); + await stream.WriteAsync(skEnc); + // wait for the server to confirm it recieved the keys + await stream.ReadExactlyAsync(new byte[1]); + if (needsRegister) { try { - await RegisterClient(User, pubKey, privKey, serverKey, sk, stream); + await RegisterClient(User, pubKey, privKey, sk, stream); } catch (Exception ex) { @@ -56,25 +63,46 @@ public class Program } else { - // attempt to login here + // start login process + byte[] msg = Request.CreateRequest(RequestType.Login, ref counter, Utils.NumberToBytes(User)); + msg = sk.EncryptCfb(msg, sk.IV, PaddingMode.PKCS7); + await stream.WriteAsync(msg); + byte[] toSign = new byte[16]; + await stream.ReadExactlyAsync(toSign, 0, 16); + toSign = sk.DecryptCfb(toSign, sk.IV, PaddingMode.None); + byte[] signed = privKey.SignData(toSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + msg = Request.CreateRequest(RequestType.ConfirmLogin, ref counter, BitConverter.GetBytes(signed.Length)); + msg = sk.EncryptCfb(msg, sk.IV, PaddingMode.None); + WriteColor($"Sending sig: {Convert.ToBase64String(signed)}", ConsoleColor.Green); + msg = [.. msg, .. signed]; + await stream.WriteAsync(msg); + byte[] buffer = new byte[512]; + int len = await stream.ReadAsync(buffer); + byte[] dec = sk.DecryptCfb(buffer[..len], sk.IV, PaddingMode.PKCS7); + string r = Encoding.UTF8.GetString(dec); + if (r == "OK") + { + Console.WriteLine("Login successful"); + } + else + { + Console.WriteLine($"Failed login: {r}"); + client.Dispose(); + return; + } } await HandleUserInput(client, stream, sk, privKey); } - async static Task RegisterClient(string user, RSA pub, RSA priv, RSA server, Aes sk, NetworkStream stream) + async static Task RegisterClient(string user, RSA pub, RSA priv, Aes sk, NetworkStream stream) { Console.WriteLine("Attempting to register with public key:"); Console.WriteLine(pub.ExportRSAPublicKeyPem()); // Generate aes key and send it forward Console.WriteLine($"Session key: {string.Join(' ', sk.Key)}"); Console.WriteLine($"Session IV: {string.Join(' ', sk.IV)}"); - byte[] skEnc = server.Encrypt([.. sk.Key, .. sk.IV], RSAEncryptionPadding.OaepSHA256); - await stream.WriteAsync(skEnc); - // wait for the server to confirm it recieved the keys - await stream.ReadExactlyAsync(new byte[1]); - // Generate the Register msg Console.WriteLine("Sending rsa public key thing"); @@ -130,6 +158,10 @@ public class Program Console.WriteLine("Registration process complete"); break; } + else + { + Console.WriteLine("Failed reigstreation: " + r); + } } } diff --git a/lib/Request.cs b/lib/Request.cs index 4df156b..8afcfc3 100644 --- a/lib/Request.cs +++ b/lib/Request.cs @@ -5,9 +5,10 @@ public enum RequestType Register = 1, ConfirmRegister = 2, Login = 3, - GetMessages = 4, - GetUserKey = 5, - SendMessage = 6, + ConfirmLogin = 4, + GetMessages = 5, + GetUserKey = 6, + SendMessage = 7, } public static class Request @@ -62,8 +63,11 @@ public static class Request break; case RequestType.Login: phone = Utils.BytesToNumber(Request[3..11]); - sigLen = BitConverter.ToInt16(Request, 11); - res += $"Phone: {phone}, signature length: {sigLen}"; + res += $"Phone: {phone}"; + break; + case RequestType.ConfirmLogin: + sigLen = BitConverter.ToInt16(Request, 3); + res += $"signature length: {sigLen}"; break; case RequestType.GetMessages: break; diff --git a/protocol.md b/protocol.md index b83933c..96123a7 100644 --- a/protocol.md +++ b/protocol.md @@ -52,8 +52,10 @@ to the server. data: Phone - 8 bytes, RSA key size (payload length) - 2 bytes - ConfirmRegister (signed & encrypted 6 digit code) data: 6 bytes for the 6 digit code (can use less but it will be padding otherwise) -- Login (signed hash): - data: 8 bytes of user's phone, signed SHA length - 2 bytes +- Login: + data: 8 bytes of user's phone +- ConfirmLogin (signed hash): + data: hash length - GetMessages: data: EMPTY - GetUserKey (dont think it needs any signing or encryption technically, as it is very diff --git a/server/Program.cs b/server/Program.cs index ff8225f..b652feb 100644 --- a/server/Program.cs +++ b/server/Program.cs @@ -7,6 +7,7 @@ using System.Net; using System.Net.Sockets; using System.Security.Cryptography; using System.Text; +using System.Text.Json.Serialization; using System.Threading.Tasks; using lib; using server; @@ -18,7 +19,7 @@ public class Program const int MSG_LEN = 16; // msg len is 128 bits = 16 bytes static readonly Data Data = new(); - static readonly Random Rnd = new((int)DateTime.Now.Ticks); + static readonly Random Rand = new((int)DateTime.Now.Ticks); static async Task Main() { @@ -110,12 +111,12 @@ public class Program Write(id, $"Imported key is: \n {pub.ExportRSAPublicKeyPem()}\n"); // generate the 6 digit code and send it byte[] code = [ - (byte)Rnd.Next(10), - (byte)Rnd.Next(10), - (byte)Rnd.Next(10), - (byte)Rnd.Next(10), - (byte)Rnd.Next(10), - (byte)Rnd.Next(10), + (byte)Rand.Next(10), + (byte)Rand.Next(10), + (byte)Rand.Next(10), + (byte)Rand.Next(10), + (byte)Rand.Next(10), + (byte)Rand.Next(10), ]; await Send6DigitCodeInSecureChannel(stream, code); // wait for the code to be back with a key @@ -170,7 +171,47 @@ public class Program else if (msg[1] == (byte)RequestType.Login) { // verify login - // TODO: Login + clientPhone = Utils.BytesToNumber(msg[3..11]); + 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"); + return; + } + byte[] challenge = new byte[16]; + Rand.NextBytes(challenge); + Write(id, $"Sending challenge: {Convert.ToBase64String(challenge)}"); + byte[] response = sk.EncryptCfb(challenge, sk.IV, PaddingMode.None); + await stream.WriteAsync(response); + len = await stream.ReadAsync(buffer); + msg = sk.DecryptCfb(buffer[..MSG_LEN], sk.IV, PaddingMode.None); + Write(id, Request.RequestToString(msg)); + if (msg[2] != counter) + { + client.Close(); + Write(id, $"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)}"); + bool valid = clientKey.VerifyData(challenge, sig, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + if (valid) + { + response = sk.EncryptCfb(Encoding.UTF8.GetBytes("OK"), sk.IV, PaddingMode.PKCS7); + await stream.WriteAsync(response); + } + else + { + Write(id, "Client failed verification, invalid signature"); + response = sk.EncryptCfb(Encoding.UTF8.GetBytes("INVALID SIG"), sk.IV, PaddingMode.PKCS7); + await stream.WriteAsync(response); + client.Close(); + return; + } } else {