This commit is contained in:
jsaey 2023-11-16 16:03:21 +01:00
parent d6f0c7b5af
commit 89cb9df95b
5 changed files with 185 additions and 4 deletions

View File

@ -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
/// <param name="uri"></param>
/// <param name="clientCertificate">The client certificate for the connection</param>
/// <param name="password">The password for the connection</param>
/// <param name="keepAliveIntervalSeconds">The keepalive interval in seconds for the connection</param>
/// <param name="cookieContainer">The cookies to be send with the connection</param>
/// <returns></returns>
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);

View File

@ -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
/// </summary>
public event Action<RemoteInfo> OnDisconnect;
/// <summary>
/// The TLS version to be used by the server
/// </summary>
public SslProtocols TlsVersion { get; set; } = SslProtocols.Tls12;
/// <summary>
/// OnError event
/// </summary>
@ -48,6 +54,9 @@ namespace EonaCat.Network
private readonly Dictionary<string, WebSocket> _connectedClients = new Dictionary<string, WebSocket>();
private readonly Dictionary<string, string> _clientNames = new Dictionary<string, string>();
public CookieContainer Cookies { get; private set; } = new CookieContainer();
public bool IsDebugHttpConnection { get; set; }
/// <summary>
/// Create secure WebSocket server with certificate support
/// </summary>
@ -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) { }
}
}
/// <summary>
/// Debug the HTTPS connection information
/// </summary>
/// <param name="context">HttpListenerContext representing the incoming connection</param>
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}");
}
}
}
}

View File

@ -0,0 +1,9 @@
using System.Security.Authentication;
namespace EonaCat.Network
{
public class WebSocketTransportContext
{
public SslProtocols TlsVersion { get; set; }
}
}

View File

@ -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<X509Certificate2> 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}";
}
}
}

View File

@ -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;