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, na długo przed rozpoczęciem ASP.NET Core. Dokładniej jest to procedura obsługi uwierzytelniania, która weryfikuje certyfikat, a następnie daje zdarzenie, w którym można rozpoznać ten certyfikat jako ClaimsPrincipal.

Serwer należyskonfigurować pod kątem uwierzytelniania certyfikatów, niezależnie od tego, czy są to usługi IIS, Kestrel, Azure Web Apps, czy też inne, których 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 używane są serwery proxy i moduły równoważenia obciążenia, jest usługa Active Directory Federated Services (ADFS) z Połączenie OpenID (OIDC).

Rozpocznij

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 Program.cspliku wywołaj metodę builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...);. Podaj pełnomocnika, OnCertificateValidated aby wykonać dodatkową walidację certyfikatu klienta wysłanego z żądaniami. Zmień te informacje na ClaimsPrincipal wartość i ustaw ją we context.Principal właściwości .

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

UseAuthentication parametr jest wymagany do ustawienia HttpContext.User wartości utworzonej ClaimsPrincipal 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

Procedura CertificateAuthenticationOptions obsługi ma pewne wbudowane weryfikacje, które są minimalnymi weryfikacjami, które należy wykonać na certyfikacie. Każde z tych ustawień jest domyślnie włączone.

AllowedCertificateTypes = Chained, SelfSigned lub All (Łańcuchowe | SelfSigned)

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.

ChainTrustValidationMode

Wartość domyślna: X509ChainTrustMode.System

Certyfikat przedstawiony przez klienta musi połączyć łańcuch z zaufanym certyfikatem głównym. To sprawdzanie steruje magazynem zaufania zawierającym te certyfikaty główne.

Domyślnie program obsługi używa magazynu zaufania systemu. Jeśli przedstawiony certyfikat klienta musi połączyć łańcuch z certyfikatem głównym, który nie jest wyświetlany w magazynie zaufania systemu, tę opcję można ustawić na X509ChainTrustMode.CustomRootTrustTrust , aby program obsługi używał elementu CustomTrustStore.

CustomTrustStore

Wartość domyślna: Pusta X509Certificate2Collection

Jeśli właściwość programu obsługi ChainTrustValidationMode jest ustawiona na X509ChainTrustMode.CustomRootTrustwartość , X509Certificate2Collection zawiera każdy certyfikat, który będzie używany do weryfikowania certyfikatu klienta do zaufanego katalogu głównego, w tym zaufanego katalogu głównego.

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

ValidateCertificateUse

Wartość domyślna: true

To sprawdzenie sprawdza, czy certyfikat przedstawiony przez klienta ma rozszerzone użycie klucza uwierzytelniania klienta (EKU) lub w ogóle nie ma jednostek EKU. Jak mówią specyfikacje, jeśli nie określono EKU, wszystkie EKU są uznawane za prawidłowe.

ValidateValidityPeriod

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 w łańcuchu do certyfikatu głównego.

Odwołajmode

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 do certyfikatu głównego.

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 programu obsługi

Procedura obsługi ma dwa zdarzenia:

  • OnAuthenticationFailed: Wywoływane, jeśli podczas uwierzytelniania wystąpi wyjątek i można zareagować.
  • OnCertificateValidated: Wywoływany po zweryfikowaniu certyfikatu, pomyślnie zweryfikowano walidację i utworzono domyślną jednostkę zabezpieczeń. To zdarzenie umożliwia przeprowadzenie własnej weryfikacji i rozszerzenie lub zastąpienie podmiotu zabezpieczeń. Przykłady obejmują:
    • Określanie, czy certyfikat jest znany usługom.

    • Konstruowanie własnego podmiotu zabezpieczeń. Rozważmy następujący przykład:

      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 okaże się, że certyfikat przychodzący nie spełnia dodatkowej weryfikacji, wywołaj context.Fail("failure reason") metodę z przyczyną niepowodzenia.

Aby uzyskać lepszą funkcjonalność, wywołaj usługę zarejestrowaną w iniekcji zależności, która łączy się z bazą danych lub innym typem magazynu użytkowników. Uzyskaj dostęp do usługi przy użyciu kontekstu przekazanego 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. Dodanie sprawdzania, na przykład wystawcy lub odcisku palca w zasadach autoryzacji, a nie wewnątrz OnCertificateValidatedelementu , jest całkowicie akceptowalne.

Konfigurowanie serwera w celu wymagania certyfikatów

Kestrel

W Program.csprogramie 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

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

IIS

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

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

Client certificate settings in IIS

Azure i niestandardowe serwery proxy sieci Web

Zapoznaj się z dokumentacją dotyczącą hosta i wdrażania, aby dowiedzieć się, jak skonfigurować oprogramowanie pośredniczące przekazujące certyfikaty.

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 przekazywania certyfikatów.

Uwaga

Oprogramowanie pośredniczące przekazywania certyfikatów jest wymagane w tym scenariuszu.

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 służy do określania AddCertificateForwarding :

  • 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 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 aplikacja jest odwrotnie proxied przez serwer NGINX z konfiguracją proxy_set_header ssl-client-cert $ssl_client_escaped_cert lub wdrożona na platformie Kubernetes przy użyciu ruchu przychodzącego NGINX, certyfikat klienta jest przekazywany do aplikacji w postaci zakodowanej pod adresem 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 parametr jest wywoływany przed wywołaniami i UseAuthenticationUseAuthorization:

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, 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.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 używającego HttpClientHandlerClientCertificates właściwości z programu obsługi. Ta procedura obsługi może być następnie używana w nazwanym wystąpieniu HttpClientConfigurePrimaryHttpMessageHandler obiektu przy użyciu metody . Jest to konfiguracja w programie 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 go użyć do pobrania nazwanego wystąpienia za pomocą programu obsługi i certyfikatu. Metoda CreateClient o nazwie klienta zdefiniowanego w Program.cs programie 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, 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.

Tworzenie głównego urzędu certyfikacji

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 musi być zaufany w systemie hosta. Certyfikat główny, który nie został utworzony przez urząd certyfikacji, nie będzie domyślnie 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 korzystania z 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);
}

Buforowanie weryfikacji certyfikatu

ASP.NET Core 5.0 i nowsze wersje obsługują możliwość włączania 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);
    });

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. Chociaż więcej pracy należy skonfigurować, 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.

ASP.NET Core 5 i nowszych dodaje wygodniejsze obsługę przekierowywania w celu uzyskania opcjonalnych certyfikatów klienta. Aby uzyskać więcej informacji, zobacz przykład Opcjonalne certyfikaty.

  • 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.

Renegocjacji

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 używać wszelkich danych HTTP, które są w locie, takich jak treść żądania POST, aby upewnić się, że połączenie jest jasne dla 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ć SslRequireCert negocjowanie certyfikatu 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 element znajduje się w netsh.exe w obszarze 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 AllowRenegotationwartości na wartość 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ć.

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 została zużyta przed wywołaniem GetClientCertificateAsync metody negocjowania. Uwaga GetClientCertificateAsync może zwrócić certyfikat o wartości null, jeśli klient odmówi podania certyfikatu.

UWAGA Zachowanie ClientCertificate właściwości zmieniło się na platformie .NET 6. Aby uzyskać więcej informacji, zobacz ten problem w serwisie GitHub.

Kestrel

Kestrel kontroluje negocjacje certyfikatu klienta z opcją ClientCertificateMode .

ClientCertificateMode.DelayCertificate jest nową opcją dostępną na platformie .NET 6 lub nowszym. Po ustawieniu 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 została zużyta przed wywołaniem GetClientCertificateAsync metody negocjowania. Uwaga GetClientCertificateAsync może zwrócić certyfikat o wartości null, jeśli klient odmówi podania certyfikatu.

UWAGA Aplikacja powinna buforować lub używać danych treści żądania przed podjęciem próby ponownego negocjowania. W przeciwnym razie GetClientCertificateAsync może zostać zgłoszony komunikat InvalidOperationException: Client stream needs to be drained before renegotiation..

Jeśli konfigurujesz programowo ustawienia protokołu TLS na hosta, istnieje nowe przeciążenie UseHttps dostępne na platformie .NET 6 i nowszych, które przyjmuje TlsHandshakeCallbackOptions i kontroluje renegocjację certyfikatu klienta za pośrednictwem programu TlsHandshakeCallbackContext.AllowDelayedClientCertificateNegotation.

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 procedura obsługi uwierzytelniania, która weryfikuje certyfikat, a następnie daje zdarzenie, w którym można rozpoznać ten certyfikat jako 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 używane są serwery proxy i moduły równoważenia obciążenia, jest usługa Active Directory Federated Services (ADFS) z Połączenie OpenID (OIDC).

Rozpocznij

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 na ClaimsPrincipal wartość i ustaw ją we context.Principal właściwości .

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

app.UseAuthentication(); Dodaj również metodę Startup.Configure . W przeciwnym razie nie zostanie ustawiona HttpContext.User wartość ClaimsPrincipal utworzona 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. Procedura obsługi konstruuje podmiot zabezpieczeń użytkownika przy użyciu typowych właściwości certyfikatu.

Konfigurowanie weryfikacji certyfikatu

Procedura CertificateAuthenticationOptions obsługi ma pewne wbudowane weryfikacje, które są minimalnymi weryfikacjami, które należy wykonać na certyfikacie. Każde z tych ustawień jest domyślnie włączone.

AllowedCertificateTypes = Chained, SelfSigned lub All (Łańcuchowe | SelfSigned)

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.

ValidateCertificateUse

Wartość domyślna: true

To sprawdzenie sprawdza, czy certyfikat przedstawiony przez klienta ma rozszerzone użycie klucza uwierzytelniania klienta (EKU) lub w ogóle nie ma jednostek EKU. Jak mówią specyfikacje, jeśli nie określono EKU, wszystkie EKU są uznawane za prawidłowe.

ValidateValidityPeriod

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 w łańcuchu do certyfikatu głównego.

Odwołajmode

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 do certyfikatu głównego.

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 programu obsługi

Procedura obsługi ma dwa zdarzenia:

  • OnAuthenticationFailed: Wywoływane, jeśli podczas uwierzytelniania wystąpi wyjątek i można zareagować.
  • OnCertificateValidated: Wywoływany po zweryfikowaniu certyfikatu, pomyślnie zweryfikowano walidację i utworzono domyślną jednostkę zabezpieczeń. To zdarzenie umożliwia przeprowadzenie własnej weryfikacji i rozszerzenie lub zastąpienie podmiotu zabezpieczeń. Przykłady obejmują:
    • Określanie, czy certyfikat jest znany usługom.

    • Konstruowanie własnego podmiotu zabezpieczeń. 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 weryfikacji, wywołaj context.Fail("failure reason") metodę z przyczyną niepowodzenia.

W przypadku rzeczywistych funkcji prawdopodobnie chcesz wywołać usługę zarejestrowaną we wstrzyknięciu zależności, która łączy się z bazą danych lub innym typem magazynu użytkowników. 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 OnCertificateValidatedelementu , jest całkowicie akceptowalne.

Konfigurowanie serwera w celu wymagania certyfikatów

Kestrel

W Program.csprogramie 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 przed wywołaniem ConfigureHttpsDefaultsListennie będą miały zastosowanych wartości domyślnych.

IIS

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

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

Client certificate settings in IIS

Azure i niestandardowe serwery proxy sieci Web

Zapoznaj się z dokumentacją dotyczącą hosta i wdrażania, aby dowiedzieć się, jak skonfigurować oprogramowanie pośredniczące przekazujące certyfikaty.

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 przekazywania certyfikatów.

Uwaga

Oprogramowanie pośredniczące przekazywania certyfikatów jest wymagane w tym scenariuszu.

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 służy do określania AddCertificateForwarding :

  • 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 proxied przez serwer NGINX z konfiguracją proxy_set_header ssl-client-cert $ssl_client_escaped_cert lub wdrożona na platformie Kubernetes przy użyciu ruchu przychodzącego NGINX, certyfikat klienta jest przekazywany do aplikacji w postaci zakodowanej pod adresem 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;
    };
});

Następnie Startup.Configure metoda dodaje oprogramowanie pośredniczące. UseCertificateForwarding parametr jest wywoływany przed wywołaniami i UseAuthenticationUseAuthorization:

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 obiektu używającego HttpClientHandlerClientCertificates właściwości z programu obsługi. Ta procedura obsługi może być następnie używana w nazwanym wystąpieniu HttpClientConfigurePrimaryHttpMessageHandler obiektu przy użyciu metody . Jest to konfiguracja w programie 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 go użyć do pobrania nazwanego wystąpienia za pomocą programu obsługi i certyfikatu. Metoda CreateClient o nazwie klienta zdefiniowanego Startup w klasie służy do pobierania wystąpienia. Żą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.

Tworzenie głównego urzędu certyfikacji

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 musi być zaufany w systemie hosta. Certyfikat główny, który nie został utworzony przez urząd certyfikacji, nie będzie domyślnie 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

ASP.NET Core 5.0 i nowsze wersje obsługują możliwość włączania 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 Startup.ConfigureServices:

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

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. Chociaż więcej pracy należy skonfigurować, 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.

ASP.NET Core 5 i nowszych dodaje wygodniejsze obsługę przekierowywania w celu uzyskania opcjonalnych certyfikatów klienta. Aby uzyskać więcej informacji, zobacz przykład Opcjonalne certyfikaty.

  • 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.

Renegocjacji

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 używać wszelkich danych HTTP, które są w locie, takich jak treść żądania POST, aby upewnić się, że połączenie jest jasne dla 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ć SslRequireCert negocjowanie certyfikatu 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 element znajduje się w netsh.exe w obszarze 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 AllowRenegotationwartości na wartość 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 AllowRenegotation może spowodować synchroniczną synchronizację podczas uzyskiwania ClientCertificate dostępu do właściwości. 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 i starszych 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 procedura obsługi uwierzytelniania, która weryfikuje certyfikat, a następnie daje zdarzenie, w którym można rozpoznać ten certyfikat jako 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 używane są serwery proxy i moduły równoważenia obciążenia, jest usługa Active Directory Federated Services (ADFS) z Połączenie OpenID (OIDC).

Rozpocznij

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 na ClaimsPrincipal wartość i ustaw ją we context.Principal właściwości .

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

app.UseAuthentication(); Dodaj również metodę Startup.Configure . W przeciwnym razie nie zostanie ustawiona HttpContext.User wartość ClaimsPrincipal utworzona 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. Procedura obsługi konstruuje podmiot zabezpieczeń użytkownika przy użyciu typowych właściwości certyfikatu.

Konfigurowanie weryfikacji certyfikatu

Procedura CertificateAuthenticationOptions obsługi ma pewne wbudowane weryfikacje, które są minimalnymi weryfikacjami, które należy wykonać na certyfikacie. Każde z tych ustawień jest domyślnie włączone.

AllowedCertificateTypes = Chained, SelfSigned lub All (Łańcuchowe | SelfSigned)

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.

ValidateCertificateUse

Wartość domyślna: true

To sprawdzenie sprawdza, czy certyfikat przedstawiony przez klienta ma rozszerzone użycie klucza uwierzytelniania klienta (EKU) lub w ogóle nie ma jednostek EKU. Jak mówią specyfikacje, jeśli nie określono EKU, wszystkie EKU są uznawane za prawidłowe.

ValidateValidityPeriod

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 w łańcuchu do certyfikatu głównego.

Odwołajmode

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 do certyfikatu głównego.

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 programu obsługi

Procedura obsługi ma dwa zdarzenia:

  • OnAuthenticationFailed: Wywoływane, jeśli podczas uwierzytelniania wystąpi wyjątek i można zareagować.
  • OnCertificateValidated: Wywoływany po zweryfikowaniu certyfikatu, pomyślnie zweryfikowano walidację i utworzono domyślną jednostkę zabezpieczeń. To zdarzenie umożliwia przeprowadzenie własnej weryfikacji i rozszerzenie lub zastąpienie podmiotu zabezpieczeń. Przykłady obejmują:
    • Określanie, czy certyfikat jest znany usługom.

    • Konstruowanie własnego podmiotu zabezpieczeń. 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 weryfikacji, wywołaj context.Fail("failure reason") metodę z przyczyną niepowodzenia.

W przypadku rzeczywistych funkcji prawdopodobnie chcesz wywołać usługę zarejestrowaną we wstrzyknięciu zależności, która łączy się z bazą danych lub innym typem magazynu użytkowników. 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 OnCertificateValidatedelementu , jest całkowicie akceptowalne.

Konfigurowanie serwera w celu wymagania certyfikatów

Kestrel

W Program.csprogramie 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 przed wywołaniem ConfigureHttpsDefaultsListennie będą miały zastosowanych wartości domyślnych.

IIS

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

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

Client certificate settings in IIS

Azure i niestandardowe serwery proxy sieci Web

Zapoznaj się z dokumentacją dotyczącą hosta i wdrażania, aby dowiedzieć się, jak skonfigurować oprogramowanie pośredniczące przekazujące certyfikaty.

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 przekazywania certyfikatów.

Uwaga

Oprogramowanie pośredniczące przekazywania certyfikatów jest wymagane w tym scenariuszu.

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 służy do określania AddCertificateForwarding :

  • 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 proxied przez serwer NGINX z konfiguracją proxy_set_header ssl-client-cert $ssl_client_escaped_cert lub wdrożona na platformie Kubernetes przy użyciu ruchu przychodzącego NGINX, certyfikat klienta jest przekazywany do aplikacji w postaci zakodowanej pod adresem 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);
}

Następnie Startup.Configure metoda dodaje oprogramowanie pośredniczące. UseCertificateForwarding parametr jest wywoływany przed wywołaniami i UseAuthenticationUseAuthorization:

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 obiektu używającego HttpClientHandlerClientCertificates właściwości z programu obsługi. Ta procedura obsługi może być następnie używana w nazwanym wystąpieniu HttpClientConfigurePrimaryHttpMessageHandler obiektu przy użyciu metody . Jest to konfiguracja w programie 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 go użyć do pobrania nazwanego wystąpienia za pomocą programu obsługi i certyfikatu. Metoda CreateClient o nazwie klienta zdefiniowanego Startup w klasie służy do pobierania wystąpienia. Żą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.

Tworzenie głównego urzędu certyfikacji

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 musi być zaufany w systemie hosta. Certyfikat główny, który nie został utworzony przez urząd certyfikacji, nie będzie domyślnie 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. Chociaż więcej pracy należy skonfigurować, 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.

ASP.NET Core 5 i nowszych dodaje wygodniejsze obsługę przekierowywania w celu uzyskania opcjonalnych certyfikatów klienta. Aby uzyskać więcej informacji, zobacz przykład Opcjonalne certyfikaty.

  • 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.

Renegocjacji

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 używać wszelkich danych HTTP, które są w locie, takich jak treść żądania POST, aby upewnić się, że połączenie jest jasne dla 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ć SslRequireCert negocjowanie certyfikatu 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 element znajduje się w netsh.exe w obszarze 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 AllowRenegotationwartości na wartość 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 AllowRenegotation może spowodować synchroniczną synchronizację podczas uzyskiwania ClientCertificate dostępu do właściwości. 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 i starszych 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 tym problemie z dyskusją w usłudze GitHub.