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.cs
builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...);
. Poskytněte delegátoviOnCertificateValidated
, aby u klientského certifikátu odesílaného s požadavky udělal jakékoli dodatečné ověření. Změňte informace na hodnotuClaimsPrincipal
a nastavte ji ucontext.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.cs
ná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:
- Na kartě Připojení vyberte svůj web.
- Poklikejte na možnost Nastavení PROTOKOLU SSL v okně Zobrazení funkcí.
- Zaškrtněte políčko Vyžadovat SSL a v části Klientské certifikáty vyberte přepínač Vyžadovat.
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.cs
souboru . 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ů:
- 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ů.
- 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.
- Nastavení vazby pro doménu a subdoménu:
- Například nastavte vazby na
contoso.com
amyClient.contoso.com
. Hostitelcontoso.com
nevyžaduje klientský certifikát, alemyClient.contoso.com
dělá to. - Další informace najdete tady: .
- Například nastavte vazby na
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 namyClient.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.cs
ná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:
- Na kartě Připojení vyberte svůj web.
- Poklikejte na možnost Nastavení PROTOKOLU SSL v okně Zobrazení funkcí.
- Zaškrtněte políčko Vyžadovat SSL a v části Klientské certifikáty vyberte přepínač Vyžadovat.
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ů:
- 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ů.
- 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.
- Nastavení vazby pro doménu a subdoménu:
- Například nastavte vazby na
contoso.com
amyClient.contoso.com
. Hostitelcontoso.com
nevyžaduje klientský certifikát, alemyClient.contoso.com
dělá to. - Další informace najdete tady: .
- Například nastavte vazby na
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 namyClient.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.cs
ná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:
- Na kartě Připojení vyberte svůj web.
- Poklikejte na možnost Nastavení PROTOKOLU SSL v okně Zobrazení funkcí.
- Zaškrtněte políčko Vyžadovat SSL a v části Klientské certifikáty vyberte přepínač Vyžadovat.
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ů:
- 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ů.
- 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.
- Nastavení vazby pro doménu a subdoménu:
- Například nastavte vazby na
contoso.com
amyClient.contoso.com
. Hostitelcontoso.com
nevyžaduje klientský certifikát, alemyClient.contoso.com
dělá to. - Další informace najdete tady: .
- Kestrel webový server v ASP.NET Core:
- ListenOptions.UseHttps
- ClientCertificateMode
- Poznámka: Kestrel V současné době nepodporuje více konfigurací protokolu TLS na jedné vazbě, budete potřebovat dvě vazby s jedinečnými IP adresami nebo porty. Další informace najdete u tohoto problému na GitHubu.
- IIS
- HTTP.sys: Konfigurace Windows Serveru
- Kestrel webový server v ASP.NET Core:
- Například nastavte vazby na
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 namyClient.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.