Skip to content

Commit 047246c

Browse files
authored
feat(HTTP.SYS): on-demand TLS client hello retrieval (#62209)
1 parent 12feaab commit 047246c

16 files changed

+130
-467
lines changed
Lines changed: 31 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,48 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Buffers;
5+
using System.Diagnostics;
46
using System.Reflection;
57
using System.Runtime.InteropServices;
8+
using Microsoft.AspNetCore.Builder;
69
using Microsoft.AspNetCore.Hosting;
10+
using Microsoft.AspNetCore.Http;
711
using Microsoft.AspNetCore.Http.Features;
812
using Microsoft.AspNetCore.Server.HttpSys;
913
using Microsoft.Extensions.Hosting;
10-
using TlsFeatureObserve;
1114
using TlsFeaturesObserve.HttpSys;
1215

1316
HttpSysConfigurator.ConfigureCacheTlsClientHello();
14-
CreateHostBuilder(args).Build().Run();
15-
16-
static IHostBuilder CreateHostBuilder(string[] args) =>
17-
Host.CreateDefaultBuilder(args)
18-
.ConfigureWebHost(webBuilder =>
19-
{
20-
webBuilder.UseStartup<Startup>()
21-
.UseHttpSys(options =>
22-
{
23-
// If you want to use https locally: https://stackoverflow.com/a/51841893
24-
options.UrlPrefixes.Add("https://*:6000"); // HTTPS
25-
26-
options.Authentication.Schemes = AuthenticationSchemes.None;
27-
options.Authentication.AllowAnonymous = true;
28-
29-
options.TlsClientHelloBytesCallback = ProcessTlsClientHello;
30-
});
31-
});
32-
33-
static void ProcessTlsClientHello(IFeatureCollection features, ReadOnlySpan<byte> tlsClientHelloBytes)
34-
{
35-
var httpConnectionFeature = features.Get<IHttpConnectionFeature>();
36-
37-
var myTlsFeature = new MyTlsFeature(
38-
connectionId: httpConnectionFeature.ConnectionId,
39-
tlsClientHelloLength: tlsClientHelloBytes.Length);
4017

41-
features.Set<IMyTlsFeature>(myTlsFeature);
42-
}
18+
var builder = WebApplication.CreateBuilder(args);
4319

44-
public interface IMyTlsFeature
20+
builder.WebHost.UseHttpSys(options =>
4521
{
46-
string ConnectionId { get; }
47-
int TlsClientHelloLength { get; }
48-
}
22+
options.UrlPrefixes.Add("https://*:6000");
23+
options.Authentication.Schemes = AuthenticationSchemes.None;
24+
options.Authentication.AllowAnonymous = true;
25+
});
4926

50-
public class MyTlsFeature : IMyTlsFeature
27+
var app = builder.Build();
28+
29+
app.Use(async (context, next) =>
5130
{
52-
public string ConnectionId { get; }
53-
public int TlsClientHelloLength { get; }
54-
55-
public MyTlsFeature(string connectionId, int tlsClientHelloLength)
56-
{
57-
ConnectionId = connectionId;
58-
TlsClientHelloLength = tlsClientHelloLength;
59-
}
60-
}
31+
var connectionFeature = context.Features.GetRequiredFeature<IHttpConnectionFeature>();
32+
var httpSysPropFeature = context.Features.GetRequiredFeature<IHttpSysRequestPropertyFeature>();
33+
34+
// first time invocation to find out required size
35+
var success = httpSysPropFeature.TryGetTlsClientHello(Array.Empty<byte>(), out var bytesReturned);
36+
Debug.Assert(!success);
37+
Debug.Assert(bytesReturned > 0);
38+
39+
// rent with enough memory span and invoke
40+
var bytes = ArrayPool<byte>.Shared.Rent(bytesReturned);
41+
success = httpSysPropFeature.TryGetTlsClientHello(bytes, out _);
42+
Debug.Assert(success);
43+
44+
await context.Response.WriteAsync($"[Response] connectionId={connectionFeature.ConnectionId}; tlsClientHello.length={bytesReturned}; tlsclienthello start={string.Join(' ', bytes.AsSpan(0, 30).ToArray())}");
45+
await next(context);
46+
});
47+
48+
app.Run();

src/Servers/HttpSys/samples/TlsFeaturesObserve/Startup.cs

Lines changed: 0 additions & 28 deletions
This file was deleted.

src/Servers/HttpSys/samples/TlsFeaturesObserve/TlsFeaturesObserve.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10+
<Reference Include="Microsoft.AspNetCore" />
1011
<Reference Include="Microsoft.AspNetCore.Server.HttpSys" />
1112
<Reference Include="Microsoft.Extensions.Hosting" />
1213
<Reference Include="Microsoft.Extensions.Logging.Console" />

src/Servers/HttpSys/src/HttpSysListener.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Diagnostics;
66
using Microsoft.AspNetCore.Http;
77
using Microsoft.AspNetCore.HttpSys.Internal;
8-
using Microsoft.AspNetCore.Server.HttpSys.RequestProcessing;
98
using Microsoft.AspNetCore.WebUtilities;
109
using Microsoft.Extensions.Logging;
1110
using Windows.Win32;
@@ -42,7 +41,6 @@ internal sealed partial class HttpSysListener : IDisposable
4241
private readonly UrlGroup _urlGroup;
4342
private readonly RequestQueue _requestQueue;
4443
private readonly DisconnectListener _disconnectListener;
45-
private readonly TlsListener? _tlsListener;
4644

4745
private readonly object _internalLock;
4846

@@ -76,20 +74,14 @@ public HttpSysListener(HttpSysOptions options, ILoggerFactory loggerFactory)
7674
_requestQueue = new RequestQueue(options.RequestQueueName, options.RequestQueueMode,
7775
options.RequestQueueSecurityDescriptor, Logger);
7876
_urlGroup = new UrlGroup(_serverSession, _requestQueue, Logger);
79-
8077
_disconnectListener = new DisconnectListener(_requestQueue, Logger);
81-
if (options.TlsClientHelloBytesCallback is not null)
82-
{
83-
_tlsListener = new TlsListener(Logger, options.TlsClientHelloBytesCallback);
84-
}
8578
}
8679
catch (Exception exception)
8780
{
8881
// If Url group or request queue creation failed, close server session before throwing.
8982
_requestQueue?.Dispose();
9083
_urlGroup?.Dispose();
9184
_serverSession?.Dispose();
92-
_tlsListener?.Dispose();
9385
Log.HttpSysListenerCtorError(Logger, exception);
9486
throw;
9587
}
@@ -106,7 +98,6 @@ internal enum State
10698

10799
internal UrlGroup UrlGroup => _urlGroup;
108100
internal RequestQueue RequestQueue => _requestQueue;
109-
internal TlsListener? TlsListener => _tlsListener;
110101
internal DisconnectListener DisconnectListener => _disconnectListener;
111102

112103
public HttpSysOptions Options { get; }
@@ -260,7 +251,6 @@ private void DisposeInternal()
260251
Debug.Assert(!_serverSession.Id.IsInvalid, "ServerSessionHandle is invalid in CloseV2Config");
261252

262253
_serverSession.Dispose();
263-
_tlsListener?.Dispose();
264254
}
265255

266256
/// <summary>

src/Servers/HttpSys/src/HttpSysOptions.cs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -250,17 +250,6 @@ public Http503VerbosityLevel Http503Verbosity
250250
/// </remarks>
251251
public bool UseLatin1RequestHeaders { get; set; }
252252

253-
/// <summary>
254-
/// A callback to be invoked to get the TLS client hello bytes.
255-
/// Null by default.
256-
/// </summary>
257-
/// <remarks>
258-
/// Works only if <c>HTTP_SERVICE_CONFIG_SSL_FLAG_ENABLE_CACHE_CLIENT_HELLO</c> flag is set on http.sys service configuration.
259-
/// See <see href="https://learn.microsoft.com/windows/win32/api/http/nf-http-httpsetserviceconfiguration"/>
260-
/// and <see href="https://learn.microsoft.com/windows/win32/api/http/ne-http-http_service_config_id"/>
261-
/// </remarks>
262-
public Action<IFeatureCollection, ReadOnlySpan<byte>>? TlsClientHelloBytesCallback { get; set; }
263-
264253
// Not called when attaching to an existing queue.
265254
internal void Apply(UrlGroup urlGroup, RequestQueue? requestQueue)
266255
{
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Server.HttpSys;
5+
6+
/// <summary>
7+
/// Provides API to read HTTP_REQUEST_PROPERTY value from the HTTP.SYS request.
8+
/// <see href="https://learn.microsoft.com/windows/win32/api/http/ne-http-http_request_property"/>
9+
/// </summary>
10+
public interface IHttpSysRequestPropertyFeature
11+
{
12+
/// <summary>
13+
/// Reads the TLS client hello from HTTP.SYS
14+
/// </summary>
15+
/// <param name="tlsClientHelloBytesDestination">Where the raw bytes of the TLS Client Hello message are written.</param>
16+
/// <param name="bytesReturned">
17+
/// Returns the number of bytes written to <paramref name="tlsClientHelloBytesDestination"/>.
18+
/// Or can return the size of the buffer needed if <paramref name="tlsClientHelloBytesDestination"/> wasn't large enough.
19+
/// </param>
20+
/// <remarks>
21+
/// Works only if <c>HTTP_SERVICE_CONFIG_SSL_FLAG_ENABLE_CACHE_CLIENT_HELLO</c> flag is set on http.sys service configuration.
22+
/// See <see href="https://learn.microsoft.com/windows/win32/api/http/nf-http-httpsetserviceconfiguration"/>
23+
/// and <see href="https://learn.microsoft.com/windows/win32/api/http/ne-http-http_service_config_id"/>
24+
/// <br/><br/>
25+
/// If you don't want to guess the required <paramref name="tlsClientHelloBytesDestination"/> size before first invocation,
26+
/// you should first call with <paramref name="tlsClientHelloBytesDestination"/> set to empty size, so that you can retrieve the required buffer size from <paramref name="bytesReturned"/>,
27+
/// then allocate that amount of memory and retry the query.
28+
/// </remarks>
29+
/// <returns>
30+
/// True, if fetching TLS client hello was successful, false if <paramref name="tlsClientHelloBytesDestination"/> size is not large enough.
31+
/// If unsuccessful for other reason throws an exception.
32+
/// </returns>
33+
/// <exception cref="HttpSysException">Any HttpSys error except for ERROR_INSUFFICIENT_BUFFER or ERROR_MORE_DATA.</exception>
34+
/// <exception cref="InvalidOperationException">If HttpSys does not support querying the TLS Client Hello.</exception>
35+
bool TryGetTlsClientHello(Span<byte> tlsClientHelloBytesDestination, out int bytesReturned);
36+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#nullable enable
2-
Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions.TlsClientHelloBytesCallback.get -> System.Action<Microsoft.AspNetCore.Http.Features.IFeatureCollection!, System.ReadOnlySpan<byte>>?
3-
Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions.TlsClientHelloBytesCallback.set -> void
42
Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions.RequestQueueSecurityDescriptor.get -> System.Security.AccessControl.GenericSecurityDescriptor?
53
Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions.RequestQueueSecurityDescriptor.set -> void
4+
Microsoft.AspNetCore.Server.HttpSys.IHttpSysRequestPropertyFeature
5+
Microsoft.AspNetCore.Server.HttpSys.IHttpSysRequestPropertyFeature.TryGetTlsClientHello(System.Span<byte> tlsClientHelloBytesDestination, out int bytesReturned) -> bool

src/Servers/HttpSys/src/RequestProcessing/Request.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
using System.Security.Cryptography.X509Certificates;
1010
using System.Security.Principal;
1111
using Microsoft.AspNetCore.Http;
12-
using Microsoft.AspNetCore.Http.Features;
1312
using Microsoft.AspNetCore.HttpSys.Internal;
1413
using Microsoft.AspNetCore.Shared;
1514
using Microsoft.Extensions.Logging;
@@ -370,9 +369,6 @@ private void GetTlsHandshakeResults()
370369
SniHostName = sni.Hostname.ToString();
371370
}
372371

373-
internal bool GetAndInvokeTlsClientHelloCallback(IFeatureCollection features, Action<IFeatureCollection, ReadOnlySpan<byte>> tlsClientHelloBytesCallback)
374-
=> RequestContext.GetAndInvokeTlsClientHelloMessageBytesCallback(features, tlsClientHelloBytesCallback);
375-
376372
public X509Certificate2? ClientCertificate
377373
{
378374
get

src/Servers/HttpSys/src/RequestProcessing/RequestContext.FeatureCollection.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ internal partial class RequestContext :
3636
IHttpResponseTrailersFeature,
3737
IHttpResetFeature,
3838
IHttpSysRequestDelegationFeature,
39+
IHttpSysRequestPropertyFeature,
3940
IConnectionLifetimeNotificationFeature
4041
{
4142
private IFeatureCollection? _features;
@@ -753,4 +754,9 @@ void IConnectionLifetimeNotificationFeature.RequestClose()
753754
Response.Headers[HeaderNames.Connection] = "close";
754755
}
755756
}
757+
758+
public bool TryGetTlsClientHello(Span<byte> tlsClientHelloBytesDestination, out int bytesReturned)
759+
{
760+
return TryGetTlsClientHelloMessageBytes(tlsClientHelloBytesDestination, out bytesReturned);
761+
}
756762
}

0 commit comments

Comments
 (0)