Skip to content

Commit fe6f852

Browse files
[release/10.0] Support querying TlsCipherSuite on Http.Sys / IIS (#63768)
* init query tls cipher suite info * correct types * dont throw excp * parse it in IIS * test ITlsHandshakeFeature data exposed * add debug publish profile for testing purposes * remove * address PR comments * share SecPkgContext_CipherInfo between httpsys+iis * add panaroid code --------- Co-authored-by: Korolev Dmitry <[email protected]>
1 parent 07545ad commit fe6f852

File tree

10 files changed

+131
-1
lines changed

10 files changed

+131
-1
lines changed

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Reflection;
77
using System.Runtime.InteropServices;
88
using Microsoft.AspNetCore.Builder;
9+
using Microsoft.AspNetCore.Connections.Features;
910
using Microsoft.AspNetCore.Hosting;
1011
using Microsoft.AspNetCore.Http;
1112
using Microsoft.AspNetCore.Http.Features;
@@ -30,6 +31,7 @@
3031
{
3132
var connectionFeature = context.Features.GetRequiredFeature<IHttpConnectionFeature>();
3233
var httpSysPropFeature = context.Features.GetRequiredFeature<IHttpSysRequestPropertyFeature>();
34+
var tlsHandshakeFeature = context.Features.GetRequiredFeature<ITlsHandshakeFeature>();
3335

3436
// first time invocation to find out required size
3537
var success = httpSysPropFeature.TryGetTlsClientHello(Array.Empty<byte>(), out var bytesReturned);
@@ -41,7 +43,14 @@
4143
success = httpSysPropFeature.TryGetTlsClientHello(bytes, out _);
4244
Debug.Assert(success);
4345

44-
await context.Response.WriteAsync($"[Response] connectionId={connectionFeature.ConnectionId}; tlsClientHello.length={bytesReturned}; tlsclienthello start={string.Join(' ', bytes.AsSpan(0, 30).ToArray())}");
46+
await context.Response.WriteAsync(
47+
$"""
48+
connectionId = {connectionFeature.ConnectionId};
49+
negotiated cipher suite = {tlsHandshakeFeature.NegotiatedCipherSuite};
50+
tlsClientHello.length = {bytesReturned};
51+
tlsclienthello start = {string.Join(' ', bytes.AsSpan(0, 30).ToArray())}
52+
""");
53+
4554
await next(context);
4655
});
4756

src/Servers/HttpSys/src/LoggerEventIds.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,5 @@ internal static class LoggerEventIds
6060
public const int AcceptObserveExpectationMismatch = 53;
6161
public const int RequestParsingError = 54;
6262
public const int TlsListenerError = 55;
63+
public const int QueryTlsCipherSuiteError = 56;
6364
}

src/Servers/HttpSys/src/NativeInterop/HttpApi.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ internal static unsafe uint HttpSetRequestProperty(SafeHandle requestQueueHandle
7070
internal static bool SupportsReset { get; }
7171
internal static bool SupportsDelegation { get; }
7272
internal static bool SupportsClientHello { get; }
73+
internal static bool SupportsQueryTlsCipherInfo { get; }
7374
internal static bool Supported { get; }
7475

7576
static unsafe HttpApi()
@@ -86,6 +87,7 @@ static unsafe HttpApi()
8687
SupportsTrailers = IsFeatureSupported(HTTP_FEATURE_ID.HttpFeatureResponseTrailers);
8788
SupportsDelegation = IsFeatureSupported(HTTP_FEATURE_ID.HttpFeatureDelegateEx);
8889
SupportsClientHello = IsFeatureSupported((HTTP_FEATURE_ID)11 /* HTTP_FEATURE_ID.HttpFeatureCacheTlsClientHello */) && HttpGetRequestPropertySupported;
90+
SupportsQueryTlsCipherInfo = IsFeatureSupported((HTTP_FEATURE_ID)15 /* HTTP_FEATURE_ID.HttpFeatureQueryCipherInfo */) && HttpGetRequestPropertySupported;
8991
}
9092
}
9193

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Globalization;
55
using System.Net;
6+
using System.Net.Security;
67
using System.Security;
78
using System.Security.Authentication;
89
using System.Security.Cryptography;
@@ -334,6 +335,8 @@ private AspNetCore.HttpSys.Internal.SocketAddress LocalEndPoint
334335

335336
public SslProtocols Protocol { get; private set; }
336337

338+
public TlsCipherSuite? NegotiatedCipherSuite { get; private set; }
339+
337340
[Obsolete(Obsoletions.RuntimeTlsCipherAlgorithmEnumsMessage, DiagnosticId = Obsoletions.RuntimeTlsCipherAlgorithmEnumsDiagId, UrlFormat = Obsoletions.RuntimeSharedUrlFormat)]
338341
public CipherAlgorithmType CipherAlgorithm { get; private set; }
339342

@@ -356,6 +359,8 @@ private void GetTlsHandshakeResults()
356359
{
357360
var handshake = RequestContext.GetTlsHandshake();
358361
Protocol = (SslProtocols)handshake.Protocol;
362+
363+
NegotiatedCipherSuite = RequestContext.GetTlsCipherSuite();
359364
#pragma warning disable SYSLIB0058 // Type or member is obsolete
360365
CipherAlgorithm = (CipherAlgorithmType)handshake.CipherType;
361366
CipherStrength = (int)handshake.CipherStrength;

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Globalization;
66
using System.IO.Pipelines;
77
using System.Net;
8+
using System.Net.Security;
89
using System.Security.Authentication;
910
using System.Security.Claims;
1011
using System.Security.Cryptography.X509Certificates;
@@ -593,6 +594,8 @@ bool IHttpBodyControlFeature.AllowSynchronousIO
593594

594595
SslProtocols ITlsHandshakeFeature.Protocol => Request.Protocol;
595596

597+
TlsCipherSuite? ITlsHandshakeFeature.NegotiatedCipherSuite => Request.NegotiatedCipherSuite;
598+
596599
#pragma warning disable SYSLIB0058 // Type or member is obsolete
597600
CipherAlgorithmType ITlsHandshakeFeature.CipherAlgorithm => Request.CipherAlgorithm;
598601

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@ private static partial class Log
2323

2424
[LoggerMessage(LoggerEventIds.RequestParsingError, LogLevel.Debug, "Failed to invoke QueryTlsClientHello; RequestId: {RequestId}; Win32 Error code: {Win32Error}", EventName = "TlsClientHelloRetrieveError")]
2525
public static partial void TlsClientHelloRetrieveError(ILogger logger, ulong requestId, uint win32Error);
26+
27+
[LoggerMessage(LoggerEventIds.QueryTlsCipherSuiteError, LogLevel.Debug, "Failed to invoke QueryTlsCipherSuite; RequestId: {RequestId}; Win32 Error code: {Win32Error}", EventName = "QueryTlsCipherSuiteError")]
28+
public static partial void QueryTlsCipherSuiteError(ILogger logger, ulong requestId, uint win32Error);
2629
}
2730
}

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
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.Net.Security;
45
using System.Runtime.InteropServices;
56
using System.Security.Principal;
67
using Microsoft.AspNetCore.Http;
@@ -219,6 +220,45 @@ internal void ForceCancelRequest()
219220
}
220221
}
221222

223+
/// <summary>
224+
/// Gets TLS cipher suite used for the request, if supported by the OS and http.sys.
225+
/// </summary>
226+
/// <returns>
227+
/// null, if query of TlsCipherSuite is not supported or the query failed.
228+
/// TlsCipherSuite value, if query is successful.
229+
/// </returns>
230+
internal unsafe TlsCipherSuite? GetTlsCipherSuite()
231+
{
232+
if (!HttpApi.SupportsQueryTlsCipherInfo)
233+
{
234+
return default;
235+
}
236+
237+
var requestId = PinsReleased ? Request.RequestId : RequestId;
238+
239+
SecPkgContext_CipherInfo cipherInfo = default;
240+
241+
var statusCode = HttpApi.HttpGetRequestProperty(
242+
requestQueueHandle: Server.RequestQueue.Handle,
243+
requestId,
244+
propertyId: (HTTP_REQUEST_PROPERTY)14 /* HTTP_REQUEST_PROPERTY.HttpRequestPropertyTlsCipherInfo */,
245+
qualifier: null,
246+
qualifierSize: 0,
247+
output: &cipherInfo,
248+
outputSize: (uint)sizeof(SecPkgContext_CipherInfo),
249+
bytesReturned: IntPtr.Zero,
250+
overlapped: IntPtr.Zero);
251+
252+
if (statusCode is ErrorCodes.ERROR_SUCCESS)
253+
{
254+
return checked((TlsCipherSuite)cipherInfo.dwCipherSuite);
255+
}
256+
257+
// OS supports querying TlsCipherSuite, but request failed.
258+
Log.QueryTlsCipherSuiteError(Logger, requestId, statusCode);
259+
return null;
260+
}
261+
222262
/// <summary>
223263
/// Attempts to get the client hello message bytes from the http.sys.
224264
/// If successful writes the bytes into <paramref name="destination"/>, and shows how many bytes were written in <paramref name="bytesReturned"/>.

src/Servers/IIS/IIS/samples/NativeIISSample/Startup.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Threading.Tasks;
77
using Microsoft.AspNetCore.Authentication;
88
using Microsoft.AspNetCore.Builder;
9+
using Microsoft.AspNetCore.Connections.Features;
910
using Microsoft.AspNetCore.Hosting;
1011
using Microsoft.AspNetCore.Hosting.Server;
1112
using Microsoft.AspNetCore.Hosting.Server.Features;
@@ -52,6 +53,19 @@ public void Configure(IApplicationBuilder app)
5253
await context.Response.WriteAsync("ClientCert: " + context.Connection.ClientCertificate + Environment.NewLine);
5354
await context.Response.WriteAsync(Environment.NewLine);
5455

56+
var handshakeFeature = context.Features.Get<ITlsHandshakeFeature>();
57+
if (handshakeFeature is not null)
58+
{
59+
await context.Response.WriteAsync(Environment.NewLine);
60+
await context.Response.WriteAsync("TLS Information:" + Environment.NewLine);
61+
await context.Response.WriteAsync($"Protocol: {handshakeFeature.Protocol}" + Environment.NewLine);
62+
63+
if (handshakeFeature.NegotiatedCipherSuite.HasValue)
64+
{
65+
await context.Response.WriteAsync($"Cipher Suite: {handshakeFeature.NegotiatedCipherSuite.Value}" + Environment.NewLine);
66+
}
67+
}
68+
5569
await context.Response.WriteAsync("User: " + context.User.Identity.Name + Environment.NewLine);
5670
if (_authSchemeProvider != null)
5771
{

src/Servers/IIS/IIS/src/Core/IISHttpContext.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,8 @@ private void GetTlsHandshakeResults()
402402
{
403403
var handshake = GetTlsHandshake();
404404
Protocol = (SslProtocols)handshake.Protocol;
405+
406+
NegotiatedCipherSuite = GetTlsCipherSuite();
405407
#pragma warning disable SYSLIB0058 // Type or member is obsolete
406408
CipherAlgorithm = (CipherAlgorithmType)handshake.CipherType;
407409
CipherStrength = (int)handshake.CipherStrength;
@@ -415,6 +417,28 @@ private void GetTlsHandshakeResults()
415417
SniHostName = sni.Hostname.ToString();
416418
}
417419

420+
private unsafe TlsCipherSuite? GetTlsCipherSuite()
421+
{
422+
SecPkgContext_CipherInfo cipherInfo = default;
423+
424+
var statusCode = NativeMethods.HttpQueryRequestProperty(
425+
RequestId,
426+
(HTTP_REQUEST_PROPERTY)14 /* HTTP_REQUEST_PROPERTY.HttpRequestPropertyTlsCipherInfo */,
427+
qualifier: null,
428+
qualifierSize: 0,
429+
output: &cipherInfo,
430+
outputSize: (uint)sizeof(SecPkgContext_CipherInfo),
431+
bytesReturned: null,
432+
overlapped: IntPtr.Zero);
433+
434+
if (statusCode == NativeMethods.HR_OK)
435+
{
436+
return checked((TlsCipherSuite)cipherInfo.dwCipherSuite);
437+
}
438+
439+
return default;
440+
}
441+
418442
private unsafe HTTP_REQUEST_PROPERTY_SNI GetClientSni()
419443
{
420444
var buffer = new byte[HttpApiTypes.SniPropertySizeInBytes];
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
using System.Runtime.InteropServices;
5+
6+
namespace Microsoft.AspNetCore.HttpSys.Internal;
7+
8+
// From Schannel.h
9+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
10+
internal unsafe struct SecPkgContext_CipherInfo
11+
{
12+
private const int SZ_ALG_MAX_SIZE = 64;
13+
14+
private readonly int dwVersion;
15+
private readonly int dwProtocol;
16+
public readonly int dwCipherSuite;
17+
private readonly int dwBaseCipherSuite;
18+
private fixed char szCipherSuite[SZ_ALG_MAX_SIZE];
19+
private fixed char szCipher[SZ_ALG_MAX_SIZE];
20+
private readonly int dwCipherLen;
21+
private readonly int dwCipherBlockLen; // in bytes
22+
private fixed char szHash[SZ_ALG_MAX_SIZE];
23+
private readonly int dwHashLen;
24+
private fixed char szExchange[SZ_ALG_MAX_SIZE];
25+
private readonly int dwMinExchangeLen;
26+
private readonly int dwMaxExchangeLen;
27+
private fixed char szCertificate[SZ_ALG_MAX_SIZE];
28+
private readonly int dwKeyType;
29+
}

0 commit comments

Comments
 (0)