I have tried to implement Certificate Authentication against my RESTful web services exactly as prescribed by Microsoft in the article at
Configure certificate authentication in ASP.NET Core | Microsoft Learn. Unfortunately, when I perform my own custom validation of the certificate and call
context.Fail("Fail reason");
then nothing happens. The API authenticates the request because the selected certificate is a vaild chained certificate. It just does not meet my custom validation requirements. How can I get authentication to fail when I perform my own custom vaildation?
What I have tried:
I have tried the following:
builder.Services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
var validationService = context.HttpContext.RequestServices
.GetRequiredService<ICertificateValidationService>();
if (!validationService.ValidateCertificate(context.ClientCertificate))
{
context.Principal = null;
context.Fail("Invalid certificate");
}
else
{
var claims = new[]
{
new Claim(
ClaimTypes.NameIdentifier,
context.ClientCertificate.Subject,
ClaimValueTypes.String, context.Options.ClaimsIssuer),
new Claim(
ClaimTypes.Name,
context.ClientCertificate.Subject,
ClaimValueTypes.String, context.Options.ClaimsIssuer)
};
context.Principal = new ClaimsPrincipal(
new ClaimsIdentity(claims, context.Scheme.Name));
context.Success();
}
return Task.CompletedTask;
}
};
});
And here is the ValidateCertificate method implementation:
public bool ValidateCertificate(X509Certificate2 clientCertificate)
{
if (clientCertificate == null!)
{
var error = new ConfigurationErrorsException("Missing certificate or certificate not sent by client.");
Logger.Log.Error(error, "Certificate validation failed. Missing certificate or certificate not sent by client.");
throw error;
}
if (_ClientCertificateSettings == null! || _ClientCertificateSettings.AllowedCertificates == null! || !_ClientCertificateSettings.AllowedCertificates.ToList().Any())
{
var error = new ConfigurationErrorsException("Certificate configuration missing. Check AppSettings.");
Logger.Log.Error(error, "Certificate validation failed. Certificate configuration missing. Check AppSettings.");
throw error;
}
if (_ClientCertificateSettings.AllowedCertificates.Any(cert => string.IsNullOrEmpty(cert.Subject)))
{
var error = new ConfigurationErrorsException("Certificate configuration missing Subject. Check AppSettings.");
Logger.Log.Error(error, "Certificate validation failed. Certificate configuration missing Subject. Check AppSettings.");
throw error;
}
if (_ClientCertificateSettings.AllowedCertificates.Any(cert => string.IsNullOrEmpty(cert.Issuer)))
{
var error = new ConfigurationErrorsException("Certificate configuration missing Subject. Check AppSettings.");
Logger.Log.Error(error, "Certificate validation failed. Certificate configuration missing Issuer. Check AppSettings.");
throw error;
}
if (DateTime.Compare(DateTime.UtcNow, clientCertificate.NotBefore) < 0 || DateTime.Compare(DateTime.UtcNow, clientCertificate.NotAfter) > 0)
{
Logger.Log.Warning($"Certificate with thumbprint {clientCertificate.Thumbprint} is expired.");
return false;
}
var foundSubject = false;
var certSubjectData = clientCertificate.Subject.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (certSubjectData.Any(certSubject => _ClientCertificateSettings.AllowedCertificates.Any(cert => cert.Subject.Equals(certSubject.Trim(), StringComparison.InvariantCultureIgnoreCase))))
{
foundSubject = true;
}
if (!foundSubject)
{
Logger.Log.Warning($"Certificate with thumbprint {clientCertificate.Thumbprint} does not have a matching Subject.");
return false;
}
var certIssuerData = clientCertificate.Issuer.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var foundIssuer = certIssuerData.Any(issuerData => _ClientCertificateSettings.AllowedCertificates.Any(cert => cert.Issuer.Equals(issuerData.Trim(), StringComparison.InvariantCultureIgnoreCase)));
if (!foundIssuer)
{
Logger.Log.Warning($"Certificate with thumbprint {clientCertificate.Thumbprint} does not have a matching Issuer.");
return false;
}
if (_ClientCertificateSettings.CheckThumbprintInCertStore)
{
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
try
{
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
var certs = store.Certificates.Find(X509FindType.FindByThumbprint, clientCertificate.Thumbprint, true);
if (certs.Count == 0)
{
Logger.Log.Warning("Invalid client certificate. The thumbprint does not match with a certificate in the certificate store. {@clientCertificate}", clientCertificate);
return false;
}
}
catch (Exception ex)
{
Logger.Log.Error(ex, "An exception occurred searching for the client certificate in the certificate store. {@clientCertificate}", clientCertificate);
throw;
}
finally
{
store.Close();
store.Dispose();
}
}
return true;
}