Configurar a autenticação mútua de TLS para o Serviço de Aplicações do Azure
Pode restringir o acesso à aplicação do Serviço de Aplicações do Azure ao ativar diferentes tipos de autenticação. Uma forma de o fazer é pedir um certificado de cliente quando o pedido de cliente for feito por TLS/SSL e validar o certificado. Esse mecanismo é chamado de autenticação mútua TLS (Transport Layer Security) ou autenticação de certificado de cliente. Este artigo mostra como configurar a aplicação para utilizar a autenticação de certificados de cliente.
Nota
O código do aplicativo é responsável por validar o certificado do cliente. O Serviço de Aplicativo não faz nada com esse certificado de cliente além de encaminhá-lo para seu aplicativo.
Se você acessar seu site por HTTP e não HTTPS, não receberá nenhum certificado de cliente. Portanto, se seu aplicativo requer certificados de cliente, você não deve permitir solicitações para seu aplicativo por HTTP.
Preparar a sua aplicação Web
Para criar associações TLS/SSL personalizadas ou habilitar certificados de cliente para seu aplicativo do Serviço de Aplicativo, seu plano do Serviço de Aplicativo deve estar na camada Básica, Padrão, Premium ou Isolada . Para se certificar de que a sua aplicação Web está no nível de preços suportado, siga estes passos:
Aceda à sua aplicação Web
Na caixa de pesquisa do portal do Azure, localize e selecione Serviços de Aplicativo.
Na página Serviços de Aplicativo, selecione o nome do seu aplicativo Web.
Agora você está na página de gerenciamento do seu aplicativo Web.
Verificar o escalão de preço
No menu esquerdo do seu aplicativo Web, na seção Configurações, selecione Aumentar a escala (plano do Serviço de Aplicativo).
Certifique-se de que seu aplicativo Web não esteja na camada F1 ou D1 , que não oferece suporte a TLS/SSL personalizado.
Se precisar de aumentar verticalmente, siga os passos na secção seguinte. Caso contrário, feche a página Aumentar escala e ignore a seção Aumentar a escala do plano do Serviço de Aplicativo.
Aumentar verticalmente o seu plano do Serviço de Aplicações
Selecione qualquer camada não livre, como B1, B2, B3 ou qualquer outra camada na categoria Produção.
Quando terminar, selecione Selecionar.
Quando a seguinte mensagem for exibida, a operação de escala foi concluída.
Habilitar certificados de cliente
Ao habilitar o certificado de cliente para seu aplicativo, você deve selecionar sua escolha de modo de certificado de cliente. Cada modo define como seu aplicativo lida com certificados de cliente de entrada:
Modos de certificado de cliente | Description |
---|---|
Obrigatório | Todas as solicitações exigem um certificado de cliente. |
Opcional | As solicitações podem ou não usar um certificado de cliente e os clientes são solicitados a fornecer um certificado por padrão. Por exemplo, os clientes do navegador mostrarão um prompt para selecionar um certificado para autenticação. |
Usuário interativo opcional | As solicitações podem ou não usar um certificado de cliente e os clientes não são solicitados a fornecer um certificado por padrão. Por exemplo, os clientes do navegador não mostrarão um prompt para selecionar um certificado para autenticação. |
Para configurar seu aplicativo para exigir certificados de cliente no portal do Azure:
- Navegue até a página de gerenciamento do seu aplicativo.
- Na navegação à esquerda da página de gerenciamento do seu aplicativo, selecione Configurações> Gerais.
- Selecione o modo de certificado de cliente de escolha. Selecione Guardar no topo da página.
Excluir caminhos da exigência de autenticação
Quando você habilita a autenticação mútua para seu aplicativo, todos os caminhos na raiz do seu aplicativo exigem um certificado de cliente para acesso. Para remover esse requisito para determinados caminhos, defina caminhos de exclusão como parte da configuração do aplicativo.
Nota
O uso de qualquer caminho de exclusão de certificado de cliente aciona a renegociação TLS para solicitações de entrada no aplicativo.
Na navegação à esquerda da página de gerenciamento do seu aplicativo, selecione Configurações> Gerais.
Ao lado de Caminhos de exclusão de certificado, selecione o ícone de edição.
Selecione Novo caminho, especifique um caminho ou uma lista de caminhos separados por
,
ou;
e selecione OK.Selecione Guardar no topo da página.
Na captura de tela a seguir, qualquer caminho para seu aplicativo que comece com /public
não solicita um certificado de cliente. A correspondência de caminho não diferencia maiúsculas de minúsculas.
Certificado de cliente e renegociação de TLS
O Serviço de Aplicativo exige a renegociação do TLS para ler uma solicitação antes de saber se deseja solicitar um certificado de cliente. Qualquer uma das seguintes configurações aciona a renegociação do TLS:
- Usando o modo de certificado de cliente "Usuário interativo opcional".
- Usando o caminho de exclusão de certificado do cliente.
Nota
TLS 1.3 e HTTP 2.0 não suportam renegociação TLS. Esses protocolos não funcionarão se seu aplicativo estiver configurado com configurações de certificado de cliente que usam renegociação TLS.
Para desabilitar a renegociação de TLS e fazer com que o aplicativo negocie certificados de cliente durante o handshake TLS, você deve configurar seu aplicativo com todas estas configurações:
- Defina o modo de certificado do cliente como "Obrigatório" ou "Opcional"
- Remover todos os caminhos de exclusão de certificado de cliente
Upload de arquivos grandes com renegociação TLS
As configurações de certificado de cliente que usam a renegociação TLS não podem suportar solicitações de entrada com arquivos grandes maiores que 100 kb devido a limitações de tamanho do buffer. Nesse cenário, qualquer solicitação POST ou PUT acima de 100 kb falhará com um erro 403. Este limite não é configurável e não pode ser aumentado.
Para abordar o limite de 100 kb, considere estas soluções alternativas:
- Desative a renegociação TLS. Atualize as configurações de certificado de cliente do seu aplicativo com todas estas configurações:
- Defina o modo de certificado do cliente como "Obrigatório" ou "Opcional"
- Remover todos os caminhos de exclusão de certificado de cliente
- Envie uma solicitação HEAD antes da solicitação PUT/POST. A solicitação HEAD lida com o certificado do cliente.
- Adicione o cabeçalho
Expect: 100-Continue
ao seu pedido. Isso faz com que o cliente aguarde até que o servidor responda com um100 Continue
antes de enviar o corpo da solicitação, que ignora os buffers.
Certificado de cliente de acesso
No Serviço de Aplicativo, o encerramento TLS da solicitação acontece no balanceador de carga frontend. Quando o Serviço de Aplicativo encaminha a solicitação para o código do aplicativo com certificados de cliente habilitados, ele injeta um X-ARR-ClientCert
cabeçalho de solicitação com o certificado do cliente. O Serviço de Aplicativo não faz nada com esse certificado de cliente além de encaminhá-lo para seu aplicativo. O código do aplicativo é responsável por validar o certificado do cliente.
Por ASP.NET, o certificado do cliente está disponível por meio da propriedade HttpRequest.ClientCertificate .
Para outras pilhas de aplicativos (Node.js, PHP, etc.), o certificado do cliente está disponível em seu aplicativo por meio de um valor codificado em base64 no cabeçalho da X-ARR-ClientCert
solicitação.
ASP.NET Exemplo de núcleo
Para ASP.NET Core, o middleware é fornecido para analisar certificados encaminhados. Middleware separado é fornecido para usar os cabeçalhos de protocolo encaminhados. Ambos devem estar presentes para que os certificados encaminhados sejam aceitos. Você pode colocar a lógica de validação de certificado personalizada nas opções CertificateAuthentication.
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
// Configure the application to use the protocol and client ip address forwarded by the frontend load balancer
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
// Only loopback proxies are allowed by default. Clear that restriction to enable this explicit configuration.
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
// Configure the application to client certificate forwarded the frontend load balancer
services.AddCertificateForwarding(options => { options.CertificateHeader = "X-ARR-ClientCert"; });
// Add certificate authentication so when authorization is performed the user will be created from the certificate
services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseForwardedHeaders();
app.UseCertificateForwarding();
app.UseHttpsRedirection();
app.UseAuthentication()
app.UseAuthorization();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
ASP.NET exemplo de WebForms
using System;
using System.Collections.Specialized;
using System.Security.Cryptography.X509Certificates;
using System.Web;
namespace ClientCertificateUsageSample
{
public partial class Cert : System.Web.UI.Page
{
public string certHeader = "";
public string errorString = "";
private X509Certificate2 certificate = null;
public string certThumbprint = "";
public string certSubject = "";
public string certIssuer = "";
public string certSignatureAlg = "";
public string certIssueDate = "";
public string certExpiryDate = "";
public bool isValidCert = false;
//
// Read the certificate from the header into an X509Certificate2 object
// Display properties of the certificate on the page
//
protected void Page_Load(object sender, EventArgs e)
{
NameValueCollection headers = base.Request.Headers;
certHeader = headers["X-ARR-ClientCert"];
if (!String.IsNullOrEmpty(certHeader))
{
try
{
byte[] clientCertBytes = Convert.FromBase64String(certHeader);
certificate = new X509Certificate2(clientCertBytes);
certSubject = certificate.Subject;
certIssuer = certificate.Issuer;
certThumbprint = certificate.Thumbprint;
certSignatureAlg = certificate.SignatureAlgorithm.FriendlyName;
certIssueDate = certificate.NotBefore.ToShortDateString() + " " + certificate.NotBefore.ToShortTimeString();
certExpiryDate = certificate.NotAfter.ToShortDateString() + " " + certificate.NotAfter.ToShortTimeString();
}
catch (Exception ex)
{
errorString = ex.ToString();
}
finally
{
isValidCert = IsValidClientCertificate();
if (!isValidCert) Response.StatusCode = 403;
else Response.StatusCode = 200;
}
}
else
{
certHeader = "";
}
}
//
// This is a SAMPLE verification routine. Depending on your application logic and security requirements,
// you should modify this method
//
private bool IsValidClientCertificate()
{
// In this example we will only accept the certificate as a valid certificate if all the conditions below are met:
// 1. The certificate isn't expired and is active for the current time on server.
// 2. The subject name of the certificate has the common name nildevecc
// 3. The issuer name of the certificate has the common name nildevecc and organization name Microsoft Corp
// 4. The thumbprint of the certificate is 30757A2E831977D8BD9C8496E4C99AB26CB9622B
//
// This example doesn't test that this certificate is chained to a Trusted Root Authority (or revoked) on the server
// and it allows for self signed certificates
//
if (certificate == null || !String.IsNullOrEmpty(errorString)) return false;
// 1. Check time validity of certificate
if (DateTime.Compare(DateTime.Now, certificate.NotBefore) < 0 || DateTime.Compare(DateTime.Now, certificate.NotAfter) > 0) return false;
// 2. Check subject name of certificate
bool foundSubject = false;
string[] certSubjectData = certificate.Subject.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string s in certSubjectData)
{
if (String.Compare(s.Trim(), "CN=nildevecc") == 0)
{
foundSubject = true;
break;
}
}
if (!foundSubject) return false;
// 3. Check issuer name of certificate
bool foundIssuerCN = false, foundIssuerO = false;
string[] certIssuerData = certificate.Issuer.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string s in certIssuerData)
{
if (String.Compare(s.Trim(), "CN=nildevecc") == 0)
{
foundIssuerCN = true;
if (foundIssuerO) break;
}
if (String.Compare(s.Trim(), "O=Microsoft Corp") == 0)
{
foundIssuerO = true;
if (foundIssuerCN) break;
}
}
if (!foundIssuerCN || !foundIssuerO) return false;
// 4. Check thumbprint of certificate
if (String.Compare(certificate.Thumbprint.Trim().ToUpper(), "30757A2E831977D8BD9C8496E4C99AB26CB9622B") != 0) return false;
return true;
}
}
}
Node.js amostra
O código de exemplo de Node.js a seguir obtém o X-ARR-ClientCert
cabeçalho e usa node-forge para converter a cadeia de caracteres PEM codificada em base64 em um objeto de certificado e validá-lo:
import { NextFunction, Request, Response } from 'express';
import { pki, md, asn1 } from 'node-forge';
export class AuthorizationHandler {
public static authorizeClientCertificate(req: Request, res: Response, next: NextFunction): void {
try {
// Get header
const header = req.get('X-ARR-ClientCert');
if (!header) throw new Error('UNAUTHORIZED');
// Convert from PEM to pki.CERT
const pem = `-----BEGIN CERTIFICATE-----${header}-----END CERTIFICATE-----`;
const incomingCert: pki.Certificate = pki.certificateFromPem(pem);
// Validate certificate thumbprint
const fingerPrint = md.sha1.create().update(asn1.toDer(pki.certificateToAsn1(incomingCert)).getBytes()).digest().toHex();
if (fingerPrint.toLowerCase() !== 'abcdef1234567890abcdef1234567890abcdef12') throw new Error('UNAUTHORIZED');
// Validate time validity
const currentDate = new Date();
if (currentDate < incomingCert.validity.notBefore || currentDate > incomingCert.validity.notAfter) throw new Error('UNAUTHORIZED');
// Validate issuer
if (incomingCert.issuer.hash.toLowerCase() !== 'abcdef1234567890abcdef1234567890abcdef12') throw new Error('UNAUTHORIZED');
// Validate subject
if (incomingCert.subject.hash.toLowerCase() !== 'abcdef1234567890abcdef1234567890abcdef12') throw new Error('UNAUTHORIZED');
next();
} catch (e) {
if (e instanceof Error && e.message === 'UNAUTHORIZED') {
res.status(401).send();
} else {
next(e);
}
}
}
}
Exemplo de Java
A classe Java a seguir codifica o certificado de X-ARR-ClientCert
para uma X509Certificate
instância. certificateIsValid()
Valida que a impressão digital do certificado corresponde à fornecida no construtor e que o certificado não expirou.
import java.io.ByteArrayInputStream;
import java.security.NoSuchAlgorithmException;
import java.security.cert.*;
import java.security.MessageDigest;
import sun.security.provider.X509Factory;
import javax.xml.bind.DatatypeConverter;
import java.util.Base64;
import java.util.Date;
public class ClientCertValidator {
private String thumbprint;
private X509Certificate certificate;
/**
* Constructor.
* @param certificate The certificate from the "X-ARR-ClientCert" HTTP header
* @param thumbprint The thumbprint to check against
* @throws CertificateException If the certificate factory cannot be created.
*/
public ClientCertValidator(String certificate, String thumbprint) throws CertificateException {
certificate = certificate
.replaceAll(X509Factory.BEGIN_CERT, "")
.replaceAll(X509Factory.END_CERT, "");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
byte [] base64Bytes = Base64.getDecoder().decode(certificate);
X509Certificate X509cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(base64Bytes));
this.setCertificate(X509cert);
this.setThumbprint(thumbprint);
}
/**
* Check that the certificate's thumbprint matches the one given in the constructor, and that the
* certificate hasn't expired.
* @return True if the certificate's thumbprint matches and hasn't expired. False otherwise.
*/
public boolean certificateIsValid() throws NoSuchAlgorithmException, CertificateEncodingException {
return certificateHasNotExpired() && thumbprintIsValid();
}
/**
* Check certificate's timestamp.
* @return Returns true if the certificate hasn't expired. Returns false if it has expired.
*/
private boolean certificateHasNotExpired() {
Date currentTime = new java.util.Date();
try {
this.getCertificate().checkValidity(currentTime);
} catch (CertificateExpiredException | CertificateNotYetValidException e) {
return false;
}
return true;
}
/**
* Check the certificate's thumbprint matches the given one.
* @return Returns true if the thumbprints match. False otherwise.
*/
private boolean thumbprintIsValid() throws NoSuchAlgorithmException, CertificateEncodingException {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] der = this.getCertificate().getEncoded();
md.update(der);
byte[] digest = md.digest();
String digestHex = DatatypeConverter.printHexBinary(digest);
return digestHex.toLowerCase().equals(this.getThumbprint().toLowerCase());
}
// Getters and setters
public void setThumbprint(String thumbprint) {
this.thumbprint = thumbprint;
}
public String getThumbprint() {
return this.thumbprint;
}
public X509Certificate getCertificate() {
return certificate;
}
public void setCertificate(X509Certificate certificate) {
this.certificate = certificate;
}
}
Exemplo de Python
Os exemplos de código Flask e Django Python a seguir implementam um decorador chamado authorize_certificate
que pode ser usado em uma função view para permitir o acesso apenas a chamadores que apresentam um certificado de cliente válido. Ele espera um certificado formatado X-ARR-ClientCert
PEM no cabeçalho e usa o pacote de criptografia Python para validar o certificado com base em sua impressão digital (impressão digital), nome comum do assunto, nome comum do emissor e datas de início e validade. Se a validação falhar, o decorador garante que uma resposta HTTP com o código de status 403 (Proibido) seja devolvida ao cliente.
from functools import wraps
from datetime import datetime, timezone
from flask import abort, request
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
def validate_cert(request):
try:
cert_value = request.headers.get('X-ARR-ClientCert')
if cert_value is None:
return False
cert_data = ''.join(['-----BEGIN CERTIFICATE-----\n', cert_value, '\n-----END CERTIFICATE-----\n',])
cert = x509.load_pem_x509_certificate(cert_data.encode('utf-8'))
fingerprint = cert.fingerprint(hashes.SHA1())
if fingerprint != b'12345678901234567890':
return False
subject = cert.subject
subject_cn = subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
if subject_cn != "contoso.com":
return False
issuer = cert.issuer
issuer_cn = issuer.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
if issuer_cn != "contoso.com":
return False
current_time = datetime.now(timezone.utc)
if current_time < cert.not_valid_before_utc:
return False
if current_time > cert.not_valid_after_utc:
return False
return True
except Exception as e:
# Handle any errors encountered during validation
print(f"Encountered the following error during certificate validation: {e}")
return False
def authorize_certificate(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not validate_cert(request):
abort(403)
return f(*args, **kwargs)
return decorated_function
O trecho de código a seguir mostra como usar o decorador em uma função de visualização Flask.
@app.route('/hellocert')
@authorize_certificate
def hellocert():
print('Request for hellocert page received')
return render_template('index.html')