Skip to content

feat(HTTP.SYS): on-demand TLS client hello retrieval #62209

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

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
74 changes: 31 additions & 43 deletions src/Servers/HttpSys/samples/TlsFeaturesObserve/Program.cs
Original file line number Diff line number Diff line change
@@ -1,60 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.HttpSys;
using Microsoft.Extensions.Hosting;
using TlsFeatureObserve;
using TlsFeaturesObserve.HttpSys;

HttpSysConfigurator.ConfigureCacheTlsClientHello();
CreateHostBuilder(args).Build().Run();

static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHost(webBuilder =>
{
webBuilder.UseStartup<Startup>()
.UseHttpSys(options =>
{
// If you want to use https locally: https://stackoverflow.com/a/51841893
options.UrlPrefixes.Add("https://*:6000"); // HTTPS

options.Authentication.Schemes = AuthenticationSchemes.None;
options.Authentication.AllowAnonymous = true;

options.TlsClientHelloBytesCallback = ProcessTlsClientHello;
});
});

static void ProcessTlsClientHello(IFeatureCollection features, ReadOnlySpan<byte> tlsClientHelloBytes)
{
var httpConnectionFeature = features.Get<IHttpConnectionFeature>();

var myTlsFeature = new MyTlsFeature(
connectionId: httpConnectionFeature.ConnectionId,
tlsClientHelloLength: tlsClientHelloBytes.Length);

features.Set<IMyTlsFeature>(myTlsFeature);
}
var builder = WebApplication.CreateBuilder(args);

public interface IMyTlsFeature
builder.WebHost.UseHttpSys(options =>
{
string ConnectionId { get; }
int TlsClientHelloLength { get; }
}
options.UrlPrefixes.Add("https://*:6000");
options.Authentication.Schemes = AuthenticationSchemes.None;
options.Authentication.AllowAnonymous = true;
});

public class MyTlsFeature : IMyTlsFeature
var app = builder.Build();

app.Use(async (context, next) =>
{
public string ConnectionId { get; }
public int TlsClientHelloLength { get; }

public MyTlsFeature(string connectionId, int tlsClientHelloLength)
{
ConnectionId = connectionId;
TlsClientHelloLength = tlsClientHelloLength;
}
}
var connectionFeature = context.Features.GetRequiredFeature<IHttpConnectionFeature>();
var httpSysPropFeature = context.Features.GetRequiredFeature<IHttpSysRequestPropertyFeature>();

// first time invocation to find out required size
var success = httpSysPropFeature.TryGetTlsClientHello(Array.Empty<byte>(), out var bytesReturned);
Debug.Assert(!success);
Debug.Assert(bytesReturned > 0);

// rent with enough memory span and invoke
var bytes = ArrayPool<byte>.Shared.Rent(bytesReturned);
success = httpSysPropFeature.TryGetTlsClientHello(bytes, out _);
Debug.Assert(success);

await context.Response.WriteAsync($"[Response] connectionId={connectionFeature.ConnectionId}; tlsClientHello.length={bytesReturned}; tlsclienthello start={string.Join(' ', bytes.AsSpan(0, 30).ToArray())}");
await next(context);
});

app.Run();
28 changes: 0 additions & 28 deletions src/Servers/HttpSys/samples/TlsFeaturesObserve/Startup.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
</PropertyGroup>

<ItemGroup>
<Reference Include="Microsoft.AspNetCore" />
<Reference Include="Microsoft.AspNetCore.Server.HttpSys" />
<Reference Include="Microsoft.Extensions.Hosting" />
<Reference Include="Microsoft.Extensions.Logging.Console" />
Expand Down
10 changes: 0 additions & 10 deletions src/Servers/HttpSys/src/HttpSysListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.AspNetCore.Server.HttpSys.RequestProcessing;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using Windows.Win32;
Expand Down Expand Up @@ -42,7 +41,6 @@ internal sealed partial class HttpSysListener : IDisposable
private readonly UrlGroup _urlGroup;
private readonly RequestQueue _requestQueue;
private readonly DisconnectListener _disconnectListener;
private readonly TlsListener? _tlsListener;

private readonly object _internalLock;

Expand Down Expand Up @@ -76,20 +74,14 @@ public HttpSysListener(HttpSysOptions options, ILoggerFactory loggerFactory)
_requestQueue = new RequestQueue(options.RequestQueueName, options.RequestQueueMode,
options.RequestQueueSecurityDescriptor, Logger);
_urlGroup = new UrlGroup(_serverSession, _requestQueue, Logger);

_disconnectListener = new DisconnectListener(_requestQueue, Logger);
if (options.TlsClientHelloBytesCallback is not null)
{
_tlsListener = new TlsListener(Logger, options.TlsClientHelloBytesCallback);
}
}
catch (Exception exception)
{
// If Url group or request queue creation failed, close server session before throwing.
_requestQueue?.Dispose();
_urlGroup?.Dispose();
_serverSession?.Dispose();
_tlsListener?.Dispose();
Log.HttpSysListenerCtorError(Logger, exception);
throw;
}
Expand All @@ -106,7 +98,6 @@ internal enum State

internal UrlGroup UrlGroup => _urlGroup;
internal RequestQueue RequestQueue => _requestQueue;
internal TlsListener? TlsListener => _tlsListener;
internal DisconnectListener DisconnectListener => _disconnectListener;

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

_serverSession.Dispose();
_tlsListener?.Dispose();
}

/// <summary>
Expand Down
11 changes: 0 additions & 11 deletions src/Servers/HttpSys/src/HttpSysOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,17 +250,6 @@ public Http503VerbosityLevel Http503Verbosity
/// </remarks>
public bool UseLatin1RequestHeaders { get; set; }

/// <summary>
/// A callback to be invoked to get the TLS client hello bytes.
/// Null by default.
/// </summary>
/// <remarks>
/// Works only if <c>HTTP_SERVICE_CONFIG_SSL_FLAG_ENABLE_CACHE_CLIENT_HELLO</c> flag is set on http.sys service configuration.
/// See <see href="https://learn.microsoft.com/windows/win32/api/http/nf-http-httpsetserviceconfiguration"/>
/// and <see href="https://learn.microsoft.com/windows/win32/api/http/ne-http-http_service_config_id"/>
/// </remarks>
public Action<IFeatureCollection, ReadOnlySpan<byte>>? TlsClientHelloBytesCallback { get; set; }

// Not called when attaching to an existing queue.
internal void Apply(UrlGroup urlGroup, RequestQueue? requestQueue)
{
Expand Down
36 changes: 36 additions & 0 deletions src/Servers/HttpSys/src/IHttpSysRequestPropertyFeature.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.Server.HttpSys;

/// <summary>
/// Provides API to read HTTP_REQUEST_PROPERTY value from the HTTP.SYS request.
/// <see href="https://learn.microsoft.com/windows/win32/api/http/ne-http-http_request_property"/>
/// </summary>
public interface IHttpSysRequestPropertyFeature
{
/// <summary>
/// Reads the TLS client hello from HTTP.SYS
/// </summary>
/// <param name="tlsClientHelloBytesDestination">Where the raw bytes of the TLS Client Hello message are written.</param>
/// <param name="bytesReturned">
/// Returns the number of bytes written to <paramref name="tlsClientHelloBytesDestination"/>.
/// Or can return the size of the buffer needed if <paramref name="tlsClientHelloBytesDestination"/> wasn't large enough.
/// </param>
/// <remarks>
/// Works only if <c>HTTP_SERVICE_CONFIG_SSL_FLAG_ENABLE_CACHE_CLIENT_HELLO</c> flag is set on http.sys service configuration.
/// See <see href="https://learn.microsoft.com/windows/win32/api/http/nf-http-httpsetserviceconfiguration"/>
/// and <see href="https://learn.microsoft.com/windows/win32/api/http/ne-http-http_service_config_id"/>
/// <br/><br/>
/// If you don't want to guess the required <paramref name="tlsClientHelloBytesDestination"/> size before first invocation,
/// 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"/>,
/// then allocate that amount of memory and retry the query.
/// </remarks>
/// <returns>
/// True, if fetching TLS client hello was successful, false if <paramref name="tlsClientHelloBytesDestination"/> size is not large enough.
/// If unsuccessful for other reason throws an exception.
/// </returns>
/// <exception cref="HttpSysException">Any HttpSys error except for ERROR_INSUFFICIENT_BUFFER or ERROR_MORE_DATA.</exception>
/// <exception cref="InvalidOperationException">If HttpSys does not support querying the TLS Client Hello.</exception>
bool TryGetTlsClientHello(Span<byte> tlsClientHelloBytesDestination, out int bytesReturned);
}
4 changes: 2 additions & 2 deletions src/Servers/HttpSys/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#nullable enable
Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions.TlsClientHelloBytesCallback.get -> System.Action<Microsoft.AspNetCore.Http.Features.IFeatureCollection!, System.ReadOnlySpan<byte>>?
Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions.TlsClientHelloBytesCallback.set -> void
Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions.RequestQueueSecurityDescriptor.get -> System.Security.AccessControl.GenericSecurityDescriptor?
Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions.RequestQueueSecurityDescriptor.set -> void
Microsoft.AspNetCore.Server.HttpSys.IHttpSysRequestPropertyFeature
Microsoft.AspNetCore.Server.HttpSys.IHttpSysRequestPropertyFeature.TryGetTlsClientHello(System.Span<byte> tlsClientHelloBytesDestination, out int bytesReturned) -> bool
4 changes: 0 additions & 4 deletions src/Servers/HttpSys/src/RequestProcessing/Request.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.AspNetCore.Shared;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -370,9 +369,6 @@ private void GetTlsHandshakeResults()
SniHostName = sni.Hostname.ToString();
}

internal bool GetAndInvokeTlsClientHelloCallback(IFeatureCollection features, Action<IFeatureCollection, ReadOnlySpan<byte>> tlsClientHelloBytesCallback)
=> RequestContext.GetAndInvokeTlsClientHelloMessageBytesCallback(features, tlsClientHelloBytesCallback);

public X509Certificate2? ClientCertificate
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ internal partial class RequestContext :
IHttpResponseTrailersFeature,
IHttpResetFeature,
IHttpSysRequestDelegationFeature,
IHttpSysRequestPropertyFeature,
IConnectionLifetimeNotificationFeature
{
private IFeatureCollection? _features;
Expand Down Expand Up @@ -753,4 +754,9 @@ void IConnectionLifetimeNotificationFeature.RequestClose()
Response.Headers[HeaderNames.Connection] = "close";
}
}

public bool TryGetTlsClientHello(Span<byte> tlsClientHelloBytesDestination, out int bytesReturned)
{
return TryGetTlsClientHelloMessageBytes(tlsClientHelloBytesDestination, out bytesReturned);
}
}
Loading
Loading