Compare commits

...

2 commits

11 changed files with 289 additions and 47 deletions

26
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,26 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/client/bin/Debug/net8.0/client.dll",
"args": [],
"cwd": "${workspaceFolder}/client",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

41
.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/online_security_project.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/online_security_project.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/online_security_project.sln"
],
"problemMatcher": "$msCompile"
}
]
}

View file

@ -1,2 +1,50 @@
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace Client;
public class Program
{
static void Main(string[] args)
{
using TcpClient client = new("127.0.0.1", 12345);
byte[] toSend = Encoding.ASCII.GetBytes("hello server");
var stream = client.GetStream();
var inputTask = Task.Run(async () => await HandleUserInput(client, stream));
var serverInput = Task.Run(async () => await HandleServerInput(client, stream));
_ = Task.WaitAny(inputTask, serverInput);
}
static async Task HandleUserInput(TcpClient client, NetworkStream stream) {
while(client.Connected) {
string? input = Console.ReadLine();
if(input == null) {
await Task.Delay(100);
continue;
}
else {
stream.Write(Encoding.ASCII.GetBytes(input));
Console.WriteLine($"[{DateTime.Now}]Sent to server: {input}");
}
}
}
static async Task HandleServerInput(TcpClient client, NetworkStream stream) {
byte[] buffer = new byte[1024];
while(client.Connected) {
int readLen = await stream.ReadAsync(buffer);
if(readLen != 0) {
string fromServer = Encoding.ASCII.GetString(buffer[..readLen]);
Console.WriteLine($"[{DateTime.Now}]\t\tFrom server: {fromServer}");
}
}
}
}

View file

@ -7,7 +7,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

View file

@ -1,6 +0,0 @@
namespace lib;
public class Class1
{
}

10
lib/Request.cs Normal file
View file

@ -0,0 +1,10 @@
namespace lib;
public enum RequestType {
Register = 1,
ConfirmRegister = 2,
GetMessages = 3,
GetUserKey = 4,
SendMessage = 5,
SendAck = 6
}

53
lib/Utils.cs Normal file
View file

@ -0,0 +1,53 @@
using System.Text;
using System.Linq;
namespace lib;
public static class Utils {
public static byte[] NumberToBytes(string Number) {
if(Number.Any(c => !char.IsDigit(c)) || Number.Length > 16) {
throw new Exception("Invalid arguments!");
}
byte[] res = Enumerable.Repeat((byte)0b1111_1111, 8).ToArray();
// Pad Number if needed to be of even length (because each 2 digits are turned into 1 byte)
Number = Number.Length % 2 == 0 ? Number : Number + '-';
for(int i = 0; i < Number.Length - 1; i += 2) {
char c1 = Number[i];
char c2 = Number[i + 1];
res[i / 2] = (byte)((DigitToByte(c1) << 4) | DigitToByte(c2));
}
return res;
}
public static string BytesToNumber(byte[] Bytes) {
string s = "";
foreach(byte b in Bytes) {
byte b1 = (byte)((b >> 4) & 0b1111);
byte b2 = (byte)(b & 0b1111);
s = s + ByteToDigit(b1) + ByteToDigit(b2);
}
return new string(s.Where(c => char.IsDigit(c)).ToArray());
}
public static byte DigitToByte(char c) {
if (int.TryParse(c.ToString(), out int d)) {
return (byte)d;
}
else {
return 0b1111; // empty, turned into '-' later to be discarded
}
}
public static char ByteToDigit(byte b) {
byte offset = Encoding.ASCII.GetBytes("0")[0];
if(b == 0b1111) {
return '-';
}
else {
return Encoding.ASCII.GetChars([(byte)(b + offset)])[0];
}
}
}

View file

@ -5,6 +5,10 @@ VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "client", "client\client.csproj", "{F38E98E8-4FE8-4F34-B5A7-004444ED1F50}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "server", "server\server.csproj", "{36C3F9FE-FA02-4AC0-89A2-65B377767C8F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "lib", "lib\lib.csproj", "{8E625330-2219-4E74-8524-A947D783B3BB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -18,5 +22,13 @@ Global
{F38E98E8-4FE8-4F34-B5A7-004444ED1F50}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F38E98E8-4FE8-4F34-B5A7-004444ED1F50}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F38E98E8-4FE8-4F34-B5A7-004444ED1F50}.Release|Any CPU.Build.0 = Release|Any CPU
{36C3F9FE-FA02-4AC0-89A2-65B377767C8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{36C3F9FE-FA02-4AC0-89A2-65B377767C8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{36C3F9FE-FA02-4AC0-89A2-65B377767C8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{36C3F9FE-FA02-4AC0-89A2-65B377767C8F}.Release|Any CPU.Build.0 = Release|Any CPU
{8E625330-2219-4E74-8524-A947D783B3BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8E625330-2219-4E74-8524-A947D783B3BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8E625330-2219-4E74-8524-A947D783B3BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8E625330-2219-4E74-8524-A947D783B3BB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View file

@ -3,34 +3,29 @@
All encryptions are made using RSA (key size to be determined), no symmetric encryptions
are used due to messages being short.
<!-- Maybe yse RSA-256 (or the closest)? if i can assume it is unbreakable that is -->
## Some problems
a. Encryption will be a-symmetric because we can assume messages are short
b. See "Registration" below
c. Server keeps public keys for everyone, and gives them to a user when they want to
send a message to someone (only the relevant key)
d. Every e2e message is both signed and encrypted aka, message M from A to B
`EM = E_Bpub(E_Apriv(N))`
e. message is sent to the server in a `E_server(E_Apriv(metadata + EM from d))`,
message ack is sent to the server as a `MessageAck(msg_id)`, which will be retrieved
by the user when it pulls for messages.
f. TODO: properly explain the different api structs
g. TODO: properly explain how the server handles data
Do i need hasing? dont think it will help too much, can just sign-encrypt i think.
### Key Derivation Function
When asking for messages, maybe do a little back and forth with the server to make sure
the request comes from the correct person?
Maybe keep track of the last message ID every time? then add it to the request,
not good, as if there were no new messages an attacker can now use said req.
Maybe the current timestamp? then the server keeps track of the last request with
timestamp for a given user? kinda acts like a better counter i think.
I dont think someone can abuse this system.
I think adding this to every request is neccessary to make sure no duplicates are
being sent, ie. if an attacker listens on the channel and sees a package sent to
the server, it can repeat it and cause mischief (maybe send duplicate messages,
or flush the user's messages or something).
an attacker can also abuse duplicates to see who a user is talking to,
simply send the request and encrypt all public keys (gotten in a legitimate way)
using the user's public key (as the server would encrypt) and compare the result
from the server
as there is no use of symmetric keys (at least for this scope), I dont believe there
is a need to use KDF, it will reduce the keyspace and we already have the public key
of the server in the clients
## Registration:
- User sends a register request to server `(phone number, RSA public key)`,
giving them a public key and encrypting using the server's public key
- Server sends the user a `Confirm` message & a 6-digit code
- Server sends the user a `VerificationRequired` message & a 6-digit code (Secure channel)
- User sends the server the 6-digit code, signed using the key provided at stage 1
(this message is very short and funny, because its a 6-digit code, signed using the
user's private key, and encrypted using the server's public key)
@ -51,19 +46,40 @@ request and ultimately signed by A and encrypted using the server's key.
The server will hold on to the message until B will send a `GetMessages` request
to the server.
## Requests:
## "Control" requests
legend:
- `E(t)` encrypt t
- `S(t)` sign t
- `ES(t)` signed and encrypted (in that order!)
- `HS(t)`/`HS t` hash and sign t
All requests are to be encrypted using the server's public key, have the sender phone
and have a timestamp on them, except when otherwise noted
- Register `(phone, pub RSA key)`
extra data: RSA key size (payload length)
- ConfirmRegister (signed & encrypted 6 digit code)
extra data: 6 bytes for the 6 digit code (can use less but it will be padding otherwise)
- GetMessages (signed & encrypted to not allow someone to "flush" someone else's msgs)
extra data: EMPTY
- GetUserKey (dont think it needs any signing or encryption technically, as it is very
simple registering and "stealing" a key outside the system, keys are assumed to be
unbreakable)
extra data: 8 bytes (4 bits per digit) of whoever we want to get the key of
- SendMessage `(to_user, msg_id, EM from d above)`
extra data: 8 bytes (4 bits per digit) of who to send the data, 4 bytes (32bit) for length
- SendAck `(to_user, msg_id - signed and encrypted)`
- server doesnt know if the msg itself is indeed valid and not tempered with
extra data: 8 bytes (4 bits per digit) of who to send the data, 4 bytes (32bit) for length
1. `Register(public key)`, no timestamp
2. `HS RegisterConfirm(6-digit code)`
3. `GetMessages`
4. `GetUserKey(phone - user to get key for)`
5. `SendMessage(phone - receiver, ES({ message_id, message }))`
6. `SendMessageACK(phone - ack receiver, ES({ message_id[], ACK/NACK }))`
I think it all can go into a:
```
{
Version byte (0) - 1 byte
RequestType - 1 byte,
Phone number - 8 bytes (every 4 bits is a number, so we have 16 numbers),
UTC timestamp - 8 bytes (long),
extra data (based on the request given) - up to 18 bytes
} = 32 bytes = 256 bits
```
and we can just append encrypted payloads to it (in SendMessage and SendAck)
To each message we also append a sha3-256 signed hash
enc_server( request - 256 bits, signed sha3-256 )
which means:
Keys: RSA-512
Hashes: SHA3-256

View file

@ -1,2 +1,44 @@
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
using System;
using System.Net;
using System.Net.Sockets;
namespace Server;
public class Program
{
static void Main(string[] args)
{
int port = 12345;
TcpListener server = new(IPAddress.Parse("0.0.0.0"), port);
try {
server.Start();
byte[] buffer = new byte[256];
while(true) {
// Currently, every time it gets a block, it will simply send it back but ToUpper
using TcpClient client = server.AcceptTcpClient();
Console.WriteLine("Got a client!");
var stream = client.GetStream();
int readLen;
while((readLen = stream.Read(buffer, 0, buffer.Length)) != 0) {
// for now, lets just read it as an ascii string
string input = System.Text.Encoding.ASCII.GetString(buffer, 0, readLen);
Console.WriteLine($"Got block: {input}");
byte[] ret = System.Text.Encoding.ASCII.GetBytes(input.ToUpper());
stream.Write(ret, 0, ret.Length);
}
}
}
catch(Exception ex) {
Console.WriteLine($"Server error: {ex.Message}");
Console.WriteLine("Trace: " + ex.StackTrace);
}
finally {
server.Stop();
}
}
}

View file

@ -7,7 +7,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>