Skip to content

Commit

Permalink
Access Control Challenge provides multiple unique challenges to cater…
Browse files Browse the repository at this point in the history
… for multiple connections coming from the same IP address.

The HostInfo class in MessageServer now contains the challenge that was accepted.

MessageClient Challenge now uses the HostInfo class Challenge property as the backing field.

Sample Server and Sample Client projects updated accordingly.
  • Loading branch information
Jonathon Aroutsidis committed Jul 4, 2024
1 parent 5b67724 commit b503a38
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 47 deletions.
27 changes: 27 additions & 0 deletions ACS Messaging/AccessControlCheck.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/// <summary>
/// Contains return values for Access Control Check.
/// </summary>
internal class AccessControlCheck
{
/// <summary>
/// Gets or Sets if the Access Control Check passed.
/// </summary>
/// <value>
/// A <b>Boolean</b> indicating if the check passed.
/// </value>
internal bool IsPassed { get; set; } = false;
/// <summary>
/// Gets or Sets if a Challenge was processed.
/// </summary>
/// <value>
/// A <b>Boolean</b> indicating if a Challenge was used.
/// </value>
internal bool IsChallenge { get; set; } = false;
/// <summary>
/// Gets or Sets the Challenge used.
/// </summary>
/// <value>
/// A <b>String</b> containing the challenge used.
/// </value>
internal string Challenge { get; set; }
}
9 changes: 5 additions & 4 deletions ACS Messaging/AccessControlRule.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net;
using System.Collections.Generic;
using System.Net;

/// <summary>
/// Contains a rule for Access Control.
Expand All @@ -17,12 +18,12 @@ public class AccessControlRule
/// </value>
public IPAddress IPAddress => ipaddress;
/// <summary>
/// Gets or Sets a Challenge for further validation.
/// Challenges for further validation which can also be individually enabled or disabled.
/// </summary>
/// <value>
/// A <b>String</b> containing challenge response expected.
/// A <b>Dictionary<string, bool></b> containing challenges expected and if the challenge is enabled or disabled.
/// </value>
public string Challenge { get; set; }
public Dictionary<string, bool> Challenges { get; private set; } = new Dictionary<string, bool>();
/// <summary>
/// Gets or Sets the flag for enabling or disabling this rule.
/// </summary>
Expand Down
8 changes: 8 additions & 0 deletions ACS Messaging/HostInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ public int Index
set { _index = value; }
}

/// <summary>
/// Gets or Sets the Challenge used for further validation.
/// </summary>
/// <value>
/// A <b>String</b> containing a challenge.
/// </value>
public string Challenge { get; set; }

/// <summary>
/// Creates a new instance of the <see cref="HostInfo" /> class.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion ACS Messaging/MessageClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public class MessageClient : MessageClientServerBase
/// <value>
/// A <b>String</b> containing challenge response to be validated.
/// </value>
public string Challenge { get; set; }
public string Challenge { get => server.Challenge; set => server.Challenge = value; }

/// <summary>
/// Gets or Sets the flag for enabling or disabling the Challenge validation response.
Expand Down
87 changes: 47 additions & 40 deletions ACS Messaging/MessageServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -736,8 +736,8 @@ private void ProcessClient(TcpClient client)
{
SslStream securestream = default(SslStream);
NetworkStream stream = default(NetworkStream);
bool isaccesscontrolpassed = ProcessAccessControl(client);
if (isaccesscontrolpassed is false) { return; }
AccessControlCheck check = ProcessAccessControl(client);
if (check.IsPassed is false) { return; }

try
{
Expand All @@ -754,7 +754,7 @@ private void ProcessClient(TcpClient client)
if (Secure == false)
{
// Remember the client and its host information.
AddClient(client);
AddClient(client, check);
// Listen asynchronously for incoming messages from this client.
Read(buffer, client, stream);
}
Expand All @@ -764,7 +764,7 @@ private void ProcessClient(TcpClient client)
securestream = new SslStream(client.GetStream(), false);
securestream.AuthenticateAsServer(servercertificate, false, SslProtocols.Tls12, true);
// Remember the client and its host information.
AddClient(client, securestream);
AddClient(client, check, securestream);
// Listen asynchronously for incoming messages from this client.
SecureRead(buffer, client, securestream);
}
Expand Down Expand Up @@ -822,80 +822,83 @@ public void ReprocessClientAccessControl(IPAddress IPAddress)
/// <summary>
/// Checks is Access Control is Enabled and processes the client as required.
/// </summary>
private bool ProcessAccessControl(TcpClient client, bool SkipChallenge = false)
private AccessControlCheck ProcessAccessControl(TcpClient client, bool SkipChallenge = false)
{
bool isaccesscontrolpassed = false;
AccessControlCheck check = new AccessControlCheck();

if (IsAccessControlEnabled is true)
{
if (AccessControlMode == AccessControlType.Whitelist) { isaccesscontrolpassed = ProcessAccessControlWhitelist(client, SkipChallenge); }
if (AccessControlMode == AccessControlType.Blacklist) { isaccesscontrolpassed = ProcessAccessControlBlacklist(client, SkipChallenge); }
if (AccessControlMode == AccessControlType.Whitelist) { ProcessAccessControlWhitelist(client, SkipChallenge, check); }
if (AccessControlMode == AccessControlType.Blacklist) { ProcessAccessControlBlacklist(client, SkipChallenge, check); }

if (isaccesscontrolpassed is false)
if (check.IsPassed is false)
{
client.GetStream().Close();
client.Close();
return isaccesscontrolpassed;
}
}
else
{
check.IsPassed = true;
}

isaccesscontrolpassed = true;
return isaccesscontrolpassed;
return check;
}

/// <summary>
/// Check access control for Whitelist Mode.
/// </summary>
private bool ProcessAccessControlWhitelist(TcpClient client, bool SkipChallenge)
private void ProcessAccessControlWhitelist(TcpClient client, bool SkipChallenge, AccessControlCheck Check)
{
bool isaccesscontrolpassed = false;
if (accesscontrollist.Count() == 0) { return isaccesscontrolpassed; }
if (accesscontrollist.Count() == 0) { return; }
IPEndPoint ipendpoint = (IPEndPoint)client.Client.RemoteEndPoint;
IPAddress ipaddress = ipendpoint.Address;
if (accesscontrollist.ContainsKey(ipaddress) is false) { return isaccesscontrolpassed; }
if (accesscontrollist.ContainsKey(ipaddress) is false) { return; }
AccessControlRule rule = accesscontrollist[ipaddress];
if (rule.IsEnabled is false) { return isaccesscontrolpassed; }
if (rule.IsEnabled is false) { return; }

if (rule.IsChallengeEnabled is true && IsAccessControlChallengeEnabled is true && SkipChallenge is false)
{
if (ProcessAccessControlChallenge(client, rule.Challenge) is false) { return isaccesscontrolpassed; }
ProcessAccessControlChallenge(client, rule.Challenges, Check);
}
else
{
Check.IsPassed = true;
}

isaccesscontrolpassed = true;
return isaccesscontrolpassed;
}

/// <summary>
/// Check access control for Blacklist Mode.
/// </summary>
private bool ProcessAccessControlBlacklist(TcpClient client, bool SkipChallenge)
private void ProcessAccessControlBlacklist(TcpClient client, bool SkipChallenge, AccessControlCheck Check)
{
bool isaccesscontrolpassed = true;
if (accesscontrollist.Count() == 0) { return isaccesscontrolpassed; }
Check.IsPassed = true;
if (accesscontrollist.Count() == 0) { return; }
IPEndPoint ipendpoint = (IPEndPoint)client.Client.RemoteEndPoint;
IPAddress ipaddress = ipendpoint.Address;
if (accesscontrollist.ContainsKey(ipaddress) is false) { return isaccesscontrolpassed; }
if (accesscontrollist.ContainsKey(ipaddress) is false) { return; }
AccessControlRule rule = accesscontrollist[ipaddress];
if (rule.IsEnabled is false) { return isaccesscontrolpassed; }
if (rule.IsEnabled is false) { return; }

if (rule.IsChallengeEnabled is true && IsAccessControlChallengeEnabled is true && SkipChallenge is false)
{
if (ProcessAccessControlChallenge(client, rule.Challenge) is true) { return isaccesscontrolpassed; }
ProcessAccessControlChallenge(client, rule.Challenges, Check);
}
else
{
Check.IsPassed = false;
}

isaccesscontrolpassed = false;
return isaccesscontrolpassed;
}

/// <summary>
/// Check access control challenge.
/// </summary>
private bool ProcessAccessControlChallenge(TcpClient client, string challenge)
private void ProcessAccessControlChallenge(TcpClient client, Dictionary<string, bool> challenges, AccessControlCheck Check)
{
// This is not ideal as we don't have good integration.
// We need to re-architect and have full integration via message framing.
// Raw connections will not support Challenge in future (application logic will need to handle an equivalent if required).
bool isaccesscontrolpassed = false;
Check.IsPassed = false;
ChallengeRequest request = new ChallengeRequest() { ID = Guid.NewGuid().ToString(), ChallengeType = ChallengeRequest.ChallengeRequestType.ChallengeRequested };
MemoryStream ms = new MemoryStream();
ushort size;
Expand Down Expand Up @@ -940,13 +943,17 @@ private bool ProcessAccessControlChallenge(TcpClient client, string challenge)

// Restore the timeout value
client.ReceiveTimeout = receivetimeout;
if (bytecount == 0) { return isaccesscontrolpassed; }
if (bytecount == 0) { return; }
size = BitConverter.ToUInt16(readbuffer, 0);
if (bytecount != size) { return isaccesscontrolpassed; }
if (bytecount != size) { return; }
ms = new MemoryStream(readbuffer, 2, size - 2);
// Because we can't use generics - lazyily generating new object
response = (ChallengeResponse)Json.DeserializeAsync(ms, new ChallengeResponse()).Result;
if (response.ID.Equals(request.ID) is false || challenge.Equals(response.Challenge) is false) { return isaccesscontrolpassed; }
if (response.ID.Equals(request.ID) is false) { return; }
if (challenges.ContainsKey(response.Challenge) is false) { return; }
if (challenges[response.Challenge] is false) { return; }
Check.Challenge = response.Challenge;
Check.IsChallenge = true;
}
catch (IOException IOe)
{
Expand All @@ -956,12 +963,12 @@ private bool ProcessAccessControlChallenge(TcpClient client, string challenge)
if (se.SocketErrorCode != SocketError.TimedOut) { OnLog(new LogEventArgs(DateTime.Now, "ERROR", se.Message)); }
}

return isaccesscontrolpassed;
return;
}
catch (Exception Ex)
{
OnLog(new LogEventArgs(DateTime.Now, "ERROR", Ex.Message));
return isaccesscontrolpassed;
return;
}

try
Expand Down Expand Up @@ -991,8 +998,7 @@ private bool ProcessAccessControlChallenge(TcpClient client, string challenge)
OnLog(new LogEventArgs(DateTime.Now, "ERROR", Ex.Message));
}

isaccesscontrolpassed = true;
return isaccesscontrolpassed;
Check.IsPassed = true;
}

/// <summary>
Expand Down Expand Up @@ -1217,7 +1223,7 @@ private async void SecureRead(byte[] buffer, TcpClient client, SslStream secures
/// <param name="securestream">
/// Optional Secure Stream object.
/// </param>
private void AddClient(TcpClient client, SslStream securestream = null)
private void AddClient(TcpClient client, AccessControlCheck Check, SslStream securestream = null)
{
HostInfo host = default(HostInfo);

Expand All @@ -1230,6 +1236,7 @@ private void AddClient(TcpClient client, SslStream securestream = null)
// Create a new host record
IPEndPoint HostAddress = (IPEndPoint)client.Client.RemoteEndPoint;
host = new HostInfo(HostAddress.Address.ToString(), HostAddress.Port, Secure, securestream);
if (Check.IsChallenge is true) { host.Challenge = Check.Challenge; }
// Remember the client and its host information.
clients.Add(client, host);
// Notify any listeners that a connection has been made.
Expand Down
3 changes: 3 additions & 0 deletions Sample Client/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
case "SetChallengeBob":
challenge = "bob";
break;
case "SetChallengeBob1":
challenge = "bob1";
break;
case "Exit":
isrunning = false;
break;
Expand Down
7 changes: 5 additions & 2 deletions Sample Server/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,13 @@
ms.RemoveAccessControlRule(new(IPAddress.Parse("127.0.0.1")) { IsEnabled = true, IsChallengeEnabled = false });
break;
case "AddACLRuleWithChallenge":
ms.AddAccessControlRule(new(IPAddress.Parse("127.0.0.1")) { IsEnabled = true, IsChallengeEnabled = true, Challenge = "bob" });
AccessControlRule rule = new(IPAddress.Parse("127.0.0.1")) { IsEnabled = true, IsChallengeEnabled = true };
rule.Challenges.Add("bob", false);
rule.Challenges.Add("bob1", true);
ms.AddAccessControlRule(rule);
break;
case "RemoveACLRuleWithChallenge":
ms.RemoveAccessControlRule(new(IPAddress.Parse("127.0.0.1")) { IsEnabled = true, IsChallengeEnabled = true, Challenge = "bob" });
ms.RemoveAccessControlRule(new(IPAddress.Parse("127.0.0.1")) { IsEnabled = true, IsChallengeEnabled = true });
break;
case "Exit":
isrunning = false;
Expand Down

0 comments on commit b503a38

Please sign in to comment.