Insufficient Logging in Aspnet with Mutual Tls
Insufficient Logging in Aspnet with Mutual Tls — how this specific combination creates or exposes the vulnerability
In ASP.NET applications that use mutual TLS (mTLS), insufficient logging creates a detection gap for handshake and identity failures. With mTLS, the server validates the client certificate during the TLS handshake. When logging is insufficient, failed handshakes—due to missing or invalid client certificates, expired certificates, or mismatched certificate chains—are often recorded only as generic connection drops or HTTP 400/403 responses without certificate context.
This lack of detail obscures whether an issue is a configuration error, a revoked certificate, or an unauthorized actor attempting to present an invalid cert. Attackers can probe endpoints without leaving meaningful traces, and defenders lose visibility into patterns such as repeated certificate validation failures that might indicate reconnaissance or exploitation attempts. Because mTLS shifts some identity assurance to the certificate layer, application-layer logs must explicitly record certificate metadata (subject, issuer, thumbprint, validation errors) to maintain auditability.
Furthermore, insufficient logging in mTLS-enabled ASP.NET apps hampers compliance and forensics. Without structured logs capturing TLS handshake outcomes and certificate details, post-incident analysis becomes guesswork, and evidence for PCI-DSS, SOC 2, or HIPAA investigations may be incomplete. Logging should capture the final outcome of client certificate validation, the negotiated cipher suite, and any relevant OWASP API Top 10–adjacent events (e.g., authentication bypass attempts disguised as cert errors).
Mutual Tls-Specific Remediation in Aspnet — concrete code fixes
To remediate insufficient logging with mutual TLS in ASP.NET, enrich events with certificate details and ensure validation outcomes are explicitly recorded. Below are concrete examples for both Kestrel and HttpListener-style configurations.
Kestrel with mutual TLS (Program.cs)
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Server.Kestrel.Https;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.ListenAnyIP(5001, listenOptions =>
{
listenOptions.UseHttps(httpsOptions =>
{
httpsOptions.ServerCertificate = new X509Certificate2("server.pfx", "password");
httpsOptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
httpsOptions.AllowedCipherSuites = new[]
{
// Explicitly allow strong cipher suites; deny weak ones to reduce noise in logs
System.Security.Authentication.CipherSuite.Tls13Aes256GcmSha384,
System.Security.Authentication.CipherSuite.Tls13ChaCha20Poly1305Sha256
};
httpsOptions.AllowedSSLProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls13;
});
});
});
// Capture certificate details in middleware for structured logging
app.Use(async (context, next)
{
var cert = context.Connection.ClientCertificate;
if (cert != null)
{
context.Items["ClientCertSubject"] = cert.Subject;
context.Items["ClientCertIssuer"] = cert.Issuer;
context.Items["ClientCertThumbprint"] = cert.Thumbprint;
context.Items["ClientCertValidFrom"] = cert.NotBefore;
context.Items["ClientCertValidTo"] = cert.NotAfter;
}
else
{
context.Items["ClientCertSubject"] = "none";
context.Items["ClientCertIssuer"] = "none";
context.Items["ClientCertThumbprint"] = "none";
}
await next();
});
app.Use(async (context, next) =>
{
// Log validation outcome explicitly
var cert = context.Connection.ClientCertificate;
var eventId = cert != null && cert.Verify() ? 200 : 204; // 200 OK, 204 No Content for failed cert validation
var logger = context.RequestServices.GetRequiredService>();
logger.LogInformation(eventId, "ClientCertificateValidation",
Subject: cert?.Subject ?? "none",
Issuer: cert?.Issuer ?? "none",
Thumbprint: cert?.Thumbprint ?? "none",
Valid: cert != null && cert.Verify(),
Cipher: context.Features.Get()?.CipherAlgorithm,
Protocol: context.Features.Get()?.Protocol);
await next();
});
app.MapGet("/", () => "Hello with mTLS");
app.Run();
HttpListener with mutual TLS (minimal example)
using System.Net;
using System.Security.Cryptography.X509Certificates;
var listener = new HttpListener();
listener.Prefixes.Add("https://+:8000/");
listener.Start();
while (true)
{
var context = await listener.GetContextAsync();
var request = context.Request;
var response = context.Response;
// Log certificate details on the server side
var cert = request.GetClientCertificate();
var logger = Console.Out;
logger.WriteLine($"[mTLS] Subject: {cert?.Subject ?? "None"}");
logger.WriteLine($"[mTLS] Issuer: {cert?.Issuer ?? "None"}");
logger.WriteLine($"[mTLS] Thumbprint: {cert?.Thumbprint ?? "None"}");
logger.WriteLine($"[mTLS] Is valid: {cert != null && cert.Verify()}");
logger.WriteLine($"[mTLS] Cipher: {request.IsSecureConnection ? request.Cipher?.Algorithm : "N/A"}");
logger.WriteLine($"[mTLS] Protocol: {request.IsSecureConnection ? request.ProtocolVersion : "N/A"}");
if (cert == null || !cert.Verify())
{
// Log authentication failures explicitly
logger.WriteLine("[mTLS] Client certificate validation failed");
response.StatusCode = 403;
await response.OutputStream.WriteAsync(System.Text.Encoding.UTF8.GetBytes("Forbidden"));
continue;
}
await response.OutputStream.WriteAsync(System.Text.Encoding.UTF8.GetBytes("OK"));
}
Remediation checklist
- Log subject, issuer, thumbprint, validity period, and chain errors for every client certificate.
- Record the outcome of certificate validation (pass/fail) and the SSL/TLS protocol and cipher negotiated.
- Use structured logging (e.g., Serilog with properties) to enable querying by certificate fields and to map findings to frameworks like OWASP API Top 10 and compliance regimes (PCI-DSS, SOC 2, HIPAA, GDPR).
- Ensure logs are centralized and retained per policy; avoid suppressing errors that indicate certificate misconfiguration or potential probing.