Implemented client encryption handler

This commit is contained in:
2019-07-18 15:45:27 +03:00
parent 3473b65390
commit 2ba2939049
8 changed files with 326 additions and 17 deletions
+11 -1
View File
@@ -1,9 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RootNamespace>MTSC_TestServer</RootNamespace>
<StartupObject>MTSC_TestServer.Program</StartupObject>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
+5 -1
View File
@@ -1,5 +1,7 @@
using MTSC.Server;
using MTSC.Server.Handlers;
using System;
using System.Security.Cryptography;
namespace MTSC_TestServer
{
@@ -8,7 +10,9 @@ namespace MTSC_TestServer
static void Main(string[] args)
{
Server server = new Server(555);
server.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024);
EncryptionHandler encryptionHandler = new EncryptionHandler(rsa);
server.AddHandler(encryptionHandler).Run();
}
}
}
+13 -3
View File
@@ -5,22 +5,32 @@ VisualStudioVersion = 16.0.29102.190
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MTSC", "MTSC\MTSC.csproj", "{869DAEBC-5884-43C9-BA0B-2C4376A7ED1C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MTSC-TestServer", "MTSC-TestServer\MTSC-TestServer.csproj", "{08A53F8F-BA6B-4C1C-B24B-CFD2626DE6C3}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MTSC-TestServer", "MTSC-TestServer\MTSC-TestServer.csproj", "{08A53F8F-BA6B-4C1C-B24B-CFD2626DE6C3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{869DAEBC-5884-43C9-BA0B-2C4376A7ED1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{869DAEBC-5884-43C9-BA0B-2C4376A7ED1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{869DAEBC-5884-43C9-BA0B-2C4376A7ED1C}.Debug|x64.ActiveCfg = Debug|x64
{869DAEBC-5884-43C9-BA0B-2C4376A7ED1C}.Debug|x64.Build.0 = Debug|x64
{869DAEBC-5884-43C9-BA0B-2C4376A7ED1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{869DAEBC-5884-43C9-BA0B-2C4376A7ED1C}.Release|Any CPU.Build.0 = Release|Any CPU
{08A53F8F-BA6B-4C1C-B24B-CFD2626DE6C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{08A53F8F-BA6B-4C1C-B24B-CFD2626DE6C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{869DAEBC-5884-43C9-BA0B-2C4376A7ED1C}.Release|x64.ActiveCfg = Release|x64
{869DAEBC-5884-43C9-BA0B-2C4376A7ED1C}.Release|x64.Build.0 = Release|x64
{08A53F8F-BA6B-4C1C-B24B-CFD2626DE6C3}.Debug|Any CPU.ActiveCfg = Debug|x64
{08A53F8F-BA6B-4C1C-B24B-CFD2626DE6C3}.Debug|Any CPU.Build.0 = Debug|x64
{08A53F8F-BA6B-4C1C-B24B-CFD2626DE6C3}.Debug|x64.ActiveCfg = Debug|x64
{08A53F8F-BA6B-4C1C-B24B-CFD2626DE6C3}.Debug|x64.Build.0 = Debug|x64
{08A53F8F-BA6B-4C1C-B24B-CFD2626DE6C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08A53F8F-BA6B-4C1C-B24B-CFD2626DE6C3}.Release|Any CPU.Build.0 = Release|Any CPU
{08A53F8F-BA6B-4C1C-B24B-CFD2626DE6C3}.Release|x64.ActiveCfg = Release|x64
{08A53F8F-BA6B-4C1C-B24B-CFD2626DE6C3}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+1 -1
View File
@@ -128,7 +128,7 @@ namespace MTSC.Client
*/
foreach(IHandler handler in handlers)
{
if(handler.PreHandleReceivedMessage(tcpClient, out message))
if(handler.PreHandleReceivedMessage(tcpClient, ref message))
{
break;
}
+264
View File
@@ -0,0 +1,264 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
namespace MTSC.Client.Handlers
{
public class EncryptionHandler : IHandler
{
private enum ConnectionState
{
Initial,
RequestingPublicKey,
NegotiatingSymKey,
Encrypted
}
private ConnectionState connectionState = ConnectionState.Initial;
private byte[] aesKey;
/// <summary>
/// Tries to initialize an encrypted connection.
/// </summary>
/// <param name="client">TcpClient connection.</param>
/// <returns>True if connection is successful. False otherwise.</returns>
public bool InitializeConnection(TcpClient client)
{
this.connectionState = ConnectionState.Initial;
return true;
}
/// <summary>
/// Handles the operations done after client disconnected.
/// </summary>
/// <param name="client"></param>
public void Disconnected(TcpClient client)
{
}
/// <summary>
/// Performs operations on the message before sending it.
/// </summary>
/// <param name="client">Client connection.</param>
/// <param name="message">Message to be processed.</param>
/// <returns>True if no other handler should process the message further.</returns>
public bool HandleSendMessage(TcpClient client, ref Message message)
{
if(connectionState == ConnectionState.Encrypted)
{
byte[] decryptedBytes = message.MessageBytes;
byte[] encryptedBytes = EncryptBytes(decryptedBytes);
message = CommunicationPrimitives.BuildMessage(encryptedBytes);
return true;
}
return false;
}
/// <summary>
/// Performs operations on the message buffer, modifying it.
/// </summary>
/// <param name="client">Client connection.</param>
/// <param name="message">Message to be processed.</param>
/// <returns>True if no other handler should process it further.</returns>
public bool PreHandleReceivedMessage(TcpClient client, ref Message message)
{
if(connectionState == ConnectionState.Encrypted)
{
byte[] encryptedBytes = message.MessageBytes;
byte[] decryptedBytes = DecryptBytes(encryptedBytes);
message = CommunicationPrimitives.BuildMessage(decryptedBytes);
return false;
}
return false;
}
/// <summary>
/// Handle the received message.
/// </summary>
/// <param name="client">Client connection.</param>
/// <param name="message">Message.</param>
/// <returns>True if no other handler should handle the message further.</returns>
public bool HandleReceivedMessage(TcpClient client, Message message)
{
if(connectionState == ConnectionState.RequestingPublicKey)
{
string ascii = ASCIIEncoding.ASCII.GetString(message.MessageBytes);
if (ascii.Contains(CommunicationPrimitives.SendPublicKey))
{
byte[] publicKeyBytes = new byte[message.MessageLength - CommunicationPrimitives.SendPublicKey.Length - 1];
Array.Copy(message.MessageBytes, message.MessageLength + 1, publicKeyBytes, 0, publicKeyBytes.Length);
string publicKey = ASCIIEncoding.ASCII.GetString(publicKeyBytes);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024);
RSAParameters parameters = new RSAParameters();
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(publicKey);
if (xmlDoc.DocumentElement.Name.Equals("RSAKeyValue"))
{
foreach (XmlNode node in xmlDoc.DocumentElement.ChildNodes)
{
switch (node.Name)
{
case "Modulus": parameters.Modulus = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
case "Exponent": parameters.Exponent = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
case "P": parameters.P = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
case "Q": parameters.Q = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
case "DP": parameters.DP = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
case "DQ": parameters.DQ = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
case "InverseQ": parameters.InverseQ = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
case "D": parameters.D = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
}
}
}
rsa.ImportParameters(parameters);
byte[] symkeyBytes = GetUniqueByteKey(32);
byte[] encryptedSymKey = rsa.Encrypt(symkeyBytes, false);
aesKey = symkeyBytes;
byte[] messageHeader = ASCIIEncoding.ASCII.GetBytes(CommunicationPrimitives.SendEncryptionKey + ":");
byte[] messageBytes = new byte[encryptedSymKey.Length + messageHeader.Length];
Array.Copy(messageHeader, 0, messageBytes, 0, messageHeader.Length);
Array.Copy(encryptedSymKey, 0, messageBytes, messageHeader.Length, encryptedSymKey.Length);
Message sendMessage = CommunicationPrimitives.BuildMessage(messageBytes);
CommunicationPrimitives.SendMessage(client, sendMessage);
connectionState = ConnectionState.NegotiatingSymKey;
return true;
}
}
else if(connectionState == ConnectionState.NegotiatingSymKey)
{
string ascii = ASCIIEncoding.ASCII.GetString(message.MessageBytes);
if(ascii == CommunicationPrimitives.AcceptEncryptionKey)
{
connectionState = ConnectionState.Encrypted;
return true;
}
else
{
connectionState = ConnectionState.Initial;
return false;
}
}
return false;
}
/// <summary>
/// Called on every tick by the client object.
/// Performs regular operations.
/// </summary>
/// <param name="client">Client connection.</param>
public void Tick(TcpClient client)
{
if(connectionState == ConnectionState.Initial)
{
Message message = CommunicationPrimitives.BuildMessage(ASCIIEncoding.ASCII.GetBytes(CommunicationPrimitives.RequestPublicKey));
CommunicationPrimitives.SendMessage(client, message);
this.connectionState = ConnectionState.RequestingPublicKey;
}
}
private string GetUniqueKey(int size)
{
return Convert.ToBase64String(GetUniqueByteKey(size));
}
private byte[] GetUniqueByteKey(int size)
{
byte[] data = new byte[size];
using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
{
crypto.GetBytes(data);
}
return data;
}
private byte[] EncryptBytes(byte[] bytesToBeEncrypted)
{
byte[] encryptedBytes = null;
// Set your salt here, change it to meet your flavor:
// The salt bytes must be at least 8 bytes.
byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
using (MemoryStream ms = new MemoryStream())
{
using (RijndaelManaged AES = new RijndaelManaged())
{
AES.KeySize = 256;
AES.BlockSize = 128;
AES.Mode = CipherMode.CBC;
var key = new Rfc2898DeriveBytes(aesKey, saltBytes, 1000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);
using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
cs.Close();
}
encryptedBytes = ms.ToArray();
}
}
return encryptedBytes;
}
private byte[] DecryptBytes(byte[] bytesToBeDecrypted)
{
byte[] decryptedBytes = null;
// Set your salt here, change it to meet your flavor:
// The salt bytes must be at least 8 bytes.
byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
using (MemoryStream ms = new MemoryStream())
{
using (RijndaelManaged AES = new RijndaelManaged())
{
AES.KeySize = 256;
AES.BlockSize = 128;
AES.Mode = CipherMode.CBC;
var key = new Rfc2898DeriveBytes(aesKey, saltBytes, 1000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);
using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
cs.Close();
}
decryptedBytes = ms.ToArray();
}
}
return decryptedBytes;
}
private string EncryptText(string input)
{
// Get the bytes of the string
byte[] bytesToBeEncrypted = Encoding.UTF8.GetBytes(input);
byte[] bytesEncrypted = EncryptBytes(bytesToBeEncrypted);
string result = Encoding.UTF8.GetString(bytesEncrypted);
return result;
}
private string DecryptText(string input)
{
// Get the bytes of the string
byte[] bytesToBeDecrypted = Encoding.UTF8.GetBytes(input);
byte[] bytesDecrypted = DecryptBytes(bytesToBeDecrypted);
string result = Encoding.UTF8.GetString(bytesDecrypted);
return result;
}
}
}
+3 -3
View File
@@ -23,7 +23,7 @@ namespace MTSC.Client.Handlers
/// <param name="client">Client socket.</param>
/// <param name="message">Message to be sent.</param>
/// <returns></returns>
bool OnSend(TcpClient client, out Message message);
bool HandleSendMessage(TcpClient client, ref Message message);
/// <summary>
/// Called before the message is handled.
/// Use this method to modify the message if necesarry.
@@ -31,7 +31,7 @@ namespace MTSC.Client.Handlers
/// <param name="client">Client object.</param>
/// <param name="message">Message to be preprocessed.</param>
/// <returns>True if the message has been preprocessed and no other handler should modify the message.</returns>
bool PreHandleReceivedMessage(TcpClient client, out Message message);
bool PreHandleReceivedMessage(TcpClient client, ref Message message);
/// <summary>
/// Called when a message has been received.
/// </summary>
@@ -48,6 +48,6 @@ namespace MTSC.Client.Handlers
/// Called when the client is disconnected.
/// </summary>
/// <param name="client">Client object.</param>
void OnDisconnect(TcpClient client);
void Disconnected(TcpClient client);
}
}
+1
View File
@@ -12,6 +12,7 @@
<AssemblyVersion>0.0.5.0</AssemblyVersion>
<FileVersion>0.0.5.0</FileVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
</Project>
+28 -8
View File
@@ -38,22 +38,35 @@ namespace MTSC.Server.Handlers
public EncryptionHandler(RSACryptoServiceProvider rsa)
{
additionalData = new Dictionary<ClientStruct, AdditionalData>();
privateKey = rsa.ToXmlString(true);
publicKey = rsa.ToXmlString(false);
privateKey = HelperFunctions.ToXmlString(rsa, true);
publicKey = HelperFunctions.ToXmlString(rsa, false);
}
#endregion
#region Public Methods
/// <summary>
/// Called when a client is being removed.
/// </summary>
/// <param name="client">Client to be removed.</param>
public void ClientRemoved(ClientStruct client)
{
additionalData.Remove(client);
}
/// <summary>
/// Handle a new client connection.
/// </summary>
/// <param name="client">New client connection.</param>
/// <returns>False if an error occurred.</returns>
public bool HandleClient(ClientStruct client)
{
additionalData.Add(client, new AdditionalData());
return false;
}
/// <summary>
/// Handle a received message.
/// </summary>
/// <param name="client">Client connection.</param>
/// <param name="message">Received message.</param>
/// <returns>True if no other handler should handle current message.</returns>
public bool HandleMessage(ClientStruct client, Message message)
{
if (additionalData[client].ClientState == ClientState.Initial || additionalData[client].ClientState == ClientState.Negotiating)
@@ -68,8 +81,8 @@ namespace MTSC.Server.Handlers
}
else if (asciiMessage.Contains(CommunicationPrimitives.SendEncryptionKey))
{
byte[] encryptedKey = new byte[message.MessageLength - CommunicationPrimitives.SendEncryptionKey.Length];
Array.Copy(message.MessageBytes, CommunicationPrimitives.SendEncryptionKey.Length, encryptedKey, 0, encryptedKey.Length);
byte[] encryptedKey = new byte[message.MessageLength - CommunicationPrimitives.SendEncryptionKey.Length - 1];
Array.Copy(message.MessageBytes, CommunicationPrimitives.SendEncryptionKey.Length + 1, encryptedKey, 0, encryptedKey.Length);
byte[] decryptedKey = rsa.Decrypt(encryptedKey, false);
additionalData[client].Key = decryptedKey;
Message sendMessage = CommunicationPrimitives.BuildMessage(ASCIIEncoding.ASCII.GetBytes(CommunicationPrimitives.AcceptEncryptionKey));
@@ -87,7 +100,12 @@ namespace MTSC.Server.Handlers
return false;
}
}
/// <summary>
/// Perform transformative operations on the message.
/// </summary>
/// <param name="client">Client connection.</param>
/// <param name="message">Message to be processed.</param>
/// <returns>True if no other handlers should perform operations on current message.</returns>
public bool PreHandleMessage(ClientStruct client, ref Message message)
{
if(additionalData[client].ClientState == ClientState.Encrypted)
@@ -108,7 +126,9 @@ namespace MTSC.Server.Handlers
return false;
}
}
/// <summary>
/// Performs periodic operations on the server.
/// </summary>
public void Tick()
{