Skip to main content
Version: v0.25.0 (Latest)

OpenID Federation Trust

The IDK supports OpenID Federation trust chain resolution and verification. OpenID Federation defines a hierarchical trust model where entities publish self-signed Entity Statements, and trust is established by resolving a chain of statements from an entity up to a recognized trust anchor.

This is particularly useful in ecosystems like the European Digital Identity Wallet (EUDI), where relying parties and credential issuers must prove membership in a federation before interactions can take place.

How It Works

The OidfTrustValidationService implements the TrustValidationService interface and is automatically contributed to the session scope via @ContributesIntoSet(SessionScope::class). It handles the TrustContext.TYPE_OPENID_FEDERATION trust type.

When you request trust validation for an entity, the service executes the following flow:

  1. Extract entity identifier. The entityIdentifier is read from the request context parameters. This is the OpenID Federation entity URL you want to validate (for example, https://wallet-provider.example.com).

  2. Resolve trust anchors. The configured trust anchors are loaded from OidfTrustConfig. Request-level overrides can be supplied to use different anchors per validation call.

  3. Check cache. The service checks the trust.oidfed.chains cache namespace. Cached results have a 30-minute TTL and are returned immediately when available.

  4. Resolve trust chain (ResolveTrustChainCommand). Starting from the entity's well-known endpoint, the service fetches Entity Statements and Subordinate Statements, building a JWT chain from the target entity up to the trust anchor.

  5. Verify trust chain (VerifyTrustChainCommand). Each JWT in the chain is cryptographically validated. Signatures are verified, expiration is checked, and issuer/subject constraints are enforced.

  6. Verify trust marks (VerifyTrustMarkCommand, optional). If required trust marks are configured, the service validates that the entity holds the specified trust marks and that those marks are issued by a recognized trust mark issuer.

  7. Cache and return. The TrustValidationResult is cached and returned to the caller.

Usage

Access the trust validation service from the session graph and call validate() with a TrustContext of type TYPE_OPENID_FEDERATION:

val trustValidationService = session.graph.trustValidationService

// Build a trust context for OpenID Federation
val context = TrustContext(
type = TrustContext.TYPE_OPENID_FEDERATION,
parameters = mapOf(
"entityIdentifier" to "https://wallet-provider.example.com"
)
)

val result = trustValidationService.validate(context)

when (result.status) {
TrustStatus.TRUSTED -> {
println("Entity is trusted")
println("Trust chain depth: ${result.validationPath?.size}")
println("Trust anchor: ${result.trustAnchor?.name}")
}
TrustStatus.UNTRUSTED -> {
println("Entity is not trusted: ${result.details}")
}
TrustStatus.VALIDATION_ERROR -> {
println("Validation error: ${result.details}")
}
else -> {
println("Status: ${result.status}, details: ${result.details}")
}
}

Overriding Trust Anchors Per Request

You can override the configured trust anchors on a per-request basis by passing them as a comma-separated list in the context parameters:

val context = TrustContext(
type = TrustContext.TYPE_OPENID_FEDERATION,
parameters = mapOf(
"entityIdentifier" to "https://issuer.example.com",
"trustAnchors" to "https://anchor1.example.com,https://anchor2.example.com",
"maxChainDepth" to "3"
)
)

val result = trustValidationService.validate(context)

Context Parameters

ParameterRequiredDescription
entityIdentifierYesThe OpenID Federation entity identifier (URL) to validate
trustAnchorsNoComma-separated list of trust anchor URLs; overrides the configured anchors
requiredTrustMarksNoComma-separated list of trust mark identifiers that the entity must hold
maxChainDepthNoMaximum number of intermediaries allowed in the chain (default: 5)

Trust Marks

Trust marks are signed assertions that an entity meets certain criteria defined by a trust mark issuer. They are optional but can be required through configuration or request parameters.

// Require specific trust marks during validation
val context = TrustContext(
type = TrustContext.TYPE_OPENID_FEDERATION,
parameters = mapOf(
"entityIdentifier" to "https://issuer.example.com",
"requiredTrustMarks" to "https://registry.example.com/marks/qualified-issuer,https://registry.example.com/marks/eudi-wallet"
)
)

val result = trustValidationService.validate(context)

if (result.trusted) {
// The entity holds all required trust marks
println("Trust anchor: ${result.trustAnchor?.name}")
println("Details: ${result.details}")
}

If any required trust mark is missing or fails verification, the result will have trusted = false with a status of TrustStatus.UNTRUSTED.

Using the Command Directly

You can also invoke the validation command directly via the command ID trust.oidfed.validate:

val commandManager = session.graph.commandManager

val result = commandManager.execute(
ValidateOidfTrustCommand(
entityIdentifier = "https://issuer.example.com",
trustAnchors = listOf("https://federation.example.com"),
requiredTrustMarks = listOf("https://registry.example.com/marks/qualified-issuer"),
maxChainDepth = 5
)
)

Configuration

Enable and configure OpenID Federation trust validation via properties:

# Enable OpenID Federation trust validation
trust.anchors.oidfed.enabled=false

# Trusted federation anchor URLs (comma-separated)
trust.anchors.oidfed.trust-anchors=https://federation.example.com

# Maximum depth of the trust chain (number of intermediaries)
trust.anchors.oidfed.max-chain-depth=5

# Required trust marks (comma-separated; empty means none required)
trust.anchors.oidfed.required-trust-marks=https://example.com/trust-mark

Caching

The service maintains two internal caches:

CacheNamespaceDefault TTLPurpose
Chain resultstrust.oidfed.chains30 minutesCaches resolved and verified trust chain outcomes
Trust anchor configstrust.oidfed.anchors30 minutesCaches fetched trust anchor Entity Statements

Caching avoids redundant network requests and cryptographic verification when validating the same entity multiple times within a short window.

Module Dependencies

The OpenID Federation trust module requires the openid-federation-client module on the classpath. Add the following to your build.gradle.kts:

dependencies {
implementation("com.sphereon.idk:lib-trust-oidfed:<version>")
implementation("com.sphereon.idk:openid-federation-client:<version>")
}

The OidfTrustValidationService is automatically discovered and registered when the module is present on the classpath. No additional wiring is needed beyond enabling the feature in configuration.