~~NOTOC~~ ====== Scenarij 9.1: mTLS klijentska autentifikacija ====== **Kategorija:** [[.:start|Autentifikacija]] \\ **Složenost:** ⭐⭐⭐⭐ (Visoka) \\ **Preduvjeti:** Klijentski i serverski certifikati \\ **Procijenjeno vrijeme:** 20-30 minuta ---- ===== Opis ===== Ovaj scenarij opisuje **Mutual TLS (mTLS) autentifikaciju**, gdje se i server i klijent međusobno autentificiraju certifikatima. **Prednosti u odnosu na autentifikaciju lozinkom:** * **Jača** - Kriptografski utemeljena * **Automatizirana** - Bez interakcije korisnika * **Opoziva** - Centralna mogućnost opoziva * **Zero-Trust** - Svaki zahtjev je autentificiran ---- ===== Tijek rada ===== sequenceDiagram participant Client as Klijent participant Server Client->>Server: ClientHello Server->>Client: ServerHello + ServerCert Server->>Client: CertificateRequest Client->>Server: ClientCert + CertificateVerify Server->>Server: Validacija klijentskog certifikata Server->>Client: Finished Client->>Server: Finished Note over Client,Server: mTLS veza uspostavljena ---- ===== Primjer koda: mTLS Server (ASP.NET Core) ===== using Microsoft.AspNetCore.Authentication.Certificate; using Microsoft.AspNetCore.Server.Kestrel.Https; var builder = WebApplication.CreateBuilder(args); // Konfiguracija Kestrel za mTLS builder.WebHost.ConfigureKestrel(options => { options.ConfigureHttpsDefaults(https => { // Učitavanje serverskog certifikata https.ServerCertificate = new X509Certificate2( "server.pfx", "ServerPassword!"); // Zahtijevanje klijentskog certifikata https.ClientCertificateMode = ClientCertificateMode.RequireCertificate; // Validacija klijentskog certifikata https.ClientCertificateValidation = (cert, chain, errors) => { // Prilagođena validacija if (errors != SslPolicyErrors.None) { Console.WriteLine($"SSL Policy greške: {errors}"); return false; } // Provjera vlastite 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); }; }); }); // Dodavanje Certificate Authentication builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme) .AddCertificate(options => { options.AllowedCertificateTypes = CertificateTypes.Chained; options.RevocationMode = X509RevocationMode.Online; options.Events = new CertificateAuthenticationEvents { OnCertificateValidated = context => { // Ekstrakcija claims iz certifikata 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($"Autentifikacija neuspješna: {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 = "Autentificirano", subject }); }); app.Run(); ---- ===== Primjer koda: mTLS Klijent (HttpClient) ===== using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ; using var ctx = PqCryptoContext.Initialize(); // Učitavanje klijentskog certifikata i ključa var clientCert = ctx.LoadCertificateWithPrivateKey( "client.crt.pem", "client.key.pem", "ClientPassword!" ); // HttpClientHandler s klijentskim certifikatom var handler = new HttpClientHandler { ClientCertificateOptions = ClientCertificateOption.Manual, ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { // Validacija serverskog certifikata if (errors != SslPolicyErrors.None) { Console.WriteLine($"Greška serverskog certifikata: {errors}"); // U produkciji: return false; } return true; } }; // Dodavanje klijentskog certifikata handler.ClientCertificates.Add(clientCert); using var httpClient = new HttpClient(handler); // Slanje zahtjeva var response = await httpClient.GetAsync("https://api.example.com/api/protected"); if (response.IsSuccessStatusCode) { var content = await response.Content.ReadAsStringAsync(); Console.WriteLine($"Odgovor: {content}"); } else { Console.WriteLine($"Greška: {response.StatusCode}"); } ---- ===== Autorizacija temeljena na certifikatu ===== public class CertificateAuthorizationHandler : AuthorizationHandler { protected override Task HandleRequirementAsync( AuthorizationHandlerContext context, CertificateRequirement requirement) { // Dohvaćanje certifikata iz Principal var thumbprint = context.User.FindFirst("certificate_thumbprint")?.Value; var subject = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(thumbprint)) { return Task.CompletedTask; } // Provjera je li certifikat autoriziran 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 AllowedThumbprints { get; set; } = new(); public List AllowedSubjectPatterns { get; set; } = new(); } // Registracija builder.Services.AddAuthorization(options => { options.AddPolicy("ServiceAccounts", policy => policy.Requirements.Add(new CertificateRequirement { AllowedSubjectPatterns = new List { @"^CN=.*\.svc\.cluster\.local$", // Kubernetes servisi @"^CN=service-[a-z]+$" // Imenovani servisi } })); }); builder.Services.AddSingleton(); ---- ===== Nginx kao mTLS Reverse Proxy ===== server { listen 443 ssl; server_name api.example.com; # Serverski certifikat ssl_certificate /etc/nginx/ssl/server-chain.pem; ssl_certificate_key /etc/nginx/ssl/server.key.pem; # Autentifikacija klijentskim certifikatom 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 / { # Proslijeđivanje informacija o klijentskom certifikatu backendu 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; } } ---- ===== Zahtjevi mTLS specifični za industriju ===== ^ Industrija ^ Trajanje certifikata ^ Opoziv ^ Posebnost ^ | **Kubernetes** | 1 godina | CRL | SPIFFE/SPIRE | | **Financijski sektor** | 90 dana | OCSP | PCI-DSS usklađenost | | **Zdravstvo** | 1 godina | CRL + OCSP | DiGAV usklađenost | | **IoT/SCADA** | 5 godina | CRL | Offline mogućnost | ---- ===== Povezani scenariji ===== ^ Povezanost ^ Scenarij ^ Opis ^ | **Preduvjet** | [[hr:int:pqcrypt:szenarien:zertifikate:client_cert|3.2 Klijentski certifikat]] | Kreiranje klijentskog certifikata | | **Povezano** | [[.:smartcard_login|9.2 Smartcard prijava]] | Hardverski token | | **Povezano** | [[hr:int:pqcrypt:szenarien:tls:mtls_deployment|10.3 mTLS Deployment]] | TLS konfiguracija | ---- << [[.:start|← Pregled autentifikacije]] | [[hr:int:pqcrypt:szenarien:start|↑ Scenariji]] | [[.:smartcard_login|9.2 Smartcard prijava →]] >> {{tag>scenarij autentifikacija mtls klijentski-certifikat zero-trust}} ---- //Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional//