Невозможно добавить TLS на мой HTTP-сервер, клиенты не могут к нему подключитьсяC#

Место общения программистов C#
Ответить
Anonymous
 Невозможно добавить TLS на мой HTTP-сервер, клиенты не могут к нему подключиться

Сообщение Anonymous »

Я пытаюсь расширить свой HTTP-сервер с помощью поддержки TLS
Мой HTTP-сервер здесь

Код: Выделить всё

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using RawHttpListener.TLS;

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; // Invalid request

string method = requestLine[0]; // e.g., GET, POST
string url = requestLine[1];     // e.g., /path
string httpVersion = requestLine[2]; // e.g., HTTP/1.1

// 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();
Task.Run(() => HandleClient(tcpClient));
}
}

private async Task HandleClient(TcpClient tcpClient)
{
using (NetworkStream stream = tcpClient.GetStream())
{
HTTPParser httpParser;
if (!this.isHTTPS)
{
//Reading RAW request, for debugging purposes
//byte[] buffer = new byte[4096]; // Adjust the buffer size as necessary
//int bytesRead;

//while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
//{
//    // Handle the data
//    string data = Encoding.UTF8.GetString(buffer, 0, bytesRead);
//    Console.WriteLine("Data: " + data);
//}

httpParser = new HTTPParser(stream, this);
await httpParser.HandleClientConnected(); // Handle the client connection
}
else
{
// Wrap the inner stream with TlsStream
//using (var tlsStream = new TlsStream(stream, "cert.pem", "key.pem", new[] { "http/1.1" }))
using (var tlsStream = new TlsStream(stream, "cert.pem", "key.pem", new[] { "http/1.1" }))
{
// Perform the TLS handshake
await tlsStream.DoHandshakeAsync();

// Read decrypted data
byte[] buffer = new byte[4096]; // Adjust the buffer size as necessary
int bytesRead;

while ((bytesRead = await tlsStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
// Handle the decrypted data
string decryptedData = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine("Decrypted Data: "  + decryptedData);

}

// Create a new HTTP parser using the TlsStream for the connection
httpParser = new HTTPParser(tlsStream, this);
await httpParser.HandleClientConnected(); // Handle the client connection

}
}
}
tcpClient.Close();
}

public async Task HTTPClientConnected(HttpContext ctx)
{
// Print out some info about the request
Console.WriteLine($"URL: {ctx.Request.Url}");
Console.WriteLine($"Method: {ctx.Request.HttpMethod}");
Console.WriteLine($"User Host Name: {ctx.Request.UserHostName}");
Console.WriteLine($"User Agent: {ctx.Request.UserAgent}");
Console.WriteLine();

if (ctx.Request.HttpMethod == "POST")
{
Console.WriteLine("POST BODY:");
// Here you can handle the POST body if needed
}

// 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
}
}
#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();
}
}
}
Вот мой файл TLSStream.cs

Код: Выделить всё

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 RawHttpListener.TLS
{
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  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();
}

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);
await _innerStream.WriteAsync(_outputBuffer, 0, count, cancellationToken);

pending = OpenSsl.BIO_ctrl_pending(_outputBio);
}
}

public override async Task  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 void DoHandshake()
{
OpenSsl.SSL_set_accept_state(_ssl);

var count = 0;

try
{
while ((count = _innerStream.Read(_inputBuffer, 0, _inputBuffer.Length)) > 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) // Typically SSL_ERROR_WANT_READ, handled non-blocking in async
{
throw new IOException($"TLS handshake failed: {nameof(OpenSsl.SSL_do_handshake)} error {error}.");
}
}

Flush(); // Replace FlushAsync with its synchronous counterpart

if (ret == 1)
{
return; // Handshake successful, return from the method.
}
}
}
finally
{
_protocolsHandle.Free();
}
}
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 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;
}
}
}
Вот мои оболочки OpenSSL для OpenSSL 1.1 (мне пришлось разместить их в другом месте, потому что текст моего вопроса был бы слишком большим, если бы они были здесь: https://hastebin.com/share/ weruzapebe.csharp
Моя библиотека OpenSSL загружается правильно
cert.pem
key.pem
работают правильно, я пытался использовать их на веб-сервере Apache, и они там работают нормально.
Я ожидал, что Firefox покажет мою расшифрованную страницу, но единственное, что я получаю в ответ, это р>

Код: Выделить всё

PR_END_OF_FILE_ERROR
Если я не выполню TLSHandshake, я получу

Код: Выделить всё

PR_CONNECT_RESET_ERROR
Я даже пытался подключиться к OpenSSL вот так:

Код: Выделить всё

openssl s_client -connect domain.local:443
Но единственное, что я получаю, это обратно

Код: Выделить всё

Connecting to 192.168.88.12
CONNECTED(00000128)
BC370000:error:0A000126:SSL routines::unexpected eof while reading:../openssl-3.
3.2/ssl/record/rec_layer_s3.c:687:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 0 bytes and written 334 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
This TLS version forbids renegotiation.
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
что я делаю неправильно, я думал, что добавить https на мой сервер будет тривиально (просто передайте свой поток openSSL, который выполнит всю работу и будет работать с расшифрованным потоком)
p>
но что-то не работает

Подробнее здесь: https://stackoverflow.com/questions/790 ... nect-to-it
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»