stream ciphers do not work with udp FOOL

goon
This commit is contained in:
Malloc of Kuzkycyziklistan 2017-09-07 16:00:03 -05:00
parent ccc7ea5b25
commit b49acd08d4
17 changed files with 295 additions and 113 deletions

View file

@ -322,13 +322,32 @@ Communication between the master server and clients will be done over a WebSocke
</thead>
<tr>
<td class="center">1</td>
<td>Succeeded</td>
<td>Boolean</td>
<td>Server Count (<i>n</i>)</td>
<td>Packed Unsigned Short</td>
</tr>
<tr>
<td class="center">2</td>
<td>Message</td>
<td>String</td>
<td><i>r</i> > 1</td>
<td colspan="2">Iterated over <i>n</i> (0 &leq; <i>i</i> &leq; <i>n - 1</i>)</td>
</tr>
<tr>
<td class="center">2 + 4<i>i</i></td>
<td>Server Id</td>
<td>Packed Unsigned Short</td>
</tr>
<tr>
<td class="center">3 + 4<i>i</i></td>
<td>User Count</td>
<td>Packed Unsigned Short</td>
</tr>
<tr>
<td class="center">4 + 4<i>i</i></td>
<td>IP Address</td>
<td>IPv4 String</td>
</tr>
<tr>
<td class="center">5 + 4<i>i</i></td>
<td>Port</td>
<td>Packed Unsigned Short</td>
</tr>
</table>

View file

@ -21,6 +21,7 @@ namespace SockScape {
"Master Port",
"Master Addr",
"Master Secret",
"Master IV",
"Max Users"
}
},

View file

@ -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;
}
}
}
}

View file

@ -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() {

View file

@ -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();

View file

@ -14,10 +14,20 @@ using MySql.Data.Entity;
namespace SockScape {
static class ServerContext {
public static Dictionary<int, Server<PlayerConnection>> Servers { get; }
= new Dictionary<int, Server<PlayerConnection>>();
public static Dictionary<int, Pool<PlayerConnection>> Pools { get; }
= new Dictionary<int, Pool<PlayerConnection>>();
public static Dictionary<ushort, Server<PlayerConnection>> Servers { get; }
= new Dictionary<ushort, Server<PlayerConnection>>();
public static Dictionary<ushort, Pool<PlayerConnection>> Pools { get; }
= new Dictionary<ushort, Pool<PlayerConnection>>();
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<PlayerConnection>((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();
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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)

View file

@ -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('_', ' ');

View file

@ -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<UInt16, Server> Servers { get; }
= new Dictionary<UInt16, Server>();
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; }
}
}

View file

@ -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<UInt16, Server> 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<UInt16, Server> List { get; }
= new Dictionary<UInt16, Server>();
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; }
}
}

View file

@ -94,17 +94,18 @@
<Compile Include="DAL\ScapeDb.cs" />
<Compile Include="DAL\Session.cs" />
<Compile Include="DAL\User.cs" />
<Compile Include="Encryption\BlockCipher.cs" />
<Compile Include="Encryption\KeyExchange.cs" />
<Compile Include="Encryption\Cipher.cs" />
<Compile Include="Encryption\StreamCipher.cs" />
<Compile Include="Migrations\201708251600325_Initial.cs" />
<Compile Include="Migrations\201708251600325_Initial.Designer.cs">
<DependentUpon>201708251600325_Initial.cs</DependentUpon>
</Compile>
<Compile Include="Migrations\Configuration.cs" />
<Compile Include="ServerList.cs" />
<Compile Include="MasterServerList.cs" />
<Compile Include="Socks\MasterConnection.cs" />
<Compile Include="Socks\MasterUdpClient.cs" />
<Compile Include="Socks\MasterUdpServer.cs" />
<Compile Include="Socks\MasterIntraClient.cs" />
<Compile Include="Socks\MasterIntraServer.cs" />
<Compile Include="Socks\Protocols\ClientServerIds.cs" />
<Compile Include="Socks\Protocols\InterMasterIds.cs" />
<Compile Include="Socks\Protocols\IntraMasterIds.cs" />

View file

@ -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:

View file

@ -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() {

View file

@ -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<string, Client>();
Clients = new Dictionary<string, Client>();
@ -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; }
}
}

View file

@ -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<uint, byte[]> Buffer
= new Dictionary<uint, byte[]>();
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();
}
}