Szenario 9.1: mTLS Client-Authentifizierung

Kategorie: Authentifizierung
Komplexität: ⭐⭐⭐⭐ (Hoch)
Voraussetzungen: Client- und Server-Zertifikate
Geschätzte Zeit: 20-30 Minuten


Beschreibung

Dieses Szenario beschreibt die Mutual TLS (mTLS) Authentifizierung, bei der sowohl Server als auch Client sich gegenseitig mit Zertifikaten authentifizieren.

Vorteile gegenüber Passwort-Authentifizierung:


Workflow

sequenceDiagram participant Client participant Server Client->>Server: ClientHello Server->>Client: ServerHello + ServerCert Server->>Client: CertificateRequest Client->>Server: ClientCert + CertificateVerify Server->>Server: Client-Zertifikat validieren Server->>Client: Finished Client->>Server: Finished Note over Client,Server: mTLS-Verbindung etabliert


Code-Beispiel: mTLS Server (ASP.NET Core)

using Microsoft.AspNetCore.Authentication.Certificate;
using Microsoft.AspNetCore.Server.Kestrel.Https;
 
var builder = WebApplication.CreateBuilder(args);
 
// Kestrel für mTLS konfigurieren
builder.WebHost.ConfigureKestrel(options =>
{
    options.ConfigureHttpsDefaults(https =>
    {
        // Server-Zertifikat laden
        https.ServerCertificate = new X509Certificate2(
            "server.pfx", "ServerPassword!");
 
        // Client-Zertifikat anfordern
        https.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
 
        // Client-Zertifikat validieren
        https.ClientCertificateValidation = (cert, chain, errors) =>
        {
            // Custom Validation
            if (errors != SslPolicyErrors.None)
            {
                Console.WriteLine($"SSL Policy Errors: {errors}");
                return false;
            }
 
            // Eigene CA prüfen
            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);
        };
    });
});
 
// Certificate Authentication hinzufügen
builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
    .AddCertificate(options =>
    {
        options.AllowedCertificateTypes = CertificateTypes.Chained;
        options.RevocationMode = X509RevocationMode.Online;
 
        options.Events = new CertificateAuthenticationEvents
        {
            OnCertificateValidated = context =>
            {
                // Claims aus Zertifikat extrahieren
                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();

Code-Beispiel: mTLS Client (HttpClient)

using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
 
using var ctx = PqCryptoContext.Initialize();
 
// Client-Zertifikat und Schlüssel laden
var clientCert = ctx.LoadCertificateWithPrivateKey(
    "client.crt.pem",
    "client.key.pem",
    "ClientPassword!"
);
 
// HttpClientHandler mit Client-Zertifikat
var handler = new HttpClientHandler
{
    ClientCertificateOptions = ClientCertificateOption.Manual,
    ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
    {
        // Server-Zertifikat validieren
        if (errors != SslPolicyErrors.None)
        {
            Console.WriteLine($"Server certificate error: {errors}");
            // In Produktion: return false;
        }
        return true;
    }
};
 
// Client-Zertifikat hinzufügen
handler.ClientCertificates.Add(clientCert);
 
using var httpClient = new HttpClient(handler);
 
// Request senden
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}");
}

Zertifikats-basierte Autorisierung

public class CertificateAuthorizationHandler : AuthorizationHandler<CertificateRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        CertificateRequirement requirement)
    {
        // Zertifikat aus Principal holen
        var thumbprint = context.User.FindFirst("certificate_thumbprint")?.Value;
        var subject = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
 
        if (string.IsNullOrEmpty(thumbprint))
        {
            return Task.CompletedTask;
        }
 
        // Prüfen ob Zertifikat autorisiert ist
        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();
}
 
// Registrierung
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 als mTLS Reverse Proxy

server {
    listen 443 ssl;
    server_name api.example.com;
 
    # Server-Zertifikat
    ssl_certificate     /etc/nginx/ssl/server-chain.pem;
    ssl_certificate_key /etc/nginx/ssl/server.key.pem;
 
    # Client-Zertifikats-Authentifizierung
    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 / {
        # Client-Zertifikat-Info an Backend weitergeben
        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;
    }
}

Branchenspezifische mTLS-Anforderungen

Branche Zertifikatslaufzeit Revocation Besonderheit
Kubernetes 1 Jahr CRL SPIFFE/SPIRE
Finanzsektor 90 Tage OCSP PCI-DSS compliant
Healthcare 1 Jahr CRL + OCSP DiGAV-konform
IoT/SCADA 5 Jahre CRL Offline-Fähigkeit

Verwandte Szenarien

Beziehung Szenario Beschreibung
Voraussetzung 3.2 Client-Zertifikat Client-Cert erstellen
Verwandt 9.2 Smartcard Login Hardware-Token
Verwandt 10.3 mTLS Deployment TLS-Konfiguration

« ← Authentifizierung-Übersicht | ↑ Szenarien | 9.2 Smartcard Login → »


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