Quantcast
Channel: Active questions tagged https - Stack Overflow
Viewing all articles
Browse latest Browse all 1584

OpenSSL BIO_Read Returns negative value

$
0
0

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


Viewing all articles
Browse latest Browse all 1584

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>