Konfigurowanie uwierzytelniania certyfikatów w programie ASP.NET Core

Microsoft.AspNetCore.Authentication.Certificate zawiera implementację podobną do uwierzytelniania certyfikatów dla ASP.NET Core. Uwierzytelnianie certyfikatu odbywa się na poziomie protokołu TLS, zanim kiedykolwiek przejdzie do ASP.NET Core. Dokładniej, ta funkcja jest obsługiwaczem uwierzytelniania, który weryfikuje certyfikat, a następnie daje zdarzenie, w którym można przypisać ten certyfikat do ClaimsPrincipal.

Musisz skonfigurować serwer do uwierzytelniania certyfikatów za pomocą usług IIS, , Azure Web Apps lub preferowanego rozwiązania.

W tym artykule opisano sposób konfigurowania uwierzytelniania certyfikatów w ASP.NET Core dla usług IIS i HTTP.sysoraz przedstawiono przykłady wywoływania różnych metod i pracy z właściwościami.

Przegląd scenariuszy serwera proxy i modułu równoważenia obciążenia

Uwierzytelnianie certyfikatu jest scenariuszem stanowym używanym głównie w przypadku, gdy serwer proxy lub moduł równoważenia obciążenia nie obsługuje ruchu między klientami i serwerami. Jeśli używany jest serwer proxy lub moduł równoważenia obciążenia, uwierzytelnianie certyfikatu działa tylko wtedy, gdy serwer proxy lub moduł równoważenia obciążenia:

  • Obsługuje uwierzytelnianie.
  • Przekazuje informacje o uwierzytelnianiu użytkownika do aplikacji (na przykład w nagłówku żądania), która działa na podstawie informacji uwierzytelniania.

Alternatywą dla uwierzytelniania certyfikatów w środowiskach, w których są używane serwery proxy i moduły równoważenia obciążenia, jest usługa Active Directory Federated Services (ADFS) z protokołem OpenID Connect (OIDC).

Wprowadzenie

Uzyskaj certyfikat HTTPS, zastosuj go i skonfiguruj serwer tak, aby wymagał certyfikatów.

W aplikacji internetowej:

  • Dodaj odwołanie do pakietu NuGet Microsoft.AspNetCore.Authentication.Certificate .

  • W pliku Program.cs wywołaj metodę builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...); . Podaj pełnomocnik programu OnCertificateValidated obsługi zdarzeń, aby ukończyć dodatkową walidację certyfikatu klienta wysłanego z żądaniami. Zmień te informacje na wartość ClaimsPrincipal i ustaw ją na właściwości context.Principal.

Jeśli uwierzytelnianie zakończy się niepowodzeniem, obsługa zwróci odpowiedź 403 (Forbidden) zamiast 401 (Unauthorized), jak można się spodziewać. Procedura obsługi zwraca inną odpowiedź, ponieważ oczekuje uwierzytelnienia w trakcie nawiązywania początkowego połączenia TLS. Zanim dotrze do obsługującego, jest już za późno. Nie ma możliwości uaktualnienia połączenia z połączenia anonimowego do jednego z certyfikatem.

Metoda UseAuthentication jest wymagana do ustawienia HttpContext.User na wartość ClaimsPrincipal utworzoną na podstawie certyfikatu. Na przykład:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

app.UseAuthentication();

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

app.Run();

W poprzednim przykładzie pokazano domyślny sposób dodawania uwierzytelniania certyfikatu. Procedura obsługi konstruuje podmiot zabezpieczeń użytkownika przy użyciu typowych właściwości certyfikatu.

Konfigurowanie weryfikacji certyfikatu

Obsługa CertificateAuthenticationOptions posiada kilka wbudowanych walidacji, które stanowią minimalne walidacje certyfikatu. Każde z tych ustawień jest domyślnie włączone. W poniższych sekcjach opisano sposób pracy z ustawieniami.

AllowedCertificateTypes = Łańcuchowe, Samopodpisane lub Wszystkie (Łańcuchowe | Samopodpisane)

Wartość domyślna: CertificateTypes.Chained

To sprawdzenie sprawdza, czy dozwolony jest tylko odpowiedni typ certyfikatu. Jeśli aplikacja korzysta z certyfikatów z podpisem własnym, ta opcja musi być ustawiona na CertificateTypes.All lub CertificateTypes.SelfSigned.

Tryb Walidacji Zaufania Łańcuchowego

Wartość domyślna: X509ChainTrustMode.System

Certyfikat przedstawiony przez klienta musi być połączony z zaufanym certyfikatem nadrzędnym. Ta kontrola określa, który magazyn zaufania zawiera te certyfikaty główne.

Domyślnie obsługa używa systemowego magazynu zaufania. Jeśli przedstawiony certyfikat klienta musi połączyć łańcuch z certyfikatem głównym, który nie jest wyświetlany w magazynie zaufania systemu, możesz ustawić opcję X509ChainTrustMode.CustomRootTrust, aby procedura obsługi używała CustomTrustStore właściwości.

CustomTrustStore

Wartość domyślna: Pusta X509Certificate2Collection

Jeśli właściwość programu obsługi ChainTrustValidationMode jest ustawiona na X509ChainTrustMode.CustomRootTrust, ten X509Certificate2Collection obiekt zawiera każdy certyfikat używany do weryfikowania certyfikatu klienta do zaufanego korzenia, w tym zaufanego korzenia.

Gdy klient przedstawia certyfikat, który jest częścią wielo poziomowego łańcucha certyfikatów, CustomTrustStore właściwość musi zawierać każdy certyfikat wystawiający w łańcuchu.

WalidacjaUżyciaCertyfikatu

Wartość domyślna: true

To sprawdzenie weryfikuje, czy certyfikat przedstawiony przez klienta ma rozszerzone użycie klucza uwierzytelniania klienta (EKU) lub w ogóle nie ma rozszerzonego użycia kluczy (EKU). Zgodnie ze specyfikacjami, jeśli nie określono EKU, wszystkie EKU są uznawane za prawidłowe.

SprawdźOkresWażności

Wartość domyślna: true

Ta kontrola sprawdza, czy certyfikat znajduje się w okresie ważności. W każdym żądaniu program obsługi gwarantuje, że certyfikat, który był ważny, gdy został przedstawiony, nie wygasł podczas bieżącej sesji.

OdwołanieFlag

Wartość domyślna: X509RevocationFlag.ExcludeRoot

Flaga określająca, które certyfikaty w łańcuchu są sprawdzane pod kątem odwołania.

Kontrole odwołania są wykonywane tylko wtedy, gdy certyfikat jest powiązany z certyfikatem głównym.

Tryb Odwołania

Wartość domyślna: X509RevocationMode.Online

Flaga określająca sposób przeprowadzania kontroli odwołania.

Określenie sprawdzania online może spowodować duże opóźnienie podczas kontaktowania się z urzędem certyfikacji.

Kontrole odwołania są wykonywane tylko wtedy, gdy certyfikat jest w łańcuchu certyfikatu głównego.

Często zadawane pytania: Czy mogę skonfigurować aplikację tak, aby wymagała certyfikatu tylko w niektórych ścieżkach?

Takie podejście nie jest możliwe. Wymiana certyfikatów jest zakończona na początku konwersacji HTTPS. Operacja jest wykonywana przez serwer przed odebraniem pierwszego żądania w tym połączeniu, więc nie można ograniczyć zakresu na podstawie żadnych pól żądania.

Zdarzenia obsługi procesów

Obsługujący ma dwa zdarzenia:

  • OnAuthenticationFailed: Wywoływane, jeśli podczas uwierzytelniania wystąpi wyjątek i można zareagować.

  • OnCertificateValidated: Wywoływany po zweryfikowaniu certyfikatu, zakończeniu weryfikacji i utworzeniu domyślnego podmiotu zabezpieczeń. To wydarzenie pozwala na wykonanie własnej walidacji oraz rozszerzenie lub zastąpienie principalu. Oto kilka przykładów:

    • Określanie, czy certyfikat jest znany twoim usługom.

    • Konstruowanie własnego podmiotu, jak w poniższym przykładzie:

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

Jeśli certyfikat przychodzący nie spełnia dodatkowej weryfikacji, wywołaj context.Fail("failure reason") z przyczyną niepowodzenia.

Aby uzyskać lepszą funkcjonalność, wywołaj usługę zarejestrowaną we wstrzykiwaniu zależności, która łączy się z bazą danych lub innym typem repozytorium użytkowników. Uzyskaj dostęp do usługi, używając kontekstu, który został przekazany do delegata. Rozważmy następujący przykład:

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

Koncepcyjnie weryfikacja certyfikatu jest problemem autoryzacji. Na przykład można dodać sprawdzanie wystawcy lub odcisku palca w zasadach autoryzacji, a nie wewnątrz OnCertificateValidated programu obsługi.

Konfigurowanie serwera w celu wymagania certyfikatów

W poniższych sekcjach opisano sposób konfigurowania serwera pod kątem wymagania certyfikatów dla określonego rozwiązania, w tym Kestrel, IIS, Azure, niestandardowych serwerów proxy sieci Web i Azure Web Apps.

Kestrel

W pliku Program.cs skonfiguruj Kestrel w następujący sposób:

var builder = WebApplication.CreateBuilder(args);

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

Uwaga

Utworzenie punktu końcowego metodą Listen przed wywołaniem metody , oznacza że do punktu końcowego nie są zastosowane wartości domyślne.

IIS

Wykonaj następujące kroki w Menedżerze usług IIS:

  1. Na karcie Połączenia wybierz witrynę.
  2. W oknie Widok funkcji kliknij dwukrotnie pozycję Ustawienia protokołu SSL.
  3. Zaznacz pole wyboru Wymagaj protokołu SSL .
  4. Dla opcji Certyfikaty klienta wybierz pozycję Wymagaj.

Zrzut ekranu przedstawiający sposób konfigurowania ustawień certyfikatu klienta w usługach IIS.

Azure i niestandardowe serwery proxy sieci Web

Aby uzyskać więcej informacji na temat konfigurowania middleware do przesyłania certyfikatów, zobacz dokumentację dotyczącą hosta i wdrażania.

Uwierzytelnianie certyfikatów w Azure Web Apps

Dla platformy Azure nie jest wymagana żadna konfiguracja przekazywania dalej. Oprogramowanie pośredniczące przekazujące certyfikaty konfiguruje konfigurację.

Uwaga

W tym scenariuszu jest wymagane oprogramowanie pośredniczące przekazujące certyfikaty.

Aby uzyskać więcej informacji, zobacz Użyj certyfikaty TLS/SSL w kodzie aplikacji (dokumentacja Azure).

Uwierzytelnianie certyfikatów w niestandardowych internetowych serwerach proxy

Metoda AddCertificateForwarding jest używana do określania:

  • Nazwa nagłówka klienta.
  • Jak załadować certyfikat (za pośrednictwem HeaderConverter właściwości).

W niestandardowych internetowych serwerach proxy certyfikat jest przekazywany jako niestandardowy nagłówek żądania, na przykład X-SSL-CERT. Aby użyć certyfikatu, skonfiguruj przekazywanie certyfikatów w pliku Program.cs :

builder.Services.AddCertificateForwarding(options =>
{
    options.CertificateHeader = "X-SSL-CERT";

    options.HeaderConverter = headerValue =>
    {
        X509Certificate2? clientCertificate = null;

        if (!string.IsNullOrWhiteSpace(headerValue))
        {
            clientCertificate = new X509Certificate2(StringToByteArray(headerValue));
        }

        return clientCertificate!;

        static byte[] StringToByteArray(string hex)
        {
            var numberChars = hex.Length;
            var bytes = new byte[numberChars / 2];

            for (int i = 0; i < numberChars; i += 2)
            {
                bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
            }

            return bytes;
        }
    };
});

Jeśli NGINX jest używany z konfiguracją proxy_set_header ssl-client-cert $ssl_client_escaped_cert jako reverse proxy dla aplikacji lub aplikacja jest wdrażana na platformie Kubernetes za pomocą NGINX Ingress, certyfikat klienta jest przekazywany do aplikacji w postaci zakodowanej w formacie URL. Aby użyć certyfikatu, zdekoduj go w następujący sposób:

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

Dodaj oprogramowanie pośredniczące w pliku Program.cs . UseCertificateForwarding Metoda jest wywoływana przed wywołaniami metod UseAuthentication i UseAuthorization

var app = builder.Build();

app.UseCertificateForwarding();

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

Oddzielna klasa może służyć do implementowania logiki walidacji. Ponieważ w tym przykładzie jest używany ten sam certyfikat z podpisem własnym, upewnij się, że można używać tylko certyfikatu. Sprawdź, czy odciski palca certyfikatu klienta i certyfikatu serwera są zgodne. Alternatywnie, można użyć dowolnego certyfikatu, który będzie wystarczający do uwierzytelniania. Certyfikat jest następnie używany wewnątrz AddCertificate metody . Możesz również zweryfikować podmiot lub wystawcę w tym miejscu, jeśli używasz certyfikatów pośrednich lub podrzędnych.

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

Implementowanie klienta HttpClient przy użyciu certyfikatu i IHttpClientFactory

W poniższym przykładzie certyfikat klienta jest dodawany do obiektu HttpClientHandler przy użyciu ClientCertificates właściwości z programu obsługi. Ta procedura obsługi może być następnie używana w nazwanym wystąpieniu HttpClient z metodą ConfigurePrimaryHttpMessageHandler. Ten scenariusz jest skonfigurowany w pliku Program.cs :

var clientCertificate =
    new X509Certificate2(
      Path.Combine(_environment.ContentRootPath, "sts_dev_cert.pfx"), "1234");

builder.Services.AddHttpClient("namedClient", c =>
{
}).ConfigurePrimaryHttpMessageHandler(() =>
{
    var handler = new HttpClientHandler();
    handler.ClientCertificates.Add(clientCertificate);
    return handler;
});

Następnie IHttpClientFactory można użyć do pobrania nazwanego wystąpienia za pomocą obsługi i certyfikatu. Metoda CreateClient, której nazwa odpowiada klientowi zdefiniowanemu w pliku Program.cs, służy do pobierania wystąpienia. Żądanie HTTP można wysłać przy użyciu klienta zgodnie z wymaganiami:

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

Jeśli prawidłowy certyfikat jest wysyłany do serwera, dane są zwracane. Jeśli żaden certyfikat lub nieprawidłowy certyfikat nie zostanie wysłany, serwer zwróci kod stanu HTTP 403.

Certyfikaty w programie PowerShell

Tworzenie certyfikatów jest najtrudniejszą częścią konfigurowania tego przepływu. Certyfikat główny można utworzyć przy użyciu New-SelfSignedCertificate polecenia cmdlet programu PowerShell. Podczas tworzenia certyfikatu użyj silnego hasła. Należy dodać parametr KeyUsageProperty i parametr KeyUsage, zgodnie z przykładami.

Tworzenie nadrzędnego urzędu certyfikacji

Poniższy kod pokazuje, jak utworzyć urząd certyfikacji (CA) jako nadrzędny:

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

Uwaga

Wartość parametru musi być zgodna -DnsName z docelowym wdrożeniem aplikacji. Na przykład "localhost" na potrzeby programowania.

Instalowanie w zaufanym katalogu głównym

Certyfikat root musi być uznawany za zaufany przez system hosta. Domyślnie zaufane są tylko certyfikaty główne utworzone przez urząd certyfikacji. Aby uzyskać informacje na temat zaufania certyfikatu głównego w Windows, zobacz dokumentację Windows lub polecenie Import-Certificate cmdlet PowerShell.

Używanie certyfikatu pośredniego

Certyfikat pośredniczący można teraz utworzyć na podstawie certyfikatu głównego. Takie podejście nie jest wymagane w przypadku wszystkich przypadków użycia, ale może być konieczne utworzenie wielu certyfikatów lub konieczność aktywowania lub wyłączania grup certyfikatów. Parametr TextExtension jest wymagany do ustawienia długości ścieżki w podstawowych ograniczeniach certyfikatu.

Certyfikat pośredniczący można następnie dodać do zaufanego certyfikatu pośredniego w systemie hosta systemu Windows.

$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

Tworzenie certyfikatu podrzędnego na podstawie certyfikatu pośredniego

Certyfikat podrzędny można utworzyć na podstawie certyfikatu pośredniego. Ten certyfikat podrzędny jest jednostką końcową. Nie trzeba tworzyć większej liczby certyfikatów podrzędnych.

$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

Tworzenie certyfikatu podrzędnego na podstawie certyfikatu głównego

Certyfikat podrzędny można również utworzyć bezpośrednio na podstawie certyfikatu głównego.

$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

Przykład: root — certyfikat pośredni — certyfikat

W poniższym przykładzie przedstawiono konfigurację głównego urzędu certyfikacji, certyfikatu pośredniego i certyfikatu podrzędnego:

$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

Jeśli używasz certyfikatów głównych, pośrednich lub podrzędnych, certyfikaty można zweryfikować przy użyciu odcisku palca lub klucza publicznego zgodnie z wymaganiami:

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

Wyniki weryfikacji certyfikatu pamięci podręcznej

.NET 5 i nowsze wersje obsługuje włączenie buforowania wyników weryfikacji. Buforowanie znacznie poprawia wydajność uwierzytelniania certyfikatu, ponieważ walidacja jest kosztowną operacją.

Domyślnie uwierzytelnianie certyfikatu wyłącza buforowanie. Aby włączyć buforowanie, wywołaj metodę AddCertificateCache w pliku Program.cs :

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

Gdy włączone jest AddCertificateCache, wynik zwracany przez OnCertificateValidated musi być czystą funkcją schematu uwierzytelniania i certyfikatu. Nie bazuj na wyniku w kontekście żądania, ponieważ buforowany wynik jest ponownie używany dla kolejnych żądań. Umieść decyzje zależne od kontekstu żądania w zasadzie autoryzacji lub zapewnij implementację ICertificateValidationCache, która uwzględnia kontekst.

Pamięć podręczna przechowuje zarówno pomyślne, jak i nieudane wyniki weryfikacji przez skonfigurowany czas życia wpisu w pamięci podręcznej.

Domyślna implementacja buforowania przechowuje wyniki w pamięci. Możesz udostępnić własną pamięć podręczną, implementując ICertificateValidationCache i rejestrując ją za pomocą iniekcji zależności. Na przykład services.AddSingleton<ICertificateValidationCache, YourCache>().

Używanie opcjonalnych certyfikatów klienta

Ta sekcja zawiera informacje dotyczące aplikacji, które muszą chronić podzbiór aplikacji przy użyciu certyfikatu. Na przykład Razor strona lub kontroler w aplikacji może wymagać certyfikatów klienta. W tym scenariuszu występują pewne wyzwania:

  • Certyfikaty klienta są funkcją PROTOKOŁU TLS, a nie funkcją PROTOKOŁU HTTP.
  • Certyfikaty klienta są negocjowane na połączenie i zwykle na początku połączenia przed udostępnieniem jakichkolwiek danych HTTP.

Istnieją dwa podejścia do implementowania opcjonalnych certyfikatów klienta:

  • Opcja 1. Użyj oddzielnych nazw hostów (SNI) i przekierowania. Chociaż ta opcja wymaga więcej pracy w celu skonfigurowania, zalecane jest podejście, ponieważ działa w większości środowisk i protokołów.
  • Opcja 2. Renegocjacja podczas żądania HTTP. Takie podejście ma kilka ograniczeń i nie jest zalecane.

Oddzielne hosty (SNI)

Na początku połączenia znana jest tylko nazwa nazwy serwera (SNI) †. Certyfikaty klienta można skonfigurować na nazwę hosta, aby jeden host wymagał certyfikatów, a inny nie.

† oznaczanie nazwy serwera (SNI) to rozszerzenie protokołu TLS używane do uwzględnienia domeny wirtualnej w ramach negocjacji protokołu SSL. Takie podejście skutecznie oznacza, że nazwa domeny wirtualnej lub nazwa hosta może służyć do identyfikowania punktu końcowego sieci.

Renegocjacja protokołu TLS

Renegocjacja protokołu TLS to proces, za pomocą którego klient i serwer mogą ponownie ocenić wymagania dotyczące szyfrowania dla pojedynczego połączenia, w tym żądania certyfikatu klienta, jeśli nie zostały wcześniej podane. Renegocjacja protokołu TLS jest zagrożeniem bezpieczeństwa i nie jest zalecana, ponieważ:

  • W przypadku protokołu HTTP/1.1 serwer musi najpierw buforować lub przetworzyć dowolne dane HTTP w locie, takie jak treść żądania POST, aby zapewnić, że połączenie jest gotowe do renegocjacji. W przeciwnym razie renegocjacja może przestać odpowiadać lub zakończyć się niepowodzeniem.
  • Protokół HTTP/2 i HTTP/3 jawnie uniemożliwiają renegocjację.
  • Istnieją zagrożenia bezpieczeństwa związane z renegocjacją. Protokół TLS 1.3 usunął renegocjację całego połączenia i zastąpił go nowym rozszerzeniem żądania tylko certyfikatu klienta po rozpoczęciu połączenia. Ten mechanizm jest ujawniany za pośrednictwem tych samych interfejsów API i nadal podlega wcześniejszym ograniczeniom buforowania i wersji protokołu HTTP.

Implementacja i konfiguracja tej funkcji różni się w zależności od wersji serwera i platformy, zgodnie z opisem w poniższych sekcjach.

IIS

Usługi IIS zarządzają negocjowaniem certyfikatu klienta w Twoim imieniu. Podsekcja aplikacji może umożliwić włączenie opcji SslRequireCert, aby negocjować certyfikat klienta dla tych żądań. Aby uzyskać więcej informacji, zobacz Konfiguracja w dokumentacji usług IIS.

Usługi IIS automatycznie buforuje wszystkie dane treści żądania do skonfigurowanego limitu rozmiaru przed ponownym negocjowaniem. Żądania przekraczające limit są odrzucane z odpowiedzią 413. Ten limit domyślnie wynosi 48 KB i można go skonfigurować, ustawiając właściwość uploadReadAheadSize .

HttpSys

Usługa HttpSys ma dwa ustawienia, które kontrolują negocjacje certyfikatu klienta i powinny być ustawione. Pierwszy znajduje się w pliku netsh.exe w obszarze http add sslcert clientcertnegotiation=enable/disable. Ta flaga wskazuje, czy negocjować certyfikat klienta na początku połączenia. Ustaw wartość na disable dla opcjonalnych certyfikatów klienta. Aby uzyskać więcej informacji, zobacz użycie parametrów http add sslcert w dokumentacji netsh.

Inne ustawienie to ClientCertificateMethod właściwość. Po ustawieniu AllowRenegotation certyfikat klienta można renegocjować podczas żądania.

Uwaga

Aplikacja powinna buforować lub wykorzystywać wszelkie dane treści żądania przed podjęciem próby renegocjacji. W przeciwnym razie żądanie może nie odpowiadać.

Aplikacja może najpierw sprawdzić ClientCertificate właściwość, aby sprawdzić, czy certyfikat jest dostępny. Jeśli nie jest dostępna, upewnij się, że treść żądania zostanie przetworzona przed wywołaniem metody GetClientCertificateAsync. GetClientCertificateAsync program może zwrócić certyfikat o wartości null, jeśli klient odmówi podania certyfikatu.

Uwaga

Zachowanie właściwości ClientCertificate zostało zmienione w .NET 6. Aby uzyskać więcej informacji, zobacz GitHub problem #466.

Kestrel

Kestrel steruje negocjowaniem certyfikatu klienta z właściwością ClientCertificateMode .

.NET 6 i nowszych udostępnia opcję DelayCertificate dla właściwości ClientCertificateMode. Po ustawieniu tej opcji aplikacja może sprawdzić ClientCertificate właściwość, aby sprawdzić, czy certyfikat jest dostępny. Jeśli nie jest dostępna, upewnij się, że treść żądania zostanie przetworzona przed wywołaniem metody GetClientCertificateAsync. GetClientCertificateAsync program może zwrócić certyfikat o wartości null, jeśli klient odmówi podania certyfikatu.

Uwaga

Aplikacja powinna buforować lub wykorzystywać wszelkie dane treści żądania przed podjęciem próby renegocjacji. W przeciwnym razie, GetClientCertificateAsync może zgłosić wyjątek InvalidOperationException: Strumień klienta musi zostać opróżniony przed renegocjacją.

Jeśli programowo skonfigurujesz ustawienia protokołu TLS dla nazwy hosta SNI, wywołaj UseHttps przeciążenie (.NET 6 lub nowsze), które przyjmuje obiekt klasy TlsHandshakeCallbackOptions. Ta opcja steruje renegocjacją certyfikatu klienta za pośrednictwem AllowDelayedClientCertificateNegotation właściwości . Aby uzyskać więcej informacji, zobacz metodę ListenOptionsHttpsExtensions.UseHttps .

Microsoft.AspNetCore.Authentication.Certificate zawiera implementację podobną do uwierzytelniania certyfikatów dla ASP.NET Core. Uwierzytelnianie certyfikatu odbywa się na poziomie protokołu TLS, który występuje na długo przed rozpoczęciem ASP.NET Core. Dokładniej, ten zarządca uwierzytelniania weryfikuje certyfikat i udostępnia zdarzenie, w którym można przekształcić certyfikat do wartości ClaimsPrincipal.

Skonfiguruj serwer pod kątem uwierzytelniania certyfikatów, niezależnie od tego, czy jest to usługa IIS, Kestrelusługa Azure Web Apps, czy cokolwiek innego, którego używasz.

Scenariusze serwera proxy i modułu równoważenia obciążenia

Uwierzytelnianie certyfikatu jest scenariuszem stanowym używanym głównie w przypadku, gdy serwer proxy lub moduł równoważenia obciążenia nie obsługuje ruchu między klientami i serwerami. Jeśli używany jest serwer proxy lub moduł równoważenia obciążenia, uwierzytelnianie certyfikatu działa tylko wtedy, gdy serwer proxy lub moduł równoważenia obciążenia:

  • Obsługuje uwierzytelnianie.
  • Przekazuje informacje o uwierzytelnianiu użytkownika do aplikacji (na przykład w nagłówku żądania), która działa na podstawie informacji uwierzytelniania.

Alternatywą dla uwierzytelniania certyfikatów w środowiskach, w których są używane serwery proxy i moduły równoważenia obciążenia, jest usługa Active Directory Federated Services (ADFS) z protokołem OpenID Connect (OIDC).

Wprowadzenie

Uzyskaj certyfikat HTTPS, zastosuj go i skonfiguruj serwer tak, aby wymagał certyfikatów.

W aplikacji internetowej dodaj odwołanie do pakietu Microsoft.AspNetCore.Authentication.Certificate . Następnie w metodzie Startup.ConfigureServices wywołaj metodę services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...); z opcjami, podając pełnomocnika do OnCertificateValidated wykonania dodatkowej weryfikacji certyfikatu klienta wysłanego z żądaniami. Zmień te informacje w ClaimsPrincipal i ustaw je na właściwość context.Principal.

Jeśli uwierzytelnianie zakończy się niepowodzeniem, ta procedura obsługi zwróci odpowiedź 403 (Forbidden) zamiast 401 (Unauthorized), czego można się spodziewać. Przyczyną jest to, że uwierzytelnianie powinno nastąpić podczas początkowego połączenia TLS. Zanim dotrze do obsługującego, jest już za późno. Nie ma możliwości uaktualnienia połączenia z połączenia anonimowego do jednego z certyfikatem.

Dodaj również app.UseAuthentication(); w metodzie Startup.Configure. W przeciwnym razie HttpContext.User nie zostanie ustawiony na ClaimsPrincipal utworzony na podstawie certyfikatu. Na przykład:

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
}

W poprzednim przykładzie pokazano domyślny sposób dodawania uwierzytelniania certyfikatu. Obsługiwacz konstruuje główną domenę użytkownika przy użyciu typowych właściwości certyfikatu.

Konfigurowanie weryfikacji certyfikatu

Obsługa CertificateAuthenticationOptions posiada kilka wbudowanych walidacji, które stanowią minimalne walidacje certyfikatu. Każde z tych ustawień jest domyślnie włączone.

AllowedCertificateTypes = Łańcuchowe, Samopodpisane lub Wszystkie (Łańcuchowe | Samopodpisane)

Wartość domyślna: CertificateTypes.Chained

To sprawdzenie sprawdza, czy dozwolony jest tylko odpowiedni typ certyfikatu. Jeśli aplikacja korzysta z certyfikatów z podpisem własnym, ta opcja musi być ustawiona na CertificateTypes.All lub CertificateTypes.SelfSigned.

WalidacjaUżyciaCertyfikatu

Wartość domyślna: true

To sprawdzenie weryfikuje, czy certyfikat przedstawiony przez klienta ma rozszerzone użycie klucza uwierzytelniania klienta (EKU) lub w ogóle nie ma rozszerzonego użycia kluczy (EKU). Zgodnie ze specyfikacjami, jeśli nie określono EKU, wszystkie EKU są uznawane za prawidłowe.

SprawdźOkresWażności

Wartość domyślna: true

Ta kontrola sprawdza, czy certyfikat znajduje się w okresie ważności. W każdym żądaniu program obsługi gwarantuje, że certyfikat, który był ważny, gdy został przedstawiony, nie wygasł podczas bieżącej sesji.

OdwołanieFlag

Wartość domyślna: X509RevocationFlag.ExcludeRoot

Flaga określająca, które certyfikaty w łańcuchu są sprawdzane pod kątem odwołania.

Kontrole odwołania są wykonywane tylko wtedy, gdy certyfikat jest powiązany z certyfikatem głównym.

Tryb Odwołania

Wartość domyślna: X509RevocationMode.Online

Flaga określająca sposób przeprowadzania kontroli odwołania.

Określenie sprawdzania online może spowodować duże opóźnienie podczas kontaktowania się z urzędem certyfikacji.

Kontrole odwołania są wykonywane tylko wtedy, gdy certyfikat jest powiązany z certyfikatem głównym.

Czy mogę skonfigurować aplikację tak, aby wymagała certyfikatu tylko w określonych ścieżkach?

Nie jest to możliwe. Pamiętaj, że wymiana certyfikatów odbywa się na początku konwersacji HTTPS, jest wykonywana przez serwer przed odebraniem pierwszego żądania na tym połączeniu, aby nie można było ograniczyć zakresu na podstawie pól żądania.

Zdarzenia obsługi

Obsługujący ma dwa zdarzenia:

  • OnAuthenticationFailed: Wywoływane, jeśli podczas uwierzytelniania wystąpi wyjątek i można zareagować.
  • OnCertificateValidated: Wywoływany po tym, jak certyfikat został zweryfikowany, przeszedł pomyślnie weryfikację i utworzono domyślnego podmiotu głównego. To wydarzenie pozwala na wykonanie własnej walidacji oraz rozszerzenie lub zastąpienie principalu. Przykłady obejmują:
    • Określanie, czy certyfikat jest znany twoim usługom.

    • Tworzenie własnej głównej encji. Rozważmy następujący przykład w pliku 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;
                  }
              };
          });
      

Jeśli okaże się, że certyfikat przychodzący nie spełnia dodatkowej walidacji, skontaktuj się z context.Fail("failure reason") podając przyczynę niepowodzenia.

W przypadku rzeczywistej funkcjonalności prawdopodobnie będzie konieczne użycie usługi zarejestrowanej w iniekcji zależności, która łączy się z bazą danych lub innym typem źródła danych użytkownika. Uzyskaj dostęp do usługi przy użyciu kontekstu przekazanego do pełnomocnika. Rozważmy następujący przykład w pliku 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;
            }
        };
    });

Koncepcyjnie weryfikacja certyfikatu jest problemem autoryzacji. Dodanie sprawdzania, na przykład wystawcy lub odcisku palca w zasadach autoryzacji, a nie wewnątrz elementu OnCertificateValidated, jest całkowicie akceptowalne.

Konfigurowanie serwera w celu wymagania certyfikatów

Kestrel

W Program.cs programie skonfiguruj Kestrel w następujący sposób:

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

Uwaga

Punkty końcowe utworzone przez wywołanie Listen przed wywołaniem nie będą miały zastosowanych wartości domyślnych.

IIS

Wykonaj następujące kroki w Menedżerze usług IIS:

  1. Wybierz swoją witrynę na karcie Połączenia .
  2. Kliknij dwukrotnie na Ustawienia SSL w oknie Widok funkcji.
  3. Zaznacz pole wyboru Wymagaj protokołu SSL i wybierz przycisk radiowy Wymagaj w sekcji Certyfikaty klienta.

Ustawienia certyfikatu klienta w usługach IIS

Azure i niestandardowe serwery proxy sieci Web

Zapoznaj się z dokumentacją dotyczącą hostowania i wdrażania, aby dowiedzieć się, jak skonfigurować middleware pośredniczące do przesyłania certyfikatów.

Używanie uwierzytelniania certyfikatów w usłudze Azure Web Apps

Dla platformy Azure nie jest wymagana żadna konfiguracja przekazywania dalej. Konfiguracja przekazywania jest konfigurowana przez oprogramowanie pośredniczące przekazujące certyfikaty.

Uwaga

W tym scenariuszu jest wymagane oprogramowanie pośredniczące przekazujące certyfikaty.

Aby uzyskać więcej informacji, zobacz Używanie certyfikatu TLS/SSL w kodzie w usłudze aplikacja systemu Azure (dokumentacja platformy Azure).

Używanie uwierzytelniania certyfikatów w niestandardowych internetowych serwerach proxy

Metoda AddCertificateForwarding jest używana do określania:

  • Nazwa nagłówka klienta.
  • Sposób ładowania certyfikatu (przy użyciu HeaderConverter właściwości ).

W niestandardowych internetowych serwerach proxy certyfikat jest przekazywany jako niestandardowy nagłówek żądania, na przykład X-SSL-CERT. Aby go użyć, skonfiguruj przekazywanie certyfikatów w programie 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;
}

Jeśli aplikacja jest odwrotnie proxy’owana przez serwer NGINX z konfiguracją proxy_set_header ssl-client-cert $ssl_client_escaped_cert lub wdrożona na platformie Kubernetes przy użyciu modułu NGINX Ingress, certyfikat klienta jest przekazywany do aplikacji w formacie zakodowanym URL. Aby użyć certyfikatu, zdekoduj go w następujący sposób:

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

Metoda Startup.Configure następnie dodaje oprogramowanie pośredniczące. UseCertificateForwarding jest wywoływany przed wywołaniami UseAuthentication i UseAuthorization.

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

    app.UseRouting();

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

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

Oddzielna klasa może służyć do implementowania logiki walidacji. Ponieważ w tym przykładzie jest używany ten sam certyfikat z podpisem własnym, upewnij się, że można używać tylko certyfikatu. Sprawdź, czy odciski palca certyfikatu klienta i certyfikatu serwera są zgodne, w przeciwnym razie można użyć dowolnego certyfikatu i będzie wystarczający do uwierzytelnienia. Będzie to używane wewnątrz AddCertificate metody. Możesz również zweryfikować podmiot lub wystawcę w tym miejscu, jeśli używasz certyfikatów pośrednich lub podrzędnych.

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

Implementowanie klienta HttpClient przy użyciu certyfikatu i programu HttpClientHandler

Element HttpClientHandler można dodać bezpośrednio w konstruktorze HttpClient klasy . Należy zachować ostrożność podczas tworzenia wystąpień klasy HttpClient. Następnie HttpClient program wyśle certyfikat z każdym żądaniem.

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

Implementowanie klienta HttpClient przy użyciu certyfikatu i nazwanego obiektu HttpClient z elementu IHttpClientFactory

W poniższym przykładzie certyfikat klienta jest dodawany do HttpClientHandler przy użyciu właściwości ClientCertificates z obsługi. Ta procedura obsługi może być używana w nazwanym wystąpieniu HttpClient, używając metody ConfigurePrimaryHttpMessageHandler. To jest skonfigurowane w Startup.ConfigureServices:

var clientCertificate = 
    new X509Certificate2(
      Path.Combine(_environment.ContentRootPath, "sts_dev_cert.pfx"), "1234");

services.AddHttpClient("namedClient", c =>
{
}).ConfigurePrimaryHttpMessageHandler(() =>
{
    var handler = new HttpClientHandler();
    handler.ClientCertificates.Add(clientCertificate);
    return handler;
});

Następnie IHttpClientFactory można użyć do pobrania nazwanego wystąpienia za pomocą obsługi i certyfikatu. Metoda CreateClient o nazwie klienta zdefiniowana w klasie Startup jest używana do pobierania instancji. Żądanie HTTP można wysłać przy użyciu klienta zgodnie z potrzebami.

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

Jeśli prawidłowy certyfikat jest wysyłany do serwera, dane są zwracane. Jeśli żaden certyfikat lub nieprawidłowy certyfikat nie zostanie wysłany, zostanie zwrócony kod stanu HTTP 403.

Tworzenie certyfikatów w programie PowerShell

Tworzenie certyfikatów jest najtrudniejszą częścią konfigurowania tego przepływu. Certyfikat główny można utworzyć przy użyciu New-SelfSignedCertificate polecenia cmdlet programu PowerShell. Podczas tworzenia certyfikatu użyj silnego hasła. Ważne jest dodanie parametru KeyUsageProperty i parametru KeyUsage , jak pokazano poniżej.

Utwórz główny urząd certyfikacji (root CA)

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

Uwaga

Wartość parametru musi być zgodna -DnsName z docelowym wdrożeniem aplikacji. Na przykład "localhost" na potrzeby programowania.

Instalowanie w zaufanym katalogu głównym

Certyfikat główny (root certificate) musi być zaufany w systemie hostującym. Certyfikat główny, który nie został utworzony przez urząd certyfikacji, nie będzie domyślnie uznawany za zaufany. Aby uzyskać informacje na temat zaufania certyfikatowi głównemu w systemie Windows, zobacz to pytanie.

Certyfikat pośredni

Certyfikat pośredniczący można teraz utworzyć na podstawie certyfikatu głównego. Nie jest to wymagane w przypadku wszystkich przypadków użycia, ale może być konieczne utworzenie wielu certyfikatów lub konieczność aktywowania lub wyłączania grup certyfikatów. Parametr TextExtension jest wymagany do ustawienia długości ścieżki w podstawowych ograniczeniach certyfikatu.

Certyfikat pośredniczący można następnie dodać do zaufanego certyfikatu pośredniego w systemie hosta systemu Windows.

$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

Tworzenie certyfikatu podrzędnego na podstawie certyfikatu pośredniego

Certyfikat podrzędny można utworzyć na podstawie certyfikatu pośredniego. Jest to jednostka końcowa i nie musi tworzyć większej liczby certyfikatów podrzędnych.

$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

Tworzenie certyfikatu podrzędnego na podstawie certyfikatu głównego

Certyfikat podrzędny można również utworzyć bezpośrednio na podstawie certyfikatu głównego.

$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

Przykładowy certyfikat główny — certyfikat pośredni — certyfikat

$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

W przypadku używania certyfikatów głównych, pośrednich lub podrzędnych certyfikaty można zweryfikować przy użyciu odcisku palca lub klucza publicznego zgodnie z wymaganiami.

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

Buforowanie weryfikacji certyfikatu

Platforma .NET 5 lub nowsza wersja obsługuje możliwość włączania buforowania wyników walidacji. Buforowanie znacznie poprawia wydajność uwierzytelniania certyfikatu, ponieważ walidacja jest kosztowną operacją.

Domyślnie uwierzytelnianie certyfikatu wyłącza buforowanie. Aby włączyć buforowanie, wywołaj AddCertificateCache w Startup.ConfigureServices:

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

Gdy włączono AddCertificateCache, wynik generowany przez OnCertificateValidated musi być czystą funkcją metody uwierzytelniania i certyfikatu. Nie bazuj na wyniku w kontekście żądania, ponieważ buforowany wynik jest ponownie używany dla kolejnych żądań. Umieść decyzje zależne od kontekstu żądania w zasadzie autoryzacji lub zapewnij implementację ICertificateValidationCache uwzględniającą kontekst.

Pamięć podręczna przechowuje zarówno udane, jak i nieudane wyniki walidacji przez skonfigurowany okres ważności wpisu pamięci podręcznej.

Domyślna implementacja buforowania przechowuje wyniki w pamięci. Możesz udostępnić własną pamięć podręczną, implementując ICertificateValidationCache i rejestrując ją za pomocą iniekcji zależności. Na przykład services.AddSingleton<ICertificateValidationCache, YourCache>().

Opcjonalne certyfikaty klienta

Ta sekcja zawiera informacje dotyczące aplikacji, które muszą chronić podzbiór aplikacji przy użyciu certyfikatu. Na przykład Razor strona lub kontroler w aplikacji może wymagać certyfikatów klienta. Stanowi to wyzwania związane z certyfikatami klienta:

  • To funkcja PROTOKOŁU TLS, a nie funkcja HTTP.
  • Są negocjowane dla połączenia i zwykle na początku połączenia przed udostępnieniem jakichkolwiek danych HTTP.

Istnieją dwa podejścia do implementowania opcjonalnych certyfikatów klienta:

  1. Używanie oddzielnych nazw hostów (SNI) i przekierowywanie. Mimo że wymaga więcej pracy przy konfiguracji, jest to zalecane, ponieważ działa w większości środowisk i protokołów.
  2. Renegocjacja podczas żądania HTTP. Ma to kilka ograniczeń i nie jest zalecane.

Oddzielne hosty (SNI)

Na początku połączenia znana jest tylko nazwa nazwy serwera (SNI) †. Certyfikaty klienta można skonfigurować na nazwę hosta, tak aby jeden host ich wymagał, a inny nie.

Platforma .NET 5 lub nowsza dodaje wygodną obsługę przekierowywania w celu uzyskania opcjonalnych certyfikatów klienta. Aby uzyskać więcej informacji, zobacz Przykład opcjonalnych certyfikatów.

  • W przypadku żądań do aplikacji internetowej, które wymagają certyfikatu klienta i których nie ma:
    • Przekieruj do tej samej strony przy użyciu poddomeny chronionej certyfikatem klienta.
    • Na przykład przekieruj do myClient.contoso.com/requestedPage. Ponieważ żądanie do myClient.contoso.com/requestedPage jest inną nazwą hosta niż contoso.com/requestedPage, klient ustanawia inne połączenie, a certyfikat klienta jest dostarczany.
    • Aby uzyskać więcej informacji, zobacz Wprowadzenie do autoryzacji w programie ASP.NET Core.

† oznaczanie nazwy serwera (SNI) to rozszerzenie protokołu TLS do uwzględnienia domeny wirtualnej w ramach negocjacji protokołu SSL. Oznacza to, że nazwa domeny wirtualnej lub nazwa hosta może służyć do identyfikowania punktu końcowego sieci.

Renegocjacja

Renegocjacja protokołu TLS to proces, za pomocą którego klient i serwer mogą ponownie ocenić wymagania dotyczące szyfrowania dla pojedynczego połączenia, w tym żądania certyfikatu klienta, jeśli nie zostały wcześniej podane. Renegocjacja protokołu TLS jest zagrożeniem bezpieczeństwa i nie jest zalecana, ponieważ:

  • W przypadku protokołu HTTP/1.1 serwer musi najpierw buforować lub przetworzyć wszelkie dane HTTP, które są w locie, takie jak treść żądania POST, aby upewnić się, że połączenie jest gotowe do renegocjacji. W przeciwnym razie renegocjacja może przestać odpowiadać lub zakończyć się niepowodzeniem.
  • Protokół HTTP/2 i HTTP/3 jawnie uniemożliwiają renegocjację.
  • Istnieją zagrożenia bezpieczeństwa związane z renegocjacją. Protokół TLS 1.3 usunął renegocjację całego połączenia i zastąpił go nowym rozszerzeniem żądania tylko certyfikatu klienta po rozpoczęciu połączenia. Ten mechanizm jest ujawniany za pośrednictwem tych samych interfejsów API i nadal podlega wcześniejszym ograniczeniom buforowania i wersji protokołu HTTP.

Implementacja i konfiguracja tej funkcji różni się w zależności od wersji serwera i platformy.

IIS

Usługi IIS zarządzają negocjowaniem certyfikatu klienta w Twoim imieniu. Podsekcja aplikacji może umożliwić włączenie opcji SslRequireCert, aby negocjować certyfikat klienta dla tych żądań. Aby uzyskać szczegółowe informacje, zobacz Konfiguracja w dokumentacji usług IIS.

Usługi IIS automatycznie buforują wszystkie dane treści żądania do skonfigurowanego limitu rozmiaru przed ponownym negocjowaniem. Żądania przekraczające limit są odrzucane z odpowiedzią 413. Ten limit jest domyślnie ustawiony na 48 KB i można go skonfigurować przez ustawienie parametru uploadReadAheadSize.

HttpSys

Protokół HttpSys ma dwa ustawienia, które kontrolują negocjacje certyfikatu klienta i oba powinny być ustawione. Pierwszy znajduje się w netsh.exe pod http add sslcert clientcertnegotiation=enable/disable. Ta flaga wskazuje, czy certyfikat klienta powinien być negocjowany na początku połączenia i powinien być ustawiony disable na wartość dla opcjonalnych certyfikatów klienta. Aby uzyskać szczegółowe informacje, zobacz dokumentację netsh .

Drugie ustawienie to ClientCertificateMethod. Po ustawieniu AllowRenegotation certyfikat klienta można renegocjować podczas żądania.

UWAGA Aplikacja powinna buforować lub używać danych treści żądania przed podjęciem próby renegocjacji. W przeciwnym razie żądanie może nie odpowiadać.

Istnieje znany problem polegający na tym, że włączenie może powodować, że renegocjacja odbywa się synchronicznie podczas dostępu do właściwości AllowRenegotation. Wywołaj metodę GetClientCertificateAsync, aby tego uniknąć. Ten problem został rozwiązany na platformie .NET 6. Aby uzyskać więcej informacji, zobacz ten problem w serwisie GitHub. Uwaga GetClientCertificateAsync może zwrócić certyfikat o wartości null, jeśli klient odmówi podania certyfikatu.

Kestrel

Kestrel kontroluje negocjacje certyfikatu klienta z opcją ClientCertificateMode .

W przypadku platformy .NET 5 lub starszej Kestrel nie obsługuje ponownego negocjowania po rozpoczęciu połączenia w celu uzyskania certyfikatu klienta. Ta funkcja została dodana na platformie .NET 6.

Microsoft.AspNetCore.Authentication.Certificate zawiera implementację podobną do uwierzytelniania certyfikatów dla ASP.NET Core. Uwierzytelnianie certyfikatu odbywa się na poziomie protokołu TLS, na długo przed rozpoczęciem ASP.NET Core. Dokładniej, jest to obsługiwacz uwierzytelniania, który weryfikuje certyfikat, a następnie generuje zdarzenie, w którym można przekształcić ten certyfikat na ClaimsPrincipal.

Skonfiguruj serwer pod kątem uwierzytelniania certyfikatów, niezależnie od tego, czy jest to usługa IIS, Kestrelusługa Azure Web Apps, czy cokolwiek innego, którego używasz.

Scenariusze serwera proxy i modułu równoważenia obciążenia

Uwierzytelnianie certyfikatu jest scenariuszem stanowym używanym głównie w przypadku, gdy serwer proxy lub moduł równoważenia obciążenia nie obsługuje ruchu między klientami i serwerami. Jeśli używany jest serwer proxy lub moduł równoważenia obciążenia, uwierzytelnianie certyfikatu działa tylko wtedy, gdy serwer proxy lub moduł równoważenia obciążenia:

  • Obsługuje uwierzytelnianie.
  • Przekazuje informacje o uwierzytelnianiu użytkownika do aplikacji (na przykład w nagłówku żądania), która działa na podstawie informacji uwierzytelniania.

Alternatywą dla uwierzytelniania certyfikatów w środowiskach, w których są używane serwery proxy i moduły równoważenia obciążenia, jest usługa Active Directory Federated Services (ADFS) z protokołem OpenID Connect (OIDC).

Wprowadzenie

Uzyskaj certyfikat HTTPS, zastosuj go i skonfiguruj serwer tak, aby wymagał certyfikatów.

W aplikacji internetowej dodaj odwołanie do pakietu Microsoft.AspNetCore.Authentication.Certificate . Następnie w metodzie Startup.ConfigureServices wywołaj metodę services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...); z opcjami, podając pełnomocnika do OnCertificateValidated wykonania dodatkowej weryfikacji certyfikatu klienta wysłanego z żądaniami. Zmień te informacje w ClaimsPrincipal i ustaw je na właściwość context.Principal.

Jeśli uwierzytelnianie zakończy się niepowodzeniem, ta procedura obsługi zwróci odpowiedź 403 (Forbidden) zamiast 401 (Unauthorized), czego można się spodziewać. Przyczyną jest to, że uwierzytelnianie powinno nastąpić podczas początkowego połączenia TLS. Zanim dotrze do obsługującego, jest już za późno. Nie ma możliwości uaktualnienia połączenia z połączenia anonimowego do jednego z certyfikatem.

Dodaj również app.UseAuthentication(); w metodzie Startup.Configure. W przeciwnym razie HttpContext.User nie zostanie ustawiony na ClaimsPrincipal utworzony na podstawie certyfikatu. Na przykład:

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
}

W poprzednim przykładzie pokazano domyślny sposób dodawania uwierzytelniania certyfikatu. Obsługiwacz konstruuje główną domenę użytkownika przy użyciu typowych właściwości certyfikatu.

Konfigurowanie weryfikacji certyfikatu

Obsługa CertificateAuthenticationOptions posiada kilka wbudowanych walidacji, które stanowią minimalne walidacje certyfikatu. Każde z tych ustawień jest domyślnie włączone.

AllowedCertificateTypes = Łańcuchowe, Samopodpisane lub Wszystkie (Łańcuchowe | Samopodpisane)

Wartość domyślna: CertificateTypes.Chained

To sprawdzenie sprawdza, czy dozwolony jest tylko odpowiedni typ certyfikatu. Jeśli aplikacja korzysta z certyfikatów z podpisem własnym, ta opcja musi być ustawiona na CertificateTypes.All lub CertificateTypes.SelfSigned.

WalidacjaUżyciaCertyfikatu

Wartość domyślna: true

To sprawdzenie weryfikuje, czy certyfikat przedstawiony przez klienta ma rozszerzone użycie klucza uwierzytelniania klienta (EKU) lub w ogóle nie ma rozszerzonego użycia kluczy (EKU). Zgodnie ze specyfikacjami, jeśli nie określono EKU, wszystkie EKU są uznawane za prawidłowe.

SprawdźOkresWażności

Wartość domyślna: true

Ta kontrola sprawdza, czy certyfikat znajduje się w okresie ważności. W każdym żądaniu program obsługi gwarantuje, że certyfikat, który był ważny, gdy został przedstawiony, nie wygasł podczas bieżącej sesji.

OdwołanieFlag

Wartość domyślna: X509RevocationFlag.ExcludeRoot

Flaga określająca, które certyfikaty w łańcuchu są sprawdzane pod kątem odwołania.

Kontrole odwołania są wykonywane tylko wtedy, gdy certyfikat jest powiązany z certyfikatem głównym.

Tryb Odwołania

Wartość domyślna: X509RevocationMode.Online

Flaga określająca sposób przeprowadzania kontroli odwołania.

Określenie sprawdzania online może spowodować duże opóźnienie podczas kontaktowania się z urzędem certyfikacji.

Kontrole odwołania są wykonywane tylko wtedy, gdy certyfikat jest powiązany z certyfikatem głównym.

Czy mogę skonfigurować aplikację tak, aby wymagała certyfikatu tylko w określonych ścieżkach?

Nie jest to możliwe. Pamiętaj, że wymiana certyfikatów odbywa się na początku konwersacji HTTPS, jest wykonywana przez serwer przed odebraniem pierwszego żądania na tym połączeniu, aby nie można było ograniczyć zakresu na podstawie pól żądania.

Zdarzenia obsługi

Obsługujący ma dwa zdarzenia:

  • OnAuthenticationFailed: Wywoływane, jeśli podczas uwierzytelniania wystąpi wyjątek i można zareagować.
  • OnCertificateValidated: Wywoływany po tym, jak certyfikat został zweryfikowany, przeszedł pomyślnie weryfikację i utworzono domyślnego podmiotu głównego. To wydarzenie pozwala na wykonanie własnej walidacji oraz rozszerzenie lub zastąpienie principalu. Przykłady obejmują:
    • Określanie, czy certyfikat jest znany twoim usługom.

    • Tworzenie własnej głównej encji. Rozważmy następujący przykład w pliku 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;
                  }
              };
          });
      

Jeśli okaże się, że certyfikat przychodzący nie spełnia dodatkowej walidacji, skontaktuj się z context.Fail("failure reason") podając przyczynę niepowodzenia.

W przypadku rzeczywistej funkcjonalności prawdopodobnie będzie konieczne użycie usługi zarejestrowanej w iniekcji zależności, która łączy się z bazą danych lub innym typem źródła danych użytkownika. Uzyskaj dostęp do usługi przy użyciu kontekstu przekazanego do pełnomocnika. Rozważmy następujący przykład w pliku 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;
            }
        };
    });

Koncepcyjnie weryfikacja certyfikatu jest problemem autoryzacji. Dodanie sprawdzania, na przykład wystawcy lub odcisku palca w zasadach autoryzacji, a nie wewnątrz elementu OnCertificateValidated, jest całkowicie akceptowalne.

Konfigurowanie serwera w celu wymagania certyfikatów

Kestrel

W Program.cs programie skonfiguruj Kestrel w następujący sposób:

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

Uwaga

Punkty końcowe utworzone przez wywołanie Listen przed wywołaniem nie będą miały zastosowanych wartości domyślnych.

IIS

Wykonaj następujące kroki w Menedżerze usług IIS:

  1. Wybierz swoją witrynę na karcie Połączenia .
  2. Kliknij dwukrotnie na Ustawienia SSL w oknie Widok funkcji.
  3. Zaznacz pole wyboru Wymagaj protokołu SSL i wybierz przycisk radiowy Wymagaj w sekcji Certyfikaty klienta.

Ustawienia certyfikatu klienta w usługach IIS

Azure i niestandardowe serwery proxy sieci Web

Zapoznaj się z dokumentacją dotyczącą hostowania i wdrażania, aby dowiedzieć się, jak skonfigurować middleware pośredniczące do przesyłania certyfikatów.

Używanie uwierzytelniania certyfikatów w usłudze Azure Web Apps

Dla platformy Azure nie jest wymagana żadna konfiguracja przekazywania dalej. Konfiguracja przekazywania jest konfigurowana przez oprogramowanie pośredniczące przekazujące certyfikaty.

Uwaga

W tym scenariuszu jest wymagane oprogramowanie pośredniczące przekazujące certyfikaty.

Aby uzyskać więcej informacji, zobacz Używanie certyfikatu TLS/SSL w kodzie w usłudze aplikacja systemu Azure (dokumentacja platformy Azure).

Używanie uwierzytelniania certyfikatów w niestandardowych internetowych serwerach proxy

Metoda AddCertificateForwarding jest używana do określania:

  • Nazwa nagłówka klienta.
  • Sposób ładowania certyfikatu (przy użyciu HeaderConverter właściwości ).

W niestandardowych internetowych serwerach proxy certyfikat jest przekazywany jako niestandardowy nagłówek żądania, na przykład X-SSL-CERT. Aby go użyć, skonfiguruj przekazywanie certyfikatów w programie 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;
}

Jeśli aplikacja jest odwrotnie proxy’owana przez serwer NGINX z konfiguracją proxy_set_header ssl-client-cert $ssl_client_escaped_cert lub wdrożona na platformie Kubernetes przy użyciu modułu NGINX Ingress, certyfikat klienta jest przekazywany do aplikacji w formacie zakodowanym URL. Aby użyć certyfikatu, zdekoduj go w następujący sposób:

Dodaj przestrzeń nazw dla elementu System.Net na początku elementu Startup.cs:

using System.Net;

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

Dodaj metodę UrlEncodedPemToByteArray :

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

Metoda Startup.Configure następnie dodaje oprogramowanie pośredniczące. UseCertificateForwarding jest wywoływany przed wywołaniami UseAuthentication i UseAuthorization.

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

    app.UseRouting();

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

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

Oddzielna klasa może służyć do implementowania logiki walidacji. Ponieważ w tym przykładzie jest używany ten sam certyfikat z podpisem własnym, upewnij się, że można używać tylko certyfikatu. Sprawdź, czy odciski palca certyfikatu klienta i certyfikatu serwera są zgodne, w przeciwnym razie można użyć dowolnego certyfikatu i będzie wystarczający do uwierzytelnienia. Będzie to używane wewnątrz AddCertificate metody. Możesz również zweryfikować podmiot lub wystawcę w tym miejscu, jeśli używasz certyfikatów pośrednich lub podrzędnych.

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

Implementowanie klienta HttpClient przy użyciu certyfikatu i programu HttpClientHandler

Element HttpClientHandler można dodać bezpośrednio w konstruktorze HttpClient klasy . Należy zachować ostrożność podczas tworzenia wystąpień klasy HttpClient. Następnie HttpClient program wyśle certyfikat z każdym żądaniem.

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

Implementowanie klienta HttpClient przy użyciu certyfikatu i nazwanego obiektu HttpClient z elementu IHttpClientFactory

W poniższym przykładzie certyfikat klienta jest dodawany do HttpClientHandler przy użyciu właściwości ClientCertificates z obsługi. Ta procedura obsługi może być używana w nazwanym wystąpieniu HttpClient, używając metody ConfigurePrimaryHttpMessageHandler. To jest skonfigurowane w Startup.ConfigureServices:

var clientCertificate = 
    new X509Certificate2(
      Path.Combine(_environment.ContentRootPath, "sts_dev_cert.pfx"), "1234");

services.AddHttpClient("namedClient", c =>
{
}).ConfigurePrimaryHttpMessageHandler(() =>
{
    var handler = new HttpClientHandler();
    handler.ClientCertificates.Add(clientCertificate);
    return handler;
});

Następnie IHttpClientFactory można użyć do pobrania nazwanego wystąpienia za pomocą obsługi i certyfikatu. Metoda CreateClient o nazwie klienta zdefiniowana w klasie Startup jest używana do pobierania instancji. Żądanie HTTP można wysłać przy użyciu klienta zgodnie z potrzebami.

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

Jeśli prawidłowy certyfikat jest wysyłany do serwera, dane są zwracane. Jeśli żaden certyfikat lub nieprawidłowy certyfikat nie zostanie wysłany, zostanie zwrócony kod stanu HTTP 403.

Tworzenie certyfikatów w programie PowerShell

Tworzenie certyfikatów jest najtrudniejszą częścią konfigurowania tego przepływu. Certyfikat główny można utworzyć przy użyciu New-SelfSignedCertificate polecenia cmdlet programu PowerShell. Podczas tworzenia certyfikatu użyj silnego hasła. Ważne jest dodanie parametru KeyUsageProperty i parametru KeyUsage , jak pokazano poniżej.

Utwórz główny urząd certyfikacji (root CA)

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

Uwaga

Wartość parametru musi być zgodna -DnsName z docelowym wdrożeniem aplikacji. Na przykład "localhost" na potrzeby programowania.

Instalowanie w zaufanym katalogu głównym

Certyfikat główny (root certificate) musi być zaufany w systemie hostującym. Certyfikat główny, który nie został utworzony przez urząd certyfikacji, nie będzie domyślnie uznawany za zaufany. Aby uzyskać informacje na temat zaufania certyfikatowi głównemu w systemie Windows, zobacz to pytanie.

Certyfikat pośredni

Certyfikat pośredniczący można teraz utworzyć na podstawie certyfikatu głównego. Nie jest to wymagane w przypadku wszystkich przypadków użycia, ale może być konieczne utworzenie wielu certyfikatów lub konieczność aktywowania lub wyłączania grup certyfikatów. Parametr TextExtension jest wymagany do ustawienia długości ścieżki w podstawowych ograniczeniach certyfikatu.

Certyfikat pośredniczący można następnie dodać do zaufanego certyfikatu pośredniego w systemie hosta systemu Windows.

$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

Tworzenie certyfikatu podrzędnego na podstawie certyfikatu pośredniego

Certyfikat podrzędny można utworzyć na podstawie certyfikatu pośredniego. Jest to jednostka końcowa i nie musi tworzyć większej liczby certyfikatów podrzędnych.

$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

Tworzenie certyfikatu podrzędnego na podstawie certyfikatu głównego

Certyfikat podrzędny można również utworzyć bezpośrednio na podstawie certyfikatu głównego.

$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

Przykładowy certyfikat główny — certyfikat pośredni — certyfikat

$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

W przypadku używania certyfikatów głównych, pośrednich lub podrzędnych certyfikaty można zweryfikować przy użyciu odcisku palca lub klucza publicznego zgodnie z wymaganiami.

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

Opcjonalne certyfikaty klienta

Ta sekcja zawiera informacje dotyczące aplikacji, które muszą chronić podzbiór aplikacji przy użyciu certyfikatu. Na przykład Razor strona lub kontroler w aplikacji może wymagać certyfikatów klienta. Stanowi to wyzwania związane z certyfikatami klienta:

  • To funkcja PROTOKOŁU TLS, a nie funkcja HTTP.
  • Są negocjowane dla połączenia i zwykle na początku połączenia przed udostępnieniem jakichkolwiek danych HTTP.

Istnieją dwa podejścia do implementowania opcjonalnych certyfikatów klienta:

  1. Używanie oddzielnych nazw hostów (SNI) i przekierowywanie. Mimo że wymaga więcej pracy przy konfiguracji, jest to zalecane, ponieważ działa w większości środowisk i protokołów.
  2. Renegocjacja podczas żądania HTTP. Ma to kilka ograniczeń i nie jest zalecane.

Oddzielne hosty (SNI)

Na początku połączenia znana jest tylko nazwa nazwy serwera (SNI) †. Certyfikaty klienta można skonfigurować na nazwę hosta, tak aby jeden host ich wymagał, a inny nie.

Platforma .NET 5 lub nowsza dodaje wygodną obsługę przekierowywania w celu uzyskania opcjonalnych certyfikatów klienta. Aby uzyskać więcej informacji, zobacz Przykład opcjonalnych certyfikatów.

  • W przypadku żądań do aplikacji internetowej, które wymagają certyfikatu klienta i których nie ma:
    • Przekieruj do tej samej strony przy użyciu poddomeny chronionej certyfikatem klienta.
    • Na przykład przekieruj do myClient.contoso.com/requestedPage. Ponieważ żądanie do myClient.contoso.com/requestedPage jest inną nazwą hosta niż contoso.com/requestedPage, klient ustanawia inne połączenie, a certyfikat klienta jest dostarczany.
    • Aby uzyskać więcej informacji, zobacz Wprowadzenie do autoryzacji w programie ASP.NET Core.

† oznaczanie nazwy serwera (SNI) to rozszerzenie protokołu TLS do uwzględnienia domeny wirtualnej w ramach negocjacji protokołu SSL. Oznacza to, że nazwa domeny wirtualnej lub nazwa hosta może służyć do identyfikowania punktu końcowego sieci.

Renegocjacja

Renegocjacja protokołu TLS to proces, za pomocą którego klient i serwer mogą ponownie ocenić wymagania dotyczące szyfrowania dla pojedynczego połączenia, w tym żądania certyfikatu klienta, jeśli nie zostały wcześniej podane. Renegocjacja protokołu TLS jest zagrożeniem bezpieczeństwa i nie jest zalecana, ponieważ:

  • W przypadku protokołu HTTP/1.1 serwer musi najpierw buforować lub przetworzyć wszelkie dane HTTP, które są w locie, takie jak treść żądania POST, aby upewnić się, że połączenie jest gotowe do renegocjacji. W przeciwnym razie renegocjacja może przestać odpowiadać lub zakończyć się niepowodzeniem.
  • Protokół HTTP/2 i HTTP/3 jawnie uniemożliwiają renegocjację.
  • Istnieją zagrożenia bezpieczeństwa związane z renegocjacją. Protokół TLS 1.3 usunął renegocjację całego połączenia i zastąpił go nowym rozszerzeniem żądania tylko certyfikatu klienta po rozpoczęciu połączenia. Ten mechanizm jest ujawniany za pośrednictwem tych samych interfejsów API i nadal podlega wcześniejszym ograniczeniom buforowania i wersji protokołu HTTP.

Implementacja i konfiguracja tej funkcji różni się w zależności od wersji serwera i platformy.

IIS

Usługi IIS zarządzają negocjowaniem certyfikatu klienta w Twoim imieniu. Podsekcja aplikacji może umożliwić włączenie opcji SslRequireCert, aby negocjować certyfikat klienta dla tych żądań. Aby uzyskać szczegółowe informacje, zobacz Konfiguracja w dokumentacji usług IIS.

Usługi IIS automatycznie buforują wszystkie dane treści żądania do skonfigurowanego limitu rozmiaru przed ponownym negocjowaniem. Żądania przekraczające limit są odrzucane z odpowiedzią 413. Ten limit jest domyślnie ustawiony na 48 KB i można go skonfigurować przez ustawienie parametru uploadReadAheadSize.

HttpSys

Protokół HttpSys ma dwa ustawienia, które kontrolują negocjacje certyfikatu klienta i oba powinny być ustawione. Pierwszy znajduje się w netsh.exe pod http add sslcert clientcertnegotiation=enable/disable. Ta flaga wskazuje, czy certyfikat klienta powinien być negocjowany na początku połączenia i powinien być ustawiony disable na wartość dla opcjonalnych certyfikatów klienta. Aby uzyskać szczegółowe informacje, zobacz dokumentację netsh .

Drugie ustawienie to ClientCertificateMethod. Po ustawieniu AllowRenegotation certyfikat klienta można renegocjować podczas żądania.

UWAGA Aplikacja powinna buforować lub używać danych treści żądania przed podjęciem próby renegocjacji. W przeciwnym razie żądanie może nie odpowiadać.

Istnieje znany problem polegający na tym, że włączenie może powodować, że renegocjacja odbywa się synchronicznie podczas dostępu do właściwości AllowRenegotation. Wywołaj metodę GetClientCertificateAsync, aby tego uniknąć. Ten problem został rozwiązany na platformie .NET 6. Aby uzyskać więcej informacji, zobacz ten problem w serwisie GitHub. Uwaga GetClientCertificateAsync może zwrócić certyfikat o wartości null, jeśli klient odmówi podania certyfikatu.

Kestrel

Kestrel kontroluje negocjacje certyfikatu klienta z opcją ClientCertificateMode .

W przypadku platformy .NET 5 lub starszej Kestrel nie obsługuje ponownego negocjowania po rozpoczęciu połączenia w celu uzyskania certyfikatu klienta. Ta funkcja została dodana na platformie .NET 6.

Pozostaw pytania, komentarze i inne opinie dotyczące opcjonalnych certyfikatów klienta w wątku dyskusji dla GitHub problem #18720.