~~NOTOC~~
====== Szenario 9.3: SSO Integration ======
**Kategorie:** [[.:start|Authentifizierung]] \\
**Komplexität:** ⭐⭐⭐⭐ (Hoch) \\
**Voraussetzungen:** Identity Provider, Client-Zertifikate \\
**Geschätzte Zeit:** 30-45 Minuten
----
===== Beschreibung =====
Dieses Szenario beschreibt die **Integration zertifikatsbasierter Authentifizierung in Single Sign-On (SSO) Systeme**. Zertifikate können als primärer oder zusätzlicher Authentifizierungsfaktor verwendet werden.
**Unterstützte Protokolle:**
* **SAML 2.0** - X.509 Certificate Holder Assertion
* **OIDC** - mTLS Client Credentials
* **WS-Federation** - X.509 Token
* **Kerberos PKINIT** - Certificate-to-Kerberos
----
===== Workflow: OIDC mit mTLS =====
sequenceDiagram
participant User
participant App
participant IdP as Identity Provider
participant API
User->>App: Zugriff
App->>IdP: Authorization Request
IdP->>User: Client-Zertifikat anfordern
User->>IdP: Zertifikat präsentieren
IdP->>IdP: Zertifikat validieren
IdP->>App: ID Token + Access Token
App->>API: Request + Access Token
API->>App: Response
----
===== Code-Beispiel: OIDC mit Zertifikats-Auth =====
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication.Certificate;
var builder = WebApplication.CreateBuilder(args);
// Zertifikats-Authentifizierung für IdP-Kommunikation
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"; // Oder Client-Cert
// mTLS für Token Endpoint
options.BackchannelHttpHandler = CreateMtlsHandler();
options.ResponseType = "code";
options.SaveTokens = true;
// Zertifikats-Claim mapping
options.ClaimActions.MapJsonKey("certificate_thumbprint", "x5t");
options.ClaimActions.MapJsonKey("certificate_subject", "x5t#S256");
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = context =>
{
// Zusätzliche Validierung
var cert = context.HttpContext.Connection.ClientCertificate;
if (cert != null)
{
// Zertifikat mit Token verknüpfen
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;
}
----
===== Code-Beispiel: SAML mit X.509 Holder-of-Key =====
using ComponentSpace.Saml2;
public class SamlCertificateAuthService
{
private readonly ISamlServiceProvider _samlServiceProvider;
public async Task AuthenticateWithCertificate(
HttpContext httpContext,
X509Certificate2 clientCert)
{
// Zertifikat als Holder-of-Key Subject Confirmation
var samlResponse = await _samlServiceProvider.ReceiveSSOAsync();
// Subject Confirmation Method prüfen
if (samlResponse.SubjectConfirmationMethod != "urn:oasis:names:tc:SAML:2.0:cm:holder-of-key")
{
throw new SecurityException("Holder-of-Key Confirmation erforderlich");
}
// Zertifikat aus SAML-Assertion mit präsentiertem vergleichen
var assertionCert = ExtractCertificateFromAssertion(samlResponse);
if (assertionCert.Thumbprint != clientCert.Thumbprint)
{
throw new SecurityException("Zertifikat stimmt nicht mit Assertion überein");
}
return samlResponse.ClaimsPrincipal;
}
private X509Certificate2 ExtractCertificateFromAssertion(SamlResponse response)
{
// X.509 Certificate aus SubjectConfirmationData extrahieren
var keyInfoXml = response.SubjectConfirmationDataKeyInfo;
// Parse X509Data from KeyInfo
// ...
return null;
}
}
----
===== Active Directory Certificate Mapping =====
public class AdCertificateMappingService
{
private readonly string _ldapPath;
public AdCertificateMappingService(string ldapPath)
{
_ldapPath = ldapPath; // z.B. "LDAP://dc.example.com"
}
public DirectoryEntry FindUserByCertificate(X509Certificate2 cert)
{
using var searcher = new DirectorySearcher(new DirectoryEntry(_ldapPath));
// Option 1: UPN aus SAN
var upn = GetUpnFromCertificate(cert);
if (upn != null)
{
searcher.Filter = $"(userPrincipalName={upn})";
return searcher.FindOne()?.GetDirectoryEntry();
}
// Option 2: altSecurityIdentities (Explicit Mapping)
var issuerSerial = $"X509:{cert.Issuer}{cert.SerialNumber}";
searcher.Filter = $"(altSecurityIdentities={issuerSerial})";
var result = searcher.FindOne();
if (result != null) return result.GetDirectoryEntry();
// Option 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("Unbekannter Mapping-Typ")
};
var altSecIds = user.Properties["altSecurityIdentities"];
altSecIds.Add(mapping);
user.CommitChanges();
Console.WriteLine($"Mapping erstellt: {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;
// Parse SAN für UPN...
return null;
}
}
public enum CertMappingType
{
IssuerSerial,
SubjectIssuer,
SubjectOnly,
IssuerSubjectHash
}
----
===== Azure AD Certificate-Based Auth =====
// Azure AD App Registration für 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) // Client-Zertifikat statt Secret
.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 mit Zertifikats-Auth
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"
}
}
----
===== Branchenspezifische SSO-Szenarien =====
^ Branche ^ IdP ^ Protokoll ^ Zertifikatsanforderung ^
| **Enterprise** | Azure AD / Okta | OIDC | UPN im SAN |
| **Government** | ADFS / PIV | SAML HoK | PIV Auth Cert |
| **Healthcare** | gematik IdP | OIDC | HBA/SMC-B |
| **Finanz** | Eigene PKI | SAML | Qualifiziert |
----
===== Verwandte Szenarien =====
^ Beziehung ^ Szenario ^ Beschreibung ^
| **Voraussetzung** | [[.:mtls_client_auth|9.1 mTLS Client-Auth]] | TLS-Ebene |
| **Verwandt** | [[.:smartcard_login|9.2 Smartcard Login]] | Hardware-Auth |
| **Verwandt** | [[de:int:pqcrypt:szenarien:zertifikate:client_cert|3.2 Client-Zertifikat]] | Cert erstellen |
----
<< [[.:smartcard_login|← 9.2 Smartcard Login]] | [[.:start|↑ Authentifizierung-Übersicht]] | [[de:int:pqcrypt:szenarien:start|→ Alle Szenarien]] >>
{{tag>szenario authentifizierung sso saml oidc azure-ad}}
----
//Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional//