diff --git a/protocol.md b/protocol.md
index 7d36327..5892067 100644
--- a/protocol.md
+++ b/protocol.md
@@ -322,13 +322,32 @@ Communication between the master server and clients will be done over a WebSocke
1 |
- Succeeded |
- Boolean |
+ Server Count (n) |
+ Packed Unsigned Short |
- 2 |
- Message |
- String |
+ r > 1 |
+ Iterated over n (0 ≤ i ≤ n - 1) |
+
+
+ 2 + 4i |
+ Server Id |
+ Packed Unsigned Short |
+
+
+ 3 + 4i |
+ User Count |
+ Packed Unsigned Short |
+
+
+ 4 + 4i |
+ IP Address |
+ IPv4 String |
+
+
+ 5 + 4i |
+ Port |
+ Packed Unsigned Short |
diff --git a/server/Configuration.cs b/server/Configuration.cs
index b6ee8b2..d77831e 100644
--- a/server/Configuration.cs
+++ b/server/Configuration.cs
@@ -21,6 +21,7 @@ namespace SockScape {
"Master Port",
"Master Addr",
"Master Secret",
+ "Master IV",
"Max Users"
}
},
diff --git a/server/Encryption/BlockCipher.cs b/server/Encryption/BlockCipher.cs
new file mode 100644
index 0000000..1efe02a
--- /dev/null
+++ b/server/Encryption/BlockCipher.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.IO;
+using System.Linq;
+using System.Numerics;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+using Glove;
+
+namespace SockScape.Encryption {
+ class BlockCipher {
+ public const int KeySize = 192;
+ public const int KeySizeBytes = KeySize / 8;
+
+ private readonly byte[] Key = new byte[KeySizeBytes];
+ private readonly byte[] IV;
+
+ public BlockCipher(BigInteger key) {
+ Key = Key.Select(x => (byte)0).ToArray();
+ Array.Copy(key.ToByteArray(), Key, Key.Length);
+
+ IV = Configuration.General["Master IV"].Str.HexStringToBytes()
+ ?? new byte[] { 0x0b, 0xfc, 0xd7, 0x2d, 0x23, 0xb7, 0x83, 0xb2 };
+ }
+
+ public byte[] Encrypt(byte[] data) {
+ try {
+ var ms = new MemoryStream();
+ var cs = new CryptoStream(ms,
+ new TripleDESCryptoServiceProvider().CreateEncryptor(Key, IV),
+ CryptoStreamMode.Write);
+
+ cs.Write(data, 0, data.Length);
+ cs.FlushFinalBlock();
+
+ byte[] ret = ms.ToArray();
+
+ cs.Close();
+ ms.Close();
+
+ return ret;
+ } catch(Exception e) {
+ Console.WriteLine($"TDES ENCRYPT ERROR: {e.Message}");
+ return null;
+ }
+ }
+
+ public byte[] Decrypt(byte[] data) {
+ try {
+ var ms = new MemoryStream(data);
+ var cs = new CryptoStream(ms,
+ new TripleDESCryptoServiceProvider().CreateEncryptor(Key, IV),
+ CryptoStreamMode.Read);
+
+ byte[] ret = new byte[data.Length];
+ cs.Read(ret, 0, data.Length);
+
+ cs.Close();
+ ms.Close();
+
+ return ret;
+ } catch(Exception e) {
+ Console.WriteLine($"TDES DECRYPT ERROR: {e.Message}");
+ return null;
+ }
+ }
+ }
+}
diff --git a/server/Encryption/KeyExchange.cs b/server/Encryption/KeyExchange.cs
index 268da0c..5d02413 100644
--- a/server/Encryption/KeyExchange.cs
+++ b/server/Encryption/KeyExchange.cs
@@ -6,10 +6,14 @@ using System.Threading.Tasks;
using System.Numerics;
using Glove;
using System.Globalization;
+using System.Security.Cryptography;
namespace SockScape.Encryption {
class Key {
- private static readonly BigInteger Secret = RNG.NextPrime(512 / 8);
+ public const int KeySize = 512;
+ public const int KeySizeBytes = KeySize / 8;
+
+ private static readonly BigInteger Secret = RNG.NextPrime(KeySizeBytes);
public BigInteger Generator { get; } = 2;
public BigInteger Modulus { get; }
public BigInteger PrivateKey { get; private set; } = BigInteger.Zero;
@@ -17,7 +21,7 @@ namespace SockScape.Encryption {
=> !PrivateKey.IsZero;
public Key() {
- Modulus = RNG.NextPrime(512 / 8);
+ Modulus = RNG.NextPrime(KeySizeBytes);
}
public Packet GenerateRequestPacket() {
diff --git a/server/Encryption/Cipher.cs b/server/Encryption/StreamCipher.cs
similarity index 90%
rename from server/Encryption/Cipher.cs
rename to server/Encryption/StreamCipher.cs
index d94d199..8c6a0c2 100644
--- a/server/Encryption/Cipher.cs
+++ b/server/Encryption/StreamCipher.cs
@@ -7,11 +7,11 @@ using System.Numerics;
using Glove;
namespace SockScape.Encryption {
- class Cipher {
- private readonly byte[] Key = new byte[512 / 8];
+ class StreamCipher {
+ private readonly byte[] Key = new byte[Encryption.Key.KeySizeBytes];
private readonly byte[] State = new byte[256];
- public Cipher(BigInteger key) {
+ public StreamCipher(BigInteger key) {
int i = 0, j = 0;
State = State.Select(x => (byte)i++).ToArray();
Key = Key.Select(x => (byte)0).ToArray();
diff --git a/server/Entrypoint.cs b/server/Entrypoint.cs
index 559861b..3ac68a4 100644
--- a/server/Entrypoint.cs
+++ b/server/Entrypoint.cs
@@ -14,10 +14,20 @@ using MySql.Data.Entity;
namespace SockScape {
static class ServerContext {
- public static Dictionary> Servers { get; }
- = new Dictionary>();
- public static Dictionary> Pools { get; }
- = new Dictionary>();
+ public static Dictionary> Servers { get; }
+ = new Dictionary>();
+ public static Dictionary> Pools { get; }
+ = new Dictionary>();
+
+ public static Packet StatusUpdatePacket {
+ get {
+ var packet = new Packet(kIntraSlaveId.StatusUpdate, (byte)Servers.Count);
+ foreach(var pool in Pools)
+ packet.AddRegions(pool.Key.Pack(), ((ushort)pool.Value.ConnectionCount).Pack(), Servers[pool.Key].Port.Pack());
+
+ return packet;
+ }
+ }
}
class Entrypoint {
@@ -33,8 +43,8 @@ namespace SockScape {
var serverHandle = new Server((ushort)server["Port"], pool, server);
- ServerContext.Pools.Add(server["Id"], pool);
- ServerContext.Servers.Add(server["Id"], serverHandle);
+ ServerContext.Pools.Add((ushort)server["Id"], pool);
+ ServerContext.Servers.Add((ushort)server["Id"], serverHandle);
serverHandle.Start();
}
diff --git a/server/Libraries/Glove/INI/Value.cs b/server/Libraries/Glove/INI/Value.cs
index d820785..5aaf319 100644
--- a/server/Libraries/Glove/INI/Value.cs
+++ b/server/Libraries/Glove/INI/Value.cs
@@ -12,24 +12,29 @@ namespace Glove.INI {
Raw = raw;
}
- public static implicit operator string(Value value) {
- return value.Raw;
- }
+ public string Str
+ => this;
- public static implicit operator bool(Value value) {
- return Boolean.TryParse(value.Raw, out bool retval) && retval;
- }
+ public Int32 Int
+ => this;
- public static implicit operator Int32(Value value) {
- return Int32.TryParse(value.Raw, out Int32 retval)
+ public double Dbl
+ => this;
+
+ public static implicit operator string(Value value)
+ => value.Raw;
+
+ public static implicit operator bool(Value value)
+ => Boolean.TryParse(value.Raw, out bool retval) && retval;
+
+ public static implicit operator Int32(Value value)
+ => Int32.TryParse(value.Raw, out Int32 retval)
? retval
: 0;
- }
- public static implicit operator double(Value value) {
- return Double.TryParse(value.Raw, out double retval)
+ public static implicit operator double(Value value)
+ => Double.TryParse(value.Raw, out double retval)
? retval
: 0;
- }
}
}
diff --git a/server/Libraries/Glove/NumericExtensions.cs b/server/Libraries/Glove/NumericExtensions.cs
index 177edd9..d6a6845 100644
--- a/server/Libraries/Glove/NumericExtensions.cs
+++ b/server/Libraries/Glove/NumericExtensions.cs
@@ -60,5 +60,23 @@ namespace Glove {
public static BigInteger HexStringToBigInt(this string value)
=> BigInteger.Parse(value, NumberStyles.HexNumber);
+
+ public static byte[] HexStringToBytes(this string value) {
+ if(value.Length % 2 == 1)
+ return null;
+
+ var bytes = new byte[value.Length / 2];
+ for(var i = 0; i < bytes.Length; ++i) {
+ char upperNibble = value[2 * i],
+ lowerNibble = value[2 * i + 1];
+
+ if(!upperNibble.IsHex() || !lowerNibble.IsHex())
+ return null;
+
+ bytes[i] = (byte)((upperNibble.HexValue() << 4) | lowerNibble.HexValue());
+ }
+
+ return bytes;
+ }
}
}
diff --git a/server/Libraries/Glove/StringExtensions.cs b/server/Libraries/Glove/StringExtensions.cs
index 29bb093..228d4de 100644
--- a/server/Libraries/Glove/StringExtensions.cs
+++ b/server/Libraries/Glove/StringExtensions.cs
@@ -5,6 +5,18 @@ using System.Text;
using System.Threading.Tasks;
namespace Glove {
+ public static class CharExtensions {
+ public static bool IsHex(this char c) {
+ return (c >= '0' && c <= '9')
+ || (c >= 'A' && c <= 'F')
+ || (c >= 'a' && c <= 'f');
+ }
+
+ public static byte HexValue(this char c) {
+ return (byte)(!c.IsHex() ? 0 : c - (c < 0x3A ? 0x30 : (c < 0x61 ? 0x37 : 0x57)));
+ }
+ }
+
public static class StringExtensions {
public static byte[] GetBytes(this string str, bool isUtf8 = true)
=> isUtf8 ? Encoding.UTF8.GetBytes(str)
diff --git a/server/Libraries/Kneesocks/Handshake.cs b/server/Libraries/Kneesocks/Handshake.cs
index 8e634a2..4e5d343 100644
--- a/server/Libraries/Kneesocks/Handshake.cs
+++ b/server/Libraries/Kneesocks/Handshake.cs
@@ -29,7 +29,7 @@ namespace Kneesocks {
Service_Unavailable = 503,
Gateway_Timeout = 504
}
- public kStatusCode? StatusCode { get; private set; } = null;
+ public kStatusCode? StatusCode { get; } = null;
protected string StatusCodeText
=> Enum.GetName(typeof(kStatusCode), StatusCode).Replace('_', ' ');
diff --git a/server/MasterServerList.cs b/server/MasterServerList.cs
new file mode 100644
index 0000000..cd3980e
--- /dev/null
+++ b/server/MasterServerList.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using Glove;
+
+namespace SockScape {
+ static class MasterServerList {
+ public static Dictionary Servers { get; }
+ = new Dictionary();
+
+ public static void Write(Server server) {
+ lock(Servers) {
+ if(HasId(server.Id) && !Servers[server.Id].Address.Equals(server.Address))
+ Console.WriteLine($"{DateTime.Now.ToShortTimeString()} - Server {server.Id} has changed IP addresses.");
+
+ Servers[server.Id] = server;
+ }
+ }
+
+ public static Packet ReportPacket {
+ get {
+ lock(Servers) {
+ var packet = new Packet(kInterMasterId.ServerListing, ((ushort)Servers.Count).Pack());
+ foreach(var server in Servers)
+ // TODO change this to support IPv6
+ packet.AddRegions(server.Key.Pack(), server.Value.UserCount.Pack(),
+ server.Value.Address.MapToIPv4().ToString(), server.Value.Port.Pack());
+
+ return packet;
+ }
+ }
+ }
+
+ public static bool HasId(UInt16 id)
+ => Servers.ContainsKey(id);
+
+ public static void Clear()
+ => Servers.Clear();
+ }
+
+ class Server {
+ public ushort Id { get; set; }
+ public ushort UserCount { get; set; }
+ public IPAddress Address { get; set; }
+ public ushort Port { get; set; }
+ }
+}
diff --git a/server/ServerList.cs b/server/ServerList.cs
deleted file mode 100644
index 0665bb1..0000000
--- a/server/ServerList.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SockScape {
- static class ServerList {
- private static ServerDictionary Servers { get; }
- = new ServerDictionary();
-
- private static Dictionary List
- => Servers.List;
-
- public static void Write(Server server) {
- lock(Servers) {
- if(HasId(server.Id) && !List[server.Id].Address.Equals(server.Address))
- Console.WriteLine($"{DateTime.Now.ToShortTimeString()} - Server {server.Id} has changed IP addresses.");
-
- List[server.Id] = server;
- }
- }
-
- public static Packet ReportPacket {
- get {
- var packet = new Packet(kInterMasterId.);
-
- return packet;
- }
- }
-
- public static bool HasId(UInt16 id)
- => List.ContainsKey(id);
-
- public static void Clear()
- => List.Clear();
-
- public class ServerDictionary {
- public Dictionary List { get; }
- = new Dictionary();
-
- public Server this[UInt16 i] {
- get => List.ContainsKey(i) ? List[i] : null;
- set => List[i] = value;
- }
- }
- }
-
- class Server {
- public ushort Id { get; set; }
- public ushort UserCount { get; set; }
- public IPAddress Address { get; set; }
- public ushort Port { get; set; }
- }
-}
diff --git a/server/SockScape.csproj b/server/SockScape.csproj
index ab9fea1..a6bbe1a 100644
--- a/server/SockScape.csproj
+++ b/server/SockScape.csproj
@@ -94,17 +94,18 @@
+
-
+
201708251600325_Initial.cs
-
+
-
-
+
+
diff --git a/server/Socks/MasterConnection.cs b/server/Socks/MasterConnection.cs
index fb1d9bc..3453e80 100644
--- a/server/Socks/MasterConnection.cs
+++ b/server/Socks/MasterConnection.cs
@@ -10,7 +10,7 @@ using SockScape.Encryption;
namespace SockScape {
class MasterConnection : Connection {
private Key Key;
- public Cipher Encryptor { get; private set; }
+ public StreamCipher Encryptor { get; private set; }
protected override void OnOpen() {
Key = new Key();
@@ -39,7 +39,7 @@ namespace SockScape {
return;
}
- Encryptor = new Cipher(Key.PrivateKey);
+ Encryptor = new StreamCipher(Key.PrivateKey);
break;
case kInterMasterId.LoginAttempt:
diff --git a/server/Socks/MasterUdpClient.cs b/server/Socks/MasterIntraClient.cs
similarity index 85%
rename from server/Socks/MasterUdpClient.cs
rename to server/Socks/MasterIntraClient.cs
index 3e60a89..1e97c62 100644
--- a/server/Socks/MasterUdpClient.cs
+++ b/server/Socks/MasterIntraClient.cs
@@ -10,9 +10,8 @@ using Glove;
using SockScape.Encryption;
namespace SockScape {
- static class MasterUdpClient {
+ static class MasterIntraClient {
private static Key Key;
- private static Cipher Encryptor;
private static UdpClient Sock;
private static Thread ListeningThread;
@@ -55,10 +54,9 @@ namespace SockScape {
switch((kIntraMasterId)packet.Id) {
case kIntraMasterId.KeyExchange:
var responsePacket = Key.ParseRequestPacket(packet);
- Encryptor = new Cipher(Key.PrivateKey);
+ Encryptor = new StreamCipher(Key.PrivateKey);
if(responsePacket != null)
Send(responsePacket);
-
else
LastMessageIn = new DateTime(0);
break;
@@ -72,6 +70,8 @@ namespace SockScape {
break;
case kIntraMasterId.EncryptionError:
+ NextSendId = NextRecvId = 0;
+ Buffer.Clear();
Key = new Key();
Encryptor = null;
LastMessageIn = new DateTime(0);
@@ -79,9 +79,9 @@ namespace SockScape {
}
}
- if (LastMessageIn.Ticks != 0) {
+ if(LastMessageIn.Ticks != 0) {
if(DeltaLastOut.TotalSeconds > 2)
- Send(new Packet());
+ Send(Encryptor.Parse(ServerContext.StatusUpdatePacket.GetBytes()));
} else
if(DeltaLastOut.TotalSeconds > 10)
Send(new Packet(kIntraSlaveId.InitiationAttempt, Configuration.General["Master Secret"]));
@@ -91,9 +91,14 @@ namespace SockScape {
}
public static void Send(Packet packet) {
- var message = packet.GetBytes();
- Sock.Send(message, message.Length);
+ Send(packet.GetBytes());
+ }
+
+ public static void Send(byte[] bytes) {
+ Sock.Send(bytes, bytes.Length);
LastMessageOut = DateTime.Now;
+ Buffer.Add(NextSendId, bytes);
+ ++NextSendId;
}
public static void Close() {
diff --git a/server/Socks/MasterUdpServer.cs b/server/Socks/MasterIntraServer.cs
similarity index 79%
rename from server/Socks/MasterUdpServer.cs
rename to server/Socks/MasterIntraServer.cs
index 73fb09c..ee780f1 100644
--- a/server/Socks/MasterUdpServer.cs
+++ b/server/Socks/MasterIntraServer.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
+using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -23,7 +24,7 @@ namespace SockScape {
if(IsOpen || ListeningThread != null)
return;
- ServerList.Clear();
+ MasterServerList.Clear();
Prospects = new Dictionary();
Clients = new Dictionary();
@@ -49,8 +50,11 @@ namespace SockScape {
encryptor == null ? Packet.FromBytes(data)
: Packet.FromBytes(encryptor.Parse(data));
- if(packet == null)
+ if(packet == null) {
+ if(encryptor != null)
+ EncryptionError(endPoint);
continue;
+ }
Clients[client].LastReceive = DateTime.Now;
switch((kIntraSlaveId)packet.Id) {
@@ -76,7 +80,8 @@ namespace SockScape {
var privateKey = Prospects[client].Key.ParseResponsePacket(packet);
if(privateKey != -1) {
- Prospects[client].Encryptor = new Cipher(privateKey);
+ Prospects[client].LastReceive = DateTime.Now;
+ Prospects[client].Encryptor = new StreamCipher(privateKey);
Clients[client] = Prospects[client];
Prospects.Remove(client);
} else
@@ -88,13 +93,13 @@ namespace SockScape {
break;
if(packet.CheckRegions(0, 1)) {
- NegativeAck(endPoint, kIntraSlaveId.StatusUpdate, "Server count is malformed.");
+ NegativeAck(endPoint, encryptor, kIntraSlaveId.StatusUpdate, "Server count is malformed.");
break;
}
byte serverCount = packet[0].Raw[0];
if(packet.RegionCount != 1 + 3 * serverCount) {
- NegativeAck(endPoint, kIntraSlaveId.StatusUpdate, "Region count does not match server count");
+ NegativeAck(endPoint, encryptor, kIntraSlaveId.StatusUpdate, "Region count does not match server count");
break;
}
@@ -102,7 +107,7 @@ namespace SockScape {
if(!packet.CheckRegions(2 + 3 * i, 2, 2, 2))
continue;
- ServerList.Write(new Server {
+ MasterServerList.Write(new Server {
Id = packet[2 + 3 * i].Raw.UnpackUInt16(),
UserCount = packet[3 + 3 * i].Raw.UnpackUInt16(),
Address = endPoint.Address,
@@ -110,7 +115,7 @@ namespace SockScape {
});
}
- PositiveAck(endPoint, kIntraSlaveId.StatusUpdate);
+ PositiveAck(endPoint, encryptor, kIntraSlaveId.StatusUpdate);
break;
}
}
@@ -122,8 +127,11 @@ namespace SockScape {
}
private static void Send(Packet packet, IPEndPoint client) {
- var message = packet.GetBytes();
- Sock.Send(message, message.Length, client);
+ Send(packet.GetBytes(), client);
+ }
+
+ private static void Send(byte[] bytes, IPEndPoint client) {
+ Sock.Send(bytes, bytes.Length, client);
}
public static void Close() {
@@ -141,12 +149,12 @@ namespace SockScape {
return Clients.ContainsKey(client);
}
- private static void PositiveAck(IPEndPoint endPoint, kIntraSlaveId id) {
- Send(new Packet(kIntraMasterId.PositiveAck, id), endPoint);
+ private static void PositiveAck(IPEndPoint endPoint, StreamCipher cipher, kIntraSlaveId id) {
+ Send(cipher.Parse(new Packet(kIntraMasterId.PositiveAck, id).GetBytes()), endPoint);
}
- private static void NegativeAck(IPEndPoint endPoint, kIntraSlaveId id, string message = "An error occurred while parsing a packet.") {
- Send(new Packet(kIntraMasterId.NegativeAck, id, message), endPoint);
+ private static void NegativeAck(IPEndPoint endPoint, StreamCipher cipher, kIntraSlaveId id, string message = "An error occurred while parsing a packet.") {
+ Send(cipher.Parse(new Packet(kIntraMasterId.NegativeAck, id, message).GetBytes()), endPoint);
}
private static void EncryptionError(IPEndPoint endPoint, string message = "A general encryption error has occurred. Renegotiation required.") {
@@ -157,7 +165,7 @@ namespace SockScape {
public IPEndPoint Address { get; set; }
public DateTime LastReceive { get; set; }
public TimeSpan ReceiveDelta => DateTime.Now - LastReceive;
- public Cipher Encryptor { get; set; }
+ public StreamCipher Encryptor { get; set; }
public Key Key { get; set; }
}
}
diff --git a/server/Socks/Protocols/Packet.cs b/server/Socks/Protocols/Packet.cs
index a820d44..296cd2a 100644
--- a/server/Socks/Protocols/Packet.cs
+++ b/server/Socks/Protocols/Packet.cs
@@ -92,6 +92,13 @@ namespace SockScape {
return this;
}
+ public Packet AddRegions(params object[] regions) {
+ foreach(var region in regions)
+ AddRegion(region);
+
+ return this;
+ }
+
public bool CheckRegions(int startIndex, params int[] lengths) {
if(startIndex + lengths.Length > RegionCount)
return false;
@@ -150,4 +157,32 @@ namespace SockScape {
=> this;
}
}
+
+ public class PacketBuffer {
+ private short MaxSize;
+ private Dictionary Buffer
+ = new Dictionary();
+
+ public PacketBuffer(short maxSize = 10) {
+ MaxSize = maxSize;
+ }
+
+ public void Add(uint id, byte[] packet) {
+ Buffer[id] = packet;
+
+ if(Buffer.Count > 10)
+ Buffer =
+ Buffer.Where(x => x.Key >= id - 10)
+ .ToDictionary(x => x.Key, x => x.Value);
+ }
+
+ public byte[] this[uint i]
+ => Buffer[i];
+
+ public bool HasId(uint id)
+ => Buffer.ContainsKey(id);
+
+ public void Clear()
+ => Buffer.Clear();
+ }
}