continue my journey from here: Cannot add TLS to my HTTP Server, clients cannot connect to itwhere I am trying to implement HTTPS server
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("<html><body><h1>Hello World</h1></body></html>"); 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 } }}
in both working and non working cases I use the same OpenSSL wrappings, which I had to put here: https://hastebin.com/share/pirutogogu.csharpI was able to make it work using sockets, working TLSStream using sockets looks like this
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<string> 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<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { // Call SSL_read directly var ret = OpenSsl.SSL_read(_ssl, buffer, offset, count); if (ret <= 0) { throw new IOException("Error reading from SSL stream."); } return ret; } public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { // Call SSL_write directly var ret = OpenSsl.SSL_write(_ssl, buffer, offset, count); if (ret <= 0) { throw new IOException("Error writing to SSL stream."); } return Task.CompletedTask; } public async Task DoHandshakeAsync(CancellationToken cancellationToken = default(CancellationToken)) { // Set the SSL object to expect the client handshake OpenSsl.SSL_set_accept_state(_ssl); // Set a timeout for the handshake process to avoid indefinite blocking var handshakeTimeout = TimeSpan.FromSeconds(10); var handshakeTask = Task.Run(async () => { int ret = -1; try { // Start handshake loop while (true) { ret = OpenSsl.SSL_do_handshake(_ssl); if (ret == 1) { // Handshake succeeded return; } int error = OpenSsl.SSL_get_error(_ssl, ret); if (error == OpenSsl.SSL_ERROR_WANT_READ || error == OpenSsl.SSL_ERROR_WANT_WRITE) { // Keep waiting for the handshake to complete await Task.Delay(100); // Wait briefly before retrying continue; } else { // An actual error occurred throw new IOException($"TLS handshake failed with error {error}"); } } } catch (IOException ex) { throw new IOException($"Handshake failed: {ex.Message}"); } }, cancellationToken); // Ensure that the handshake doesn't run indefinitely if (await Task.WhenAny(handshakeTask, Task.Delay(handshakeTimeout, cancellationToken)) == handshakeTask) { await handshakeTask; // Propagate exception if any } else { throw new TimeoutException("TLS handshake timed out."); } } public string GetNegotiatedApplicationProtocol() { OpenSsl.SSL_get0_alpn_selected(_ssl, out var protocol); return protocol; } // Ensure resources are freed protected override void Dispose(bool disposing) { base.Dispose(disposing); if (_ssl != IntPtr.Zero) { OpenSsl.SSL_free(_ssl); _ssl = IntPtr.Zero; } if (_ctx != IntPtr.Zero) { OpenSsl.SSL_CTX_free(_ctx); _ctx = IntPtr.Zero; } } }}
If I use BIO instead of sockets
using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Runtime.InteropServices;using System.Text;using System.Threading;using System.Threading.Tasks;namespace OpenSsl{ public class TlsStream : Stream { private static unsafe OpenSsl.alpn_select_cb_t _alpnSelectCallback = AlpnSelectCallback; private readonly Stream _innerStream; private readonly byte[] _protocols; private readonly GCHandle _protocolsHandle; private IntPtr _ctx; private IntPtr _ssl; private IntPtr _inputBio; private IntPtr _outputBio; private readonly byte[] _inputBuffer = new byte[1024 * 1024]; private readonly byte[] _outputBuffer = new byte[1024 * 1024]; static TlsStream() { OpenSsl.SSL_library_init(); OpenSsl.SSL_load_error_strings(); OpenSsl.ERR_load_BIO_strings(); OpenSsl.OpenSSL_add_all_algorithms(); } public TlsStream(Stream innerStream, string certificatePath, string privateKeyPath, IEnumerable<string> protocols) { _innerStream = innerStream; _protocols = ToWireFormat(protocols); _protocolsHandle = GCHandle.Alloc(_protocols); _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."); } OpenSsl.SSL_CTX_set_alpn_select_cb(_ctx, _alpnSelectCallback, GCHandle.ToIntPtr(_protocolsHandle)); _ssl = OpenSsl.SSL_new(_ctx); _inputBio = OpenSsl.BIO_new(OpenSsl.BIO_s_mem()); OpenSsl.BIO_set_mem_eof_return(_inputBio, -1); _outputBio = OpenSsl.BIO_new(OpenSsl.BIO_s_mem()); OpenSsl.BIO_set_mem_eof_return(_outputBio, -1); OpenSsl.SSL_set_bio(_ssl, _inputBio, _outputBio); } ~TlsStream() { if (_ssl != IntPtr.Zero) { OpenSsl.SSL_free(_ssl); } if (_ctx != IntPtr.Zero) { // This frees the BIOs. OpenSsl.SSL_CTX_free(_ctx); } if (_protocolsHandle.IsAllocated) { _protocolsHandle.Free(); } } 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(); } // Method to retrieve the OpenSSL error queue message private string GetOpenSslErrorMessage() { var errCode = OpenSsl.ERR_get_error(); if (errCode == 0) return "No OpenSSL errors in the queue."; var errMsg = OpenSsl.ERR_reason_error_string(errCode); return $"OpenSSL error code {errCode}: {errMsg}"; } public override async Task FlushAsync(CancellationToken cancellationToken) { var pending = OpenSsl.BIO_ctrl_pending(_outputBio); while (pending > 0) { var count = OpenSsl.BIO_read(_outputBio, _outputBuffer, 0, _outputBuffer.Length); //Console.WriteLine($"BIO_read returned: {count}"); //Console.WriteLine($"Buffer Length: {_outputBuffer.Length}"); // Handle BIO_read errors if (count < 0) { string errorMessage = GetOpenSslErrorMessage(); throw new InvalidOperationException($"BIO_read returned a negative value, indicating an error: {errorMessage}"); } if (count > _outputBuffer.Length) { throw new ArgumentOutOfRangeException(nameof(count), "BIO_read returned a value larger than the buffer size."); } if (count > 0) { await _innerStream.WriteAsync(_outputBuffer, 0, count, cancellationToken); } pending = OpenSsl.BIO_ctrl_pending(_outputBio); } } public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { if (OpenSsl.BIO_ctrl_pending(_inputBio) == 0) { var bytesRead = await _innerStream.ReadAsync(_inputBuffer, 0, _inputBuffer.Length, cancellationToken); if (bytesRead == 0) { return 0; } OpenSsl.BIO_write(_inputBio, _inputBuffer, 0, bytesRead); } return OpenSsl.SSL_read(_ssl, buffer, offset, count); } public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { OpenSsl.SSL_write(_ssl, buffer, offset, count); return FlushAsync(cancellationToken); } public async Task DoHandshakeAsync(CancellationToken cancellationToken = default(CancellationToken)) { OpenSsl.SSL_set_accept_state(_ssl); var count = 0; try { while ((count = await _innerStream.ReadAsync(_inputBuffer, 0, _inputBuffer.Length, cancellationToken)) > 0) { if (count == 0) { throw new IOException("TLS handshake failed: the inner stream was closed."); } OpenSsl.BIO_write(_inputBio, _inputBuffer, 0, count); var ret = OpenSsl.SSL_do_handshake(_ssl); if (ret != 1) { var error = OpenSsl.SSL_get_error(_ssl, ret); if (error != 2) { throw new IOException($"TLS handshake failed: {nameof(OpenSsl.SSL_do_handshake)} error {error}."); } } await FlushAsync(cancellationToken); if (ret == 1) { return; } } } finally { _protocolsHandle.Free(); } } public string GetNegotiatedApplicationProtocol() { OpenSsl.SSL_get0_alpn_selected(_ssl, out var protocol); return protocol; } private static unsafe int AlpnSelectCallback(IntPtr ssl, out byte* @out, out byte outlen, byte* @in, uint inlen, IntPtr arg) { var protocols = GCHandle.FromIntPtr(arg); var server = (byte[])protocols.Target; fixed (byte* serverPtr = server) { return OpenSsl.SSL_select_next_proto(out @out, out outlen, serverPtr, (uint)server.Length, @in, (uint)inlen) == OpenSsl.OPENSSL_NPN_NEGOTIATED ? OpenSsl.SSL_TLSEXT_ERR_OK : OpenSsl.SSL_TLSEXT_ERR_NOACK; } } private static byte[] ToWireFormat(IEnumerable<string> protocols) { var buffer = new byte[protocols.Count() + protocols.Sum(protocol => protocol.Length)]; var offset = 0; foreach (var protocol in protocols) { buffer[offset++] = (byte)protocol.Length; offset += Encoding.ASCII.GetBytes(protocol, 0, protocol.Length, buffer, offset); } return buffer; } }}
I always get this error, and server closes connection imidiatly, after the handshake is over, same thing if using firefox or openSSL like this: openssl s_client -connect domain.local:443 -CAfile XWDS.crt
Listening for connections on 192.168.88.12:443Accepted connection from 192.168.88.12:62764Starting TLS Handshake...Error during TLS handshake: BIO_read returned a negative value, indicating an error: No OpenSSL errors in the queue. at OpenSsl.TlsStream.<FlushAsync>d__30.MoveNext() in C:\Users\cliente\Documents\Visual Studio 2017\Projects\Network\OpenSSLWrapperTest\OpenSSLWrapper\TlsStream.cs:line 147--- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.GetResult() at OpenSsl.TlsStream.<DoHandshakeAsync>d__33.MoveNext() in C:\Users\cliente\Documents\Visual Studio 2017\Projects\Network\OpenSSLWrapperTest\OpenSSLKestrelWrapper\TlsStream.cs:line 218--- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.GetResult() at RawHttpListener.RawHttpServer.<InnerOnConnectionAsync>d__6.MoveNext() in C:\Users\cliente\Documents\Visual Studio 2017\Projects\Network\OpenSSLWrapperTest\OpenSSLWrapper\Program.cs:line 195--- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() at RawHttpListener.RawHttpServer.<HandleClient>d__8.MoveNext() in C:\Users\cliente\Documents\Visual Studio 2017\Projects\Network\OpenSSLWrapperTest\OpenSSLWrapper\Program.cs:line 245
for some reason I cannot get any error out of this, so I would know whats happening
You can see OpenSSL Output here: https://hastebin.com/share/lumeyihexa.yaml
I am not sure why I cannot make this work with BIO, would realy like to use BIO