Security implementation

This commit is contained in:
Sardelka
2022-06-24 10:33:36 +08:00
parent 82e7cdd785
commit 82ab9237f5
16 changed files with 486 additions and 354 deletions

View File

@ -6,8 +6,10 @@ using Lidgren.Network;
using RageCoop.Core;
using System.Threading.Tasks;
using System.Threading;
using System.Security.Cryptography;
using GTA.Math;
using GTA.Native;
using System.Net;
namespace RageCoop.Client
{
@ -16,9 +18,10 @@ namespace RageCoop.Client
public static NetClient Client;
public static float Latency = 0;
public static bool ShowNetworkInfo = false;
public static Security Security;
static Networking()
{
Security=new Security(Main.Logger);
Task.Run(() =>
{
while (true)
@ -36,7 +39,7 @@ namespace RageCoop.Client
});
}
public static void ToggleConnection(string address)
public static void ToggleConnection(string address,string password=null)
{
if (IsOnServer)
{
@ -44,6 +47,7 @@ namespace RageCoop.Client
}
else
{
password = password ?? Main.Settings.Password;
// 623c92c287cc392406e7aaaac1c0f3b0 = RAGECOOP
NetPeerConfiguration config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0")
{
@ -51,6 +55,7 @@ namespace RageCoop.Client
};
config.EnableMessageType(NetIncomingMessageType.ConnectionLatencyUpdated);
config.EnableMessageType(NetIncomingMessageType.UnconnectedData);
Client = new NetClient(config);
Client.Start();
@ -70,17 +75,27 @@ namespace RageCoop.Client
}
EntityPool.AddPlayer();
// Send HandshakePacket
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
new Packets.Handshake()
Task.Run(() =>
{
PedID = Main.LocalPlayerID,
Username = Main.Settings.Username,
ModVersion = Main.CurrentVersion,
}.Pack(outgoingMessage);
GetServerPublicKey(address);
Client.Connect(ip[0], short.Parse(ip[1]), outgoingMessage);
// Send HandshakePacket
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
var handshake=new Packets.Handshake()
{
PedID = Main.LocalPlayerID,
Username = Main.Settings.Username,
ModVersion = Main.CurrentVersion,
PassHashEncrypted=Security.Encrypt(password.GetHash())
};
Security.GetSymmetricKeysCrypted(out handshake.AesKeyCrypted,out handshake.AesIVCrypted);
handshake.Pack(outgoingMessage);
Client.Connect(ip[0], short.Parse(ip[1]), outgoingMessage);
Main.QueueAction(() => { GTA.UI.Notification.Show($"~y~Trying to connect..."); });
});
}
}
public static bool IsOnServer
@ -111,39 +126,18 @@ namespace RageCoop.Client
}
/*
private static void DecodeNativeCallWithResponse(Packets.NativeResponse packet)
{
object result = DecodeNativeCall(packet.Hash, packet.Args, true, packet.ResultType);
if (Main.CheckNativeHash.ContainsKey(packet.Hash))
{
foreach (KeyValuePair<ulong, byte> hash in Main.CheckNativeHash)
{
if (hash.Key == packet.Hash)
{
lock (Main.ServerItems)
{
Main.ServerItems.Add((int)result, hash.Value);
}
break;
}
}
}
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
new Packets.NativeResponse()
{
Hash = 0,
Args = new List<object>() { result },
ID = packet.ID
}.Pack(outgoingMessage);
Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Native);
Client.FlushSendQueue();
}
*/
#endregion // -- PLAYER --
private static void GetServerPublicKey(string address,int timeout=10000)
{
var msg=Client.CreateMessage();
new Packets.PublicKeyRequest().Pack(msg);
var adds =address.Split(':');
Client.SendUnconnectedMessage(msg,adds[0],int.Parse(adds[1]));
PublicKeyReceived.WaitOne(timeout);
}
#endregion
}

View File

@ -6,13 +6,16 @@ using Lidgren.Network;
using RageCoop.Core;
using GTA;
using RageCoop.Client.Menus;
using System.Threading;
using GTA.Math;
using GTA.Native;
using System.Net;
namespace RageCoop.Client
{
internal static partial class Networking
{
private static AutoResetEvent PublicKeyReceived=new AutoResetEvent(false);
public static void ProcessMessage(NetIncomingMessage message)
{
if(message == null) { return; }
@ -30,34 +33,16 @@ namespace RageCoop.Client
#if !NON_INTERACTIVE
CoopMenu.InitiateConnectionMenuSetting();
#endif
Main.QueueAction(() => { GTA.UI.Notification.Show("~y~Trying to connect..."); return true; });
break;
case NetConnectionStatus.Connected:
if (message.SenderConnection.RemoteHailMessage.ReadByte() != (byte)PacketTypes.Handshake)
{
Client.Disconnect("Wrong packet!");
}
else
{
int len = message.SenderConnection.RemoteHailMessage.ReadInt32();
byte[] data = message.SenderConnection.RemoteHailMessage.ReadBytes(len);
Main.QueueAction(() => {
CoopMenu.ConnectedMenuSetting();
Main.MainChat.Init();
PlayerList.Cleanup();
GTA.UI.Notification.Show("~g~Connected!");
});
Packets.Handshake handshakePacket = new Packets.Handshake();
handshakePacket.Unpack(data);
#if !NON_INTERACTIVE
#endif
Main.QueueAction(() => {
CoopMenu.ConnectedMenuSetting();
Main.MainChat.Init();
PlayerList.Cleanup();
GTA.UI.Notification.Show("~g~Connected!");
});
Main.Logger.Info(">> Connected <<");
}
Main.Logger.Info(">> Connected <<");
break;
case NetConnectionStatus.Disconnected:
DownloadManager.Cleanup();
@ -89,161 +74,173 @@ namespace RageCoop.Client
}
break;
case NetIncomingMessageType.Data:
if (message.LengthBytes==0) { break; }
var packetType = (PacketTypes)message.ReadByte();
try
{
int len = message.ReadInt32();
byte[] data = message.ReadBytes(len);
switch (packetType)
if (message.LengthBytes==0) { break; }
var packetType = PacketTypes.Unknown;
try
{
case PacketTypes.CleanUpWorld:
{
Main.QueueAction(() => { Main.CleanUpWorld(); return true; });
}
break;
case PacketTypes.PlayerConnect:
{
packetType = (PacketTypes)message.ReadByte();
int len = message.ReadInt32();
byte[] data = message.ReadBytes(len);
switch (packetType)
{
case PacketTypes.PlayerConnect:
{
Packets.PlayerConnect packet = new Packets.PlayerConnect();
packet.Unpack(data);
Packets.PlayerConnect packet = new Packets.PlayerConnect();
packet.Unpack(data);
Main.QueueAction(() => PlayerConnect(packet));
}
break;
case PacketTypes.PlayerDisconnect:
{
Packets.PlayerDisconnect packet = new Packets.PlayerDisconnect();
packet.Unpack(data);
Main.QueueAction(() => PlayerDisconnect(packet));
}
break;
case PacketTypes.PlayerInfoUpdate:
{
var packet = new Packets.PlayerInfoUpdate();
packet.Unpack(data);
PlayerList.UpdatePlayer(packet);
Main.QueueAction(() => PlayerConnect(packet));
}
break;
}
#region ENTITY SYNC
case PacketTypes.VehicleSync:
{
case PacketTypes.PlayerDisconnect:
{
Packets.VehicleSync packet = new Packets.VehicleSync();
packet.Unpack(data);
VehicleSync(packet);
Packets.PlayerDisconnect packet = new Packets.PlayerDisconnect();
packet.Unpack(data);
Main.QueueAction(() => PlayerDisconnect(packet));
}
break;
case PacketTypes.PedSync:
{
Packets.PedSync packet = new Packets.PedSync();
packet.Unpack(data);
PedSync(packet);
}
break;
case PacketTypes.VehicleStateSync:
{
Packets.VehicleStateSync packet = new Packets.VehicleStateSync();
packet.Unpack(data);
VehicleStateSync(packet);
}
break;
case PacketTypes.PedStateSync:
{
Packets.PedStateSync packet = new Packets.PedStateSync();
packet.Unpack(data);
PedStateSync(packet);
}
break;
case PacketTypes.ProjectileSync:
{
Packets.ProjectileSync packet = new Packets.ProjectileSync();
packet.Unpack(data);
ProjectileSync(packet);
}
break;
}
#endregion
case PacketTypes.ChatMessage:
{
case PacketTypes.PlayerInfoUpdate:
{
var packet = new Packets.PlayerInfoUpdate();
packet.Unpack(data);
PlayerList.UpdatePlayer(packet);
break;
}
#region ENTITY SYNC
case PacketTypes.VehicleSync:
{
Packets.ChatMessage packet = new Packets.ChatMessage();
packet.Unpack(data);
Packets.VehicleSync packet = new Packets.VehicleSync();
packet.Unpack(data);
VehicleSync(packet);
Main.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message); return true; });
}
break;
case PacketTypes.PedSync:
{
Packets.PedSync packet = new Packets.PedSync();
packet.Unpack(data);
PedSync(packet);
}
break;
case PacketTypes.VehicleStateSync:
{
Packets.VehicleStateSync packet = new Packets.VehicleStateSync();
packet.Unpack(data);
VehicleStateSync(packet);
}
break;
case PacketTypes.PedStateSync:
{
}
break;
case PacketTypes.CustomEvent:
{
Packets.CustomEvent packet = new Packets.CustomEvent();
packet.Unpack(data);
Scripting.API.Events.InvokeCustomEventReceived(packet);
}
break;
case PacketTypes.FileTransferChunk:
{
Packets.FileTransferChunk packet = new Packets.FileTransferChunk();
packet.Unpack(data);
Packets.PedStateSync packet = new Packets.PedStateSync();
packet.Unpack(data);
PedStateSync(packet);
DownloadManager.Write(packet.ID, packet.FileChunk);
}
break;
case PacketTypes.ProjectileSync:
{
Packets.ProjectileSync packet = new Packets.ProjectileSync();
packet.Unpack(data);
ProjectileSync(packet);
break;
}
#endregion
case PacketTypes.ChatMessage:
{
}
break;
case PacketTypes.FileTransferRequest:
{
Packets.FileTransferRequest packet = new Packets.FileTransferRequest();
packet.Unpack(data);
Packets.ChatMessage packet = new Packets.ChatMessage();
packet.Unpack(data);
DownloadManager.AddFile(packet.ID, packet.Name, packet.FileLength);
Main.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message); return true; });
}
break;
case PacketTypes.FileTransferComplete:
{
Packets.FileTransferComplete packet = new Packets.FileTransferComplete();
packet.Unpack(data);
Main.Logger.Debug($"Finalizing download:{packet.ID}");
DownloadManager.Complete(packet.ID);
}
break;
case PacketTypes.CustomEvent:
{
Packets.CustomEvent packet = new Packets.CustomEvent();
packet.Unpack(data);
Scripting.API.Events.InvokeCustomEventReceived(packet);
}
break;
case PacketTypes.FileTransferChunk:
{
Packets.FileTransferChunk packet = new Packets.FileTransferChunk();
packet.Unpack(data);
}
break;
default:
if (packetType.IsSyncEvent())
{
// Dispatch to main thread
Main.QueueAction(() => { SyncEvents.HandleEvent(packetType, data); return true; });
}
break;
DownloadManager.Write(packet.ID, packet.FileChunk);
}
break;
case PacketTypes.FileTransferRequest:
{
Packets.FileTransferRequest packet = new Packets.FileTransferRequest();
packet.Unpack(data);
DownloadManager.AddFile(packet.ID, packet.Name, packet.FileLength);
}
break;
case PacketTypes.FileTransferComplete:
{
Packets.FileTransferComplete packet = new Packets.FileTransferComplete();
packet.Unpack(data);
Main.Logger.Debug($"Finalizing download:{packet.ID}");
DownloadManager.Complete(packet.ID);
}
break;
default:
if (packetType.IsSyncEvent())
{
// Dispatch to main thread
Main.QueueAction(() => { SyncEvents.HandleEvent(packetType, data); return true; });
}
break;
}
}
catch (Exception ex)
{
Main.QueueAction(() => {
GTA.UI.Notification.Show("~r~~h~Packet Error");
return true;
});
Main.Logger.Error($"[{packetType}] {ex.Message}");
Main.Logger.Error(ex);
Client.Disconnect($"Packet Error [{packetType}]");
}
break;
}
catch (Exception ex)
{
Main.QueueAction(() => {
GTA.UI.Notification.Show("~r~~h~Packet Error");
return true;
});
Main.Logger.Error($"[{packetType}] {ex.Message}");
Main.Logger.Error(ex);
Client.Disconnect($"Packet Error [{packetType}]");
}
break;
case NetIncomingMessageType.ConnectionLatencyUpdated:
Latency = message.ReadFloat();
break;
case NetIncomingMessageType.UnconnectedData:
{
var packetType = (PacketTypes)message.ReadByte();
int len = message.ReadInt32();
byte[] data = message.ReadBytes(len);
if (packetType==PacketTypes.PublicKeyResponse)
{
var packet=new Packets.PublicKeyResponse();
packet.Unpack(data);
Security.SetServerPublicKey(packet.Modulus,packet.Exponent);
PublicKeyReceived.Set();
}
break;
}
case NetIncomingMessageType.DebugMessage:
case NetIncomingMessageType.ErrorMessage:
case NetIncomingMessageType.WarningMessage:

View File

@ -16,6 +16,7 @@ namespace RageCoop.Client.Scripting
{
API.RegisterCustomEventHandler(CustomEvents.SetAutoRespawn,SetAutoRespawn);
API.RegisterCustomEventHandler(CustomEvents.NativeCall,NativeCall);
API.RegisterCustomEventHandler(CustomEvents.CleanUpWorld, (s) => Main.QueueAction(() => Main.CleanUpWorld()));
}
public override void OnStop()

View File

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.IO;
using RageCoop.Core;
namespace RageCoop.Client
{
internal class Security
{
public RSA ServerRSA { get; set; }
public Aes ClientAes { get; set; }=Aes.Create();
private Logger Logger;
public Security(Logger logger)
{
Logger = logger;
ClientAes.GenerateKey();
ClientAes.GenerateIV();
}
public void GetSymmetricKeysCrypted(out byte[] cryptedKey,out byte[] cryptedIV)
{
// Logger?.Debug($"Aes.Key:{ClientAes.Key.Dump()}, Aes.IV:{ClientAes.IV.Dump()}");
cryptedKey =ServerRSA.Encrypt(ClientAes.Key, RSAEncryptionPadding.Pkcs1);
cryptedIV =ServerRSA.Encrypt(ClientAes.IV,RSAEncryptionPadding.Pkcs1);
}
public byte[] Encrypt(byte[] data)
{
return new CryptoStream(new MemoryStream(data), ClientAes.CreateEncryptor(), CryptoStreamMode.Read).ReadToEnd();
}
public void SetServerPublicKey(byte[] modulus,byte[] exponent)
{
var para = new RSAParameters();
para.Modulus = modulus;
para.Exponent = exponent;
ServerRSA=RSA.Create(para);
}
}
}

View File

@ -11,6 +11,8 @@ namespace RageCoop.Client
/// Don't use it!
/// </summary>
public string Username { get; set; } = "Player";
public string Password { get; set; } = "";
/// <summary>
/// Don't use it!
/// </summary>

View File

@ -9,6 +9,7 @@ using GTA.Native;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
namespace RageCoop.Client
{
@ -197,6 +198,11 @@ namespace RageCoop.Client
{
Function.Call(Hash.SET_RADIO_TO_STATION_INDEX, index);
}
public static byte[] GetHash(this string inputString)
{
using (HashAlgorithm algorithm = SHA256.Create())
return algorithm.ComputeHash(Encoding.UTF8.GetBytes(inputString));
}
}
}

View File

@ -54,7 +54,10 @@ namespace RageCoop.Core
CurrentIndex += length;
return value;
}
public byte[] ReadByteArray()
{
return ReadByteArray(ReadInt());
}
public short ReadShort()
{
short value = BitConverter.ToInt16(ResultArray, CurrentIndex);

View File

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using GTA.Math;
using System.Security.Cryptography;
using System.Net;
using System.IO;
namespace RageCoop.Core
{
public class CoreUtils
@ -82,6 +83,11 @@ namespace RageCoop.Core
bytes.AddInt(sb.Length);
bytes.AddRange(sb);
}
public static void AddArray(this List<byte> bytes, byte[] toadd)
{
bytes.AddInt(toadd.Length);
bytes.AddRange(toadd);
}
public static int GetHash(string s)
{
@ -202,5 +208,28 @@ namespace RageCoop.Core
action(obj);
}
}
public static byte[] ReadToEnd(this Stream stream)
{
if (stream is MemoryStream)
return ((MemoryStream)stream).ToArray();
using (var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
public static byte[] Join(this List<byte[]> arrays)
{
if (arrays.Count==1) { return arrays[0]; }
var output = new byte[arrays.Sum(arr => arr.Length)];
int writeIdx = 0;
foreach (var byteArr in arrays)
{
byteArr.CopyTo(output, writeIdx);
writeIdx += byteArr.Length;
}
return output;
}
}
}

View File

@ -13,12 +13,14 @@ namespace RageCoop.Core
PlayerConnect=1,
PlayerDisconnect=2,
PlayerInfoUpdate=3,
PublicKeyRequest=4,
PublicKeyResponse=5,
ChatMessage=10,
// NativeCall=11,
// NativeResponse=12,
//Mod=13,
CleanUpWorld=14,
// Mod=13,
// CleanUpWorld=14,
FileTransferChunk=15,
FileTransferRequest=16,
@ -49,6 +51,7 @@ namespace RageCoop.Core
#endregion
#endregion
Unknown=255
}
public static class PacketExtensions
{

View File

@ -16,6 +16,21 @@ namespace RageCoop.Core
public string ModVersion { get; set; }
/// <summary>
/// The asymetrically crypted Aes key
/// </summary>
public byte[] AesKeyCrypted;
/// <summary>
/// The asymetrically crypted Aes IV
/// </summary>
public byte[] AesIVCrypted;
/// <summary>
/// The password hash with client Aes
/// </summary>
public byte[] PassHashEncrypted { get; set; }
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
@ -36,6 +51,16 @@ namespace RageCoop.Core
byteArray.AddRange(BitConverter.GetBytes(modVersionBytes.Length));
byteArray.AddRange(modVersionBytes);
// Write AesKeyCrypted
byteArray.AddArray(AesKeyCrypted);
// Write AesIVCrypted
byteArray.AddArray(AesIVCrypted);
// Write PassHash
byteArray.AddArray(PassHashEncrypted);
byte[] result = byteArray.ToArray();
message.Write(result.Length);
@ -52,12 +77,17 @@ namespace RageCoop.Core
PedID = reader.ReadInt();
// Read Username
int usernameLength = reader.ReadInt();
Username = reader.ReadString(usernameLength);
Username = reader.ReadString(reader.ReadInt());
// Read ModVersion
int modVersionLength = reader.ReadInt();
ModVersion = reader.ReadString(modVersionLength);
ModVersion = reader.ReadString(reader.ReadInt());
AesKeyCrypted=reader.ReadByteArray();
AesIVCrypted=reader.ReadByteArray();
PassHashEncrypted=reader.ReadByteArray();
#endregion
}
}
@ -202,5 +232,52 @@ namespace RageCoop.Core
BlipColor=(GTA.BlipColor)reader.ReadByte();
}
}
public class PublicKeyResponse : Packet
{
public byte[] Modulus;
public byte[] Exponent;
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.PublicKeyResponse);
List<byte> byteArray = new List<byte>();
byteArray.AddArray(Modulus);
byteArray.AddArray(Exponent);
byte[] result = byteArray.ToArray();
message.Write(result.Length);
message.Write(result);
#endregion
}
public override void Unpack(byte[] array)
{
#region NetIncomingMessageToPacket
var reader=new BitReader(array);
Modulus=reader.ReadByteArray();
Exponent=reader.ReadByteArray();
#endregion
}
}
public class PublicKeyRequest : Packet
{
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.PublicKeyRequest);
#endregion
}
public override void Unpack(byte[] array)
{
}
}
}
}

View File

@ -16,8 +16,8 @@ namespace RageCoop.Core.Scripting
public static readonly int OnPlayerDied = Hash("RageCoop.OnPlayerDied");
public static readonly int SetAutoRespawn = Hash("RageCoop.SetAutoRespawn");
public static readonly int NativeCall = Hash("RageCoop.NativeCall");
// public static readonly int NativeCallNoResponse = Hash("RageCoop.NativeCallNoResponse");
public static readonly int NativeResponse = Hash("RageCoop.NativeResponse");
public static readonly int CleanUpWorld = Hash("RageCoop.CleanUpWorld");
/// <summary>
/// Get a Int32 hash of a string.
/// </summary>

View File

@ -10,6 +10,7 @@ namespace RageCoop.Server
{
public class ServerPed
{
public int ID { get;internal set; }
/// <summary>
/// The ID of the ped's last vehicle.
/// </summary>
@ -56,6 +57,7 @@ namespace RageCoop.Server
public PlayerConfig Config { get { return _config; }set { _config=value;Server.SendPlayerInfos(); } }
internal readonly Dictionary<int, Action<object>> Callbacks = new();
internal byte[] PublicKey { get; set; }
public bool IsReady { get; internal set; }=false;
public string Username { get;internal set; } = "N/A";
#region CUSTOMDATA FUNCTIONS
@ -119,114 +121,14 @@ namespace RageCoop.Server
Server.Logger?.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
}
/*
/// <summary>
/// Send a native call to client and ignore its return value.
/// Send a CleanUpWorld message to this client.
/// </summary>
/// <param name="hash">The function's hash</param>
/// <param name="args">Arguments</param>
public void SendNativeCall(ulong hash, params object[] args)
/// <param name="clients"></param>
public void SendCleanUpWorld(List<Client> clients = null)
{
try
{
NetConnection userConnection = Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == NetID);
if (userConnection == null)
{
Server.Logger?.Error($"[Client->SendNativeCall(ulong hash, params object[] args)]: Connection \"{NetID}\" not found!");
return;
}
if (args != null && args.Length == 0)
{
Server.Logger?.Error($"[Client->SendNativeCall(ulong hash, Dictionary<string, object> args)]: Missing arguments!");
return;
}
Packets.NativeCall packet = new()
{
Hash = hash,
Args = new List<object>(args) ?? new List<object>(),
};
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
packet.Pack(outgoingMessage);
Server.MainNetServer.SendMessage(outgoingMessage, userConnection, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Native);
}
catch (Exception e)
{
Server.Logger?.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
SendCustomEvent(CustomEvents.CleanUpWorld, null);
}
/// <summary>
/// Send a native call to client and do a callback when the response is received.
/// </summary>
/// <param name="callback">The callback to be invoked when the response is received.</param>
/// <param name="hash">The function's hash</param>
/// <param name="returnType">The return type of the response</param>
/// <param name="args">Arguments</param>
public void SendNativeCallWithResponse(Action<object> callback, GTA.Native.Hash hash, Type returnType, params object[] args)
{
try
{
NetConnection userConnection = Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == NetID);
if (userConnection == null)
{
Server.Logger?.Error($"[Client->SendNativeResponse(Action<object> callback, ulong hash, Type type, params object[] args)]: Connection \"{NetID}\" not found!");
return;
}
if (args != null && args.Length == 0)
{
Server.Logger?.Error($"[Client->SendNativeCall(ulong hash, Dictionary<string, object> args)]: Missing arguments!");
return;
}
long id = ++_callbacksCount;
Callbacks.Add(id, callback);
byte returnTypeValue = 0x00;
if (returnType == typeof(int))
{
// NOTHING BECAUSE VALUE IS 0x00
}
else if (returnType == typeof(bool))
{
returnTypeValue = 0x01;
}
else if (returnType == typeof(float))
{
returnTypeValue = 0x02;
}
else if (returnType == typeof(string))
{
returnTypeValue = 0x03;
}
else if (returnType == typeof(Vector3))
{
returnTypeValue = 0x04;
}
else
{
Server.Logger?.Error($"[Client->SendNativeCall(ulong hash, Dictionary<string, object> args)]: Missing return type!");
return;
}
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
new Packets.NativeResponse()
{
Hash = (ulong)hash,
Args = new List<object>(args) ?? new List<object>(),
ResultType = returnTypeValue,
ID = id
}.Pack(outgoingMessage);
Server.MainNetServer.SendMessage(outgoingMessage, userConnection, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Native);
}
catch (Exception e)
{
Server.Logger?.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
}
*/
/// <summary>
/// Send a native call to client and do a callback when the response received.
@ -273,18 +175,6 @@ namespace RageCoop.Server
}
return ID;
}
public void SendCleanUpWorld()
{
NetConnection userConnection = Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == NetID);
if (userConnection == null)
{
Server.Logger?.Error($"[Client->SendCleanUpWorld()]: Connection \"{NetID}\" not found!");
return;
}
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
outgoingMessage.Write((byte)PacketTypes.CleanUpWorld);
Server.MainNetServer.SendMessage(outgoingMessage, userConnection, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Default);
}
public void SendCustomEvent(int id,List<object> args)
{

View File

@ -196,20 +196,7 @@ namespace RageCoop.Server.Scripting
/// </summary>
public void SendCleanUpWorldToAll(List<Client> clients = null)
{
if (Server.MainNetServer.ConnectionsCount == 0)
{
return;
}
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
outgoingMessage.Write((byte)PacketTypes.CleanUpWorld);
if (clients == null)
{
Server.MainNetServer.SendToAll(outgoingMessage, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Default);
}
else
{
clients.ForEach(client => { Server.MainNetServer.SendMessage(outgoingMessage,client.Connection, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Default); });
}
SendCustomEvent(CustomEvents.CleanUpWorld,null,clients);
}
/// <summary>
@ -248,7 +235,7 @@ namespace RageCoop.Server.Scripting
/// <param name="eventHash">An unique identifier of the event</param>
/// <param name="args">The objects conataing your data, supported types: byte, short, ushort, int, uint, long, ulong, float, bool, string.</param>
/// <param name="targets">The target clients to send. Leave it null to send to all clients</param>
public void SendCustomEvent(int eventHash,List<object> args,List<Client> targets=null)
public void SendCustomEvent(int eventHash,List<object> args=null,List<Client> targets=null)
{
targets ??= new(Server.Clients.Values);
var p = new Packets.CustomEvent()

View File

@ -32,6 +32,11 @@ namespace RageCoop.Server.Scripting
{
public int ID { get; set; }
public string Username { get; set; }
/// <summary>
/// The hashed value of client password, sent with RSA asymmetric encryption.
/// </summary>
public string PasswordHash { get; set; }
public IPEndPoint EndPoint { get; set; }
public void Deny(string reason)
{

View File

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.IO;
using RageCoop.Core;
using System.Runtime.Serialization;
using System.Security.Cryptography;
namespace RageCoop.Server
{
internal class Security
{
private readonly Logger Logger;
public Security(Logger logger)
{
Logger= logger;
}
public RSA RSA=RSA.Create(2048);
private Dictionary<IPEndPoint, Aes> SecuredConnections = new Dictionary<IPEndPoint, Aes>();
public bool HasSecuredConnection(IPEndPoint target)
{
return SecuredConnections.ContainsKey(target);
}
public byte[] Encrypt(byte[] data, IPEndPoint target)
{
var ms=new MemoryStream();
using (var cs = new CryptoStream(ms, SecuredConnections[target].CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(data, 0, data.Length);
}
return ms.ToArray();
}
public byte[] Decrypt(byte[] data, IPEndPoint target)
{
return new CryptoStream(new MemoryStream(data), SecuredConnections[target].CreateDecryptor(), CryptoStreamMode.Read).ReadToEnd();
}
public void AddConnection(IPEndPoint endpoint, byte[] cryptedKey,byte[] cryptedIV)
{
var key = RSA.Decrypt(cryptedKey, RSAEncryptionPadding.Pkcs1);
var iv = RSA.Decrypt(cryptedIV, RSAEncryptionPadding.Pkcs1);
// Logger?.Debug($"key:{key.Dump()}, iv:{iv.Dump()}");
var conAes = Aes.Create();
conAes.Key = key;
conAes.IV = iv;
if (!SecuredConnections.ContainsKey(endpoint))
{
SecuredConnections.Add(endpoint,conAes);
}
else
{
SecuredConnections[endpoint] = conAes;
}
}
public void RemoveConnection(IPEndPoint ep)
{
if (SecuredConnections.ContainsKey(ep))
{
SecuredConnections.Remove(ep);
}
}
public void GetPublicKey(out byte[] modulus, out byte[] exponent)
{
var key = RSA.ExportParameters(false);
modulus = key.Modulus;
exponent = key.Exponent;
}
public void ClearConnections()
{
SecuredConnections.Clear();
}
}
}

View File

@ -39,11 +39,13 @@ namespace RageCoop.Server
private Resources Resources;
public API API;
public Logger Logger;
private Security Security;
public Server(Logger logger=null)
{
Logger=logger;
API=new API(this);
Resources=new Resources(this);
Security=new Security(Logger);
Logger?.Info("================");
Logger?.Info($"Server bound to: 0.0.0.0:{MainSettings.Port}");
Logger?.Info($"Server version: {Assembly.GetCallingAssembly().GetName().Version}");
@ -57,10 +59,12 @@ namespace RageCoop.Server
MaximumConnections = MainSettings.MaxPlayers,
EnableUPnP = false,
AutoFlushSendQueue = true,
MaximumTransmissionUnit=2000, // PublicKeyResponse
};
config.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
config.EnableMessageType(NetIncomingMessageType.ConnectionLatencyUpdated);
config.EnableMessageType(NetIncomingMessageType.UnconnectedData);
MainNetServer = new NetServer(config);
MainNetServer.Start();
@ -170,8 +174,6 @@ namespace RageCoop.Server
{
Logger?.Info("Listening for clients");
Logger?.Info("Please use CTRL + C if you want to stop the server!");
while (!Program.ReadyToStop)
{
try
@ -191,10 +193,10 @@ namespace RageCoop.Server
Resources.StopAll();
}
Client sender;
private void ProcessMessage(NetIncomingMessage message)
{
if(message == null) { return; }
Client sender;
if (message == null) { return; }
switch (message.MessageType)
{
case NetIncomingMessageType.ConnectionApproval:
@ -214,12 +216,12 @@ namespace RageCoop.Server
Packets.Handshake packet = new();
packet.Unpack(data);
GetHandshake(message.SenderConnection, packet);
}
catch (Exception e)
{
Logger?.Info($"IP [{message.SenderConnection.RemoteEndPoint.Address}] was blocked, reason: {e.Message}");
Logger?.Error(e);
message.SenderConnection.Deny(e.Message);
}
}
@ -413,6 +415,19 @@ namespace RageCoop.Server
case NetIncomingMessageType.VerboseDebugMessage:
Logger?.Debug(message.ReadString());
break;
case NetIncomingMessageType.UnconnectedData:
{
if (message.ReadByte()==(byte)PacketTypes.PublicKeyRequest)
{
var msg = MainNetServer.CreateMessage();
var p=new Packets.PublicKeyResponse();
Security.GetPublicKey(out p.Modulus,out p.Exponent);
p.Pack(msg);
Logger?.Debug($"Sending public key to {message.SenderEndPoint}, length:{msg.LengthBytes}");
MainNetServer.SendUnconnectedMessage(msg, message.SenderEndPoint);
}
}
break;
default:
Logger?.Error(string.Format("Unhandled type: {0} {1} bytes {2} | {3}", message.MessageType, message.LengthBytes, message.DeliveryMethod, message.SequenceChannel));
break;
@ -480,12 +495,25 @@ namespace RageCoop.Server
connection.Deny("Username is already taken!");
return;
}
string passhash;
try
{
Security.AddConnection(connection.RemoteEndPoint, packet.AesKeyCrypted,packet.AesIVCrypted);
passhash=Security.Decrypt(packet.PassHashEncrypted,connection.RemoteEndPoint).GetString();
}
catch (Exception ex)
{
Logger?.Error($"Cannot process handshake packet from {connection.RemoteEndPoint}");
Logger?.Error(ex);
connection.Deny("Malformed handshak packet!");
return;
}
var args = new HandshakeEventArgs()
{
EndPoint=connection.RemoteEndPoint,
ID=packet.PedID,
Username=packet.Username
Username=packet.Username,
PasswordHash=passhash,
};
API.Events.InvokePlayerHandshake(args);
if (args.Cancel)
@ -495,6 +523,7 @@ namespace RageCoop.Server
}
connection.Approve();
Client tmpClient;
@ -510,23 +539,14 @@ namespace RageCoop.Server
ID=packet.PedID,
Player = new()
{
ID= packet.PedID,
}
}
);;
}
Logger?.Debug($"Handshake sucess, Player:{packet.Username} PedID:{packet.PedID}");
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
// Create a new handshake packet
new Packets.Handshake()
{
PedID = packet.PedID,
Username = string.Empty,
ModVersion = string.Empty,
}.Pack(outgoingMessage);
// Accept the connection and send back a new handshake packet with the connection ID
connection.Approve(outgoingMessage);
}
// The connection has been approved, now we need to send all other players to the new player and the new player to all players