diff --git a/EonaCat.Network/System/Sockets/Web/WebSocketSecureClient.cs b/EonaCat.Network/System/Sockets/Web/WebSocketSecureClient.cs index c90eb5d..2d4688f 100644 --- a/EonaCat.Network/System/Sockets/Web/WebSocketSecureClient.cs +++ b/EonaCat.Network/System/Sockets/Web/WebSocketSecureClient.cs @@ -1,4 +1,5 @@ using System; +using System.Net; using System.Net.WebSockets; using System.Security.Cryptography.X509Certificates; using System.Text; @@ -44,13 +45,15 @@ namespace EonaCat.Network /// /// The client certificate for the connection /// The password for the connection + /// The keepalive interval in seconds for the connection + /// The cookies to be send with the connection /// - public Task ConnectAsync(string uri, X509Certificate2 clientCertificate = null, string password = null) + public Task ConnectAsync(string uri, X509Certificate2 clientCertificate = null, string password = null, int keepAliveIntervalSeconds = 30, CookieContainer? cookieContainer = null) { - return CreateWebSocketClientAsync(uri, clientCertificate, password); + return CreateWebSocketClientAsync(uri, clientCertificate, password, keepAliveIntervalSeconds, cookieContainer); } - private async Task CreateWebSocketClientAsync(string uri, X509Certificate2 clientCertificate, string password) + private async Task CreateWebSocketClientAsync(string uri, X509Certificate2 clientCertificate, string password, int keepAliveIntervalSeconds = 30, CookieContainer? cookieContainer = null) { _webSocket = new ClientWebSocket(); @@ -70,6 +73,17 @@ namespace EonaCat.Network _webSocket.Options.SetRequestHeader("ClientName", ClientName); } + if (cookieContainer != null && cookieContainer.Count > 0) + { + // Manually set cookies in the request header + _webSocket.Options.SetRequestHeader("Cookie", cookieContainer.GetCookieHeader(new Uri(uri))); + } + + if (keepAliveIntervalSeconds > 0) + { + _webSocket.Options.KeepAliveInterval = TimeSpan.FromSeconds(keepAliveIntervalSeconds); + } + try { Uri serverUri = new Uri(uri); diff --git a/EonaCat.Network/System/Sockets/Web/WebSocketSecureServer.cs b/EonaCat.Network/System/Sockets/Web/WebSocketSecureServer.cs index 6f5351d..e9c8229 100644 --- a/EonaCat.Network/System/Sockets/Web/WebSocketSecureServer.cs +++ b/EonaCat.Network/System/Sockets/Web/WebSocketSecureServer.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Net; using System.Net.WebSockets; +using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; @@ -33,6 +34,11 @@ namespace EonaCat.Network /// public event Action OnDisconnect; + /// + /// The TLS version to be used by the server + /// + public SslProtocols TlsVersion { get; set; } = SslProtocols.Tls12; + /// /// OnError event /// @@ -48,6 +54,9 @@ namespace EonaCat.Network private readonly Dictionary _connectedClients = new Dictionary(); private readonly Dictionary _clientNames = new Dictionary(); + public CookieContainer Cookies { get; private set; } = new CookieContainer(); + public bool IsDebugHttpConnection { get; set; } + /// /// Create secure WebSocket server with certificate support /// @@ -63,7 +72,18 @@ namespace EonaCat.Network foreach (var uriPrefix in uriPrefixes) { - _listener.Prefixes.Add(uriPrefix); + if (uriPrefix.StartsWith("https")) + { + _listener.Prefixes.Add(uriPrefix); + } + else if (uriPrefix.StartsWith("wss")) + { + _listener.Prefixes.Add(uriPrefix.Replace("wss", "https")); + } + else + { + throw new ArgumentException("Invalid URI prefix. Use 'https' or 'wss' prefixes."); + } } } @@ -88,6 +108,12 @@ namespace EonaCat.Network { var context = await _listener.GetContextAsync().ConfigureAwait(false); + if (IsDebugHttpConnection) + { + // Debug connection + DebugHttpsConnection(context); + } + if (context.Request.IsWebSocketRequest) { if (_passwordProtectionEnabled) @@ -102,6 +128,12 @@ namespace EonaCat.Network } } + // Manually set cookies in the HTTP response header + if (Cookies.Count > 0) + { + context.Response.Headers.Add("Set-Cookie", Cookies.GetCookieHeader(context.Request.Url)); + } + string clientName = null; foreach (var key in context.Request.Headers.AllKeys) { @@ -128,6 +160,7 @@ namespace EonaCat.Network } } + private async Task HandleWebSocketConnectionAsync(WebSocket webSocket, string clientName, CancellationToken cancellationToken) { string clientId = Guid.NewGuid().ToString(); @@ -186,5 +219,62 @@ namespace EonaCat.Network catch (AggregateException) { } } } + + /// + /// Debug the HTTPS connection information + /// + /// HttpListenerContext representing the incoming connection + private void DebugHttpsConnection(HttpListenerContext context) + { + try + { + Console.WriteLine($"Incoming HTTPS Connection from: {context.Request.RemoteEndPoint}"); + + // Output headers + Console.WriteLine("Headers:"); + foreach (string key in context.Request.Headers.Keys) + { + Console.WriteLine($"{key}: {context.Request.Headers[key]}"); + } + + // Check if the connection is WebSocket request + if (context.Request.IsWebSocketRequest) + { + Console.WriteLine("WebSocket Request detected."); + + // Output WebSocket-specific information + if (_passwordProtectionEnabled) + { + var password = context.Request.Headers["Password"]; + Console.WriteLine($"Password: {password}"); + } + + if (Cookies.Count > 0) + { + Console.WriteLine($"Cookies: {Cookies.GetCookieHeader(context.Request.Url)}"); + } + + foreach (var key in context.Request.Headers.AllKeys) + { + if (key == "ClientName") + { + var clientName = context.Request.Headers["ClientName"]; + Console.WriteLine($"ClientName: {clientName}"); + break; + } + } + } + else + { + Console.WriteLine("Invalid WebSocket Request. Closing connection."); + context.Response.StatusCode = 400; + context.Response.Close(); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error while debugging HTTPS connection: {ex.Message}"); + } + } } } diff --git a/EonaCat.Network/System/Sockets/Web/WebSocketTransportContext.cs b/EonaCat.Network/System/Sockets/Web/WebSocketTransportContext.cs new file mode 100644 index 0000000..484d7a3 --- /dev/null +++ b/EonaCat.Network/System/Sockets/Web/WebSocketTransportContext.cs @@ -0,0 +1,9 @@ +using System.Security.Authentication; + +namespace EonaCat.Network +{ + public class WebSocketTransportContext + { + public SslProtocols TlsVersion { get; set; } + } +} diff --git a/EonaCat.Network/System/Tools/CertificateInfoHelper.cs b/EonaCat.Network/System/Tools/CertificateInfoHelper.cs new file mode 100644 index 0000000..a1d5d48 --- /dev/null +++ b/EonaCat.Network/System/Tools/CertificateInfoHelper.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace EonaCat.Network +{ + public class CertificateInfoHelper + { + public static string GetCertificatesInformation(IEnumerable certificates) + { + StringBuilder stringBuilder = new StringBuilder(); + + foreach (var certificate in certificates) + { + if (certificate != null) + { + stringBuilder.AppendLine(GetCertificateInformation(certificate)); + } + } + return stringBuilder.ToString(); + } + + public static string GetCertificateInformation(X509Certificate2 certificate) + { + StringBuilder stringBuilder = new StringBuilder(); + + stringBuilder.AppendLine("Certificate Information:"); + stringBuilder.AppendLine($"Subject: {certificate.Subject}"); + stringBuilder.AppendLine($"Issuer: {certificate.Issuer}"); + stringBuilder.AppendLine($"Serial Number: {certificate.SerialNumber}"); + stringBuilder.AppendLine($"Thumbprint: {certificate.Thumbprint}"); + stringBuilder.AppendLine($"Valid From: {certificate.NotBefore}"); + stringBuilder.AppendLine($"Valid Until: {certificate.NotAfter}"); + stringBuilder.AppendLine($"Has Private Key: {certificate.HasPrivateKey}"); + stringBuilder.AppendLine(); + + stringBuilder.AppendLine("Public Key Information:"); + stringBuilder.AppendLine($"Algorithm: {certificate.PublicKey.Key.KeyExchangeAlgorithm}"); + stringBuilder.AppendLine($"Key Size: {certificate.PublicKey.Key.KeySize}"); + stringBuilder.AppendLine(); + + stringBuilder.AppendLine("Certificate Extensions:"); + foreach (X509Extension extension in certificate.Extensions) + { + stringBuilder.AppendLine($" {extension.Oid.FriendlyName}: {extension.Format(true)}"); + } + + return stringBuilder.ToString(); + } + + public static string GetSubject(X509Certificate2 certificate) + { + return certificate.Subject; + } + + public static string GetIssuer(X509Certificate2 certificate) + { + return certificate.Issuer; + } + + public static string GetValidityPeriod(X509Certificate2 certificate) + { + return $"Valid From: {certificate.NotBefore}, Valid Until: {certificate.NotAfter}"; + } + } +} \ No newline at end of file diff --git a/EonaCat.Network/System/Tools/Helpers.cs b/EonaCat.Network/System/Tools/Helpers.cs index c091144..f182cc7 100644 --- a/EonaCat.Network/System/Tools/Helpers.cs +++ b/EonaCat.Network/System/Tools/Helpers.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; +using System.Text; using System.Text.RegularExpressions; using System.Threading;