Description
Is there an existing issue for this?
- I have searched the existing issues
Intro
We need to add implementation at least for HTTP.SYS / Kestrel servers. Based on SslStream API Proposal for Kestrel support (dotnet/runtime proposal), it seems more likely that a callback will be introduced there (something like SslServerAuthenticationOptions.ClientHelloBytesCallback
).
Or we can just support it in aspnetcore directly similarly to how YARP has already done it (see TlsFilter middleware).
Proposed API
Kestrel
Exposing a separate extension on ListenOptionsHttpsExtensions
or on a new type ListenOptionsTlsExtensions
works for Kestrel
namespace Microsoft.AspNetCore.Hosting
{
public static class ListenOptionsHttpsExtensions
{
+ public static ListenOptions ConfigureTlsClientHelloCallback(this ListenOptions listenOptions, Action<ConnectionContext, ReadOnlySequence<byte>> tlsClientHelloCallback); // has info about connection; has the tls client hello bytes
}
+ public static class ListenOptionsTlsExtensions
+ {
+ public static ListenOptions ConfigureTlsClientHelloCallback(this ListenOptions listenOptions, Action<ConnectionContext, ReadOnlySequence<byte>> tlsClientHelloCallback); // has info about connection; has the tls client hello bytes
+ }
}
Kestrel Usage:
options.ListenAnyIP(443, listenOptions =>
{
listenOptions.ConfigureTlsClientHelloCallback((connectionContext, tlsClientHelloBytes) => {
// do whatever with tlsClientHelloBytes
});
});
Implementation of such a kind would be reading every packet, then if it appears to start with TLS client hello header, then we would try to identify the length of it, read needed data, and then invoke a callback. Later we would be able to even add an overload to provide a parsed TLS client hello message in a strictly typed way (like TlsFrameInfo)
This also helps user to associate the tls client hello with a specific connection (ConnectionContext
parameter).
ReadOnlySequence<byte>
should be chosen instead of ReadOnlySpan<byte>
because tls info can come in different segments, and it will not be a contiguous memory then (see TlsFilter's if (!buffer.IsSingleSegment)
)
Http.Sys
In order to align the implementation for different servers, I am proposing to add a similar callback to HttpSysOptions
namespace Microsoft.AspNetCore.Server.HttpSys;
public class HttpSysOptions
{
+ Action<IFeatureCollection, ReadOnlySpan<byte>> ClientHelloBytesCallback { get; set; }
}
Http.sys Usage:
options.ClientHelloBytesCallback = (requestFeature, bytes)=>
{
// do whatever with tlsClientHelloBytes
});
HTTP.SYS API would have same idea, but different implementation: a) having a callback in a different options class and b) having an IFeatureCollection
instead of ConnectionContext
(there is none yet for HTTP.SYS). That would also give full control to the user to lookup to any feature collection or even set their own feature for future call rejection for instance.
Other considered designs (and why they are not chosen)
Middleware
Adding a separate middleware on top of IApplicationBuilder.Run()
will not fulfil the need, since such a middleware is terminal (runs after the internal ASP.NET middleware chain including SslStream one):
app.Run(async (HttpContext httpContext) =>
{
var transportFeature = httpContext.Features.Get<IConnectionTransportFeature>();
var pipeReader = transportFeature.Transport.Input;
// imagine here we get the buffer for the TLS Client Hello message
var bytes = await pipeReader.ReadAsync();
var tlsCLientHelloMessage = bytes.Buffer.FirstSpan;
tlsClientHelloCallback(httpContext, tlsCLientHelloMessage); // invoking, so users can do whatever
});
Exposing bytes field on the ITlsHandshakeFeature
(or similar)
This is considered a non-performant API for both servers due to increasing a memory footprint significantly (see dotnet/runtime#113729 (comment)):
public interface ITlsHandshakeFeature
{
public byte[] TlsClientHelloMessageBytes { get; }
}
Is your feature request related to a problem? Please describe the problem.
The intention here is to have an API to access a TLS client hello message in a format of raw bytes (in example byte[]
). In such a way users can analyze it and make whatever decision they want to.