From 9564e2002d3b7e82bc2be8634cc8f0fef40d6613 Mon Sep 17 00:00:00 2001 From: Jeroen Saey Date: Thu, 21 Aug 2025 07:45:14 +0200 Subject: [PATCH] Added AesPassword --- EonaCat.Connections.Client/Program.cs | 133 ++-- EonaCat.Connections.Server/Program.cs | 3 +- EonaCat.Connections/Helpers/AesKeyExchange.cs | 20 +- EonaCat.Connections/Models/Configuration.cs | 3 +- EonaCat.Connections/NetworkClient.cs | 530 +++++++------- EonaCat.Connections/NetworkServer.cs | 690 +++++++++--------- 6 files changed, 696 insertions(+), 683 deletions(-) diff --git a/EonaCat.Connections.Client/Program.cs b/EonaCat.Connections.Client/Program.cs index 7dfdaf6..05041f5 100644 --- a/EonaCat.Connections.Client/Program.cs +++ b/EonaCat.Connections.Client/Program.cs @@ -1,27 +1,27 @@ -using EonaCat.Connections; -using EonaCat.Connections.Models; -using System.Text; - -namespace EonaCat.Connections.Client.Example -{ - public class Program - { - private static NetworkClient _client; - - public static async Task Main(string[] args) - { - await CreateClientAsync().ConfigureAwait(false); - - while (true) - { - Console.Write("Enter message to send (or 'exit' to quit): "); - var message = Console.ReadLine(); - if (!string.IsNullOrEmpty(message) && message.Equals("exit", StringComparison.OrdinalIgnoreCase)) - { - await _client.DisconnectAsync().ConfigureAwait(false); - break; - } - +using EonaCat.Connections; +using EonaCat.Connections.Models; +using System.Text; + +namespace EonaCat.Connections.Client.Example +{ + public class Program + { + private static NetworkClient _client; + + public static async Task Main(string[] args) + { + await CreateClientAsync().ConfigureAwait(false); + + while (true) + { + Console.Write("Enter message to send (or 'exit' to quit): "); + var message = Console.ReadLine(); + if (!string.IsNullOrEmpty(message) && message.Equals("exit", StringComparison.OrdinalIgnoreCase)) + { + await _client.DisconnectAsync().ConfigureAwait(false); + break; + } + var jsonUrl = "https://microsoftedge.github.io/Demos/json-dummy-data/5MB-min.json"; try @@ -35,46 +35,47 @@ namespace EonaCat.Connections.Client.Example catch (Exception ex) { Console.WriteLine($"Failed to download large JSON file: {ex.Message}"); - } - - if (!string.IsNullOrEmpty(message)) - { - await _client.SendAsync(message).ConfigureAwait(false); - } - } - } - - private static async Task CreateClientAsync() - { - var config = new Configuration - { - Protocol = ProtocolType.TCP, - Host = "127.0.0.1", - Port = 1111, - UseSsl = false, - UseAesEncryption = false, - //ServerCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("client.pfx", "p@ss"), - }; - - _client = new NetworkClient(config); - - // Subscribe to events - _client.OnConnected += (sender, e) => - Console.WriteLine($"Connected to server at {e.RemoteEndPoint}"); - - _client.OnDataReceived += (sender, e) => - Console.WriteLine($"Server says: {(e.IsBinary ? $"{e.Data.Length} bytes" : e.StringData)}"); - - _client.OnDisconnected += (sender, e) => - Console.WriteLine("Disconnected from server"); - - await _client.ConnectAsync(); - - // Send nickname - await _client.SendNicknameAsync("TestUser"); - - // Send a message - await _client.SendAsync("Hello server!"); - } - } + } + + if (!string.IsNullOrEmpty(message)) + { + await _client.SendAsync(message).ConfigureAwait(false); + } + } + } + + private static async Task CreateClientAsync() + { + var config = new Configuration + { + Protocol = ProtocolType.TCP, + Host = "127.0.0.1", + Port = 1111, + UseSsl = false, + UseAesEncryption = true, + AesPassword = "p@ss", + //ServerCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("client.pfx", "p@ss"), + }; + + _client = new NetworkClient(config); + + // Subscribe to events + _client.OnConnected += (sender, e) => + Console.WriteLine($"Connected to server at {e.RemoteEndPoint}"); + + _client.OnDataReceived += (sender, e) => + Console.WriteLine($"Server says: {(e.IsBinary ? $"{e.Data.Length} bytes" : e.StringData)}"); + + _client.OnDisconnected += (sender, e) => + Console.WriteLine("Disconnected from server"); + + await _client.ConnectAsync(); + + // Send nickname + await _client.SendNicknameAsync("TestUser"); + + // Send a message + await _client.SendAsync("Hello server!"); + } + } } \ No newline at end of file diff --git a/EonaCat.Connections.Server/Program.cs b/EonaCat.Connections.Server/Program.cs index 98c32be..747cd82 100644 --- a/EonaCat.Connections.Server/Program.cs +++ b/EonaCat.Connections.Server/Program.cs @@ -37,8 +37,9 @@ namespace EonaCat.Connections.Server.Example Protocol = ProtocolType.TCP, Port = 1111, UseSsl = false, - UseAesEncryption = false, + UseAesEncryption = true, MaxConnections = 100000, + AesPassword = "p@ss", //ServerCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("server.pfx", "p@ss"), }; diff --git a/EonaCat.Connections/Helpers/AesKeyExchange.cs b/EonaCat.Connections/Helpers/AesKeyExchange.cs index 80e8026..80ea79f 100644 --- a/EonaCat.Connections/Helpers/AesKeyExchange.cs +++ b/EonaCat.Connections/Helpers/AesKeyExchange.cs @@ -13,7 +13,7 @@ namespace EonaCat.Connections.Helpers /// /// /// - public static async Task SendAesKeyAsync(Stream stream, Aes aes) + public static async Task SendAesKeyAsync(Stream stream, Aes aes, string password = null) { var rawKey = aes.Key; var iv = aes.IV; @@ -29,8 +29,12 @@ namespace EonaCat.Connections.Helpers await WriteBytesWithLengthAsync(stream, salt); await stream.FlushAsync(); - // Derive stronger key using PBKDF2-SHA256 + salt + pepper - var derivedKey = PBKDF2_SHA256(Combine(rawKey, Encoding.UTF8.GetBytes(Pepper)), salt, 100_000, 32); + // Derive key using PBKDF2-SHA256 + salt + password + pepper + if (string.IsNullOrEmpty(password)) + { + password = "EonaCat.Connections"; + } + var derivedKey = PBKDF2_SHA256(Combine(Combine(rawKey, Encoding.UTF8.GetBytes(password)), Encoding.UTF8.GetBytes(Pepper)), salt, 100_000, 32); aes.Key = derivedKey; return aes; @@ -41,13 +45,19 @@ namespace EonaCat.Connections.Helpers /// /// /// - public static async Task ReceiveAesKeyAsync(Stream stream) + public static async Task ReceiveAesKeyAsync(Stream stream, string password = null) { var rawKey = await ReadBytesWithLengthAsync(stream); var iv = await ReadBytesWithLengthAsync(stream); var salt = await ReadBytesWithLengthAsync(stream); - var derivedKey = PBKDF2_SHA256(Combine(rawKey, Encoding.UTF8.GetBytes(Pepper)), salt, 100_000, 32); + if (string.IsNullOrEmpty(password)) + { + password = "EonaCat.Connections"; + } + + // Derived key using PBKDF2-SHA256 + salt + password + pepper + var derivedKey = PBKDF2_SHA256(Combine(Combine(rawKey, Encoding.UTF8.GetBytes(password)), Encoding.UTF8.GetBytes(Pepper)), salt, 100_000, 32); Aes _aesEncryption = Aes.Create(); _aesEncryption.Key = derivedKey; diff --git a/EonaCat.Connections/Models/Configuration.cs b/EonaCat.Connections/Models/Configuration.cs index 51c623d..54ee2be 100644 --- a/EonaCat.Connections/Models/Configuration.cs +++ b/EonaCat.Connections/Models/Configuration.cs @@ -22,6 +22,7 @@ namespace EonaCat.Connections.Models public bool EnableNagle { get; set; } = false; // For testing purposes, allow self-signed certificates - public bool IsSelfSignedEnabled { get; set; } = true; + public bool IsSelfSignedEnabled { get; set; } = true; + public string AesPassword { get; set; } } } \ No newline at end of file diff --git a/EonaCat.Connections/NetworkClient.cs b/EonaCat.Connections/NetworkClient.cs index 2936634..d80fb02 100644 --- a/EonaCat.Connections/NetworkClient.cs +++ b/EonaCat.Connections/NetworkClient.cs @@ -1,148 +1,148 @@ -using EonaCat.Connections.EventArguments; -using EonaCat.Connections.Helpers; -using EonaCat.Connections.Models; -using System.Collections.Concurrent; -using System.Net; -using System.Net.Security; -using System.Net.Sockets; -using System.Security.Authentication; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using ErrorEventArgs = EonaCat.Connections.EventArguments.ErrorEventArgs; - -namespace EonaCat.Connections -{ - public class NetworkClient - { - private readonly Configuration _config; - private TcpClient _tcpClient; - private UdpClient _udpClient; - private Stream _stream; - private Aes _aesEncryption; - private CancellationTokenSource _cancellation; - private bool _isConnected; - - public event EventHandler OnConnected; - public event EventHandler OnDataReceived; - public event EventHandler OnDisconnected; - public event EventHandler OnSslError; - public event EventHandler OnEncryptionError; - public event EventHandler OnGeneralError; - - public NetworkClient(Configuration config) - { - _config = config; - } - - public async Task ConnectAsync() - { - _cancellation = new CancellationTokenSource(); - - if (_config.Protocol == ProtocolType.TCP) - { - await ConnectTcpAsync(); - } - else - { - await ConnectUdp(); - } - } - - private async Task ConnectTcpAsync() - { - try - { - _tcpClient = new TcpClient(); - await _tcpClient.ConnectAsync(_config.Host, _config.Port); - - Stream stream = _tcpClient.GetStream(); - - // Setup SSL if required - if (_config.UseSsl) - { - try - { - var sslStream = new SslStream(stream, false, userCertificateValidationCallback:RemoteCertificateValidationCallback); - await sslStream.AuthenticateAsClientAsync(_config.Host); - stream = sslStream; - } - catch (Exception ex) - { - OnSslError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "SSL authentication failed" }); - return; - } - } - - // Setup AES encryption if required - if (_config.UseAesEncryption) - { - try - { - _aesEncryption = await AesKeyExchange.ReceiveAesKeyAsync(stream); - } - catch (Exception ex) - { - OnEncryptionError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "AES setup failed" }); - return; - } - } - - _stream = stream; - _isConnected = true; - - OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = "self", RemoteEndPoint = new IPEndPoint(IPAddress.Parse(_config.Host), _config.Port) }); - - // Start receiving data - _ = Task.Run(() => ReceiveDataAsync(), _cancellation.Token); - } - catch (Exception ex) - { - OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Failed to connect" }); - } - } - - private bool RemoteCertificateValidationCallback(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) - { - if (_config.IsSelfSignedEnabled) - { - return true; // Accept self-signed certificates - } - - if (sslPolicyErrors == SslPolicyErrors.None) - { - return true; // Certificate is valid - } - - // Log or handle the SSL error as needed - OnSslError?.Invoke(this, new ErrorEventArgs - { - Exception = new AuthenticationException("SSL certificate validation failed"), - Message = $"SSL Policy Errors: {sslPolicyErrors}" - }); - return false; // Reject the certificate - } - - private async Task ConnectUdp() - { - try - { - _udpClient = new UdpClient(); - _udpClient.Connect(_config.Host, _config.Port); - _isConnected = true; - - OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = "self", RemoteEndPoint = new IPEndPoint(IPAddress.Parse(_config.Host), _config.Port) }); - - // Start receiving data - _ = Task.Run(() => ReceiveUdpDataAsync(), _cancellation.Token); - } - catch (Exception ex) - { - OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Failed to connect UDP" }); - } - } - - +using EonaCat.Connections.EventArguments; +using EonaCat.Connections.Helpers; +using EonaCat.Connections.Models; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using ErrorEventArgs = EonaCat.Connections.EventArguments.ErrorEventArgs; + +namespace EonaCat.Connections +{ + public class NetworkClient + { + private readonly Configuration _config; + private TcpClient _tcpClient; + private UdpClient _udpClient; + private Stream _stream; + private Aes _aesEncryption; + private CancellationTokenSource _cancellation; + private bool _isConnected; + + public event EventHandler OnConnected; + public event EventHandler OnDataReceived; + public event EventHandler OnDisconnected; + public event EventHandler OnSslError; + public event EventHandler OnEncryptionError; + public event EventHandler OnGeneralError; + + public NetworkClient(Configuration config) + { + _config = config; + } + + public async Task ConnectAsync() + { + _cancellation = new CancellationTokenSource(); + + if (_config.Protocol == ProtocolType.TCP) + { + await ConnectTcpAsync(); + } + else + { + await ConnectUdp(); + } + } + + private async Task ConnectTcpAsync() + { + try + { + _tcpClient = new TcpClient(); + await _tcpClient.ConnectAsync(_config.Host, _config.Port); + + Stream stream = _tcpClient.GetStream(); + + // Setup SSL if required + if (_config.UseSsl) + { + try + { + var sslStream = new SslStream(stream, false, userCertificateValidationCallback:RemoteCertificateValidationCallback); + await sslStream.AuthenticateAsClientAsync(_config.Host); + stream = sslStream; + } + catch (Exception ex) + { + OnSslError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "SSL authentication failed" }); + return; + } + } + + // Setup AES encryption if required + if (_config.UseAesEncryption) + { + try + { + _aesEncryption = await AesKeyExchange.ReceiveAesKeyAsync(stream, _config.AesPassword); + } + catch (Exception ex) + { + OnEncryptionError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "AES setup failed" }); + return; + } + } + + _stream = stream; + _isConnected = true; + + OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = "self", RemoteEndPoint = new IPEndPoint(IPAddress.Parse(_config.Host), _config.Port) }); + + // Start receiving data + _ = Task.Run(() => ReceiveDataAsync(), _cancellation.Token); + } + catch (Exception ex) + { + OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Failed to connect" }); + } + } + + private bool RemoteCertificateValidationCallback(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) + { + if (_config.IsSelfSignedEnabled) + { + return true; // Accept self-signed certificates + } + + if (sslPolicyErrors == SslPolicyErrors.None) + { + return true; // Certificate is valid + } + + // Log or handle the SSL error as needed + OnSslError?.Invoke(this, new ErrorEventArgs + { + Exception = new AuthenticationException("SSL certificate validation failed"), + Message = $"SSL Policy Errors: {sslPolicyErrors}" + }); + return false; // Reject the certificate + } + + private async Task ConnectUdp() + { + try + { + _udpClient = new UdpClient(); + _udpClient.Connect(_config.Host, _config.Port); + _isConnected = true; + + OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = "self", RemoteEndPoint = new IPEndPoint(IPAddress.Parse(_config.Host), _config.Port) }); + + // Start receiving data + _ = Task.Run(() => ReceiveUdpDataAsync(), _cancellation.Token); + } + catch (Exception ex) + { + OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Failed to connect UDP" }); + } + } + + private async Task ReceiveDataAsync() { while (!_cancellation.Token.IsCancellationRequested && _isConnected) @@ -209,29 +209,29 @@ namespace EonaCat.Connections } return offset; } - - - private async Task ReceiveUdpDataAsync() - { - while (!_cancellation.Token.IsCancellationRequested && _isConnected) - { - try - { - var result = await _udpClient.ReceiveAsync(); - await ProcessReceivedDataAsync(result.Buffer); - } - catch (Exception ex) - { - OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error receiving data" }); - _isConnected = false; - - // Start reconnect - _ = Task.Run(() => AutoReconnectAsync()); - break; - } - } - } - + + + private async Task ReceiveUdpDataAsync() + { + while (!_cancellation.Token.IsCancellationRequested && _isConnected) + { + try + { + var result = await _udpClient.ReceiveAsync(); + await ProcessReceivedDataAsync(result.Buffer); + } + catch (Exception ex) + { + OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error receiving data" }); + _isConnected = false; + + // Start reconnect + _ = Task.Run(() => AutoReconnectAsync()); + break; + } + } + } + private async Task ProcessReceivedDataAsync(byte[] data) { try @@ -269,8 +269,8 @@ namespace EonaCat.Connections OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error processing data" }); } } - - + + public async Task SendAsync(byte[] data) { if (!_isConnected) return; @@ -310,99 +310,99 @@ namespace EonaCat.Connections else OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error sending data" }); } - } - - - public async Task SendAsync(string message) - { - await SendAsync(Encoding.UTF8.GetBytes(message)); - } - - public async Task SendNicknameAsync(string nickname) - { - await SendAsync($"NICKNAME:{nickname}"); - } - - private async Task EncryptDataAsync(byte[] data, Aes aes) - { - using (var encryptor = aes.CreateEncryptor()) - using (var ms = new MemoryStream()) - using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) - { - await cs.WriteAsync(data, 0, data.Length); - cs.FlushFinalBlock(); - return ms.ToArray(); - } - } - - private async Task DecryptDataAsync(byte[] data, Aes aes) - { - using (var decryptor = aes.CreateDecryptor()) - using (var ms = new MemoryStream(data)) - using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) - using (var result = new MemoryStream()) - { - await cs.CopyToAsync(result); - return result.ToArray(); - } - } - - private async Task AutoReconnectAsync() - { - if (!_config.EnableAutoReconnect) - { - return; - } - - int attempt = 0; - - while (!_isConnected && (_config.MaxReconnectAttempts == 0 || attempt < _config.MaxReconnectAttempts)) - { - attempt++; - try - { - OnGeneralError?.Invoke(this, new ErrorEventArgs { Message = $"Attempting to reconnect (Attempt {attempt})" }); - await ConnectAsync(); - - if (_isConnected) - { - OnGeneralError?.Invoke(this, new ErrorEventArgs { Message = $"Reconnected successfully after {attempt} attempt(s)" }); - break; - } - } - catch - { - // Ignore exceptions, we'll retry - } - - await Task.Delay(_config.ReconnectDelayMs); - } - - if (!_isConnected) - { - OnGeneralError?.Invoke(this, new ErrorEventArgs { Message = "Failed to reconnect" }); - } - } - - - public async Task DisconnectAsync() - { - _isConnected = false; - _cancellation?.Cancel(); - _tcpClient?.Close(); - _udpClient?.Close(); - _stream?.Dispose(); - _aesEncryption?.Dispose(); - - OnDisconnected?.Invoke(this, new ConnectionEventArgs { ClientId = "self" }); - - _ = Task.Run(() => AutoReconnectAsync()); - } - - public void Dispose() - { - DisconnectAsync().Wait(); - _cancellation?.Dispose(); - } - } + } + + + public async Task SendAsync(string message) + { + await SendAsync(Encoding.UTF8.GetBytes(message)); + } + + public async Task SendNicknameAsync(string nickname) + { + await SendAsync($"NICKNAME:{nickname}"); + } + + private async Task EncryptDataAsync(byte[] data, Aes aes) + { + using (var encryptor = aes.CreateEncryptor()) + using (var ms = new MemoryStream()) + using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) + { + await cs.WriteAsync(data, 0, data.Length); + cs.FlushFinalBlock(); + return ms.ToArray(); + } + } + + private async Task DecryptDataAsync(byte[] data, Aes aes) + { + using (var decryptor = aes.CreateDecryptor()) + using (var ms = new MemoryStream(data)) + using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) + using (var result = new MemoryStream()) + { + await cs.CopyToAsync(result); + return result.ToArray(); + } + } + + private async Task AutoReconnectAsync() + { + if (!_config.EnableAutoReconnect) + { + return; + } + + int attempt = 0; + + while (!_isConnected && (_config.MaxReconnectAttempts == 0 || attempt < _config.MaxReconnectAttempts)) + { + attempt++; + try + { + OnGeneralError?.Invoke(this, new ErrorEventArgs { Message = $"Attempting to reconnect (Attempt {attempt})" }); + await ConnectAsync(); + + if (_isConnected) + { + OnGeneralError?.Invoke(this, new ErrorEventArgs { Message = $"Reconnected successfully after {attempt} attempt(s)" }); + break; + } + } + catch + { + // Ignore exceptions, we'll retry + } + + await Task.Delay(_config.ReconnectDelayMs); + } + + if (!_isConnected) + { + OnGeneralError?.Invoke(this, new ErrorEventArgs { Message = "Failed to reconnect" }); + } + } + + + public async Task DisconnectAsync() + { + _isConnected = false; + _cancellation?.Cancel(); + _tcpClient?.Close(); + _udpClient?.Close(); + _stream?.Dispose(); + _aesEncryption?.Dispose(); + + OnDisconnected?.Invoke(this, new ConnectionEventArgs { ClientId = "self" }); + + _ = Task.Run(() => AutoReconnectAsync()); + } + + public void Dispose() + { + DisconnectAsync().Wait(); + _cancellation?.Dispose(); + } + } } \ No newline at end of file diff --git a/EonaCat.Connections/NetworkServer.cs b/EonaCat.Connections/NetworkServer.cs index 3350539..cec1f23 100644 --- a/EonaCat.Connections/NetworkServer.cs +++ b/EonaCat.Connections/NetworkServer.cs @@ -1,248 +1,248 @@ -using EonaCat.Connections.EventArguments; -using EonaCat.Connections.Helpers; -using EonaCat.Connections.Models; -using System.Collections.Concurrent; -using System.Net; -using System.Net.Security; -using System.Net.Sockets; -using System.Security.Authentication; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using ErrorEventArgs = EonaCat.Connections.EventArguments.ErrorEventArgs; - -namespace EonaCat.Connections -{ - public class NetworkServer - { - private readonly Configuration _config; - private readonly Stats _stats; - private readonly ConcurrentDictionary _clients; - private TcpListener _tcpListener; - private UdpClient _udpListener; - private CancellationTokenSource _serverCancellation; - private readonly object _statsLock = new object(); - - public event EventHandler OnConnected; - public event EventHandler OnConnectedWithNickname; - public event EventHandler OnDataReceived; - public event EventHandler OnDisconnected; - public event EventHandler OnSslError; - public event EventHandler OnEncryptionError; - public event EventHandler OnGeneralError; - - public NetworkServer(Configuration config) - { - _config = config; - _stats = new Stats { StartTime = DateTime.UtcNow }; - _clients = new ConcurrentDictionary(); - } - - public Stats GetStats() - { - lock (_statsLock) - { - _stats.ActiveConnections = _clients.Count; - return _stats; - } - } - - public async Task StartAsync() - { - _serverCancellation = new CancellationTokenSource(); - - if (_config.Protocol == ProtocolType.TCP) - { - await StartTcpServerAsync(); - } - else - { - await StartUdpServerAsync(); - } - } - - private async Task StartTcpServerAsync() - { - _tcpListener = new TcpListener(IPAddress.Parse(_config.Host), _config.Port); - _tcpListener.Start(); - - Console.WriteLine($"TCP Server started on {_config.Host}:{_config.Port}"); - - while (!_serverCancellation.Token.IsCancellationRequested) - { - try - { - var tcpClient = await _tcpListener.AcceptTcpClientAsync(); - _ = Task.Run(() => HandleTcpClientAsync(tcpClient), _serverCancellation.Token); - } - catch (ObjectDisposedException) - { - break; - } - catch (Exception ex) - { - OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error accepting TCP client" }); - } - } - } - - private async Task StartUdpServerAsync() - { - _udpListener = new UdpClient(_config.Port); - Console.WriteLine($"UDP Server started on {_config.Host}:{_config.Port}"); - - while (!_serverCancellation.Token.IsCancellationRequested) - { - try - { - var result = await _udpListener.ReceiveAsync(); - _ = Task.Run(() => HandleUdpDataAsync(result), _serverCancellation.Token); - } - catch (ObjectDisposedException) - { - break; - } - catch (Exception ex) - { - OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error receiving UDP data" }); - } - } - } - - private async Task HandleTcpClientAsync(TcpClient tcpClient) - { - var clientId = Guid.NewGuid().ToString(); - var client = new Connection - { - Id = clientId, - TcpClient = tcpClient, - RemoteEndPoint = (IPEndPoint)tcpClient.Client.RemoteEndPoint, - ConnectedAt = DateTime.UtcNow, - CancellationToken = new CancellationTokenSource() - }; - - try - { - // Configure TCP client - tcpClient.NoDelay = !_config.EnableNagle; - if (_config.EnableKeepAlive) - { - tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); - } - - Stream stream = tcpClient.GetStream(); - - // Setup SSL if required - if (_config.UseSsl) - { - try - { - var sslStream = new SslStream(stream, false, userCertificateValidationCallback:RemoteCertificateValidationCallback); - await sslStream.AuthenticateAsServerAsync(_config.ServerCertificate, false, SslProtocols.Tls12, false); - stream = sslStream; - client.IsSecure = true; - } - catch (Exception ex) - { - OnSslError?.Invoke(this, new ErrorEventArgs { ClientId = clientId, Exception = ex, Message = "SSL authentication failed" }); - return; - } - } - - // Setup AES encryption if required - if (_config.UseAesEncryption) - { - try - { - client.AesEncryption = Aes.Create(); - client.AesEncryption.GenerateKey(); - client.AesEncryption.GenerateIV(); - client.IsEncrypted = true; - - // Securely send raw AES key + IV + salt - await AesKeyExchange.SendAesKeyAsync(stream, client.AesEncryption); - } - catch (Exception ex) - { - OnEncryptionError?.Invoke(this, new ErrorEventArgs - { - ClientId = clientId, - Exception = ex, - Message = "AES setup failed" - }); - return; - } - } - - client.Stream = stream; - _clients[clientId] = client; - - lock (_statsLock) - { - _stats.TotalConnections++; - } - - OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = clientId, RemoteEndPoint = client.RemoteEndPoint }); - - // Handle client communication - await HandleClientCommunicationAsync(client); - } - catch (Exception ex) - { - OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = clientId, Exception = ex, Message = "Error handling TCP client" }); - } - finally - { - await DisconnectClientAsync(clientId); - } - } - - private bool RemoteCertificateValidationCallback(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) - { - if (_config.IsSelfSignedEnabled) - { - return true; // Accept self-signed certificates - } - - if (sslPolicyErrors == SslPolicyErrors.None) - { - return true; // Certificate is valid - } - - // Log or handle the SSL error as needed - OnSslError?.Invoke(this, new ErrorEventArgs - { - Exception = new AuthenticationException("SSL certificate validation failed"), - Message = $"SSL Policy Errors: {sslPolicyErrors}" - }); - return false; // Reject the certificate - } - - private async Task HandleUdpDataAsync(UdpReceiveResult result) - { - var clientKey = result.RemoteEndPoint.ToString(); - - if (!_clients.TryGetValue(clientKey, out var client)) - { - client = new Connection - { - Id = clientKey, - RemoteEndPoint = result.RemoteEndPoint, - ConnectedAt = DateTime.UtcNow - }; - _clients[clientKey] = client; - - lock (_statsLock) - { - _stats.TotalConnections++; - } - - OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = clientKey, RemoteEndPoint = result.RemoteEndPoint }); - } - - await ProcessReceivedDataAsync(client, result.Buffer); - } - +using EonaCat.Connections.EventArguments; +using EonaCat.Connections.Helpers; +using EonaCat.Connections.Models; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using ErrorEventArgs = EonaCat.Connections.EventArguments.ErrorEventArgs; + +namespace EonaCat.Connections +{ + public class NetworkServer + { + private readonly Configuration _config; + private readonly Stats _stats; + private readonly ConcurrentDictionary _clients; + private TcpListener _tcpListener; + private UdpClient _udpListener; + private CancellationTokenSource _serverCancellation; + private readonly object _statsLock = new object(); + + public event EventHandler OnConnected; + public event EventHandler OnConnectedWithNickname; + public event EventHandler OnDataReceived; + public event EventHandler OnDisconnected; + public event EventHandler OnSslError; + public event EventHandler OnEncryptionError; + public event EventHandler OnGeneralError; + + public NetworkServer(Configuration config) + { + _config = config; + _stats = new Stats { StartTime = DateTime.UtcNow }; + _clients = new ConcurrentDictionary(); + } + + public Stats GetStats() + { + lock (_statsLock) + { + _stats.ActiveConnections = _clients.Count; + return _stats; + } + } + + public async Task StartAsync() + { + _serverCancellation = new CancellationTokenSource(); + + if (_config.Protocol == ProtocolType.TCP) + { + await StartTcpServerAsync(); + } + else + { + await StartUdpServerAsync(); + } + } + + private async Task StartTcpServerAsync() + { + _tcpListener = new TcpListener(IPAddress.Parse(_config.Host), _config.Port); + _tcpListener.Start(); + + Console.WriteLine($"TCP Server started on {_config.Host}:{_config.Port}"); + + while (!_serverCancellation.Token.IsCancellationRequested) + { + try + { + var tcpClient = await _tcpListener.AcceptTcpClientAsync(); + _ = Task.Run(() => HandleTcpClientAsync(tcpClient), _serverCancellation.Token); + } + catch (ObjectDisposedException) + { + break; + } + catch (Exception ex) + { + OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error accepting TCP client" }); + } + } + } + + private async Task StartUdpServerAsync() + { + _udpListener = new UdpClient(_config.Port); + Console.WriteLine($"UDP Server started on {_config.Host}:{_config.Port}"); + + while (!_serverCancellation.Token.IsCancellationRequested) + { + try + { + var result = await _udpListener.ReceiveAsync(); + _ = Task.Run(() => HandleUdpDataAsync(result), _serverCancellation.Token); + } + catch (ObjectDisposedException) + { + break; + } + catch (Exception ex) + { + OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error receiving UDP data" }); + } + } + } + + private async Task HandleTcpClientAsync(TcpClient tcpClient) + { + var clientId = Guid.NewGuid().ToString(); + var client = new Connection + { + Id = clientId, + TcpClient = tcpClient, + RemoteEndPoint = (IPEndPoint)tcpClient.Client.RemoteEndPoint, + ConnectedAt = DateTime.UtcNow, + CancellationToken = new CancellationTokenSource() + }; + + try + { + // Configure TCP client + tcpClient.NoDelay = !_config.EnableNagle; + if (_config.EnableKeepAlive) + { + tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); + } + + Stream stream = tcpClient.GetStream(); + + // Setup SSL if required + if (_config.UseSsl) + { + try + { + var sslStream = new SslStream(stream, false, userCertificateValidationCallback:RemoteCertificateValidationCallback); + await sslStream.AuthenticateAsServerAsync(_config.ServerCertificate, false, SslProtocols.Tls12, false); + stream = sslStream; + client.IsSecure = true; + } + catch (Exception ex) + { + OnSslError?.Invoke(this, new ErrorEventArgs { ClientId = clientId, Exception = ex, Message = "SSL authentication failed" }); + return; + } + } + + // Setup AES encryption if required + if (_config.UseAesEncryption) + { + try + { + client.AesEncryption = Aes.Create(); + client.AesEncryption.GenerateKey(); + client.AesEncryption.GenerateIV(); + client.IsEncrypted = true; + + // Securely send raw AES key + IV + salt + await AesKeyExchange.SendAesKeyAsync(stream, client.AesEncryption, _config.AesPassword); + } + catch (Exception ex) + { + OnEncryptionError?.Invoke(this, new ErrorEventArgs + { + ClientId = clientId, + Exception = ex, + Message = "AES setup failed" + }); + return; + } + } + + client.Stream = stream; + _clients[clientId] = client; + + lock (_statsLock) + { + _stats.TotalConnections++; + } + + OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = clientId, RemoteEndPoint = client.RemoteEndPoint }); + + // Handle client communication + await HandleClientCommunicationAsync(client); + } + catch (Exception ex) + { + OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = clientId, Exception = ex, Message = "Error handling TCP client" }); + } + finally + { + await DisconnectClientAsync(clientId); + } + } + + private bool RemoteCertificateValidationCallback(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) + { + if (_config.IsSelfSignedEnabled) + { + return true; // Accept self-signed certificates + } + + if (sslPolicyErrors == SslPolicyErrors.None) + { + return true; // Certificate is valid + } + + // Log or handle the SSL error as needed + OnSslError?.Invoke(this, new ErrorEventArgs + { + Exception = new AuthenticationException("SSL certificate validation failed"), + Message = $"SSL Policy Errors: {sslPolicyErrors}" + }); + return false; // Reject the certificate + } + + private async Task HandleUdpDataAsync(UdpReceiveResult result) + { + var clientKey = result.RemoteEndPoint.ToString(); + + if (!_clients.TryGetValue(clientKey, out var client)) + { + client = new Connection + { + Id = clientKey, + RemoteEndPoint = result.RemoteEndPoint, + ConnectedAt = DateTime.UtcNow + }; + _clients[clientKey] = client; + + lock (_statsLock) + { + _stats.TotalConnections++; + } + + OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = clientKey, RemoteEndPoint = result.RemoteEndPoint }); + } + + await ProcessReceivedDataAsync(client, result.Buffer); + } + private async Task HandleClientCommunicationAsync(Connection client) { var lengthBuffer = new byte[4]; // length prefix @@ -312,8 +312,8 @@ namespace EonaCat.Connections } return offset; } - - + + private async Task ProcessReceivedDataAsync(Connection client, byte[] data) { try @@ -367,36 +367,36 @@ namespace EonaCat.Connections OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = client.Id, Exception = ex, Message = "Error processing data" }); } } - - - public async Task SendToClientAsync(string clientId, byte[] data) - { - if (_clients.TryGetValue(clientId, out var client)) - { - await SendDataAsync(client, data); - } - } - - public async Task SendToClientAsync(string clientId, string message) - { - await SendToClientAsync(clientId, Encoding.UTF8.GetBytes(message)); - } - - public async Task BroadcastAsync(byte[] data) - { - var tasks = new List(); - foreach (var client in _clients.Values) - { - tasks.Add(SendDataAsync(client, data)); - } - await Task.WhenAll(tasks); - } - - public async Task BroadcastAsync(string message) - { - await BroadcastAsync(Encoding.UTF8.GetBytes(message)); - } - + + + public async Task SendToClientAsync(string clientId, byte[] data) + { + if (_clients.TryGetValue(clientId, out var client)) + { + await SendDataAsync(client, data); + } + } + + public async Task SendToClientAsync(string clientId, string message) + { + await SendToClientAsync(clientId, Encoding.UTF8.GetBytes(message)); + } + + public async Task BroadcastAsync(byte[] data) + { + var tasks = new List(); + foreach (var client in _clients.Values) + { + tasks.Add(SendDataAsync(client, data)); + } + await Task.WhenAll(tasks); + } + + public async Task BroadcastAsync(string message) + { + await BroadcastAsync(Encoding.UTF8.GetBytes(message)); + } + private async Task SendDataAsync(Connection client, byte[] data) { try @@ -442,72 +442,72 @@ namespace EonaCat.Connections else OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = client.Id, Exception = ex, Message = "Error sending data" }); } - } - - - private async Task EncryptDataAsync(byte[] data, Aes aes) - { - using (var encryptor = aes.CreateEncryptor()) - using (var ms = new MemoryStream()) - using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) - { - await cs.WriteAsync(data, 0, data.Length); - cs.FlushFinalBlock(); - return ms.ToArray(); - } - } - - private async Task DecryptDataAsync(byte[] data, Aes aes) - { - using (var decryptor = aes.CreateDecryptor()) - using (var ms = new MemoryStream(data)) - using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) - using (var result = new MemoryStream()) - { - await cs.CopyToAsync(result); - return result.ToArray(); - } - } - - private async Task DisconnectClientAsync(string clientId) - { - if (_clients.TryRemove(clientId, out var client)) - { - try - { - client.CancellationToken?.Cancel(); - client.TcpClient?.Close(); - client.Stream?.Dispose(); - client.AesEncryption?.Dispose(); - - OnDisconnected?.Invoke(this, new ConnectionEventArgs { ClientId = clientId, RemoteEndPoint = client.RemoteEndPoint }); - } - catch (Exception ex) - { - OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = clientId, Exception = ex, Message = "Error disconnecting client" }); - } - } - } - - public void Stop() - { - _serverCancellation?.Cancel(); - _tcpListener?.Stop(); - _udpListener?.Close(); - - // Disconnect all clients - var disconnectTasks = new List(); - foreach (var clientId in _clients.Keys.ToArray()) - { - disconnectTasks.Add(DisconnectClientAsync(clientId)); - } - Task.WaitAll(disconnectTasks.ToArray()); - } - - public void Dispose() - { - Stop(); - _serverCancellation?.Dispose(); - } - } + } + + + private async Task EncryptDataAsync(byte[] data, Aes aes) + { + using (var encryptor = aes.CreateEncryptor()) + using (var ms = new MemoryStream()) + using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) + { + await cs.WriteAsync(data, 0, data.Length); + cs.FlushFinalBlock(); + return ms.ToArray(); + } + } + + private async Task DecryptDataAsync(byte[] data, Aes aes) + { + using (var decryptor = aes.CreateDecryptor()) + using (var ms = new MemoryStream(data)) + using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) + using (var result = new MemoryStream()) + { + await cs.CopyToAsync(result); + return result.ToArray(); + } + } + + private async Task DisconnectClientAsync(string clientId) + { + if (_clients.TryRemove(clientId, out var client)) + { + try + { + client.CancellationToken?.Cancel(); + client.TcpClient?.Close(); + client.Stream?.Dispose(); + client.AesEncryption?.Dispose(); + + OnDisconnected?.Invoke(this, new ConnectionEventArgs { ClientId = clientId, RemoteEndPoint = client.RemoteEndPoint }); + } + catch (Exception ex) + { + OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = clientId, Exception = ex, Message = "Error disconnecting client" }); + } + } + } + + public void Stop() + { + _serverCancellation?.Cancel(); + _tcpListener?.Stop(); + _udpListener?.Close(); + + // Disconnect all clients + var disconnectTasks = new List(); + foreach (var clientId in _clients.Keys.ToArray()) + { + disconnectTasks.Add(DisconnectClientAsync(clientId)); + } + Task.WaitAll(disconnectTasks.ToArray()); + } + + public void Dispose() + { + Stop(); + _serverCancellation?.Dispose(); + } + } } \ No newline at end of file