Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
11 changes: 10 additions & 1 deletion src/Servers/HttpSys/samples/TlsFeaturesObserve/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,6 +31,7 @@
{
var connectionFeature = context.Features.GetRequiredFeature<IHttpConnectionFeature>();
var httpSysPropFeature = context.Features.GetRequiredFeature<IHttpSysRequestPropertyFeature>();
var tlsHandshakeFeature = context.Features.GetRequiredFeature<ITlsHandshakeFeature>();

// first time invocation to find out required size
var success = httpSysPropFeature.TryGetTlsClientHello(Array.Empty<byte>(), out var bytesReturned);
Expand All @@ -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);
});

Expand Down
1 change: 1 addition & 0 deletions src/Servers/HttpSys/src/LoggerEventIds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
2 changes: 2 additions & 0 deletions src/Servers/HttpSys/src/NativeInterop/HttpApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
5 changes: 5 additions & 0 deletions src/Servers/HttpSys/src/RequestProcessing/Request.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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; }

Expand All @@ -356,6 +359,8 @@ private void GetTlsHandshakeResults()
{
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
41 changes: 41 additions & 0 deletions src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -219,6 +221,45 @@ internal void ForceCancelRequest()
}
}

/// <summary>
/// Gets TLS cipher suite used for the request, if supported by the OS and http.sys.
/// </summary>
/// <returns>
/// null, if query of TlsCipherSuite is not supported or the query failed.
/// TlsCipherSuite value, if query is successful.
/// </returns>
internal unsafe TlsCipherSuite? GetTlsCipherSuite()
{
if (!HttpApi.SupportsQueryTlsCipherInfo)
{
return default;
}

var requestId = PinsReleased ? Request.RequestId : RequestId;

SecPkgContext_CipherInfo cipherInfo = default;

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.Zero,
overlapped: IntPtr.Zero);

if (statusCode is ErrorCodes.ERROR_SUCCESS)
{
return (TlsCipherSuite)cipherInfo.dwCipherSuite;
}

// OS supports querying TlsCipherSuite, but request failed.
Log.QueryTlsCipherSuiteError(Logger, requestId, statusCode);
return null;
}

/// <summary>
/// Attempts to get the client hello message bytes from the http.sys.
/// If successful writes the bytes into <paramref name="destination"/>, and shows how many bytes were written in <paramref name="bytesReturned"/>.
Expand Down
14 changes: 14 additions & 0 deletions src/Servers/IIS/IIS/samples/NativeIISSample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<ITlsHandshakeFeature>();
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)
{
Expand Down
25 changes: 25 additions & 0 deletions src/Servers/IIS/IIS/src/Core/IISHttpContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -415,6 +418,28 @@ private void GetTlsHandshakeResults()
SniHostName = sni.Hostname.ToString();
}

private unsafe TlsCipherSuite? GetTlsCipherSuite()
{
SecPkgContext_CipherInfo cipherInfo = default;

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)
{
return (TlsCipherSuite)cipherInfo.dwCipherSuite;
}

return default;
}

private unsafe HTTP_REQUEST_PROPERTY_SNI GetClientSni()
{
var buffer = new byte[HttpApiTypes.SniPropertySizeInBytes];
Expand Down
30 changes: 30 additions & 0 deletions src/Servers/IIS/IIS/src/Core/Native/SecPkgContext_CipherInfo.cs
Original file line number Diff line number Diff line change
@@ -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;
}

Loading