Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rudimentary HTTPS support #145

Merged
merged 10 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Bunkum.Protocols.Gemini/Bunkum.Protocols.Gemini.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<ItemGroup>
<ProjectReference Include="..\Bunkum.Core\Bunkum.Core.csproj" />
<ProjectReference Include="..\Bunkum.Listener\Bunkum.Listener.csproj" />
<ProjectReference Include="..\Bunkum.Protocols.TlsSupport\Bunkum.Protocols.TlsSupport.csproj" />
</ItemGroup>

</Project>
8 changes: 5 additions & 3 deletions Bunkum.Protocols.Gemini/BunkumGeminiServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Bunkum.Core.Configuration;
using Bunkum.Listener;
using Bunkum.Protocols.Gemini.Socket;
using Bunkum.Protocols.TlsSupport;
using NotEnoughLogs;
using NotEnoughLogs.Sinks;

Expand All @@ -11,6 +12,7 @@ namespace Bunkum.Protocols.Gemini;
public class BunkumGeminiServer : BunkumServer
{
private readonly X509Certificate2 _cert;
private readonly SslConfiguration _sslConfiguration;

/// <summary>
/// Create a new BunkumGeminiServer
Expand All @@ -21,14 +23,14 @@ public class BunkumGeminiServer : BunkumServer
public BunkumGeminiServer(SslConfiguration? sslConfiguration = null, LoggerConfiguration? configuration = null, List<ILoggerSink>? sinks = null) : base(configuration, sinks)
{
//If the SSL configuration is not specified, load the config from JSON
sslConfiguration ??= Config.LoadFromJsonFile<SslConfiguration>("geminissl.json", this.Logger);
this._sslConfiguration = sslConfiguration ?? Config.LoadFromJsonFile<SslConfiguration>("geminissl.json", this.Logger);

this._cert = new X509Certificate2(File.ReadAllBytes(sslConfiguration.SslCertificate), sslConfiguration.CertificatePassword);
this._cert = new X509Certificate2(File.ReadAllBytes(sslConfiguration.SslCertificate), this._sslConfiguration.CertificatePassword);
}

protected override BunkumListener CreateDefaultListener(Uri listenEndpoint, bool useForwardedIp, Logger logger)
{
return new SocketGeminiListener(this._cert, listenEndpoint, logger);
return new SocketGeminiListener(this._cert, this._sslConfiguration, listenEndpoint, logger);
}
protected override string ProtocolUriName => "gemini";
}
21 changes: 15 additions & 6 deletions Bunkum.Protocols.Gemini/Socket/SocketGeminiListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
Expand All @@ -11,6 +11,7 @@
using Bunkum.Listener.Extensions;
using Bunkum.Listener.Protocol;
using Bunkum.Listener.Request;
using Bunkum.Protocols.TlsSupport;
using NotEnoughLogs;

namespace Bunkum.Protocols.Gemini.Socket;
Expand All @@ -20,14 +21,16 @@ public partial class SocketGeminiListener : BunkumGeminiListener
private System.Net.Sockets.Socket? _socket;
private readonly Uri _listenEndpoint;
private readonly X509Certificate2 _cert;
private readonly SslConfiguration _sslConfiguration;

[GeneratedRegex("^[a-zA-Z]+$")]
private static partial Regex LettersRegex();

public SocketGeminiListener(X509Certificate2 cert, Uri listenEndpoint, Logger logger) : base(logger)
public SocketGeminiListener(X509Certificate2 cert, SslConfiguration sslConfiguration, Uri listenEndpoint, Logger logger) : base(logger)
{
this._listenEndpoint = listenEndpoint;
this._cert = cert;
this._sslConfiguration = sslConfiguration;

this.Logger.LogInfo(ListenerCategory.Startup, "Internal Gemini server is listening at URL {0}", listenEndpoint);
}
Expand Down Expand Up @@ -88,13 +91,19 @@ private async Task<ListenerContext> ReadRequestIntoContext(System.Net.Sockets.So
{
SslStream stream = new(rawStream);

await stream.AuthenticateAsServerAsync(new SslServerAuthenticationOptions
SslServerAuthenticationOptions authOptions = new()
{
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13,
EnabledSslProtocols = this._sslConfiguration.EnabledSslProtocols,
ServerCertificate = this._cert,
ClientCertificateRequired = true,
RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => true,
});
RemoteCertificateValidationCallback = (_, _, _, _) => true,
};

// If the cipher suite set is enabled, and we are not on windows, enable the selected cipher suites
if (this._sslConfiguration.EnabledCipherSuites != null && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
authOptions.CipherSuitesPolicy = new CipherSuitesPolicy(this._sslConfiguration.EnabledCipherSuites);

await stream.AuthenticateAsServerAsync(authOptions);

Uri uri = new(GetPath(stream));

Expand Down
15 changes: 0 additions & 15 deletions Bunkum.Protocols.Gemini/SslConfiguration.cs

This file was deleted.

2 changes: 2 additions & 0 deletions Bunkum.Protocols.Http/BunkumHttpServer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Security.Cryptography.X509Certificates;
using Bunkum.Core;
using Bunkum.Core.Configuration;
using Bunkum.Listener;
using NotEnoughLogs;
using NotEnoughLogs.Sinks;
Expand Down
50 changes: 47 additions & 3 deletions Bunkum.Protocols.Http/Socket/SocketHttpListener.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using System.Diagnostics;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using System.Web;
using Bunkum.Listener;
Expand All @@ -16,14 +20,27 @@ public partial class SocketHttpListener : BunkumHttpListener
private System.Net.Sockets.Socket? _socket;
private readonly Uri _listenEndpoint;
private readonly bool _useForwardedIp;

private readonly X509Certificate2? _cert;
private readonly SslProtocols _enabledSslProtocols;
private readonly TlsCipherSuite[]? _enabledCipherSuites;

[GeneratedRegex("^[a-zA-Z]+$")]
private static partial Regex LettersRegex();

public SocketHttpListener(Uri listenEndpoint, bool useForwardedIp, Logger logger) : base(logger)
public SocketHttpListener(
Uri listenEndpoint,
bool useForwardedIp,
Logger logger,
X509Certificate2? certificate = null,
SslProtocols enabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13,
TlsCipherSuite[]? enabledCipherSuites = null)
: base(logger)
{
this._listenEndpoint = listenEndpoint;
this._useForwardedIp = useForwardedIp;
this._cert = certificate;
this._enabledCipherSuites = enabledCipherSuites;
this._enabledSslProtocols = enabledSslProtocols;

this.Logger.LogInfo(ListenerCategory.Startup, "Internal HTTP server is listening at URL " + listenEndpoint);
}
Expand Down Expand Up @@ -75,8 +92,31 @@ public override void StartListening()
}
}

private ListenerContext ReadRequestIntoContext(System.Net.Sockets.Socket client, Stream stream)
private ListenerContext ReadRequestIntoContext(System.Net.Sockets.Socket client, Stream rawStream)
{
Stream stream = rawStream;
SslStream? sslStream = null;
if (this._cert != null)
{
sslStream = new SslStream(rawStream);

SslServerAuthenticationOptions authOptions = new()
{
EnabledSslProtocols = this._enabledSslProtocols,
ServerCertificate = this._cert,
ClientCertificateRequired = false,
RemoteCertificateValidationCallback = (_, _, _, _) => true,
};

// If the cipher suite set is enabled, and we are not on windows, enable the selected cipher suites
if (this._enabledCipherSuites != null && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
authOptions.CipherSuitesPolicy = new CipherSuitesPolicy(this._enabledCipherSuites);

sslStream.AuthenticateAsServer(authOptions);

stream = sslStream;
}

Span<char> method = stackalloc char[RequestLineMethodLimit];
Span<char> path = stackalloc char[RequestLinePathLimit];
Span<char> version = stackalloc char[RequestLineVersionLimit];
Expand Down Expand Up @@ -199,6 +239,10 @@ private ListenerContext ReadRequestIntoContext(System.Net.Sockets.Socket client,
}
context.InputStream = inputStream;

// If this an SSL connection, set the remote certificate
if (sslStream != null)
context.RemoteCertificate = sslStream.RemoteCertificate;

return context;
}

Expand Down
11 changes: 7 additions & 4 deletions Bunkum.Protocols.Http/Socket/SocketHttpListenerContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ public SocketHttpListenerContext(System.Net.Sockets.Socket socket, Stream stream

protected override async Task SendResponseInternal(HttpStatusCode code, ArraySegment<byte>? data = null)
{
// this is dumb and stupid
this.ResponseHeaders.Add("Server", "Bunkum");
this.ResponseHeaders.Add("Connection", "close");
this.ResponseHeaders.Add("Date", DateTime.UtcNow.ToString("ddd, dd MMM yyyy HH:mm:ss 'GMT'"));
// These are "TryAdd" and not "Add" since if you are proxying requests,
// both of these should be set to the headers sent by the proxied server, and not be generated by Bunkum directly
this.ResponseHeaders.TryAdd("Server", "Bunkum");
this.ResponseHeaders.TryAdd("Date", DateTime.UtcNow.ToString("ddd, dd MMM yyyy HH:mm:ss 'GMT'"));
// This is an unconditional set because Bunkum does not support keep-alive connections,
// theres currently no reason for anything other than Bunkum to set this value
this.ResponseHeaders["Connection"] = "close";

List<string> response = new() { $"HTTP/1.1 {code.GetHashCode()} {code.ToString()}" }; // TODO: spaced code names ("Not Found" instead of "NotFound")
foreach ((string? key, string? value) in this.ResponseHeaders)
Expand Down
14 changes: 14 additions & 0 deletions Bunkum.Protocols.Https/Bunkum.Protocols.Https.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Bunkum.Protocols.Http\Bunkum.Protocols.Http.csproj" />
<ProjectReference Include="..\Bunkum.Protocols.TlsSupport\Bunkum.Protocols.TlsSupport.csproj" />
</ItemGroup>

</Project>
33 changes: 33 additions & 0 deletions Bunkum.Protocols.Https/BunkumHttpsServer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Security.Cryptography.X509Certificates;
using Bunkum.Core;
using Bunkum.Core.Configuration;
using Bunkum.Listener;
using Bunkum.Protocols.TlsSupport;
using NotEnoughLogs;
using NotEnoughLogs.Sinks;

namespace Bunkum.Protocols.Https;

public class BunkumHttpsServer : BunkumServer
{
private readonly X509Certificate2? _cert;
private readonly SslConfiguration _sslConfiguration;

public BunkumHttpsServer(LoggerConfiguration? configuration = null, List<ILoggerSink>? sinks = null,
SslConfiguration? sslConfiguration = null) : base(configuration, sinks)
{
//If the SSL configuration is not specified, load the config from JSON
this._sslConfiguration = sslConfiguration ?? Config.LoadFromJsonFile<SslConfiguration>("ssl.json", this.Logger);

this._cert = new X509Certificate2(File.ReadAllBytes(this._sslConfiguration.SslCertificate), this._sslConfiguration.CertificatePassword);
}

/// <inherit-doc/>
protected override BunkumListener CreateDefaultListener(Uri listenEndpoint, bool useForwardedIp, Logger logger)
{
return new Http.Socket.SocketHttpListener(listenEndpoint, useForwardedIp, logger, this._cert, this._sslConfiguration.EnabledSslProtocols, this._sslConfiguration.EnabledCipherSuites);
}

/// <inherit-doc/>
protected override string ProtocolUriName => "https";
}
13 changes: 13 additions & 0 deletions Bunkum.Protocols.TlsSupport/Bunkum.Protocols.TlsSupport.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Bunkum.Core\Bunkum.Core.csproj" />
</ItemGroup>

</Project>
52 changes: 52 additions & 0 deletions Bunkum.Protocols.TlsSupport/SslConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Net.Security;
using System.Security.Authentication;
using Bunkum.Core.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace Bunkum.Protocols.TlsSupport;

public class SslConfiguration : Config
{
public override int CurrentConfigVersion => 1;
public override int Version { get; set; }

protected override void Migrate(int oldVer, dynamic oldConfig)
{}

/// <summary>
/// The path to the certificate
/// </summary>
public string SslCertificate { get; set; } = "cert.pfx";
/// <summary>
/// The password for the certificate, null if none
/// </summary>
public string? CertificatePassword { get; set; }

/// <summary>
/// The SSL protocols which are enabled. If null, enables TLS1.3 and TLS1.2
/// </summary>
[JsonProperty("enabledSslProtocols", ItemConverterType = typeof(StringEnumConverter))]
private SslProtocols[]? _EnabledSslProtocols { get; set; }
/// <summary>
/// The cipher suites which are enabled. If null, lets the system decide
/// </summary>
[JsonProperty(ItemConverterType = typeof(StringEnumConverter))]
public TlsCipherSuite[]? EnabledCipherSuites { get; set; }

public SslProtocols EnabledSslProtocols
{
get
{
SslProtocols protocols = SslProtocols.None;

if (this._EnabledSslProtocols == null)
protocols = SslProtocols.Tls12 | SslProtocols.Tls13;
else
protocols = this._EnabledSslProtocols
.Aggregate(protocols, (current, protocol) => current | protocol);

return protocols;
}
}
}
14 changes: 14 additions & 0 deletions Bunkum.sln
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Database", "Database", "{ED
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataStore", "DataStore", "{D09D7F3C-B198-40D8-B7E0-CC2CFFC75CDD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bunkum.Protocols.Https", "Bunkum.Protocols.Https\Bunkum.Protocols.Https.csproj", "{FEDB1C3F-5549-4B44-94CE-54FFE590A35A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bunkum.Protocols.TlsSupport", "Bunkum.Protocols.TlsSupport\Bunkum.Protocols.TlsSupport.csproj", "{B7B1AAD8-2E00-439E-A587-7434E51C1E9E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -174,6 +178,14 @@ Global
{23BC7994-0E93-4FB3-8D31-16EC3BE1198B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{23BC7994-0E93-4FB3-8D31-16EC3BE1198B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{23BC7994-0E93-4FB3-8D31-16EC3BE1198B}.Release|Any CPU.Build.0 = Release|Any CPU
{FEDB1C3F-5549-4B44-94CE-54FFE590A35A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FEDB1C3F-5549-4B44-94CE-54FFE590A35A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FEDB1C3F-5549-4B44-94CE-54FFE590A35A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FEDB1C3F-5549-4B44-94CE-54FFE590A35A}.Release|Any CPU.Build.0 = Release|Any CPU
{B7B1AAD8-2E00-439E-A587-7434E51C1E9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B7B1AAD8-2E00-439E-A587-7434E51C1E9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7B1AAD8-2E00-439E-A587-7434E51C1E9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B7B1AAD8-2E00-439E-A587-7434E51C1E9E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{B6B9F1DD-7E85-402E-88A7-EF6B88E6CE9A} = {77922B8B-F6FE-47D6-8742-6BA528BC8D7C}
Expand All @@ -200,5 +212,7 @@ Global
{DEE9CFC0-9529-451F-9633-AF5B49498AFB} = {EDBF1652-710C-4CF9-8890-AF3B46FDA8E1}
{D09D7F3C-B198-40D8-B7E0-CC2CFFC75CDD} = {77922B8B-F6FE-47D6-8742-6BA528BC8D7C}
{993BD323-2E2C-4E79-9252-F92B8CEB834D} = {D09D7F3C-B198-40D8-B7E0-CC2CFFC75CDD}
{FEDB1C3F-5549-4B44-94CE-54FFE590A35A} = {BBAA9024-6FEC-4C68-9F96-83D2055EC6D2}
{B7B1AAD8-2E00-439E-A587-7434E51C1E9E} = {BBAA9024-6FEC-4C68-9F96-83D2055EC6D2}
EndGlobalSection
EndGlobal
Loading