~~NOTOC~~
====== Scenario 9.3: Integrazione SSO ======
**Categoria:** [[.:start|Autenticazione]] \\
**Complessita:** ⭐⭐⭐⭐ (Alta) \\
**Prerequisiti:** Identity Provider, Certificati client \\
**Tempo stimato:** 30-45 minuti
----
===== Descrizione =====
Questo scenario descrive l'**integrazione dell'autenticazione basata su certificati nei sistemi Single Sign-On (SSO)**. I certificati possono essere utilizzati come fattore di autenticazione primario o aggiuntivo.
**Protocolli supportati:**
* **SAML 2.0** - X.509 Certificate Holder Assertion
* **OIDC** - mTLS Client Credentials
* **WS-Federation** - X.509 Token
* **Kerberos PKINIT** - Certificate-to-Kerberos
----
===== Workflow: OIDC con mTLS =====
sequenceDiagram
participant User
participant App
participant IdP as Identity Provider
participant API
User->>App: Accesso
App->>IdP: Authorization Request
IdP->>User: Richiedere certificato client
User->>IdP: Presentare certificato
IdP->>IdP: Validare certificato
IdP->>App: ID Token + Access Token
App->>API: Request + Access Token
API->>App: Response
----
===== Esempio di codice: OIDC con autenticazione certificato =====
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication.Certificate;
var builder = WebApplication.CreateBuilder(args);
// Autenticazione certificato per comunicazione 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"; // Oppure certificato client
// mTLS per Token Endpoint
options.BackchannelHttpHandler = CreateMtlsHandler();
options.ResponseType = "code";
options.SaveTokens = true;
// Mapping claim certificato
options.ClaimActions.MapJsonKey("certificate_thumbprint", "x5t");
options.ClaimActions.MapJsonKey("certificate_subject", "x5t#S256");
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = context =>
{
// Validazione aggiuntiva
var cert = context.HttpContext.Connection.ClientCertificate;
if (cert != null)
{
// Collegare certificato al token
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;
}
----
===== Esempio di codice: SAML con X.509 Holder-of-Key =====
using ComponentSpace.Saml2;
public class SamlCertificateAuthService
{
private readonly ISamlServiceProvider _samlServiceProvider;
public async Task AuthenticateWithCertificate(
HttpContext httpContext,
X509Certificate2 clientCert)
{
// Certificato come Holder-of-Key Subject Confirmation
var samlResponse = await _samlServiceProvider.ReceiveSSOAsync();
// Verificare Subject Confirmation Method
if (samlResponse.SubjectConfirmationMethod != "urn:oasis:names:tc:SAML:2.0:cm:holder-of-key")
{
throw new SecurityException("Richiesta Holder-of-Key Confirmation");
}
// Confrontare certificato dall'asserzione SAML con quello presentato
var assertionCert = ExtractCertificateFromAssertion(samlResponse);
if (assertionCert.Thumbprint != clientCert.Thumbprint)
{
throw new SecurityException("Il certificato non corrisponde all'asserzione");
}
return samlResponse.ClaimsPrincipal;
}
private X509Certificate2 ExtractCertificateFromAssertion(SamlResponse response)
{
// Estrarre X.509 Certificate da SubjectConfirmationData
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; // es. "LDAP://dc.example.com"
}
public DirectoryEntry FindUserByCertificate(X509Certificate2 cert)
{
using var searcher = new DirectorySearcher(new DirectoryEntry(_ldapPath));
// Opzione 1: UPN da SAN
var upn = GetUpnFromCertificate(cert);
if (upn != null)
{
searcher.Filter = $"(userPrincipalName={upn})";
return searcher.FindOne()?.GetDirectoryEntry();
}
// Opzione 2: altSecurityIdentities (Mapping esplicito)
var issuerSerial = $"X509:{cert.Issuer}{cert.SerialNumber}";
searcher.Filter = $"(altSecurityIdentities={issuerSerial})";
var result = searcher.FindOne();
if (result != null) return result.GetDirectoryEntry();
// Opzione 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("Tipo di mapping sconosciuto")
};
var altSecIds = user.Properties["altSecurityIdentities"];
altSecIds.Add(mapping);
user.CommitChanges();
Console.WriteLine($"Mapping creato: {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 per UPN...
return null;
}
}
public enum CertMappingType
{
IssuerSerial,
SubjectIssuer,
SubjectOnly,
IssuerSubjectHash
}
----
===== Azure AD Certificate-Based Auth =====
// Azure AD App Registration per 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) // Certificato client invece di 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 con autenticazione certificato
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"
}
}
----
===== Scenari SSO specifici per settore =====
^ Settore ^ IdP ^ Protocollo ^ Requisiti certificato ^
| **Enterprise** | Azure AD / Okta | OIDC | UPN nel SAN |
| **Government** | ADFS / PIV | SAML HoK | PIV Auth Cert |
| **Healthcare** | gematik IdP | OIDC | HBA/SMC-B |
| **Finanza** | PKI propria | SAML | Qualificato |
----
===== Scenari correlati =====
^ Relazione ^ Scenario ^ Descrizione ^
| **Prerequisito** | [[.:mtls_client_auth|9.1 mTLS Client-Auth]] | Livello TLS |
| **Correlato** | [[.:smartcard_login|9.2 Smartcard Login]] | Auth hardware |
| **Correlato** | [[it:int:pqcrypt:szenarien:zertifikate:client_cert|3.2 Certificato client]] | Creare cert |
----
<< [[.:smartcard_login|← 9.2 Smartcard Login]] | [[.:start|↑ Panoramica autenticazione]] | [[it:int:pqcrypt:szenarien:start|→ Tutti gli scenari]] >>
{{tag>scenario autenticazione sso saml oidc azure-ad}}
----
//Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional//