-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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
feat: fetch TLS client hello message from HTTP.SYS #60806
Open
DeagleGross
wants to merge
25
commits into
dotnet:main
Choose a base branch
from
DeagleGross:dmkorolev/ja4
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
d7953f6
setup for tls clinet hello exposure
DeagleGross aa3ae32
correctly retry access
DeagleGross 840cdac
last minute changes
DeagleGross c9431ec
fix warnings
DeagleGross 1d0f0d8
hook up tls client hello callback
DeagleGross 3c4d6fc
fix warnings & publish API
DeagleGross 0c31882
minimal
DeagleGross 5d7d187
only go via callback if options has callback set; remove unused
DeagleGross 8cf4cea
a bit refactor
DeagleGross 6f5cfaf
merge main
DeagleGross 7566aec
update in slnx !
DeagleGross 946440a
PR review
DeagleGross dc8ae7e
Merge branch 'main' into dmkorolev/ja4
DeagleGross 0ad12ca
address PR comments x1
DeagleGross e9c7ead
TTL & evict approach
DeagleGross e170454
address comments 1
DeagleGross b830fca
periodic timer
DeagleGross e1bdba4
address comments x3
DeagleGross 033e211
TryAdd
DeagleGross 23bfa1b
make a static field (just in case)
DeagleGross c69797f
Cache updates
BrennanConroy 886de4f
test
BrennanConroy 3fe7871
whitespace
BrennanConroy 8dc51f8
clear array
BrennanConroy 54a9c75
appcontext
BrennanConroy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
126 changes: 126 additions & 0 deletions
126
src/Servers/HttpSys/samples/TlsFeaturesObserve/HttpSys/HttpSysConfigurator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using System.Net; | ||
using System.Net.Sockets; | ||
using System.Runtime.InteropServices; | ||
using System.Text; | ||
using Microsoft.AspNetCore.Http; | ||
|
||
namespace TlsFeaturesObserve.HttpSys; | ||
|
||
internal static class HttpSysConfigurator | ||
{ | ||
const uint HTTP_INITIALIZE_CONFIG = 0x00000002; | ||
const uint ERROR_ALREADY_EXISTS = 183; | ||
|
||
static readonly HTTPAPI_VERSION HttpApiVersion = new HTTPAPI_VERSION(1, 0); | ||
|
||
internal static void ConfigureCacheTlsClientHello() | ||
{ | ||
IPEndPoint ipPort = new IPEndPoint(new IPAddress([0, 0, 0, 0]), 6000); | ||
string certThumbprint = "" /* your cert thumbprint here */; | ||
Guid appId = Guid.NewGuid(); | ||
string sslCertStoreName = "My"; | ||
|
||
CallHttpApi(() => SetConfiguration(ipPort, certThumbprint, appId, sslCertStoreName)); | ||
} | ||
|
||
static void SetConfiguration(IPEndPoint ipPort, string certThumbprint, Guid appId, string sslCertStoreName) | ||
{ | ||
GCHandle sockAddrHandle = CreateSockaddrStructure(ipPort); | ||
var pIpPort = sockAddrHandle.AddrOfPinnedObject(); | ||
var httpServiceConfigSslKey = new HTTP_SERVICE_CONFIG_SSL_KEY(pIpPort); | ||
|
||
byte[] hash = GetHash(certThumbprint); | ||
var handleHash = GCHandle.Alloc(hash, GCHandleType.Pinned); | ||
var configSslParam = new HTTP_SERVICE_CONFIG_SSL_PARAM | ||
{ | ||
AppId = appId, | ||
DefaultFlags = 0x00008000 /* HTTP_SERVICE_CONFIG_SSL_FLAG_ENABLE_CACHE_CLIENT_HELLO */, | ||
DefaultRevocationFreshnessTime = 0, | ||
DefaultRevocationUrlRetrievalTimeout = 15, | ||
pSslCertStoreName = sslCertStoreName, | ||
pSslHash = handleHash.AddrOfPinnedObject(), | ||
SslHashLength = hash.Length, | ||
pDefaultSslCtlIdentifier = null, | ||
pDefaultSslCtlStoreName = sslCertStoreName | ||
}; | ||
|
||
var configSslSet = new HTTP_SERVICE_CONFIG_SSL_SET | ||
{ | ||
ParamDesc = configSslParam, | ||
KeyDesc = httpServiceConfigSslKey | ||
}; | ||
|
||
var pInputConfigInfo = Marshal.AllocCoTaskMem( | ||
Marshal.SizeOf(typeof(HTTP_SERVICE_CONFIG_SSL_SET))); | ||
Marshal.StructureToPtr(configSslSet, pInputConfigInfo, false); | ||
|
||
uint status = HttpSetServiceConfiguration(nint.Zero, | ||
HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo, | ||
pInputConfigInfo, | ||
Marshal.SizeOf(configSslSet), | ||
nint.Zero); | ||
|
||
if (status == ERROR_ALREADY_EXISTS || status == 0) // already present or success | ||
{ | ||
Console.WriteLine("HttpServiceConfiguration is correct"); | ||
} | ||
else | ||
{ | ||
Console.WriteLine("Failed to HttpSetServiceConfiguration: " + status); | ||
} | ||
} | ||
|
||
static byte[] GetHash(string thumbprint) | ||
{ | ||
int length = thumbprint.Length; | ||
byte[] bytes = new byte[length / 2]; | ||
for (int i = 0; i < length; i += 2) | ||
{ | ||
bytes[i / 2] = Convert.ToByte(thumbprint.Substring(i, 2), 16); | ||
} | ||
|
||
return bytes; | ||
} | ||
|
||
static GCHandle CreateSockaddrStructure(IPEndPoint ipEndPoint) | ||
{ | ||
SocketAddress socketAddress = ipEndPoint.Serialize(); | ||
|
||
// use an array of bytes instead of the sockaddr structure | ||
byte[] sockAddrStructureBytes = new byte[socketAddress.Size]; | ||
GCHandle sockAddrHandle = GCHandle.Alloc(sockAddrStructureBytes, GCHandleType.Pinned); | ||
for (int i = 0; i < socketAddress.Size; ++i) | ||
{ | ||
sockAddrStructureBytes[i] = socketAddress[i]; | ||
} | ||
return sockAddrHandle; | ||
} | ||
|
||
static void CallHttpApi(Action body) | ||
{ | ||
const uint flags = HTTP_INITIALIZE_CONFIG; | ||
uint retVal = HttpInitialize(HttpApiVersion, flags, IntPtr.Zero); | ||
body(); | ||
} | ||
|
||
// disabled warning since it is just a sample | ||
#pragma warning disable SYSLIB1054 // Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time | ||
[DllImport("httpapi.dll", SetLastError = true)] | ||
private static extern uint HttpInitialize( | ||
HTTPAPI_VERSION version, | ||
uint flags, | ||
IntPtr pReserved); | ||
|
||
[DllImport("httpapi.dll", SetLastError = true)] | ||
public static extern uint HttpSetServiceConfiguration( | ||
nint serviceIntPtr, | ||
HTTP_SERVICE_CONFIG_ID configId, | ||
nint pConfigInformation, | ||
int configInformationLength, | ||
nint pOverlapped); | ||
#pragma warning restore SYSLIB1054 // Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time | ||
} |
95 changes: 95 additions & 0 deletions
95
src/Servers/HttpSys/samples/TlsFeaturesObserve/HttpSys/Native.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Runtime.InteropServices; | ||
using System.Text; | ||
|
||
namespace TlsFeaturesObserve.HttpSys; | ||
|
||
[StructLayout(LayoutKind.Sequential, Pack = 2)] | ||
public struct HTTPAPI_VERSION | ||
{ | ||
public ushort HttpApiMajorVersion; | ||
public ushort HttpApiMinorVersion; | ||
|
||
public HTTPAPI_VERSION(ushort majorVersion, ushort minorVersion) | ||
{ | ||
HttpApiMajorVersion = majorVersion; | ||
HttpApiMinorVersion = minorVersion; | ||
} | ||
} | ||
|
||
public enum HTTP_SERVICE_CONFIG_ID | ||
{ | ||
HttpServiceConfigIPListenList = 0, | ||
HttpServiceConfigSSLCertInfo, | ||
HttpServiceConfigUrlAclInfo, | ||
HttpServiceConfigMax | ||
} | ||
|
||
[StructLayout(LayoutKind.Sequential)] | ||
public struct HTTP_SERVICE_CONFIG_SSL_SET | ||
{ | ||
public HTTP_SERVICE_CONFIG_SSL_KEY KeyDesc; | ||
public HTTP_SERVICE_CONFIG_SSL_PARAM ParamDesc; | ||
} | ||
|
||
[StructLayout(LayoutKind.Sequential)] | ||
public struct HTTP_SERVICE_CONFIG_SSL_KEY | ||
{ | ||
public IntPtr pIpPort; | ||
|
||
public HTTP_SERVICE_CONFIG_SSL_KEY(IntPtr pIpPort) | ||
{ | ||
this.pIpPort = pIpPort; | ||
} | ||
} | ||
|
||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | ||
public struct HTTP_SERVICE_CONFIG_SSL_PARAM | ||
{ | ||
public int SslHashLength; | ||
public IntPtr pSslHash; | ||
public Guid AppId; | ||
[MarshalAs(UnmanagedType.LPWStr)] | ||
public string pSslCertStoreName; | ||
public CertCheckModes DefaultCertCheckMode; | ||
public int DefaultRevocationFreshnessTime; | ||
public int DefaultRevocationUrlRetrievalTimeout; | ||
[MarshalAs(UnmanagedType.LPWStr)] | ||
public string pDefaultSslCtlIdentifier; | ||
[MarshalAs(UnmanagedType.LPWStr)] | ||
public string pDefaultSslCtlStoreName; | ||
public uint DefaultFlags; // HTTP_SERVICE_CONFIG_SSL_FLAG | ||
} | ||
|
||
[Flags] | ||
public enum CertCheckModes : uint | ||
{ | ||
/// <summary> | ||
/// Enables the client certificate revocation check. | ||
/// </summary> | ||
None = 0, | ||
|
||
/// <summary> | ||
/// Client certificate is not to be verified for revocation. | ||
/// </summary> | ||
DoNotVerifyCertificateRevocation = 1, | ||
|
||
/// <summary> | ||
/// Only cached certificate is to be used the revocation check. | ||
/// </summary> | ||
VerifyRevocationWithCachedCertificateOnly = 2, | ||
|
||
/// <summary> | ||
/// The RevocationFreshnessTime setting is enabled. | ||
/// </summary> | ||
EnableRevocationFreshnessTime = 4, | ||
|
||
/// <summary> | ||
/// No usage check is to be performed. | ||
/// </summary> | ||
NoUsageCheck = 0x10000 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Reflection; | ||
using System.Runtime.InteropServices; | ||
using Microsoft.AspNetCore.Hosting; | ||
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); | ||
} | ||
|
||
public interface IMyTlsFeature | ||
{ | ||
string ConnectionId { get; } | ||
int TlsClientHelloLength { get; } | ||
} | ||
|
||
public class MyTlsFeature : IMyTlsFeature | ||
{ | ||
public string ConnectionId { get; } | ||
public int TlsClientHelloLength { get; } | ||
|
||
public MyTlsFeature(string connectionId, int tlsClientHelloLength) | ||
{ | ||
ConnectionId = connectionId; | ||
TlsClientHelloLength = tlsClientHelloLength; | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
src/Servers/HttpSys/samples/TlsFeaturesObserve/Properties/launchSettings.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"profiles": { | ||
"TlsFeaturesObserve": { | ||
"commandName": "Project", | ||
"launchBrowser": true, | ||
"applicationUrl": "http://localhost:5000", | ||
"nativeDebugging": true | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.AspNetCore.Connections.Features; | ||
using Microsoft.AspNetCore.Hosting; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Http.Features; | ||
using Microsoft.AspNetCore.Server.HttpSys; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace TlsFeatureObserve; | ||
|
||
public class Startup | ||
{ | ||
public void Configure(IApplicationBuilder app) | ||
{ | ||
app.Run(async (HttpContext context) => | ||
{ | ||
context.Response.ContentType = "text/plain"; | ||
|
||
var tlsFeature = context.Features.Get<IMyTlsFeature>(); | ||
await context.Response.WriteAsync("TlsClientHello data: " + $"connectionId={tlsFeature?.ConnectionId}; length={tlsFeature?.TlsClientHelloLength}"); | ||
}); | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
src/Servers/HttpSys/samples/TlsFeaturesObserve/TlsFeaturesObserve.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> | ||
<OutputType>Exe</OutputType> | ||
<ServerGarbageCollection>true</ServerGarbageCollection> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<Reference Include="Microsoft.AspNetCore.Server.HttpSys" /> | ||
<Reference Include="Microsoft.Extensions.Hosting" /> | ||
<Reference Include="Microsoft.Extensions.Logging.Console" /> | ||
</ItemGroup> | ||
</Project> |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fetching TLS client hello feature from HTTP.SYS works only if a pre-configuration was done for ssl cert binding and netsh ssl config beforehand. User would need to set service configuration via HttpSetServiceConfiguration with
HTTP_SERVICE_CONFIG_SSL_FLAG_ENABLE_CACHE_CLIENT_HELLO
flag.That is and should not be a part of ASP.NET app, but I added that for "sample app" for simplicity of reproduction.