Sdílet prostřednictvím


Konfigurace ověřování certifikátů v ASP.NET Core

Microsoft.AspNetCore.Authentication.Certificate obsahuje implementaci podobnou ověřování certifikátů pro ASP.NET Core. Ověřování certifikátů probíhá na úrovni protokolu TLS, a to dlouho předtím, než se někdy dostane k ASP.NET Core. Přesněji řečeno, jedná se o obslužnou rutinu ověřování, která ověřuje certifikát a poté poskytuje událost, ve které můžete tento certifikát přeložit na ClaimsPrincipal.

Server musíte nakonfigurovat pro ověřování certifikátů, ať už je to služba IIS, Kestrel, Azure Web Apps nebo cokoli jiného, co používáte.

Scénáře proxy serveru a nástroje pro vyrovnávání zatížení

Ověřování certifikátů je stavový scénář, kdy proxy server nebo nástroj pro vyrovnávání zatížení nezpracuje provoz mezi klienty a servery. Pokud se používá proxy server nebo nástroj pro vyrovnávání zatížení, ověřování certifikátů funguje jenom v případě, že proxy server nebo nástroj pro vyrovnávání zatížení:

  • Zpracovává ověřování.
  • Předá ověřovací informace uživatele aplikaci (například v hlavičce požadavku), která funguje na ověřovacích informacích.

Alternativou k ověřování certifikátů v prostředích, kde se používají proxy servery a nástroje pro vyrovnávání zatížení, je služba Active Directory Federated Services (ADFS) s OpenID Connect (OIDC).

Začínáme

Získejte certifikát HTTPS, použijte ho a nakonfigurujte server tak, aby vyžadoval certifikáty.

Ve webové aplikaci:

  • Přidejte odkaz na balíček NuGet Microsoft.AspNetCore.Authentication.Certificate .
  • Zavolejte Program.csbuilder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...);. Poskytněte delegátovi OnCertificateValidated , aby u klientského certifikátu odesílaného s požadavky udělal jakékoli dodatečné ověření. Změňte informace na hodnotu ClaimsPrincipal a nastavte ji u context.Principal vlastnosti.

Pokud ověřování selže, vrátí tato obslužná rutina 403 (Forbidden) 401 (Unauthorized)spíše odpověď , jak byste mohli očekávat. Důvodem je, že ověřování by mělo proběhnout během počátečního připojení TLS. V době, kdy dosáhne obslužné rutiny, je příliš pozdě. Neexistuje způsob, jak upgradovat připojení z anonymního připojení na jedno s certifikátem.

UseAuthentication je nutné nastavit HttpContext.User na ClaimsPrincipal vytvořený z certifikátu. Příklad:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

app.UseAuthentication();

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

app.Run();

Předchozí příklad ukazuje výchozí způsob přidání ověřování certifikátu. Obslužná rutina vytvoří objekt zabezpečení uživatele pomocí společných vlastností certifikátu.

Konfigurace ověření certifikátu

Obslužná CertificateAuthenticationOptions rutina má několik předdefinovaných ověření, která jsou minimálními ověřeními, která byste měli provést u certifikátu. Každé z těchto nastavení je ve výchozím nastavení povolené.

AllowedCertificateTypes = Chained, SelfSigned nebo All (zřetězeno | SelfSigned)

Výchozí hodnota: CertificateTypes.Chained

Tato kontrola ověří, že je povolený pouze příslušný typ certifikátu. Pokud aplikace používá certifikáty podepsané svým držitelem, musí být tato možnost nastavena na CertificateTypes.All hodnotu nebo CertificateTypes.SelfSigned.

ChainTrustValidationMode

Výchozí hodnota: X509ChainTrustMode.System

Certifikát předaný klientem musí zřetězen s důvěryhodným kořenovým certifikátem. Tato kontrola určuje, které úložiště důvěryhodnosti obsahuje tyto kořenové certifikáty.

Ve výchozím nastavení obslužná rutina používá úložiště důvěryhodnosti systému. Pokud prezentovaný klientský certifikát musí zřetězení na kořenový certifikát, který se nezobrazuje v systémovém úložišti důvěryhodnosti, lze tuto možnost nastavit na X509ChainTrustMode.CustomRootTrust , aby obslužná rutina používala CustomTrustStore.

CustomTrustStore

Výchozí hodnota: Prázdné X509Certificate2Collection

Pokud je vlastnost obslužné rutiny ChainTrustValidationMode nastavena na X509ChainTrustMode.CustomRootTrust, obsahuje X509Certificate2Collection každý certifikát, který se použije k ověření klientského certifikátu až do důvěryhodného kořenového adresáře, včetně důvěryhodného kořenového adresáře.

Když klient předloží certifikát, který je součástí řetězu certifikátů s více úrovněmi, CustomTrustStore musí obsahovat každý vydávající certifikát v řetězu.

ValidateCertificateUse

Výchozí hodnota: true

Tato kontrola ověří, že certifikát prezentovaný klientem používá rozšířený klíč ověřování klienta (EKU) nebo vůbec žádné EKU. Jak uvádí specifikace, pokud není zadán žádný EKU, považují se všechny EKU za platné.

ValidateValidityPeriod

Výchozí hodnota: true

Tato kontrola ověří, že certifikát spadá do doby platnosti. V každém požadavku obslužná rutina zajistí, že během aktuální relace nevypršela platnost certifikátu, který byl platný při jeho předložení.

RevocationFlag

Výchozí hodnota: X509RevocationFlag.ExcludeRoot

Příznak, který určuje, které certifikáty v řetězu se kontrolují pro odvolání.

Kontroly odvolání se provádějí pouze v případě, že je certifikát zřetězený s kořenovým certifikátem.

RevocationMode

Výchozí hodnota: X509RevocationMode.Online

Příznak, který určuje, jak se provádějí kontroly odvolání.

Zadání online kontroly může vést k dlouhé prodlevě, když se kontaktuje certifikační autorita.

Kontroly odvolání se provádějí pouze v případě, že je certifikát zřetězený s kořenovým certifikátem.

Můžu aplikaci nakonfigurovat tak, aby vyžadovala certifikát jenom na určitých cestách?

To není možné. Nezapomeňte, že výměna certifikátů se provádí na začátku konverzace HTTPS. Server ji provádí před přijetím prvního požadavku na toto připojení, takže není možné nastavit obor na základě polí požadavku.

Události obslužné rutiny

Obslužná rutina má dvě události:

  • OnAuthenticationFailed: Volá se, pokud během ověřování dojde k výjimce a umožní vám reagovat.
  • OnCertificateValidated: Volá se po ověření certifikátu, úspěšném ověření a vytvoření výchozího objektu zabezpečení. Tato událost umožňuje provést vlastní ověření a rozšířit nebo nahradit objekt zabezpečení. Příklady:
    • Určení, jestli je certifikát známý pro vaše služby.

    • Vytvoření vlastního objektu zabezpečení Představte si následující příklad:

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

Pokud zjistíte, že příchozí certifikát nesplňuje vaše dodatečné ověření, zavolejte context.Fail("failure reason") s důvodem selhání.

Pro lepší funkčnost volejte službu zaregistrovanou v injektáži závislostí, která se připojuje k databázi nebo jinému typu úložiště uživatelů. Přístup ke službě pomocí kontextu předaného delegátu. Představte si následující příklad:

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

Ověření certifikátu je koncepčně problém s autorizací. Přidání kontroly, například vystavitele nebo kryptografického otisku v autorizačních zásadách, nikoli uvnitř OnCertificateValidated, je naprosto přijatelné.

Konfigurace serveru tak, aby vyžadoval certifikáty

Kestrel

V Program.csnástroji , nakonfigurujte Kestrel následující:

var builder = WebApplication.CreateBuilder(args);

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

Poznámka:

Koncové body vytvořené voláním Listen před voláním ConfigureHttpsDefaults nebudou mít použité výchozí hodnoty.

IIS

Ve Správci služby IIS proveďte následující kroky:

  1. Na kartě Připojení vyberte svůj web.
  2. Poklikejte na možnost Nastavení PROTOKOLU SSL v okně Zobrazení funkcí.
  3. Zaškrtněte políčko Vyžadovat SSL a v části Klientské certifikáty vyberte přepínač Vyžadovat.

Nastavení klientského certifikátu ve službě IIS

Azure a vlastní webové proxy servery

Informace o konfiguraci middlewaru předávání certifikátů najdete v dokumentaci k hostiteli a nasazení.

Použití ověřování certifikátů ve službě Azure Web Apps

Azure nevyžaduje žádnou konfiguraci předávání. Konfigurace předávání je nastavena middlewarem předávání certifikátů.

Poznámka:

Pro tento scénář se vyžaduje middleware pro předávání certifikátů.

Další informace najdete v tématu Použití certifikátu TLS/SSL v kódu ve službě Aplikace Azure Service (dokumentace k Azure).

Použití ověřování certifikátů ve vlastních webových proxy serverech

Metoda AddCertificateForwarding se používá k určení:

  • Název hlavičky klienta.
  • Jak se certifikát načte (pomocí HeaderConverter vlastnosti).

Ve vlastních webových proxy serverech se certifikát předává jako vlastní hlavička požadavku, například X-SSL-CERT. Pokud ho chcete použít, nakonfigurujte předávání certifikátů v 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;
        }
    };
});

Pokud je aplikace zpětnou adresou NGINX s konfigurací proxy_set_header ssl-client-cert $ssl_client_escaped_cert nebo nasazenou v Kubernetes pomocí příchozího přenosu dat NGINX, klientský certifikát se předá aplikaci ve formuláři zakódovaném v adrese URL. Pokud chcete certifikát použít, dekódujte ho následujícím způsobem:

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

Přidejte middleware do Program.cssouboru . UseCertificateForwarding je volána před voláním a UseAuthentication UseAuthorization:

var app = builder.Build();

app.UseCertificateForwarding();

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

K implementaci logiky ověřování lze použít samostatnou třídu. Vzhledem k tomu, že se v tomto příkladu používá stejný certifikát podepsaný svým držitelem, ujistěte se, že je možné použít pouze váš certifikát. Ověřte, že kryptografické otisky klientského certifikátu i certifikátu serveru odpovídají, jinak je možné použít jakýkoli certifikát a stačit k ověření. To by se použilo AddCertificate uvnitř metody. Pokud používáte zprostředkující nebo podřízené certifikáty, můžete zde také ověřit subjekt nebo vystavitele.

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

Implementace HttpClient pomocí certifikátu a IHttpClientFactory

V následujícím příkladu se klientský certifikát přidá do HttpClientHandler vlastnosti pomocí ClientCertificates obslužné rutiny. Tuto obslužnou rutinu lze pak použít v pojmenované HttpClient instanci metody ConfigurePrimaryHttpMessageHandler . Toto nastavení je nastaveno v 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;
});

Potom IHttpClientFactory lze použít k získání pojmenované instance pomocí obslužné rutiny a certifikátu. Metoda CreateClient s názvem klienta definovaného v Program.cs se používá k získání instance. Požadavek HTTP je možné odeslat pomocí klienta podle potřeby:

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

Pokud se na server odešle správný certifikát, vrátí se data. Pokud se neposílají žádný certifikát nebo se odešle nesprávný certifikát, vrátí se stavový kód HTTP 403.

Vytvoření certifikátů v PowerShellu

Vytvoření certifikátů je nejsnadnější součástí nastavení tohoto toku. Kořenový certifikát je možné vytvořit pomocí rutiny PowerShellu New-SelfSignedCertificate . Při vytváření certifikátu použijte silné heslo. Je důležité přidat KeyUsageProperty parametr a KeyUsage parametr, jak je znázorněno.

Vytvoření kořenové certifikační autority

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

Poznámka:

Hodnota -DnsName parametru musí odpovídat cíli nasazení aplikace. Například "localhost" pro vývoj.

Instalace v důvěryhodném kořenovém adresáři

Kořenový certifikát musí být v hostitelském systému důvěryhodný. Ve výchozím nastavení jsou důvěryhodné jenom kořenové certifikáty vytvořené certifikační autoritou. Informace o tom, jak důvěřovat kořenovému certifikátu ve Windows, najdete v dokumentaci k Windows nebo v rutině PowerShellu Import-Certificate .

Zprostředkující certifikát

Zprostředkující certifikát je teď možné vytvořit z kořenového certifikátu. To není nutné pro všechny případy použití, ale možná budete muset vytvořit mnoho certifikátů nebo potřebujete aktivovat nebo zakázat skupiny certifikátů. Parametr TextExtension je nutný k nastavení délky cesty v základních omezeních certifikátu.

Zprostředkující certifikát je pak možné přidat do důvěryhodného zprostředkujícího certifikátu v hostitelském systému 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

Vytvoření podřízeného certifikátu z zprostředkujícího certifikátu

Podřízený certifikát lze vytvořit z zprostředkujícího certifikátu. Jedná se o koncovou entitu a nemusí vytvářet další podřízené certifikáty.

$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

Vytvoření podřízeného certifikátu z kořenového certifikátu

Podřízený certifikát lze také vytvořit přímo z kořenového certifikátu.

$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

Příklad kořenového certifikátu – zprostředkující certifikát – certifikát

$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

Při použití kořenového, zprostředkujícího nebo podřízeného certifikátu je možné certifikáty ověřit pomocí kryptografického otisku nebo veřejného klíče podle potřeby:

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

Ukládání do mezipaměti ověřování certifikátů

ASP.NET Core 5.0 a novějších verzích podporují možnost ukládání výsledků ověřování do mezipaměti. Ukládání do mezipaměti výrazně zvyšuje výkon ověřování certifikátů, protože ověřování je náročná operace.

Ve výchozím nastavení ověřování certifikátů zakáže ukládání do mezipaměti. Pokud chcete povolit ukládání do mezipaměti, zavolejte do AddCertificateCache Program.cs:

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

Výchozí implementace ukládání do mezipaměti ukládá výsledky v paměti. Vlastní mezipaměť můžete poskytnout implementací ICertificateValidationCache a registrací pomocí injektáže závislostí. Například services.AddSingleton<ICertificateValidationCache, YourCache>().

Volitelné klientské certifikáty

Tato část obsahuje informace pro aplikace, které musí chránit podmnožinu aplikace certifikátem. Například Razor stránka nebo kontroler v aplikaci můžou vyžadovat klientské certifikáty. To představuje problémy jako klientské certifikáty:

  • Jedná se o funkci PROTOKOLU TLS, ne o funkci HTTP.
  • Vyjednávají se pro jednotlivá připojení a obvykle na začátku připojení před dostupností dat HTTP.

Existují dva přístupy k implementaci volitelných klientských certifikátů:

  1. Použití samostatných názvů hostitelů (SNI) a přesměrování I když se konfigurace provádí více, doporučuje se to, protože funguje ve většině prostředí a protokolů.
  2. Opakované vyjednávání během požadavku HTTP. To má několik omezení a nedoporučuje se.

Samostatné hostitele (SNI)

Na začátku připojení je známo pouze označení názvu serveru (SNI) †. Klientské certifikáty je možné nakonfigurovat pro každý název hostitele, aby je jeden hostitel vyžadoval a jiný ne.

ASP.NET Core 5 a novějším přidává pohodlnější podporu pro přesměrování na získání volitelných klientských certifikátů. Další informace najdete v ukázce volitelných certifikátů.

  • Požadavky na webovou aplikaci, které vyžadují klientský certifikát a nemají ho:
    • Přesměrujte na stejnou stránku pomocí subdomény chráněné klientským certifikátem.
    • Například přesměrovat na myClient.contoso.com/requestedPage. Vzhledem k tomu, že požadavek na myClient.contoso.com/requestedPage hostitele je jiný než contoso.com/requestedPage, klient vytvoří jiné připojení a klientský certifikát se poskytne.
    • Další informace najdete v tématu Úvod k autorizaci v ASP.NET Core.

† Označení názvu serveru (SNI) je rozšíření TLS, které zahrnuje virtuální doménu jako součást vyjednávání SSL. To znamená, že virtuální název domény nebo název hostitele se dá použít k identifikaci koncového bodu sítě.

Opětovné jednání

Opětovné vyjednávání protokolu TLS je proces, pomocí kterého klient a server můžou znovu posoudit požadavky na šifrování pro jednotlivá připojení, včetně vyžádání klientského certifikátu, pokud nebylo dříve zadané. Opětovné vyjednávání protokolu TLS představuje bezpečnostní riziko a nedoporučuje se, protože:

  • V PROTOKOLU HTTP/1.1 musí server nejprve ukládat do vyrovnávací paměti nebo využívat veškerá data HTTP, která jsou v testovací verzi, jako jsou například těla požadavku POST, aby bylo zajištěno, že je připojení pro nové vyjednávání jasné. V opačném případě může nové vyjednávání přestat reagovat nebo selhat.
  • HTTP/2 a HTTP/3 explicitně zakazují opakované vyjednávání.
  • Při opětovném vyjednávání existují rizika zabezpečení. Protokol TLS 1.3 odebral nové vyjednávání celého připojení a nahradil ho novým rozšířením pro vyžádání pouze klientského certifikátu po spuštění připojení. Tento mechanismus je přístupný prostřednictvím stejných rozhraní API a stále podléhá předchozím omezením ukládání do vyrovnávací paměti a verzí protokolu HTTP.

Implementace a konfigurace této funkce se liší podle verze serveru a architektury.

IIS

Služba IIS spravuje vyjednávání klientských certifikátů vaším jménem. Pododdíl aplikace umožňuje SslRequireCert vyjednat klientský certifikát pro tyto požadavky. Podrobnosti najdete v dokumentaci ke službě IIS v části Konfigurace.

Služba IIS automaticky uloží veškerá data textu požadavku do nakonfigurovaného limitu velikosti před opětovným vyjednáváním. Žádosti, které překračují limit, se zamítnou odpovědí 413. Tento limit je ve výchozím nastavení 48 kB a je konfigurovatelný nastavením uploadReadAheadSize.

HttpSys

HttpSys má dvě nastavení, která řídí vyjednávání klientských certifikátů a obě mají být nastaveny. První je v netsh.exe pod http add sslcert clientcertnegotiation=enable/disable. Tento příznak označuje, jestli se má klientský certifikát vyjednat na začátku připojení a měl by být nastavený na disable volitelné klientské certifikáty. Podrobnosti najdete v dokumentaci netsh.

Druhé nastavení je ClientCertificateMethod. Pokud je nastavená hodnota AllowRenegotation, klientský certifikát lze během žádosti znovu projednat.

POZNÁMKA: Aplikace by měla ukládat do vyrovnávací paměti nebo využívat jakákoli data textu požadavku před pokusem o nové vyjednávání, jinak může požadavek přestat reagovat.

Aplikace může nejprve zkontrolovat ClientCertificate vlastnost a zjistit, jestli je certifikát k dispozici. Pokud není k dispozici, ujistěte se, že tělo požadavku bylo před voláním GetClientCertificateAsync k vyjednání spotřebováno. Poznámka GetClientCertificateAsync může vrátit certifikát null, pokud klient odmítne zadat certifikát.

POZNÁMKA: Chování ClientCertificate vlastnosti změněno v .NET 6. Další informace najdete u tohoto problému na GitHubu.

Kestrel

Kestrel řídí vyjednávání klientských certifikátů s ClientCertificateMode možností.

ClientCertificateMode.DelayCertificate je nová možnost dostupná v .NET 6 nebo novějším. Při nastavení může aplikace zkontrolovat ClientCertificate vlastnost a zjistit, jestli je certifikát dostupný. Pokud není k dispozici, ujistěte se, že tělo požadavku bylo před voláním GetClientCertificateAsync k vyjednání spotřebováno. Poznámka GetClientCertificateAsync může vrátit certifikát null, pokud klient odmítne zadat certifikát.

POZNÁMKA: Aplikace by měla ukládat do vyrovnávací paměti nebo spotřebovávat jakákoli data těla požadavku před pokusem o nové vyjednávání, jinak GetClientCertificateAsync může vyvolat InvalidOperationException: Client stream needs to be drained before renegotiation..

Pokud konfigurujete nastavení protokolu TLS na název hostitele SNI prostřednictvím kódu programu, zavolejte UseHttps přetížení (.NET 6 nebo novější), které přebírá TlsHandshakeCallbackOptions a řídí nové vyjednávání klientských certifikátů prostřednictvím TlsHandshakeCallbackContext.AllowDelayedClientCertificateNegotation.

Microsoft.AspNetCore.Authentication.Certificate obsahuje implementaci podobnou ověřování certifikátů pro ASP.NET Core. Ověřování certifikátů probíhá na úrovni protokolu TLS, a to dlouho předtím, než se někdy dostane k ASP.NET Core. Přesněji řečeno, jedná se o obslužnou rutinu ověřování, která ověřuje certifikát a poté poskytuje událost, ve které můžete tento certifikát přeložit na ClaimsPrincipal.

Nakonfigurujte server pro ověřování certifikátů, ať už je to služba IIS, KestrelAzure Web Apps nebo cokoli jiného, co používáte.

Scénáře proxy serveru a nástroje pro vyrovnávání zatížení

Ověřování certifikátů je stavový scénář, kdy proxy server nebo nástroj pro vyrovnávání zatížení nezpracuje provoz mezi klienty a servery. Pokud se používá proxy server nebo nástroj pro vyrovnávání zatížení, ověřování certifikátů funguje jenom v případě, že proxy server nebo nástroj pro vyrovnávání zatížení:

  • Zpracovává ověřování.
  • Předá ověřovací informace uživatele aplikaci (například v hlavičce požadavku), která funguje na ověřovacích informacích.

Alternativou k ověřování certifikátů v prostředích, kde se používají proxy servery a nástroje pro vyrovnávání zatížení, je služba Active Directory Federated Services (ADFS) s OpenID Connect (OIDC).

Začínáme

Získejte certifikát HTTPS, použijte ho a nakonfigurujte server tak, aby vyžadoval certifikáty.

Ve webové aplikaci přidejte odkaz na balíček Microsoft.AspNetCore.Authentication.Certificate . Pak v Startup.ConfigureServices metodě zavolejte services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...); s vašimi možnostmi a zadejte delegáta OnCertificateValidated , aby u klientského certifikátu odeslaného s požadavky udělal jakékoli dodatečné ověření. Změňte informace na hodnotu ClaimsPrincipal a nastavte ji u context.Principal vlastnosti.

Pokud ověřování selže, vrátí tato obslužná rutina 403 (Forbidden) 401 (Unauthorized)spíše odpověď , jak byste mohli očekávat. Důvodem je, že ověřování by mělo proběhnout během počátečního připojení TLS. V době, kdy dosáhne obslužné rutiny, je příliš pozdě. Neexistuje způsob, jak upgradovat připojení z anonymního připojení na jedno s certifikátem.

app.UseAuthentication(); Přidejte také metoduStartup.Configure. V opačném případě nebude nastaveno HttpContext.User na ClaimsPrincipal vytvoření z certifikátu. Příklad:

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
}

Předchozí příklad ukazuje výchozí způsob přidání ověřování certifikátu. Obslužná rutina vytvoří objekt zabezpečení uživatele pomocí společných vlastností certifikátu.

Konfigurace ověření certifikátu

Obslužná CertificateAuthenticationOptions rutina má několik předdefinovaných ověření, která jsou minimálními ověřeními, která byste měli provést u certifikátu. Každé z těchto nastavení je ve výchozím nastavení povolené.

AllowedCertificateTypes = Chained, SelfSigned nebo All (zřetězeno | SelfSigned)

Výchozí hodnota: CertificateTypes.Chained

Tato kontrola ověří, že je povolený pouze příslušný typ certifikátu. Pokud aplikace používá certifikáty podepsané svým držitelem, musí být tato možnost nastavena na CertificateTypes.All hodnotu nebo CertificateTypes.SelfSigned.

ValidateCertificateUse

Výchozí hodnota: true

Tato kontrola ověří, že certifikát prezentovaný klientem používá rozšířený klíč ověřování klienta (EKU) nebo vůbec žádné EKU. Jak uvádí specifikace, pokud není zadán žádný EKU, považují se všechny EKU za platné.

ValidateValidityPeriod

Výchozí hodnota: true

Tato kontrola ověří, že certifikát spadá do doby platnosti. V každém požadavku obslužná rutina zajistí, že během aktuální relace nevypršela platnost certifikátu, který byl platný při jeho předložení.

RevocationFlag

Výchozí hodnota: X509RevocationFlag.ExcludeRoot

Příznak, který určuje, které certifikáty v řetězu se kontrolují pro odvolání.

Kontroly odvolání se provádějí pouze v případě, že je certifikát zřetězený s kořenovým certifikátem.

RevocationMode

Výchozí hodnota: X509RevocationMode.Online

Příznak, který určuje, jak se provádějí kontroly odvolání.

Zadání online kontroly může vést k dlouhé prodlevě, když se kontaktuje certifikační autorita.

Kontroly odvolání se provádějí pouze v případě, že je certifikát zřetězený s kořenovým certifikátem.

Můžu aplikaci nakonfigurovat tak, aby vyžadovala certifikát jenom na určitých cestách?

To není možné. Nezapomeňte, že výměna certifikátů se provádí na začátku konverzace HTTPS. Server ji provádí před přijetím prvního požadavku na toto připojení, takže není možné nastavit obor na základě polí požadavku.

Události obslužné rutiny

Obslužná rutina má dvě události:

  • OnAuthenticationFailed: Volá se, pokud během ověřování dojde k výjimce a umožní vám reagovat.
  • OnCertificateValidated: Volá se po ověření certifikátu, úspěšném ověření a vytvoření výchozího objektu zabezpečení. Tato událost umožňuje provést vlastní ověření a rozšířit nebo nahradit objekt zabezpečení. Příklady:
    • Určení, jestli je certifikát známý pro vaše služby.

    • Vytvoření vlastního objektu zabezpečení Podívejte se na následující příklad v 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;
                  }
              };
          });
      

Pokud zjistíte, že příchozí certifikát nesplňuje vaše dodatečné ověření, zavolejte context.Fail("failure reason") s důvodem selhání.

Pro skutečné funkce budete pravděpodobně chtít volat službu zaregistrovanou v injektáži závislostí, která se připojuje k databázi nebo jinému typu úložiště uživatelů. Přistupovat ke službě pomocí kontextu předaného vašemu delegátu. Podívejte se na následující příklad v 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;
            }
        };
    });

Ověření certifikátu je koncepčně problém s autorizací. Přidání kontroly, například vystavitele nebo kryptografického otisku v autorizačních zásadách, nikoli uvnitř OnCertificateValidated, je naprosto přijatelné.

Konfigurace serveru tak, aby vyžadoval certifikáty

Kestrel

V Program.csnástroji , nakonfigurujte Kestrel následující:

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

Poznámka:

Koncové body vytvořené voláním Listen před voláním ConfigureHttpsDefaults nebudou mít použité výchozí hodnoty.

IIS

Ve Správci služby IIS proveďte následující kroky:

  1. Na kartě Připojení vyberte svůj web.
  2. Poklikejte na možnost Nastavení PROTOKOLU SSL v okně Zobrazení funkcí.
  3. Zaškrtněte políčko Vyžadovat SSL a v části Klientské certifikáty vyberte přepínač Vyžadovat.

Nastavení klientského certifikátu ve službě IIS

Azure a vlastní webové proxy servery

Informace o konfiguraci middlewaru předávání certifikátů najdete v dokumentaci k hostiteli a nasazení.

Použití ověřování certifikátů ve službě Azure Web Apps

Azure nevyžaduje žádnou konfiguraci předávání. Konfigurace předávání je nastavena middlewarem předávání certifikátů.

Poznámka:

Pro tento scénář se vyžaduje middleware pro předávání certifikátů.

Další informace najdete v tématu Použití certifikátu TLS/SSL v kódu ve službě Aplikace Azure Service (dokumentace k Azure).

Použití ověřování certifikátů ve vlastních webových proxy serverech

Metoda AddCertificateForwarding se používá k určení:

  • Název hlavičky klienta.
  • Jak se certifikát načte (pomocí HeaderConverter vlastnosti).

Ve vlastních webových proxy serverech se certifikát předává jako vlastní hlavička požadavku, například X-SSL-CERT. Pokud ho chcete použít, nakonfigurujte předávání certifikátů v 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;
}

Pokud je aplikace zpětnou adresou NGINX s konfigurací proxy_set_header ssl-client-cert $ssl_client_escaped_cert nebo nasazenou v Kubernetes pomocí příchozího přenosu dat NGINX, klientský certifikát se předá aplikaci ve formuláři zakódovaném v adrese URL. Pokud chcete certifikát použít, dekódujte ho následujícím způsobem:

V Startup.ConfigureServices (Startup.cs):

services.AddCertificateForwarding(options =>
{
    options.CertificateHeader = "ssl-client-cert";
    options.HeaderConverter = (headerValue) =>
    {
        X509Certificate2 clientCertificate = null;

        if (!string.IsNullOrWhiteSpace(headerValue))
        {
            string certPem = WebUtility.UrlDecode(headerValue);
            clientCertificate = X509Certificate2.CreateFromPem(certPem);
        }

        return clientCertificate;
    };
});

Metoda Startup.Configure pak přidá middleware. UseCertificateForwarding je volána před voláním a UseAuthentication UseAuthorization:

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

    app.UseRouting();

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

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

K implementaci logiky ověřování lze použít samostatnou třídu. Vzhledem k tomu, že se v tomto příkladu používá stejný certifikát podepsaný svým držitelem, ujistěte se, že je možné použít pouze váš certifikát. Ověřte, že kryptografické otisky klientského certifikátu i certifikátu serveru odpovídají, jinak je možné použít jakýkoli certifikát a stačit k ověření. To by se použilo AddCertificate uvnitř metody. Pokud používáte zprostředkující nebo podřízené certifikáty, můžete zde také ověřit subjekt nebo vystavitele.

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

Implementace HttpClient pomocí certifikátu a obslužné rutiny HttpClient

Lze HttpClientHandler přidat přímo v konstruktoru HttpClient třídy. Při vytváření instancí objektu HttpClient. Certifikát HttpClient se pak odešle s každou žádostí.

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

Implementace HttpClient pomocí certifikátu a pojmenovaného HttpClient z IHttpClientFactory

V následujícím příkladu se klientský certifikát přidá do HttpClientHandler vlastnosti pomocí ClientCertificates obslužné rutiny. Tuto obslužnou rutinu lze pak použít v pojmenované HttpClient instanci metody ConfigurePrimaryHttpMessageHandler . Toto nastavení je nastaveno v 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;
});

Potom IHttpClientFactory lze použít k získání pojmenované instance pomocí obslužné rutiny a certifikátu. Metoda CreateClient s názvem klienta definovaného ve Startup třídě se používá k získání instance. Požadavek HTTP je možné odeslat pomocí klienta podle potřeby.

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

Pokud se na server odešle správný certifikát, vrátí se data. Pokud se neposílají žádný certifikát nebo se odešle nesprávný certifikát, vrátí se stavový kód HTTP 403.

Vytvoření certifikátů v PowerShellu

Vytvoření certifikátů je nejsnadnější součástí nastavení tohoto toku. Kořenový certifikát je možné vytvořit pomocí rutiny PowerShellu New-SelfSignedCertificate . Při vytváření certifikátu použijte silné heslo. Je důležité přidat KeyUsageProperty parametr a KeyUsage parametr, jak je znázorněno.

Vytvoření kořenové certifikační autority

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

Poznámka:

Hodnota -DnsName parametru musí odpovídat cíli nasazení aplikace. Například "localhost" pro vývoj.

Instalace v důvěryhodném kořenovém adresáři

Kořenový certifikát musí být v hostitelském systému důvěryhodný. Kořenový certifikát, který nevytvořil certifikační autorita, nebude ve výchozím nastavení důvěryhodný. Informace o tom, jak důvěřovat kořenovému certifikátu ve Windows, najdete v této otázce.

Zprostředkující certifikát

Zprostředkující certifikát je teď možné vytvořit z kořenového certifikátu. To není nutné pro všechny případy použití, ale možná budete muset vytvořit mnoho certifikátů nebo potřebujete aktivovat nebo zakázat skupiny certifikátů. Parametr TextExtension je nutný k nastavení délky cesty v základních omezeních certifikátu.

Zprostředkující certifikát je pak možné přidat do důvěryhodného zprostředkujícího certifikátu v hostitelském systému 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

Vytvoření podřízeného certifikátu z zprostředkujícího certifikátu

Podřízený certifikát lze vytvořit z zprostředkujícího certifikátu. Jedná se o koncovou entitu a nemusí vytvářet další podřízené certifikáty.

$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

Vytvoření podřízeného certifikátu z kořenového certifikátu

Podřízený certifikát lze také vytvořit přímo z kořenového certifikátu.

$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

Příklad kořenového certifikátu – zprostředkující certifikát – certifikát

$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

Při použití kořenových, zprostředkujících nebo podřízených certifikátů je možné certifikáty ověřit pomocí kryptografického otisku nebo veřejného klíče podle potřeby.

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

Ukládání do mezipaměti ověřování certifikátů

ASP.NET Core 5.0 a novějších verzích podporují možnost ukládání výsledků ověřování do mezipaměti. Ukládání do mezipaměti výrazně zvyšuje výkon ověřování certifikátů, protože ověřování je náročná operace.

Ve výchozím nastavení ověřování certifikátů zakáže ukládání do mezipaměti. Pokud chcete povolit ukládání do mezipaměti, zavolejte do AddCertificateCache Startup.ConfigureServices:

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

Výchozí implementace ukládání do mezipaměti ukládá výsledky v paměti. Vlastní mezipaměť můžete poskytnout implementací ICertificateValidationCache a registrací pomocí injektáže závislostí. Například services.AddSingleton<ICertificateValidationCache, YourCache>().

Volitelné klientské certifikáty

Tato část obsahuje informace pro aplikace, které musí chránit podmnožinu aplikace certifikátem. Například Razor stránka nebo kontroler v aplikaci můžou vyžadovat klientské certifikáty. To představuje problémy jako klientské certifikáty:

  • Jedná se o funkci PROTOKOLU TLS, ne o funkci HTTP.
  • Vyjednávají se pro jednotlivá připojení a obvykle na začátku připojení před dostupností dat HTTP.

Existují dva přístupy k implementaci volitelných klientských certifikátů:

  1. Použití samostatných názvů hostitelů (SNI) a přesměrování I když se konfigurace provádí více, doporučuje se to, protože funguje ve většině prostředí a protokolů.
  2. Opakované vyjednávání během požadavku HTTP. To má několik omezení a nedoporučuje se.

Samostatné hostitele (SNI)

Na začátku připojení je známo pouze označení názvu serveru (SNI) †. Klientské certifikáty je možné nakonfigurovat pro každý název hostitele, aby je jeden hostitel vyžadoval a jiný ne.

ASP.NET Core 5 a novějším přidává pohodlnější podporu pro přesměrování na získání volitelných klientských certifikátů. Další informace najdete v ukázce volitelných certifikátů.

  • Požadavky na webovou aplikaci, které vyžadují klientský certifikát a nemají ho:
    • Přesměrujte na stejnou stránku pomocí subdomény chráněné klientským certifikátem.
    • Například přesměrovat na myClient.contoso.com/requestedPage. Vzhledem k tomu, že požadavek na myClient.contoso.com/requestedPage hostitele je jiný než contoso.com/requestedPage, klient vytvoří jiné připojení a klientský certifikát se poskytne.
    • Další informace najdete v tématu Úvod k autorizaci v ASP.NET Core.

† Označení názvu serveru (SNI) je rozšíření TLS, které zahrnuje virtuální doménu jako součást vyjednávání SSL. To znamená, že virtuální název domény nebo název hostitele se dá použít k identifikaci koncového bodu sítě.

Opětovné jednání

Opětovné vyjednávání protokolu TLS je proces, pomocí kterého klient a server můžou znovu posoudit požadavky na šifrování pro jednotlivá připojení, včetně vyžádání klientského certifikátu, pokud nebylo dříve zadané. Opětovné vyjednávání protokolu TLS představuje bezpečnostní riziko a nedoporučuje se, protože:

  • V PROTOKOLU HTTP/1.1 musí server nejprve ukládat do vyrovnávací paměti nebo využívat veškerá data HTTP, která jsou v testovací verzi, jako jsou například těla požadavku POST, aby bylo zajištěno, že je připojení pro nové vyjednávání jasné. V opačném případě může nové vyjednávání přestat reagovat nebo selhat.
  • HTTP/2 a HTTP/3 explicitně zakazují opakované vyjednávání.
  • Při opětovném vyjednávání existují rizika zabezpečení. Protokol TLS 1.3 odebral nové vyjednávání celého připojení a nahradil ho novým rozšířením pro vyžádání pouze klientského certifikátu po spuštění připojení. Tento mechanismus je přístupný prostřednictvím stejných rozhraní API a stále podléhá předchozím omezením ukládání do vyrovnávací paměti a verzí protokolu HTTP.

Implementace a konfigurace této funkce se liší podle verze serveru a architektury.

IIS

Služba IIS spravuje vyjednávání klientských certifikátů vaším jménem. Pododdíl aplikace umožňuje SslRequireCert vyjednat klientský certifikát pro tyto požadavky. Podrobnosti najdete v dokumentaci ke službě IIS v části Konfigurace.

Služba IIS automaticky uloží veškerá data textu požadavku do nakonfigurovaného limitu velikosti před opětovným vyjednáváním. Žádosti, které překračují limit, se zamítnou odpovědí 413. Tento limit je ve výchozím nastavení 48 kB a je konfigurovatelný nastavením uploadReadAheadSize.

HttpSys

HttpSys má dvě nastavení, která řídí vyjednávání klientských certifikátů a obě mají být nastaveny. První je v netsh.exe pod http add sslcert clientcertnegotiation=enable/disable. Tento příznak označuje, jestli se má klientský certifikát vyjednat na začátku připojení a měl by být nastavený na disable volitelné klientské certifikáty. Podrobnosti najdete v dokumentaci netsh.

Druhé nastavení je ClientCertificateMethod. Pokud je nastavená hodnota AllowRenegotation, klientský certifikát lze během žádosti znovu projednat.

POZNÁMKA: Aplikace by měla ukládat do vyrovnávací paměti nebo využívat jakákoli data textu požadavku před pokusem o nové vyjednávání, jinak může požadavek přestat reagovat.

Existuje známý problém, kdy povolení AllowRenegotation může způsobit synchronní přejednání při přístupu k ClientCertificate vlastnosti. Zavolejte metodu GetClientCertificateAsync , aby se tomu zabránilo. Tento problém je vyřešený v .NET 6. Další informace najdete u tohoto problému na GitHubu. Poznámka GetClientCertificateAsync může vrátit certifikát null, pokud klient odmítne zadat certifikát.

Kestrel

Kestrel řídí vyjednávání klientských certifikátů s ClientCertificateMode možností.

Pro .NET 5 a starší Kestrel nepodporuje opětovné vyjednávání po spuštění připojení k získání klientského certifikátu. Tato funkce byla přidána v .NET 6.

Microsoft.AspNetCore.Authentication.Certificate obsahuje implementaci podobnou ověřování certifikátů pro ASP.NET Core. Ověřování certifikátů probíhá na úrovni protokolu TLS, a to dlouho předtím, než se někdy dostane k ASP.NET Core. Přesněji řečeno, jedná se o obslužnou rutinu ověřování, která ověřuje certifikát a poté poskytuje událost, ve které můžete tento certifikát přeložit na ClaimsPrincipal.

Nakonfigurujte server pro ověřování certifikátů, ať už je to služba IIS, KestrelAzure Web Apps nebo cokoli jiného, co používáte.

Scénáře proxy serveru a nástroje pro vyrovnávání zatížení

Ověřování certifikátů je stavový scénář, kdy proxy server nebo nástroj pro vyrovnávání zatížení nezpracuje provoz mezi klienty a servery. Pokud se používá proxy server nebo nástroj pro vyrovnávání zatížení, ověřování certifikátů funguje jenom v případě, že proxy server nebo nástroj pro vyrovnávání zatížení:

  • Zpracovává ověřování.
  • Předá ověřovací informace uživatele aplikaci (například v hlavičce požadavku), která funguje na ověřovacích informacích.

Alternativou k ověřování certifikátů v prostředích, kde se používají proxy servery a nástroje pro vyrovnávání zatížení, je služba Active Directory Federated Services (ADFS) s OpenID Connect (OIDC).

Začínáme

Získejte certifikát HTTPS, použijte ho a nakonfigurujte server tak, aby vyžadoval certifikáty.

Ve webové aplikaci přidejte odkaz na balíček Microsoft.AspNetCore.Authentication.Certificate . Pak v Startup.ConfigureServices metodě zavolejte services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...); s vašimi možnostmi a zadejte delegáta OnCertificateValidated , aby u klientského certifikátu odeslaného s požadavky udělal jakékoli dodatečné ověření. Změňte informace na hodnotu ClaimsPrincipal a nastavte ji u context.Principal vlastnosti.

Pokud ověřování selže, vrátí tato obslužná rutina 403 (Forbidden) 401 (Unauthorized)spíše odpověď , jak byste mohli očekávat. Důvodem je, že ověřování by mělo proběhnout během počátečního připojení TLS. V době, kdy dosáhne obslužné rutiny, je příliš pozdě. Neexistuje způsob, jak upgradovat připojení z anonymního připojení na jedno s certifikátem.

app.UseAuthentication(); Přidejte také metoduStartup.Configure. V opačném případě nebude nastaveno HttpContext.User na ClaimsPrincipal vytvoření z certifikátu. Příklad:

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
}

Předchozí příklad ukazuje výchozí způsob přidání ověřování certifikátu. Obslužná rutina vytvoří objekt zabezpečení uživatele pomocí společných vlastností certifikátu.

Konfigurace ověření certifikátu

Obslužná CertificateAuthenticationOptions rutina má několik předdefinovaných ověření, která jsou minimálními ověřeními, která byste měli provést u certifikátu. Každé z těchto nastavení je ve výchozím nastavení povolené.

AllowedCertificateTypes = Chained, SelfSigned nebo All (zřetězeno | SelfSigned)

Výchozí hodnota: CertificateTypes.Chained

Tato kontrola ověří, že je povolený pouze příslušný typ certifikátu. Pokud aplikace používá certifikáty podepsané svým držitelem, musí být tato možnost nastavena na CertificateTypes.All hodnotu nebo CertificateTypes.SelfSigned.

ValidateCertificateUse

Výchozí hodnota: true

Tato kontrola ověří, že certifikát prezentovaný klientem používá rozšířený klíč ověřování klienta (EKU) nebo vůbec žádné EKU. Jak uvádí specifikace, pokud není zadán žádný EKU, považují se všechny EKU za platné.

ValidateValidityPeriod

Výchozí hodnota: true

Tato kontrola ověří, že certifikát spadá do doby platnosti. V každém požadavku obslužná rutina zajistí, že během aktuální relace nevypršela platnost certifikátu, který byl platný při jeho předložení.

RevocationFlag

Výchozí hodnota: X509RevocationFlag.ExcludeRoot

Příznak, který určuje, které certifikáty v řetězu se kontrolují pro odvolání.

Kontroly odvolání se provádějí pouze v případě, že je certifikát zřetězený s kořenovým certifikátem.

RevocationMode

Výchozí hodnota: X509RevocationMode.Online

Příznak, který určuje, jak se provádějí kontroly odvolání.

Zadání online kontroly může vést k dlouhé prodlevě, když se kontaktuje certifikační autorita.

Kontroly odvolání se provádějí pouze v případě, že je certifikát zřetězený s kořenovým certifikátem.

Můžu aplikaci nakonfigurovat tak, aby vyžadovala certifikát jenom na určitých cestách?

To není možné. Nezapomeňte, že výměna certifikátů se provádí na začátku konverzace HTTPS. Server ji provádí před přijetím prvního požadavku na toto připojení, takže není možné nastavit obor na základě polí požadavku.

Události obslužné rutiny

Obslužná rutina má dvě události:

  • OnAuthenticationFailed: Volá se, pokud během ověřování dojde k výjimce a umožní vám reagovat.
  • OnCertificateValidated: Volá se po ověření certifikátu, úspěšném ověření a vytvoření výchozího objektu zabezpečení. Tato událost umožňuje provést vlastní ověření a rozšířit nebo nahradit objekt zabezpečení. Příklady:
    • Určení, jestli je certifikát známý pro vaše služby.

    • Vytvoření vlastního objektu zabezpečení Podívejte se na následující příklad v 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;
                  }
              };
          });
      

Pokud zjistíte, že příchozí certifikát nesplňuje vaše dodatečné ověření, zavolejte context.Fail("failure reason") s důvodem selhání.

Pro skutečné funkce budete pravděpodobně chtít volat službu zaregistrovanou v injektáži závislostí, která se připojuje k databázi nebo jinému typu úložiště uživatelů. Přistupovat ke službě pomocí kontextu předaného vašemu delegátu. Podívejte se na následující příklad v 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;
            }
        };
    });

Ověření certifikátu je koncepčně problém s autorizací. Přidání kontroly, například vystavitele nebo kryptografického otisku v autorizačních zásadách, nikoli uvnitř OnCertificateValidated, je naprosto přijatelné.

Konfigurace serveru tak, aby vyžadoval certifikáty

Kestrel

V Program.csnástroji , nakonfigurujte Kestrel následující:

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

Poznámka:

Koncové body vytvořené voláním Listen před voláním ConfigureHttpsDefaults nebudou mít použité výchozí hodnoty.

IIS

Ve Správci služby IIS proveďte následující kroky:

  1. Na kartě Připojení vyberte svůj web.
  2. Poklikejte na možnost Nastavení PROTOKOLU SSL v okně Zobrazení funkcí.
  3. Zaškrtněte políčko Vyžadovat SSL a v části Klientské certifikáty vyberte přepínač Vyžadovat.

Nastavení klientského certifikátu ve službě IIS

Azure a vlastní webové proxy servery

Informace o konfiguraci middlewaru předávání certifikátů najdete v dokumentaci k hostiteli a nasazení.

Použití ověřování certifikátů ve službě Azure Web Apps

Azure nevyžaduje žádnou konfiguraci předávání. Konfigurace předávání je nastavena middlewarem předávání certifikátů.

Poznámka:

Pro tento scénář se vyžaduje middleware pro předávání certifikátů.

Další informace najdete v tématu Použití certifikátu TLS/SSL v kódu ve službě Aplikace Azure Service (dokumentace k Azure).

Použití ověřování certifikátů ve vlastních webových proxy serverech

Metoda AddCertificateForwarding se používá k určení:

  • Název hlavičky klienta.
  • Jak se certifikát načte (pomocí HeaderConverter vlastnosti).

Ve vlastních webových proxy serverech se certifikát předává jako vlastní hlavička požadavku, například X-SSL-CERT. Pokud ho chcete použít, nakonfigurujte předávání certifikátů v 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;
}

Pokud je aplikace zpětnou adresou NGINX s konfigurací proxy_set_header ssl-client-cert $ssl_client_escaped_cert nebo nasazenou v Kubernetes pomocí příchozího přenosu dat NGINX, klientský certifikát se předá aplikaci ve formuláři zakódovaném v adrese URL. Pokud chcete certifikát použít, dekódujte ho následujícím způsobem:

Přidejte obor názvů do System.Net horní části :Startup.cs

using System.Net;

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

Přidejte metodu UrlEncodedPemToByteArray :

private static byte[] UrlEncodedPemToByteArray(string urlEncodedBase64Pem)
{
    var base64Pem = WebUtility.UrlDecode(urlEncodedBase64Pem);
    var base64Cert = base64Pem
        .Replace("-----BEGIN CERTIFICATE-----", string.Empty)
        .Replace("-----END CERTIFICATE-----", string.Empty)
        .Trim();

    return Convert.FromBase64String(base64Cert);
}

Metoda Startup.Configure pak přidá middleware. UseCertificateForwarding je volána před voláním a UseAuthentication UseAuthorization:

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

    app.UseRouting();

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

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

K implementaci logiky ověřování lze použít samostatnou třídu. Vzhledem k tomu, že se v tomto příkladu používá stejný certifikát podepsaný svým držitelem, ujistěte se, že je možné použít pouze váš certifikát. Ověřte, že kryptografické otisky klientského certifikátu i certifikátu serveru odpovídají, jinak je možné použít jakýkoli certifikát a stačit k ověření. To by se použilo AddCertificate uvnitř metody. Pokud používáte zprostředkující nebo podřízené certifikáty, můžete zde také ověřit subjekt nebo vystavitele.

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

Implementace HttpClient pomocí certifikátu a obslužné rutiny HttpClient

Lze HttpClientHandler přidat přímo v konstruktoru HttpClient třídy. Při vytváření instancí objektu HttpClient. Certifikát HttpClient se pak odešle s každou žádostí.

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

Implementace HttpClient pomocí certifikátu a pojmenovaného HttpClient z IHttpClientFactory

V následujícím příkladu se klientský certifikát přidá do HttpClientHandler vlastnosti pomocí ClientCertificates obslužné rutiny. Tuto obslužnou rutinu lze pak použít v pojmenované HttpClient instanci metody ConfigurePrimaryHttpMessageHandler . Toto nastavení je nastaveno v 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;
});

Potom IHttpClientFactory lze použít k získání pojmenované instance pomocí obslužné rutiny a certifikátu. Metoda CreateClient s názvem klienta definovaného ve Startup třídě se používá k získání instance. Požadavek HTTP je možné odeslat pomocí klienta podle potřeby.

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

Pokud se na server odešle správný certifikát, vrátí se data. Pokud se neposílají žádný certifikát nebo se odešle nesprávný certifikát, vrátí se stavový kód HTTP 403.

Vytvoření certifikátů v PowerShellu

Vytvoření certifikátů je nejsnadnější součástí nastavení tohoto toku. Kořenový certifikát je možné vytvořit pomocí rutiny PowerShellu New-SelfSignedCertificate . Při vytváření certifikátu použijte silné heslo. Je důležité přidat KeyUsageProperty parametr a KeyUsage parametr, jak je znázorněno.

Vytvoření kořenové certifikační autority

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

Poznámka:

Hodnota -DnsName parametru musí odpovídat cíli nasazení aplikace. Například "localhost" pro vývoj.

Instalace v důvěryhodném kořenovém adresáři

Kořenový certifikát musí být v hostitelském systému důvěryhodný. Kořenový certifikát, který nevytvořil certifikační autorita, nebude ve výchozím nastavení důvěryhodný. Informace o tom, jak důvěřovat kořenovému certifikátu ve Windows, najdete v této otázce.

Zprostředkující certifikát

Zprostředkující certifikát je teď možné vytvořit z kořenového certifikátu. To není nutné pro všechny případy použití, ale možná budete muset vytvořit mnoho certifikátů nebo potřebujete aktivovat nebo zakázat skupiny certifikátů. Parametr TextExtension je nutný k nastavení délky cesty v základních omezeních certifikátu.

Zprostředkující certifikát je pak možné přidat do důvěryhodného zprostředkujícího certifikátu v hostitelském systému 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

Vytvoření podřízeného certifikátu z zprostředkujícího certifikátu

Podřízený certifikát lze vytvořit z zprostředkujícího certifikátu. Jedná se o koncovou entitu a nemusí vytvářet další podřízené certifikáty.

$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

Vytvoření podřízeného certifikátu z kořenového certifikátu

Podřízený certifikát lze také vytvořit přímo z kořenového certifikátu.

$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

Příklad kořenového certifikátu – zprostředkující certifikát – certifikát

$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

Při použití kořenových, zprostředkujících nebo podřízených certifikátů je možné certifikáty ověřit pomocí kryptografického otisku nebo veřejného klíče podle potřeby.

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

Volitelné klientské certifikáty

Tato část obsahuje informace pro aplikace, které musí chránit podmnožinu aplikace certifikátem. Například Razor stránka nebo kontroler v aplikaci můžou vyžadovat klientské certifikáty. To představuje problémy jako klientské certifikáty:

  • Jedná se o funkci PROTOKOLU TLS, ne o funkci HTTP.
  • Vyjednávají se pro jednotlivá připojení a obvykle na začátku připojení před dostupností dat HTTP.

Existují dva přístupy k implementaci volitelných klientských certifikátů:

  1. Použití samostatných názvů hostitelů (SNI) a přesměrování I když se konfigurace provádí více, doporučuje se to, protože funguje ve většině prostředí a protokolů.
  2. Opakované vyjednávání během požadavku HTTP. To má několik omezení a nedoporučuje se.

Samostatné hostitele (SNI)

Na začátku připojení je známo pouze označení názvu serveru (SNI) †. Klientské certifikáty je možné nakonfigurovat pro každý název hostitele, aby je jeden hostitel vyžadoval a jiný ne.

ASP.NET Core 5 a novějším přidává pohodlnější podporu pro přesměrování na získání volitelných klientských certifikátů. Další informace najdete v ukázce volitelných certifikátů.

  • Požadavky na webovou aplikaci, které vyžadují klientský certifikát a nemají ho:
    • Přesměrujte na stejnou stránku pomocí subdomény chráněné klientským certifikátem.
    • Například přesměrovat na myClient.contoso.com/requestedPage. Vzhledem k tomu, že požadavek na myClient.contoso.com/requestedPage hostitele je jiný než contoso.com/requestedPage, klient vytvoří jiné připojení a klientský certifikát se poskytne.
    • Další informace najdete v tématu Úvod k autorizaci v ASP.NET Core.

† Označení názvu serveru (SNI) je rozšíření TLS, které zahrnuje virtuální doménu jako součást vyjednávání SSL. To znamená, že virtuální název domény nebo název hostitele se dá použít k identifikaci koncového bodu sítě.

Opětovné jednání

Opětovné vyjednávání protokolu TLS je proces, pomocí kterého klient a server můžou znovu posoudit požadavky na šifrování pro jednotlivá připojení, včetně vyžádání klientského certifikátu, pokud nebylo dříve zadané. Opětovné vyjednávání protokolu TLS představuje bezpečnostní riziko a nedoporučuje se, protože:

  • V PROTOKOLU HTTP/1.1 musí server nejprve ukládat do vyrovnávací paměti nebo využívat veškerá data HTTP, která jsou v testovací verzi, jako jsou například těla požadavku POST, aby bylo zajištěno, že je připojení pro nové vyjednávání jasné. V opačném případě může nové vyjednávání přestat reagovat nebo selhat.
  • HTTP/2 a HTTP/3 explicitně zakazují opakované vyjednávání.
  • Při opětovném vyjednávání existují rizika zabezpečení. Protokol TLS 1.3 odebral nové vyjednávání celého připojení a nahradil ho novým rozšířením pro vyžádání pouze klientského certifikátu po spuštění připojení. Tento mechanismus je přístupný prostřednictvím stejných rozhraní API a stále podléhá předchozím omezením ukládání do vyrovnávací paměti a verzí protokolu HTTP.

Implementace a konfigurace této funkce se liší podle verze serveru a architektury.

IIS

Služba IIS spravuje vyjednávání klientských certifikátů vaším jménem. Pododdíl aplikace umožňuje SslRequireCert vyjednat klientský certifikát pro tyto požadavky. Podrobnosti najdete v dokumentaci ke službě IIS v části Konfigurace.

Služba IIS automaticky uloží veškerá data textu požadavku do nakonfigurovaného limitu velikosti před opětovným vyjednáváním. Žádosti, které překračují limit, se zamítnou odpovědí 413. Tento limit je ve výchozím nastavení 48 kB a je konfigurovatelný nastavením uploadReadAheadSize.

HttpSys

HttpSys má dvě nastavení, která řídí vyjednávání klientských certifikátů a obě mají být nastaveny. První je v netsh.exe pod http add sslcert clientcertnegotiation=enable/disable. Tento příznak označuje, jestli se má klientský certifikát vyjednat na začátku připojení a měl by být nastavený na disable volitelné klientské certifikáty. Podrobnosti najdete v dokumentaci netsh.

Druhé nastavení je ClientCertificateMethod. Pokud je nastavená hodnota AllowRenegotation, klientský certifikát lze během žádosti znovu projednat.

POZNÁMKA: Aplikace by měla ukládat do vyrovnávací paměti nebo využívat jakákoli data textu požadavku před pokusem o nové vyjednávání, jinak může požadavek přestat reagovat.

Existuje známý problém, kdy povolení AllowRenegotation může způsobit synchronní přejednání při přístupu k ClientCertificate vlastnosti. Zavolejte metodu GetClientCertificateAsync , aby se tomu zabránilo. Tento problém je vyřešený v .NET 6. Další informace najdete u tohoto problému na GitHubu. Poznámka GetClientCertificateAsync může vrátit certifikát null, pokud klient odmítne zadat certifikát.

Kestrel

Kestrel řídí vyjednávání klientských certifikátů s ClientCertificateMode možností.

Pro .NET 5 a starší Kestrel nepodporuje opětovné vyjednávání po spuštění připojení k získání klientského certifikátu. Tato funkce byla přidána v .NET 6.

V tomto problému s diskuzí na GitHubu nechte dotazy, komentáře a další názory na volitelné klientské certifikáty.