Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Microsoft.AspNetCore.Authentication.Certificate
bevat een implementatie die vergelijkbaar is met certificaatverificatie voor ASP.NET Core. Certificaatverificatie vindt plaats op TLS-niveau, lang voordat het ooit ASP.NET Core wordt. Nauwkeuriger is dit een verificatiehandler waarmee het certificaat wordt gevalideerd en u vervolgens een gebeurtenis krijgt waarin u dat certificaat kunt oplossen naar een ClaimsPrincipal
.
U moetuw server configureren voor certificaatverificatie, of het nu IIS, KestrelAzure Web Apps of wat u ook gebruikt.
Scenario's voor proxy en load balancer
Certificaatverificatie is een stateful scenario dat voornamelijk wordt gebruikt waarbij een proxy of load balancer geen verkeer tussen clients en servers verwerkt. Als een proxy of load balancer wordt gebruikt, werkt certificaatauthenticatie alleen als de proxy of load balancer:
- Verzorgt de authenticatie.
- Geeft de gebruikersverificatiegegevens door aan de app (bijvoorbeeld in een aanvraagheader), die op de verificatiegegevens reageert.
Een alternatief voor certificaatverificatie in omgevingen waarin proxy's en load balancers worden gebruikt, is Active Directory Federated Services (ADFS) met OpenID Connect (OIDC).
Aan de slag
Verkrijg een HTTPS-certificaat, pas dit toe en configureer uw server om certificaten te vereisen.
In de web-app:
- Voeg een verwijzing toe naar het NuGet-pakket Microsoft.AspNetCore.Authentication.Certificate .
- In
Program.cs
, belbuilder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...);
. Geef een gemachtigde op omOnCertificateValidated
aanvullende validatie uit te voeren op het clientcertificaat dat is verzonden met aanvragen. Zet deze informatie om in eenClaimsPrincipal
en stel deze in op decontext.Principal
eigenschap.
Als de verificatie mislukt, retourneert deze handler een 403 (Forbidden)
antwoord in plaats van een 401 (Unauthorized)
, zoals u zou verwachten. De redenering is dat de verificatie moet plaatsvinden tijdens de eerste TLS-verbinding. Tegen de tijd dat het de handler bereikt, is het te laat. U kunt de verbinding niet upgraden van een anonieme verbinding naar een verbinding met een certificaat.
UseAuthentication
is vereist om HttpContext.User
in te stellen op een ClaimsPrincipal
gemaakt op basis van het certificaat. Voorbeeld:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate();
var app = builder.Build();
app.UseAuthentication();
app.MapGet("/", () => "Hello World!");
app.Run();
In het voorgaande voorbeeld ziet u de standaardmethode voor het toevoegen van certificaatverificatie. De handler maakt een gebruikersprincipaal met behulp van de algemene certificaateigenschappen.
Certificaatvalidatie configureren
De CertificateAuthenticationOptions
handler heeft een aantal ingebouwde validaties die de minimale validaties zijn die u op een certificaat moet uitvoeren. Elk van deze instellingen is standaard ingeschakeld.
AllowedCertificateTypes = Gekoppeld, Zelfondertekend, of Alle (Gekoppeld | Zelfondertekend)
Standaardwaarde: CertificateTypes.Chained
Met deze controle wordt gecontroleerd of alleen het juiste certificaattype is toegestaan. Als de app zelfondertekende certificaten gebruikt, moet deze optie worden ingesteld op CertificateTypes.All
of CertificateTypes.SelfSigned
.
ChainTrustValidatieMode
Standaardwaarde: X509ChainTrustMode.System
Het certificaat dat door de client wordt gepresenteerd, moet worden gekoppeld aan een vertrouwd basiscertificaat. Met deze controle bepaalt u welk vertrouwensarchief deze basiscertificaten bevat.
De handler maakt standaard gebruik van het vertrouwensarchief van het systeem. Als het gepresenteerde clientcertificaat moet worden gekoppeld aan een basiscertificaat dat niet wordt weergegeven in het systeemvertrouwensarchief, kan deze optie worden ingesteld op X509ChainTrustMode.CustomRootTrust om de handler te laten gebruiken CustomTrustStore
.
CustomTrustStore
Standaardwaarde: Leeg X509Certificate2Collection
Als de ChainTrustValidationMode eigenschap van de handler is ingesteld op X509ChainTrustMode.CustomRootTrust
, bevat deze X509Certificate2Collection elk certificaat dat wordt gebruikt om het clientcertificaat te valideren tot aan een vertrouwde root, inclusief de vertrouwde root.
Wanneer de client een certificaat presenteert dat deel uitmaakt van een certificaatketen met meerdere niveaus, CustomTrustStore
moet elk verlenend certificaat in de keten bevatten.
Certificaatgebruik valideren
Standaardwaarde: true
Met deze controle wordt gecontroleerd of het certificaat dat door de client wordt gepresenteerd, het uitgebreide sleutelgebruik (EKU) voor clientverificatie heeft, of dat er helemaal geen EKU's zijn. Zoals de specificaties zeggen, als er geen EKU is opgegeven, worden alle EKU's als geldig beschouwd.
BevestigGeldigheidsperiode
Standaardwaarde: true
Met deze controle wordt gecontroleerd of het certificaat binnen de geldigheidsperiode valt. Bij elke aanvraag zorgt de handler ervoor dat een certificaat dat geldig was toen het werd gepresenteerd, niet is verlopen tijdens de huidige sessie.
Intrekkingsflag
Standaardwaarde: X509RevocationFlag.ExcludeRoot
Een vlag die aangeeft welke certificaten in de keten worden gecontroleerd op intrekking.
Intrekkingscontroles worden alleen uitgevoerd wanneer het certificaat is gekoppeld aan een basiscertificaat.
Intrekkingsmodus
Standaardwaarde: X509RevocationMode.Online
Een vlag die aangeeft hoe intrekkingscontroles worden uitgevoerd.
Het specificeren van een onlinecontrole kan leiden tot een lange vertraging terwijl er contact wordt opgenomen met de certificaatinstantie.
Intrekkingscontroles worden alleen uitgevoerd wanneer het certificaat is gekoppeld aan een basiscertificaat.
Kan ik mijn app zo configureren dat alleen een certificaat op bepaalde paden is vereist?
Dit is niet mogelijk. Onthoud dat de certificaatuitwisseling wordt uitgevoerd aan het begin van het HTTPS-gesprek. Dit wordt gedaan door de server voordat de eerste aanvraag op die verbinding wordt ontvangen, zodat het niet mogelijk is om het bereik te bepalen op basis van aanvraagvelden.
Handler-gebeurtenissen
De handler heeft twee gebeurtenissen:
-
OnAuthenticationFailed
: Wordt aangeroepen als er een uitzondering optreedt tijdens de verificatie en kunt u reageren. -
OnCertificateValidated
: Aangeroepen nadat het certificaat succesvol gevalideerd is en er een standaardprincipal is gemaakt. Met deze gebeurtenis kunt u uw eigen validatie uitvoeren en de principal uitbreiden of vervangen. Voorbeelden hiervan zijn:Bepalen of het certificaat bekend is bij uw services.
Uw eigen principal samenstellen. Bekijk het volgende voorbeeld:
builder.Services.AddAuthentication( CertificateAuthenticationDefaults.AuthenticationScheme) .AddCertificate(options => { options.Events = new CertificateAuthenticationEvents { OnCertificateValidated = context => { 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; } }; });
Als het binnenkomende certificaat niet voldoet aan uw extra validatie, geef dan een foutreden door aan context.Fail("failure reason")
.
Voor een betere functionaliteit, roep een service aan die is geregistreerd in dependency injection en verbinding maakt met een database of een ander type gebruikersopslag. Open de service met behulp van de context die is doorgegeven aan de gemachtigde. Bekijk het volgende voorbeeld:
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))
{
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;
}
};
});
Conceptueel is de validatie van het certificaat een autorisatieprobleem. Het toevoegen van een controle op bijvoorbeeld een uitgever of vingerafdruk in een autorisatiebeleid, in plaats van binnen OnCertificateValidated
, is volledig acceptabel.
Configureer uw server zo dat certificaten vereist zijn.
Kestrel
Program.cs
Configureer Kestrelals volgt in:
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<KestrelServerOptions>(options =>
{
options.ConfigureHttpsDefaults(options =>
options.ClientCertificateMode = ClientCertificateMode.RequireCertificate);
});
Opmerking
Eindpunten die zijn gemaakt door Listenaan te roepen voordatConfigureHttpsDefaults worden aangeroepen, worden de standaardwaarden niet toegepast.
IIS
Voer de volgende stappen uit in IIS Manager:
- Selecteer uw site op het tabblad Verbindingen .
- Dubbelklik op de optie SSL-instellingen in het venster Functiesweergave .
- Vink het selectievakje SSL vereisen aan en selecteer de optie Vereisen in de sectie Clientcertificaten.
Azure en aangepaste webproxy's
Raadpleeg de host- en implementatiedocumentatie voor het configureren van de middleware voor het doorsturen van certificaten.
Certificaatverificatie gebruiken in Azure Web Apps
Er is geen doorstuurconfiguratie vereist voor Azure. De doorstuurconfiguratie wordt ingesteld door de Middleware voor het doorsturen van certificaten.
Opmerking
Middleware voor het doorsturen van certificaten is vereist voor dit scenario.
Zie Een TLS/SSL-certificaat gebruiken in uw code in Azure App Service (Documentatie voor Azure) voor meer informatie.
Certificaatverificatie gebruiken in aangepaste webproxy's
De AddCertificateForwarding
methode wordt gebruikt om het volgende op te geven:
- De naam van de clientheader.
- Hoe het certificaat moet worden geladen (met behulp van de
HeaderConverter
eigenschap).
In aangepaste webproxy's wordt het certificaat doorgegeven als een aangepaste aanvraagheader, bijvoorbeeld X-SSL-CERT
. Als u het wilt gebruiken, configureert u het doorsturen van certificaten in Program.cs
:
builder.Services.AddCertificateForwarding(options =>
{
options.CertificateHeader = "X-SSL-CERT";
options.HeaderConverter = headerValue =>
{
X509Certificate2? clientCertificate = null;
if (!string.IsNullOrWhiteSpace(headerValue))
{
clientCertificate = new X509Certificate2(StringToByteArray(headerValue));
}
return clientCertificate!;
static byte[] StringToByteArray(string hex)
{
var numberChars = hex.Length;
var bytes = new byte[numberChars / 2];
for (int i = 0; i < numberChars; i += 2)
{
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
}
return bytes;
}
};
});
nl-NL: Als de app omgekeerd geproxy'd wordt door NGINX met de configuratie proxy_set_header ssl-client-cert $ssl_client_escaped_cert
of geïmplementeerd op Kubernetes met behulp van NGINX Ingress, wordt het clientcertificaat doorgegeven aan de app in URL-gecodeerde vorm. Als u het certificaat wilt gebruiken, decodert u het als volgt:
builder.Services.AddCertificateForwarding(options =>
{
options.CertificateHeader = "ssl-client-cert";
options.HeaderConverter = (headerValue) =>
{
X509Certificate2? clientCertificate = null;
if (!string.IsNullOrWhiteSpace(headerValue))
{
clientCertificate = X509Certificate2.CreateFromPem(
WebUtility.UrlDecode(headerValue));
}
return clientCertificate!;
};
});
Voeg de middleware toe in Program.cs
.
UseCertificateForwarding
wordt aangeroepen vóór de aanroepen naar UseAuthentication
en UseAuthorization
:
var app = builder.Build();
app.UseCertificateForwarding();
app.UseAuthentication();
app.UseAuthorization();
Een afzonderlijke klasse kan worden gebruikt om validatielogica te implementeren. Omdat hetzelfde zelfondertekende certificaat in dit voorbeeld wordt gebruikt, moet u ervoor zorgen dat alleen uw certificaat kan worden gebruikt. Controleer of de vingerafdrukken van zowel het clientcertificaat als het servercertificaat overeenkomen, anders kan elk certificaat worden gebruikt en is voldoende om te verifiëren. Dit wordt gebruikt in de AddCertificate
methode. U kunt hier ook het onderwerp of de uitgever valideren als u intermediaire of subcertificaten gebruikt.
using System.Security.Cryptography.X509Certificates;
namespace CertAuthSample.Snippets;
public class SampleCertificateValidationService : ICertificateValidationService
{
public bool ValidateCertificate(X509Certificate2 clientCertificate)
{
// Don't hardcode passwords in production code.
// Use a certificate thumbprint or Azure Key Vault.
var expectedCertificate = new X509Certificate2(
Path.Combine("/path/to/pfx"), "1234");
return clientCertificate.Thumbprint == expectedCertificate.Thumbprint;
}
}
Een HttpClient implementeren met behulp van een certificaat en IHttpClientFactory
In het volgende voorbeeld wordt een clientcertificaat aan een HttpClientHandler
toegevoegd met behulp van de ClientCertificates
eigenschap van de handler. Deze handler kan vervolgens worden gebruikt in een benoemd exemplaar van een HttpClient
met behulp van de ConfigurePrimaryHttpMessageHandler methode. Dit is ingesteld in Program.cs
:
var clientCertificate =
new X509Certificate2(
Path.Combine(_environment.ContentRootPath, "sts_dev_cert.pfx"), "1234");
builder.Services.AddHttpClient("namedClient", c =>
{
}).ConfigurePrimaryHttpMessageHandler(() =>
{
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(clientCertificate);
return handler;
});
De IHttpClientFactory
kan vervolgens worden gebruikt om het benoemde exemplaar op te halen met behulp van de handler en het certificaat. De CreateClient
methode met de naam van de client die in Program.cs
is gedefinieerd, wordt gebruikt om het exemplaar op te halen. De HTTP-aanvraag kan naar behoefte worden verzonden met behulp van de client:
public class SampleHttpService
{
private readonly IHttpClientFactory _httpClientFactory;
public SampleHttpService(IHttpClientFactory httpClientFactory)
=> _httpClientFactory = httpClientFactory;
public async Task<JsonDocument> GetAsync()
{
var httpClient = _httpClientFactory.CreateClient("namedClient");
var httpResponseMessage = await httpClient.GetAsync("https://example.com");
if (httpResponseMessage.IsSuccessStatusCode)
{
return JsonDocument.Parse(
await httpResponseMessage.Content.ReadAsStringAsync());
}
throw new ApplicationException($"Status code: {httpResponseMessage.StatusCode}");
}
}
Als het juiste certificaat naar de server wordt verzonden, worden de gegevens geretourneerd. Als er geen certificaat of het verkeerde certificaat wordt verzonden, wordt een HTTP 403-statuscode geretourneerd.
Certificaten maken in PowerShell
Het maken van de certificaten is het moeilijkste onderdeel van het instellen van deze stroom. U kunt een basiscertificaat maken met behulp van de New-SelfSignedCertificate
PowerShell-cmdlet. Wanneer u het certificaat maakt, gebruikt u een sterk wachtwoord. Het is belangrijk om de KeyUsageProperty
parameter en de KeyUsage
parameter toe te voegen, zoals wordt weergegeven.
Basis-CA maken
New-SelfSignedCertificate -DnsName "root_ca_dev_damienbod.com", "root_ca_dev_damienbod.com" -CertStoreLocation "cert:\LocalMachine\My" -NotAfter (Get-Date).AddYears(20) -FriendlyName "root_ca_dev_damienbod.com" -KeyUsageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature
$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText
Get-ChildItem -Path cert:\localMachine\my\"The thumbprint..." | Export-PfxCertificate -FilePath C:\git\root_ca_dev_damienbod.pfx -Password $mypwd
Export-Certificate -Cert cert:\localMachine\my\"The thumbprint..." -FilePath root_ca_dev_damienbod.crt
Opmerking
De -DnsName
parameterwaarde moet overeenkomen met het implementatiedoel van de app. Bijvoorbeeld 'localhost' voor ontwikkeling.
Installeren in de vertrouwde basis
Het basiscertificaat moet worden vertrouwd op uw hostsysteem. Alleen basiscertificaten die door een certificeringsinstantie zijn gemaakt, worden standaard vertrouwd. Zie de Windows-documentatie of de Import-Certificate
PowerShell-cmdlet voor informatie over het vertrouwen van het basiscertificaat in Windows.
Tussenliggend certificaat
Er kan nu een tussenliggend certificaat worden gemaakt op basis van het basiscertificaat. Dit is niet vereist voor alle use cases, maar mogelijk moet u veel certificaten maken of groepen certificaten activeren of uitschakelen. De TextExtension
parameter is vereist om de padlengte in te stellen in de basisbeperkingen van het certificaat.
Het tussenliggende certificaat kan vervolgens worden toegevoegd aan het vertrouwde tussenliggende certificaat in het Windows-hostsysteem.
$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText
$parentcert = ( Get-ChildItem -Path cert:\LocalMachine\My\"The thumbprint of the root..." )
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "intermediate_dev_damienbod.com" -Signer $parentcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "intermediate_dev_damienbod.com" -KeyUsageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature -TextExtension @("2.5.29.19={text}CA=1&pathlength=1")
Get-ChildItem -Path cert:\localMachine\my\"The thumbprint..." | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\intermediate_dev_damienbod.pfx -Password $mypwd
Export-Certificate -Cert cert:\localMachine\my\"The thumbprint..." -FilePath intermediate_dev_damienbod.crt
Een onderliggend certificaat maken op basis van een tussenliggend certificaat
Er kan een kindercertificaat worden gemaakt op basis van het tussenliggende certificaat. Dit is de eindentiteit die geen kindcertificaten hoeft te maken.
$parentcert = ( Get-ChildItem -Path cert:\LocalMachine\My\"The thumbprint from the Intermediate certificate..." )
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "child_a_dev_damienbod.com" -Signer $parentcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "child_a_dev_damienbod.com"
$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText
Get-ChildItem -Path cert:\localMachine\my\"The thumbprint..." | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\child_a_dev_damienbod.pfx -Password $mypwd
Export-Certificate -Cert cert:\localMachine\my\"The thumbprint..." -FilePath child_a_dev_damienbod.crt
Kindcertificaat maken van rootcertificaat
Een kindcertificaat kan ook rechtstreeks vanuit het basiscertificaat worden gemaakt.
$rootcert = ( Get-ChildItem -Path cert:\LocalMachine\My\"The thumbprint from the root cert..." )
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "child_a_dev_damienbod.com" -Signer $rootcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "child_a_dev_damienbod.com"
$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText
Get-ChildItem -Path cert:\localMachine\my\"The thumbprint..." | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\child_a_dev_damienbod.pfx -Password $mypwd
Export-Certificate -Cert cert:\localMachine\my\"The thumbprint..." -FilePath child_a_dev_damienbod.crt
Voorbeeld rootcertificaat - tussenliggend certificaat - certificaat
$mypwdroot = ConvertTo-SecureString -String "1234" -Force -AsPlainText
$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText
New-SelfSignedCertificate -DnsName "root_ca_dev_damienbod.com", "root_ca_dev_damienbod.com" -CertStoreLocation "cert:\LocalMachine\My" -NotAfter (Get-Date).AddYears(20) -FriendlyName "root_ca_dev_damienbod.com" -KeyUsageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature
Get-ChildItem -Path cert:\localMachine\my\0C89639E4E2998A93E423F919B36D4009A0F9991 | Export-PfxCertificate -FilePath C:\git\root_ca_dev_damienbod.pfx -Password $mypwdroot
Export-Certificate -Cert cert:\localMachine\my\0C89639E4E2998A93E423F919B36D4009A0F9991 -FilePath root_ca_dev_damienbod.crt
$rootcert = ( Get-ChildItem -Path cert:\LocalMachine\My\0C89639E4E2998A93E423F919B36D4009A0F9991 )
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "child_a_dev_damienbod.com" -Signer $rootcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "child_a_dev_damienbod.com" -KeyUsageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature -TextExtension @("2.5.29.19={text}CA=1&pathlength=1")
Get-ChildItem -Path cert:\localMachine\my\BA9BF91ED35538A01375EFC212A2F46104B33A44 | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\child_a_dev_damienbod.pfx -Password $mypwd
Export-Certificate -Cert cert:\localMachine\my\BA9BF91ED35538A01375EFC212A2F46104B33A44 -FilePath child_a_dev_damienbod.crt
$parentcert = ( Get-ChildItem -Path cert:\LocalMachine\My\BA9BF91ED35538A01375EFC212A2F46104B33A44 )
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "child_b_from_a_dev_damienbod.com" -Signer $parentcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "child_b_from_a_dev_damienbod.com"
Get-ChildItem -Path cert:\localMachine\my\141594A0AE38CBBECED7AF680F7945CD51D8F28A | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\child_b_from_a_dev_damienbod.pfx -Password $mypwd
Export-Certificate -Cert cert:\localMachine\my\141594A0AE38CBBECED7AF680F7945CD51D8F28A -FilePath child_b_from_a_dev_damienbod.crt
Wanneer u de basis-, tussen- of onderliggende certificaten gebruikt, kunnen de certificaten worden gevalideerd met behulp van de vingerafdruk of PublicKey, indien nodig:
using System.Security.Cryptography.X509Certificates;
namespace CertAuthSample.Snippets;
public class SampleCertificateThumbprintsValidationService : ICertificateValidationService
{
private readonly string[] validThumbprints = new[]
{
"141594A0AE38CBBECED7AF680F7945CD51D8F28A",
"0C89639E4E2998A93E423F919B36D4009A0F9991",
"BA9BF91ED35538A01375EFC212A2F46104B33A44"
};
public bool ValidateCertificate(X509Certificate2 clientCertificate)
=> validThumbprints.Contains(clientCertificate.Thumbprint);
}
Opslaan in cache van certificaatvalidatie
.NET 5 of nieuwere versies ondersteunen de mogelijkheid om caching van validatieresultaten in te schakelen. De caching verbetert de prestaties van certificaatverificatie aanzienlijk, omdat validatie een dure bewerking is.
Certificaatverificatie schakelt standaard caching uit. Om caching in te schakelen, roept u AddCertificateCache
aan in Program.cs
:
builder.Services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate()
.AddCertificateCache(options =>
{
options.CacheSize = 1024;
options.CacheEntryExpiration = TimeSpan.FromMinutes(2);
});
De standaard caching-implementatie slaat resultaten op in het geheugen. U kunt uw eigen cache opgeven door ICertificateValidationCache
te implementeren en het te registreren via afhankelijkheidsinjectie. Bijvoorbeeld: services.AddSingleton<ICertificateValidationCache, YourCache>()
.
Optionele clientcertificaten
Deze sectie bevat informatie voor apps die een subset van de app met een certificaat moeten beveiligen. Voor een Razor pagina of controller in de app zijn bijvoorbeeld clientcertificaten vereist. Dit brengt uitdagingen met zich mee als clientcertificaten:
- Zijn een TLS-functie, geen HTTP-functie.
- Wordt onderhandeld per verbinding en meestal aan het begin van de verbinding voordat er HTTP-gegevens beschikbaar zijn.
Er zijn twee benaderingen voor het implementeren van optionele clientcertificaten:
- Gebruik afzonderlijke hostnamen (SNI) en doorsturen. Hoewel er meer werk nodig is om te configureren, wordt dit aanbevolen omdat dit werkt in de meeste omgevingen en protocollen.
- Heronderhandeling tijdens een HTTP-aanvraag. Dit heeft verschillende beperkingen en wordt niet aanbevolen.
Afzonderlijke hosts (SNI)
Aan het begin van de verbinding is alleen de SNI-† (Server Name Indication) bekend. Clientcertificaten kunnen per hostnaam worden geconfigureerd, zodat de ene host deze vereist en een andere niet.
- Binding instellen voor het domein en subdomein:
- Stel bijvoorbeeld bindingen in op
contoso.com
enmyClient.contoso.com
. Voor decontoso.com
host is geen clientcertificaat vereist, maarmyClient.contoso.com
wel. - Zie voor meer informatie:
- Stel bijvoorbeeld bindingen in op
.NET 5 of hoger voegt handigere ondersteuning toe voor het omleiden om optionele clientcertificaten te verkrijgen. Zie het voorbeeld van optionele certificaten voor meer informatie.
- Voor aanvragen voor de web-app waarvoor een clientcertificaat vereist is maar niet aanwezig is:
- Omleiden naar dezelfde pagina met behulp van het met het clientcertificaat beveiligde subdomein.
- Bijvoorbeeld omleiden naar
myClient.contoso.com/requestedPage
. Omdat de aanvraag naarmyClient.contoso.com/requestedPage
een andere hostnaam heeft dancontoso.com/requestedPage
, brengt de client een andere verbinding tot stand en wordt het clientcertificaat verstrekt. - Zie Inleiding tot autorisatie in ASP.NET Core voor meer informatie.
† Server Name Indication (SNI) is een TLS-extensie om een virtueel domein op te nemen als onderdeel van SSL-onderhandeling. Dit betekent dat de virtuele domeinnaam of een hostnaam effectief kan worden gebruikt om het netwerkeindpunt te identificeren.
Heronderhandeling
TLS-heronderhandeling is een proces waarmee de client en server de versleutelingsvereisten voor een afzonderlijke verbinding opnieuw kunnen beoordelen, inclusief het aanvragen van een clientcertificaat als dat nog niet eerder is opgegeven. TLS-heronderhandeling is een beveiligingsrisico en wordt niet aanbevolen omdat:
- In HTTP/1.1 moet de server eerst HTTP-gegevens bufferen of verwerken die in de doorgang zijn, zoals POST-verzoeklichamen om ervoor te zorgen dat de verbinding vrij is voor de heronderhandeling. Anders kan de heronderhandeling stoppen met reageren of mislukken.
- HTTP/2 en HTTP/3 verbieden expliciet heronderhandeling.
- Er zijn beveiligingsrisico's verbonden aan heronderhandeling. TLS 1.3 heeft de heronderhandeling van de hele verbinding verwijderd en vervangen door een nieuwe extensie voor alleen het aanvragen van een clientcertificaat nadat de verbinding is gestart. Dit mechanisme wordt weergegeven via dezelfde API's en is nog steeds onderhevig aan de eerdere beperkingen van buffering en HTTP-protocolversies.
De implementatie en configuratie van deze functie variëren per server- en frameworkversie.
IIS
IIS beheert namens u de onderhandeling van het clientcertificaat. Een subsectie van de toepassing kan de SslRequireCert
optie inschakelen om te onderhandelen over het clientcertificaat voor deze aanvragen. Zie Configuratie in de IIS-documentatie voor meer informatie.
IIS buffert automatisch eventuele aanvraagbodygegevens tot een geconfigureerde groottelimiet voordat opnieuw wordt onderhandeld. Aanvragen die de limiet overschrijden, worden geweigerd met een 413-antwoord. Deze limiet is standaard ingesteld op 48 kB en kan worden geconfigureerd door de uploadReadAheadSize in te stellen.
HttpSys
HttpSys heeft twee instellingen die de onderhandeling van het clientcertificaat regelen en beide moeten worden ingesteld. De eerste bevindt zich in netsh.exe onder http add sslcert clientcertnegotiation=enable/disable
. Deze vlag geeft aan of het clientcertificaat moet worden onderhandeld aan het begin van een verbinding en moet worden ingesteld disable
op voor optionele clientcertificaten. Zie de netsh-documenten voor meer informatie.
De andere instelling is ClientCertificateMethod. Wanneer ingesteld op AllowRenegotation
, kan het clientcertificaat tijdens een aanvraag opnieuw worden onderhandeld.
NOTITIE De toepassing moet de hoofdtekstgegevens van de aanvraag bufferen of gebruiken voordat de heronderhandeling wordt uitgevoerd, anders reageert de aanvraag mogelijk niet meer.
Een toepassing kan eerst de ClientCertificate eigenschap controleren om te zien of het certificaat beschikbaar is. Als deze niet beschikbaar is, moet u ervoor zorgen dat de requestbody is gebruikt voordat u GetClientCertificateAsync aanroept om er een te onderhandelen. Opmerking GetClientCertificateAsync
kan een null-certificaat retourneren als de client weigert er een op te geven.
NOTITIE Het gedrag van de ClientCertificate
eigenschap is gewijzigd in .NET 6. Zie dit GitHub-probleem voor meer informatie.
Kestrel
Kestrel beheert onderhandeling van clientcertificaten met de ClientCertificateMode optie.
ClientCertificateMode.DelayCertificate is een nieuwe optie beschikbaar in .NET 6 of hoger. Wanneer deze optie is ingesteld, kan een app de ClientCertificate eigenschap controleren om te zien of het certificaat beschikbaar is. Als deze niet beschikbaar is, zorg ervoor dat de aanvraagbody is verbruikt voordat u GetClientCertificateAsync aanroept om er een te onderhandelen. Opmerking GetClientCertificateAsync
kan een null-certificaat retourneren als de client weigert er een op te geven.
OPMERKING De toepassing moet de inhoud van het aanvraagbericht bufferen of consumeren voordat de heronderhandeling wordt uitgevoerd, anders kan GetClientCertificateAsync
een InvalidOperationException: Client stream needs to be drained before renegotiation.
-fout veroorzaken.
Als u via een programma de TLS-instellingen per SNI-hostnaam configureert, roep dan de UseHttps
overload (.NET 6 of hoger) aan die TlsHandshakeCallbackOptions en het clientcertificaat heronderhandeling en controle via TlsHandshakeCallbackContext.AllowDelayedClientCertificateNegotation beheert.
Microsoft.AspNetCore.Authentication.Certificate
bevat een implementatie die vergelijkbaar is met certificaatverificatie voor ASP.NET Core. Certificaatverificatie vindt plaats op TLS-niveau, lang voordat het ooit ASP.NET Core wordt. Nauwkeuriger is dit een verificatiehandler waarmee het certificaat wordt gevalideerd en u vervolgens een gebeurtenis krijgt waarin u dat certificaat kunt oplossen naar een ClaimsPrincipal
.
Configureer uw server voor certificaatverificatie, of het nu IIS, KestrelAzure Web Apps of wat u ook gebruikt.
Scenario's voor proxy en load balancer
Certificaatverificatie is een stateful scenario dat voornamelijk wordt gebruikt waarbij een proxy of load balancer geen verkeer tussen clients en servers verwerkt. Als een proxy of load balancer wordt gebruikt, werkt certificaatauthenticatie alleen als de proxy of load balancer:
- Verzorgt de authenticatie.
- Geeft de gebruikersverificatiegegevens door aan de app (bijvoorbeeld in een aanvraagheader), die op de verificatiegegevens reageert.
Een alternatief voor certificaatverificatie in omgevingen waarin proxy's en load balancers worden gebruikt, is Active Directory Federated Services (ADFS) met OpenID Connect (OIDC).
Aan de slag
Verkrijg een HTTPS-certificaat, pas dit toe en configureer uw server om certificaten te vereisen.
Voeg in uw web-app een verwijzing toe naar het pakket Microsoft.AspNetCore.Authentication.Certificate . Roep vervolgens in de Startup.ConfigureServices
methode services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...);
uw opties aan en geef een gemachtigde op om OnCertificateValidated
eventuele aanvullende validatie uit te voeren op het clientcertificaat dat is verzonden met aanvragen. Zet deze informatie om in een ClaimsPrincipal
en stel deze in op de context.Principal
eigenschap.
Als de verificatie mislukt, retourneert deze handler een 403 (Forbidden)
antwoord in plaats van een 401 (Unauthorized)
, zoals u zou verwachten. De redenering is dat de verificatie moet plaatsvinden tijdens de eerste TLS-verbinding. Tegen de tijd dat het de handler bereikt, is het te laat. U kunt de verbinding niet upgraden van een anonieme verbinding naar een verbinding met een certificaat.
Voeg ook app.UseAuthentication();
toe in de Startup.Configure
methode. Anders wordt het HttpContext.User
niet ingesteld op ClaimsPrincipal
dat is gemaakt van het certificaat. Voorbeeld:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate()
// Adding an ICertificateValidationCache results in certificate auth caching the results.
// The default implementation uses a memory cache.
.AddCertificateCache();
// All other service configuration
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
// All other app configuration
}
In het voorgaande voorbeeld ziet u de standaardmethode voor het toevoegen van certificaatverificatie. De handler maakt een gebruikersprincipaal met behulp van de algemene certificaateigenschappen.
Certificaatvalidatie configureren
De CertificateAuthenticationOptions
handler heeft een aantal ingebouwde validaties die de minimale validaties zijn die u op een certificaat moet uitvoeren. Elk van deze instellingen is standaard ingeschakeld.
AllowedCertificateTypes = Gekoppeld, Zelfondertekend, of Alle (Gekoppeld | Zelfondertekend)
Standaardwaarde: CertificateTypes.Chained
Met deze controle wordt gecontroleerd of alleen het juiste certificaattype is toegestaan. Als de app zelfondertekende certificaten gebruikt, moet deze optie worden ingesteld op CertificateTypes.All
of CertificateTypes.SelfSigned
.
Certificaatgebruik valideren
Standaardwaarde: true
Met deze controle wordt gecontroleerd of het certificaat dat door de client wordt gepresenteerd, het uitgebreide sleutelgebruik (EKU) voor clientverificatie heeft, of dat er helemaal geen EKU's zijn. Zoals de specificaties zeggen, als er geen EKU is opgegeven, worden alle EKU's als geldig beschouwd.
BevestigGeldigheidsperiode
Standaardwaarde: true
Met deze controle wordt gecontroleerd of het certificaat binnen de geldigheidsperiode valt. Bij elke aanvraag zorgt de handler ervoor dat een certificaat dat geldig was toen het werd gepresenteerd, niet is verlopen tijdens de huidige sessie.
Intrekkingsflag
Standaardwaarde: X509RevocationFlag.ExcludeRoot
Een vlag die aangeeft welke certificaten in de keten worden gecontroleerd op intrekking.
Intrekkingscontroles worden alleen uitgevoerd wanneer het certificaat is gekoppeld aan een basiscertificaat.
Intrekkingsmodus
Standaardwaarde: X509RevocationMode.Online
Een vlag die aangeeft hoe intrekkingscontroles worden uitgevoerd.
Het specificeren van een onlinecontrole kan leiden tot een lange vertraging terwijl er contact wordt opgenomen met de certificaatinstantie.
Intrekkingscontroles worden alleen uitgevoerd wanneer het certificaat is gekoppeld aan een basiscertificaat.
Kan ik mijn app zo configureren dat alleen een certificaat op bepaalde paden is vereist?
Dit is niet mogelijk. Onthoud dat de certificaatuitwisseling wordt uitgevoerd aan het begin van het HTTPS-gesprek. Dit wordt gedaan door de server voordat de eerste aanvraag op die verbinding wordt ontvangen, zodat het niet mogelijk is om het bereik te bepalen op basis van aanvraagvelden.
Handler-gebeurtenissen
De handler heeft twee gebeurtenissen:
-
OnAuthenticationFailed
: Wordt aangeroepen als er een uitzondering optreedt tijdens de verificatie en kunt u reageren. -
OnCertificateValidated
: Aangeroepen nadat het certificaat succesvol gevalideerd is en er een standaardprincipal is gemaakt. Met deze gebeurtenis kunt u uw eigen validatie uitvoeren en de principal uitbreiden of vervangen. Voorbeelden hiervan zijn:Bepalen of het certificaat bekend is bij uw services.
Uw eigen principal samenstellen. Bekijk het volgende voorbeeld in
Startup.ConfigureServices
:services.AddAuthentication( CertificateAuthenticationDefaults.AuthenticationScheme) .AddCertificate(options => { options.Events = new CertificateAuthenticationEvents { OnCertificateValidated = context => { 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; } }; });
Als het binnenkomende certificaat niet voldoet aan uw extra validatie, geef dan een foutreden door aan context.Fail("failure reason")
.
Voor echte functionaliteit wilt u waarschijnlijk een service aanroepen die is geregistreerd bij afhankelijkheidsinjectie die verbinding maakt met een database of een ander type gebruikersarchief. Open uw service met behulp van de context die is doorgegeven aan uw gemachtigde. Bekijk het volgende voorbeeld in Startup.ConfigureServices
:
services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
var validationService =
context.HttpContext.RequestServices
.GetRequiredService<ICertificateValidationService>();
if (validationService.ValidateCertificate(
context.ClientCertificate))
{
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;
}
};
});
Conceptueel is de validatie van het certificaat een autorisatieprobleem. Het toevoegen van een controle op bijvoorbeeld een uitgever of vingerafdruk in een autorisatiebeleid, in plaats van binnen OnCertificateValidated
, is volledig acceptabel.
Configureer uw server zo dat certificaten vereist zijn.
Kestrel
Program.cs
Configureer Kestrelals volgt in:
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.ConfigureKestrel(o =>
{
o.ConfigureHttpsDefaults(o =>
o.ClientCertificateMode = ClientCertificateMode.RequireCertificate);
});
});
}
Opmerking
Eindpunten die zijn gemaakt door Listenaan te roepen voordatConfigureHttpsDefaults worden aangeroepen, worden de standaardwaarden niet toegepast.
IIS
Voer de volgende stappen uit in IIS Manager:
- Selecteer uw site op het tabblad Verbindingen .
- Dubbelklik op de optie SSL-instellingen in het venster Functiesweergave .
- Vink het selectievakje SSL vereisen aan en selecteer de optie Vereisen in de sectie Clientcertificaten.
Azure en aangepaste webproxy's
Raadpleeg de host- en implementatiedocumentatie voor het configureren van de middleware voor het doorsturen van certificaten.
Certificaatverificatie gebruiken in Azure Web Apps
Er is geen doorstuurconfiguratie vereist voor Azure. De doorstuurconfiguratie wordt ingesteld door de Middleware voor het doorsturen van certificaten.
Opmerking
Middleware voor het doorsturen van certificaten is vereist voor dit scenario.
Zie Een TLS/SSL-certificaat gebruiken in uw code in Azure App Service (Documentatie voor Azure) voor meer informatie.
Certificaatverificatie gebruiken in aangepaste webproxy's
De AddCertificateForwarding
methode wordt gebruikt om het volgende op te geven:
- De naam van de clientheader.
- Hoe het certificaat moet worden geladen (met behulp van de
HeaderConverter
eigenschap).
In aangepaste webproxy's wordt het certificaat doorgegeven als een aangepaste aanvraagheader, bijvoorbeeld X-SSL-CERT
. Als u het wilt gebruiken, configureert u het doorsturen van certificaten in Startup.ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
services.AddCertificateForwarding(options =>
{
options.CertificateHeader = "X-SSL-CERT";
options.HeaderConverter = (headerValue) =>
{
X509Certificate2 clientCertificate = null;
if(!string.IsNullOrWhiteSpace(headerValue))
{
byte[] bytes = StringToByteArray(headerValue);
clientCertificate = new X509Certificate2(bytes);
}
return clientCertificate;
};
});
}
private static byte[] StringToByteArray(string hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
{
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
}
return bytes;
}
nl-NL: Als de app omgekeerd geproxy'd wordt door NGINX met de configuratie proxy_set_header ssl-client-cert $ssl_client_escaped_cert
of geïmplementeerd op Kubernetes met behulp van NGINX Ingress, wordt het clientcertificaat doorgegeven aan de app in URL-gecodeerde vorm. Als u het certificaat wilt gebruiken, decodert u het als volgt:
In Startup.ConfigureServices
(Startup.cs
):
services.AddCertificateForwarding(options =>
{
options.CertificateHeader = "ssl-client-cert";
options.HeaderConverter = (headerValue) =>
{
X509Certificate2 clientCertificate = null;
if (!string.IsNullOrWhiteSpace(headerValue))
{
string certPem = WebUtility.UrlDecode(headerValue);
clientCertificate = X509Certificate2.CreateFromPem(certPem);
}
return clientCertificate;
};
});
De Startup.Configure
methode voegt vervolgens de middleware toe.
UseCertificateForwarding
wordt aangeroepen vóór de aanroepen naar UseAuthentication
en UseAuthorization
:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseRouting();
app.UseCertificateForwarding();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Een afzonderlijke klasse kan worden gebruikt om validatielogica te implementeren. Omdat hetzelfde zelfondertekende certificaat in dit voorbeeld wordt gebruikt, moet u ervoor zorgen dat alleen uw certificaat kan worden gebruikt. Controleer of de vingerafdrukken van zowel het clientcertificaat als het servercertificaat overeenkomen, anders kan elk certificaat worden gebruikt en is voldoende om te verifiëren. Dit wordt gebruikt in de AddCertificate
methode. U kunt hier ook het onderwerp of de uitgever valideren als u intermediaire of subcertificaten gebruikt.
using System.IO;
using System.Security.Cryptography.X509Certificates;
namespace AspNetCoreCertificateAuthApi
{
public class MyCertificateValidationService
{
public bool ValidateCertificate(X509Certificate2 clientCertificate)
{
// Do not hardcode passwords in production code
// Use thumbprint or key vault
var cert = new X509Certificate2(
Path.Combine("sts_dev_cert.pfx"), "1234");
if (clientCertificate.Thumbprint == cert.Thumbprint)
{
return true;
}
return false;
}
}
}
Een HttpClient implementeren met behulp van een certificaat en de HttpClientHandler
De HttpClientHandler
kan rechtstreeks in de constructor van de HttpClient
klasse worden toegevoegd. Er moet voorzichtig te werk worden gegaan bij het maken van exemplaren van de HttpClient
. Het HttpClient
certificaat wordt vervolgens met elke aanvraag verzonden.
private async Task<JsonDocument> GetApiDataUsingHttpClientHandler()
{
var cert = new X509Certificate2(Path.Combine(_environment.ContentRootPath, "sts_dev_cert.pfx"), "1234");
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(cert);
var client = new HttpClient(handler);
var request = new HttpRequestMessage()
{
RequestUri = new Uri("https://localhost:44379/api/values"),
Method = HttpMethod.Get,
};
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
var data = JsonDocument.Parse(responseContent);
return data;
}
throw new ApplicationException($"Status code: {response.StatusCode}, Error: {response.ReasonPhrase}");
}
Een HttpClient implementeren met behulp van een certificaat en een httpclient met de naam IHttpClientFactory
In het volgende voorbeeld wordt een clientcertificaat aan een HttpClientHandler
toegevoegd met behulp van de ClientCertificates
eigenschap van de handler. Deze handler kan vervolgens worden gebruikt in een benoemd exemplaar van een HttpClient
met behulp van de ConfigurePrimaryHttpMessageHandler methode. Dit is ingesteld in Startup.ConfigureServices
:
var clientCertificate =
new X509Certificate2(
Path.Combine(_environment.ContentRootPath, "sts_dev_cert.pfx"), "1234");
services.AddHttpClient("namedClient", c =>
{
}).ConfigurePrimaryHttpMessageHandler(() =>
{
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(clientCertificate);
return handler;
});
De IHttpClientFactory
kan vervolgens worden gebruikt om het benoemde exemplaar op te halen met behulp van de handler en het certificaat. De CreateClient
methode met de naam van de client die in de Startup
klasse is gedefinieerd, wordt gebruikt om het exemplaar op te halen. De HTTP-aanvraag kan naar behoefte worden verzonden met behulp van de client.
private readonly IHttpClientFactory _clientFactory;
public ApiService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
private async Task<JsonDocument> GetApiDataWithNamedClient()
{
var client = _clientFactory.CreateClient("namedClient");
var request = new HttpRequestMessage()
{
RequestUri = new Uri("https://localhost:44379/api/values"),
Method = HttpMethod.Get,
};
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
var data = JsonDocument.Parse(responseContent);
return data;
}
throw new ApplicationException($"Status code: {response.StatusCode}, Error: {response.ReasonPhrase}");
}
Als het juiste certificaat naar de server wordt verzonden, worden de gegevens geretourneerd. Als er geen certificaat of het verkeerde certificaat wordt verzonden, wordt een HTTP 403-statuscode geretourneerd.
Certificaten maken in PowerShell
Het maken van de certificaten is het moeilijkste onderdeel van het instellen van deze stroom. U kunt een basiscertificaat maken met behulp van de New-SelfSignedCertificate
PowerShell-cmdlet. Wanneer u het certificaat maakt, gebruikt u een sterk wachtwoord. Het is belangrijk om de KeyUsageProperty
parameter en de KeyUsage
parameter toe te voegen, zoals wordt weergegeven.
Basis-CA maken
New-SelfSignedCertificate -DnsName "root_ca_dev_damienbod.com", "root_ca_dev_damienbod.com" -CertStoreLocation "cert:\LocalMachine\My" -NotAfter (Get-Date).AddYears(20) -FriendlyName "root_ca_dev_damienbod.com" -KeyUsageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature
$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText
Get-ChildItem -Path cert:\localMachine\my\"The thumbprint..." | Export-PfxCertificate -FilePath C:\git\root_ca_dev_damienbod.pfx -Password $mypwd
Export-Certificate -Cert cert:\localMachine\my\"The thumbprint..." -FilePath root_ca_dev_damienbod.crt
Opmerking
De -DnsName
parameterwaarde moet overeenkomen met het implementatiedoel van de app. Bijvoorbeeld 'localhost' voor ontwikkeling.
Installeren in de vertrouwde basis
Het rootcertificaat moet op uw hostsysteem vertrouwd worden. Een basiscertificaat dat niet is gemaakt door een certificeringsinstantie, wordt niet standaard vertrouwd. Zie deze vraag voor informatie over het vertrouwen van het basiscertificaat in Windows.
Tussenliggend certificaat
Er kan nu een tussenliggend certificaat worden gemaakt op basis van het basiscertificaat. Dit is niet vereist voor alle use cases, maar mogelijk moet u veel certificaten maken of groepen certificaten activeren of uitschakelen. De TextExtension
parameter is vereist om de padlengte in te stellen in de basisbeperkingen van het certificaat.
Het tussenliggende certificaat kan vervolgens worden toegevoegd aan het vertrouwde tussenliggende certificaat in het Windows-hostsysteem.
$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText
$parentcert = ( Get-ChildItem -Path cert:\LocalMachine\My\"The thumbprint of the root..." )
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "intermediate_dev_damienbod.com" -Signer $parentcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "intermediate_dev_damienbod.com" -KeyUsageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature -TextExtension @("2.5.29.19={text}CA=1&pathlength=1")
Get-ChildItem -Path cert:\localMachine\my\"The thumbprint..." | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\intermediate_dev_damienbod.pfx -Password $mypwd
Export-Certificate -Cert cert:\localMachine\my\"The thumbprint..." -FilePath intermediate_dev_damienbod.crt
Een onderliggend certificaat maken op basis van een tussenliggend certificaat
Er kan een kindercertificaat worden gemaakt op basis van het tussenliggende certificaat. Dit is de eindentiteit die geen kindcertificaten hoeft te maken.
$parentcert = ( Get-ChildItem -Path cert:\LocalMachine\My\"The thumbprint from the Intermediate certificate..." )
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "child_a_dev_damienbod.com" -Signer $parentcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "child_a_dev_damienbod.com"
$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText
Get-ChildItem -Path cert:\localMachine\my\"The thumbprint..." | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\child_a_dev_damienbod.pfx -Password $mypwd
Export-Certificate -Cert cert:\localMachine\my\"The thumbprint..." -FilePath child_a_dev_damienbod.crt
Kindcertificaat maken van rootcertificaat
Een kindcertificaat kan ook rechtstreeks vanuit het basiscertificaat worden gemaakt.
$rootcert = ( Get-ChildItem -Path cert:\LocalMachine\My\"The thumbprint from the root cert..." )
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "child_a_dev_damienbod.com" -Signer $rootcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "child_a_dev_damienbod.com"
$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText
Get-ChildItem -Path cert:\localMachine\my\"The thumbprint..." | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\child_a_dev_damienbod.pfx -Password $mypwd
Export-Certificate -Cert cert:\localMachine\my\"The thumbprint..." -FilePath child_a_dev_damienbod.crt
Voorbeeld rootcertificaat - tussenliggend certificaat - certificaat
$mypwdroot = ConvertTo-SecureString -String "1234" -Force -AsPlainText
$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText
New-SelfSignedCertificate -DnsName "root_ca_dev_damienbod.com", "root_ca_dev_damienbod.com" -CertStoreLocation "cert:\LocalMachine\My" -NotAfter (Get-Date).AddYears(20) -FriendlyName "root_ca_dev_damienbod.com" -KeyUsageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature
Get-ChildItem -Path cert:\localMachine\my\0C89639E4E2998A93E423F919B36D4009A0F9991 | Export-PfxCertificate -FilePath C:\git\root_ca_dev_damienbod.pfx -Password $mypwdroot
Export-Certificate -Cert cert:\localMachine\my\0C89639E4E2998A93E423F919B36D4009A0F9991 -FilePath root_ca_dev_damienbod.crt
$rootcert = ( Get-ChildItem -Path cert:\LocalMachine\My\0C89639E4E2998A93E423F919B36D4009A0F9991 )
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "child_a_dev_damienbod.com" -Signer $rootcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "child_a_dev_damienbod.com" -KeyUsageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature -TextExtension @("2.5.29.19={text}CA=1&pathlength=1")
Get-ChildItem -Path cert:\localMachine\my\BA9BF91ED35538A01375EFC212A2F46104B33A44 | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\child_a_dev_damienbod.pfx -Password $mypwd
Export-Certificate -Cert cert:\localMachine\my\BA9BF91ED35538A01375EFC212A2F46104B33A44 -FilePath child_a_dev_damienbod.crt
$parentcert = ( Get-ChildItem -Path cert:\LocalMachine\My\BA9BF91ED35538A01375EFC212A2F46104B33A44 )
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "child_b_from_a_dev_damienbod.com" -Signer $parentcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "child_b_from_a_dev_damienbod.com"
Get-ChildItem -Path cert:\localMachine\my\141594A0AE38CBBECED7AF680F7945CD51D8F28A | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\child_b_from_a_dev_damienbod.pfx -Password $mypwd
Export-Certificate -Cert cert:\localMachine\my\141594A0AE38CBBECED7AF680F7945CD51D8F28A -FilePath child_b_from_a_dev_damienbod.crt
Wanneer u de basis-, tussen- of onderliggende certificaten gebruikt, kunnen de certificaten naar behoefte worden gevalideerd met behulp van de vingerafdruk of PublicKey.
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography.X509Certificates;
namespace AspNetCoreCertificateAuthApi
{
public class MyCertificateValidationService
{
public bool ValidateCertificate(X509Certificate2 clientCertificate)
{
return CheckIfThumbprintIsValid(clientCertificate);
}
private bool CheckIfThumbprintIsValid(X509Certificate2 clientCertificate)
{
var listOfValidThumbprints = new List<string>
{
"141594A0AE38CBBECED7AF680F7945CD51D8F28A",
"0C89639E4E2998A93E423F919B36D4009A0F9991",
"BA9BF91ED35538A01375EFC212A2F46104B33A44"
};
if (listOfValidThumbprints.Contains(clientCertificate.Thumbprint))
{
return true;
}
return false;
}
}
}
Opslaan in cache van certificaatvalidatie
.NET 5 of nieuwere versies ondersteunen de mogelijkheid om caching van validatieresultaten in te schakelen. De caching verbetert de prestaties van certificaatverificatie aanzienlijk, omdat validatie een dure bewerking is.
Certificaatverificatie schakelt standaard caching uit. Om caching in te schakelen, roept u AddCertificateCache
aan in Startup.ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate()
.AddCertificateCache(options =>
{
options.CacheSize = 1024;
options.CacheEntryExpiration = TimeSpan.FromMinutes(2);
});
}
De standaard caching-implementatie slaat resultaten op in het geheugen. U kunt uw eigen cache opgeven door ICertificateValidationCache
te implementeren en het te registreren via afhankelijkheidsinjectie. Bijvoorbeeld: services.AddSingleton<ICertificateValidationCache, YourCache>()
.
Optionele clientcertificaten
Deze sectie bevat informatie voor apps die een subset van de app met een certificaat moeten beveiligen. Voor een Razor pagina of controller in de app zijn bijvoorbeeld clientcertificaten vereist. Dit brengt uitdagingen met zich mee als clientcertificaten:
- Zijn een TLS-functie, geen HTTP-functie.
- Wordt onderhandeld per verbinding en meestal aan het begin van de verbinding voordat er HTTP-gegevens beschikbaar zijn.
Er zijn twee benaderingen voor het implementeren van optionele clientcertificaten:
- Gebruik afzonderlijke hostnamen (SNI) en doorsturen. Hoewel er meer werk nodig is om te configureren, wordt dit aanbevolen omdat dit werkt in de meeste omgevingen en protocollen.
- Heronderhandeling tijdens een HTTP-aanvraag. Dit heeft verschillende beperkingen en wordt niet aanbevolen.
Afzonderlijke hosts (SNI)
Aan het begin van de verbinding is alleen de SNI-† (Server Name Indication) bekend. Clientcertificaten kunnen per hostnaam worden geconfigureerd, zodat de ene host deze vereist en een andere niet.
- Binding instellen voor het domein en subdomein:
- Stel bijvoorbeeld bindingen in op
contoso.com
enmyClient.contoso.com
. Voor decontoso.com
host is geen clientcertificaat vereist, maarmyClient.contoso.com
wel. - Zie voor meer informatie:
- Stel bijvoorbeeld bindingen in op
.NET 5 of hoger voegt handigere ondersteuning toe voor het omleiden om optionele clientcertificaten te verkrijgen. Zie het voorbeeld van optionele certificaten voor meer informatie.
- Voor aanvragen voor de web-app waarvoor een clientcertificaat vereist is maar niet aanwezig is:
- Omleiden naar dezelfde pagina met behulp van het met het clientcertificaat beveiligde subdomein.
- Bijvoorbeeld omleiden naar
myClient.contoso.com/requestedPage
. Omdat de aanvraag naarmyClient.contoso.com/requestedPage
een andere hostnaam heeft dancontoso.com/requestedPage
, brengt de client een andere verbinding tot stand en wordt het clientcertificaat verstrekt. - Zie Inleiding tot autorisatie in ASP.NET Core voor meer informatie.
† Server Name Indication (SNI) is een TLS-extensie om een virtueel domein op te nemen als onderdeel van SSL-onderhandeling. Dit betekent dat de virtuele domeinnaam of een hostnaam effectief kan worden gebruikt om het netwerkeindpunt te identificeren.
Heronderhandeling
TLS-heronderhandeling is een proces waarmee de client en server de versleutelingsvereisten voor een afzonderlijke verbinding opnieuw kunnen beoordelen, inclusief het aanvragen van een clientcertificaat als dat nog niet eerder is opgegeven. TLS-heronderhandeling is een beveiligingsrisico en wordt niet aanbevolen omdat:
- In HTTP/1.1 moet de server eerst HTTP-gegevens bufferen of verwerken die in de doorgang zijn, zoals POST-verzoeklichamen om ervoor te zorgen dat de verbinding vrij is voor de heronderhandeling. Anders kan de heronderhandeling stoppen met reageren of mislukken.
- HTTP/2 en HTTP/3 verbieden expliciet heronderhandeling.
- Er zijn beveiligingsrisico's verbonden aan heronderhandeling. TLS 1.3 heeft de heronderhandeling van de hele verbinding verwijderd en vervangen door een nieuwe extensie voor alleen het aanvragen van een clientcertificaat nadat de verbinding is gestart. Dit mechanisme wordt weergegeven via dezelfde API's en is nog steeds onderhevig aan de eerdere beperkingen van buffering en HTTP-protocolversies.
De implementatie en configuratie van deze functie variëren per server- en frameworkversie.
IIS
IIS beheert namens u de onderhandeling van het clientcertificaat. Een subsectie van de toepassing kan de SslRequireCert
optie inschakelen om te onderhandelen over het clientcertificaat voor deze aanvragen. Zie Configuratie in de IIS-documentatie voor meer informatie.
IIS buffert automatisch eventuele aanvraagbodygegevens tot een geconfigureerde groottelimiet voordat opnieuw wordt onderhandeld. Aanvragen die de limiet overschrijden, worden geweigerd met een 413-antwoord. Deze limiet is standaard ingesteld op 48 kB en kan worden geconfigureerd door de uploadReadAheadSize in te stellen.
HttpSys
HttpSys heeft twee instellingen die de onderhandeling van het clientcertificaat regelen en beide moeten worden ingesteld. De eerste bevindt zich in netsh.exe onder http add sslcert clientcertnegotiation=enable/disable
. Deze vlag geeft aan of het clientcertificaat moet worden onderhandeld aan het begin van een verbinding en moet worden ingesteld disable
op voor optionele clientcertificaten. Zie de netsh-documenten voor meer informatie.
De andere instelling is ClientCertificateMethod. Wanneer ingesteld op AllowRenegotation
, kan het clientcertificaat tijdens een aanvraag opnieuw worden onderhandeld.
NOTITIE De toepassing moet de hoofdtekstgegevens van de aanvraag bufferen of gebruiken voordat de heronderhandeling wordt uitgevoerd, anders reageert de aanvraag mogelijk niet meer.
Er is een bekend probleem waarbij het inschakelen AllowRenegotation
ervoor kan zorgen dat de heronderhandeling synchroon plaatsvindt bij het openen van de ClientCertificate eigenschap. Roep de GetClientCertificateAsync methode aan om dit te voorkomen. Dit is opgelost in .NET 6. Zie dit GitHub-probleem voor meer informatie. Opmerking GetClientCertificateAsync
kan een null-certificaat retourneren als de client weigert er een op te geven.
Kestrel
Kestrel beheert onderhandeling van clientcertificaten met de ClientCertificateMode optie.
Voor .NET 5 of eerder Kestrel biedt geen ondersteuning voor opnieuw onderhandelen na het starten van een verbinding om een clientcertificaat te verkrijgen. Deze functie is toegevoegd in .NET 6.
Microsoft.AspNetCore.Authentication.Certificate
bevat een implementatie die vergelijkbaar is met certificaatverificatie voor ASP.NET Core. Certificaatverificatie vindt plaats op TLS-niveau, lang voordat het ooit ASP.NET Core wordt. Nauwkeuriger is dit een verificatiehandler waarmee het certificaat wordt gevalideerd en u vervolgens een gebeurtenis krijgt waarin u dat certificaat kunt oplossen naar een ClaimsPrincipal
.
Configureer uw server voor certificaatverificatie, of het nu IIS, KestrelAzure Web Apps of wat u ook gebruikt.
Scenario's voor proxy en load balancer
Certificaatverificatie is een stateful scenario dat voornamelijk wordt gebruikt waarbij een proxy of load balancer geen verkeer tussen clients en servers verwerkt. Als een proxy of load balancer wordt gebruikt, werkt certificaatauthenticatie alleen als de proxy of load balancer:
- Verzorgt de authenticatie.
- Geeft de gebruikersverificatiegegevens door aan de app (bijvoorbeeld in een aanvraagheader), die op de verificatiegegevens reageert.
Een alternatief voor certificaatverificatie in omgevingen waarin proxy's en load balancers worden gebruikt, is Active Directory Federated Services (ADFS) met OpenID Connect (OIDC).
Aan de slag
Verkrijg een HTTPS-certificaat, pas dit toe en configureer uw server om certificaten te vereisen.
Voeg in uw web-app een verwijzing toe naar het pakket Microsoft.AspNetCore.Authentication.Certificate . Roep vervolgens in de Startup.ConfigureServices
methode services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...);
uw opties aan en geef een gemachtigde op om OnCertificateValidated
eventuele aanvullende validatie uit te voeren op het clientcertificaat dat is verzonden met aanvragen. Zet deze informatie om in een ClaimsPrincipal
en stel deze in op de context.Principal
eigenschap.
Als de verificatie mislukt, retourneert deze handler een 403 (Forbidden)
antwoord in plaats van een 401 (Unauthorized)
, zoals u zou verwachten. De redenering is dat de verificatie moet plaatsvinden tijdens de eerste TLS-verbinding. Tegen de tijd dat het de handler bereikt, is het te laat. U kunt de verbinding niet upgraden van een anonieme verbinding naar een verbinding met een certificaat.
Voeg ook app.UseAuthentication();
toe in de Startup.Configure
methode. Anders wordt het HttpContext.User
niet ingesteld op ClaimsPrincipal
dat is gemaakt van het certificaat. Voorbeeld:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate();
// All other service configuration
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
// All other app configuration
}
In het voorgaande voorbeeld ziet u de standaardmethode voor het toevoegen van certificaatverificatie. De handler maakt een gebruikersprincipaal met behulp van de algemene certificaateigenschappen.
Certificaatvalidatie configureren
De CertificateAuthenticationOptions
handler heeft een aantal ingebouwde validaties die de minimale validaties zijn die u op een certificaat moet uitvoeren. Elk van deze instellingen is standaard ingeschakeld.
AllowedCertificateTypes = Gekoppeld, Zelfondertekend, of Alle (Gekoppeld | Zelfondertekend)
Standaardwaarde: CertificateTypes.Chained
Met deze controle wordt gecontroleerd of alleen het juiste certificaattype is toegestaan. Als de app zelfondertekende certificaten gebruikt, moet deze optie worden ingesteld op CertificateTypes.All
of CertificateTypes.SelfSigned
.
Certificaatgebruik valideren
Standaardwaarde: true
Met deze controle wordt gecontroleerd of het certificaat dat door de client wordt gepresenteerd, het uitgebreide sleutelgebruik (EKU) voor clientverificatie heeft, of dat er helemaal geen EKU's zijn. Zoals de specificaties zeggen, als er geen EKU is opgegeven, worden alle EKU's als geldig beschouwd.
BevestigGeldigheidsperiode
Standaardwaarde: true
Met deze controle wordt gecontroleerd of het certificaat binnen de geldigheidsperiode valt. Bij elke aanvraag zorgt de handler ervoor dat een certificaat dat geldig was toen het werd gepresenteerd, niet is verlopen tijdens de huidige sessie.
Intrekkingsflag
Standaardwaarde: X509RevocationFlag.ExcludeRoot
Een vlag die aangeeft welke certificaten in de keten worden gecontroleerd op intrekking.
Intrekkingscontroles worden alleen uitgevoerd wanneer het certificaat is gekoppeld aan een basiscertificaat.
Intrekkingsmodus
Standaardwaarde: X509RevocationMode.Online
Een vlag die aangeeft hoe intrekkingscontroles worden uitgevoerd.
Het specificeren van een onlinecontrole kan leiden tot een lange vertraging terwijl er contact wordt opgenomen met de certificaatinstantie.
Intrekkingscontroles worden alleen uitgevoerd wanneer het certificaat is gekoppeld aan een basiscertificaat.
Kan ik mijn app zo configureren dat alleen een certificaat op bepaalde paden is vereist?
Dit is niet mogelijk. Onthoud dat de certificaatuitwisseling wordt uitgevoerd aan het begin van het HTTPS-gesprek. Dit wordt gedaan door de server voordat de eerste aanvraag op die verbinding wordt ontvangen, zodat het niet mogelijk is om het bereik te bepalen op basis van aanvraagvelden.
Handler-gebeurtenissen
De handler heeft twee gebeurtenissen:
-
OnAuthenticationFailed
: Wordt aangeroepen als er een uitzondering optreedt tijdens de verificatie en kunt u reageren. -
OnCertificateValidated
: Aangeroepen nadat het certificaat succesvol gevalideerd is en er een standaardprincipal is gemaakt. Met deze gebeurtenis kunt u uw eigen validatie uitvoeren en de principal uitbreiden of vervangen. Voorbeelden hiervan zijn:Bepalen of het certificaat bekend is bij uw services.
Uw eigen principal samenstellen. Bekijk het volgende voorbeeld in
Startup.ConfigureServices
:services.AddAuthentication( CertificateAuthenticationDefaults.AuthenticationScheme) .AddCertificate(options => { options.Events = new CertificateAuthenticationEvents { OnCertificateValidated = context => { 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; } }; });
Als het binnenkomende certificaat niet voldoet aan uw extra validatie, geef dan een foutreden door aan context.Fail("failure reason")
.
Voor echte functionaliteit wilt u waarschijnlijk een service aanroepen die is geregistreerd bij afhankelijkheidsinjectie die verbinding maakt met een database of een ander type gebruikersarchief. Open uw service met behulp van de context die is doorgegeven aan uw gemachtigde. Bekijk het volgende voorbeeld in Startup.ConfigureServices
:
services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
var validationService =
context.HttpContext.RequestServices
.GetRequiredService<ICertificateValidationService>();
if (validationService.ValidateCertificate(
context.ClientCertificate))
{
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;
}
};
});
Conceptueel is de validatie van het certificaat een autorisatieprobleem. Het toevoegen van een controle op bijvoorbeeld een uitgever of vingerafdruk in een autorisatiebeleid, in plaats van binnen OnCertificateValidated
, is volledig acceptabel.
Configureer uw server zo dat certificaten vereist zijn.
Kestrel
Program.cs
Configureer Kestrelals volgt in:
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.ConfigureKestrel(o =>
{
o.ConfigureHttpsDefaults(o =>
o.ClientCertificateMode = ClientCertificateMode.RequireCertificate);
});
});
}
Opmerking
Eindpunten die zijn gemaakt door Listenaan te roepen voordatConfigureHttpsDefaults worden aangeroepen, worden de standaardwaarden niet toegepast.
IIS
Voer de volgende stappen uit in IIS Manager:
- Selecteer uw site op het tabblad Verbindingen .
- Dubbelklik op de optie SSL-instellingen in het venster Functiesweergave .
- Vink het selectievakje SSL vereisen aan en selecteer de optie Vereisen in de sectie Clientcertificaten.
Azure en aangepaste webproxy's
Raadpleeg de host- en implementatiedocumentatie voor het configureren van de middleware voor het doorsturen van certificaten.
Certificaatverificatie gebruiken in Azure Web Apps
Er is geen doorstuurconfiguratie vereist voor Azure. De doorstuurconfiguratie wordt ingesteld door de Middleware voor het doorsturen van certificaten.
Opmerking
Middleware voor het doorsturen van certificaten is vereist voor dit scenario.
Zie Een TLS/SSL-certificaat gebruiken in uw code in Azure App Service (Documentatie voor Azure) voor meer informatie.
Certificaatverificatie gebruiken in aangepaste webproxy's
De AddCertificateForwarding
methode wordt gebruikt om het volgende op te geven:
- De naam van de clientheader.
- Hoe het certificaat moet worden geladen (met behulp van de
HeaderConverter
eigenschap).
In aangepaste webproxy's wordt het certificaat doorgegeven als een aangepaste aanvraagheader, bijvoorbeeld X-SSL-CERT
. Als u het wilt gebruiken, configureert u het doorsturen van certificaten in Startup.ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
services.AddCertificateForwarding(options =>
{
options.CertificateHeader = "X-SSL-CERT";
options.HeaderConverter = (headerValue) =>
{
X509Certificate2 clientCertificate = null;
if(!string.IsNullOrWhiteSpace(headerValue))
{
byte[] bytes = StringToByteArray(headerValue);
clientCertificate = new X509Certificate2(bytes);
}
return clientCertificate;
};
});
}
private static byte[] StringToByteArray(string hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
{
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
}
return bytes;
}
nl-NL: Als de app omgekeerd geproxy'd wordt door NGINX met de configuratie proxy_set_header ssl-client-cert $ssl_client_escaped_cert
of geïmplementeerd op Kubernetes met behulp van NGINX Ingress, wordt het clientcertificaat doorgegeven aan de app in URL-gecodeerde vorm. Als u het certificaat wilt gebruiken, decodert u het als volgt:
Voeg de naamruimte voor System.Net toe aan het begin van Startup.cs
:
using System.Net;
In Startup.ConfigureServices
:
services.AddCertificateForwarding(options =>
{
options.CertificateHeader = "ssl-client-cert";
options.HeaderConverter = (headerValue) =>
{
X509Certificate2 clientCertificate = null;
if (!string.IsNullOrWhiteSpace(headerValue))
{
var bytes = UrlEncodedPemToByteArray(headerValue);
clientCertificate = new X509Certificate2(bytes);
}
return clientCertificate;
};
});
Voeg de UrlEncodedPemToByteArray
methode toe:
private static byte[] UrlEncodedPemToByteArray(string urlEncodedBase64Pem)
{
var base64Pem = WebUtility.UrlDecode(urlEncodedBase64Pem);
var base64Cert = base64Pem
.Replace("-----BEGIN CERTIFICATE-----", string.Empty)
.Replace("-----END CERTIFICATE-----", string.Empty)
.Trim();
return Convert.FromBase64String(base64Cert);
}
De Startup.Configure
methode voegt vervolgens de middleware toe.
UseCertificateForwarding
wordt aangeroepen vóór de aanroepen naar UseAuthentication
en UseAuthorization
:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseRouting();
app.UseCertificateForwarding();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Een afzonderlijke klasse kan worden gebruikt om validatielogica te implementeren. Omdat hetzelfde zelfondertekende certificaat in dit voorbeeld wordt gebruikt, moet u ervoor zorgen dat alleen uw certificaat kan worden gebruikt. Controleer of de vingerafdrukken van zowel het clientcertificaat als het servercertificaat overeenkomen, anders kan elk certificaat worden gebruikt en is voldoende om te verifiëren. Dit wordt gebruikt in de AddCertificate
methode. U kunt hier ook het onderwerp of de uitgever valideren als u intermediaire of subcertificaten gebruikt.
using System.IO;
using System.Security.Cryptography.X509Certificates;
namespace AspNetCoreCertificateAuthApi
{
public class MyCertificateValidationService
{
public bool ValidateCertificate(X509Certificate2 clientCertificate)
{
// Do not hardcode passwords in production code
// Use thumbprint or key vault
var cert = new X509Certificate2(
Path.Combine("sts_dev_cert.pfx"), "1234");
if (clientCertificate.Thumbprint == cert.Thumbprint)
{
return true;
}
return false;
}
}
}
Een HttpClient implementeren met behulp van een certificaat en de HttpClientHandler
De HttpClientHandler
kan rechtstreeks in de constructor van de HttpClient
klasse worden toegevoegd. Er moet voorzichtig te werk worden gegaan bij het maken van exemplaren van de HttpClient
. Het HttpClient
certificaat wordt vervolgens met elke aanvraag verzonden.
private async Task<JsonDocument> GetApiDataUsingHttpClientHandler()
{
var cert = new X509Certificate2(Path.Combine(_environment.ContentRootPath, "sts_dev_cert.pfx"), "1234");
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(cert);
var client = new HttpClient(handler);
var request = new HttpRequestMessage()
{
RequestUri = new Uri("https://localhost:44379/api/values"),
Method = HttpMethod.Get,
};
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
var data = JsonDocument.Parse(responseContent);
return data;
}
throw new ApplicationException($"Status code: {response.StatusCode}, Error: {response.ReasonPhrase}");
}
Een HttpClient implementeren met behulp van een certificaat en een httpclient met de naam IHttpClientFactory
In het volgende voorbeeld wordt een clientcertificaat aan een HttpClientHandler
toegevoegd met behulp van de ClientCertificates
eigenschap van de handler. Deze handler kan vervolgens worden gebruikt in een benoemd exemplaar van een HttpClient
met behulp van de ConfigurePrimaryHttpMessageHandler methode. Dit is ingesteld in Startup.ConfigureServices
:
var clientCertificate =
new X509Certificate2(
Path.Combine(_environment.ContentRootPath, "sts_dev_cert.pfx"), "1234");
services.AddHttpClient("namedClient", c =>
{
}).ConfigurePrimaryHttpMessageHandler(() =>
{
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(clientCertificate);
return handler;
});
De IHttpClientFactory
kan vervolgens worden gebruikt om het benoemde exemplaar op te halen met behulp van de handler en het certificaat. De CreateClient
methode met de naam van de client die in de Startup
klasse is gedefinieerd, wordt gebruikt om het exemplaar op te halen. De HTTP-aanvraag kan naar behoefte worden verzonden met behulp van de client.
private readonly IHttpClientFactory _clientFactory;
public ApiService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
private async Task<JsonDocument> GetApiDataWithNamedClient()
{
var client = _clientFactory.CreateClient("namedClient");
var request = new HttpRequestMessage()
{
RequestUri = new Uri("https://localhost:44379/api/values"),
Method = HttpMethod.Get,
};
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
var data = JsonDocument.Parse(responseContent);
return data;
}
throw new ApplicationException($"Status code: {response.StatusCode}, Error: {response.ReasonPhrase}");
}
Als het juiste certificaat naar de server wordt verzonden, worden de gegevens geretourneerd. Als er geen certificaat of het verkeerde certificaat wordt verzonden, wordt een HTTP 403-statuscode geretourneerd.
Certificaten maken in PowerShell
Het maken van de certificaten is het moeilijkste onderdeel van het instellen van deze stroom. U kunt een basiscertificaat maken met behulp van de New-SelfSignedCertificate
PowerShell-cmdlet. Wanneer u het certificaat maakt, gebruikt u een sterk wachtwoord. Het is belangrijk om de KeyUsageProperty
parameter en de KeyUsage
parameter toe te voegen, zoals wordt weergegeven.
Basis-CA maken
New-SelfSignedCertificate -DnsName "root_ca_dev_damienbod.com", "root_ca_dev_damienbod.com" -CertStoreLocation "cert:\LocalMachine\My" -NotAfter (Get-Date).AddYears(20) -FriendlyName "root_ca_dev_damienbod.com" -KeyUsageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature
$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText
Get-ChildItem -Path cert:\localMachine\my\"The thumbprint..." | Export-PfxCertificate -FilePath C:\git\root_ca_dev_damienbod.pfx -Password $mypwd
Export-Certificate -Cert cert:\localMachine\my\"The thumbprint..." -FilePath root_ca_dev_damienbod.crt
Opmerking
De -DnsName
parameterwaarde moet overeenkomen met het implementatiedoel van de app. Bijvoorbeeld 'localhost' voor ontwikkeling.
Installeren in de vertrouwde basis
Het rootcertificaat moet op uw hostsysteem vertrouwd worden. Een basiscertificaat dat niet is gemaakt door een certificeringsinstantie, wordt niet standaard vertrouwd. Zie deze vraag voor informatie over het vertrouwen van het basiscertificaat in Windows.
Tussenliggend certificaat
Er kan nu een tussenliggend certificaat worden gemaakt op basis van het basiscertificaat. Dit is niet vereist voor alle use cases, maar mogelijk moet u veel certificaten maken of groepen certificaten activeren of uitschakelen. De TextExtension
parameter is vereist om de padlengte in te stellen in de basisbeperkingen van het certificaat.
Het tussenliggende certificaat kan vervolgens worden toegevoegd aan het vertrouwde tussenliggende certificaat in het Windows-hostsysteem.
$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText
$parentcert = ( Get-ChildItem -Path cert:\LocalMachine\My\"The thumbprint of the root..." )
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "intermediate_dev_damienbod.com" -Signer $parentcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "intermediate_dev_damienbod.com" -KeyUsageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature -TextExtension @("2.5.29.19={text}CA=1&pathlength=1")
Get-ChildItem -Path cert:\localMachine\my\"The thumbprint..." | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\intermediate_dev_damienbod.pfx -Password $mypwd
Export-Certificate -Cert cert:\localMachine\my\"The thumbprint..." -FilePath intermediate_dev_damienbod.crt
Een onderliggend certificaat maken op basis van een tussenliggend certificaat
Er kan een kindercertificaat worden gemaakt op basis van het tussenliggende certificaat. Dit is de eindentiteit die geen kindcertificaten hoeft te maken.
$parentcert = ( Get-ChildItem -Path cert:\LocalMachine\My\"The thumbprint from the Intermediate certificate..." )
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "child_a_dev_damienbod.com" -Signer $parentcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "child_a_dev_damienbod.com"
$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText
Get-ChildItem -Path cert:\localMachine\my\"The thumbprint..." | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\child_a_dev_damienbod.pfx -Password $mypwd
Export-Certificate -Cert cert:\localMachine\my\"The thumbprint..." -FilePath child_a_dev_damienbod.crt
Kindcertificaat maken van rootcertificaat
Een kindcertificaat kan ook rechtstreeks vanuit het basiscertificaat worden gemaakt.
$rootcert = ( Get-ChildItem -Path cert:\LocalMachine\My\"The thumbprint from the root cert..." )
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "child_a_dev_damienbod.com" -Signer $rootcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "child_a_dev_damienbod.com"
$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText
Get-ChildItem -Path cert:\localMachine\my\"The thumbprint..." | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\child_a_dev_damienbod.pfx -Password $mypwd
Export-Certificate -Cert cert:\localMachine\my\"The thumbprint..." -FilePath child_a_dev_damienbod.crt
Voorbeeld rootcertificaat - tussenliggend certificaat - certificaat
$mypwdroot = ConvertTo-SecureString -String "1234" -Force -AsPlainText
$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText
New-SelfSignedCertificate -DnsName "root_ca_dev_damienbod.com", "root_ca_dev_damienbod.com" -CertStoreLocation "cert:\LocalMachine\My" -NotAfter (Get-Date).AddYears(20) -FriendlyName "root_ca_dev_damienbod.com" -KeyUsageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature
Get-ChildItem -Path cert:\localMachine\my\0C89639E4E2998A93E423F919B36D4009A0F9991 | Export-PfxCertificate -FilePath C:\git\root_ca_dev_damienbod.pfx -Password $mypwdroot
Export-Certificate -Cert cert:\localMachine\my\0C89639E4E2998A93E423F919B36D4009A0F9991 -FilePath root_ca_dev_damienbod.crt
$rootcert = ( Get-ChildItem -Path cert:\LocalMachine\My\0C89639E4E2998A93E423F919B36D4009A0F9991 )
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "child_a_dev_damienbod.com" -Signer $rootcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "child_a_dev_damienbod.com" -KeyUsageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature -TextExtension @("2.5.29.19={text}CA=1&pathlength=1")
Get-ChildItem -Path cert:\localMachine\my\BA9BF91ED35538A01375EFC212A2F46104B33A44 | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\child_a_dev_damienbod.pfx -Password $mypwd
Export-Certificate -Cert cert:\localMachine\my\BA9BF91ED35538A01375EFC212A2F46104B33A44 -FilePath child_a_dev_damienbod.crt
$parentcert = ( Get-ChildItem -Path cert:\LocalMachine\My\BA9BF91ED35538A01375EFC212A2F46104B33A44 )
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "child_b_from_a_dev_damienbod.com" -Signer $parentcert -NotAfter (Get-Date).AddYears(20) -FriendlyName "child_b_from_a_dev_damienbod.com"
Get-ChildItem -Path cert:\localMachine\my\141594A0AE38CBBECED7AF680F7945CD51D8F28A | Export-PfxCertificate -FilePath C:\git\AspNetCoreCertificateAuth\Certs\child_b_from_a_dev_damienbod.pfx -Password $mypwd
Export-Certificate -Cert cert:\localMachine\my\141594A0AE38CBBECED7AF680F7945CD51D8F28A -FilePath child_b_from_a_dev_damienbod.crt
Wanneer u de basis-, tussen- of onderliggende certificaten gebruikt, kunnen de certificaten naar behoefte worden gevalideerd met behulp van de vingerafdruk of PublicKey.
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography.X509Certificates;
namespace AspNetCoreCertificateAuthApi
{
public class MyCertificateValidationService
{
public bool ValidateCertificate(X509Certificate2 clientCertificate)
{
return CheckIfThumbprintIsValid(clientCertificate);
}
private bool CheckIfThumbprintIsValid(X509Certificate2 clientCertificate)
{
var listOfValidThumbprints = new List<string>
{
"141594A0AE38CBBECED7AF680F7945CD51D8F28A",
"0C89639E4E2998A93E423F919B36D4009A0F9991",
"BA9BF91ED35538A01375EFC212A2F46104B33A44"
};
if (listOfValidThumbprints.Contains(clientCertificate.Thumbprint))
{
return true;
}
return false;
}
}
}
Optionele clientcertificaten
Deze sectie bevat informatie voor apps die een subset van de app met een certificaat moeten beveiligen. Voor een Razor pagina of controller in de app zijn bijvoorbeeld clientcertificaten vereist. Dit brengt uitdagingen met zich mee als clientcertificaten:
- Zijn een TLS-functie, geen HTTP-functie.
- Wordt onderhandeld per verbinding en meestal aan het begin van de verbinding voordat er HTTP-gegevens beschikbaar zijn.
Er zijn twee benaderingen voor het implementeren van optionele clientcertificaten:
- Gebruik afzonderlijke hostnamen (SNI) en doorsturen. Hoewel er meer werk nodig is om te configureren, wordt dit aanbevolen omdat dit werkt in de meeste omgevingen en protocollen.
- Heronderhandeling tijdens een HTTP-aanvraag. Dit heeft verschillende beperkingen en wordt niet aanbevolen.
Afzonderlijke hosts (SNI)
Aan het begin van de verbinding is alleen de SNI-† (Server Name Indication) bekend. Clientcertificaten kunnen per hostnaam worden geconfigureerd, zodat de ene host deze vereist en een andere niet.
- Binding instellen voor het domein en subdomein:
- Stel bijvoorbeeld bindingen in op
contoso.com
enmyClient.contoso.com
. Voor decontoso.com
host is geen clientcertificaat vereist, maarmyClient.contoso.com
wel. - Zie voor meer informatie:
-
Kestrel webserver in ASP.NET Core:
- ListenOptions.UseHttps
- ClientCertificateMode
- Opmerking Kestrel biedt momenteel geen ondersteuning voor meerdere TLS-configuraties voor één binding. U hebt twee bindingen met unieke IP-adressen of poorten nodig. Zie dit GitHub-probleem voor meer informatie.
- IIS
- HTTP.sys: Windows Server configureren
-
Kestrel webserver in ASP.NET Core:
- Stel bijvoorbeeld bindingen in op
.NET 5 of hoger voegt handigere ondersteuning toe voor het omleiden om optionele clientcertificaten te verkrijgen. Zie het voorbeeld van optionele certificaten voor meer informatie.
- Voor aanvragen voor de web-app waarvoor een clientcertificaat vereist is maar niet aanwezig is:
- Omleiden naar dezelfde pagina met behulp van het met het clientcertificaat beveiligde subdomein.
- Bijvoorbeeld omleiden naar
myClient.contoso.com/requestedPage
. Omdat de aanvraag naarmyClient.contoso.com/requestedPage
een andere hostnaam heeft dancontoso.com/requestedPage
, brengt de client een andere verbinding tot stand en wordt het clientcertificaat verstrekt. - Zie Inleiding tot autorisatie in ASP.NET Core voor meer informatie.
† Server Name Indication (SNI) is een TLS-extensie om een virtueel domein op te nemen als onderdeel van SSL-onderhandeling. Dit betekent dat de virtuele domeinnaam of een hostnaam effectief kan worden gebruikt om het netwerkeindpunt te identificeren.
Heronderhandeling
TLS-heronderhandeling is een proces waarmee de client en server de versleutelingsvereisten voor een afzonderlijke verbinding opnieuw kunnen beoordelen, inclusief het aanvragen van een clientcertificaat als dat nog niet eerder is opgegeven. TLS-heronderhandeling is een beveiligingsrisico en wordt niet aanbevolen omdat:
- In HTTP/1.1 moet de server eerst HTTP-gegevens bufferen of verwerken die in de doorgang zijn, zoals POST-verzoeklichamen om ervoor te zorgen dat de verbinding vrij is voor de heronderhandeling. Anders kan de heronderhandeling stoppen met reageren of mislukken.
- HTTP/2 en HTTP/3 verbieden expliciet heronderhandeling.
- Er zijn beveiligingsrisico's verbonden aan heronderhandeling. TLS 1.3 heeft de heronderhandeling van de hele verbinding verwijderd en vervangen door een nieuwe extensie voor alleen het aanvragen van een clientcertificaat nadat de verbinding is gestart. Dit mechanisme wordt weergegeven via dezelfde API's en is nog steeds onderhevig aan de eerdere beperkingen van buffering en HTTP-protocolversies.
De implementatie en configuratie van deze functie variëren per server- en frameworkversie.
IIS
IIS beheert namens u de onderhandeling van het clientcertificaat. Een subsectie van de toepassing kan de SslRequireCert
optie inschakelen om te onderhandelen over het clientcertificaat voor deze aanvragen. Zie Configuratie in de IIS-documentatie voor meer informatie.
IIS buffert automatisch eventuele aanvraagbodygegevens tot een geconfigureerde groottelimiet voordat opnieuw wordt onderhandeld. Aanvragen die de limiet overschrijden, worden geweigerd met een 413-antwoord. Deze limiet is standaard ingesteld op 48 kB en kan worden geconfigureerd door de uploadReadAheadSize in te stellen.
HttpSys
HttpSys heeft twee instellingen die de onderhandeling van het clientcertificaat regelen en beide moeten worden ingesteld. De eerste bevindt zich in netsh.exe onder http add sslcert clientcertnegotiation=enable/disable
. Deze vlag geeft aan of het clientcertificaat moet worden onderhandeld aan het begin van een verbinding en moet worden ingesteld disable
op voor optionele clientcertificaten. Zie de netsh-documenten voor meer informatie.
De andere instelling is ClientCertificateMethod. Wanneer ingesteld op AllowRenegotation
, kan het clientcertificaat tijdens een aanvraag opnieuw worden onderhandeld.
NOTITIE De toepassing moet de hoofdtekstgegevens van de aanvraag bufferen of gebruiken voordat de heronderhandeling wordt uitgevoerd, anders reageert de aanvraag mogelijk niet meer.
Er is een bekend probleem waarbij het inschakelen AllowRenegotation
ervoor kan zorgen dat de heronderhandeling synchroon plaatsvindt bij het openen van de ClientCertificate eigenschap. Roep de GetClientCertificateAsync methode aan om dit te voorkomen. Dit is opgelost in .NET 6. Zie dit GitHub-probleem voor meer informatie. Opmerking GetClientCertificateAsync
kan een null-certificaat retourneren als de client weigert er een op te geven.
Kestrel
Kestrel beheert onderhandeling van clientcertificaten met de ClientCertificateMode optie.
Voor .NET 5 of eerder Kestrel biedt geen ondersteuning voor opnieuw onderhandelen na het starten van een verbinding om een clientcertificaat te verkrijgen. Deze functie is toegevoegd in .NET 6.
Laat vragen, opmerkingen en andere feedback over optionele clientcertificaten in dit GitHub-discussieprobleem achter.