где я пытаюсь реализовать HTTPS-сервер
HTTPServer
Код: Выделить всё
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using OpenSsl;
namespace RawHttpListener
{
#region HTTPParser
public interface IHTTPClientHandler
{
Task HTTPClientConnected(HttpContext ctx);
}
public class HttpContext
{
public HttpRequest Request { get; set; }
public HttpResponse Response { get; set; }
public HttpContext(HttpRequest request, HttpResponse response)
{
Request = request;
Response = response;
}
}
public class HttpRequest
{
public string HttpMethod { get; set; }
public string Url { get; set; }
public string UserHostName { get; set; }
public string UserAgent { get; set; }
public string Body { get; set; }
public HttpRequest(string httpMethod, string url, string userHostName, string userAgent)
{
HttpMethod = httpMethod;
Url = url;
UserHostName = userHostName;
UserAgent = userAgent;
}
}
public class HttpResponse
{
public string ContentType { get; set; }
public Encoding ContentEncoding { get; set; }
public long ContentLength64 { get; set; }
public Stream OutputStream { get; set; }
public HttpResponse(Stream outputStream)
{
OutputStream = outputStream;
}
}
public class HTTPParser : Stream
{
private readonly IHTTPClientHandler _clientHandler;
private readonly Stream _baseStream;
private readonly MemoryStream _memoryStream = new MemoryStream();
private const int BufferSize = 4096;
private readonly byte[] _buffer = new byte[BufferSize];
public HTTPParser(Stream baseStream, IHTTPClientHandler clientHandler)
{
_baseStream = baseStream;
_clientHandler = clientHandler;
}
public async Task HandleClientConnected()
{
int bytesRead;
// Read the incoming data until the connection is closed
while ((bytesRead = await _baseStream.ReadAsync(_buffer, 0, _buffer.Length)) > 0)
{
await _memoryStream.WriteAsync(_buffer, 0, bytesRead);
// Check for the end of the request (empty line means headers end)
if (bytesRead < BufferSize) break;
}
// Convert the raw request to a string
string rawRequest = Encoding.UTF8.GetString(_memoryStream.ToArray());
//Console.WriteLine("Raw HTTP Request:");
//Console.WriteLine(rawRequest);
// Handle the HTTP request
await HandleHttpRequest(rawRequest);
}
private async Task HandleHttpRequest(string rawRequest)
{
// Fill the MyOwnHttpListenerContext object and call the HTTPClientConnected method
var lines = rawRequest.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
if (lines.Length == 0) return;
// Parse the request line (first line)
var requestLine = lines[0].Split(' ');
if (requestLine.Length < 3) return;
string method = requestLine[0];
string url = requestLine[1];
string httpVersion = requestLine[2];
// Extract headers
string userHostName = string.Empty;
string userAgent = string.Empty;
for (int i = 1; i < lines.Length; i++) // Skip the request line
{
if (lines[i].StartsWith("Host:"))
{
userHostName = lines[i].Substring(6).Trim(); // Skip "Host: "
}
else if (lines[i].StartsWith("User-Agent:"))
{
userAgent = lines[i].Substring(12).Trim(); // Skip "User-Agent: "
}
}
// Create the request and response objects
var request = new HttpRequest(method, url, userHostName, userAgent);
var response = new HttpResponse(_baseStream);
var ctx = new HttpContext(request, response);
await _clientHandler.HTTPClientConnected(ctx);
}
// Implementing abstract members of Stream class
public override bool CanRead => _baseStream.CanRead;
public override bool CanSeek => _baseStream.CanSeek;
public override bool CanWrite => _baseStream.CanWrite;
public override long Length => _baseStream.Length;
public override long Position { get => _baseStream.Position; set => _baseStream.Position = value; }
public override void Flush() => _baseStream.Flush();
public override int Read(byte[] buffer, int offset, int count) => _baseStream.Read(buffer, offset, count);
public override long Seek(long offset, SeekOrigin origin) => _baseStream.Seek(offset, origin);
public override void SetLength(long value) => _baseStream.SetLength(value);
public override void Write(byte[] buffer, int offset, int count) => _baseStream.Write(buffer, offset, count);
}
#endregion
#region RawHttpServer
public class RawHttpServer : IHTTPClientHandler
{
public string ipAddress = string.Empty; // Change to your desired IP address
public int port; // Use a non-privileged port for testing
bool isHTTPS;
public RawHttpServer()
{
this.ipAddress = "172.0.0.1";
this.port = 80;
this.isHTTPS = false;
}
public RawHttpServer(IPAddress ip, int port, bool isHTTPS)
{
this.ipAddress = ip.ToString();
this.port = port;
this.isHTTPS = isHTTPS;
}
public void Start()
{
TcpListener tcpListener = new TcpListener(IPAddress.Parse(ipAddress), port);
tcpListener.Start();
Console.WriteLine($"Listening for connections on {ipAddress}:{port}");
while (true)
{
TcpClient tcpClient = tcpListener.AcceptTcpClient();
Console.WriteLine($"Accepted connection from {tcpClient.Client.RemoteEndPoint}");
Task.Run(() => HandleClient(tcpClient));
}
}
private async Task HandleClient(TcpClient tcpClient)
{
using (NetworkStream stream = tcpClient.GetStream())
{
HTTPParser httpParser;
if (!this.isHTTPS)
{
// For HTTP connections
httpParser = new HTTPParser(stream, this);
await httpParser.HandleClientConnected(); // Handle the client connection
}
else
{
// For HTTPS connections
try
{
Console.WriteLine("Starting TLS Handshake...");
//using (var tlsStream = new TlsStream(tcpClient, "cert.pem", "key.pem", new[] { "h2", "http/1.1" })) //If using BIO //If I use socket handle, I have to provide TCPClient object
using (var tlsStream = new TlsStream(stream, "cert.pem", "key.pem", new[] { "h2", "http/1.1" })) //If using BIO
{
// Perform the TLS handshake
await tlsStream.DoHandshakeAsync();
Console.WriteLine("TLS Handshake completed.");
string applicationProtocol = tlsStream.GetNegotiatedApplicationProtocol();
Console.WriteLine($"Negotiated Application Protocol: {applicationProtocol}");
// Create a new HTTP parser using the TlsStream for the connection
httpParser = new HTTPParser(tlsStream, this);
await httpParser.HandleClientConnected(); // Handle the client connection
}
}
catch (Exception ex)
{
Console.WriteLine($"Error during TLS handshake: {ex.Message}");
Console.WriteLine(ex.StackTrace);
}
}
}
tcpClient.Close();
}
public async Task HTTPClientConnected(HttpContext ctx)
{
// Print out some info about the request
Console.WriteLine($"Received request: {ctx.Request.HttpMethod} {ctx.Request.Url}");
Console.WriteLine($"User Host Name: {ctx.Request.UserHostName}");
Console.WriteLine($"User Agent: {ctx.Request.UserAgent}");
if (ctx.Request.HttpMethod == "POST")
{
Console.WriteLine("POST BODY: ");
Console.WriteLine(ctx.Request.Body);
}
// Write the response info
byte[] data = Encoding.UTF8.GetBytes("Hello World");
ctx.Response.ContentType = "text/html";
ctx.Response.ContentEncoding = Encoding.UTF8;
ctx.Response.ContentLength64 = data.Length;
// Write out to the response stream (asynchronously), then close it
await ctx.Response.OutputStream.WriteAsync(data, 0, data.Length);
ctx.Response.OutputStream.Close(); // Close the output stream
Console.WriteLine("Response sent to the client.");
}
}
#endregion
//Our main class here
class Program
{
static void Main(string[] args)
{
bool isHTTPS = true;
RawHttpServer server = new RawHttpServer(IPAddress.Parse("192.168.88.12"), 443, isHTTPS);
server.Start();
//Usefull: https://github.com/FoxCouncil/LibFoxyProxy/tree/72a32174a7adc71885c8b0a2758eab173f5a0d4a
}
}
}
Я удалось заставить его работать с использованием сокетов, работа TLSStream с использованием сокетов выглядит так
Код: Выделить всё
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace OpenSsl
{
public class TlsStream : Stream
{
private readonly TcpClient _tcpClient;
private readonly Stream _innerStream;
private IntPtr _ctx;
private IntPtr _ssl;
static TlsStream()
{
OpenSsl.SSL_library_init();
OpenSsl.SSL_load_error_strings();
OpenSsl.ERR_load_BIO_strings();
OpenSsl.OpenSSL_add_all_algorithms();
}
public TlsStream(TcpClient tcpClient, Stream innerStream, string certificatePath, string privateKeyPath, IEnumerable protocols)
{
_tcpClient = tcpClient;
_ctx = OpenSsl.SSL_CTX_new(OpenSsl.TLSv1_2_method());
if (_ctx == IntPtr.Zero)
{
throw new Exception("Unable to create SSL context.");
}
OpenSsl.SSL_CTX_set_ecdh_auto(_ctx, 1);
if (OpenSsl.SSL_CTX_use_certificate_file(_ctx, certificatePath, 1) != 1)
{
throw new Exception("Unable to load certificate file.");
}
if (OpenSsl.SSL_CTX_use_PrivateKey_file(_ctx, privateKeyPath, 1) != 1)
{
throw new Exception("Unable to load private key file.");
}
_ssl = OpenSsl.SSL_new(_ctx);
// Set the file descriptor (socket FD) directly on the SSL object
//var socket = tcpClient.Client; // Get the underlying socket
//OpenSsl.SSL_set_fd(_ssl, socket.Handle.ToInt32());
Console.WriteLine("[TLS Stream] Setting file descriptor for SSL...");
OpenSsl.SSL_set_fd(_ssl, (int)_tcpClient.Client.Handle); // Set the file descriptor for SSL
}
public override bool CanRead => true;
public override bool CanWrite => true;
public override bool CanSeek => false;
public override long Length => throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
public override void Flush() => FlushAsync(default(CancellationToken)).GetAwaiter().GetResult();
public override int Read(byte[] buffer, int offset, int count)
{
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
}
public override void Write(byte[] buffer, int offset, int count)
{
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
}
public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
// Call SSL_read directly
var ret = OpenSsl.SSL_read(_ssl, buffer, offset, count);
if (ret
Подробнее здесь: [url]https://stackoverflow.com/questions/79067359/openssl-bio-read-returns-negative-value[/url]