Konfigurieren der Zertifikatauthentifizierung in ASP.NET Core

Microsoft.AspNetCore.Authentication.Certificate enthält eine Implementierung ähnlich der Zertifikatauthentifizierung für ASP.NET Core. Die Zertifikatauthentifizierung erfolgt auf TLS-Ebene, bevor sie zu ASP.NET Core wird. Genauer gesagt ist diese Funktionalität ein Authentifizierungshandler, der das Zertifikat überprüft und Ihnen dann ein Ereignis liefert, mit dem Sie dieses Zertifikat in ein ClaimsPrincipal auflösen können.

Sie müssenIhren Server so konfigurieren, dass die Zertifikatauthentifizierung mit IIS, Kestrel, Azure-Web-Apps oder Ihrer bevorzugten Lösung erfolgt.

In diesem Artikel wird beschrieben, wie Sie die Zertifikatauthentifizierung in ASP.NET Core für IIS und HTTP.sys konfigurieren und Beispiele zum Aufrufen verschiedener Methoden sowie zur Verwendung von Eigenschaften bereitstellen.

Überprüfen von Proxy- und Lastenausgleichsszenarien

Die Zertifikatauthentifizierung ist ein zustandsbehaftetes Szenario, das hauptsächlich verwendet wird, in dem der Datenverkehr zwischen Clients und Servern nicht durch einen Proxy oder Load Balancer verarbeitet wird. Wenn ein Proxy oder Lastenausgleich verwendet wird, funktioniert die Zertifikatauthentifizierung nur, wenn der Proxy oder der Lastenausgleich bestimmte Bedingungen erfüllt:

  • Verantwortlich für die Authentifizierung
  • Die Authentifizierungsinformationen des Benutzers werden an die App übermittelt (z. B. in einem Anforderungsheader), die auf die Authentifizierungsinformationen zugreift.

Eine Alternative zur Zertifikatauthentifizierung in Umgebungen, in denen Proxys und Lastenausgleichsmodule verwendet werden, bildet Active Directory-Verbunddienste (AD FS) mit OpenID Connect (OIDC).

Erste Schritte

Rufen Sie ein HTTPS-Zertifikat ab, wenden Sie es an, und konfigurieren Sie Ihren Server so, dass Zertifikate erforderlich sind.

In der Web-App:

  • Fügen Sie einen Verweis auf das NuGet-Paket Microsoft.AspNetCore.Authentication.Certificate hinzu.

  • Rufen Sie in der datei Program.cs die builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...); Methode auf. Stellen Sie einen Delegaten für den OnCertificateValidated-Ereignishandler bereit, um zusätzliche Validierungen zum Clientzertifikats abzuschließen, die mit Anforderungen gesendet werden. Wandeln Sie diese Informationen in einen ClaimsPrincipal Wert um, und legen Sie sie für die context.Principal Eigenschaft fest.

Wenn die Authentifizierung fehlschlägt, gibt dieser Handler eine 403 (Forbidden)-Antwort zurück, anstatt einer 401 (Unauthorized)-Antwort, wie Sie vielleicht erwarten. Der Handler gibt eine andere Antwort zurück, da erwartet wird, dass die Authentifizierung während der anfänglichen TLS-Verbindung erfolgt. Wenn der Handler erreicht wird, ist es zu spät. Es gibt keine Möglichkeit, die Verbindung von einer anonymen Verbindung in eine Verbindung mit einem Zertifikat zu ändern.

Die Methode UseAuthentication ist erforderlich, um HttpContext.User auf einen Wert festzulegenClaimsPrincipal, der aus dem Zertifikat erstellt wurde. Beispiel:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(
        CertificateAuthenticationDefaults.AuthenticationScheme)
    .AddCertificate();

var app = builder.Build();

app.UseAuthentication();

app.MapGet("/", () => "Hello World!");

app.Run();

Im obigen Beispiel wird das Standardverfahren zum Hinzufügen der Zertifikatauthentifizierung veranschaulicht. Der Handler erstellt mithilfe der allgemeinen Zertifikateigenschaften einen Benutzerprinzipal.

Konfigurieren der Zertifikatüberprüfung

Der CertificateAuthenticationOptions-Handler verfügt über einige integrierte Validierungen. Dabei handelt es such um die mindestens erforderlichen Überprüfungen, die Sie für ein Zertifikat ausführen sollten. Jede dieser Einstellungen ist standardmäßig aktiviert. In den folgenden Abschnitten wird beschrieben, wie Sie mit den Einstellungen arbeiten.

AllowedCertificateTypes = Chained, SelfSigned oder All (Chained | SelfSigned)

Standardwert: CertificateTypes.Chained

Bei dieser Überprüfung wird getestet, ob nur der entsprechende Zertifikattyp zulässig ist. Wenn die App selbstsignierte Zertifikate verwendet, muss diese Option auf CertificateTypes.All oder CertificateTypes.SelfSigned festgelegt werden.

ChainTrustValidationMode

Standardwert: X509ChainTrustMode.System

Das vom Client angegebene Zertifikat muss mit einem vertrauenswürdigen Stammzertifikat verkettet sein. Diese Überprüfung kontrolliert, welcher Vertrauensspeicher diese Stammzertifikate enthält.

Standardmäßig verwendet der Handler den Systemvertrauensspeicher. Wenn das angezeigte Clientzertifikat mit einem Stammzertifikat verkettet werden muss, das nicht im Systemvertrauensspeicher angezeigt wird, können Sie die Option auf X509ChainTrustMode.CustomRootTrust festlegen, damit der Handler die CustomTrustStore Eigenschaft verwendet.

CustomTrustStore

Standardwert: Leer X509Certificate2Collection

Wenn die ChainTrustValidationMode-Eigenschaft des Handlers auf X509ChainTrustMode.CustomRootTrust festgelegt ist, enthält dieses X509Certificate2Collection-Objekt jedes Zertifikat, das zur Validierung des Clientzertifikats bis zu einem vertrauenswürdigen Stamm verwendet wird, einschließlich des vertrauenswürdigen Stamms.

Wenn der Client ein Zertifikat vorlegt, das Teil einer mehrstufigen Zertifikatkette ist, muss die Eigenschaft CustomTrustStore alle ausstellenden Zertifikate in der Kette enthalten.

ValidateCertificateUse

Standardwert: true

Bei dieser Überprüfung wird getestet, ob das vom Client bereitgestellte Zertifikat über die erweiterte Schlüsselverwendung (EKU) der Clientauthentifizierung oder überhaupt keine EKUs verfügt. Wenn keine EKU angegeben wird, gelten laut Spezifikationen alle EKUs als gültig.

Gültigkeitszeitraum überprüfen

Standardwert: true

Bei dieser Überprüfung wird getestet, ob das Zertifikat innerhalb seines Gültigkeitszeitraums liegt. Bei jeder Anforderung stellt der Handler sicher, dass ein Zertifikat, das bei der Präsentation gültig war, während der aktuellen Sitzung nicht abgelaufen ist.

RevocationFlag

Standardwert: X509RevocationFlag.ExcludeRoot

Dieses Flag gibt an, welche Zertifikate in der Kette auf Widerruf geprüft werden sollen.

Sperrprüfungen werden nur ausgeführt, wenn das Zertifikat mit einem Stammzertifikat verkettet ist.

RevocationMode

Standardwert: X509RevocationMode.Online

Dieses Flag legt fest, wie Widerrufsprüfungen durchgeführt werden.

Die Anforderung einer Onlineüberprüfung kann zu einer langen Verzögerung führen, während die Zertifizierungsstelle kontaktiert wird.

Sperrüberprüfungen werden nur ausgeführt, wenn das Zertifikat mit einem Stammzertifikat verkettet wird.

Häufig gestellte Fragen: Kann ich meine App so konfigurieren, dass nur für bestimmte Pfade ein Zertifikat erforderlich ist?

Dieser Ansatz ist nicht möglich. Der Zertifikataustausch wird zu Beginn der HTTPS-Verbindung abgeschlossen. Der Vorgang erfolgt vom Server, bevor die erste Anforderung für diese Verbindung empfangen wird. Daher ist es nicht möglich, den Bereich anhand von Anforderungsfeldern einzugrenzen.

Prozesshandlerereignisse

Der Handler weist zwei Ereignisse auf:

  • OnAuthenticationFailed wird aufgerufen, wenn während der Authentifizierung eine Ausnahme auftritt und Sie reagieren können.

  • OnCertificateValidated: Wird aufgerufen, nachdem das Zertifikat validiert wurde, die Validierung bestanden hat und ein Standard-Principal erstellt wurde. Mit diesem Ereignis können Sie Ihre eigene Validierung durchführen und den Prinzipal erweitern oder ersetzen. Beispiele sind:

    • Ermitteln, ob das Zertifikat Ihrem Diensten bekannt ist

    • Erstellen Sie einen eigenen Principal, wie im folgenden Beispiel:

      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;
                  }
              };
          });
      

Wenn das eingehende Zertifikat Ihre zusätzliche Überprüfung nicht erfüllt, rufen Sie context.Fail("failure reason") mit einem Fehlergrund auf.

Rufen Sie zur Verbesserung der Funktionalität einen Dienst auf, der bei der Abhängigkeitsinjektion registriert ist und eine Verbindung mit einer Datenbank oder einem anderen Benutzerspeicher herstellt. Greifen Sie auf den Dienst zu, indem Sie den an den Delegaten übergebenen Kontext verwenden. Betrachten Sie das folgende Beispiel:

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;
            }
        };
    });

Konzeptionell ist die Validierung des Zertifikats ein Autorisierungsschritt. Sie können z. B. eine Überprüfung eines Ausstellers oder Fingerabdrucks in einer Autorisierungsrichtlinie hinzufügen statt innerhalb des OnCertificateValidated Handlers.

Konfigurieren Ihres Servers für die Erzwingung von Zertifikaten

In den folgenden Abschnitten wird beschrieben, wie Sie Ihren Server so konfigurieren, dass Zertifikate für eine bestimmte Lösung erforderlich sind, einschließlich Kestrel, IIS, Azure, benutzerdefinierte Webproxys und Azure Web-Apps.

Kestrel

Konfigurieren Sie in der Kestrel wie folgt:

var builder = WebApplication.CreateBuilder(args);

builder.Services.Configure<KestrelServerOptions>(options =>
{
    options.ConfigureHttpsDefaults(options =>
        options.ClientCertificateMode = ClientCertificateMode.RequireCertificate);
});

Hinweis

Wenn ein Endpunkt durch Aufrufen der Listen Methode erstellt wird, bevor die ConfigureHttpsDefaults Methode aufgerufen wird, hat der Endpunkt nicht die Standardeinstellungen angewendet.

IIS

Führen Sie im IIS-Manager die folgenden Schritte aus:

  1. Wählen Sie auf der Registerkarte "Verbindungen " Ihre Website aus.
  2. Doppelklicken Sie im Fenster "Featuresansicht " auf "SSL-Einstellungen".
  3. Aktivieren Sie das Kontrollkästchen SSL erforderlich .
  4. Wählen Sie für die Option "Clientzertifikate" die Option "Erforderlich" aus.

Screenshot, der zeigt, wie Die Clientzertifikateinstellungen in IIS konfiguriert werden.

Azure und benutzerdefinierte Webproxys

Weitere Informationen zum Konfigurieren der Middleware für die Zertifikatweiterleitung finden Sie in der Host- und Bereitstellungsdokumentation.

Zertifikatauthentifizierung in Azure Web-Apps

Für Azure ist keine Weiterleitungskonfiguration erforderlich. Die Middleware für die Zertifikatweiterleitung richtet die Konfiguration ein.

Hinweis

Für dieses Szenario ist Middleware für die Zertifikatweiterleitung erforderlich.

Weitere Informationen finden Sie unter TLS/SSL-Zertifikate in Ihrem App-Code verwenden (Azure-Dokumentation).

Zertifikatauthentifizierung in benutzerdefinierten Webproxys

Mit der AddCertificateForwarding-Methode wird Folgendes angegeben:

  • Der Client-Header-Name
  • So laden Sie das Zertifikat (über die HeaderConverter-Eigenschaft).

Bei benutzerdefinierten Webproxys wird das Zertifikat als benutzerdefinierter Anforderungsheader übergeben, z. B. X-SSL-CERT. Um das Zertifikat zu verwenden, konfigurieren Sie die Zertifikatweiterleitung in der Program.cs Datei:

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;
        }
    };
});

Wenn NGINX mit der Konfiguration proxy_set_header ssl-client-cert $ssl_client_escaped_cert zum Reverseproxy der App verwendet wird oder die App mithilfe von NGINX Ingress auf Kubernetes bereitgestellt wird, wird das Clientzertifikat in URL-codierter Form an die App übergeben. Um das Zertifikat zu verwenden, decodieren Sie es wie folgt:

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!;
    };
});

Fügen Sie die Middleware in der Program.cs Datei hinzu. Die UseCertificateForwarding Methode wird aufgerufen, bevor die UseAuthentication und UseAuthorization Methoden aufgerufen werden.

var app = builder.Build();

app.UseCertificateForwarding();

app.UseAuthentication();
app.UseAuthorization();

Mithilfe einer separaten Klasse können Sie Validierungslogik implementieren. Da in diesem Beispiel dasselbe selbstsignierte Zertifikat verwendet wird, müssen Sie sicherstellen, dass nur Ihr Zertifikat verwendet werden kann. Überprüfen Sie, ob die Fingerabdrucke des Clientzertifikats und des Serverzertifikats übereinstimmen. Andernfalls kann jedes Zertifikat verwendet und für die Authentifizierung ausreichend sein. Das Zertifikat wird dann innerhalb der AddCertificate Methode verwendet. Sie können das Subjekt oder den Aussteller ebenfalls hier überprüfen, wenn Sie Zwischen- oder untergeordnete Zertifikate verwenden.

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;
    }
}

Implementieren Sie einen HttpClient mit einem Zertifikat und IHttpClientFactory

Im folgenden Beispiel wird dem HttpClientHandler ein Clientzertifikat hinzugefügt. Dazu wird die ClientCertificates-Eigenschaft aus dem Handler verwendet. Dieser Handler kann dann in einer benannten Instanz eines HttpClient mit der ConfigurePrimaryHttpMessageHandler-Methode verwendet werden. Dieses Szenario ist in der datei Program.cs konfiguriert:

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;
});

Die IHttpClientFactory kann dann verwendet werden, um die benannte Instanz mit dem Handler und dem Zertifikat abzurufen. Die CreateClient Methode mit dem Namen des Clients, der in der datei Program.cs definiert ist, wird verwendet, um die Instanz abzurufen. Die HTTP-Anforderung kann nach Bedarf mithilfe des Clients gesendet werden:

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}");
    }
}

Wenn das richtige Zertifikat an den Server gesendet wird, werden die Daten zurückgegeben. Wenn kein Zertifikat oder das falsche Zertifikat gesendet wird, gibt der Server einen HTTP 403-Statuscode zurück.

Zertifikate in PowerShell

Das Erstellen der Zertifikate ist der schwierigste Teil beim Einrichten dieses Flows. Sie können ein Stammzertifikat mithilfe des New-SelfSignedCertificate PowerShell-Cmdlets erstellen. Verwenden Sie beim Erstellen des Zertifikats ein sicheres Kennwort. Es ist wichtig, den KeyUsageProperty Parameter und den KeyUsage Parameter wie in den Beispielen dargestellt hinzuzufügen.

Erstellen einer Stammzertifizierungsstelle

Der folgende Code zeigt, wie Sie eine Zertifizierungsstelle (CA) im Root erstellen:

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

Hinweis

Der Wert des -DnsName-Parameters muss mit dem Bereitstellungsziel der App übereinstimmen. Für die Entwicklung wird beispielsweise „localhost“ verwendet.

Installieren im vertrauenswürdigen Stamm

Das Stammzertifikat muss auf Ihrem Hostsystem als vertrauenswürdig gelten. Standardmäßig werden nur von einer Zertifizierungsstelle erstellte Stammzertifikate als vertrauenswürdig eingestuft. Informationen darüber, wie man dem Stammzertifikat unter Windows vertraut, finden Sie in der Windows-Dokumentation oder dem Import-Certificate PowerShell-Cmdlet.

Verwenden eines Zwischenzertifikats

Aus dem Stammzertifikat kann nun ein Zwischenzertifikat erstellt werden. Dieser Ansatz ist nicht für alle Anwendungsfälle erforderlich, aber Möglicherweise müssen Sie viele Zertifikate erstellen oder Gruppen von Zertifikaten aktivieren oder deaktivieren. Der TextExtension-Parameter ist erforderlich, um die Pfadlänge in den grundlegenden Einschränkungen des Zertifikats festzulegen.

Das Zwischenzertifikat kann dann dem vertrauenswürdigen Zwischenzertifikat im Windows-Hostsystem hinzugefügt werden.

$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

Erstellen eines untergeordneten Zertifikats aus einem Zwischenzertifikat

Aus dem Zwischenzertifikat kann ein Kindzertifikat erstellt werden. Dieses untergeordnete Zertifikat ist die Endentität. Sie müssen keine weiteren untergeordneten Zertifikate erstellen.

$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

Erstellen eines Tochterzertifikats aus einem Stammzertifikat

Ein Kinderzertifikat kann auch direkt aus dem Stammzertifikat erstellt werden.

$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

Beispiel: Stamm - Zwischenzertifikat - Zertifikat

Das folgende Beispiel zeigt die Konfiguration der Stammzertifizierungsstelle, des Zwischenzertifikats und des untergeordneten Zertifikats:

$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

Wenn Sie die Stamm-, Zwischen- oder untergeordneten Zertifikate verwenden, können die Zertifikate nach Bedarf mithilfe des Fingerabdrucks oder des PublicKey überprüft werden:

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);
}

Ergebnisse der Cachezertifikatüberprüfung

.NET 5 und neueren Versionen unterstützen die Möglichkeit, das Zwischenspeichern von Validierungsergebnissen zu ermöglichen. Durch die Zwischenspeicherung wird die Leistung der Zertifikatauthentifizierung erheblich verbessert, da die Überprüfung ein teurer Vorgang ist.

Standardmäßig deaktiviert die Zertifikatauthentifizierung die Zwischenspeicherung. Rufen Sie zum Aktivieren der Zwischenspeicherung die AddCertificateCache Methode in der datei Program.cs auf:

builder.Services.AddAuthentication(
        CertificateAuthenticationDefaults.AuthenticationScheme)
    .AddCertificate()
    .AddCertificateCache(options =>
    {
        options.CacheSize = 1024;
        options.CacheEntryExpiration = TimeSpan.FromMinutes(2);
    });

Bei der Standardimplementierung der Zwischenspeicherung werden die Ergebnisse im Arbeitsspeicher gespeichert. Sie können einen eigenen Cache bereitstellen, indem Sie ICertificateValidationCache implementieren und bei der Abhängigkeitsinjektion registrieren. Beispiel: services.AddSingleton<ICertificateValidationCache, YourCache>().

Verwenden optionaler Clientzertifikate

Dieser Abschnitt enthält Informationen zu Apps, die einen Teil der App mit einem Zertifikat schützen müssen. Für eine Razor-Seite oder einen Controller in der App können beispielsweise Clientzertifikate erforderlich sein. Dieses Szenario stellt einige Herausforderungen dar:

  • Clientzertifikate sind ein TLS-Feature, kein HTTP-Feature.
  • Clientzertifikate werden pro Verbindung und in der Regel am Anfang der Verbindung ausgehandelt, bevor HTTP-Daten verfügbar sind.

Es gibt zwei Ansätze für die Implementierung optionaler Clientzertifikate:

  • Option 1: Verwenden Sie separate Hostnamen (SNI) und Umleitung. Während diese Option mehr Arbeit zum Konfigurieren erfordert, wird der Ansatz empfohlen, da er in den meisten Umgebungen und Protokollen funktioniert.
  • Option 2: Neuverhandlung während einer HTTP-Anforderung. Dieser Ansatz hat mehrere Einschränkungen und wird nicht empfohlen.

Separate Hosts (SNI)

Zu Beginn der Verbindung ist nur die Servernamensanzeige (Server Name Indication, SNI†) bekannt. Clientzertifikate können pro Hostname konfiguriert werden, sodass ein Host die Zertifikate erfordert und ein anderer nicht.

† Server Name Indication (SNI) ist eine TLS-Erweiterung, die verwendet wird, um eine virtuelle Domäne als Teil der SSL-Aushandlung einzuschließen. Dieser Ansatz bedeutet effektiv, dass der virtuelle Domänenname oder ein Hostname verwendet werden kann, um den Netzwerkendpunkt zu identifizieren.

TLS-Neuverhandlung

DIE TLS-Neuverhandlung ist ein Prozess, mit dem der Client und der Server die Verschlüsselungsanforderungen für eine einzelne Verbindung neu bewerten können, einschließlich des Anforderns eines Clientzertifikats, falls noch nicht angegeben. Die erneute TLS-Aushandlung stellt ein Sicherheitsrisiko dar und wird aus folgenden Gründen nicht empfohlen:

  • In HTTP/1.1 muss der Server zunächst alle aktiven HTTP-Daten puffern oder verarbeiten, wie z. B. POST-Anforderungstexte, um sicherzustellen, dass die Verbindung für die Neuverhandlung frei ist. Andernfalls kann die Neuverhandlung nicht mehr reagieren oder fehlschlagen.
  • HTTP/2 und HTTP/3 verbieten explizit die erneute Aushandlung.
  • Die erneute Aushandlung ist mit Sicherheitsrisiken verbunden. In TLS 1.3 wurde die erneute Aushandlung für die gesamte Verbindung entfernt und durch eine neue Erweiterung für das Anfordern ausschließlich des Clientzertifikats nach dem Verbindungsstart ersetzt. Dieser Mechanismus wird über dieselben APIs verfügbar gemacht und unterliegt weiterhin den vorherigen Einschränkungen bei der Pufferung und den HTTP-Protokollversionen.

Die Implementierung und Konfiguration dieses Features variiert je nach Server- und Frameworkversion, wie in den folgenden Abschnitten beschrieben.

IIS

IIS verwaltet die Clientzertifikataushandlung in Ihrem Namen. Ein Unterabschnitt der Anwendung kann die SslRequireCert-Option zum Aushandeln des Clientzertifikats für diese Anforderungen aktivieren. Weitere Informationen finden Sie in der IIS-Dokumentation unter "Konfiguration".

IIS puffert vor der Neuverhandlung automatisch alle Anforderungstextdaten bis zu einem konfigurierten Größenlimit. Anforderungen, die den Grenzwert überschreiten, werden mit der Antwort 413 abgelehnt. Dieser Grenzwert ist standardmäßig auf 48 KB festgelegt und kann durch Festlegen der uploadReadAheadSize-Eigenschaft konfiguriert werden.

HttpSys

HttpSys verfügt über zwei Einstellungen, die die Clientzertifikatverhandlung steuern, und beide sollten festgelegt werden. Der erste befindet sich in der netsh.exe Datei unter http add sslcert clientcertnegotiation=enable/disable. Dieses Kennzeichen gibt an, ob das Clientzertifikat zu Beginn einer Verbindung ausgehandelt werden soll. Legen Sie den Wert disable für optionale Clientzertifikate fest. Weitere Informationen finden Sie unter der http add sslcert Parameterverwendung in den Netsh-Dokumenten.

Die andere Einstellung ist die ClientCertificateMethod-Eigenschaft. Bei der Festlegung auf AllowRenegotation kann das Clientzertifikat während einer Anforderung neu ausgehandelt werden.

Hinweis

Die Anwendung sollte alle Daten des Anforderungstexts puffern oder verwenden, bevor sie versucht, die Neuverhandlung einzuleiten. Andernfalls reagiert die Anforderung möglicherweise nicht mehr.

Eine Anwendung kann zunächst die ClientCertificate-Eigenschaft überprüfen, um festzustellen, ob das Zertifikat verfügbar ist. Wenn diese nicht verfügbar ist, stellen Sie sicher, dass der Anforderungstext verarbeitet wird, bevor Sie die GetClientCertificateAsync-Methode aufrufen, um eine auszuhandeln. GetClientCertificateAsync kann ein NULL-Zertifikat zurückgeben, wenn der Client die Bereitstellung eines Zertifikats ablehnt.

Hinweis

Das Verhalten der ClientCertificate-Eigenschaft wurde in .NET 6 geändert. Weitere Informationen finden Sie unter GitHub Problem #466.

Kestrel

Kestrel steuert die Clientzertifikatverhandlung mit der ClientCertificateMode Eigenschaft.

.NET 6 und höher bietet die Option DelayCertificate für die Eigenschaft ClientCertificateMode. Wenn diese Option festgelegt ist, kann eine App die ClientCertificate Eigenschaft überprüfen, um festzustellen, ob das Zertifikat verfügbar ist. Falls sie nicht verfügbar ist, stellen Sie sicher, dass der Anforderungstext verwendet wird, bevor Sie zum Aushandeln die GetClientCertificateAsync-Methode aufrufen. GetClientCertificateAsync kann ein NULL-Zertifikat zurückgeben, wenn der Client die Bereitstellung eines Zertifikats ablehnt.

Hinweis

Die Anwendung sollte alle Daten des Anforderungstexts puffern oder verwenden, bevor sie versucht, die Neuverhandlung einzuleiten. GetClientCertificateAsync Andernfalls kann die Ausnahme "InvalidOperationException" ausgelöst werden: Der Clientdatenstrom muss vor der Neuverhandlung abgelassen werden.

Wenn Sie die TLS-Einstellungen pro SNI-Hostname programmgesteuert konfigurieren, rufen Sie die UseHttpsÜberladung (.NET 6 oder höher) auf, die ein TlsHandshakeCallbackOptions Klassenobjekt verwendet. Diese Option steuert die Neuverhandlung des Clientzertifikats über die AllowDelayedClientCertificateNegotation Eigenschaft. Weitere Informationen finden Sie unter der ListenOptionsHttpsExtensions.UseHttps-Methode .

Microsoft.AspNetCore.Authentication.Certificate enthält eine Implementierung ähnlich der Zertifikatauthentifizierung für ASP.NET Core. Zertifikate werden auf der TLS-Schicht authentifiziert, was lange bevor es jemals zu ASP.NET Core kommt, erfolgt. Genauer gesagt überprüft dieser Authentifizierungshandler das Zertifikat und gibt Ihnen ein Ereignis, in dem Sie das Zertifikat in einen ClaimsPrincipal Wert auflösen können.

Konfigurieren Sie Ihren Server für die Zertifikatauthentifizierung. Dabei können Sie IIS, Kestrel, Azure-Web-Apps oder etwas anderes verwenden.

Szenarien mit Proxy und Lastenausgleich

Die Zertifikatauthentifizierung ist ein zustandsbehaftetes Szenario, das hauptsächlich verwendet wird, in dem der Datenverkehr zwischen Clients und Servern nicht durch einen Proxy oder Load Balancer verarbeitet wird. Wenn ein Proxy oder Lastenausgleich verwendet wird, funktioniert die Zertifikatauthentifizierung nur, wenn der Proxy oder der Lastenausgleich folgende Funktionen übernimmt:

  • Handhabt die Authentifizierung.
  • Das Übergeben der Benutzerauthentifizierungsinformationen an die App (z. B. in einem Anforderungsheader), die die Authentifizierungsinformationen verarbeitet.

Eine Alternative zur Zertifikatauthentifizierung in Umgebungen, in denen Proxys und Lastenausgleichsmodule verwendet werden, bildet Active Directory-Verbunddienste (AD FS) mit OpenID Connect (OIDC).

Erste Schritte

Rufen Sie ein HTTPS-Zertifikat ab, wenden Sie es an, und konfigurieren Sie Ihren Server so, dass Zertifikate erforderlich sind.

Fügen Sie in Ihre Web-App einen Verweis auf das Paket Microsoft.AspNetCore.Authentication.Certificate hinzu. Rufen Sie dann in der Startup.ConfigureServices-Methode services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...); mit Ihren Optionen auf, und geben Sie einen Delegaten für OnCertificateValidated an, um eine zusätzliche Validierung des mit den Anfragen gesendeten Clientzertifikats durchzuführen. Wandeln Sie diese Informationen in einen ClaimsPrincipal um, und legen Sie diesen in der context.Principal-Eigenschaft fest.

Bei einem Authentifizierungsfehler gibt dieser Handler wie erwartet eine 403 (Forbidden)-Antwort anstelle von 401 (Unauthorized) zurück. Der Grund hierfür ist, dass die Authentifizierung während der ersten TLS-Verbindung erfolgen sollte. Wenn der Handler erreicht wird, ist es zu spät. Es gibt keine Möglichkeit, die Verbindung von einer anonymen Verbindung in eine Verbindung mit einem Zertifikat zu ändern.

Fügen Sie auch app.UseAuthentication(); in der Startup.Configure-Methode hinzu. Andernfalls wird HttpContext.User nicht auf ClaimsPrincipal gesetzt, das aus dem Zertifikat erstellt wurde. Beispiel:

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
}

Im obigen Beispiel wird das Standardverfahren zum Hinzufügen der Zertifikatauthentifizierung veranschaulicht. Der Handler erstellt einen Benutzer-Principal mit den gängigen Zertifikateigenschaften.

Konfigurieren der Zertifikatüberprüfung

Der CertificateAuthenticationOptions-Handler verfügt über einige integrierte Validierungen. Dabei handelt es such um die mindestens erforderlichen Überprüfungen, die Sie für ein Zertifikat ausführen sollten. Jede dieser Einstellungen ist standardmäßig aktiviert.

AllowedCertificateTypes = Chained, SelfSigned oder All (Chained | SelfSigned)

Standardwert: CertificateTypes.Chained

Bei dieser Überprüfung wird getestet, ob nur der entsprechende Zertifikattyp zulässig ist. Wenn die App selbstsignierte Zertifikate verwendet, muss diese Option auf CertificateTypes.All oder CertificateTypes.SelfSigned festgelegt werden.

ValidateCertificateUse

Standardwert: true

Bei dieser Überprüfung wird getestet, ob das vom Client bereitgestellte Zertifikat über die erweiterte Schlüsselverwendung (EKU) der Clientauthentifizierung oder überhaupt keine EKUs verfügt. Wenn keine EKU angegeben wird, gelten laut Spezifikationen alle EKUs als gültig.

Gültigkeitszeitraum überprüfen

Standardwert: true

Bei dieser Überprüfung wird getestet, ob das Zertifikat innerhalb seines Gültigkeitszeitraums liegt. Bei jeder Anforderung stellt der Handler sicher, dass ein Zertifikat, das bei der Präsentation gültig war, während der aktuellen Sitzung nicht abgelaufen ist.

RevocationFlag

Standardwert: X509RevocationFlag.ExcludeRoot

Dieses Flag gibt an, welche Zertifikate in der Kette auf Widerruf geprüft werden sollen.

Sperrprüfungen werden nur ausgeführt, wenn das Zertifikat mit einem Stammzertifikat verkettet ist.

RevocationMode

Standardwert: X509RevocationMode.Online

Dieses Flag legt fest, wie Widerrufsprüfungen durchgeführt werden.

Die Anforderung einer Onlineüberprüfung kann zu einer langen Verzögerung führen, während die Zertifizierungsstelle kontaktiert wird.

Sperrprüfungen werden nur ausgeführt, wenn das Zertifikat mit einem Stammzertifikat verkettet ist.

Kann ich meine App so konfigurieren, dass ein Zertifikat nur für bestimmte Pfade erforderlich ist?

Dies ist nicht möglich. Denken Sie daran, dass der Zertifikataustausch zu Beginn der HTTPS-Konversation erfolgt. Er wird vom Server ausgeführt, bevor die erste Anforderung für diese Verbindung empfangen wird, sodass es nicht möglich ist, eine Bereichseinschränkung basierend auf Anforderungsfeldern zu verwenden.

Handlerereignisse

Der Handler weist zwei Ereignisse auf:

  • OnAuthenticationFailed wird aufgerufen, wenn während der Authentifizierung eine Ausnahme auftritt und Sie reagieren können.
  • OnCertificateValidated wird aufgerufen, nachdem das Zertifikat überprüft, die Überprüfung bestanden und ein Standardprinzipal erstellt wurde. Mit diesem Ereignis können Sie Ihre eigene Validierung durchführen und den Prinzipal erweitern oder ersetzen. Beispiele dafür sind:
    • Ermitteln, ob das Zertifikat Ihrem Diensten bekannt ist

    • Erstellen eines eigenen Prinzipals Betrachten Sie das folgende Beispiel 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;
                  }
              };
          });
      

Wenn Sie feststellen, dass das eingehende Zertifikat Ihre zusätzlichen Anforderungen nicht erfüllt, informieren Sie context.Fail("failure reason") über den Grund des Fehlers.

Bei der praktischen Anwendung rufen Sie wahrscheinlich einen Dienst auf, der bei der Abhängigkeitsinjektion registriert ist und eine Verbindung mit einer Datenbank oder einem anderen Benutzerspeicher herstellt. Greifen Sie mithilfe des Kontexts, der an den Delegaten übergeben wurde, auf Ihren Dienst zu. Betrachten Sie das folgende Beispiel 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;
            }
        };
    });

Konzeptionell ist die Validierung des Zertifikats ein Autorisierungsschritt. Das Hinzufügen einer Überprüfung, z. B. eines Ausstellers oder Fingerabdrucks in einer Autorisierungsrichtlinie und nicht innerhalb von OnCertificateValidated, ist problemlos möglich.

Konfigurieren Ihres Servers für die Erzwingung von Zertifikaten

Kestrel

Konfigurieren Sie Program.cs in Kestrel wie folgt:

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);
            });
        });
}

Hinweis

Auf Endpunkte, die durch Aufrufen von Listenvor dem Aufrufen von ConfigureHttpsDefaults erstellt werden, werden die Standardwerte nicht angewendet.

IIS

Führen Sie im IIS-Manager die folgenden Schritte aus:

  1. Wählen Sie Ihre Website auf der Registerkarte Verbindungen aus.
  2. Doppelklicken Sie im Fenster Featureansicht auf die Option SSL-Einstellungen.
  3. Aktivieren Sie das Kontrollkästchen SSL anfordern und im Abschnitt Clientzertifikate das Optionsfeld Erforderlich.

Einstellungen für Clientzertifikate in IIS

Azure und benutzerdefinierte Webproxys

Informationen zum Konfigurieren der Middleware für die Zertifikatweiterleitung finden Sie in der Host- und Bereitstellungsdokumentation.

Verwenden der Zertifikatauthentifizierung in Azure-Web-Apps

Für Azure ist keine Weiterleitungskonfiguration erforderlich. Die Weiterleitungskonfiguration wird von der Middleware für die Zertifikatweiterleitung eingerichtet.

Hinweis

Für dieses Szenario ist Middleware für die Zertifikatweiterleitung erforderlich.

Weitere Informationen finden Sie unter Verwenden eines TLS-/SSL-Zertifikats in Ihrem Code in Azure App Service (Azure-Dokumentation).

Verwenden der Zertifikatauthentifizierung in benutzerdefinierten Webproxys

Mit der AddCertificateForwarding-Methode wird Folgendes angegeben:

  • Der Client-Header-Name
  • Wie das Zertifikat geladen werden soll (mit der HeaderConverter-Eigenschaft)

Bei benutzerdefinierten Webproxys wird das Zertifikat als benutzerdefinierter Anforderungsheader übergeben, z. B. X-SSL-CERT. Um es zu verwenden, konfigurieren Sie die Zertifikatweiterleitung 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;
}

Wenn für die App ein Reverseproxy von NGINX mit der Konfiguration proxy_set_header ssl-client-cert $ssl_client_escaped_cert verwendet wird oder wenn sie in Kubernetes mithilfe von NGINX Ingress bereitgestellt wird, wird das Clientzertifikat URL-codiert an die App übergeben. Um das Zertifikat zu verwenden, decodieren Sie es wie folgt:

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;
    };
});

Die Startup.Configure-Methode fügt dann die Middleware hinzu. UseCertificateForwarding wird vor den Aufrufen von UseAuthentication und UseAuthorization aufgerufen:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...

    app.UseRouting();

    app.UseCertificateForwarding();
    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

Mithilfe einer separaten Klasse können Sie Validierungslogik implementieren. Da in diesem Beispiel dasselbe selbstsignierte Zertifikat verwendet wird, müssen Sie sicherstellen, dass nur Ihr Zertifikat verwendet werden kann. Überprüfen Sie, ob die Fingerabdrücke des Clientzertifikats und des Serverzertifikats übereinstimmen. Andernfalls kann jedes Zertifikat verwendet werden, und es reicht für die Authentifizierung aus. Dies wird innerhalb der AddCertificate-Methode verwendet. Sie können hier auch den Antragsteller oder den Aussteller überprüfen, wenn Sie zwischengeschaltete oder untergeordnete Zertifikate verwenden.

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;
        }
    }
}

Implementieren eines HttpClient mithilfe eines Zertifikats und von HttpClientHandler

Der HttpClientHandler kann direkt im Konstruktor der HttpClient-Klasse hinzugefügt werden. Beim Erstellen von Instanzen von HttpClient ist Vorsicht geboten. Der HttpClient sendet dann das Zertifikat mit jeder Anforderung.

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}");
}

Implementieren Sie einen HttpClient mithilfe eines Zertifikats und eines benannten HttpClient aus der IHttpClientFactory.

Im folgenden Beispiel wird ein Clientzertifikat mithilfe der ClientCertificates-Eigenschaft des Handlers einem HttpClientHandler hinzugefügt. Dieser Handler kann dann in einer benannten Instanz eines HttpClient mithilfe der ConfigurePrimaryHttpMessageHandler-Methode verwendet werden. Dies wird in Startup.ConfigureServices eingerichtet:

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;
});

Die IHttpClientFactory kann dann verwendet werden, um die benannte Instanz mit dem Handler und dem Zertifikat abzurufen. Die CreateClient-Methode mit dem Namen des in der Startup-Klasse definierten Clients wird verwendet, um die Instanz abzurufen. Die HTTP-Anforderung kann bei Bedarf über den Client gesendet werden.

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}");
}

Wenn das richtige Zertifikat an den Server gesendet wird, werden die Daten zurückgegeben. Wenn kein Zertifikat oder ein falsches Zertifikat übermittelt wurde, wird der HTTP-Statuscode 403 zurückgegeben.

Erstellen von Zertifikaten in PowerShell

Das Erstellen der Zertifikate ist der schwierigste Teil beim Einrichten dieses Flows. Ein Stammzertifikat kann mithilfe des PowerShell-Cmdlets New-SelfSignedCertificate erstellt werden. Verwenden Sie beim Erstellen des Zertifikats ein sicheres Kennwort. Es ist wichtig, den KeyUsageProperty-Parameter und den KeyUsage-Parameter wie gezeigt hinzuzufügen.

Erstellen einer Stammzertifizierungsstelle

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

Hinweis

Der Wert des -DnsName-Parameters muss mit dem Bereitstellungsziel der App übereinstimmen. Während der Entwicklung kann z. B. "localhost" verwendet werden.

Installieren im vertrauenswürdigen Stamm

Das Stammzertifikat muss auf Ihrem Hostsystem als vertrauenswürdig gelten. Ein Stammzertifikat, das nicht von einer Zertifizierungsstelle ausgestellt wurde, gilt standardmäßig nicht als vertrauenswürdig. Weitere Informationen dazu, wie man dem Stammzertifikat unter Windows vertraut, finden Sie unter dieser Frage.

Zwischenzertifikat

Aus dem Stammzertifikat kann nun ein Zwischenzertifikat erstellt werden. Dies ist nicht für alle Anwendungsfälle erforderlich, aber möglicherweise müssen Sie viele Zertifikate erstellen oder Zertifikatgruppen aktivieren oder deaktivieren. Der TextExtension-Parameter ist erforderlich, um die Pfadlänge in den grundlegenden Einschränkungen des Zertifikats festzulegen.

Das Zwischenzertifikat kann dann dem vertrauenswürdigen Zwischenzertifikat im Windows-Hostsystem hinzugefügt werden.

$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

Erstellen eines untergeordneten Zertifikats aus einem Zwischenzertifikat

Aus dem Zwischenzertifikat kann ein Kinderzertifikat erstellt werden. Dies ist die letzte Entität, von der keine weiteren untergeordneten Zertifikate erstellt werden müssen.

$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

Erstellen eines untergeordneten Zertifikats aus einem Stammzertifikat

Ein Kindzertifikat kann auch direkt ausgehend vom Stammzertifikat erstellt werden.

$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

Beispielstamm > Zwischenzertifikat > Zertifikat

$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

Bei Verwendung der Stamm-, Zwischen- oder untergeordneten Zertifikate können die Zertifikate nach Bedarf mit dem Fingerabdruck oder dem öffentlichen Schlüssel überprüft werden.

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;
        }
    }
}

Zwischenspeichern der Zertifikatvalidierung

.NET 5 oder höhere Versionen unterstützen die Möglichkeit, das Zwischenspeichern von Validierungsergebnissen zu ermöglichen. Die Zwischenspeicherung verbessert die Leistung der Zertifikatauthentifizierung erheblich, da die Validierung ein aufwendiger Vorgang ist.

Standardmäßig deaktiviert die Zertifikatauthentifizierung die Zwischenspeicherung. Rufen Sie zum Aktivieren der Zwischenspeicherung AddCertificateCache in Startup.ConfigureServices auf:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(
        CertificateAuthenticationDefaults.AuthenticationScheme)
            .AddCertificate()
            .AddCertificateCache(options =>
            {
                options.CacheSize = 1024;
                options.CacheEntryExpiration = TimeSpan.FromMinutes(2);
            });
}

Bei der Standardimplementierung der Zwischenspeicherung werden die Ergebnisse im Arbeitsspeicher gespeichert. Sie können einen eigenen Cache bereitstellen, indem Sie ICertificateValidationCache implementieren und bei der Abhängigkeitsinjektion registrieren. Beispiel: services.AddSingleton<ICertificateValidationCache, YourCache>().

Optionale Clientzertifikate

Dieser Abschnitt enthält Informationen zu Apps, die einen Teil der App mit einem Zertifikat schützen müssen. Für eine Razor-Seite oder einen Controller in der App können beispielsweise Clientzertifikate erforderlich sein. Dies stellt Herausforderungen im Zusammenhang mit Clientzertifikaten dar:

  • Es handelt sich um ein TLS-Feature, nicht um ein HTTP-Feature.
  • werden pro Verbindung und in der Regel zu Beginn der Verbindung ausgehandelt, bevor HTTP-Daten verfügbar sind.

Es gibt zwei Ansätze zum Implementieren optionaler Clientzertifikate:

  1. Verwenden von separaten Hostnamen (SNI) und Umleitung. Auch wenn der Aufwand höher ist, wird dies empfohlen, da es in den meisten Umgebungen und mit den meisten Protokollen funktioniert.
  2. Neuverhandlung während einer HTTP-Anforderung. Dieser Ansatz weist einige Einschränkungen auf und wird nicht empfohlen.

Separate Hosts (SNI)

Zu Beginn der Verbindung ist nur die Servernamensanzeige (Server Name Indication, SNI†) bekannt. Clientzertifikate können pro Hostnamen konfiguriert werden, sodass ein Host sie benötigt und ein anderer nicht.

.NET 5 oder höher bietet eine bequemere Unterstützung für die Umleitung, um optionale Clientzertifikate zu erwerben. Weitere Informationen finden Sie im Beispiel für optionale Zertifikate.

  • Für Anforderungen an die Web-App, die ein Clientzertifikat erfordern, aber über keins verfügen:
    • Leiten Sie die Anforderung an dieselbe Seite mit der durch ein Clientzertifikat geschützten Unterdomäne um.
    • Richten Sie z. B. eine Umleitung zu myClient.contoso.com/requestedPage ein. Da die Anforderung an myClient.contoso.com/requestedPage einen anderen Hostnamen als contoso.com/requestedPage verwendet, stellt der Client eine andere Verbindung her, und das Clientzertifikat wird bereitgestellt.
    • Weitere Informationen finden Sie unter Einführung in die Autorisierung in ASP.NET Core.

† Die Servernamensanzeige (SNI) ist eine TLS-Erweiterung zum Einbinden einer virtuellen Domäne als Teil der SSL-Aushandlung. Dies bedeutet, dass der Name der virtuellen Domäne oder eines Hosts zur Identifizierung des Netzwerkendpunkts verwendet werden kann.

Erneutes Aushandeln

Die erneute TLS-Aushandlung ist ein Prozess, bei dem Client und Server die Verschlüsselungsanforderungen für eine einzelne Verbindung neu bewerten können. Dies schließt auch die Anforderung eines Clientzertifikats ein, falls dieses zuvor nicht bereitgestellt wurde. Die erneute TLS-Aushandlung stellt ein Sicherheitsrisiko dar und wird aus folgenden Gründen nicht empfohlen:

  • In HTTP/1.1 muss der Server zuerst alle sich in Übertragung befindenden HTTP-Daten, wie POST-Anforderungstexte, puffern oder verarbeiten, um sicherzustellen, dass die Verbindung für die erneute Aushandlung frei ist. Andernfalls reagiert die erneute Aushandlung eventuell nicht mehr reagieren oder führt zu einem Fehler.
  • HTTP/2 und HTTP/3 verbieten explizit die erneute Aushandlung.
  • Die erneute Aushandlung ist mit Sicherheitsrisiken verbunden. In TLS 1.3 wurde die erneute Aushandlung für die gesamte Verbindung entfernt und durch eine neue Erweiterung für das Anfordern ausschließlich des Clientzertifikats nach dem Verbindungsstart ersetzt. Dieser Mechanismus wird über dieselben APIs verfügbar gemacht und unterliegt weiterhin den vorherigen Einschränkungen bei der Pufferung und den HTTP-Protokollversionen.

Die Implementierung und Konfiguration dieses Features variiert je nach Server- und Frameworkversion.

IIS

IIS verwaltet die Clientzertifikataushandlung in Ihrem Namen. Ein Unterabschnitt der Anwendung kann die SslRequireCert-Option zum Aushandeln des Clientzertifikats für diese Anforderungen aktivieren. Einzelheiten dazu finden Sie in der IIS-Dokumentation zur Konfiguration.

IIS puffert alle Daten im Anforderungstext vor der erneuten Aushandlung automatisch bis zu einem konfigurierten Größengrenzwert. Anforderungen, die den Grenzwert überschreiten, werden mit der Antwort 413 abgelehnt. Dieser Grenzwert ist standardmäßig auf 48 KB festgelegt und kann durch Festlegen von uploadReadAheadSize konfiguriert werden.

HttpSys

HttpSys verfügt über zwei Einstellungen zur Steuerung der Clientzertifikataushandlung, die beide festgelegt werden sollten. Die erste befindet sich in „netsh.exe“ unter http add sslcert clientcertnegotiation=enable/disable. Dieses Flag gibt an, ob das Clientzertifikat zu Beginn einer Verbindung ausgehandelt werden soll. Es sollte für optionale Clientzertifikate auf disable festgelegt werden. Weitere Informationen finden Sie in der netsh-Dokumentation.

Die andere Einstellung ist ClientCertificateMethod. Bei der Festlegung auf AllowRenegotation kann das Clientzertifikat während einer Anforderung neu ausgehandelt werden.

HINWEIS: Die Anwendung sollte alle Daten im Anforderungstext puffern oder verarbeiten, bevor eine erneute Aushandlung versucht wird. Andernfalls reagiert die Anforderung möglicherweise nicht mehr.

Es gibt ein bekanntes Problem, durch das bei der Aktivierung von AllowRenegotation die erneute Aushandlung synchron erfolgen kann, wenn auf die ClientCertificate-Eigenschaft zugegriffen wird. Rufen Sie die GetClientCertificateAsync-Methode auf, um dies zu verhindern. Dieses Problem wurde in .NET 6 behoben. Weitere Informationen finden Sie in diesem GitHub-Issue. Hinweis: GetClientCertificateAsync kann ein NULL-Zertifikat zurückgeben, wenn der Client die Bereitstellung eines Zertifikats ablehnt.

Kestrel

Kestrel steuert die Aushandlung von Clientzertifikaten mit der ClientCertificateMode-Option.

Für .NET 5 oder früher unterstützt Kestrel keine Neuverhandlung, um nach dem Verbindungsstart ein Clientzertifikat zu erwerben. Dieses Feature wurde in .NET 6 hinzugefügt.

Microsoft.AspNetCore.Authentication.Certificate enthält eine Implementierung ähnlich der Zertifikatauthentifizierung für ASP.NET Core. Die Zertifikatsauthentifizierung erfolgt auf der TLS-Ebene, lange bevor sie überhaupt zum ASP.NET Core gelangt. Genauer gesagt ist dies ein Authentifizierungshandler, der das Zertifikat überprüft und Ihnen dann ein Ereignis bereitstellt, bei dem Sie dieses Zertifikat in einen ClaimsPrincipal auflösen können.

Konfigurieren Sie Ihren Server für die Zertifikatauthentifizierung. Dabei können Sie IIS, Kestrel, Azure-Web-Apps oder etwas anderes verwenden.

Szenarien mit Proxy und Lastenausgleich

Die Zertifikatauthentifizierung ist ein zustandsbehaftetes Szenario, das hauptsächlich verwendet wird, in dem der Datenverkehr zwischen Clients und Servern nicht durch einen Proxy oder Load Balancer verarbeitet wird. Wenn ein Proxy oder Lastenausgleich verwendet wird, funktioniert die Zertifikatauthentifizierung nur, wenn der Proxy oder der Lastenausgleich folgende Funktionen übernimmt:

  • Handhabt die Authentifizierung.
  • Das Übergeben der Benutzerauthentifizierungsinformationen an die App (z. B. in einem Anforderungsheader), die die Authentifizierungsinformationen verarbeitet.

Eine Alternative zur Zertifikatauthentifizierung in Umgebungen, in denen Proxys und Lastenausgleichsmodule verwendet werden, bildet Active Directory-Verbunddienste (AD FS) mit OpenID Connect (OIDC).

Erste Schritte

Rufen Sie ein HTTPS-Zertifikat ab, wenden Sie es an, und konfigurieren Sie Ihren Server so, dass Zertifikate erforderlich sind.

Fügen Sie in Ihre Web-App einen Verweis auf das Paket Microsoft.AspNetCore.Authentication.Certificate hinzu. Rufen Sie dann in der Startup.ConfigureServices-Methode services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...); mit Ihren Optionen auf, und geben Sie einen Delegaten für OnCertificateValidated an, um eine zusätzliche Überprüfung des Clientzertifikats durchzuführen, das mit den Anforderungen gesendet wird. Wandeln Sie diese Informationen in einen ClaimsPrincipal um, und legen Sie diesen in der context.Principal-Eigenschaft fest.

Bei einem Authentifizierungsfehler gibt dieser Handler wie erwartet eine 403 (Forbidden)-Antwort anstelle von 401 (Unauthorized) zurück. Der Grund hierfür ist, dass die Authentifizierung während der ersten TLS-Verbindung erfolgen sollte. Wenn der Handler erreicht wird, ist es zu spät. Es gibt keine Möglichkeit, die Verbindung von einer anonymen Verbindung in eine Verbindung mit einem Zertifikat zu ändern.

Fügen Sie auch app.UseAuthentication(); in der Startup.Configure-Methode hinzu. Andernfalls wird HttpContext.User nicht auf den ClaimsPrincipal festgelegt, der aus dem Zertifikat erstellt wurde. Beispiel:

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
}

Im obigen Beispiel wird das Standardverfahren zum Hinzufügen der Zertifikatauthentifizierung veranschaulicht. Der Handler erstellt einen Benutzerprinzipal mit den allgemeinen Zertifikateigenschaften.

Konfigurieren der Zertifikatüberprüfung

Der CertificateAuthenticationOptions-Handler verfügt über einige integrierte Validierungen. Dabei handelt es such um die mindestens erforderlichen Überprüfungen, die Sie für ein Zertifikat ausführen sollten. Jede dieser Einstellungen ist standardmäßig aktiviert.

AllowedCertificateTypes = Chained, SelfSigned oder All (Chained | SelfSigned)

Standardwert: CertificateTypes.Chained

Bei dieser Überprüfung wird getestet, ob nur der entsprechende Zertifikattyp zulässig ist. Wenn die App selbstsignierte Zertifikate verwendet, muss diese Option auf CertificateTypes.All oder CertificateTypes.SelfSigned festgelegt werden.

ValidateCertificateUse

Standardwert: true

Bei dieser Überprüfung wird getestet, ob das vom Client bereitgestellte Zertifikat über die erweiterte Schlüsselverwendung (EKU) der Clientauthentifizierung oder überhaupt keine EKUs verfügt. Wenn keine EKU angegeben wird, gelten laut Spezifikationen alle EKUs als gültig.

Gültigkeitszeitraum überprüfen

Standardwert: true

Bei dieser Überprüfung wird getestet, ob das Zertifikat innerhalb seines Gültigkeitszeitraums liegt. Bei jeder Anforderung stellt der Handler sicher, dass ein Zertifikat, das bei der Präsentation gültig war, während der aktuellen Sitzung nicht abgelaufen ist.

RevocationFlag

Standardwert: X509RevocationFlag.ExcludeRoot

Dieses Flag gibt an, welche Zertifikate in der Kette auf Widerruf überprüft werden sollen.

Sperrprüfungen werden nur ausgeführt, wenn das Zertifikat mit einem Stammzertifikat verkettet ist.

RevocationMode

Standardwert: X509RevocationMode.Online

Dieses Flag gibt an, wie Widerrufsüberprüfungen ausgeführt werden.

Die Spezifikation einer Online-Überprüfung kann eine erhebliche Verzögerung verursachen, während die Zertifizierungsstelle kontaktiert wird.

Sperrprüfungen werden nur ausgeführt, wenn das Zertifikat mit einem Stammzertifikat verkettet ist.

Kann ich meine App so konfigurieren, dass ein Zertifikat nur für bestimmte Pfade erforderlich ist?

Dies ist nicht möglich. Denken Sie daran, dass der Zertifikataustausch am Anfang der HTTPS-Verbindung erfolgt. Er wird vom Server ausgeführt, bevor die erste Anforderung für diese Verbindung empfangen wird, sodass es nicht möglich ist, auf Anforderungsfeldern basierende Einschränkungen anzuwenden.

Handlerereignisse

Der Behandler hat zwei Ereignisse.

  • OnAuthenticationFailed wird aufgerufen, wenn während der Authentifizierung eine Ausnahme auftritt und Sie reagieren können.
  • OnCertificateValidated wird aufgerufen, nachdem das Zertifikat überprüft, die Überprüfung bestanden und ein Standardprinzipal erstellt wurde. Mit diesem Ereignis können Sie Ihre eigene Validierung durchführen und den Prinzipal erweitern oder ersetzen. Beispiele dafür sind:
    • Ermitteln, ob das Zertifikat Ihrem Diensten bekannt ist

    • Erstellen eines eigenen Prinzipals Betrachten Sie das folgende Beispiel 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;
                  }
              };
          });
      

Wenn Sie feststellen, dass das eingehende Zertifikat Ihre zusätzliche Überprüfung nicht erfüllt, rufen Sie context.Fail("failure reason") unter Angabe des Fehlergrundes auf.

Für die tatsächliche Funktionalität möchten Sie wahrscheinlich einen Dienst aufrufen, der in der Abhängigkeitsinjektion registriert ist und eine Verbindung zu einer Datenbank oder einem anderen Benutzerspeicher herstellt. Greifen Sie auf Ihren Dienst zu, indem Sie den an Ihren Delegierten übergebenen Kontext verwenden. Betrachten Sie das folgende Beispiel 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;
            }
        };
    });

Konzeptionell ist die Validierung des Zertifikats ein Autorisierungsschritt. Das Hinzufügen einer Überprüfung, z. B. eines Ausstellers oder Fingerabdrucks in einer Autorisierungsrichtlinie und nicht innerhalb von OnCertificateValidated, ist problemlos möglich.

Konfigurieren Ihres Servers für die Erzwingung von Zertifikaten

Kestrel

Konfigurieren Sie Program.cs in Kestrel wie folgt:

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);
            });
        });
}

Hinweis

Auf Endpunkte, die durch Aufrufen von Listenvor dem Aufrufen von ConfigureHttpsDefaults erstellt werden, werden die Standardwerte nicht angewendet.

IIS

Führen Sie im IIS-Manager die folgenden Schritte aus:

  1. Wählen Sie Ihre Website auf der Registerkarte Verbindungen aus.
  2. Doppelklicken Sie im Fenster Featureansicht auf die Option SSL-Einstellungen.
  3. Aktivieren Sie das Kontrollkästchen SSL anfordern und im Abschnitt Clientzertifikate das Optionsfeld Erforderlich.

Einstellungen für Clientzertifikate in IIS

Azure und benutzerdefinierte Webproxys

Informationen zum Konfigurieren der Middleware für die Zertifikatweiterleitung finden Sie in der Host- und Bereitstellungsdokumentation.

Verwenden der Zertifikatauthentifizierung in Azure-Web-Apps

Für Azure ist keine Weiterleitungskonfiguration erforderlich. Die Weiterleitungskonfiguration wird von der Middleware für die Zertifikatweiterleitung eingerichtet.

Hinweis

Für dieses Szenario ist Middleware für die Zertifikatweiterleitung erforderlich.

Weitere Informationen finden Sie unter Verwenden eines TLS-/SSL-Zertifikats in Ihrem Code in Azure App Service (Azure-Dokumentation).

Verwenden der Zertifikatauthentifizierung in benutzerdefinierten Webproxys

Mit der AddCertificateForwarding-Methode wird Folgendes angegeben:

  • Der Client-Header-Name
  • Wie das Zertifikat geladen werden soll (mit der HeaderConverter-Eigenschaft)

Bei benutzerdefinierten Webproxys wird das Zertifikat als benutzerdefinierter Anforderungsheader übergeben, z. B. X-SSL-CERT. Um es zu verwenden, konfigurieren Sie die Zertifikatweiterleitung 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;
}

Wenn für die App ein Reverseproxy von NGINX mit der Konfiguration proxy_set_header ssl-client-cert $ssl_client_escaped_cert verwendet wird oder wenn sie in Kubernetes mithilfe von NGINX Ingress bereitgestellt wird, wird das Clientzertifikat URL-codiert an die App übergeben. Um das Zertifikat zu verwenden, decodieren Sie es wie folgt:

Fügen Sie den Namespace für System.Net am Anfang der Datei Startup.cs hinzu:

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;
    };
});

Fügen Sie die UrlEncodedPemToByteArray-Methode hinzu:

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);
}

Die Startup.Configure-Methode fügt dann die Middleware hinzu. UseCertificateForwarding wird vor den Aufrufen von UseAuthentication und UseAuthorization aufgerufen:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...

    app.UseRouting();

    app.UseCertificateForwarding();
    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

Mithilfe einer separaten Klasse können Sie Validierungslogik implementieren. Da in diesem Beispiel dasselbe selbstsignierte Zertifikat verwendet wird, müssen Sie sicherstellen, dass nur Ihr Zertifikat verwendet werden kann. Überprüfen Sie, ob die Fingerabdrücke des Clientzertifikats und des Serverzertifikats übereinstimmen. Andernfalls kann jedes Zertifikat verwendet werden, und es reicht für die Authentifizierung aus. Dies wird innerhalb der AddCertificate-Methode verwendet. Sie können hier auch den Antragsteller oder den Aussteller überprüfen, wenn Sie zwischengeschaltete oder untergeordnete Zertifikate verwenden.

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;
        }
    }
}

Implementieren eines HttpClient mithilfe eines Zertifikats und von HttpClientHandler

Der HttpClientHandler kann direkt im Konstruktor der HttpClient-Klasse hinzugefügt werden. Beim Erstellen von Instanzen von HttpClient ist Vorsicht geboten. Der HttpClient sendet dann das Zertifikat mit jeder Anforderung.

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}");
}

Implementieren Sie einen HttpClient mithilfe eines Zertifikats und eines benannten HttpClient aus der IHttpClientFactory.

Im folgenden Beispiel wird ein Clientzertifikat mithilfe der ClientCertificates-Eigenschaft des Handlers einem HttpClientHandler hinzugefügt. Dieser Handler kann dann in einer benannten Instanz eines HttpClient mithilfe der ConfigurePrimaryHttpMessageHandler-Methode verwendet werden. Dies wird in Startup.ConfigureServices eingerichtet:

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;
});

Die IHttpClientFactory kann dann verwendet werden, um die benannte Instanz mit dem Handler und dem Zertifikat abzurufen. Die CreateClient-Methode mit dem Namen des in der Startup-Klasse definierten Clients wird verwendet, um die Instanz abzurufen. Die HTTP-Anforderung kann bei Bedarf über den Client gesendet werden.

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}");
}

Wenn das richtige Zertifikat an den Server gesendet wird, werden die Daten zurückgegeben. Wenn kein Zertifikat oder ein falsches Zertifikat übermittelt wurde, wird der HTTP-Statuscode 403 zurückgegeben.

Erstellen von Zertifikaten in PowerShell

Das Erstellen der Zertifikate ist der schwierigste Teil beim Einrichten dieses Flows. Ein Stammzertifikat kann mithilfe des PowerShell-Cmdlets New-SelfSignedCertificate erstellt werden. Verwenden Sie beim Erstellen des Zertifikats ein sicheres Kennwort. Es ist wichtig, den KeyUsageProperty-Parameter und den KeyUsage-Parameter wie gezeigt hinzuzufügen.

Erstellen einer Stammzertifizierungsstelle

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

Hinweis

Der Wert des -DnsName-Parameters muss mit dem Bereitstellungsziel der App übereinstimmen. Für die Entwicklung wird beispielsweise „localhost“ verwendet.

Installieren im vertrauenswürdigen Stamm

Das Stammzertifikat muss auf Ihrem Hostsystem als vertrauenswürdig gelten. Ein Stammzertifikat, das nicht von einer Zertifizierungsstelle ausgestellt wurde, gilt standardmäßig nicht als vertrauenswürdig. Weitere Informationen dazu, wie man unter Windows dem Stammzertifikat vertraut, finden Sie unter dieser Frage.

Zwischenzertifikat

Aus dem Stammzertifikat kann nun ein Zwischenzertifikat erstellt werden. Dies ist nicht für alle Anwendungsfälle erforderlich, aber möglicherweise müssen Sie viele Zertifikate erstellen oder Zertifikatgruppen aktivieren oder deaktivieren. Der TextExtension-Parameter ist erforderlich, um die Pfadlänge in den grundlegenden Einschränkungen des Zertifikats festzulegen.

Das Zwischenzertifikat kann dann dem vertrauenswürdigen Zwischenzertifikat im Windows-Hostsystem hinzugefügt werden.

$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

Erstellen eines untergeordneten Zertifikats aus einem Zwischenzertifikat

Aus dem Zwischenzertifikat kann ein Kindzertifikat erstellt werden. Dies ist die letzte Entität, von der keine weiteren untergeordneten Zertifikate erstellt werden müssen.

$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

Erstellen eines Tochterzertifikats aus einem Stammzertifikat

Ein Kinderzertifikat kann auch direkt aus dem Stammzertifikat erstellt werden.

$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

Beispielstamm > Zwischenzertifikat > Zertifikat

$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

Bei Verwendung der Stamm-, Zwischen- oder untergeordneten Zertifikate können die Zertifikate nach Bedarf mit dem Fingerabdruck oder dem öffentlichen Schlüssel überprüft werden.

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;
        }
    }
}

Optionale Clientzertifikate

Dieser Abschnitt enthält Informationen zu Apps, die einen Teil der App mit einem Zertifikat schützen müssen. Für eine Razor-Seite oder einen Controller in der App können beispielsweise Clientzertifikate erforderlich sein. Dies stellt Herausforderungen als Clientzertifikate dar:

  • Es handelt sich um ein TLS-Feature, kein HTTP-Feature.
  • werden pro Verbindung und in der Regel zu Beginn der Verbindung ausgehandelt, bevor HTTP-Daten verfügbar sind.

Es gibt zwei Ansätze zum Implementieren optionaler Clientzertifikate:

  1. Verwenden von separaten Hostnamen (SNI) und Umleitung. Auch wenn der Aufwand höher ist, wird dies empfohlen, da es in den meisten Umgebungen und mit den meisten Protokollen funktioniert.
  2. Neuverhandlung während einer HTTP-Anforderung. Dieser Ansatz weist einige Einschränkungen auf und wird nicht empfohlen.

Separate Hosts (SNI)

Zu Beginn der Verbindung ist nur die Servernamensanzeige (Server Name Indication, SNI†) bekannt. Clientzertifikate können pro Hostnamen konfiguriert werden, sodass ein Host sie benötigt und ein anderer nicht.

.NET 5 oder höher bietet eine bequemere Unterstützung für die Umleitung, um optionale Clientzertifikate zu erwerben. Weitere Informationen finden Sie im Beispiel für optionale Zertifikate.

  • Für Anforderungen an die Web-App, die ein Clientzertifikat erfordern, aber über keins verfügen:
    • Leiten Sie die Anforderung an dieselbe Seite mit der durch ein Clientzertifikat geschützten Unterdomäne um.
    • Richten Sie z. B. eine Umleitung zu myClient.contoso.com/requestedPage ein. Da die Anforderung an myClient.contoso.com/requestedPage einen anderen Hostnamen als contoso.com/requestedPage verwendet, stellt der Client eine andere Verbindung her, und das Clientzertifikat wird bereitgestellt.
    • Weitere Informationen finden Sie unter Einführung in die Autorisierung in ASP.NET Core.

† Die Servernamensanzeige (SNI) ist eine TLS-Erweiterung zum Einbinden einer virtuellen Domäne als Teil der SSL-Aushandlung. Dies bedeutet, dass der Name der virtuellen Domäne oder eines Hosts zur Identifizierung des Netzwerkendpunkts verwendet werden kann.

Erneutes Aushandeln

Die erneute TLS-Aushandlung ist ein Prozess, bei dem Client und Server die Verschlüsselungsanforderungen für eine einzelne Verbindung neu bewerten können. Dies schließt auch die Anforderung eines Clientzertifikats ein, falls dieses zuvor nicht bereitgestellt wurde. Die erneute TLS-Aushandlung stellt ein Sicherheitsrisiko dar und wird aus folgenden Gründen nicht empfohlen:

  • In HTTP/1.1 muss der Server zuerst alle sich in Übertragung befindenden HTTP-Daten, wie POST-Anforderungstexte, puffern oder verarbeiten, um sicherzustellen, dass die Verbindung für die erneute Aushandlung frei ist. Andernfalls reagiert die erneute Aushandlung eventuell nicht mehr reagieren oder führt zu einem Fehler.
  • HTTP/2 und HTTP/3 verbieten explizit die erneute Aushandlung.
  • Die erneute Aushandlung ist mit Sicherheitsrisiken verbunden. In TLS 1.3 wurde die erneute Aushandlung für die gesamte Verbindung entfernt und durch eine neue Erweiterung für das Anfordern ausschließlich des Clientzertifikats nach dem Verbindungsstart ersetzt. Dieser Mechanismus wird über dieselben APIs verfügbar gemacht und unterliegt weiterhin den vorherigen Einschränkungen bei der Pufferung und den HTTP-Protokollversionen.

Die Implementierung und Konfiguration dieses Features variiert je nach Server- und Frameworkversion.

IIS

IIS verwaltet die Clientzertifikataushandlung in Ihrem Namen. Ein Unterabschnitt der Anwendung kann die SslRequireCert-Option zum Aushandeln des Clientzertifikats für diese Anforderungen aktivieren. Einzelheiten dazu finden Sie in der IIS-Dokumentation zur Konfiguration.

IIS puffert alle Daten im Anforderungstext vor der erneuten Aushandlung automatisch bis zu einem konfigurierten Größengrenzwert. Anforderungen, die den Grenzwert überschreiten, werden mit der Antwort 413 abgelehnt. Dieser Grenzwert ist standardmäßig auf 48 KB festgelegt und kann durch Festlegen von uploadReadAheadSize konfiguriert werden.

HttpSys

HttpSys verfügt über zwei Einstellungen zur Steuerung der Clientzertifikataushandlung, die beide festgelegt werden sollten. Die erste befindet sich in „netsh.exe“ unter http add sslcert clientcertnegotiation=enable/disable. Dieses Flag gibt an, ob das Clientzertifikat zu Beginn einer Verbindung ausgehandelt werden soll. Es sollte für optionale Clientzertifikate auf disable festgelegt werden. Weitere Informationen finden Sie in der netsh-Dokumentation.

Die andere Einstellung ist ClientCertificateMethod. Bei der Festlegung auf AllowRenegotation kann das Clientzertifikat während einer Anforderung neu ausgehandelt werden.

HINWEIS: Die Anwendung sollte alle Daten im Anforderungstext puffern oder verarbeiten, bevor eine erneute Aushandlung versucht wird. Andernfalls reagiert die Anforderung möglicherweise nicht mehr.

Es gibt ein bekanntes Problem, durch das bei der Aktivierung von AllowRenegotation die erneute Aushandlung synchron erfolgen kann, wenn auf die ClientCertificate-Eigenschaft zugegriffen wird. Rufen Sie die GetClientCertificateAsync-Methode auf, um dies zu verhindern. Dieses Problem wurde in .NET 6 behoben. Weitere Informationen finden Sie in diesem GitHub-Issue. Hinweis: GetClientCertificateAsync kann ein NULL-Zertifikat zurückgeben, wenn der Client die Bereitstellung eines Zertifikats ablehnt.

Kestrel

Kestrel steuert die Aushandlung von Clientzertifikaten mit der ClientCertificateMode-Option.

Für .NET 5 oder früher unterstützt Kestrel keine Neuverhandlung, um nach dem Verbindungsstart ein Clientzertifikat zu erwerben. Dieses Feature wurde in .NET 6 hinzugefügt.

Hinterlassen Sie Fragen, Kommentare und weiteres Feedback zu optionalen Clientzertifikaten im Diskussionsthread für GitHub Problem #18720.