implement secure rcon for IW4x

This commit is contained in:
RaidMax 2023-07-28 15:34:27 -05:00
parent 41efe26a48
commit 4e02e7841f
4 changed files with 108 additions and 27 deletions

View File

@ -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<EndPoint, ConnectionState> 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<byte[][]> SendPayloadAsync(Socket rconSocket, byte[] payload, bool waitForResponse,
CancellationToken token = default)
{
@ -480,7 +487,7 @@ namespace Integrations.Cod
return string.Join("", splitStatusStrings);
}
/// <summary>
/// Recombines multiple game messages into one
/// </summary>
@ -513,7 +520,7 @@ namespace Integrations.Cod
{
return connectionState.ReceivedBytes.ToArray();
}
#endregion
}
}

View File

@ -16,4 +16,8 @@
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="protobuf-net" Version="3.2.26" />
</ItemGroup>
</Project>

View File

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

View File

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