Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Microsoft.AspNetCore.Authentication.Certificatecontém uma implementação semelhante à Autenticação de Certificado para ASP.NET Core. A autenticação de certificado ocorre no nível do TLS antes de chegar ao ASP.NET Core. Mais precisamente, essa funcionalidade é um manipulador de autenticação que valida o certificado e, em seguida, fornece um evento em que você pode resolver esse certificado para um ClaimsPrincipal.
Você deveconfigurar seu servidor para autenticação de certificado com IIS, Kestrel, Azure Aplicativos Web ou sua solução preferida.
Este artigo descreve como configurar a autenticação de certificado em ASP.NET Core para IIS e HTTP.sys e fornece exemplos para chamar vários métodos e trabalhar com propriedades.
Examinar cenários de proxy e balanceador de carga
A autenticação de certificado é um cenário com estado usado principalmente em que um proxy ou balanceador de carga não manipula o tráfego entre clientes e servidores. Se um proxy ou balanceador de carga for usado, a autenticação de certificado só funcionará se o proxy ou o balanceador de carga:
- Lida com a autenticação.
- Passa as informações de autenticação do usuário para o aplicativo (por exemplo, em um cabeçalho de solicitação), que atua nas informações de autenticação.
Uma alternativa à autenticação de certificado em ambientes em que proxies e balanceadores de carga são usados é o ADFS (Serviços Federados do Active Directory) com o OIDC (OpenID Connect).
Introdução
Adquira um certificado HTTPS, aplique-o e configure o servidor para exigir certificados.
No aplicativo Web:
Adicione uma referência ao pacote NuGet Microsoft.AspNetCore.Authentication.Certificate.
No arquivo Program.cs , chame o
builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...);método. Forneça um delegado para que oOnCertificateValidatedmanipulador de eventos conclua qualquer validação suplementar no certificado do cliente enviado com solicitações. Transforme essas informações em umClaimsPrincipalvalor e defina-as nacontext.Principalpropriedade.
Se a autenticação falhar, esse manipulador retornará uma 403 (Forbidden) resposta em vez de uma 401 (Unauthorized), como você pode esperar. O manipulador retorna uma resposta diferente porque espera que a autenticação ocorra durante a conexão TLS inicial. Quando ele chega ao manipulador, é tarde demais. Não há como atualizar a conexão de uma conexão anônima para uma com um certificado.
O UseAuthentication método é necessário para definir HttpContext.User como um ClaimsPrincipal valor criado a partir do certificado. Por exemplo:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate();
var app = builder.Build();
app.UseAuthentication();
app.MapGet("/", () => "Hello World!");
app.Run();
O exemplo anterior demonstra a maneira padrão de adicionar a autenticação de certificado. O manipulador constrói um principal de usuário usando as propriedades de certificado comuns.
Configurar a validação do certificado
O CertificateAuthenticationOptions manipulador tem algumas validações internas que são as validações mínimas que você deve executar em um certificado. Cada uma dessas configurações é habilitada por padrão. As seções a seguir descrevem como trabalhar com as configurações.
AllowedCertificateTypes = Chained, SelfSigned ou All (Encadeado | AutoSigned)
Valor padrão: CertificateTypes.Chained
A checagem valida se apenas o tipo apropriado de certificado é permitido. Se o aplicativo estiver usando certificados autoassinados, essa opção precisará ser definida como CertificateTypes.All ou CertificateTypes.SelfSigned.
Modo de Validação da Cadeia de Confiança
Valor padrão: X509ChainTrustMode.System
O certificado apresentado pelo cliente deve ser encadeado a um certificado raiz confiável. Esta verificação controla qual repositório de confiança contém esses certificados raiz.
Por padrão, o manipulador usa o repositório de confiança do sistema. Se o certificado do cliente apresentado precisar ser encadeado a um certificado raiz que não apareça no repositório de confiança do sistema, você poderá definir a opção como X509ChainTrustMode.CustomRootTrust para que o manipulador use a CustomTrustStore propriedade.
CustomTrustStore
Valor padrão: X509Certificate2Collection vazio
Se a propriedade ChainTrustValidationMode do manipulador estiver definida como X509ChainTrustMode.CustomRootTrust, esse objeto X509Certificate2Collection contém todos os certificados utilizados para validar o certificado do cliente até uma raiz confiável, incluindo a própria raiz confiável.
Quando o cliente apresenta um certificado que faz parte de uma cadeia de certificados de vários níveis, a CustomTrustStore propriedade deve conter todos os certificados emissores na cadeia.
ValidateCertificateUse
Valor padrão: true
Esse marcar valida se o certificado apresentado pelo cliente tem o EKU (uso de chave estendida) da Autenticação do Cliente ou nenhuma EKUs. Como dizem as especificações, se nenhum EKU for especificado, todas as EKUs serão consideradas válidas.
ValidarPeríodoDeValidade
Valor padrão: true
Essa verificação valida se o certificado está dentro do seu período de validade. Em cada solicitação, o manipulador garante que um certificado válido quando ele foi apresentado não expirou durante sua sessão atual.
IndicadorDeRevogação
Valor padrão: X509RevocationFlag.ExcludeRoot
Um sinalizador que especifica quais certificados na cadeia são verificados quanto à revogação.
As verificações de revogação só são executadas quando o certificado é encadeado a um certificado raiz.
Modo de Revogação
Valor padrão: X509RevocationMode.Online
Um sinalizador que especifica como as verificações de revogação são executadas.
Especificar uma verificação online pode resultar em um longo atraso enquanto se entra em contato com a autoridade de certificação.
As verificações de revogação são executadas somente quando o certificado é encadeado a um certificado raiz.
Perguntas frequentes: posso configurar meu aplicativo para exigir um certificado somente em determinados caminhos?
Essa abordagem não é possível. A troca de certificados é concluída no início da conversa HTTPS. A operação é feita pelo servidor antes que a primeira solicitação seja recebida nessa conexão, portanto, não é possível definir o escopo com base em nenhum campo de solicitação.
Eventos do manipulador de processos
O manipulador tem dois eventos:
OnAuthenticationFailed: Chamado se uma exceção ocorrer durante a autenticação, permitindo que você reaja.OnCertificateValidated: chamado após a validação do certificado, uma vez que a validação foi bem-sucedida e um principal padrão foi criado. Esse evento permite que você execute sua própria validação e aumente ou substitua a entidade de segurança. Os exemplos incluem:Determinando se o certificado é conhecido por seus serviços.
Criando seu próprio princípio, como no exemplo a seguir:
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; } }; });
Se o certificado de entrada não atender à validação extra, chame context.Fail("failure reason") com um motivo de falha.
Para obter uma melhor funcionalidade, chame um serviço registrado na injeção de dependência que se conecta a um banco de dados ou outro tipo de repositório de usuários. Acesse o serviço usando o contexto passado para o delegado. Considere o seguinte exemplo:
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;
}
};
});
Conceitualmente, a validação do certificado é uma preocupação de autorização. Por exemplo, você pode incluir uma verificação do emissor ou da impressão digital em uma política de autorização, em vez de dentro do manipulador OnCertificateValidated.
Configurar seu servidor para exigir certificados
As seções a seguir descrevem como configurar seu servidor para exigir certificados para uma solução específica, incluindo Kestrel, IIS, Azure, proxies web personalizados e Azure Aplicativos Web.
Kestrel
No arquivo Program.cs , configure Kestrel da seguinte maneira:
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<KestrelServerOptions>(options =>
{
options.ConfigureHttpsDefaults(options =>
options.ClientCertificateMode = ClientCertificateMode.RequireCertificate);
});
Observação
Quando um endpoint é criado chamando o método Listenantes de chamar o método ConfigureHttpsDefaults, o endpoint não tem os padrões aplicados.
IIS
Conclua as seguintes etapas no Gerenciador do IIS:
- Na guia Conexões , selecione seu site.
- Na janela Exibição de Recursos , clique duas vezes em Configurações de SSL.
- Marque a caixa de seleção Exigir SSL .
- Para a opção Certificados do Cliente , selecione Exigir.
Proxies web personalizados e do Azure
Para obter mais informações sobre como configurar o middleware de encaminhamento de certificado, consulte a documentação de hospedagem e implantação.
Autenticação de certificado no Azure Aplicativos Web
Nenhuma configuração de encaminhamento é necessária para o Azure. O Middleware de Encaminhamento de Certificado configura a configuração.
Observação
O Middleware de Encaminhamento de Certificado é necessário para esse cenário.
Para obter mais informações, consulte Use certificados TLS/SSL no código do aplicativo (documentação do Azure).
Autenticação de certificado em proxies web personalizados
O método AddCertificateForwarding é usado para especificar:
- O nome do cabeçalho do cliente.
- Como carregar o certificado (por meio da
HeaderConverterpropriedade).
Em proxies web personalizados, o certificado é passado como um cabeçalho de solicitação personalizado, por exemplo X-SSL-CERT. Para usar o certificado, configure o encaminhamento de certificado no arquivo 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;
}
};
});
Se o NGINX for usado com a configuração proxy_set_header ssl-client-cert $ssl_client_escaped_cert como proxy reverso do aplicativo, ou se o aplicativo for implantado no Kubernetes usando o NGINX Ingress, o certificado do cliente será passado para o aplicativo no formulário codificado em URL. Para usar o certificado, decodifica-o da seguinte maneira:
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!;
};
});
Adicione o middleware no arquivo Program.cs . O método UseCertificateForwarding é chamado antes das chamadas para os métodos UseAuthentication e UseAuthorization.
var app = builder.Build();
app.UseCertificateForwarding();
app.UseAuthentication();
app.UseAuthorization();
Uma classe separada pode ser usada para implementar a lógica de validação. Como o mesmo certificado autoassinado é usado neste exemplo, verifique se apenas o certificado pode ser usado. Valide se as impressões digitais do certificado do cliente e do certificado do servidor correspondem. Caso contrário, qualquer certificado pode ser usado e ser suficiente para autenticação. Em seguida, o certificado é usado dentro do AddCertificate método. Você também pode validar o titular ou o emissor aqui, caso utilize certificados intermediários ou subordinados.
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;
}
}
Implementar um HttpClient com um certificado e uma IHttpClientFactory
No exemplo a seguir, um certificado do cliente é adicionado a um HttpClientHandler usando a ClientCertificates propriedade do manipulador. Esse manipulador pode então ser usado em uma instância nomeada de um HttpClient com o método ConfigurePrimaryHttpMessageHandler. Esse cenário é configurado no arquivo 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;
});
O IHttpClientFactory pode ser usado para obter a instância nomeada com o manipulador e o certificado. O CreateClient método com o nome do cliente definido no arquivo Program.cs é usado para obter a instância. A solicitação HTTP pode ser enviada usando o cliente conforme necessário:
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}");
}
}
Se o certificado correto for enviado ao servidor, os dados serão retornados. Se nenhum certificado ou certificado incorreto for enviado, o servidor retornará um código de status HTTP 403.
Certificados em PowerShell
Criar os certificados é a parte mais difícil na configuração desse fluxo. Você pode criar um certificado raiz usando o cmdlet do New-SelfSignedCertificate PowerShell. Ao criar o certificado, use uma senha forte. É importante adicionar o KeyUsageProperty parâmetro e o KeyUsage parâmetro, conforme mostrado nos exemplos.
Criar autoridade de certificação raiz
O código a seguir mostra como criar uma AC (autoridade de certificação) na raiz:
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
Observação
O valor do parâmetro -DnsName deve corresponder ao destino de implantação do aplicativo. Por exemplo, "localhost" para desenvolvimento.
Instalar na raiz confiável
O certificado raiz precisa ser confiável em seu sistema host. Apenas certificados raizes criados por uma autoridade de certificação serão confiáveis por padrão. Para obter informações sobre como confiar no certificado raiz no Windows, consulte a documentação Windows ou o cmdlet Import-Certificate PowerShell.
Usar um certificado intermediário
Um certificado intermediário agora pode ser criado com base no certificado raiz. Essa abordagem não é necessária para todos os casos de uso, mas talvez seja necessário criar muitos certificados ou precisar ativar ou desabilitar grupos de certificados. O parâmetro TextExtension é necessário para definir o comprimento do caminho nas restrições básicas do certificado.
Em seguida, o certificado intermediário pode ser adicionado ao certificado intermediário confiável no sistema host do 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
Criar certificado filho com base no certificado intermediário
Um certificado filho pode ser criado com base no certificado intermediário. Este certificado filho é a entidade final. Você não precisa criar mais certificados filhos.
$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
Criar certificado filho com base no certificado raiz
Um certificado filho também pode ser criado diretamente do certificado raiz.
$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
Exemplo: raiz – certificado intermediário – certificado
O exemplo a seguir mostra a configuração da AC raiz, do certificado intermediário e do certificado filho:
$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
Ao utilizar os certificados raiz, intermediários ou filhos, é possível validá-los usando a impressão digital ou a chave pública, conforme necessário:
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);
}
Resultados da validação do certificado de cache
.NET 5 e versões posteriores dão suporte à capacidade de habilitar o cache de resultados de validação. O cache melhora drasticamente o desempenho da autenticação de certificado porque a validação é uma operação cara.
Por padrão, a autenticação de certificado desabilita o cache. Para habilitar o cache, chame o AddCertificateCache método no arquivo Program.cs :
builder.Services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate()
.AddCertificateCache(options =>
{
options.CacheSize = 1024;
options.CacheEntryExpiration = TimeSpan.FromMinutes(2);
});
A implementação de cache padrão armazena os resultados na memória. Você pode fornecer seu próprio cache implementando ICertificateValidationCache e registrando-o com injeção de dependência. Por exemplo, services.AddSingleton<ICertificateValidationCache, YourCache>().
Usar certificados de cliente opcionais
Esta seção fornece informações para aplicativos que devem proteger um subconjunto do aplicativo com um certificado. Por exemplo, uma Página ou controlador Razor no aplicativo pode exigir certificados de cliente. Este cenário apresenta alguns desafios:
- Os certificados do cliente são um recurso TLS, não um recurso HTTP.
- Os certificados do cliente são negociados por conexão e geralmente no início da conexão antes que todos os dados HTTP estejam disponíveis.
Há duas abordagens para implementar certificados de cliente opcionais:
- Opção 1: usar nomes de host separados (SNI) e redirecionamento. Embora essa opção envolva mais trabalho para configurar, a abordagem é recomendada porque funciona na maioria dos ambientes e protocolos.
- Opção 2: Renegociação durante uma solicitação HTTP. Essa abordagem tem várias limitações e não é recomendada.
Hosts Separados (SNI)
No início da conexão, somente a SNI (Indicação de Nome do Servidor)† é conhecida. Os certificados do cliente podem ser configurados por nome de host para que um host exija os certificados e outro não.
Configure a associação para o domínio e o subdomínio.
Por exemplo, configure associações em
contoso.comemyClient.contoso.com. Ocontoso.comhost não requer um certificado do cliente, masmyClient.contoso.comrequer.Para obter mais informações, consulte os seguintes recursos:
IIS
HTTP.sys
.NET 5 e posterior oferece suporte mais conveniente para redirecionamento para adquirir certificados de cliente opcionais. Para obter mais informações, consulte o exemplo certificados opcionais.
Para solicitações para o aplicativo Web que exigem um certificado de cliente e não têm um, redirecione para a mesma página usando o subdomínio protegido pelo certificado do cliente.
Por exemplo, redirecionar para
myClient.contoso.com/requestedPage. Como a solicitação paramyClient.contoso.com/requestedPageé um nome de host diferente decontoso.com/requestedPage, o cliente estabelece uma conexão diferente e o certificado do cliente é fornecido.Para obter mais informações, consulte Introdução à autorização no ASP.NET Core.
† SNI (Indicação de Nome do Servidor) é uma extensão TLS usada para incluir um domínio virtual como parte da negociação de SSL. Essa abordagem efetivamente significa que o nome de domínio virtual ou um nome de host pode ser usado para identificar o ponto de extremidade de rede.
Renegociação do TLS
A renegociação do TLS é um processo pelo qual o cliente e o servidor podem reavaliar os requisitos de criptografia para uma conexão individual, incluindo a solicitação de um certificado do cliente se não for fornecido anteriormente. A renegociação do TLS é um risco à segurança e não é recomendada porque:
- No HTTP/1.1, o servidor deve primeiro armazenar em buffer ou processar quaisquer dados HTTP em trânsito, como corpos de solicitações POST, para garantir que a conexão esteja livre para a renegociação. Caso contrário, a renegociação poderá parar de responder ou falhar.
- HTTP/2 e HTTP/3 proíbem explicitamente a renegociação.
- Há riscos de segurança associados à renegociação. O TLS 1.3 removeu a renegociação de toda a conexão e a substituiu por uma nova extensão para solicitar apenas o certificado do cliente após o início da conexão. Esse mecanismo é exposto por meio das mesmas APIs e ainda está sujeito às restrições anteriores de buffer e versões de protocolo HTTP.
A implementação e a configuração desse recurso variam de acordo com a versão do servidor e da estrutura, conforme descrito nas seções a seguir.
IIS
O IIS gerencia a negociação de certificado do cliente em seu nome. Uma subseção do aplicativo pode habilitar a opção SslRequireCert de negociar o certificado do cliente para essas solicitações. Para obter mais informações, consulte Configuração na documentação do IIS.
O IIS armazena automaticamente os dados do corpo da solicitação em buffer até um limite de tamanho configurado antes da renegociação. As solicitações que excedem o limite são rejeitadas com uma resposta 413. Esse limite é padrão para 48 KB e é configurável definindo a propriedade uploadReadAheadSize .
HttpSys
O HttpSys tem duas configurações que controlam a negociação do certificado do cliente e ambas devem ser definidas. O primeiro está no arquivo netsh.exe em http add sslcert clientcertnegotiation=enable/disable. Esse sinalizador indica se o certificado do cliente deve ser negociado no início de uma conexão. Defina o valor como disable para certificados de cliente opcionais. Para obter mais informações, consulte o uso do http add sslcert parâmetro nos documentos do netsh.
A outra configuração é a propriedade ClientCertificateMethod. Quando definido como AllowRenegotation, o certificado do cliente pode ser renegociado durante uma solicitação.
Observação
O aplicativo deve armazenar ou consumir dados do corpo da solicitação antes de tentar a renegociação. Caso contrário, a solicitação poderá ficar sem resposta.
Um aplicativo pode verificar primeiro a propriedade ClientCertificate para ver se o certificado está disponível. Se não estiver disponível, certifique-se de que o corpo da solicitação seja processado antes de chamar o método GetClientCertificateAsync para negociar um.
GetClientCertificateAsync pode retornar um certificado nulo se o cliente se recusar a fornecer um.
Observação
O comportamento da propriedade ClientCertificate foi alterado em .NET 6. Para obter mais informações, consulte GitHub problema nº 466.
Kestrel
Kestrel controla a negociação do certificado do cliente por meio da propriedade ClientCertificateMode.
.NET 6 e posteriores fornece a opção DelayCertificate para a propriedade ClientCertificateMode. Quando essa opção é definida, um aplicativo pode verificar a ClientCertificate propriedade para ver se o certificado está disponível. Se não estiver disponível, certifique-se de que o corpo da solicitação seja processado antes de chamar o método GetClientCertificateAsync para negociar um.
GetClientCertificateAsync pode retornar um certificado nulo se o cliente se recusar a fornecer um.
Observação
O aplicativo deve armazenar em buffer ou processar todos os dados do corpo da solicitação antes de tentar a renegociação. Caso contrário, GetClientCertificateAsync pode gerar a exceção , InvalidOperationException: o fluxo do cliente precisa ser drenado antes da renegociação.
Se você definir programaticamente as configurações do TLS por nome de host SNI, chame a sobrecarga UseHttps (.NET 6 ou posterior) que usa um objeto da classe TlsHandshakeCallbackOptions. Essa opção controla a renegociação de certificado do cliente por meio da AllowDelayedClientCertificateNegotation propriedade. Para obter mais informações, consulte o método ListenOptionsHttpsExtensions.UseHttps .
Microsoft.AspNetCore.Authentication.Certificatecontém uma implementação semelhante à Autenticação de Certificado para ASP.NET Core. A autenticação de certificado ocorre no nível do TLS, que ocorre muito antes de chegar ao ASP.NET Core. Mais precisamente, este manipulador de autenticação valida o certificado e permite que você acesse um evento no qual é possível resolver o certificado para um valor ClaimsPrincipal.
Configure seu servidorde rede para autenticação de certificado, seja ele o IIS, Kestrel, o Azure Aplicativos Web ou qualquer outro que você esteja usando.
Cenários de proxy e balanceador de carga
A autenticação de certificado é um cenário com estado usado principalmente em que um proxy ou balanceador de carga não manipula o tráfego entre clientes e servidores. Se um proxy ou balanceador de carga for usado, a autenticação de certificado só funcionará se o proxy ou o balanceador de carga:
- Lida com a autenticação.
- Passa as informações de autenticação do usuário para o aplicativo (por exemplo, em um cabeçalho de solicitação), que atua nas informações de autenticação.
Uma alternativa à autenticação de certificado em ambientes em que proxies e balanceadores de carga são usados é o ADFS (Serviços Federados do Active Directory) com o OIDC (OpenID Connect).
Introdução
Adquira um certificado HTTPS, aplique-o e configure o servidor para exigir certificados.
Em seu aplicativo Web, adicione uma referência ao pacote Microsoft.AspNetCore.Authentication.Certificate . No método Startup.ConfigureServices, em seguida, chame services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...); com suas opções, fornecendo um delegado para que OnCertificateValidated faça qualquer validação suplementar no certificado do cliente enviado com as solicitações. Transforme essas informações em um ClaimsPrincipal e defina-as na propriedade context.Principal.
Se a autenticação falhar, esse manipulador retornará uma 403 (Forbidden) resposta em vez de 401 (Unauthorized), como você poderia esperar. O raciocínio é que a autenticação deve ocorrer durante a conexão TLS inicial. Quando ele chega ao manipulador, é tarde demais. Não há como atualizar a conexão de uma conexão anônima para uma com um certificado.
Adicione app.UseAuthentication(); também no método Startup.Configure. Caso contrário, o HttpContext.User não será definido como ClaimsPrincipal criado a partir do certificado. Por exemplo:
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
}
O exemplo anterior demonstra a maneira padrão de adicionar a autenticação de certificado. O manipulador constrói uma entidade de segurança de usuário usando as propriedades comuns do certificado.
Configurar a validação do certificado
O CertificateAuthenticationOptions manipulador tem algumas validações internas que são as validações mínimas que você deve executar em um certificado. Cada uma dessas configurações é habilitada por padrão.
AllowedCertificateTypes = Chained, SelfSigned ou All (Encadeado | AutoSigned)
Valor padrão: CertificateTypes.Chained
A checagem valida se apenas o tipo apropriado de certificado é permitido. Se o aplicativo estiver usando certificados autoassinados, essa opção precisará ser definida como CertificateTypes.All ou CertificateTypes.SelfSigned.
ValidateCertificateUse
Valor padrão: true
Esse marcar valida se o certificado apresentado pelo cliente tem o EKU (uso de chave estendida) da Autenticação do Cliente ou nenhuma EKUs. Como dizem as especificações, se nenhum EKU for especificado, todas as EKUs serão consideradas válidas.
ValidarPeríodoDeValidade
Valor padrão: true
Essa verificação valida se o certificado está dentro do seu período de validade. Em cada solicitação, o manipulador garante que um certificado válido quando ele foi apresentado não expirou durante sua sessão atual.
IndicadorDeRevogação
Valor padrão: X509RevocationFlag.ExcludeRoot
Um sinalizador que especifica quais certificados na cadeia são verificados quanto à revogação.
As verificações de revogação só são executadas quando o certificado é encadeado a um certificado raiz.
Modo de Revogação
Valor padrão: X509RevocationMode.Online
Um sinalizador que especifica como as verificações de revogação são executadas.
Especificar uma verificação online pode resultar em um longo atraso enquanto se entra em contato com a autoridade de certificação.
As verificações de revogação só são executadas quando o certificado é encadeado a um certificado raiz.
Posso configurar meu aplicativo para exigir um certificado somente em determinados caminhos?
Isso não é possível. Lembre-se de que a troca de certificados é feita no início da conversa HTTPS, ela é feita pelo servidor antes que a primeira solicitação seja recebida nessa conexão para que não seja possível definir o escopo com base em nenhum campo de solicitação.
Eventos de manipulador
O manipulador tem dois eventos:
-
OnAuthenticationFailed: Chamado se uma exceção ocorrer durante a autenticação, permitindo que você reaja. -
OnCertificateValidated: chamado após o certificado ter sido validado, ter passado na validação e ter sido criado uma entidade padrão. Esse evento permite que você execute sua própria validação e aumente ou substitua a entidade de segurança. Para obter exemplos, incluem:Determinando se o certificado é conhecido por seus serviços.
Construindo sua própria entidade de segurança. Considere o exemplo a seguir em
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; } }; });
Se você encontrar que o certificado de entrada não atende à sua validação extra, chame context.Fail("failure reason") com um motivo de falha.
Para uma funcionalidade real, você provavelmente desejará chamar um serviço registrado na injeção de dependência que se conecta a um banco de dados ou outro tipo de repositório de usuários. Acesse o serviço usando o contexto passado para o delegado. Considere o exemplo a seguir em 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;
}
};
});
Conceitualmente, a validação do certificado é uma preocupação de autorização. Adicionar um marcar, por exemplo, um emissor ou impressão digital em uma política de autorização, em vez de dentro de OnCertificateValidated, é perfeitamente aceitável.
Configurar seu servidor para exigir certificados
Kestrel
No Program.cs, configure o Kestrel da seguinte maneira:
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);
});
});
}
Observação
Os pontos de extremidade criados com a chamada de Listenantes da chamada de ConfigureHttpsDefaults não terão os padrões aplicados.
IIS
Conclua as seguintes etapas no Gerenciador do IIS:
- Selecione seu site na guia Conexões.
- Clique duas vezes na opção Configurações de SSL na janela Exibição de Recursos.
- Marque a caixa de seleção Exigir SSL e selecione o botão de opção Exigir na seção Certificados do cliente.
Azure e proxies web personalizados
Consulte a documentação de hospedagem e implantação para saber como configurar o middleware de encaminhamento de certificado.
Usar a autenticação de certificado nos Aplicativos Web do Azure
Nenhuma configuração de encaminhamento é necessária para o Azure. A configuração de encaminhamento é realizada pelo Certificate Forwarding Middleware.
Observação
O Middleware de Encaminhamento de Certificado é necessário para esse cenário.
Para obter mais informações, confira Usar um certificado TLS/SSL no código do Serviço de Aplicativo do Azure.
Usar autenticação de certificado em proxies personalizados da Web
O método AddCertificateForwarding é usado para especificar:
- O nome do cabeçalho do cliente.
- Como o certificado deve ser carregado (usando a propriedade
HeaderConverter).
Em proxies web personalizados, o certificado é passado como um cabeçalho de solicitação personalizado, por exemplo X-SSL-CERT. Para usá-lo, configure o encaminhamento de certificado em 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;
}
Se o aplicativo for proxy reverso pelo NGINX com a configuração proxy_set_header ssl-client-cert $ssl_client_escaped_cert ou implantado no Kubernetes usando a Entrada NGINX, o certificado do cliente será passado para o aplicativo no formulário codificado em URL. Para usar o certificado, decodifica-o da seguinte maneira:
Em 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;
};
});
O método Startup.Configure adiciona o middleware.
UseCertificateForwarding é chamado antes das chamadas para UseAuthentication e UseAuthorization:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseRouting();
app.UseCertificateForwarding();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Uma classe separada pode ser usada para implementar a lógica de validação. Como o mesmo certificado autoassinado é usado neste exemplo, verifique se apenas o certificado pode ser usado. Valide se as impressões digitais do certificado do cliente e do certificado do servidor correspondem, caso contrário, qualquer certificado pode ser usado e será suficiente para autenticar. Isso seria usado dentro do método AddCertificate. Você também pode validar o assunto ou o emissor aqui se estiver usando certificados intermediários ou filho.
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;
}
}
}
Implementar um HttpClient usando um certificado e o HttpClientHandler
O HttpClientHandler pode ser adicionado diretamente no construtor da classe HttpClient. Deve-se tomar cuidado ao criar instâncias do HttpClient. O HttpClient enviará o certificado com cada solicitação.
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}");
}
Implementar um HttpClient usando um certificado e um HttpClient nomeado de IHttpClientFactory
No exemplo a seguir, um certificado do cliente é adicionado a um HttpClientHandler usando a propriedade ClientCertificates do manipulador. Esse manipulador pode ser usado em uma instância nomeada de um HttpClient usando o método ConfigurePrimaryHttpMessageHandler. Isso é configurado em 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;
});
O IHttpClientFactory pode ser usado para obter a instância nomeada com o manipulador e o certificado. O método CreateClient com o nome do cliente definido na classe Startup é usado para obter a instância . A solicitação HTTP pode ser enviada usando o cliente conforme necessário.
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}");
}
Se o certificado correto for enviado ao servidor, os dados serão retornados. Se nenhum certificado ou um certificado incorreto for enviado, um código de status HTTP 403 será retornado.
Criar certificados no PowerShell
Criar os certificados é a parte mais difícil na configuração desse fluxo. Um certificado raiz pode ser criado usando o New-SelfSignedCertificate cmdlet do PowerShell. Ao criar o certificado, use uma senha forte. É importante adicionar o KeyUsageProperty parâmetro e o KeyUsage parâmetro, conforme mostrado.
Criar CA raiz
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
Observação
O valor do parâmetro -DnsName deve corresponder ao destino de implantação do aplicativo. Por exemplo, "localhost" para desenvolvimento.
Instalar na raiz confiável
O certificado raiz precisa ser confiável em seu sistema host. Um certificado raiz que não foi criado por uma autoridade de certificação não será confiável por padrão. Para obter informações sobre como confiar no certificado raiz no Windows, consulte esta pergunta.
Certificado intermediário
Um certificado intermediário agora pode ser criado com base no certificado raiz. Isso não é necessário para todos os casos de uso, mas talvez seja necessário criar muitos certificados ou precisar ativar ou desabilitar grupos de certificados. O parâmetro TextExtension é necessário para definir o comprimento do caminho nas restrições básicas do certificado.
Em seguida, o certificado intermediário pode ser adicionado ao certificado intermediário confiável no sistema host do 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
Criar certificado filho com base no certificado intermediário
Um certificado filho pode ser criado com base no certificado intermediário. Essa é a entidade final e não precisa criar mais certificados filho.
$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
Criar certificado filho com base no certificado raiz
Um certificado filho também pode ser criado diretamente do certificado raiz.
$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
Raiz de exemplo – certificado intermediário – certificado
$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
Ao usar os certificados raiz, intermediário ou filho, os certificados podem ser validados usando a Impressão Digital ou PublicKey conforme necessário.
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;
}
}
}
Cache de validação de certificado
O .NET 5 ou versões posteriores dão suporte à capacidade de habilitar o cache de resultados de validação. O cache melhora drasticamente o desempenho da autenticação de certificado, pois a validação é uma operação cara.
Por padrão, a autenticação de certificado desabilita o cache. Para habilitar o cache, chame AddCertificateCache em Startup.ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate()
.AddCertificateCache(options =>
{
options.CacheSize = 1024;
options.CacheEntryExpiration = TimeSpan.FromMinutes(2);
});
}
A implementação de cache padrão armazena os resultados na memória. Você pode fornecer seu próprio cache implementando ICertificateValidationCache e registrando-o com injeção de dependência. Por exemplo, services.AddSingleton<ICertificateValidationCache, YourCache>().
Certificados do cliente opcionais
Esta seção fornece informações para aplicativos que devem proteger um subconjunto do aplicativo com um certificado. Por exemplo, uma Página ou controlador Razor no aplicativo pode exigir certificados de cliente. Isso apresenta desafios como os certificados de cliente:
- São um recurso TLS, não um recurso HTTP.
- São negociados por conexão e geralmente no início da conexão antes que todos os dados HTTP estejam disponíveis.
Há duas abordagens para implementar certificados de cliente opcionais:
- Usando SNI (nomes de host) separados e redirecionamento. Embora haja mais trabalho para configurar, isso é recomendado porque ele funciona na maioria dos ambientes e protocolos.
- Renegociação durante uma solicitação HTTP. Isso tem várias limitações e não é recomendado.
Hosts separados (SNI)
No início da conexão, somente a SNI (Indicação de Nome do Servidor)† é conhecida. Os certificados do cliente podem ser configurados por nome de host para que um host os exija e outro não.
- Configurar a associação para o domínio e o subdomínio:
- Por exemplo, configure associações em
contoso.comemyClient.contoso.com. Ocontoso.comhost não requer um certificado do cliente, masmyClient.contoso.comrequer. - Para obter mais informações, consulte:
- Por exemplo, configure associações em
O .NET 5 ou posterior adiciona suporte mais conveniente para redirecionamento para adquirir certificados de cliente opcionais. Para obter mais informações, consulte o exemplo certificados opcionais.
- Para solicitações para o aplicativo Web que exigem um certificado de cliente e não têm um:
- Redirecione para a mesma página usando o subdomínio protegido pelo certificado do cliente.
- Por exemplo, redirecionar para
myClient.contoso.com/requestedPage. Como a solicitação paramyClient.contoso.com/requestedPageé um nome de host diferente decontoso.com/requestedPage, o cliente estabelece uma conexão diferente e o certificado do cliente é fornecido. - Para obter mais informações, consulte Introdução à autorização no ASP.NET Core.
† A SNI (Indicação de Nome de Servidor) é uma extensão TLS para incluir um domínio virtual como parte da negociação SSL. Isso significa que o nome de domínio virtual, ou um nome de host, pode ser usado para identificar o ponto final da rede.
Renegociação
A renegociação do TLS é um processo pelo qual o cliente e o servidor podem reavaliar os requisitos de criptografia para uma conexão individual, incluindo a solicitação de um certificado do cliente, se não for fornecido anteriormente. A renegociação do TLS é um risco à segurança e não é recomendada porque:
- Em HTTP/1.1, o servidor deve primeiro armazenar ou consumir todos os dados HTTP que estão em trânsito, como os corpos das solicitações POST, para assegurar que a conexão esteja livre antes da renegociação. Caso contrário, a renegociação poderá parar de responder ou falhar.
- HTTP/2 e HTTP/3 proíbem explicitamente a renegociação.
- Há riscos de segurança associados à renegociação. O TLS 1.3 removeu a renegociação de toda a conexão e a substituiu por uma nova extensão para solicitar apenas o certificado do cliente após o início da conexão. Esse mecanismo é exposto por meio das mesmas APIs e ainda está sujeito às restrições anteriores de buffer e versões de protocolo HTTP.
A implementação e a configuração desse recurso variam de acordo com a versão do servidor e da estrutura.
IIS
O IIS gerencia a negociação de certificado do cliente em seu nome. Uma subseção do aplicativo pode habilitar a opção SslRequireCert de negociar o certificado do cliente para essas solicitações. Consulte Configuração na documentação do IIS para obter detalhes.
O IIS armazenará automaticamente os dados do corpo da solicitação em buffer até um limite de tamanho configurado antes da renegociação. As solicitações que excedem o limite são rejeitadas com uma resposta 413. Esse limite usa como padrão 48 KB e é configurável definindo o uploadReadAheadSize.
HttpSys
O HttpSys tem duas configurações que controlam a negociação de certificado do cliente e ambas devem ser definidas. O primeiro está em netsh.exe em http add sslcert clientcertnegotiation=enable/disable. Esse sinalizador indica se o certificado do cliente deve ser negociado no início de uma conexão e deve ser definido disable como para certificados de cliente opcionais. Consulte os documentos do netsh para obter detalhes.
A outra configuração é ClientCertificateMethod. Quando definido como AllowRenegotation, o certificado do cliente pode ser renegociado durante uma solicitação.
NOTA O aplicativo deve armazenar ou consumir dados do corpo da solicitação antes de tentar a renegociação, caso contrário, a solicitação poderá ficar sem resposta.
Há um problema conhecido em que a habilitação AllowRenegotation pode fazer com que a renegociação ocorra de forma síncrona ao acessar a propriedade ClientCertificate. Chame o método GetClientCertificateAsync para evitar isso. Isso foi resolvido no .NET 6. Saiba mais neste tópico do GitHub. Nota GetClientCertificateAsync pode retornar um certificado nulo se o cliente se recusar a fornecer um.
Kestrel
Kestrel controla a negociação do certificado do cliente usando a opção ClientCertificateMode.
Para .NET 5 ou versões anteriores, Kestrel não dá suporte à renegociação após o início de uma conexão para a aquisição de um certificado de cliente. Esse recurso foi adicionado ao .NET 6.
Microsoft.AspNetCore.Authentication.Certificatecontém uma implementação semelhante à Autenticação de Certificado para ASP.NET Core. A autenticação de certificado ocorre no nível do TLS, muito antes de chegar ao ASP.NET Core. Mais precisamente, esse é um manipulador de autenticação que valida o certificado e, em seguida, fornece um evento em que você pode resolve esse certificado para um ClaimsPrincipal.
Configure seu servidorde rede para autenticação de certificado, seja ele o IIS, Kestrel, o Azure Aplicativos Web ou qualquer outro que você esteja usando.
Cenários de proxy e balanceador de carga
A autenticação de certificado é um cenário com estado usado principalmente em que um proxy ou balanceador de carga não manipula o tráfego entre clientes e servidores. Se um proxy ou balanceador de carga for usado, a autenticação de certificado só funcionará se o proxy ou o balanceador de carga:
- Lida com a autenticação.
- Passa as informações de autenticação do usuário para o aplicativo (por exemplo, em um cabeçalho de solicitação), que atua nas informações de autenticação.
Uma alternativa à autenticação de certificado em ambientes em que proxies e balanceadores de carga são usados é o ADFS (Serviços Federados do Active Directory) com o OIDC (OpenID Connect).
Introdução
Adquira um certificado HTTPS, aplique-o e configure o servidor para exigir certificados.
Em seu aplicativo Web, adicione uma referência ao pacote Microsoft.AspNetCore.Authentication.Certificate . No método Startup.ConfigureServices, em seguida, chame services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(...); com suas opções, fornecendo um delegado para que OnCertificateValidated faça qualquer validação suplementar no certificado do cliente enviado com as solicitações. Transforme essas informações em um ClaimsPrincipal e defina-as na propriedade context.Principal.
Se a autenticação falhar, esse manipulador retornará uma 403 (Forbidden) resposta em vez de 401 (Unauthorized), como você poderia esperar. O raciocínio é que a autenticação deve ocorrer durante a conexão TLS inicial. Quando ele chega ao manipulador, é tarde demais. Não há como atualizar a conexão de uma conexão anônima para uma com um certificado.
Adicione app.UseAuthentication(); também no método Startup.Configure. Caso contrário, o HttpContext.User não será definido como ClaimsPrincipal criado a partir do certificado. Por exemplo:
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
}
O exemplo anterior demonstra a maneira padrão de adicionar a autenticação de certificado. O manipulador constrói um principal de usuário usando as propriedades comuns do certificado.
Configurar a validação do certificado
O CertificateAuthenticationOptions manipulador tem algumas validações internas que são as validações mínimas que você deve executar em um certificado. Cada uma dessas configurações é habilitada por padrão.
AllowedCertificateTypes = Chained, SelfSigned ou All (Encadeado | AutoSigned)
Valor padrão: CertificateTypes.Chained
Essa verificação garante que somente o tipo de certificado apropriado é permitido. Se o aplicativo estiver usando certificados autoassinados, essa opção precisará ser definida como CertificateTypes.All ou CertificateTypes.SelfSigned.
ValidateCertificateUse
Valor padrão: true
Esse marcar valida se o certificado apresentado pelo cliente tem o EKU (uso de chave estendida) da Autenticação do Cliente ou nenhuma EKUs. Como dizem as especificações, se nenhum EKU for especificado, todas as EKUs serão consideradas válidas.
ValidarPeríodoDeValidade
Valor padrão: true
Esta checagem valida que o certificado está dentro de seu período de validade. Em cada solicitação, o manipulador garante que um certificado válido quando ele foi apresentado não expirou durante sua sessão atual.
IndicadorDeRevogação
Valor padrão: X509RevocationFlag.ExcludeRoot
Um sinalizador que especifica quais certificados na cadeia são verificados quanto à revogação.
As verificações de revogação só são executadas quando o certificado é encadeado a um certificado raiz.
Modo de Revogação
Valor padrão: X509RevocationMode.Online
Um sinalizador que especifica como as verificações de revogação são executadas.
Especificar uma marcar online pode resultar em um longo atraso enquanto a autoridade de certificação é contatada.
As verificações de revogação só são executadas quando o certificado é encadeado a um certificado raiz.
Posso configurar meu aplicativo para exigir um certificado somente em determinados caminhos?
Isso não é possível. Lembre-se de que a troca de certificados é feita no início da conversa HTTPS, ela é feita pelo servidor antes que a primeira solicitação seja recebida nessa conexão para que não seja possível definir o escopo com base em nenhum campo de solicitação.
Eventos de manipulador
O manipulador tem dois eventos:
-
OnAuthenticationFailed: Chamado se uma exceção ocorrer durante a autenticação e permite que você reaja. -
OnCertificateValidated: chamado após o certificado ter sido validado, ter passado na validação e ter sido criado uma entidade padrão. Esse evento permite que você execute sua própria validação e aumente ou substitua a entidade de segurança. Para obter exemplos, incluem:Determinando se o certificado é conhecido por seus serviços.
Construindo sua própria entidade de segurança. Considere o exemplo a seguir em
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; } }; });
Se você encontrar que o certificado de entrada não atende à sua validação extra, chame context.Fail("failure reason") com um motivo de falha.
Para uma funcionalidade real, você provavelmente desejará chamar um serviço registrado na injeção de dependência que se conecta a um banco de dados ou outro tipo de repositório de usuários. Acesse o serviço usando o contexto passado para o delegado. Considere o exemplo a seguir em 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;
}
};
});
Conceitualmente, a validação do certificado é uma preocupação de autorização. Adicionar um marcar, por exemplo, um emissor ou impressão digital em uma política de autorização, em vez de dentro de OnCertificateValidated, é perfeitamente aceitável.
Configurar seu servidor para exigir certificados
Kestrel
No Program.cs, configure o Kestrel da seguinte maneira:
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);
});
});
}
Observação
Endpoints criados ao chamar Listenantes de chamar ConfigureHttpsDefaults não terão os padrões aplicados.
IIS
Conclua as seguintes etapas no Gerenciador do IIS:
- Selecione seu site na guia Conexões.
- Clique duas vezes na opção Configurações de SSL na janela Exibição de Recursos.
- Marque a caixa de seleção Exigir SSL e selecione o botão de opção Exigir na seção Certificados do cliente.
Azure e proxies web personalizados
Consulte a documentação de hospedagem e implantação para saber como configurar o middleware de encaminhamento de certificado.
Usar a autenticação de certificado nos Aplicativos Web do Azure
Nenhuma configuração de encaminhamento é necessária para o Azure. A configuração de encaminhamento é realizada pelo Certificate Forwarding Middleware.
Observação
O Middleware de Encaminhamento de Certificado é necessário para esse cenário.
Para obter mais informações, confira Usar um certificado TLS/SSL no código do Serviço de Aplicativo do Azure.
Usar autenticação de certificado em proxies personalizados da Web
O método AddCertificateForwarding é usado para especificar:
- O nome do cabeçalho do cliente.
- Como o certificado deve ser carregado (usando a propriedade
HeaderConverter).
Em proxies web personalizados, o certificado é passado como um cabeçalho de solicitação personalizado, por exemplo X-SSL-CERT. Para usá-lo, configure o encaminhamento de certificado em 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;
}
Se o aplicativo for proxy reverso pelo NGINX com a configuração proxy_set_header ssl-client-cert $ssl_client_escaped_cert ou implantado no Kubernetes usando a Entrada NGINX, o certificado do cliente será passado para o aplicativo no formulário codificado em URL. Para usar o certificado, decodifica-o da seguinte maneira:
Adicione o namespace para System.Net à parte superior de Startup.cs:
using System.Net;
Em 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;
};
});
Adicione o método 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);
}
O método Startup.Configure adiciona o middleware.
UseCertificateForwarding é chamado antes das chamadas para UseAuthentication e UseAuthorization:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseRouting();
app.UseCertificateForwarding();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Uma classe separada pode ser usada para implementar a lógica de validação. Como o mesmo certificado autoassinado é usado neste exemplo, verifique se apenas o certificado pode ser usado. Valide se as impressões digitais do certificado do cliente e do certificado do servidor correspondem, caso contrário, qualquer certificado pode ser usado e será suficiente para autenticar. Isso seria usado dentro do método AddCertificate. Você também pode validar o assunto ou o emissor aqui se estiver usando certificados intermediários ou filho.
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;
}
}
}
Implementar um HttpClient usando um certificado e o HttpClientHandler
O HttpClientHandler pode ser adicionado diretamente no construtor da classe HttpClient. Deve-se tomar cuidado ao criar instâncias do HttpClient. O HttpClient enviará o certificado com cada solicitação.
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}");
}
Implementar um HttpClient usando um certificado e um HttpClient nomeado de IHttpClientFactory
No exemplo a seguir, um certificado do cliente é adicionado a um HttpClientHandler usando a propriedade ClientCertificates do manipulador. Esse manipulador pode ser usado em uma instância nomeada de um HttpClient usando o método ConfigurePrimaryHttpMessageHandler. Isso é configurado em 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;
});
O IHttpClientFactory pode ser usado para obter a instância nomeada com o manipulador e o certificado. O método CreateClient com o nome do cliente definido na classe Startup é usado para obter a instância . A solicitação HTTP pode ser enviada usando o cliente conforme necessário.
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}");
}
Se o certificado correto for enviado ao servidor, os dados serão retornados. Se nenhum certificado ou um certificado incorreto for enviado, um código de status HTTP 403 será retornado.
Criar certificados no PowerShell
Criar os certificados é a parte mais difícil na configuração desse fluxo. Um certificado raiz pode ser criado usando o New-SelfSignedCertificate cmdlet do PowerShell. Ao criar o certificado, use uma senha forte. É importante adicionar o KeyUsageProperty parâmetro e o KeyUsage parâmetro, conforme mostrado.
Criar CA raiz
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
Observação
O valor do parâmetro -DnsName deve corresponder ao destino de implantação do aplicativo. Por exemplo, "localhost" para desenvolvimento.
Instalar na raiz confiável
O certificado raiz precisa ser confiável em seu sistema host. Um certificado raiz que não foi criado por uma autoridade de certificação não será confiável por padrão. Para obter informações sobre como confiar no certificado raiz no Windows, consulte esta pergunta.
Certificado intermediário
Um certificado intermediário agora pode ser criado com base no certificado raiz. Isso não é necessário para todos os casos de uso, mas talvez seja necessário criar muitos certificados ou precisar ativar ou desabilitar grupos de certificados. O parâmetro TextExtension é necessário para definir o comprimento do caminho nas restrições básicas do certificado.
Em seguida, o certificado intermediário pode ser adicionado ao certificado intermediário confiável no sistema host do 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
Criar certificado filho com base no certificado intermediário
Um certificado filho pode ser criado com base no certificado intermediário. Essa é a entidade final e não precisa criar mais certificados filho.
$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
Criar certificado filho com base no certificado raiz
Um certificado filho também pode ser criado diretamente do certificado raiz.
$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
Raiz de exemplo – certificado intermediário – certificado
$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
Ao usar os certificados raiz, intermediário ou filho, os certificados podem ser validados usando a Impressão Digital ou PublicKey conforme necessário.
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;
}
}
}
Certificados do cliente opcionais
Esta seção fornece informações para aplicativos que devem proteger um subconjunto do aplicativo com um certificado. Por exemplo, uma Página ou controlador Razor no aplicativo pode exigir certificados de cliente. Isso apresenta desafios, como os certificados de clientes.
- São um recurso TLS, não um recurso HTTP.
- São negociados por conexão e geralmente no início da conexão antes que todos os dados HTTP estejam disponíveis.
Há duas abordagens para implementar certificados de cliente opcionais:
- Usando nomes de host (SNI) separados e redirecionando. Embora haja mais trabalho para configurar, isso é recomendado porque ele funciona na maioria dos ambientes e protocolos.
- Renegociação durante uma solicitação HTTP. Isso tem várias limitações e não é recomendado.
Hosts separados (SNI)
No início da conexão, somente a SNI (Indicação de Nome do Servidor)† é conhecida. Os certificados do cliente podem ser configurados por nome de host para que um host os exija e outro não.
- Configurar a associação para o domínio e o subdomínio:
- Por exemplo, configure associações em
contoso.comemyClient.contoso.com. Ocontoso.comhost não requer um certificado do cliente, masmyClient.contoso.comrequer. - Para obter mais informações, consulte:
-
Kestrelservidor Web no ASP.NET Core:
- ListenOptions.UseHttps
- ClientCertificateMode
- Nota Kestrel atualmente não dá suporte a várias configurações de TLS em uma associação, você precisará de duas associações com IPs ou portas exclusivas. Saiba mais neste tópico do GitHub.
- IIS
- HTTP.sys: Configurar o Windows Server
-
Kestrelservidor Web no ASP.NET Core:
- Por exemplo, configure associações em
O .NET 5 ou posterior adiciona suporte mais conveniente para redirecionamento para adquirir certificados de cliente opcionais. Para obter mais informações, consulte o exemplo certificados opcionais.
- Para solicitações para o aplicativo Web que exigem um certificado de cliente e não têm um:
- Redirecione para a mesma página usando o subdomínio protegido pelo certificado do cliente.
- Por exemplo, redirecionar para
myClient.contoso.com/requestedPage. Como a solicitação paramyClient.contoso.com/requestedPageé um nome de host diferente decontoso.com/requestedPage, o cliente estabelece uma conexão diferente e o certificado do cliente é fornecido. - Para obter mais informações, consulte Introdução à autorização no ASP.NET Core.
† A SNI (Indicação de Nome de Servidor) é uma extensão TLS para incluir um domínio virtual como parte da negociação SSL. Isso significa que o nome de domínio virtual, ou um nome de host, pode ser usado para identificar o ponto final da rede.
Renegociação
A renegociação do TLS é um processo pelo qual o cliente e o servidor podem reavaliar os requisitos de criptografia para uma conexão individual, incluindo a solicitação de um certificado do cliente, se não for fornecido anteriormente. A renegociação do TLS é um risco à segurança e não é recomendada porque:
- Em HTTP/1.1, o servidor deve primeiro armazenar ou consumir todos os dados HTTP que estão em trânsito, como os corpos das solicitações POST, para assegurar que a conexão esteja livre antes da renegociação. Caso contrário, a renegociação poderá parar de responder ou falhar.
- HTTP/2 e HTTP/3 proíbem explicitamente a renegociação.
- Há riscos de segurança associados à renegociação. O TLS 1.3 removeu a renegociação de toda a conexão e a substituiu por uma nova extensão para solicitar apenas o certificado do cliente após o início da conexão. Esse mecanismo é exposto por meio das mesmas APIs e ainda está sujeito às restrições anteriores de buffer e versões de protocolo HTTP.
A implementação e a configuração desse recurso variam de acordo com a versão do servidor e da estrutura.
IIS
O IIS gerencia a negociação de certificado do cliente em seu nome. Uma subseção do aplicativo pode habilitar a opção SslRequireCert de negociar o certificado do cliente para essas solicitações. Consulte Configuração na documentação do IIS para obter detalhes.
O IIS armazenará automaticamente os dados do corpo da solicitação em buffer até um limite de tamanho configurado antes da renegociação. As solicitações que excedem o limite são rejeitadas com uma resposta 413. Esse limite usa como padrão 48 KB e é configurável definindo o uploadReadAheadSize.
HttpSys
O HttpSys tem duas configurações que controlam a negociação de certificado do cliente e ambas devem ser definidas. O primeiro está em netsh.exe em http add sslcert clientcertnegotiation=enable/disable. Esse sinalizador indica se o certificado do cliente deve ser negociado no início de uma conexão e deve ser definido disable como para certificados de cliente opcionais. Consulte os documentos do netsh para obter detalhes.
A outra configuração é ClientCertificateMethod. Quando definido como AllowRenegotation, o certificado do cliente pode ser renegociado durante uma solicitação.
NOTA O aplicativo deve armazenar ou consumir dados do corpo da solicitação antes de tentar a renegociação, caso contrário, a solicitação poderá ficar sem resposta.
Há um problema conhecido em que a habilitação AllowRenegotation pode fazer com que a renegociação ocorra de forma síncrona ao acessar a propriedade ClientCertificate. Chame o método GetClientCertificateAsync para evitar isso. Isso foi resolvido no .NET 6. Saiba mais neste tópico do GitHub. Nota GetClientCertificateAsync pode retornar um certificado nulo se o cliente se recusar a fornecer um.
Kestrel
Kestrel controla a negociação do certificado do cliente usando a opção ClientCertificateMode.
Para .NET 5 ou versões anteriores, Kestrel não dá suporte à renegociação após o início de uma conexão para a aquisição de um certificado de cliente. Esse recurso foi adicionado ao .NET 6.
Deixe perguntas, comentários e outras opiniões sobre certificados de cliente opcionais no tópico de discussão para o problema nº 18720 no GitHub.