diff --git a/Integrations/Cod/CodRConConnection.cs b/Integrations/Cod/CodRConConnection.cs index 780600940..2b299361e 100644 --- a/Integrations/Cod/CodRConConnection.cs +++ b/Integrations/Cod/CodRConConnection.cs @@ -8,6 +8,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using Integrations.Cod.SecureRcon; using Microsoft.Extensions.Logging; using Serilog.Context; using SharedLibraryCore; @@ -24,6 +25,7 @@ namespace Integrations.Cod public class CodRConConnection : IRConConnection { private static readonly ConcurrentDictionary ActiveQueries = new(); + private const string PkPattern = "-----BEGIN PRIVATE KEY-----"; public IPEndPoint Endpoint { get; } public string RConPassword { get; } @@ -147,49 +149,33 @@ namespace Integrations.Cod { var convertedRConPassword = ConvertEncoding(RConPassword); var convertedParameters = ConvertEncoding(parameters); - byte SafeConversion(char c) - { - try - { - return Convert.ToByte(c); - } - - catch - { - return (byte)'.'; - } - }; switch (type) { case StaticHelpers.QueryType.GET_DVAR: waitForResponse = true; - payload = string - .Format(_config.CommandPrefixes.RConGetDvar, convertedRConPassword, - convertedParameters + '\0').Select(SafeConversion).ToArray(); + payload = BuildPayload(_config.CommandPrefixes.RConGetDvar, convertedRConPassword, + convertedParameters); break; case StaticHelpers.QueryType.SET_DVAR: - payload = string - .Format(_config.CommandPrefixes.RConSetDvar, convertedRConPassword, - convertedParameters + '\0').Select(SafeConversion).ToArray(); + payload = BuildPayload(_config.CommandPrefixes.RConSetDvar, convertedRConPassword, + convertedParameters); break; case StaticHelpers.QueryType.COMMAND: - payload = string - .Format(_config.CommandPrefixes.RConCommand, convertedRConPassword, - convertedParameters + '\0').Select(SafeConversion).ToArray(); + payload = BuildPayload(_config.CommandPrefixes.RConCommand, convertedRConPassword, + convertedParameters); break; case StaticHelpers.QueryType.GET_STATUS: waitForResponse = true; - payload = (_config.CommandPrefixes.RConGetStatus + '\0').Select(SafeConversion).ToArray(); + payload = (_config.CommandPrefixes.RConGetStatus + '\0').Select(Helpers.SafeConversion).ToArray(); break; case StaticHelpers.QueryType.GET_INFO: waitForResponse = true; - payload = (_config.CommandPrefixes.RConGetInfo + '\0').Select(SafeConversion).ToArray(); + payload = (_config.CommandPrefixes.RConGetInfo + '\0').Select(Helpers.SafeConversion).ToArray(); break; case StaticHelpers.QueryType.COMMAND_STATUS: waitForResponse = true; - payload = string.Format(_config.CommandPrefixes.RConCommand, convertedRConPassword, "status\0") - .Select(SafeConversion).ToArray(); + payload = BuildPayload(_config.CommandPrefixes.RConCommand, convertedRConPassword, "status"); break; } } @@ -334,6 +320,27 @@ namespace Integrations.Cod return validatedResponse; } + private byte[] BuildPayload(string queryTemplate, string convertedRConPassword, string convertedParameters) + { + byte[] payload; + if (!RConPassword.StartsWith(PkPattern)) + { + payload = string + .Format(queryTemplate, convertedRConPassword, + convertedParameters + '\0').Select(Helpers.SafeConversion).ToArray(); + } + else + { + var textContent = string + .Format(queryTemplate, "", convertedParameters) + .Replace("rcon", "rconSafe ") + .Replace(" ", "").Split(" "); + payload = Helpers.BuildSafeRconPayload(textContent[0], textContent[1], RConPassword); + } + + return payload; + } + private async Task SendPayloadAsync(Socket rconSocket, byte[] payload, bool waitForResponse, CancellationToken token = default) { @@ -480,7 +487,7 @@ namespace Integrations.Cod return string.Join("", splitStatusStrings); } - + /// /// Recombines multiple game messages into one /// @@ -513,7 +520,7 @@ namespace Integrations.Cod { return connectionState.ReceivedBytes.ToArray(); } - + #endregion } } diff --git a/Integrations/Cod/Integrations.Cod.csproj b/Integrations/Cod/Integrations.Cod.csproj index d001723b0..7d10e54a8 100644 --- a/Integrations/Cod/Integrations.Cod.csproj +++ b/Integrations/Cod/Integrations.Cod.csproj @@ -16,4 +16,8 @@ + + + + diff --git a/Integrations/Cod/SecureRcon/Helpers.cs b/Integrations/Cod/SecureRcon/Helpers.cs new file mode 100644 index 000000000..307500164 --- /dev/null +++ b/Integrations/Cod/SecureRcon/Helpers.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using ProtoBuf; + +namespace Integrations.Cod.SecureRcon; + +public static class Helpers +{ + private static byte[] ToSerializedMessage(this SecureCommand command) + { + using var ms = new MemoryStream(); + Serializer.Serialize(ms, command); + return ms.ToArray(); + } + + private static byte[] SignData(byte[] data, string privateKey) + { + using var rsa = new RSACryptoServiceProvider(512); + rsa.ImportFromPem(privateKey); + var rsaFormatter = new RSAPKCS1SignatureFormatter(rsa); + rsaFormatter.SetHashAlgorithm("SHA512"); + var hash = SHA512.Create(); + var hashedData = hash.ComputeHash(data); + var signature = rsaFormatter.CreateSignature(hashedData); + + return signature; + } + + public static byte SafeConversion(char c) + { + try + { + return Convert.ToByte(c); + } + + catch + { + return (byte)'.'; + } + } + + public static byte[] BuildSafeRconPayload(string prefix, string command, string signingKey) + { + var message = command.Select(SafeConversion).ToArray(); + var header = (prefix + "\n").Select(SafeConversion).ToArray(); + + var secureCommand = new SecureCommand + { + SecMessage = message, + Signature = SignData(message, signingKey) + }; + + return header.Concat(secureCommand.ToSerializedMessage()).ToArray(); + } +} diff --git a/Integrations/Cod/SecureRcon/SecureCommand.cs b/Integrations/Cod/SecureRcon/SecureCommand.cs new file mode 100644 index 000000000..9dcf63744 --- /dev/null +++ b/Integrations/Cod/SecureRcon/SecureCommand.cs @@ -0,0 +1,13 @@ +using ProtoBuf; + +namespace Integrations.Cod.SecureRcon; + +[ProtoContract] +public class SecureCommand +{ + [ProtoMember(1)] + public byte[] SecMessage { get; set; } + + [ProtoMember(2)] + public byte[] Signature { get; set; } +}