Mengonfigurasi autentikasi sertifikat di ASP.NET Core

Microsoft.AspNetCore.Authentication.Certificate berisi implementasi yang mirip dengan Autentikasi Sertifikat untuk ASP.NET Core. Autentikasi sertifikat terjadi di tingkat TLS, jauh sebelum sampai ke ASP.NET Core. Lebih akurat, ini adalah handler autentikasi yang memvalidasi sertifikat dan kemudian memberi Anda peristiwa di mana Anda dapat menyelesaikan sertifikat tersebut ke ClaimsPrincipal.

Anda harusmengonfigurasi server Anda untuk autentikasi sertifikat, baik itu IIS, Kestrel, Azure Web Apps, atau apa pun yang Anda gunakan.

Skenario proksi dan load balancer

Autentikasi sertifikat adalah skenario stateful yang terutama digunakan di mana proksi atau load balancer tidak menangani lalu lintas antara klien dan server. Jika proksi atau load balancer digunakan, autentikasi sertifikat hanya berfungsi jika proksi atau load balancer:

  • Menangani autentikasi.
  • Meneruskan informasi autentikasi pengguna ke aplikasi (misalnya, di header permintaan), yang bertindak berdasarkan informasi autentikasi.

Alternatif untuk autentikasi sertifikat di lingkungan tempat proksi dan load balancer digunakan adalah Active Directory Federated Services (ADFS) dengan OpenID Koneksi (OIDC).

Mulai

Dapatkan sertifikat HTTPS, terapkan, dan konfigurasikan server Anda untuk memerlukan sertifikat.

Di aplikasi web:

  • Tambahkan referensi ke paket NuGet Microsoft.AspNetCore.Authentication.Certificate .
  • Di Program.cs, panggil builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...);. Berikan delegasi untuk OnCertificateValidated melakukan validasi tambahan pada sertifikat klien yang dikirim dengan permintaan. Ubah informasi tersebut context.Principal menjadi dan ClaimsPrincipal atur pada properti .

Jika autentikasi gagal, handler ini mengembalikan respons, seperti yang 403 (Forbidden)401 (Unauthorized)mungkin Anda harapkan. Alasannya adalah bahwa autentikasi harus terjadi selama koneksi TLS awal. Pada saat mencapai handler, sudah terlambat. Tidak ada cara untuk meningkatkan koneksi dari koneksi anonim ke koneksi anonim ke koneksi dengan sertifikat.

UseAuthentication diperlukan untuk mengatur HttpContext.User ke yang ClaimsPrincipal dibuat dari sertifikat. Misalnya:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

app.UseAuthentication();

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

app.Run();

Contoh sebelumnya menunjukkan cara default untuk menambahkan autentikasi sertifikat. Handler membangun prinsipal pengguna menggunakan properti sertifikat umum.

Mengonfigurasi validasi sertifikat

Handler CertificateAuthenticationOptions memiliki beberapa validasi bawaan yang merupakan validasi minimum yang harus Anda lakukan pada sertifikat. Masing-masing pengaturan ini diaktifkan secara default.

AllowedCertificateTypes = Chained, SelfSigned, atau All (Chained | Ditandatangani Sendiri)

Nilai default: CertificateTypes.Chained

Pemeriksaan ini memvalidasi bahwa hanya jenis sertifikat yang sesuai yang diizinkan. Jika aplikasi menggunakan sertifikat yang ditandatangani sendiri, opsi ini perlu diatur ke CertificateTypes.All atau CertificateTypes.SelfSigned.

ChainTrustValidationMode

Nilai default: X509ChainTrustMode.System

Sertifikat yang disajikan oleh klien harus ditautkan ke sertifikat akar tepercaya. Pemeriksaan ini mengontrol penyimpanan kepercayaan mana yang berisi sertifikat akar ini.

Secara default, handler menggunakan penyimpanan kepercayaan sistem. Jika sertifikat klien yang disajikan perlu ditautkan ke sertifikat akar yang tidak muncul di penyimpanan kepercayaan sistem, opsi ini dapat diatur ke X509ChainTrustMode.CustomRootTrust untuk membuat handler menggunakan CustomTrustStore.

CustomTrustStore

Nilai default: Kosong X509Certificate2Collection

Jika properti handler ChainTrustValidationMode diatur ke X509ChainTrustMode.CustomRootTrust, ini X509Certificate2Collection berisi setiap sertifikat yang akan digunakan untuk memvalidasi sertifikat klien hingga akar tepercaya, termasuk akar tepercaya.

Ketika klien menyajikan sertifikat yang merupakan bagian dari rantai sertifikat multi-tingkat, CustomTrustStore harus berisi setiap sertifikat penerbitan dalam rantai.

ValidateCertificateUse

Nilai default: true

Pemeriksaan ini memvalidasi bahwa sertifikat yang disajikan oleh klien memiliki penggunaan kunci yang diperluas Autentikasi Klien (EKU), atau tidak ada EKUs sama sekali. Seperti yang dikatakan spesifikasi, jika tidak ada EKU yang ditentukan, maka semua EKUs dianggap valid.

ValidateValidityPeriod

Nilai default: true

Pemeriksaan ini memvalidasi bahwa sertifikat berada dalam periode validitasnya. Pada setiap permintaan, handler memastikan bahwa sertifikat yang valid ketika disajikan belum kedaluwarsa selama sesi saat ini.

PencabutanFlag

Nilai default: X509RevocationFlag.ExcludeRoot

Bendera yang menentukan sertifikat mana dalam rantai yang diperiksa untuk pencabutan.

Pemeriksaan pencabutan hanya dilakukan ketika sertifikat ditautkan ke sertifikat akar.

RevocationMode

Nilai default: X509RevocationMode.Online

Bendera yang menentukan bagaimana pemeriksaan pencabutan dilakukan.

Menentukan pemeriksaan online dapat mengakibatkan penundaan panjang saat otoritas sertifikat dihubungi.

Pemeriksaan pencabutan hanya dilakukan ketika sertifikat ditautkan ke sertifikat akar.

Dapatkah saya mengonfigurasi aplikasi saya untuk memerlukan sertifikat hanya pada jalur tertentu?

Ini tidak mungkin. Ingat pertukaran sertifikat dilakukan pada awal percakapan HTTPS, itu dilakukan oleh server sebelum permintaan pertama diterima pada koneksi tersebut sehingga tidak mungkin untuk mencakup berdasarkan bidang permintaan apa pun.

Peristiwa handler

Handler memiliki dua peristiwa:

  • OnAuthenticationFailed: Dipanggil jika pengecualian terjadi selama autentikasi dan memungkinkan Anda untuk bereaksi.
  • OnCertificateValidated: Dipanggil setelah sertifikat divalidasi, validasi yang diteruskan, dan prinsip default telah dibuat. Kejadian ini memungkinkan Anda untuk melakukan validasi dan augment Anda sendiri atau mengganti prinsipal. Misalnya meliputi:
    • Menentukan apakah sertifikat diketahui oleh layanan Anda.

    • Membangun prinsipalmu sendiri. Pertimbangkan contoh berikut:

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

Jika Anda menemukan sertifikat masuk tidak memenuhi validasi tambahan Anda, hubungi context.Fail("failure reason") dengan alasan kegagalan.

Untuk fungsionalitas yang lebih baik, panggil layanan yang terdaftar dalam injeksi dependensi yang tersambung ke database atau jenis penyimpanan pengguna lainnya. Akses layanan dengan menggunakan konteks yang diteruskan ke delegasi. Pertimbangkan contoh berikut:

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

Secara konseptual, validasi sertifikat adalah masalah otorisasi. Menambahkan pemeriksaan, misalnya, penerbit atau thumbprint dalam kebijakan otorisasi, daripada di dalam OnCertificateValidated, dapat diterima dengan sempurna.

Mengonfigurasi server Anda untuk mewajibkan sertifikat

Kestrel

Di Program.cs, konfigurasikan Kestrel sebagai berikut:

var builder = WebApplication.CreateBuilder(args);

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

Catatan

Titik akhir yang dibuat dengan memanggil Listensebelum memanggil ConfigureHttpsDefaults tidak akan menerapkan default.

IIS

Selesaikan langkah-langkah berikut di Manajer IIS:

  1. Pilih situs Anda dari tab Koneksi ion.
  2. Klik dua kali opsi Pengaturan SSL di jendela Tampilan Fitur.
  3. Centang kotak Wajibkan SSL , dan pilih tombol Wajibkan radio di bagian Sertifikat klien .

Client certificate settings in IIS

Proksi web Azure dan kustom

Lihat dokumentasi host dan sebarkan untuk cara mengonfigurasi middleware penerusan sertifikat.

Menggunakan autentikasi sertifikat di Azure Web Apps

Tidak diperlukan konfigurasi penerusan untuk Azure. Konfigurasi penerusan disiapkan oleh Middleware Penerusan Sertifikat.

Catatan

Middleware Penerusan Sertifikat diperlukan untuk skenario ini.

Untuk informasi selengkapnya, lihat Menggunakan sertifikat TLS/SSL dalam kode Anda di Azure App Service (dokumentasi Azure).

Menggunakan autentikasi sertifikat dalam proksi web kustom

Metode AddCertificateForwarding ini digunakan untuk menentukan:

  • Nama header klien.
  • Bagaimana sertifikat akan dimuat (menggunakan HeaderConverter properti ).

Dalam proksi web kustom, sertifikat diteruskan sebagai header permintaan kustom, misalnya X-SSL-CERT. Untuk menggunakannya, konfigurasikan penerusan sertifikat di 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;
        }
    };
});

Jika aplikasi diproksi balik oleh NGINX dengan konfigurasi proxy_set_header ssl-client-cert $ssl_client_escaped_cert atau disebarkan di Kubernetes menggunakan NGINX Ingress, sertifikat klien diteruskan ke aplikasi dalam formulir yang dikodekan URL. Untuk menggunakan sertifikat, dekodekan sebagai berikut:

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

Tambahkan middleware di Program.cs. UseCertificateForwarding dipanggil sebelum panggilan ke UseAuthentication dan UseAuthorization:

var app = builder.Build();

app.UseCertificateForwarding();

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

Kelas terpisah dapat digunakan untuk menerapkan logika validasi. Karena sertifikat yang ditandatangani sendiri yang sama digunakan dalam contoh ini, pastikan bahwa hanya sertifikat Anda yang dapat digunakan. Validasi bahwa thumbprint sertifikat klien dan sertifikat server cocok, jika tidak, sertifikat apa pun dapat digunakan dan akan cukup untuk mengautentikasi. Ini akan digunakan di AddCertificate dalam metode . Anda juga dapat memvalidasi subjek atau penerbit di sini jika Anda menggunakan sertifikat menengah atau turunan.

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

Menerapkan HttpClient menggunakan sertifikat dan IHttpClientFactory

Dalam contoh berikut, sertifikat klien ditambahkan ke HttpClientHandler menggunakan ClientCertificates properti dari handler. Handler ini kemudian dapat digunakan dalam instans bernama dari HttpClient menggunakan ConfigurePrimaryHttpMessageHandler metode . Ini adalah penyiapan di 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;
});

IHttpClientFactory kemudian dapat digunakan untuk mendapatkan instans bernama dengan handler dan sertifikat. Metode CreateClient dengan nama klien yang ditentukan digunakan Program.cs untuk mendapatkan instans. Permintaan HTTP dapat dikirim menggunakan klien sesuai kebutuhan:

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

Jika sertifikat yang benar dikirim ke server, data dikembalikan. Jika tidak ada sertifikat atau sertifikat yang salah dikirim, kode status HTTP 403 dikembalikan.

Membuat sertifikat di PowerShell

Membuat sertifikat adalah bagian tersulit dalam menyiapkan alur ini. Sertifikat akar dapat dibuat menggunakan New-SelfSignedCertificate cmdlet PowerShell. Saat membuat sertifikat, gunakan kata sandi yang kuat. Penting untuk menambahkan KeyUsageProperty parameter dan parameter seperti yang ditunjukkan KeyUsage .

Membuat CA akar

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

Catatan

Nilai -DnsName parameter harus cocok dengan target penyebaran aplikasi. Misalnya, "localhost" untuk pengembangan.

Menginstal di akar tepercaya

Sertifikat akar perlu dipercaya pada sistem host Anda. Sertifikat akar yang tidak dibuat oleh otoritas sertifikat tidak akan dipercaya secara default. Untuk informasi tentang cara mempercayai sertifikat akar di Windows, lihat pertanyaan ini.

Sertifikat menengah

Sertifikat perantara sekarang dapat dibuat dari sertifikat akar. Ini tidak diperlukan untuk semua kasus penggunaan, tetapi Anda mungkin perlu membuat banyak sertifikat atau perlu mengaktifkan atau menonaktifkan grup sertifikat. Parameter TextExtension diperlukan untuk mengatur panjang jalur dalam batasan dasar sertifikat.

Sertifikat perantara kemudian dapat ditambahkan ke sertifikat perantara tepercaya dalam sistem host 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

Membuat sertifikat anak dari sertifikat perantara

Sertifikat anak dapat dibuat dari sertifikat perantara. Ini adalah entitas akhir dan tidak perlu membuat lebih banyak sertifikat anak.

$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

Membuat sertifikat anak dari sertifikat akar

Sertifikat anak juga dapat dibuat dari sertifikat akar secara langsung.

$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

Contoh akar - sertifikat perantara - sertifikat

$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

Saat menggunakan sertifikat akar, menengah, atau turunan, sertifikat dapat divalidasi menggunakan Thumbprint atau PublicKey sesuai kebutuhan:

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

Penembolokan validasi sertifikat

ASP.NET Core 5.0 dan versi yang lebih baru mendukung kemampuan untuk mengaktifkan penembolokan hasil validasi. Penembolokan secara dramatis meningkatkan performa autentikasi sertifikat, karena validasi adalah operasi yang mahal.

Secara default, autentikasi sertifikat menonaktifkan penembolokan. Untuk mengaktifkan penembolokan, panggil AddCertificateCache di Program.cs:

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

Implementasi penembolokan default menyimpan hasil dalam memori. Anda dapat menyediakan cache Anda sendiri dengan menerapkan ICertificateValidationCache dan mendaftarkannya dengan injeksi dependensi. Contohnya, services.AddSingleton<ICertificateValidationCache, YourCache>().

Sertifikat klien opsional

Bagian ini menyediakan informasi untuk aplikasi yang harus melindungi subset aplikasi dengan sertifikat. Misalnya, Razor Halaman atau pengontrol di aplikasi mungkin memerlukan sertifikat klien. Hal ini menghadirkan tantangan sebagai sertifikat klien:

  • Adalah fitur TLS, bukan fitur HTTP.
  • Dinegosiasikan per koneksi dan biasanya pada awal koneksi sebelum data HTTP tersedia.

Ada dua pendekatan untuk menerapkan sertifikat klien opsional:

  1. Menggunakan nama host (SNI) terpisah dan pengalihan. Meskipun lebih banyak pekerjaan untuk dikonfigurasi, ini direkomendasikan karena berfungsi di sebagian besar lingkungan dan protokol.
  2. Negosiasi ulang selama permintaan HTTP. Ini memiliki beberapa batasan dan tidak disarankan.

Host Terpisah (SNI)

Pada awal koneksi, hanya Indikasi Nama Server (SNI)† yang diketahui. Sertifikat klien dapat dikonfigurasi per nama host sehingga satu host memerlukannya dan yang lain tidak.

ASP.NET Core 5 dan yang lebih baru menambahkan dukungan yang lebih nyaman untuk dialihkan guna memperoleh sertifikat klien opsional. Untuk informasi selengkapnya, lihat Sampel sertifikat opsional.

  • Untuk permintaan ke aplikasi web yang memerlukan sertifikat klien dan tidak memilikinya:
    • Alihkan ke halaman yang sama menggunakan subdomain yang dilindungi sertifikat klien.
    • Misalnya, alihkan ke myClient.contoso.com/requestedPage. Karena permintaan ke myClient.contoso.com/requestedPage adalah nama host yang berbeda dari contoso.com/requestedPage, klien membuat koneksi yang berbeda dan sertifikat klien disediakan.
    • Untuk informasi selengkapnya, lihat Pengantar otorisasi di ASP.NET Core.

† Server Name Indication (SNI) adalah ekstensi TLS untuk menyertakan domain virtual sebagai bagian dari negosiasi SSL. Ini secara efektif berarti nama domain virtual, atau nama host, dapat digunakan untuk mengidentifikasi titik akhir jaringan.

Renegosiasi

Negosiasi ulang TLS adalah proses di mana klien dan server dapat menilai kembali persyaratan enkripsi untuk koneksi individual, termasuk meminta sertifikat klien jika sebelumnya tidak disediakan. Negosiasi ulang TLS adalah risiko keamanan dan tidak disarankan karena:

  • Di HTTP/1.1, server harus terlebih dahulu buffer atau menggunakan data HTTP apa pun yang sedang dalam penerbangan seperti badan permintaan POST untuk memastikan koneksi jelas untuk negosiasi ulang. Jika tidak, negosiasi ulang dapat berhenti merespons atau gagal.
  • HTTP/2 dan HTTP/3 secara eksplisit melarang negosiasi ulang.
  • Ada risiko keamanan yang terkait dengan negosiasi ulang. TLS 1.3 menghapus negosiasi ulang seluruh koneksi dan menggantinya dengan ekstensi baru untuk meminta hanya sertifikat klien setelah dimulainya koneksi. Mekanisme ini diekspos melalui API yang sama dan masih tunduk pada batasan sebelumnya dari versi buffering dan protokol HTTP.

Implementasi dan konfigurasi fitur ini bervariasi menurut server dan versi kerangka kerja.

IIS

IIS mengelola negosiasi sertifikat klien atas nama Anda. Sub bagian dari aplikasi dapat mengaktifkan SslRequireCert opsi untuk menegosiasikan sertifikat klien untuk permintaan tersebut. Lihat Konfigurasi dalam dokumentasi IIS untuk detailnya.

IIS akan secara otomatis menyangga data isi permintaan hingga batas ukuran yang dikonfigurasi sebelum melakukan negosiasi ulang. Permintaan yang melebihi batas ditolak dengan respons 413. Batas ini default ke 48KB dan dapat dikonfigurasi dengan mengatur uploadReadAheadSize.

HttpSys

HttpSys memiliki dua pengaturan yang mengontrol negosiasi sertifikat klien dan keduanya harus diatur. Yang pertama ada di netsh.exe di bawah http add sslcert clientcertnegotiation=enable/disable. Bendera ini menunjukkan apakah sertifikat klien harus dinegosiasikan pada awal koneksi dan harus diatur ke disable untuk sertifikat klien opsional. Lihat dokumen netsh untuk detailnya.

Pengaturan lainnya adalah ClientCertificateMethod. Ketika diatur ke AllowRenegotation, sertifikat klien dapat dinegosiasikan ulang selama permintaan.

CATATAN Aplikasi harus buffer atau mengkonsumsi data isi permintaan sebelum mencoba negosiasi ulang, jika tidak, permintaan mungkin menjadi tidak responsif.

Aplikasi dapat terlebih dahulu memeriksa ClientCertificate properti untuk melihat apakah sertifikat tersedia. Jika tidak tersedia, pastikan isi permintaan telah dikonsumsi sebelum memanggil GetClientCertificateAsync untuk menegosiasikannya. Catatan GetClientCertificateAsync dapat mengembalikan sertifikat null jika klien menolak untuk menyediakannya.

CATATAN Perilaku properti yang ClientCertificate diubah di .NET 6. Untuk informasi lebih lanjut, lihat masalah GitHub ini.

Kestrel

Kestrel mengontrol negosiasi sertifikat klien dengan ClientCertificateMode opsi .

ClientCertificateMode.DelayCertificate adalah opsi baru yang tersedia di .NET 6 atau yang lebih baru. Saat diatur, aplikasi dapat memeriksa ClientCertificate properti untuk melihat apakah sertifikat tersedia. Jika tidak tersedia, pastikan isi permintaan telah dikonsumsi sebelum memanggil GetClientCertificateAsync untuk menegosiasikannya. Catatan GetClientCertificateAsync dapat mengembalikan sertifikat null jika klien menolak untuk menyediakannya.

CATATAN Aplikasi harus buffer atau mengkonsumsi data isi permintaan sebelum mencoba negosiasi ulang, jika tidak GetClientCertificateAsync , dapat melemparkan InvalidOperationException: Client stream needs to be drained before renegotiation..

Jika Anda secara terprogram mengonfigurasi pengaturan TLS per host ada Kelebihan UseHttps baru yang tersedia di .NET 6 dan yang lebih baru yang mengambil TlsHandshakeCallbackOptions dan mengontrol negosiasi ulang sertifikat klien melalui TlsHandshakeCallbackContext.AllowDelayedClientCertificateNegotation.

Microsoft.AspNetCore.Authentication.Certificate berisi implementasi yang mirip dengan Autentikasi Sertifikat untuk ASP.NET Core. Autentikasi sertifikat terjadi di tingkat TLS, jauh sebelum sampai ke ASP.NET Core. Lebih akurat, ini adalah handler autentikasi yang memvalidasi sertifikat dan kemudian memberi Anda peristiwa di mana Anda dapat menyelesaikan sertifikat tersebut ke ClaimsPrincipal.

Konfigurasikan server Anda untuk autentikasi sertifikat, baik itu IIS, Kestrel, Azure Web Apps, atau apa pun yang Anda gunakan.

Skenario proksi dan load balancer

Autentikasi sertifikat adalah skenario stateful yang terutama digunakan di mana proksi atau load balancer tidak menangani lalu lintas antara klien dan server. Jika proksi atau load balancer digunakan, autentikasi sertifikat hanya berfungsi jika proksi atau load balancer:

  • Menangani autentikasi.
  • Meneruskan informasi autentikasi pengguna ke aplikasi (misalnya, di header permintaan), yang bertindak berdasarkan informasi autentikasi.

Alternatif untuk autentikasi sertifikat di lingkungan tempat proksi dan load balancer digunakan adalah Active Directory Federated Services (ADFS) dengan OpenID Koneksi (OIDC).

Mulai

Dapatkan sertifikat HTTPS, terapkan, dan konfigurasikan server Anda untuk memerlukan sertifikat.

Di aplikasi web Anda, tambahkan referensi ke paket Microsoft.AspNetCore.Authentication.Certificate . Kemudian dalam metode , Startup.ConfigureServices hubungi services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...); dengan opsi Anda, menyediakan delegasi untuk OnCertificateValidated melakukan validasi tambahan pada sertifikat klien yang dikirim dengan permintaan. Ubah informasi tersebut context.Principal menjadi dan ClaimsPrincipal atur pada properti .

Jika autentikasi gagal, handler ini mengembalikan respons, seperti yang 403 (Forbidden)401 (Unauthorized)mungkin Anda harapkan. Alasannya adalah bahwa autentikasi harus terjadi selama koneksi TLS awal. Pada saat mencapai handler, sudah terlambat. Tidak ada cara untuk meningkatkan koneksi dari koneksi anonim ke koneksi anonim ke koneksi dengan sertifikat.

app.UseAuthentication(); Tambahkan juga dalam Startup.Configure metode . Jika tidak, HttpContext.User tidak akan diatur untuk ClaimsPrincipal dibuat dari sertifikat. Misalnya:

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
}

Contoh sebelumnya menunjukkan cara default untuk menambahkan autentikasi sertifikat. Handler membangun prinsipal pengguna menggunakan properti sertifikat umum.

Mengonfigurasi validasi sertifikat

Handler CertificateAuthenticationOptions memiliki beberapa validasi bawaan yang merupakan validasi minimum yang harus Anda lakukan pada sertifikat. Masing-masing pengaturan ini diaktifkan secara default.

AllowedCertificateTypes = Chained, SelfSigned, atau All (Chained | Ditandatangani Sendiri)

Nilai default: CertificateTypes.Chained

Pemeriksaan ini memvalidasi bahwa hanya jenis sertifikat yang sesuai yang diizinkan. Jika aplikasi menggunakan sertifikat yang ditandatangani sendiri, opsi ini perlu diatur ke CertificateTypes.All atau CertificateTypes.SelfSigned.

ValidateCertificateUse

Nilai default: true

Pemeriksaan ini memvalidasi bahwa sertifikat yang disajikan oleh klien memiliki penggunaan kunci yang diperluas Autentikasi Klien (EKU), atau tidak ada EKUs sama sekali. Seperti yang dikatakan spesifikasi, jika tidak ada EKU yang ditentukan, maka semua EKUs dianggap valid.

ValidateValidityPeriod

Nilai default: true

Pemeriksaan ini memvalidasi bahwa sertifikat berada dalam periode validitasnya. Pada setiap permintaan, handler memastikan bahwa sertifikat yang valid ketika disajikan belum kedaluwarsa selama sesi saat ini.

PencabutanFlag

Nilai default: X509RevocationFlag.ExcludeRoot

Bendera yang menentukan sertifikat mana dalam rantai yang diperiksa untuk pencabutan.

Pemeriksaan pencabutan hanya dilakukan ketika sertifikat ditautkan ke sertifikat akar.

RevocationMode

Nilai default: X509RevocationMode.Online

Bendera yang menentukan bagaimana pemeriksaan pencabutan dilakukan.

Menentukan pemeriksaan online dapat mengakibatkan penundaan panjang saat otoritas sertifikat dihubungi.

Pemeriksaan pencabutan hanya dilakukan ketika sertifikat ditautkan ke sertifikat akar.

Dapatkah saya mengonfigurasi aplikasi saya untuk memerlukan sertifikat hanya pada jalur tertentu?

Ini tidak mungkin. Ingat pertukaran sertifikat dilakukan pada awal percakapan HTTPS, itu dilakukan oleh server sebelum permintaan pertama diterima pada koneksi tersebut sehingga tidak mungkin untuk mencakup berdasarkan bidang permintaan apa pun.

Peristiwa handler

Handler memiliki dua peristiwa:

  • OnAuthenticationFailed: Dipanggil jika pengecualian terjadi selama autentikasi dan memungkinkan Anda untuk bereaksi.
  • OnCertificateValidated: Dipanggil setelah sertifikat divalidasi, validasi yang diteruskan, dan prinsip default telah dibuat. Kejadian ini memungkinkan Anda untuk melakukan validasi dan augment Anda sendiri atau mengganti prinsipal. Misalnya meliputi:
    • Menentukan apakah sertifikat diketahui oleh layanan Anda.

    • Membangun prinsipalmu sendiri. Pertimbangkan contoh berikut di 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;
                  }
              };
          });
      

Jika Anda menemukan sertifikat masuk tidak memenuhi validasi tambahan Anda, hubungi context.Fail("failure reason") dengan alasan kegagalan.

Untuk fungsionalitas nyata, Anda mungkin ingin memanggil layanan yang terdaftar dalam injeksi dependensi yang tersambung ke database atau jenis penyimpanan pengguna lainnya. Akses layanan Anda dengan menggunakan konteks yang diteruskan ke delegasi Anda. Pertimbangkan contoh berikut di 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;
            }
        };
    });

Secara konseptual, validasi sertifikat adalah masalah otorisasi. Menambahkan pemeriksaan, misalnya, penerbit atau thumbprint dalam kebijakan otorisasi, daripada di dalam OnCertificateValidated, dapat diterima dengan sempurna.

Mengonfigurasi server Anda untuk mewajibkan sertifikat

Kestrel

Di Program.cs, konfigurasikan Kestrel sebagai berikut:

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

Catatan

Titik akhir yang dibuat dengan memanggil Listensebelum memanggil ConfigureHttpsDefaults tidak akan menerapkan default.

IIS

Selesaikan langkah-langkah berikut di Manajer IIS:

  1. Pilih situs Anda dari tab Koneksi ion.
  2. Klik dua kali opsi Pengaturan SSL di jendela Tampilan Fitur.
  3. Centang kotak Wajibkan SSL , dan pilih tombol Wajibkan radio di bagian Sertifikat klien .

Client certificate settings in IIS

Proksi web Azure dan kustom

Lihat dokumentasi host dan sebarkan untuk cara mengonfigurasi middleware penerusan sertifikat.

Menggunakan autentikasi sertifikat di Azure Web Apps

Tidak diperlukan konfigurasi penerusan untuk Azure. Konfigurasi penerusan disiapkan oleh Middleware Penerusan Sertifikat.

Catatan

Middleware Penerusan Sertifikat diperlukan untuk skenario ini.

Untuk informasi selengkapnya, lihat Menggunakan sertifikat TLS/SSL dalam kode Anda di Azure App Service (dokumentasi Azure).

Menggunakan autentikasi sertifikat dalam proksi web kustom

Metode AddCertificateForwarding ini digunakan untuk menentukan:

  • Nama header klien.
  • Bagaimana sertifikat akan dimuat (menggunakan HeaderConverter properti ).

Dalam proksi web kustom, sertifikat diteruskan sebagai header permintaan kustom, misalnya X-SSL-CERT. Untuk menggunakannya, konfigurasikan penerusan sertifikat di 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;
}

Jika aplikasi diproksi balik oleh NGINX dengan konfigurasi proxy_set_header ssl-client-cert $ssl_client_escaped_cert atau disebarkan di Kubernetes menggunakan NGINX Ingress, sertifikat klien diteruskan ke aplikasi dalam formulir yang dikodekan URL. Untuk menggunakan sertifikat, dekodekan sebagai berikut:

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

Metode kemudian Startup.Configure menambahkan middleware. UseCertificateForwarding dipanggil sebelum panggilan ke UseAuthentication dan UseAuthorization:

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

    app.UseRouting();

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

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

Kelas terpisah dapat digunakan untuk menerapkan logika validasi. Karena sertifikat yang ditandatangani sendiri yang sama digunakan dalam contoh ini, pastikan bahwa hanya sertifikat Anda yang dapat digunakan. Validasi bahwa thumbprint sertifikat klien dan sertifikat server cocok, jika tidak, sertifikat apa pun dapat digunakan dan akan cukup untuk mengautentikasi. Ini akan digunakan di AddCertificate dalam metode . Anda juga dapat memvalidasi subjek atau penerbit di sini jika Anda menggunakan sertifikat menengah atau turunan.

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

Menerapkan HttpClient menggunakan sertifikat dan HttpClientHandler

HttpClientHandler dapat ditambahkan langsung di konstruktor HttpClient kelas. Perawatan harus diambil saat membuat instans .HttpClient Kemudian HttpClient akan mengirim sertifikat dengan setiap permintaan.

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

Menerapkan HttpClient menggunakan sertifikat dan httpClient bernama dari IHttpClientFactory

Dalam contoh berikut, sertifikat klien ditambahkan ke HttpClientHandler menggunakan ClientCertificates properti dari handler. Handler ini kemudian dapat digunakan dalam instans bernama dari HttpClient menggunakan ConfigurePrimaryHttpMessageHandler metode . Ini adalah penyiapan di 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;
});

IHttpClientFactory kemudian dapat digunakan untuk mendapatkan instans bernama dengan handler dan sertifikat. Metode CreateClient dengan nama klien yang ditentukan dalam Startup kelas digunakan untuk mendapatkan instans. Permintaan HTTP dapat dikirim menggunakan klien sesuai kebutuhan.

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

Jika sertifikat yang benar dikirim ke server, data dikembalikan. Jika tidak ada sertifikat atau sertifikat yang salah dikirim, kode status HTTP 403 dikembalikan.

Membuat sertifikat di PowerShell

Membuat sertifikat adalah bagian tersulit dalam menyiapkan alur ini. Sertifikat akar dapat dibuat menggunakan New-SelfSignedCertificate cmdlet PowerShell. Saat membuat sertifikat, gunakan kata sandi yang kuat. Penting untuk menambahkan KeyUsageProperty parameter dan parameter seperti yang ditunjukkan KeyUsage .

Membuat CA akar

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

Catatan

Nilai -DnsName parameter harus cocok dengan target penyebaran aplikasi. Misalnya, "localhost" untuk pengembangan.

Menginstal di akar tepercaya

Sertifikat akar perlu dipercaya pada sistem host Anda. Sertifikat akar yang tidak dibuat oleh otoritas sertifikat tidak akan dipercaya secara default. Untuk informasi tentang cara mempercayai sertifikat akar di Windows, lihat pertanyaan ini.

Sertifikat menengah

Sertifikat perantara sekarang dapat dibuat dari sertifikat akar. Ini tidak diperlukan untuk semua kasus penggunaan, tetapi Anda mungkin perlu membuat banyak sertifikat atau perlu mengaktifkan atau menonaktifkan grup sertifikat. Parameter TextExtension diperlukan untuk mengatur panjang jalur dalam batasan dasar sertifikat.

Sertifikat perantara kemudian dapat ditambahkan ke sertifikat perantara tepercaya dalam sistem host 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

Membuat sertifikat anak dari sertifikat perantara

Sertifikat anak dapat dibuat dari sertifikat perantara. Ini adalah entitas akhir dan tidak perlu membuat lebih banyak sertifikat anak.

$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

Membuat sertifikat anak dari sertifikat akar

Sertifikat anak juga dapat dibuat dari sertifikat akar secara langsung.

$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

Contoh akar - sertifikat perantara - sertifikat

$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

Saat menggunakan sertifikat akar, menengah, atau turunan, sertifikat dapat divalidasi menggunakan Thumbprint atau PublicKey sesuai kebutuhan.

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

Penembolokan validasi sertifikat

ASP.NET Core 5.0 dan versi yang lebih baru mendukung kemampuan untuk mengaktifkan penembolokan hasil validasi. Penembolokan secara dramatis meningkatkan performa autentikasi sertifikat, karena validasi adalah operasi yang mahal.

Secara default, autentikasi sertifikat menonaktifkan penembolokan. Untuk mengaktifkan penembolokan, panggil AddCertificateCache di Startup.ConfigureServices:

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

Implementasi penembolokan default menyimpan hasil dalam memori. Anda dapat menyediakan cache Anda sendiri dengan menerapkan ICertificateValidationCache dan mendaftarkannya dengan injeksi dependensi. Contohnya, services.AddSingleton<ICertificateValidationCache, YourCache>().

Sertifikat klien opsional

Bagian ini menyediakan informasi untuk aplikasi yang harus melindungi subset aplikasi dengan sertifikat. Misalnya, Razor Halaman atau pengontrol di aplikasi mungkin memerlukan sertifikat klien. Hal ini menghadirkan tantangan sebagai sertifikat klien:

  • Adalah fitur TLS, bukan fitur HTTP.
  • Dinegosiasikan per koneksi dan biasanya pada awal koneksi sebelum data HTTP tersedia.

Ada dua pendekatan untuk menerapkan sertifikat klien opsional:

  1. Menggunakan nama host (SNI) terpisah dan pengalihan. Meskipun lebih banyak pekerjaan untuk dikonfigurasi, ini direkomendasikan karena berfungsi di sebagian besar lingkungan dan protokol.
  2. Negosiasi ulang selama permintaan HTTP. Ini memiliki beberapa batasan dan tidak disarankan.

Host Terpisah (SNI)

Pada awal koneksi, hanya Indikasi Nama Server (SNI)† yang diketahui. Sertifikat klien dapat dikonfigurasi per nama host sehingga satu host memerlukannya dan yang lain tidak.

ASP.NET Core 5 dan yang lebih baru menambahkan dukungan yang lebih nyaman untuk dialihkan guna memperoleh sertifikat klien opsional. Untuk informasi selengkapnya, lihat Sampel sertifikat opsional.

  • Untuk permintaan ke aplikasi web yang memerlukan sertifikat klien dan tidak memilikinya:
    • Alihkan ke halaman yang sama menggunakan subdomain yang dilindungi sertifikat klien.
    • Misalnya, alihkan ke myClient.contoso.com/requestedPage. Karena permintaan ke myClient.contoso.com/requestedPage adalah nama host yang berbeda dari contoso.com/requestedPage, klien membuat koneksi yang berbeda dan sertifikat klien disediakan.
    • Untuk informasi selengkapnya, lihat Pengantar otorisasi di ASP.NET Core.

† Server Name Indication (SNI) adalah ekstensi TLS untuk menyertakan domain virtual sebagai bagian dari negosiasi SSL. Ini secara efektif berarti nama domain virtual, atau nama host, dapat digunakan untuk mengidentifikasi titik akhir jaringan.

Renegosiasi

Negosiasi ulang TLS adalah proses di mana klien dan server dapat menilai kembali persyaratan enkripsi untuk koneksi individual, termasuk meminta sertifikat klien jika sebelumnya tidak disediakan. Negosiasi ulang TLS adalah risiko keamanan dan tidak disarankan karena:

  • Di HTTP/1.1, server harus terlebih dahulu buffer atau menggunakan data HTTP apa pun yang sedang dalam penerbangan seperti badan permintaan POST untuk memastikan koneksi jelas untuk negosiasi ulang. Jika tidak, negosiasi ulang dapat berhenti merespons atau gagal.
  • HTTP/2 dan HTTP/3 secara eksplisit melarang negosiasi ulang.
  • Ada risiko keamanan yang terkait dengan negosiasi ulang. TLS 1.3 menghapus negosiasi ulang seluruh koneksi dan menggantinya dengan ekstensi baru untuk meminta hanya sertifikat klien setelah dimulainya koneksi. Mekanisme ini diekspos melalui API yang sama dan masih tunduk pada batasan sebelumnya dari versi buffering dan protokol HTTP.

Implementasi dan konfigurasi fitur ini bervariasi menurut server dan versi kerangka kerja.

IIS

IIS mengelola negosiasi sertifikat klien atas nama Anda. Sub bagian dari aplikasi dapat mengaktifkan SslRequireCert opsi untuk menegosiasikan sertifikat klien untuk permintaan tersebut. Lihat Konfigurasi dalam dokumentasi IIS untuk detailnya.

IIS akan secara otomatis menyangga data isi permintaan hingga batas ukuran yang dikonfigurasi sebelum melakukan negosiasi ulang. Permintaan yang melebihi batas ditolak dengan respons 413. Batas ini default ke 48KB dan dapat dikonfigurasi dengan mengatur uploadReadAheadSize.

HttpSys

HttpSys memiliki dua pengaturan yang mengontrol negosiasi sertifikat klien dan keduanya harus diatur. Yang pertama ada di netsh.exe di bawah http add sslcert clientcertnegotiation=enable/disable. Bendera ini menunjukkan apakah sertifikat klien harus dinegosiasikan pada awal koneksi dan harus diatur ke disable untuk sertifikat klien opsional. Lihat dokumen netsh untuk detailnya.

Pengaturan lainnya adalah ClientCertificateMethod. Ketika diatur ke AllowRenegotation, sertifikat klien dapat dinegosiasikan ulang selama permintaan.

CATATAN Aplikasi harus buffer atau mengkonsumsi data isi permintaan sebelum mencoba negosiasi ulang, jika tidak, permintaan mungkin menjadi tidak responsif.

Ada masalah yang diketahui di mana mengaktifkan AllowRenegotation dapat menyebabkan negosiasi ulang terjadi secara sinkron saat mengakses ClientCertificate properti. GetClientCertificateAsync Panggil metode untuk menghindari hal ini. Ini telah ditangani di .NET 6. Untuk informasi lebih lanjut, lihat masalah GitHub ini. Catatan GetClientCertificateAsync dapat mengembalikan sertifikat null jika klien menolak untuk menyediakannya.

Kestrel

Kestrel mengontrol negosiasi sertifikat klien dengan ClientCertificateMode opsi .

Untuk .NET 5 dan yang lebih lama Kestrel tidak mendukung negosiasi ulang setelah dimulainya koneksi untuk memperoleh sertifikat klien. Fitur ini telah ditambahkan di .NET 6.

Microsoft.AspNetCore.Authentication.Certificate berisi implementasi yang mirip dengan Autentikasi Sertifikat untuk ASP.NET Core. Autentikasi sertifikat terjadi di tingkat TLS, jauh sebelum sampai ke ASP.NET Core. Lebih akurat, ini adalah handler autentikasi yang memvalidasi sertifikat dan kemudian memberi Anda peristiwa di mana Anda dapat menyelesaikan sertifikat tersebut ke ClaimsPrincipal.

Konfigurasikan server Anda untuk autentikasi sertifikat, baik itu IIS, Kestrel, Azure Web Apps, atau apa pun yang Anda gunakan.

Skenario proksi dan load balancer

Autentikasi sertifikat adalah skenario stateful yang terutama digunakan di mana proksi atau load balancer tidak menangani lalu lintas antara klien dan server. Jika proksi atau load balancer digunakan, autentikasi sertifikat hanya berfungsi jika proksi atau load balancer:

  • Menangani autentikasi.
  • Meneruskan informasi autentikasi pengguna ke aplikasi (misalnya, di header permintaan), yang bertindak berdasarkan informasi autentikasi.

Alternatif untuk autentikasi sertifikat di lingkungan tempat proksi dan load balancer digunakan adalah Active Directory Federated Services (ADFS) dengan OpenID Koneksi (OIDC).

Mulai

Dapatkan sertifikat HTTPS, terapkan, dan konfigurasikan server Anda untuk memerlukan sertifikat.

Di aplikasi web Anda, tambahkan referensi ke paket Microsoft.AspNetCore.Authentication.Certificate . Kemudian dalam metode , Startup.ConfigureServices hubungi services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...); dengan opsi Anda, menyediakan delegasi untuk OnCertificateValidated melakukan validasi tambahan pada sertifikat klien yang dikirim dengan permintaan. Ubah informasi tersebut context.Principal menjadi dan ClaimsPrincipal atur pada properti .

Jika autentikasi gagal, handler ini mengembalikan respons, seperti yang 403 (Forbidden)401 (Unauthorized)mungkin Anda harapkan. Alasannya adalah bahwa autentikasi harus terjadi selama koneksi TLS awal. Pada saat mencapai handler, sudah terlambat. Tidak ada cara untuk meningkatkan koneksi dari koneksi anonim ke koneksi anonim ke koneksi dengan sertifikat.

app.UseAuthentication(); Tambahkan juga dalam Startup.Configure metode . Jika tidak, HttpContext.User tidak akan diatur untuk ClaimsPrincipal dibuat dari sertifikat. Misalnya:

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
}

Contoh sebelumnya menunjukkan cara default untuk menambahkan autentikasi sertifikat. Handler membangun prinsipal pengguna menggunakan properti sertifikat umum.

Mengonfigurasi validasi sertifikat

Handler CertificateAuthenticationOptions memiliki beberapa validasi bawaan yang merupakan validasi minimum yang harus Anda lakukan pada sertifikat. Masing-masing pengaturan ini diaktifkan secara default.

AllowedCertificateTypes = Chained, SelfSigned, atau All (Chained | Ditandatangani Sendiri)

Nilai default: CertificateTypes.Chained

Pemeriksaan ini memvalidasi bahwa hanya jenis sertifikat yang sesuai yang diizinkan. Jika aplikasi menggunakan sertifikat yang ditandatangani sendiri, opsi ini perlu diatur ke CertificateTypes.All atau CertificateTypes.SelfSigned.

ValidateCertificateUse

Nilai default: true

Pemeriksaan ini memvalidasi bahwa sertifikat yang disajikan oleh klien memiliki penggunaan kunci yang diperluas Autentikasi Klien (EKU), atau tidak ada EKUs sama sekali. Seperti yang dikatakan spesifikasi, jika tidak ada EKU yang ditentukan, maka semua EKUs dianggap valid.

ValidateValidityPeriod

Nilai default: true

Pemeriksaan ini memvalidasi bahwa sertifikat berada dalam periode validitasnya. Pada setiap permintaan, handler memastikan bahwa sertifikat yang valid ketika disajikan belum kedaluwarsa selama sesi saat ini.

PencabutanFlag

Nilai default: X509RevocationFlag.ExcludeRoot

Bendera yang menentukan sertifikat mana dalam rantai yang diperiksa untuk pencabutan.

Pemeriksaan pencabutan hanya dilakukan ketika sertifikat ditautkan ke sertifikat akar.

RevocationMode

Nilai default: X509RevocationMode.Online

Bendera yang menentukan bagaimana pemeriksaan pencabutan dilakukan.

Menentukan pemeriksaan online dapat mengakibatkan penundaan panjang saat otoritas sertifikat dihubungi.

Pemeriksaan pencabutan hanya dilakukan ketika sertifikat ditautkan ke sertifikat akar.

Dapatkah saya mengonfigurasi aplikasi saya untuk memerlukan sertifikat hanya pada jalur tertentu?

Ini tidak mungkin. Ingat pertukaran sertifikat dilakukan pada awal percakapan HTTPS, itu dilakukan oleh server sebelum permintaan pertama diterima pada koneksi tersebut sehingga tidak mungkin untuk mencakup berdasarkan bidang permintaan apa pun.

Peristiwa handler

Handler memiliki dua peristiwa:

  • OnAuthenticationFailed: Dipanggil jika pengecualian terjadi selama autentikasi dan memungkinkan Anda untuk bereaksi.
  • OnCertificateValidated: Dipanggil setelah sertifikat divalidasi, validasi yang diteruskan, dan prinsip default telah dibuat. Kejadian ini memungkinkan Anda untuk melakukan validasi dan augment Anda sendiri atau mengganti prinsipal. Misalnya meliputi:
    • Menentukan apakah sertifikat diketahui oleh layanan Anda.

    • Membangun prinsipalmu sendiri. Pertimbangkan contoh berikut di 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;
                  }
              };
          });
      

Jika Anda menemukan sertifikat masuk tidak memenuhi validasi tambahan Anda, hubungi context.Fail("failure reason") dengan alasan kegagalan.

Untuk fungsionalitas nyata, Anda mungkin ingin memanggil layanan yang terdaftar dalam injeksi dependensi yang tersambung ke database atau jenis penyimpanan pengguna lainnya. Akses layanan Anda dengan menggunakan konteks yang diteruskan ke delegasi Anda. Pertimbangkan contoh berikut di 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;
            }
        };
    });

Secara konseptual, validasi sertifikat adalah masalah otorisasi. Menambahkan pemeriksaan, misalnya, penerbit atau thumbprint dalam kebijakan otorisasi, daripada di dalam OnCertificateValidated, dapat diterima dengan sempurna.

Mengonfigurasi server Anda untuk mewajibkan sertifikat

Kestrel

Di Program.cs, konfigurasikan Kestrel sebagai berikut:

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

Catatan

Titik akhir yang dibuat dengan memanggil Listensebelum memanggil ConfigureHttpsDefaults tidak akan menerapkan default.

IIS

Selesaikan langkah-langkah berikut di Manajer IIS:

  1. Pilih situs Anda dari tab Koneksi ion.
  2. Klik dua kali opsi Pengaturan SSL di jendela Tampilan Fitur.
  3. Centang kotak Wajibkan SSL , dan pilih tombol Wajibkan radio di bagian Sertifikat klien .

Client certificate settings in IIS

Proksi web Azure dan kustom

Lihat dokumentasi host dan sebarkan untuk cara mengonfigurasi middleware penerusan sertifikat.

Menggunakan autentikasi sertifikat di Azure Web Apps

Tidak diperlukan konfigurasi penerusan untuk Azure. Konfigurasi penerusan disiapkan oleh Middleware Penerusan Sertifikat.

Catatan

Middleware Penerusan Sertifikat diperlukan untuk skenario ini.

Untuk informasi selengkapnya, lihat Menggunakan sertifikat TLS/SSL dalam kode Anda di Azure App Service (dokumentasi Azure).

Menggunakan autentikasi sertifikat dalam proksi web kustom

Metode AddCertificateForwarding ini digunakan untuk menentukan:

  • Nama header klien.
  • Bagaimana sertifikat akan dimuat (menggunakan HeaderConverter properti ).

Dalam proksi web kustom, sertifikat diteruskan sebagai header permintaan kustom, misalnya X-SSL-CERT. Untuk menggunakannya, konfigurasikan penerusan sertifikat di 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;
}

Jika aplikasi diproksi balik oleh NGINX dengan konfigurasi proxy_set_header ssl-client-cert $ssl_client_escaped_cert atau disebarkan di Kubernetes menggunakan NGINX Ingress, sertifikat klien diteruskan ke aplikasi dalam formulir yang dikodekan URL. Untuk menggunakan sertifikat, dekodekan sebagai berikut:

Tambahkan namespace layanan untuk System.Net ke bagian Startup.csatas :

using System.Net;

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

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

Metode kemudian Startup.Configure menambahkan middleware. UseCertificateForwarding dipanggil sebelum panggilan ke UseAuthentication dan UseAuthorization:

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

    app.UseRouting();

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

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

Kelas terpisah dapat digunakan untuk menerapkan logika validasi. Karena sertifikat yang ditandatangani sendiri yang sama digunakan dalam contoh ini, pastikan bahwa hanya sertifikat Anda yang dapat digunakan. Validasi bahwa thumbprint sertifikat klien dan sertifikat server cocok, jika tidak, sertifikat apa pun dapat digunakan dan akan cukup untuk mengautentikasi. Ini akan digunakan di AddCertificate dalam metode . Anda juga dapat memvalidasi subjek atau penerbit di sini jika Anda menggunakan sertifikat menengah atau turunan.

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

Menerapkan HttpClient menggunakan sertifikat dan HttpClientHandler

HttpClientHandler dapat ditambahkan langsung di konstruktor HttpClient kelas. Perawatan harus diambil saat membuat instans .HttpClient Kemudian HttpClient akan mengirim sertifikat dengan setiap permintaan.

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

Menerapkan HttpClient menggunakan sertifikat dan httpClient bernama dari IHttpClientFactory

Dalam contoh berikut, sertifikat klien ditambahkan ke HttpClientHandler menggunakan ClientCertificates properti dari handler. Handler ini kemudian dapat digunakan dalam instans bernama dari HttpClient menggunakan ConfigurePrimaryHttpMessageHandler metode . Ini adalah penyiapan di 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;
});

IHttpClientFactory kemudian dapat digunakan untuk mendapatkan instans bernama dengan handler dan sertifikat. Metode CreateClient dengan nama klien yang ditentukan dalam Startup kelas digunakan untuk mendapatkan instans. Permintaan HTTP dapat dikirim menggunakan klien sesuai kebutuhan.

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

Jika sertifikat yang benar dikirim ke server, data dikembalikan. Jika tidak ada sertifikat atau sertifikat yang salah dikirim, kode status HTTP 403 dikembalikan.

Membuat sertifikat di PowerShell

Membuat sertifikat adalah bagian tersulit dalam menyiapkan alur ini. Sertifikat akar dapat dibuat menggunakan New-SelfSignedCertificate cmdlet PowerShell. Saat membuat sertifikat, gunakan kata sandi yang kuat. Penting untuk menambahkan KeyUsageProperty parameter dan parameter seperti yang ditunjukkan KeyUsage .

Membuat CA akar

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

Catatan

Nilai -DnsName parameter harus cocok dengan target penyebaran aplikasi. Misalnya, "localhost" untuk pengembangan.

Menginstal di akar tepercaya

Sertifikat akar perlu dipercaya pada sistem host Anda. Sertifikat akar yang tidak dibuat oleh otoritas sertifikat tidak akan dipercaya secara default. Untuk informasi tentang cara mempercayai sertifikat akar di Windows, lihat pertanyaan ini.

Sertifikat menengah

Sertifikat perantara sekarang dapat dibuat dari sertifikat akar. Ini tidak diperlukan untuk semua kasus penggunaan, tetapi Anda mungkin perlu membuat banyak sertifikat atau perlu mengaktifkan atau menonaktifkan grup sertifikat. Parameter TextExtension diperlukan untuk mengatur panjang jalur dalam batasan dasar sertifikat.

Sertifikat perantara kemudian dapat ditambahkan ke sertifikat perantara tepercaya dalam sistem host 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

Membuat sertifikat anak dari sertifikat perantara

Sertifikat anak dapat dibuat dari sertifikat perantara. Ini adalah entitas akhir dan tidak perlu membuat lebih banyak sertifikat anak.

$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

Membuat sertifikat anak dari sertifikat akar

Sertifikat anak juga dapat dibuat dari sertifikat akar secara langsung.

$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

Contoh akar - sertifikat perantara - sertifikat

$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

Saat menggunakan sertifikat akar, menengah, atau turunan, sertifikat dapat divalidasi menggunakan Thumbprint atau PublicKey sesuai kebutuhan.

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

Sertifikat klien opsional

Bagian ini menyediakan informasi untuk aplikasi yang harus melindungi subset aplikasi dengan sertifikat. Misalnya, Razor Halaman atau pengontrol di aplikasi mungkin memerlukan sertifikat klien. Hal ini menghadirkan tantangan sebagai sertifikat klien:

  • Adalah fitur TLS, bukan fitur HTTP.
  • Dinegosiasikan per koneksi dan biasanya pada awal koneksi sebelum data HTTP tersedia.

Ada dua pendekatan untuk menerapkan sertifikat klien opsional:

  1. Menggunakan nama host (SNI) terpisah dan pengalihan. Meskipun lebih banyak pekerjaan untuk dikonfigurasi, ini direkomendasikan karena berfungsi di sebagian besar lingkungan dan protokol.
  2. Negosiasi ulang selama permintaan HTTP. Ini memiliki beberapa batasan dan tidak disarankan.

Host Terpisah (SNI)

Pada awal koneksi, hanya Indikasi Nama Server (SNI)† yang diketahui. Sertifikat klien dapat dikonfigurasi per nama host sehingga satu host memerlukannya dan yang lain tidak.

ASP.NET Core 5 dan yang lebih baru menambahkan dukungan yang lebih nyaman untuk dialihkan guna memperoleh sertifikat klien opsional. Untuk informasi selengkapnya, lihat Sampel sertifikat opsional.

  • Untuk permintaan ke aplikasi web yang memerlukan sertifikat klien dan tidak memilikinya:
    • Alihkan ke halaman yang sama menggunakan subdomain yang dilindungi sertifikat klien.
    • Misalnya, alihkan ke myClient.contoso.com/requestedPage. Karena permintaan ke myClient.contoso.com/requestedPage adalah nama host yang berbeda dari contoso.com/requestedPage, klien membuat koneksi yang berbeda dan sertifikat klien disediakan.
    • Untuk informasi selengkapnya, lihat Pengantar otorisasi di ASP.NET Core.

† Server Name Indication (SNI) adalah ekstensi TLS untuk menyertakan domain virtual sebagai bagian dari negosiasi SSL. Ini secara efektif berarti nama domain virtual, atau nama host, dapat digunakan untuk mengidentifikasi titik akhir jaringan.

Renegosiasi

Negosiasi ulang TLS adalah proses di mana klien dan server dapat menilai kembali persyaratan enkripsi untuk koneksi individual, termasuk meminta sertifikat klien jika sebelumnya tidak disediakan. Negosiasi ulang TLS adalah risiko keamanan dan tidak disarankan karena:

  • Di HTTP/1.1, server harus terlebih dahulu buffer atau menggunakan data HTTP apa pun yang sedang dalam penerbangan seperti badan permintaan POST untuk memastikan koneksi jelas untuk negosiasi ulang. Jika tidak, negosiasi ulang dapat berhenti merespons atau gagal.
  • HTTP/2 dan HTTP/3 secara eksplisit melarang negosiasi ulang.
  • Ada risiko keamanan yang terkait dengan negosiasi ulang. TLS 1.3 menghapus negosiasi ulang seluruh koneksi dan menggantinya dengan ekstensi baru untuk meminta hanya sertifikat klien setelah dimulainya koneksi. Mekanisme ini diekspos melalui API yang sama dan masih tunduk pada batasan sebelumnya dari versi buffering dan protokol HTTP.

Implementasi dan konfigurasi fitur ini bervariasi menurut server dan versi kerangka kerja.

IIS

IIS mengelola negosiasi sertifikat klien atas nama Anda. Sub bagian dari aplikasi dapat mengaktifkan SslRequireCert opsi untuk menegosiasikan sertifikat klien untuk permintaan tersebut. Lihat Konfigurasi dalam dokumentasi IIS untuk detailnya.

IIS akan secara otomatis menyangga data isi permintaan hingga batas ukuran yang dikonfigurasi sebelum melakukan negosiasi ulang. Permintaan yang melebihi batas ditolak dengan respons 413. Batas ini default ke 48KB dan dapat dikonfigurasi dengan mengatur uploadReadAheadSize.

HttpSys

HttpSys memiliki dua pengaturan yang mengontrol negosiasi sertifikat klien dan keduanya harus diatur. Yang pertama ada di netsh.exe di bawah http add sslcert clientcertnegotiation=enable/disable. Bendera ini menunjukkan apakah sertifikat klien harus dinegosiasikan pada awal koneksi dan harus diatur ke disable untuk sertifikat klien opsional. Lihat dokumen netsh untuk detailnya.

Pengaturan lainnya adalah ClientCertificateMethod. Ketika diatur ke AllowRenegotation, sertifikat klien dapat dinegosiasikan ulang selama permintaan.

CATATAN Aplikasi harus buffer atau mengkonsumsi data isi permintaan sebelum mencoba negosiasi ulang, jika tidak, permintaan mungkin menjadi tidak responsif.

Ada masalah yang diketahui di mana mengaktifkan AllowRenegotation dapat menyebabkan negosiasi ulang terjadi secara sinkron saat mengakses ClientCertificate properti. GetClientCertificateAsync Panggil metode untuk menghindari hal ini. Ini telah ditangani di .NET 6. Untuk informasi lebih lanjut, lihat masalah GitHub ini. Catatan GetClientCertificateAsync dapat mengembalikan sertifikat null jika klien menolak untuk menyediakannya.

Kestrel

Kestrel mengontrol negosiasi sertifikat klien dengan ClientCertificateMode opsi .

Untuk .NET 5 dan yang lebih lama Kestrel tidak mendukung negosiasi ulang setelah dimulainya koneksi untuk memperoleh sertifikat klien. Fitur ini telah ditambahkan di .NET 6.

Tinggalkan pertanyaan, komentar, dan umpan balik lainnya tentang sertifikat klien opsional dalam masalah diskusi GitHub ini.