~~NOTOC~~
====== Scenarij 9.1: mTLS overitev odjemalca ======
**Kategorija:** [[.:start|Overitev]] \\
**Kompleksnost:** 4/5 (Visoka) \\
**Predpogoji:** Certifikati odjemalca in strežnika \\
**Ocenjeni čas:** 20-30 minut
----
===== Opis =====
Ta scenarij opisuje **Mutual TLS (mTLS) overitev**, pri kateri se tako strežnik kot odjemalec medsebojno overita s certifikati.
**Prednosti pred overitvijo z geslom:**
* **Močnejša** - Kriptografsko osnovana
* **Avtomatizirana** - Brez uporabniške interakcije
* **Preklicljiva** - Centralna možnost preklica
* **Zero-Trust** - Vsaka zahteva je overjena
----
===== Potek dela =====
sequenceDiagram
participant Client as Odjemalec
participant Server as Strežnik
Client->>Server: ClientHello
Server->>Client: ServerHello + ServerCert
Server->>Client: CertificateRequest
Client->>Server: ClientCert + CertificateVerify
Server->>Server: Validacija certifikata odjemalca
Server->>Client: Finished
Client->>Server: Finished
Note over Client,Server: mTLS-povezava vzpostavljena
----
===== Primer kode: mTLS strežnik (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 =>
{
// Nalaganje certifikata strežnika
https.ServerCertificate = new X509Certificate2(
"server.pfx", "ServerPassword!");
// Zahtevanje certifikata odjemalca
https.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
// Validacija certifikata odjemalca
https.ClientCertificateValidation = (cert, chain, errors) =>
{
// Prilagojena validacija
if (errors != SslPolicyErrors.None)
{
Console.WriteLine($"SSL Policy Errors: {errors}");
return false;
}
// Preverjanje lastne 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);
};
});
});
// Dodajanje overitve s certifikatom
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($"Overitev spodletela: {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 = "Overjeno", subject });
});
app.Run();
----
===== Primer kode: mTLS odjemalec (HttpClient) =====
using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
using var ctx = PqCryptoContext.Initialize();
// Nalaganje certifikata in ključa odjemalca
var clientCert = ctx.LoadCertificateWithPrivateKey(
"client.crt.pem",
"client.key.pem",
"ClientPassword!"
);
// HttpClientHandler s certifikatom odjemalca
var handler = new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
// Validacija certifikata strežnika
if (errors != SslPolicyErrors.None)
{
Console.WriteLine($"Napaka certifikata strežnika: {errors}");
// V produkciji: return false;
}
return true;
}
};
// Dodajanje certifikata odjemalca
handler.ClientCertificates.Add(clientCert);
using var httpClient = new HttpClient(handler);
// Pošiljanje zahteve
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($"Napaka: {response.StatusCode}");
}
----
===== Avtorizacija na osnovi certifikatov =====
public class CertificateAuthorizationHandler : AuthorizationHandler
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
CertificateRequirement requirement)
{
// Pridobitev 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;
}
// Preverjanje ali je certifikat avtoriziran
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 storitve
@"^CN=service-[a-z]+$" // Poimenovane storitve
}
}));
});
builder.Services.AddSingleton();
----
===== Nginx kot mTLS Reverse Proxy =====
server {
listen 443 ssl;
server_name api.example.com;
# Certifikat strežnika
ssl_certificate /etc/nginx/ssl/server-chain.pem;
ssl_certificate_key /etc/nginx/ssl/server.key.pem;
# Overitev s certifikatom odjemalca
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 / {
# Posredovanje info o certifikatu odjemalca zaledju
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;
}
}
----
===== Panožno-specifične mTLS zahteve =====
^ Panoga ^ Veljavnost certifikata ^ Preklic ^ Posebnost ^
| **Kubernetes** | 1 leto | CRL | SPIFFE/SPIRE |
| **Finančni sektor** | 90 dni | OCSP | PCI-DSS skladen |
| **Zdravstvo** | 1 leto | CRL + OCSP | DiGAV-skladen |
| **IoT/SCADA** | 5 let | CRL | Offline-zmožnost |
----
===== Povezani scenariji =====
^ Povezava ^ Scenarij ^ Opis ^
| **Predpogoj** | [[sl:int:pqcrypt:szenarien:zertifikate:client_cert|3.2 Certifikat odjemalca]] | Ustvarjanje Client-Cert |
| **Povezano** | [[.:smartcard_login|9.2 Prijava s pametno kartico]] | Strojni žeton |
| **Povezano** | [[sl:int:pqcrypt:szenarien:tls:mtls_deployment|10.3 mTLS Deployment]] | TLS-konfiguracija |
----
<< [[.:start|Pregled overitve]] | [[sl:int:pqcrypt:szenarien:start|Scenariji]] | [[.:smartcard_login|9.2 Prijava s pametno kartico]] >>
{{tag>scenarij overitev mtls certifikat-odjemalca zero-trust}}
----
//Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional//