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:
-
Extract entity identifier. The
entityIdentifieris read from the request context parameters. This is the OpenID Federation entity URL you want to validate (for example,https://wallet-provider.example.com). -
Resolve trust anchors. The configured trust anchors are loaded from
OidfTrustConfig. Request-level overrides can be supplied to use different anchors per validation call. -
Check cache. The service checks the
trust.oidfed.chainscache namespace. Cached results have a 30-minute TTL and are returned immediately when available. -
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. -
Verify trust chain (
VerifyTrustChainCommand). Each JWT in the chain is cryptographically validated. Signatures are verified, expiration is checked, and issuer/subject constraints are enforced. -
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. -
Cache and return. The
TrustValidationResultis 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:
- Android/kotlin
- iOS/Swift
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}")
}
}
let trustValidationService = session.graph.trustValidationService
// Build a trust context for OpenID Federation
let context = TrustContext(
type: TrustContext.TYPE_OPENID_FEDERATION,
parameters: [
"entityIdentifier": "https://wallet-provider.example.com"
]
)
let result = try await trustValidationService.validate(context: context)
if result.trusted {
print("Entity is trusted")
print("Validation path: \(result.validationPath ?? [])")
print("Trust anchor: \(result.trustAnchor?.name ?? "unknown")")
} else if result.status == .validationError {
print("Validation error: \(result.details ?? "unknown error")")
} else {
print("Entity is not trusted: \(result.details ?? "no 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:
- Android/kotlin
- iOS/Swift
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)
let context = TrustContext(
type: TrustContext.TYPE_OPENID_FEDERATION,
parameters: [
"entityIdentifier": "https://issuer.example.com",
"trustAnchors": "https://anchor1.example.com,https://anchor2.example.com",
"maxChainDepth": "3"
]
)
let result = try await trustValidationService.validate(context: context)
Context Parameters
| Parameter | Required | Description |
|---|---|---|
entityIdentifier | Yes | The OpenID Federation entity identifier (URL) to validate |
trustAnchors | No | Comma-separated list of trust anchor URLs; overrides the configured anchors |
requiredTrustMarks | No | Comma-separated list of trust mark identifiers that the entity must hold |
maxChainDepth | No | Maximum 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.
- Android/kotlin
- iOS/Swift
// 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}")
}
// Require specific trust marks during validation
let context = TrustContext(
type: TrustContext.TYPE_OPENID_FEDERATION,
parameters: [
"entityIdentifier": "https://issuer.example.com",
"requiredTrustMarks": "https://registry.example.com/marks/qualified-issuer,https://registry.example.com/marks/eudi-wallet"
]
)
let result = try await trustValidationService.validate(context: context)
if result.trusted {
// The entity holds all required trust marks
print("Trust anchor: \(result.trustAnchor?.name ?? "unknown")")
print("Details: \(result.details ?? "none")")
}
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:
- Android/kotlin
- iOS/Swift
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
)
)
let commandManager = session.graph.commandManager
let result = try await commandManager.execute(
command: ValidateOidfTrustCommand(
entityIdentifier: "https://issuer.example.com",
trustAnchors: ["https://federation.example.com"],
requiredTrustMarks: ["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:
| Cache | Namespace | Default TTL | Purpose |
|---|---|---|---|
| Chain results | trust.oidfed.chains | 30 minutes | Caches resolved and verified trust chain outcomes |
| Trust anchor configs | trust.oidfed.anchors | 30 minutes | Caches 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.