Background and Motivation
While developers can use a combination of ServerCertificateSelector and IFileProvider today to automatically reload a server's TLS certificate (as mentioned in #32351), developers cannot as easily reload the certificate(s) used to validate the client's certificate chain. Developers can of course write their own ClientCertificateValidation delegate, but I think that also means re-performing much of the logic that already exists within the Microsoft.AspNetCore.Authentication.Certificate package.
I propose adding a new API that allow developers to leverage the convenience of the Microsoft.AspNetCore.Authentication.Certificate while also enabling more dynamic scenarios, like the automatic reload of trusted components in the client certificate chain.
Proposed API
Based on some initial feedback from @Tratcher to consider the existing events, I propose adding a new event that triggers just before validation (as opposed to the existing event OnCertificateValidated that triggers just afterwards). This event's input context contains information about the HTTP request from the BaseContext<T>, as well as properties that may aid in further customizing the X509ChainPolicy.
The new event and its input are below:
namespace Microsoft.AspNetCore.Authentication.Certificate;
public class CertificateAuthenticationEvents
{
+ public Func<CertificateValidatingContext, Task> OnCertificateValidating { get; set; } = context => Task.CompletedTask;
public Func<CertificateValidatedContext, Task> OnCertificateValidated { get; set; } = context => Task.CompletedTask;
public Func<CertificateChallengeContext, Task> OnChallenge { get; set; } = context => Task.CompletedTask;
}
+public class CertificateValidatingContext : BaseContext<CertificateAuthenticationOptions>
+{
+ public X509ChainPolicy ChainPolicy { get; set; } = default!;
+ public X509Certificate2 ClientCertificate { get; set; } = default!;
+ public bool IsSelfSigned { get; set; }
+}
This new event is then used by the CertificateAuthenticationHandler when validating:
var chainPolicy = BuildChainPolicy(clientCertificate, isCertificateSelfSigned);
+ var certificateValidatingContext = new CertificateValidatingContext(Context, Scheme, Options)
+ {
+ ChainPolicy = chainPolicy,
+ ClientCertificate = clientCertificate,
+ IsSelfSigned = isCertificateSelfSigned,
+ };
+
+ await Events.CertificateValidating(certificateValidatingContext);
using var chain = new X509Chain
{
ChainPolicy = chainPolicy
};
var certificateIsValid = chain.Build(clientCertificate);
I tried to model the new event as the others exist today, such that they do not return any data and instead rely on the context as a sort of communication medium. ChainPolicy (whose property name is used by X509Chain.ChainPolicy too) can be used by developers to make changes to the validation. ClientCertificate and IsSelfSigned are used internally to generate the policy (in addition to the options), and I thought they could be helpful. I am not opposed to removing them though.
Usage Examples
Based on the docs here.
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidating = context =>
{
var caCertProvider = context.HttpContext.RequestServices.GetRequiredService<ICaCertProvider>();
context.ChainPolicty.CustomTrustStore.Clear();
context.ChainPolicty.Add(caCertProvider.Certificate);
return Task.CompletedTask;
},
},
};
var app = builder.Build();
app.UseAuthentication();
app.MapGet("/", () => "Hello World!");
app.Run();
Alternative Designs
Event Context Properties
Instead of access to the entire X509ChainPolicy, developers could be simply given the CustomTrustStore and AdditionalChainCertificates collections thereby limiting the scope of changes that could be made.
+public class CertificateValidatingContext : BaseContext<CertificateAuthenticationOptions>
+{
+ public X509CertificateCollection CustomTrustStore { get; set } = default!;
+ public X509CertificateCollection AdditionalChainCertificates { get; set; } = default!;
+}
Selector Properties
At first, I was first imaging this API to be a parallel of HttpsConnectionAdapterOptions.ServerCertificateSelector with new selector properties for the CertificateAuthenticationOptions class.
public class CertificateAuthenticationOptions : AuthenticationSchemeOptions
{
public X509Certificate2Collection AdditionalChainCertificates { get; set; }
+ public Func<X509Certificate2Collection> AdditionalChainCertificatesSelector { get; set; }
public X509Certificate2Collection CustomTrustStore { get; set; }
+ public Func<X509Certificate2Collection> CustomTrustStoreSelector { get; set; }
}
There should be some input to the delegates, and the names could be a lot more creative, but I think this adds too much bloat to the class. It may also be confusing. Although I do appreciate the symmetry for client CA certificates.
Risks
The biggest problem I see with the proposal is that it may expose too much in the new event; it could be overwhelming and/or simply unnecessary for developers. In fact, developers could completely overwrite the entire 509ChainPolicy! Is exposing the X509Certificate2 object before validation also risky business?
Background and Motivation
While developers can use a combination of
ServerCertificateSelectorandIFileProvidertoday to automatically reload a server's TLS certificate (as mentioned in #32351), developers cannot as easily reload the certificate(s) used to validate the client's certificate chain. Developers can of course write their ownClientCertificateValidationdelegate, but I think that also means re-performing much of the logic that already exists within theMicrosoft.AspNetCore.Authentication.Certificatepackage.I propose adding a new API that allow developers to leverage the convenience of the
Microsoft.AspNetCore.Authentication.Certificatewhile also enabling more dynamic scenarios, like the automatic reload of trusted components in the client certificate chain.Proposed API
Based on some initial feedback from @Tratcher to consider the existing events, I propose adding a new event that triggers just before validation (as opposed to the existing event
OnCertificateValidatedthat triggers just afterwards). This event's input context contains information about the HTTP request from theBaseContext<T>, as well as properties that may aid in further customizing theX509ChainPolicy.The new event and its input are below:
namespace Microsoft.AspNetCore.Authentication.Certificate; public class CertificateAuthenticationEvents { + public Func<CertificateValidatingContext, Task> OnCertificateValidating { get; set; } = context => Task.CompletedTask; public Func<CertificateValidatedContext, Task> OnCertificateValidated { get; set; } = context => Task.CompletedTask; public Func<CertificateChallengeContext, Task> OnChallenge { get; set; } = context => Task.CompletedTask; } +public class CertificateValidatingContext : BaseContext<CertificateAuthenticationOptions> +{ + public X509ChainPolicy ChainPolicy { get; set; } = default!; + public X509Certificate2 ClientCertificate { get; set; } = default!; + public bool IsSelfSigned { get; set; } +}This new event is then used by the
CertificateAuthenticationHandlerwhen validating:I tried to model the new event as the others exist today, such that they do not return any data and instead rely on the context as a sort of communication medium.
ChainPolicy(whose property name is used byX509Chain.ChainPolicytoo) can be used by developers to make changes to the validation.ClientCertificateandIsSelfSignedare used internally to generate the policy (in addition to the options), and I thought they could be helpful. I am not opposed to removing them though.Usage Examples
Based on the docs here.
Alternative Designs
Event Context Properties
Instead of access to the entire
X509ChainPolicy, developers could be simply given theCustomTrustStoreandAdditionalChainCertificatescollections thereby limiting the scope of changes that could be made.Selector Properties
At first, I was first imaging this API to be a parallel of
HttpsConnectionAdapterOptions.ServerCertificateSelectorwith new selector properties for theCertificateAuthenticationOptionsclass.public class CertificateAuthenticationOptions : AuthenticationSchemeOptions { public X509Certificate2Collection AdditionalChainCertificates { get; set; } + public Func<X509Certificate2Collection> AdditionalChainCertificatesSelector { get; set; } public X509Certificate2Collection CustomTrustStore { get; set; } + public Func<X509Certificate2Collection> CustomTrustStoreSelector { get; set; } }There should be some input to the delegates, and the names could be a lot more creative, but I think this adds too much bloat to the class. It may also be confusing. Although I do appreciate the symmetry for client CA certificates.
Risks
The biggest problem I see with the proposal is that it may expose too much in the new event; it could be overwhelming and/or simply unnecessary for developers. In fact, developers could completely overwrite the entire
509ChainPolicy! Is exposing theX509Certificate2object before validation also risky business?