Scenario 9.1: Autenticazione client mTLS

Categoria: Autenticazione
Complessita: ⭐⭐⭐⭐ (Alta)
Prerequisiti: Certificati client e server
Tempo stimato: 20-30 minuti


Descrizione

Questo scenario descrive l'autenticazione Mutual TLS (mTLS), in cui sia il server che il client si autenticano reciprocamente con certificati.

Vantaggi rispetto all'autenticazione con password:


Workflow

sequenceDiagram participant Client participant Server Client->>Server: ClientHello Server->>Client: ServerHello + ServerCert Server->>Client: CertificateRequest Client->>Server: ClientCert + CertificateVerify Server->>Server: Validare certificato client Server->>Client: Finished Client->>Server: Finished Note over Client,Server: Connessione mTLS stabilita


Esempio di codice: Server mTLS (ASP.NET Core)

using Microsoft.AspNetCore.Authentication.Certificate;
using Microsoft.AspNetCore.Server.Kestrel.Https;
 
var builder = WebApplication.CreateBuilder(args);
 
// Configurare Kestrel per mTLS
builder.WebHost.ConfigureKestrel(options =>
{
    options.ConfigureHttpsDefaults(https =>
    {
        // Caricare certificato server
        https.ServerCertificate = new X509Certificate2(
            "server.pfx", "ServerPassword!");
 
        // Richiedere certificato client
        https.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
 
        // Validare certificato client
        https.ClientCertificateValidation = (cert, chain, errors) =>
        {
            // Validazione personalizzata
            if (errors != SslPolicyErrors.None)
            {
                Console.WriteLine($"SSL Policy Errors: {errors}");
                return false;
            }
 
            // Verificare propria CA
            var trustedRoots = new X509Certificate2Collection();
            trustedRoots.Add(new X509Certificate2("root-ca.crt"));
 
            var customChain = new X509Chain();
            customChain.ChainPolicy.CustomTrustStore.AddRange(trustedRoots);
            customChain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
            customChain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
 
            return customChain.Build(cert);
        };
    });
});
 
// Aggiungere Certificate Authentication
builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
    .AddCertificate(options =>
    {
        options.AllowedCertificateTypes = CertificateTypes.Chained;
        options.RevocationMode = X509RevocationMode.Online;
 
        options.Events = new CertificateAuthenticationEvents
        {
            OnCertificateValidated = context =>
            {
                // Estrarre claims dal certificato
                var claims = new[]
                {
                    new Claim(ClaimTypes.NameIdentifier, context.ClientCertificate.Subject),
                    new Claim(ClaimTypes.Name, context.ClientCertificate.GetNameInfo(X509NameType.SimpleName, false)),
                    new Claim("certificate_thumbprint", context.ClientCertificate.Thumbprint)
                };
 
                context.Principal = new ClaimsPrincipal(
                    new ClaimsIdentity(claims, context.Scheme.Name));
 
                context.Success();
                return Task.CompletedTask;
            },
 
            OnAuthenticationFailed = context =>
            {
                Console.WriteLine($"Auth failed: {context.Exception.Message}");
                return Task.CompletedTask;
            }
        };
    });
 
builder.Services.AddAuthorization();
 
var app = builder.Build();
 
app.UseAuthentication();
app.UseAuthorization();
 
app.MapGet("/api/protected", [Authorize] (HttpContext ctx) =>
{
    var subject = ctx.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    return Results.Ok(new { message = "Authenticated", subject });
});
 
app.Run();

Esempio di codice: Client mTLS (HttpClient)

using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
 
using var ctx = PqCryptoContext.Initialize();
 
// Caricare certificato e chiave client
var clientCert = ctx.LoadCertificateWithPrivateKey(
    "client.crt.pem",
    "client.key.pem",
    "ClientPassword!"
);
 
// HttpClientHandler con certificato client
var handler = new HttpClientHandler
{
    ClientCertificateOptions = ClientCertificateOption.Manual,
    ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
    {
        // Validare certificato server
        if (errors != SslPolicyErrors.None)
        {
            Console.WriteLine($"Server certificate error: {errors}");
            // In produzione: return false;
        }
        return true;
    }
};
 
// Aggiungere certificato client
handler.ClientCertificates.Add(clientCert);
 
using var httpClient = new HttpClient(handler);
 
// Inviare richiesta
var response = await httpClient.GetAsync("https://api.example.com/api/protected");
 
if (response.IsSuccessStatusCode)
{
    var content = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"Response: {content}");
}
else
{
    Console.WriteLine($"Error: {response.StatusCode}");
}

Autorizzazione basata su certificato

public class CertificateAuthorizationHandler : AuthorizationHandler<CertificateRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        CertificateRequirement requirement)
    {
        // Ottenere certificato dal Principal
        var thumbprint = context.User.FindFirst("certificate_thumbprint")?.Value;
        var subject = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
 
        if (string.IsNullOrEmpty(thumbprint))
        {
            return Task.CompletedTask;
        }
 
        // Verificare se il certificato e autorizzato
        if (requirement.AllowedThumbprints.Contains(thumbprint) ||
            requirement.AllowedSubjectPatterns.Any(p => Regex.IsMatch(subject, p)))
        {
            context.Succeed(requirement);
        }
 
        return Task.CompletedTask;
    }
}
 
public class CertificateRequirement : IAuthorizationRequirement
{
    public HashSet<string> AllowedThumbprints { get; set; } = new();
    public List<string> AllowedSubjectPatterns { get; set; } = new();
}
 
// Registrazione
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ServiceAccounts", policy =>
        policy.Requirements.Add(new CertificateRequirement
        {
            AllowedSubjectPatterns = new List<string>
            {
                @"^CN=.*\.svc\.cluster\.local$",  // Kubernetes services
                @"^CN=service-[a-z]+$"            // Named services
            }
        }));
});
 
builder.Services.AddSingleton<IAuthorizationHandler, CertificateAuthorizationHandler>();

Nginx come Reverse Proxy mTLS

server {
    listen 443 ssl;
    server_name api.example.com;
 
    # Certificato server
    ssl_certificate     /etc/nginx/ssl/server-chain.pem;
    ssl_certificate_key /etc/nginx/ssl/server.key.pem;
 
    # Autenticazione certificato client
    ssl_client_certificate /etc/nginx/ssl/ca-chain.pem;
    ssl_verify_client on;
    ssl_verify_depth 2;
 
    # TLS 1.3
    ssl_protocols TLSv1.3;
    ssl_prefer_server_ciphers on;
 
    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
 
    location / {
        # Passare info certificato client al backend
        proxy_set_header X-Client-Cert $ssl_client_cert;
        proxy_set_header X-Client-Verify $ssl_client_verify;
        proxy_set_header X-Client-S-DN $ssl_client_s_dn;
 
        proxy_pass http://backend:8080;
    }
}

Requisiti mTLS specifici per settore

Settore Durata certificato Revocation Particolarita
Kubernetes 1 anno CRL SPIFFE/SPIRE
Settore finanziario 90 giorni OCSP Conforme PCI-DSS
Sanita 1 anno CRL + OCSP Conforme DiGAV
IoT/SCADA 5 anni CRL Capacita offline

Scenari correlati

Relazione Scenario Descrizione
Prerequisito 3.2 Certificato client Creare cert client
Correlato 9.2 Smartcard Login Token hardware
Correlato 10.3 Deployment mTLS Configurazione TLS

« ← Panoramica autenticazione | ↑ Scenari | 9.2 Smartcard Login → »


Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional