implement secure rcon for IW4x
This commit is contained in:
parent
41efe26a48
commit
4e02e7841f
@ -8,6 +8,7 @@ using System.Text;
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Integrations.Cod.SecureRcon;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Serilog.Context;
|
using Serilog.Context;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
@ -24,6 +25,7 @@ namespace Integrations.Cod
|
|||||||
public class CodRConConnection : IRConConnection
|
public class CodRConConnection : IRConConnection
|
||||||
{
|
{
|
||||||
private static readonly ConcurrentDictionary<EndPoint, ConnectionState> ActiveQueries = new();
|
private static readonly ConcurrentDictionary<EndPoint, ConnectionState> ActiveQueries = new();
|
||||||
|
private const string PkPattern = "-----BEGIN PRIVATE KEY-----";
|
||||||
public IPEndPoint Endpoint { get; }
|
public IPEndPoint Endpoint { get; }
|
||||||
public string RConPassword { get; }
|
public string RConPassword { get; }
|
||||||
|
|
||||||
@ -147,49 +149,33 @@ namespace Integrations.Cod
|
|||||||
{
|
{
|
||||||
var convertedRConPassword = ConvertEncoding(RConPassword);
|
var convertedRConPassword = ConvertEncoding(RConPassword);
|
||||||
var convertedParameters = ConvertEncoding(parameters);
|
var convertedParameters = ConvertEncoding(parameters);
|
||||||
byte SafeConversion(char c)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return Convert.ToByte(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return (byte)'.';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case StaticHelpers.QueryType.GET_DVAR:
|
case StaticHelpers.QueryType.GET_DVAR:
|
||||||
waitForResponse = true;
|
waitForResponse = true;
|
||||||
payload = string
|
payload = BuildPayload(_config.CommandPrefixes.RConGetDvar, convertedRConPassword,
|
||||||
.Format(_config.CommandPrefixes.RConGetDvar, convertedRConPassword,
|
convertedParameters);
|
||||||
convertedParameters + '\0').Select(SafeConversion).ToArray();
|
|
||||||
break;
|
break;
|
||||||
case StaticHelpers.QueryType.SET_DVAR:
|
case StaticHelpers.QueryType.SET_DVAR:
|
||||||
payload = string
|
payload = BuildPayload(_config.CommandPrefixes.RConSetDvar, convertedRConPassword,
|
||||||
.Format(_config.CommandPrefixes.RConSetDvar, convertedRConPassword,
|
convertedParameters);
|
||||||
convertedParameters + '\0').Select(SafeConversion).ToArray();
|
|
||||||
break;
|
break;
|
||||||
case StaticHelpers.QueryType.COMMAND:
|
case StaticHelpers.QueryType.COMMAND:
|
||||||
payload = string
|
payload = BuildPayload(_config.CommandPrefixes.RConCommand, convertedRConPassword,
|
||||||
.Format(_config.CommandPrefixes.RConCommand, convertedRConPassword,
|
convertedParameters);
|
||||||
convertedParameters + '\0').Select(SafeConversion).ToArray();
|
|
||||||
break;
|
break;
|
||||||
case StaticHelpers.QueryType.GET_STATUS:
|
case StaticHelpers.QueryType.GET_STATUS:
|
||||||
waitForResponse = true;
|
waitForResponse = true;
|
||||||
payload = (_config.CommandPrefixes.RConGetStatus + '\0').Select(SafeConversion).ToArray();
|
payload = (_config.CommandPrefixes.RConGetStatus + '\0').Select(Helpers.SafeConversion).ToArray();
|
||||||
break;
|
break;
|
||||||
case StaticHelpers.QueryType.GET_INFO:
|
case StaticHelpers.QueryType.GET_INFO:
|
||||||
waitForResponse = true;
|
waitForResponse = true;
|
||||||
payload = (_config.CommandPrefixes.RConGetInfo + '\0').Select(SafeConversion).ToArray();
|
payload = (_config.CommandPrefixes.RConGetInfo + '\0').Select(Helpers.SafeConversion).ToArray();
|
||||||
break;
|
break;
|
||||||
case StaticHelpers.QueryType.COMMAND_STATUS:
|
case StaticHelpers.QueryType.COMMAND_STATUS:
|
||||||
waitForResponse = true;
|
waitForResponse = true;
|
||||||
payload = string.Format(_config.CommandPrefixes.RConCommand, convertedRConPassword, "status\0")
|
payload = BuildPayload(_config.CommandPrefixes.RConCommand, convertedRConPassword, "status");
|
||||||
.Select(SafeConversion).ToArray();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -334,6 +320,27 @@ namespace Integrations.Cod
|
|||||||
return validatedResponse;
|
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,
|
private async Task<byte[][]> SendPayloadAsync(Socket rconSocket, byte[] payload, bool waitForResponse,
|
||||||
CancellationToken token = default)
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
@ -480,7 +487,7 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
return string.Join("", splitStatusStrings);
|
return string.Join("", splitStatusStrings);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Recombines multiple game messages into one
|
/// Recombines multiple game messages into one
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -513,7 +520,7 @@ namespace Integrations.Cod
|
|||||||
{
|
{
|
||||||
return connectionState.ReceivedBytes.ToArray();
|
return connectionState.ReceivedBytes.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,4 +16,8 @@
|
|||||||
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
|
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="protobuf-net" Version="3.2.26" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
57
Integrations/Cod/SecureRcon/Helpers.cs
Normal file
57
Integrations/Cod/SecureRcon/Helpers.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
13
Integrations/Cod/SecureRcon/SecureCommand.cs
Normal file
13
Integrations/Cod/SecureRcon/SecureCommand.cs
Normal 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; }
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user