I have got this working now with NET 7 and running Kestrel on Linux. I have set up a solution where I run an application in a Docker Linux-container from Visual Studio. I have not got it working on Windows.
The solution is here: https://github.com/HansKindberg-Lab/Kestrel-mTLS-CTL-Example
NET 7 is required, at the time of writing NET 7 Preview 7.
I have started from this issue: Developers using Kestrel can configure the list of CAs per-hostname #45456, https://github.com/dotnet/runtime/issues/45456
Briefly:
appsettings.json
{
"Kestrel": {
"Certificates": {
"Default": {
"KeyPath": "Certificates/https-certificate.key",
"Path": "Certificates/https-certificate.crt"
}
},
"EndpointDefaults": {
"ClientCertificateMode": "RequireCertificate",
}
}
}
Program.cs
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel((webHostBuilderContext, kestrelServerOptions) =>
{
kestrelServerOptions.ConfigureHttpsDefaults(httpsConnectionAdapterOptions =>
{
httpsConnectionAdapterOptions.OnAuthenticate = (connectionContext, sslServerAuthenticationOptions) =>
{
if(!sslServerAuthenticationOptions.ClientCertificateRequired)
return;
sslServerAuthenticationOptions.CertificateChainPolicy = new X509ChainPolicy
{
TrustMode = X509ChainTrustMode.System
};
var certificates = new X509Certificate2Collection();
certificates.ImportFromPemFile("Certificates/intermediate-certificate-1.crt");
certificates.ImportFromPemFile("Certificates/intermediate-certificate-2.crt");
var sslCertificateTrust = SslCertificateTrust.CreateForX509Collection(certificates, true);
sslServerAuthenticationOptions.ServerCertificateContext = SslStreamCertificateContext.Create((X509Certificate2)sslServerAuthenticationOptions.ServerCertificate, null, false, sslCertificateTrust);
};
});
});
builder.Services.AddRazorPages();
var app = builder.Build();
app.UseRouting();
app.MapRazorPages();
app.Run();
It would be great if the code above could be accomplished by configuration. I do not know how. Now it is hard-coded.
There are more details in the github-repository.