Security implementation
This commit is contained in:
@ -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();
|
||||
Task.Run(() =>
|
||||
{
|
||||
GetServerPublicKey(address);
|
||||
|
||||
|
||||
// Send HandshakePacket
|
||||
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
|
||||
new Packets.Handshake()
|
||||
var handshake=new Packets.Handshake()
|
||||
{
|
||||
PedID = Main.LocalPlayerID,
|
||||
Username = Main.Settings.Username,
|
||||
ModVersion = Main.CurrentVersion,
|
||||
}.Pack(outgoingMessage);
|
||||
|
||||
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
|
||||
|
||||
}
|
||||
|
@ -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,25 +33,8 @@ 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);
|
||||
|
||||
Packets.Handshake handshakePacket = new Packets.Handshake();
|
||||
handshakePacket.Unpack(data);
|
||||
|
||||
#if !NON_INTERACTIVE
|
||||
|
||||
#endif
|
||||
|
||||
Main.QueueAction(() => {
|
||||
CoopMenu.ConnectedMenuSetting();
|
||||
Main.MainChat.Init();
|
||||
@ -57,7 +43,6 @@ namespace RageCoop.Client
|
||||
});
|
||||
|
||||
Main.Logger.Info(">> Connected <<");
|
||||
}
|
||||
break;
|
||||
case NetConnectionStatus.Disconnected:
|
||||
DownloadManager.Cleanup();
|
||||
@ -89,21 +74,17 @@ namespace RageCoop.Client
|
||||
}
|
||||
break;
|
||||
case NetIncomingMessageType.Data:
|
||||
if (message.LengthBytes==0) { break; }
|
||||
|
||||
var packetType = (PacketTypes)message.ReadByte();
|
||||
try
|
||||
{
|
||||
|
||||
if (message.LengthBytes==0) { break; }
|
||||
var packetType = PacketTypes.Unknown;
|
||||
try
|
||||
{
|
||||
packetType = (PacketTypes)message.ReadByte();
|
||||
int len = message.ReadInt32();
|
||||
byte[] data = message.ReadBytes(len);
|
||||
switch (packetType)
|
||||
{
|
||||
case PacketTypes.CleanUpWorld:
|
||||
{
|
||||
Main.QueueAction(() => { Main.CleanUpWorld(); return true; });
|
||||
}
|
||||
break;
|
||||
case PacketTypes.PlayerConnect:
|
||||
{
|
||||
|
||||
@ -241,9 +222,25 @@ namespace RageCoop.Client
|
||||
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:
|
||||
|
@ -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()
|
||||
|
40
RageCoop.Client/Security.cs
Normal file
40
RageCoop.Client/Security.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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,115 +121,15 @@ 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;
|
||||
SendCustomEvent(CustomEvents.CleanUpWorld, null);
|
||||
}
|
||||
|
||||
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} <<");
|
||||
}
|
||||
}
|
||||
/// <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.
|
||||
/// </summary>
|
||||
@ -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)
|
||||
{
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
{
|
||||
|
78
RageCoop.Server/Security.cs
Normal file
78
RageCoop.Server/Security.cs
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Reference in New Issue
Block a user