diff --git a/src/NLog.Web.AspNetCore/Enums/TlsHandshakeProperty.cs b/src/NLog.Web.AspNetCore/Enums/TlsHandshakeProperty.cs new file mode 100644 index 00000000..7ed7a3b5 --- /dev/null +++ b/src/NLog.Web.AspNetCore/Enums/TlsHandshakeProperty.cs @@ -0,0 +1,37 @@ +namespace NLog.Web.Enums +{ + /// + /// Specifies which of the 7 properties of ITlsHandshakeFeature to emit + /// + public enum TlsHandshakeProperty + { + /// + /// Gets the CipherAlgorithmType. + /// + CipherAlgorithm, + /// + /// Gets the cipher strength + /// + CipherStrength, + /// + /// Gets the HashAlgorithmType. + /// + HashAlgorithm, + /// + /// Gets the hash strength. + /// + HashStrength, + /// + /// Gets the KeyExchangeAlgorithm. + /// + KeyExchangeAlgorithm, + /// + /// Gets the key exchange algorithm strength. + /// + KeyExchangeStrength, + /// + /// Gets the SslProtocols. + /// + Protocol + } +} diff --git a/src/NLog.Web.AspNetCore/LayoutRenderers/AspNetTlsHandshakeLayoutRenderer.cs b/src/NLog.Web.AspNetCore/LayoutRenderers/AspNetTlsHandshakeLayoutRenderer.cs new file mode 100644 index 00000000..65b723d4 --- /dev/null +++ b/src/NLog.Web.AspNetCore/LayoutRenderers/AspNetTlsHandshakeLayoutRenderer.cs @@ -0,0 +1,79 @@ +#if ASP_NET_CORE3 +using Microsoft.AspNetCore.Connections.Features; +using NLog.Config; +using NLog.LayoutRenderers; +using NLog.Web.Enums; +using NLog.Web.Internal; +using System.Text; + +namespace NLog.Web.LayoutRenderers +{ + /// + /// ASP.NET TLS Handshake + /// + /// + /// ${aspnet-tls-handshake:Property=CipherAlgorithm} + /// ${aspnet-tls-handshake:Property=CipherStrength} + /// ${aspnet-tls-handshake:Property=HashAlgorithm} + /// ${aspnet-tls-handshake:Property=HashStrength} + /// ${aspnet-tls-handshake:Property=KeyExchangeAlgorithm} + /// ${aspnet-tls-handshake:Property=KeyExchangeStrength} + /// ${aspnet-tls-handshake:Property=Protocol} + /// + [LayoutRenderer("aspnet-tls-handshake")] + public class AspNetTlsHandshakeLayoutRenderer : AspNetLayoutRendererBase + { + /// + /// Specifies which of the 7 properties of ITlsHandshakeFeature to emit + /// Defaults to the protocol + /// + [DefaultParameter] + public TlsHandshakeProperty Property { get; set; } = TlsHandshakeProperty.Protocol; + + /// + /// Render TLS Handshake Information + /// + /// + /// + protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent) + { + var features = HttpContextAccessor.HttpContext.TryGetFeatureCollection(); + if(features == null) + { + return; + } + var tlsHandshake = features.Get(); + if (tlsHandshake == null) + { + return; + } + + switch (Property) + { + case TlsHandshakeProperty.CipherAlgorithm: + builder.Append(tlsHandshake.CipherAlgorithm); + break; + case TlsHandshakeProperty.CipherStrength: + builder.Append(tlsHandshake.CipherStrength); + break; + case TlsHandshakeProperty.HashAlgorithm: + builder.Append(tlsHandshake.HashAlgorithm); + break; + case TlsHandshakeProperty.HashStrength: + builder.Append(tlsHandshake.HashStrength); + break; + case TlsHandshakeProperty.KeyExchangeAlgorithm: + builder.Append(tlsHandshake.KeyExchangeAlgorithm); + break; + case TlsHandshakeProperty.KeyExchangeStrength: + builder.Append(tlsHandshake.KeyExchangeStrength); + break; + case TlsHandshakeProperty.Protocol: + builder.Append(tlsHandshake.Protocol); + break; + } + + } + } +} +#endif \ No newline at end of file diff --git a/src/Shared/Internal/HttpContextExtensions.cs b/src/Shared/Internal/HttpContextExtensions.cs index 234999fc..9c0f35d6 100644 --- a/src/Shared/Internal/HttpContextExtensions.cs +++ b/src/Shared/Internal/HttpContextExtensions.cs @@ -4,6 +4,9 @@ using System.Web; #else using System.Text; +#if ASP_NET_CORE3 +using Microsoft.AspNetCore.Connections.Features; +#endif using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; #endif @@ -61,6 +64,14 @@ internal static HttpResponse TryGetResponse(this HttpContext context) InternalLogger.Debug("HttpContext Response Lookup returned null"); return response; } + + internal static IFeatureCollection TryGetFeatureCollection(this HttpContext context) + { + var features = context?.Features; + if (features == null) + InternalLogger.Debug("HttpContext Features Lookup returned null"); + return features; + } #endif #if ASP_NET_CORE2 diff --git a/tests/NLog.Web.AspNetCore.Tests/LayoutRenderers/AspNetTlsHandshakeLayoutRendererTests.cs b/tests/NLog.Web.AspNetCore.Tests/LayoutRenderers/AspNetTlsHandshakeLayoutRendererTests.cs new file mode 100644 index 00000000..437714c1 --- /dev/null +++ b/tests/NLog.Web.AspNetCore.Tests/LayoutRenderers/AspNetTlsHandshakeLayoutRendererTests.cs @@ -0,0 +1,143 @@ +#if ASP_NET_CORE3 +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using NLog.Web.Enums; +using NLog.Web.LayoutRenderers; +using NSubstitute; +using System.Security.Authentication; +using Xunit; + +namespace NLog.Web.Tests.LayoutRenderers +{ + public class AspNetTlsHandshakeLayoutRendererTests : LayoutRenderersTestBase + { + private static void SetupFeature(HttpContext httpContext) + { + var tlsHandshakeFeature = Substitute.For(); + + tlsHandshakeFeature.CipherAlgorithm.Returns(CipherAlgorithmType.Aes256); + tlsHandshakeFeature.CipherStrength.Returns(256); + + tlsHandshakeFeature.HashAlgorithm.Returns(HashAlgorithmType.Sha512); + tlsHandshakeFeature.HashStrength.Returns(512); + + tlsHandshakeFeature.KeyExchangeAlgorithm.Returns(ExchangeAlgorithmType.RsaSign); + tlsHandshakeFeature.KeyExchangeStrength.Returns(1024); + + tlsHandshakeFeature.Protocol.Returns(SslProtocols.Tls13); + + + var featureCollection = new FeatureCollection(); + featureCollection.Set(tlsHandshakeFeature); + + httpContext.Features.Returns(featureCollection); + } + + [Fact] + public void CipherAlgorithmTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + renderer.Property = TlsHandshakeProperty.CipherAlgorithm; + SetupFeature(httpContext); + // Act + var result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal(CipherAlgorithmType.Aes256.ToString(), result); + } + + + [Fact] + public void CipherStrengthTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + renderer.Property = TlsHandshakeProperty.CipherStrength; + SetupFeature(httpContext); + // Act + var result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal("256", result); + } + + [Fact] + public void HashAlgorithmTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + renderer.Property = TlsHandshakeProperty.HashAlgorithm; + SetupFeature(httpContext); + // Act + var result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal(HashAlgorithmType.Sha512.ToString(), result); + } + + [Fact] + public void HashStrengthTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + renderer.Property = TlsHandshakeProperty.HashStrength; + SetupFeature(httpContext); + // Act + var result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal("512", result); + } + + [Fact] + public void KeyExchangeAlgorithmTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + renderer.Property = TlsHandshakeProperty.KeyExchangeAlgorithm; + SetupFeature(httpContext); + // Act + var result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal(ExchangeAlgorithmType.RsaSign.ToString(), result); + } + + [Fact] + public void KeyExchangeStrengthTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + renderer.Property = TlsHandshakeProperty.KeyExchangeStrength; + SetupFeature(httpContext); + // Act + var result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal("1024", result); + } + + [Fact] + public void ProtocolTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + renderer.Property = TlsHandshakeProperty.Protocol; + SetupFeature(httpContext); + // Act + var result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal(SslProtocols.Tls13.ToString(), result); + } + + + protected override void NullRendersEmptyString() + { + // Arrange + var (renderer, _) = CreateWithHttpContext(); + + // Act + string result = renderer.Render(LogEventInfo.CreateNullEvent()); + + // Assert + Assert.Equal("None", result); + } + } +} +#endif \ No newline at end of file