From 468a834a6875e873fcbffe40d5b9960568eb8ee6 Mon Sep 17 00:00:00 2001 From: Korolev Dmitry Date: Mon, 15 Sep 2025 15:16:25 +0200 Subject: [PATCH 01/10] init query tls cipher suite info --- src/Servers/HttpSys/src/LoggerEventIds.cs | 1 + .../HttpSys/src/NativeInterop/HttpApi.cs | 2 ++ .../Types/SecPkgContext_CipherInfo.cs | 29 +++++++++++++++ .../HttpSys/src/RequestProcessing/Request.cs | 8 +++++ .../RequestContext.FeatureCollection.cs | 3 ++ .../RequestProcessing/RequestContext.Log.cs | 3 ++ .../src/RequestProcessing/RequestContext.cs | 36 +++++++++++++++++++ 7 files changed, 82 insertions(+) create mode 100644 src/Servers/HttpSys/src/NativeInterop/Types/SecPkgContext_CipherInfo.cs diff --git a/src/Servers/HttpSys/src/LoggerEventIds.cs b/src/Servers/HttpSys/src/LoggerEventIds.cs index e6d745f506be..a9f2c969c6bc 100644 --- a/src/Servers/HttpSys/src/LoggerEventIds.cs +++ b/src/Servers/HttpSys/src/LoggerEventIds.cs @@ -60,4 +60,5 @@ internal static class LoggerEventIds public const int AcceptObserveExpectationMismatch = 53; public const int RequestParsingError = 54; public const int TlsListenerError = 55; + public const int QueryTlsCipherSuiteError = 56; } diff --git a/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs b/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs index f5dfbc96a6cd..fbada3843642 100644 --- a/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs +++ b/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs @@ -70,6 +70,7 @@ internal static unsafe uint HttpSetRequestProperty(SafeHandle requestQueueHandle internal static bool SupportsReset { get; } internal static bool SupportsDelegation { get; } internal static bool SupportsClientHello { get; } + internal static bool SupportsQueryTlsCipherInfo { get; } internal static bool Supported { get; } static unsafe HttpApi() @@ -86,6 +87,7 @@ static unsafe HttpApi() SupportsTrailers = IsFeatureSupported(HTTP_FEATURE_ID.HttpFeatureResponseTrailers); SupportsDelegation = IsFeatureSupported(HTTP_FEATURE_ID.HttpFeatureDelegateEx); SupportsClientHello = IsFeatureSupported((HTTP_FEATURE_ID)11 /* HTTP_FEATURE_ID.HttpFeatureCacheTlsClientHello */) && HttpGetRequestPropertySupported; + SupportsQueryTlsCipherInfo = IsFeatureSupported((HTTP_FEATURE_ID)15 /* HTTP_FEATURE_ID.HttpFeatureQueryCipherInfo */) && HttpGetRequestPropertySupported; } } diff --git a/src/Servers/HttpSys/src/NativeInterop/Types/SecPkgContext_CipherInfo.cs b/src/Servers/HttpSys/src/NativeInterop/Types/SecPkgContext_CipherInfo.cs new file mode 100644 index 000000000000..1eb1642b778b --- /dev/null +++ b/src/Servers/HttpSys/src/NativeInterop/Types/SecPkgContext_CipherInfo.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace Microsoft.AspNetCore.Server.HttpSys.NativeInterop.Types; + +// From Schannel.h +[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] +internal unsafe struct SecPkgContext_CipherInfo +{ + private const int SZ_ALG_MAX_SIZE = 64; + + private readonly int dwVersion; + private readonly int dwProtocol; + public readonly int dwCipherSuite; + private readonly int dwBaseCipherSuite; + private fixed char szCipherSuite[SZ_ALG_MAX_SIZE]; + private fixed char szCipher[SZ_ALG_MAX_SIZE]; + private readonly int dwCipherLen; + private readonly int dwCipherBlockLen; // in bytes + private fixed char szHash[SZ_ALG_MAX_SIZE]; + private readonly int dwHashLen; + private fixed char szExchange[SZ_ALG_MAX_SIZE]; + private readonly int dwMinExchangeLen; + private readonly int dwMaxExchangeLen; + private fixed char szCertificate[SZ_ALG_MAX_SIZE]; + private readonly int dwKeyType; +} diff --git a/src/Servers/HttpSys/src/RequestProcessing/Request.cs b/src/Servers/HttpSys/src/RequestProcessing/Request.cs index 8e4babf7ca21..bcc6cea11c63 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/Request.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/Request.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Net; +using System.Net.Security; using System.Security; using System.Security.Authentication; using System.Security.Cryptography; @@ -334,6 +335,8 @@ private AspNetCore.HttpSys.Internal.SocketAddress LocalEndPoint public SslProtocols Protocol { get; private set; } + public TlsCipherSuite NegotiatedCipherSuite { get; private set; } + [Obsolete(Obsoletions.RuntimeTlsCipherAlgorithmEnumsMessage, DiagnosticId = Obsoletions.RuntimeTlsCipherAlgorithmEnumsDiagId, UrlFormat = Obsoletions.RuntimeSharedUrlFormat)] public CipherAlgorithmType CipherAlgorithm { get; private set; } @@ -354,6 +357,11 @@ private AspNetCore.HttpSys.Internal.SocketAddress LocalEndPoint private void GetTlsHandshakeResults() { + if (RequestContext.TryGetTlsCipherSuite(out var tlsCipherSuite)) + { + NegotiatedCipherSuite = tlsCipherSuite; + } + var handshake = RequestContext.GetTlsHandshake(); Protocol = (SslProtocols)handshake.Protocol; #pragma warning disable SYSLIB0058 // Type or member is obsolete diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.FeatureCollection.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.FeatureCollection.cs index 1c80f92febc2..a66e44d1484c 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.FeatureCollection.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.FeatureCollection.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.IO.Pipelines; using System.Net; +using System.Net.Security; using System.Security.Authentication; using System.Security.Claims; using System.Security.Cryptography.X509Certificates; @@ -593,6 +594,8 @@ bool IHttpBodyControlFeature.AllowSynchronousIO SslProtocols ITlsHandshakeFeature.Protocol => Request.Protocol; + TlsCipherSuite? ITlsHandshakeFeature.NegotiatedCipherSuite => Request.NegotiatedCipherSuite; + #pragma warning disable SYSLIB0058 // Type or member is obsolete CipherAlgorithmType ITlsHandshakeFeature.CipherAlgorithm => Request.CipherAlgorithm; diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.Log.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.Log.cs index d7766698bc41..0fce6896778b 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.Log.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.Log.cs @@ -23,5 +23,8 @@ private static partial class Log [LoggerMessage(LoggerEventIds.RequestParsingError, LogLevel.Debug, "Failed to invoke QueryTlsClientHello; RequestId: {RequestId}; Win32 Error code: {Win32Error}", EventName = "TlsClientHelloRetrieveError")] public static partial void TlsClientHelloRetrieveError(ILogger logger, ulong requestId, uint win32Error); + + [LoggerMessage(LoggerEventIds.QueryTlsCipherSuiteError, LogLevel.Debug, "Failed to invoke QueryTlsCipherSuite; RequestId: {RequestId}; Win32 Error code: {Win32Error}", EventName = "QueryTlsCipherSuiteError")] + public static partial void QueryTlsCipherSuiteError(ILogger logger, ulong requestId, uint win32Error); } } diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs index eba7d33ff3b8..1f46187e4d88 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs @@ -1,10 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Net.Security; using System.Runtime.InteropServices; using System.Security.Principal; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.HttpSys.Internal; +using Microsoft.AspNetCore.Server.HttpSys.NativeInterop.Types; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; using Windows.Win32; @@ -219,6 +221,40 @@ internal void ForceCancelRequest() } } + internal unsafe bool TryGetTlsCipherSuite(out TlsCipherSuite tlsCipherSuite) + { + tlsCipherSuite = default; + if (!HttpApi.SupportsQueryTlsCipherInfo) + { + return false; + } + + var requestId = PinsReleased ? Request.RequestId : RequestId; + + SecPkgContext_CipherInfo cipherInfo = default; + uint bytesReturned = 0; + + var statusCode = HttpApi.HttpGetRequestProperty( + requestQueueHandle: Server.RequestQueue.Handle, + requestId, + propertyId: (HTTP_REQUEST_PROPERTY)14 /* HTTP_REQUEST_PROPERTY.HttpRequestPropertyTlsCipherInfo */, + qualifier: null, + qualifierSize: 0, + output: &cipherInfo, + outputSize: (uint)sizeof(SecPkgContext_CipherInfo), + bytesReturned: (IntPtr)(&bytesReturned), + overlapped: IntPtr.Zero); + + if (statusCode is ErrorCodes.ERROR_SUCCESS) + { + // TODO parse SecPkgContext_CipherInfo to TlsCipherSuite here! + return true; + } + + Log.QueryTlsCipherSuiteError(Logger, requestId, statusCode); + return false; + } + /// /// Attempts to get the client hello message bytes from the http.sys. /// If successful writes the bytes into , and shows how many bytes were written in . From a25eeacaec6574d667249fc2cf10220408bbbe54 Mon Sep 17 00:00:00 2001 From: Korolev Dmitry Date: Mon, 15 Sep 2025 17:31:23 +0200 Subject: [PATCH 02/10] correct types --- .../samples/TlsFeaturesObserve/Program.cs | 11 +++++++++- .../HttpSys/src/RequestProcessing/Request.cs | 9 +++----- .../src/RequestProcessing/RequestContext.cs | 21 ++++++++++++------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/Servers/HttpSys/samples/TlsFeaturesObserve/Program.cs b/src/Servers/HttpSys/samples/TlsFeaturesObserve/Program.cs index ed61268897ef..1b9e03dc5706 100644 --- a/src/Servers/HttpSys/samples/TlsFeaturesObserve/Program.cs +++ b/src/Servers/HttpSys/samples/TlsFeaturesObserve/Program.cs @@ -6,6 +6,7 @@ using System.Reflection; using System.Runtime.InteropServices; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; @@ -30,6 +31,7 @@ { var connectionFeature = context.Features.GetRequiredFeature(); var httpSysPropFeature = context.Features.GetRequiredFeature(); + var tlsHandshakeFeature = context.Features.GetRequiredFeature(); // first time invocation to find out required size var success = httpSysPropFeature.TryGetTlsClientHello(Array.Empty(), out var bytesReturned); @@ -41,7 +43,14 @@ 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 context.Response.WriteAsync( + $""" + connectionId = {connectionFeature.ConnectionId}; + negotiated cipher suite = {tlsHandshakeFeature.NegotiatedCipherSuite}; + tlsClientHello.length = {bytesReturned}; + tlsclienthello start = {string.Join(' ', bytes.AsSpan(0, 30).ToArray())} + """); + await next(context); }); diff --git a/src/Servers/HttpSys/src/RequestProcessing/Request.cs b/src/Servers/HttpSys/src/RequestProcessing/Request.cs index bcc6cea11c63..3d99fe8e718b 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/Request.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/Request.cs @@ -335,7 +335,7 @@ private AspNetCore.HttpSys.Internal.SocketAddress LocalEndPoint public SslProtocols Protocol { get; private set; } - public TlsCipherSuite NegotiatedCipherSuite { get; private set; } + public TlsCipherSuite? NegotiatedCipherSuite { get; private set; } [Obsolete(Obsoletions.RuntimeTlsCipherAlgorithmEnumsMessage, DiagnosticId = Obsoletions.RuntimeTlsCipherAlgorithmEnumsDiagId, UrlFormat = Obsoletions.RuntimeSharedUrlFormat)] public CipherAlgorithmType CipherAlgorithm { get; private set; } @@ -357,13 +357,10 @@ private AspNetCore.HttpSys.Internal.SocketAddress LocalEndPoint private void GetTlsHandshakeResults() { - if (RequestContext.TryGetTlsCipherSuite(out var tlsCipherSuite)) - { - NegotiatedCipherSuite = tlsCipherSuite; - } - var handshake = RequestContext.GetTlsHandshake(); Protocol = (SslProtocols)handshake.Protocol; + + NegotiatedCipherSuite = RequestContext.GetTlsCipherSuite(); #pragma warning disable SYSLIB0058 // Type or member is obsolete CipherAlgorithm = (CipherAlgorithmType)handshake.CipherType; CipherStrength = (int)handshake.CipherStrength; diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs index 1f46187e4d88..f233f9ccd520 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs @@ -221,12 +221,19 @@ internal void ForceCancelRequest() } } - internal unsafe bool TryGetTlsCipherSuite(out TlsCipherSuite tlsCipherSuite) + /// + /// Gets TLS cipher suite used for the request, if supported by the OS and http.sys. + /// + /// + /// null, if query of TlsCipherSuite is not supported. + /// TlsCipherSuite value, if query is successful. + /// Throws , if query is supported but fails for some reason. + /// + internal unsafe TlsCipherSuite? GetTlsCipherSuite() { - tlsCipherSuite = default; if (!HttpApi.SupportsQueryTlsCipherInfo) { - return false; + return default; } var requestId = PinsReleased ? Request.RequestId : RequestId; @@ -237,7 +244,7 @@ internal unsafe bool TryGetTlsCipherSuite(out TlsCipherSuite tlsCipherSuite) var statusCode = HttpApi.HttpGetRequestProperty( requestQueueHandle: Server.RequestQueue.Handle, requestId, - propertyId: (HTTP_REQUEST_PROPERTY)14 /* HTTP_REQUEST_PROPERTY.HttpRequestPropertyTlsCipherInfo */, + propertyId: (HTTP_REQUEST_PROPERTY)14 /* HTTP_REQUEST_PROPERTY.HttpRequestPropertyTlsCipherInfo */, qualifier: null, qualifierSize: 0, output: &cipherInfo, @@ -247,12 +254,12 @@ internal unsafe bool TryGetTlsCipherSuite(out TlsCipherSuite tlsCipherSuite) if (statusCode is ErrorCodes.ERROR_SUCCESS) { - // TODO parse SecPkgContext_CipherInfo to TlsCipherSuite here! - return true; + return (TlsCipherSuite)cipherInfo.dwCipherSuite; } + // OS supports querying TlsCipherSuite, but request failed. Log.QueryTlsCipherSuiteError(Logger, requestId, statusCode); - return false; + throw new HttpSysException((int)statusCode); } /// From e0ed20a9b2d562b62fe4896fafa7995b3bf8ccce Mon Sep 17 00:00:00 2001 From: Korolev Dmitry Date: Mon, 15 Sep 2025 17:49:49 +0200 Subject: [PATCH 03/10] dont throw excp --- src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs index f233f9ccd520..e00e58b6bba2 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs @@ -225,9 +225,8 @@ internal void ForceCancelRequest() /// Gets TLS cipher suite used for the request, if supported by the OS and http.sys. /// /// - /// null, if query of TlsCipherSuite is not supported. + /// null, if query of TlsCipherSuite is not supported or the query failed. /// TlsCipherSuite value, if query is successful. - /// Throws , if query is supported but fails for some reason. /// internal unsafe TlsCipherSuite? GetTlsCipherSuite() { @@ -259,7 +258,7 @@ internal void ForceCancelRequest() // OS supports querying TlsCipherSuite, but request failed. Log.QueryTlsCipherSuiteError(Logger, requestId, statusCode); - throw new HttpSysException((int)statusCode); + return null; } /// From 9d4e1c9fe0fbfe9b181c76f230b9a50c82153e72 Mon Sep 17 00:00:00 2001 From: Korolev Dmitry Date: Wed, 17 Sep 2025 20:03:52 +0200 Subject: [PATCH 04/10] parse it in IIS --- .../IIS/IIS/src/Core/IISHttpContext.cs | 30 +++++++++++++++++++ .../Core/Native/SecPkgContext_CipherInfo.cs | 30 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/Servers/IIS/IIS/src/Core/Native/SecPkgContext_CipherInfo.cs diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs index 3ddc9315cf66..5320eb018dec 100644 --- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs +++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs @@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.HttpSys.Internal; using Microsoft.AspNetCore.Server.IIS.Core.IO; +using Microsoft.AspNetCore.Server.IIS.Core.Native; using Microsoft.AspNetCore.Shared; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; @@ -402,6 +403,8 @@ private void GetTlsHandshakeResults() { var handshake = GetTlsHandshake(); Protocol = (SslProtocols)handshake.Protocol; + + NegotiatedCipherSuite = GetTlsCipherSuite(); #pragma warning disable SYSLIB0058 // Type or member is obsolete CipherAlgorithm = (CipherAlgorithmType)handshake.CipherType; CipherStrength = (int)handshake.CipherStrength; @@ -415,6 +418,33 @@ private void GetTlsHandshakeResults() SniHostName = sni.Hostname.ToString(); } + private unsafe TlsCipherSuite? GetTlsCipherSuite() + { + var size = sizeof(SecPkgContext_CipherInfo); + var buffer = new byte[size]; + + fixed (byte* pBuffer = buffer) + { + var statusCode = NativeMethods.HttpQueryRequestProperty( + RequestId, + (HTTP_REQUEST_PROPERTY)14 /* HTTP_REQUEST_PROPERTY.HttpRequestPropertyTlsCipherInfo */, + qualifier: null, + qualifierSize: 0, + (void*)pBuffer, + (uint)buffer.Length, + bytesReturned: null, + IntPtr.Zero); + + if (statusCode == NativeMethods.HR_OK) + { + var cipherInfo = Marshal.PtrToStructure((IntPtr)pBuffer); + return (TlsCipherSuite)cipherInfo.dwCipherSuite; + } + + return default; + } + } + private unsafe HTTP_REQUEST_PROPERTY_SNI GetClientSni() { var buffer = new byte[HttpApiTypes.SniPropertySizeInBytes]; diff --git a/src/Servers/IIS/IIS/src/Core/Native/SecPkgContext_CipherInfo.cs b/src/Servers/IIS/IIS/src/Core/Native/SecPkgContext_CipherInfo.cs new file mode 100644 index 000000000000..400f3812ff65 --- /dev/null +++ b/src/Servers/IIS/IIS/src/Core/Native/SecPkgContext_CipherInfo.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace Microsoft.AspNetCore.Server.IIS.Core.Native; + +// From Schannel.h +[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] +internal unsafe struct SecPkgContext_CipherInfo +{ + private const int SZ_ALG_MAX_SIZE = 64; + + private readonly int dwVersion; + private readonly int dwProtocol; + public readonly int dwCipherSuite; + private readonly int dwBaseCipherSuite; + private fixed char szCipherSuite[SZ_ALG_MAX_SIZE]; + private fixed char szCipher[SZ_ALG_MAX_SIZE]; + private readonly int dwCipherLen; + private readonly int dwCipherBlockLen; // in bytes + private fixed char szHash[SZ_ALG_MAX_SIZE]; + private readonly int dwHashLen; + private fixed char szExchange[SZ_ALG_MAX_SIZE]; + private readonly int dwMinExchangeLen; + private readonly int dwMaxExchangeLen; + private fixed char szCertificate[SZ_ALG_MAX_SIZE]; + private readonly int dwKeyType; +} + From a99a592057b82f68331ba867ad42281a3b404e26 Mon Sep 17 00:00:00 2001 From: Korolev Dmitry Date: Thu, 18 Sep 2025 19:51:33 +0200 Subject: [PATCH 05/10] test ITlsHandshakeFeature data exposed --- .../IIS/IIS/samples/NativeIISSample/Startup.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Servers/IIS/IIS/samples/NativeIISSample/Startup.cs b/src/Servers/IIS/IIS/samples/NativeIISSample/Startup.cs index 0ff3b86369c6..e3559fa5b1e0 100644 --- a/src/Servers/IIS/IIS/samples/NativeIISSample/Startup.cs +++ b/src/Servers/IIS/IIS/samples/NativeIISSample/Startup.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; @@ -52,6 +53,19 @@ public void Configure(IApplicationBuilder app) await context.Response.WriteAsync("ClientCert: " + context.Connection.ClientCertificate + Environment.NewLine); await context.Response.WriteAsync(Environment.NewLine); + var handshakeFeature = context.Features.Get(); + if (handshakeFeature is not null) + { + await context.Response.WriteAsync(Environment.NewLine); + await context.Response.WriteAsync("TLS Information:" + Environment.NewLine); + await context.Response.WriteAsync($"Protocol: {handshakeFeature.Protocol}" + Environment.NewLine); + + if (handshakeFeature.NegotiatedCipherSuite.HasValue) + { + await context.Response.WriteAsync($"Cipher Suite: {handshakeFeature.NegotiatedCipherSuite.Value}" + Environment.NewLine); + } + } + await context.Response.WriteAsync("User: " + context.User.Identity.Name + Environment.NewLine); if (_authSchemeProvider != null) { From d0bfb595e5d25baac70c19ed221af3cad8628ece Mon Sep 17 00:00:00 2001 From: Korolev Dmitry Date: Thu, 18 Sep 2025 19:52:13 +0200 Subject: [PATCH 06/10] add debug publish profile for testing purposes --- .../Properties/PublishProfiles/debug.pubxml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/Servers/IIS/IIS/samples/NativeIISSample/Properties/PublishProfiles/debug.pubxml diff --git a/src/Servers/IIS/IIS/samples/NativeIISSample/Properties/PublishProfiles/debug.pubxml b/src/Servers/IIS/IIS/samples/NativeIISSample/Properties/PublishProfiles/debug.pubxml new file mode 100644 index 000000000000..93d23e585a4f --- /dev/null +++ b/src/Servers/IIS/IIS/samples/NativeIISSample/Properties/PublishProfiles/debug.pubxml @@ -0,0 +1,21 @@ + + + + + + + true + false + true + Debug + Any CPU + FileSystem + bin\Debug\Publish + FileSystem + <_TargetId>Folder + + net10.0 + 54619f9a-6cba-d9a5-500c-580c8a857f30 + false + + From 0c8e486da3e80eb274b971dd424d5713af6be9a2 Mon Sep 17 00:00:00 2001 From: Korolev Dmitry Date: Thu, 18 Sep 2025 19:52:30 +0200 Subject: [PATCH 07/10] remove --- .../Properties/PublishProfiles/debug.pubxml | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 src/Servers/IIS/IIS/samples/NativeIISSample/Properties/PublishProfiles/debug.pubxml diff --git a/src/Servers/IIS/IIS/samples/NativeIISSample/Properties/PublishProfiles/debug.pubxml b/src/Servers/IIS/IIS/samples/NativeIISSample/Properties/PublishProfiles/debug.pubxml deleted file mode 100644 index 93d23e585a4f..000000000000 --- a/src/Servers/IIS/IIS/samples/NativeIISSample/Properties/PublishProfiles/debug.pubxml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - true - false - true - Debug - Any CPU - FileSystem - bin\Debug\Publish - FileSystem - <_TargetId>Folder - - net10.0 - 54619f9a-6cba-d9a5-500c-580c8a857f30 - false - - From ae7b44e603f0825b04e9ca4ec5f973504ba54dca Mon Sep 17 00:00:00 2001 From: Korolev Dmitry Date: Thu, 18 Sep 2025 21:40:45 +0200 Subject: [PATCH 08/10] address PR comments --- .../src/RequestProcessing/RequestContext.cs | 3 +- .../IIS/IIS/src/Core/IISHttpContext.cs | 35 ++++++++----------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs index e00e58b6bba2..f7d38ece009c 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs @@ -238,7 +238,6 @@ internal void ForceCancelRequest() var requestId = PinsReleased ? Request.RequestId : RequestId; SecPkgContext_CipherInfo cipherInfo = default; - uint bytesReturned = 0; var statusCode = HttpApi.HttpGetRequestProperty( requestQueueHandle: Server.RequestQueue.Handle, @@ -248,7 +247,7 @@ internal void ForceCancelRequest() qualifierSize: 0, output: &cipherInfo, outputSize: (uint)sizeof(SecPkgContext_CipherInfo), - bytesReturned: (IntPtr)(&bytesReturned), + bytesReturned: IntPtr.Zero, overlapped: IntPtr.Zero); if (statusCode is ErrorCodes.ERROR_SUCCESS) diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs index 5320eb018dec..11f49a6b2ffb 100644 --- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs +++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs @@ -420,29 +420,24 @@ private void GetTlsHandshakeResults() private unsafe TlsCipherSuite? GetTlsCipherSuite() { - var size = sizeof(SecPkgContext_CipherInfo); - var buffer = new byte[size]; + SecPkgContext_CipherInfo cipherInfo = default; - fixed (byte* pBuffer = buffer) - { - var statusCode = NativeMethods.HttpQueryRequestProperty( - RequestId, - (HTTP_REQUEST_PROPERTY)14 /* HTTP_REQUEST_PROPERTY.HttpRequestPropertyTlsCipherInfo */, - qualifier: null, - qualifierSize: 0, - (void*)pBuffer, - (uint)buffer.Length, - bytesReturned: null, - IntPtr.Zero); + var statusCode = NativeMethods.HttpQueryRequestProperty( + RequestId, + (HTTP_REQUEST_PROPERTY)14 /* HTTP_REQUEST_PROPERTY.HttpRequestPropertyTlsCipherInfo */, + qualifier: null, + qualifierSize: 0, + output: &cipherInfo, + outputSize: (uint)sizeof(SecPkgContext_CipherInfo), + bytesReturned: null, + overlapped: IntPtr.Zero); - if (statusCode == NativeMethods.HR_OK) - { - var cipherInfo = Marshal.PtrToStructure((IntPtr)pBuffer); - return (TlsCipherSuite)cipherInfo.dwCipherSuite; - } - - return default; + if (statusCode == NativeMethods.HR_OK) + { + return (TlsCipherSuite)cipherInfo.dwCipherSuite; } + + return default; } private unsafe HTTP_REQUEST_PROPERTY_SNI GetClientSni() From 45eef3a0e1157c709d6cb343453e497c1264ed6e Mon Sep 17 00:00:00 2001 From: Korolev Dmitry Date: Fri, 19 Sep 2025 12:13:39 +0200 Subject: [PATCH 09/10] share SecPkgContext_CipherInfo between httpsys+iis --- .../Types/SecPkgContext_CipherInfo.cs | 29 ------------------- .../src/RequestProcessing/RequestContext.cs | 1 - .../IIS/IIS/src/Core/IISHttpContext.cs | 1 - .../SecPkgContext_CipherInfo.cs | 3 +- 4 files changed, 1 insertion(+), 33 deletions(-) delete mode 100644 src/Servers/HttpSys/src/NativeInterop/Types/SecPkgContext_CipherInfo.cs rename src/{Servers/IIS/IIS/src/Core/Native => Shared/HttpSys/NativeInterop}/SecPkgContext_CipherInfo.cs (94%) diff --git a/src/Servers/HttpSys/src/NativeInterop/Types/SecPkgContext_CipherInfo.cs b/src/Servers/HttpSys/src/NativeInterop/Types/SecPkgContext_CipherInfo.cs deleted file mode 100644 index 1eb1642b778b..000000000000 --- a/src/Servers/HttpSys/src/NativeInterop/Types/SecPkgContext_CipherInfo.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.InteropServices; - -namespace Microsoft.AspNetCore.Server.HttpSys.NativeInterop.Types; - -// From Schannel.h -[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] -internal unsafe struct SecPkgContext_CipherInfo -{ - private const int SZ_ALG_MAX_SIZE = 64; - - private readonly int dwVersion; - private readonly int dwProtocol; - public readonly int dwCipherSuite; - private readonly int dwBaseCipherSuite; - private fixed char szCipherSuite[SZ_ALG_MAX_SIZE]; - private fixed char szCipher[SZ_ALG_MAX_SIZE]; - private readonly int dwCipherLen; - private readonly int dwCipherBlockLen; // in bytes - private fixed char szHash[SZ_ALG_MAX_SIZE]; - private readonly int dwHashLen; - private fixed char szExchange[SZ_ALG_MAX_SIZE]; - private readonly int dwMinExchangeLen; - private readonly int dwMaxExchangeLen; - private fixed char szCertificate[SZ_ALG_MAX_SIZE]; - private readonly int dwKeyType; -} diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs index f7d38ece009c..81c08fbd7989 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs @@ -6,7 +6,6 @@ using System.Security.Principal; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.HttpSys.Internal; -using Microsoft.AspNetCore.Server.HttpSys.NativeInterop.Types; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; using Windows.Win32; diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs index 11f49a6b2ffb..eec81bbbac0b 100644 --- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs +++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs @@ -18,7 +18,6 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.HttpSys.Internal; using Microsoft.AspNetCore.Server.IIS.Core.IO; -using Microsoft.AspNetCore.Server.IIS.Core.Native; using Microsoft.AspNetCore.Shared; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; diff --git a/src/Servers/IIS/IIS/src/Core/Native/SecPkgContext_CipherInfo.cs b/src/Shared/HttpSys/NativeInterop/SecPkgContext_CipherInfo.cs similarity index 94% rename from src/Servers/IIS/IIS/src/Core/Native/SecPkgContext_CipherInfo.cs rename to src/Shared/HttpSys/NativeInterop/SecPkgContext_CipherInfo.cs index 400f3812ff65..1bba1c1afcef 100644 --- a/src/Servers/IIS/IIS/src/Core/Native/SecPkgContext_CipherInfo.cs +++ b/src/Shared/HttpSys/NativeInterop/SecPkgContext_CipherInfo.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices; -namespace Microsoft.AspNetCore.Server.IIS.Core.Native; +namespace Microsoft.AspNetCore.HttpSys.Internal; // From Schannel.h [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] @@ -27,4 +27,3 @@ internal unsafe struct SecPkgContext_CipherInfo private fixed char szCertificate[SZ_ALG_MAX_SIZE]; private readonly int dwKeyType; } - From 6ac1c07758ae291e982629ac3c7da8d8941ab8f1 Mon Sep 17 00:00:00 2001 From: Korolev Dmitry Date: Fri, 19 Sep 2025 12:19:24 +0200 Subject: [PATCH 10/10] add panaroid code --- src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs | 2 +- src/Servers/IIS/IIS/src/Core/IISHttpContext.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs index 81c08fbd7989..5aefb9df1cf3 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs @@ -251,7 +251,7 @@ internal void ForceCancelRequest() if (statusCode is ErrorCodes.ERROR_SUCCESS) { - return (TlsCipherSuite)cipherInfo.dwCipherSuite; + return checked((TlsCipherSuite)cipherInfo.dwCipherSuite); } // OS supports querying TlsCipherSuite, but request failed. diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs index eec81bbbac0b..4a1417ed1b52 100644 --- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs +++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs @@ -433,7 +433,7 @@ private void GetTlsHandshakeResults() if (statusCode == NativeMethods.HR_OK) { - return (TlsCipherSuite)cipherInfo.dwCipherSuite; + return checked((TlsCipherSuite)cipherInfo.dwCipherSuite); } return default;