Scenarij 9.3: SSO integracija

Kategorija: 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<ClaimsPrincipal> 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:<I>{cert.Issuer}<S>{cert.SerialNumber}";
        searcher.Filter = $"(altSecurityIdentities={issuerSerial})";
        var result = searcher.FindOne();
        if (result != null) return result.GetDirectoryEntry();
 
        // Možnost 3: Subject + Issuer
        var subjectIssuer = $"X509:<S>{cert.Subject}<I>{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:<I>{cert.Issuer}<SR>{cert.SerialNumber}",
            CertMappingType.SubjectIssuer =>
                $"X509:<S>{cert.Subject}<I>{cert.Issuer}",
            CertMappingType.SubjectOnly =>
                $"X509:<S>{cert.Subject}",
            CertMappingType.IssuerSubjectHash =>
                $"X509:<SHA1-PUKEY>{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<AuthenticationResult> AcquireTokenForClient(string[] scopes)
    {
        return await _app.AcquireTokenForClient(scopes).ExecuteAsync();
    }
 
    public async Task<AuthenticationResult> 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 9.1 mTLS Client-Auth TLS-raven
Povezano 9.2 Prijava s pametno kartico Strojna overitev
Povezano 3.2 Certifikat odjemalca Ustvarjanje certifikata

« 9.2 Prijava s pametno kartico | Pregled overitve | Vsi scenariji »


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

Zuletzt geändert: dne 30.01.2026 ob 01:36