~~NOTOC~~
====== Scenarij 9.3: SSO integracija ======
**Kategorija:** [[.:start|Overitev]] \\
**Kompleksnost:** 4/5 (Visoka) \\
**Predpogoji:** Identity Provider, certifikati odjemalcev \\
**Ocenjeni čas:** 30-45 minut
----
===== Opis =====
Ta scenarij opisuje **integracijo overitve na osnovi certifikatov v sisteme enotne prijave (SSO)**. Certifikati se lahko uporabljajo kot primarni ali dodaten faktor overitve.
**Podprti protokoli:**
* **SAML 2.0** - X.509 Certificate Holder Assertion
* **OIDC** - mTLS Client Credentials
* **WS-Federation** - X.509 Token
* **Kerberos PKINIT** - Certificate-to-Kerberos
----
===== Potek dela: OIDC z mTLS =====
sequenceDiagram
participant User as Uporabnik
participant App as Aplikacija
participant IdP as Identity Provider
participant API
User->>App: Dostop
App->>IdP: Authorization Request
IdP->>User: Zahteva certifikat odjemalca
User->>IdP: Predložitev certifikata
IdP->>IdP: Validacija certifikata
IdP->>App: ID Token + Access Token
App->>API: Zahteva + Access Token
API->>App: Odgovor
----
===== Primer kode: OIDC z overitvijo s certifikatom =====
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication.Certificate;
var builder = WebApplication.CreateBuilder(args);
// Overitev s certifikatom za komunikacijo z IdP
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie("Cookies")
.AddOpenIdConnect(options =>
{
options.Authority = "https://idp.example.com";
options.ClientId = "my-app";
options.ClientSecret = "secret"; // Ali certifikat odjemalca
// mTLS za Token Endpoint
options.BackchannelHttpHandler = CreateMtlsHandler();
options.ResponseType = "code";
options.SaveTokens = true;
// Preslikava claims certifikata
options.ClaimActions.MapJsonKey("certificate_thumbprint", "x5t");
options.ClaimActions.MapJsonKey("certificate_subject", "x5t#S256");
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = context =>
{
// Dodatna validacija
var cert = context.HttpContext.Connection.ClientCertificate;
if (cert != null)
{
// Povezava certifikata z žetonom
var identity = context.Principal.Identity as ClaimsIdentity;
identity?.AddClaim(new Claim("client_cert_subject", cert.Subject));
}
return Task.CompletedTask;
}
};
});
static HttpClientHandler CreateMtlsHandler()
{
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(
new X509Certificate2("client.pfx", "Password!")
);
return handler;
}
----
===== Primer kode: SAML z X.509 Holder-of-Key =====
using ComponentSpace.Saml2;
public class SamlCertificateAuthService
{
private readonly ISamlServiceProvider _samlServiceProvider;
public async Task AuthenticateWithCertificate(
HttpContext httpContext,
X509Certificate2 clientCert)
{
// Certifikat kot Holder-of-Key Subject Confirmation
var samlResponse = await _samlServiceProvider.ReceiveSSOAsync();
// Preverjanje Subject Confirmation Method
if (samlResponse.SubjectConfirmationMethod != "urn:oasis:names:tc:SAML:2.0:cm:holder-of-key")
{
throw new SecurityException("Zahtevana Holder-of-Key Confirmation");
}
// Primerjava certifikata iz SAML-Assertion s predloženim
var assertionCert = ExtractCertificateFromAssertion(samlResponse);
if (assertionCert.Thumbprint != clientCert.Thumbprint)
{
throw new SecurityException("Certifikat se ne ujema z Assertion");
}
return samlResponse.ClaimsPrincipal;
}
private X509Certificate2 ExtractCertificateFromAssertion(SamlResponse response)
{
// Ekstrakcija X.509 certifikata iz SubjectConfirmationData
var keyInfoXml = response.SubjectConfirmationDataKeyInfo;
// Razčlenjevanje X509Data iz KeyInfo
// ...
return null;
}
}
----
===== Active Directory preslikava certifikatov =====
public class AdCertificateMappingService
{
private readonly string _ldapPath;
public AdCertificateMappingService(string ldapPath)
{
_ldapPath = ldapPath; // npr. "LDAP://dc.example.com"
}
public DirectoryEntry FindUserByCertificate(X509Certificate2 cert)
{
using var searcher = new DirectorySearcher(new DirectoryEntry(_ldapPath));
// Možnost 1: UPN iz SAN
var upn = GetUpnFromCertificate(cert);
if (upn != null)
{
searcher.Filter = $"(userPrincipalName={upn})";
return searcher.FindOne()?.GetDirectoryEntry();
}
// Možnost 2: altSecurityIdentities (Eksplicitna preslikava)
var issuerSerial = $"X509:{cert.Issuer}{cert.SerialNumber}";
searcher.Filter = $"(altSecurityIdentities={issuerSerial})";
var result = searcher.FindOne();
if (result != null) return result.GetDirectoryEntry();
// Možnost 3: Subject + Issuer
var subjectIssuer = $"X509:{cert.Subject}{cert.Issuer}";
searcher.Filter = $"(altSecurityIdentities={subjectIssuer})";
return searcher.FindOne()?.GetDirectoryEntry();
}
public void MapCertificateToUser(string userDn, X509Certificate2 cert, CertMappingType type)
{
using var user = new DirectoryEntry($"{_ldapPath}/{userDn}");
string mapping = type switch
{
CertMappingType.IssuerSerial =>
$"X509:{cert.Issuer}{cert.SerialNumber}",
CertMappingType.SubjectIssuer =>
$"X509:{cert.Subject}{cert.Issuer}",
CertMappingType.SubjectOnly =>
$"X509:{cert.Subject}",
CertMappingType.IssuerSubjectHash =>
$"X509:{cert.GetCertHashString()}",
_ => throw new ArgumentException("Neznan tip preslikave")
};
var altSecIds = user.Properties["altSecurityIdentities"];
altSecIds.Add(mapping);
user.CommitChanges();
Console.WriteLine($"Preslikava ustvarjena: {mapping}");
}
private string GetUpnFromCertificate(X509Certificate2 cert)
{
// UPN OID: 1.3.6.1.4.1.311.20.2.3
var sanExt = cert.Extensions["2.5.29.17"];
if (sanExt == null) return null;
// Razčlenjevanje SAN za UPN...
return null;
}
}
public enum CertMappingType
{
IssuerSerial,
SubjectIssuer,
SubjectOnly,
IssuerSubjectHash
}
----
===== Azure AD overitev na osnovi certifikatov =====
// Azure AD App Registration za CBA
public class AzureAdCbaService
{
private readonly IConfidentialClientApplication _app;
public AzureAdCbaService(string tenantId, string clientId, X509Certificate2 clientCert)
{
_app = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithAuthority($"https://login.microsoftonline.com/{tenantId}")
.WithCertificate(clientCert) // Certifikat odjemalca namesto skrivnosti
.Build();
}
public async Task AcquireTokenForClient(string[] scopes)
{
return await _app.AcquireTokenForClient(scopes).ExecuteAsync();
}
public async Task AcquireTokenOnBehalfOf(
string[] scopes,
string userToken)
{
var userAssertion = new UserAssertion(userToken);
return await _app.AcquireTokenOnBehalfOf(scopes, userAssertion).ExecuteAsync();
}
}
// Azure AD B2C z overitvijo s certifikatom
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
----
===== Keycloak X.509 Authenticator =====
{
"alias": "x509-auth",
"providerId": "auth-x509-client-username-form",
"enabled": true,
"config": {
"x509-cert-auth.mapping-source-selection": "Subject's Common Name",
"x509-cert-auth.regular-expression": "CN=(.*?)(?:,|$)",
"x509-cert-auth.user-mapping-method": "Username or Email",
"x509-cert-auth.crl-checking-enabled": "true",
"x509-cert-auth.ocsp-checking-enabled": "true",
"x509-cert-auth.ocsp-responder-uri": "http://ocsp.example.com",
"x509-cert-auth.timestamp-validation-enabled": "true",
"x509-cert-auth.keyusage": "digitalSignature",
"x509-cert-auth.extendedkeyusage": "id-kp-clientAuth"
}
}
----
===== Panožno-specifični SSO scenariji =====
^ Panoga ^ IdP ^ Protokol ^ Zahteva za certifikat ^
| **Podjetje** | Azure AD / Okta | OIDC | UPN v SAN |
| **Vlada** | ADFS / PIV | SAML HoK | PIV Auth Cert |
| **Zdravstvo** | gematik IdP | OIDC | HBA/SMC-B |
| **Finance** | Lastna PKI | SAML | Kvalificiran |
----
===== Povezani scenariji =====
^ Povezava ^ Scenarij ^ Opis ^
| **Predpogoj** | [[.:mtls_client_auth|9.1 mTLS Client-Auth]] | TLS-raven |
| **Povezano** | [[.:smartcard_login|9.2 Prijava s pametno kartico]] | Strojna overitev |
| **Povezano** | [[sl:int:pqcrypt:szenarien:zertifikate:client_cert|3.2 Certifikat odjemalca]] | Ustvarjanje certifikata |
----
<< [[.:smartcard_login|9.2 Prijava s pametno kartico]] | [[.:start|Pregled overitve]] | [[sl:int:pqcrypt:szenarien:start|Vsi scenariji]] >>
{{tag>scenarij overitev sso saml oidc azure-ad}}
----
//Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional//