diff --git a/RageCoop.Client/Networking/Networking.cs b/RageCoop.Client/Networking/Networking.cs index 5b88da5..3443b98 100644 --- a/RageCoop.Client/Networking/Networking.cs +++ b/RageCoop.Client/Networking/Networking.cs @@ -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 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() { 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 } diff --git a/RageCoop.Client/Networking/Receive.cs b/RageCoop.Client/Networking/Receive.cs index 38830de..49bca86 100644 --- a/RageCoop.Client/Networking/Receive.cs +++ b/RageCoop.Client/Networking/Receive.cs @@ -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: diff --git a/RageCoop.Client/Scripting/BaseScript.cs b/RageCoop.Client/Scripting/BaseScript.cs index 1df8ef8..78aab75 100644 --- a/RageCoop.Client/Scripting/BaseScript.cs +++ b/RageCoop.Client/Scripting/BaseScript.cs @@ -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() diff --git a/RageCoop.Client/Security.cs b/RageCoop.Client/Security.cs new file mode 100644 index 0000000..91d91fb --- /dev/null +++ b/RageCoop.Client/Security.cs @@ -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); + } + } +} diff --git a/RageCoop.Client/Settings.cs b/RageCoop.Client/Settings.cs index 2b18d1f..c743cb9 100644 --- a/RageCoop.Client/Settings.cs +++ b/RageCoop.Client/Settings.cs @@ -11,6 +11,8 @@ namespace RageCoop.Client /// Don't use it! /// public string Username { get; set; } = "Player"; + + public string Password { get; set; } = ""; /// /// Don't use it! /// diff --git a/RageCoop.Client/Util/Util.cs b/RageCoop.Client/Util/Util.cs index 26ad3dc..c3c1e90 100644 --- a/RageCoop.Client/Util/Util.cs +++ b/RageCoop.Client/Util/Util.cs @@ -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)); + } } } diff --git a/RageCoop.Core/BitReader.cs b/RageCoop.Core/BitReader.cs index 5bbe17d..62e267c 100644 --- a/RageCoop.Core/BitReader.cs +++ b/RageCoop.Core/BitReader.cs @@ -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); diff --git a/RageCoop.Core/CoreUtils.cs b/RageCoop.Core/CoreUtils.cs index 102874b..bceafd3 100644 --- a/RageCoop.Core/CoreUtils.cs +++ b/RageCoop.Core/CoreUtils.cs @@ -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 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 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; + } } } diff --git a/RageCoop.Core/Packets/Packets.cs b/RageCoop.Core/Packets/Packets.cs index d44a87e..cc56706 100644 --- a/RageCoop.Core/Packets/Packets.cs +++ b/RageCoop.Core/Packets/Packets.cs @@ -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 { diff --git a/RageCoop.Core/Packets/PlayerPackets.cs b/RageCoop.Core/Packets/PlayerPackets.cs index 9f4016d..0401345 100644 --- a/RageCoop.Core/Packets/PlayerPackets.cs +++ b/RageCoop.Core/Packets/PlayerPackets.cs @@ -16,6 +16,21 @@ namespace RageCoop.Core public string ModVersion { get; set; } + /// + /// The asymetrically crypted Aes key + /// + public byte[] AesKeyCrypted; + + /// + /// The asymetrically crypted Aes IV + /// + public byte[] AesIVCrypted; + + /// + /// The password hash with client Aes + /// + 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 byteArray = new List(); + + 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) + { + } + } } } diff --git a/RageCoop.Core/Scripting/CustomEvents.cs b/RageCoop.Core/Scripting/CustomEvents.cs index c90b03e..68948ef 100644 --- a/RageCoop.Core/Scripting/CustomEvents.cs +++ b/RageCoop.Core/Scripting/CustomEvents.cs @@ -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"); /// /// Get a Int32 hash of a string. /// diff --git a/RageCoop.Server/Client.cs b/RageCoop.Server/Client.cs index 2818baa..24b5b2f 100644 --- a/RageCoop.Server/Client.cs +++ b/RageCoop.Server/Client.cs @@ -10,6 +10,7 @@ namespace RageCoop.Server { public class ServerPed { + public int ID { get;internal set; } /// /// The ID of the ped's last vehicle. /// @@ -56,6 +57,7 @@ namespace RageCoop.Server public PlayerConfig Config { get { return _config; }set { _config=value;Server.SendPlayerInfos(); } } internal readonly Dictionary> 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} <<"); } } - /* /// - /// Send a native call to client and ignore its return value. + /// Send a CleanUpWorld message to this client. /// - /// The function's hash - /// Arguments - public void SendNativeCall(ulong hash, params object[] args) + /// + public void SendCleanUpWorld(List 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 args)]: Missing arguments!"); - return; - } - - Packets.NativeCall packet = new() - { - Hash = hash, - Args = new List(args) ?? new List(), - }; - - 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); } - /// - /// Send a native call to client and do a callback when the response is received. - /// - /// The callback to be invoked when the response is received. - /// The function's hash - /// The return type of the response - /// Arguments - public void SendNativeCallWithResponse(Action 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 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 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 args)]: Missing return type!"); - return; - } - - NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage(); - new Packets.NativeResponse() - { - Hash = (ulong)hash, - Args = new List(args) ?? new List(), - 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} <<"); - } - } - */ /// /// 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 args) { diff --git a/RageCoop.Server/Scripting/API.cs b/RageCoop.Server/Scripting/API.cs index 8ad28a6..c391d14 100644 --- a/RageCoop.Server/Scripting/API.cs +++ b/RageCoop.Server/Scripting/API.cs @@ -196,20 +196,7 @@ namespace RageCoop.Server.Scripting /// public void SendCleanUpWorldToAll(List 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); } /// @@ -248,7 +235,7 @@ namespace RageCoop.Server.Scripting /// An unique identifier of the event /// The objects conataing your data, supported types: byte, short, ushort, int, uint, long, ulong, float, bool, string. /// The target clients to send. Leave it null to send to all clients - public void SendCustomEvent(int eventHash,List args,List targets=null) + public void SendCustomEvent(int eventHash,List args=null,List targets=null) { targets ??= new(Server.Clients.Values); var p = new Packets.CustomEvent() diff --git a/RageCoop.Server/Scripting/EventArgs/EventArgs.cs b/RageCoop.Server/Scripting/EventArgs/EventArgs.cs index 5209994..7e1bdc9 100644 --- a/RageCoop.Server/Scripting/EventArgs/EventArgs.cs +++ b/RageCoop.Server/Scripting/EventArgs/EventArgs.cs @@ -32,6 +32,11 @@ namespace RageCoop.Server.Scripting { public int ID { get; set; } public string Username { get; set; } + + /// + /// The hashed value of client password, sent with RSA asymmetric encryption. + /// + public string PasswordHash { get; set; } public IPEndPoint EndPoint { get; set; } public void Deny(string reason) { diff --git a/RageCoop.Server/Security.cs b/RageCoop.Server/Security.cs new file mode 100644 index 0000000..cac4184 --- /dev/null +++ b/RageCoop.Server/Security.cs @@ -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 SecuredConnections = new Dictionary(); + + 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(); + } + + } +} diff --git a/RageCoop.Server/Server.cs b/RageCoop.Server/Server.cs index 7f8dadf..d96417b 100644 --- a/RageCoop.Server/Server.cs +++ b/RageCoop.Server/Server.cs @@ -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