Trust Establishment
The IDK provides a unified trust establishment framework that validates trust across multiple protocols: ETSI trust lists, X.509 PKI, OpenID Federation, and Decentralized Identifiers. Each protocol is implemented as a TrustValidationService that plugs into the framework through dependency injection. Application code uses a single API regardless of the underlying trust mechanism.
Trust validation checks whether:
- The issuer of this credential is authorized to issue it
- The certificate chain is valid and rooted in a recognized trust anchor
- The credential or certificate has not been revoked
- The issuer meets the regulatory requirements of the target framework
Supported Trust Frameworks
The framework dispatches validation requests based on the TrustContext.type field. Each context type maps to a dedicated TrustValidationService implementation contributed via @ContributesIntoSet(SessionScope::class):
| Context Type | Constant | Module | Description |
|---|---|---|---|
TYPE_ETSI_TSL | etsi_tsl | lib-trust-etsi | ETSI TS 119 612/602 trust service status lists |
TYPE_X509 | x509 | lib-trust-x509 | X.509 certificate chain and CA bundle validation |
TYPE_CA_BUNDLE | ca_bundle | lib-trust-x509 | CA bundle file/URL-based validation |
TYPE_OPENID_FEDERATION | openid_federation | openid-federation-trust | OpenID Federation trust chain resolution |
TYPE_DID | did | lib-trust-did | DID-based trust with method allow-lists |
TYPE_PUBLIC_KEY | public_key | lib-trust-core | Direct public key trust |
The framework selects the correct implementation by calling supports(context) on each registered service. You can enable multiple frameworks simultaneously and the system will route each request to the appropriate validator.
Validating Trust
All trust validation flows through the TrustValidationService.validate() method. You construct a TrustValidationRequest that pairs an identifier with a TrustContext, and the service returns a TrustValidationResult describing the outcome.
Building a Request
A TrustValidationRequest contains:
identifier: TheIdentifierOptsOrResultto validate (a certificate, DID, or other identifier).context: ATrustContextspecifying which framework to use and any framework-specific parameters.validationTime: OptionalInstantto validate against a specific point in time (defaults to now).checkRevocation: Whether to perform revocation checking (defaults totrue).
Handling the Result
The TrustValidationResult contains the validation outcome:
trusted:trueif the identifier is trusted under the given context.status: ATrustStatusenum:TRUSTED,UNTRUSTED,REVOKED,EXPIRED,NOT_YET_VALID,VALIDATION_ERROR, orUNKNOWN.trustAnchor: TheTrustAnchorthat was matched, if any.validationPath: The chain of identifiers from the subject to the trust anchor.details: A human-readable explanation of the validation outcome.validatedAt: TheInstantwhen validation was performed.
- Android/Kotlin
- iOS/Swift
val trustService = session.graph.trustValidationService
// Build a trust context for X.509 validation
val context = TrustContext(
type = TrustContext.TYPE_X509
)
// Create the validation request
val request = TrustValidationRequest(
identifier = certificateIdentifier,
context = context,
checkRevocation = true
)
// Validate and handle the result
val result = trustService.validate(request)
when (result.status) {
TrustStatus.TRUSTED -> {
println("Trusted via anchor: ${result.trustAnchor?.name}")
println("Validation path: ${result.validationPath}")
}
TrustStatus.REVOKED -> {
println("Certificate has been revoked")
}
TrustStatus.EXPIRED -> {
println("Certificate has expired")
}
TrustStatus.UNTRUSTED -> {
println("No matching trust anchor found")
}
TrustStatus.NOT_YET_VALID -> {
println("Certificate is not yet valid")
}
TrustStatus.VALIDATION_ERROR -> {
println("Validation failed: ${result.details}")
}
TrustStatus.UNKNOWN -> {
println("Trust status could not be determined")
}
}
let trustService = session.graph.trustValidationService
let context = TrustContext(
type: TrustContext.TYPE_X509
)
let request = TrustValidationRequest(
identifier: certificateIdentifier,
context: context,
checkRevocation: true
)
let result = try await trustService.validate(request: request)
if result.trusted {
print("Trusted via anchor: \(result.trustAnchor?.name ?? "unknown")")
} else {
print("Validation failed with status: \(result.status)")
}
Choosing a Trust Framework
The TrustContext constructor accepts optional framework and parameters fields for framework-specific behavior:
// ETSI trust list validation with territory filter
val etsiContext = TrustContext(
type = TrustContext.TYPE_ETSI_TSL,
framework = "etsi",
parameters = mapOf("territory" to "DE")
)
// OpenID Federation with a specific trust anchor
val oidfContext = TrustContext(
type = TrustContext.TYPE_OPENID_FEDERATION,
parameters = mapOf("trustAnchor" to "https://federation.example.com")
)
// DID trust limited to specific methods
val didContext = TrustContext(
type = TrustContext.TYPE_DID,
parameters = mapOf("allowedMethods" to "web,key")
)
Using Commands
The framework also exposes trust operations as commands for integration with the IDK command infrastructure:
| Command | ID | Description |
|---|---|---|
ValidateTrustCommand | trust.validation.validate | Validate trust for an identifier |
GetTrustAnchorsCommand | trust.anchors.list | List trust anchors for a context |
RefreshTrustAnchorsCommand | trust.anchors.refresh | Refresh cached trust anchors |
CheckRevocationCommand | trust.revocation.check | Check revocation status of a certificate |
Trust Anchors
A TrustAnchor represents a root of trust in the system. Each trust framework resolves anchors differently. X.509 uses root CA certificates, ETSI uses Trust Service Providers from published trust lists, OpenID Federation uses federation entity statements, and DID-based trust uses trusted DID documents.
Every TrustAnchor contains:
id: A unique identifier for the anchor.type: One ofTYPE_ETSI_TSP,TYPE_ROOT_CA,TYPE_DID_DOCUMENT,TYPE_OPENID_FED_ENTITY, orTYPE_PUBLIC_KEY.name: A human-readable name.keyInfo: The anchor's public key as aResolvedKeyInfoType.uri: An optional URI for the anchor (e.g., the TSL URL or DID).metadata: Framework-specific metadata as key-value pairs.validFrom/validUntil: Optional validity period.
Retrieving Anchors
Use getTrustAnchors() on a TrustValidationService or the GetTrustAnchorsCommand to list the currently loaded trust anchors:
- Android/Kotlin
- iOS/Swift
val trustService = session.graph.trustValidationService
// Get all trust anchors for the service
val anchors = trustService.getTrustAnchors()
for (anchor in anchors) {
println("Anchor: ${anchor.name} (${anchor.type})")
println(" ID: ${anchor.id}")
anchor.uri?.let { println(" URI: $it") }
anchor.validUntil?.let { println(" Valid until: $it") }
}
let trustService = session.graph.trustValidationService
let anchors = try await trustService.getTrustAnchors()
for anchor in anchors {
print("Anchor: \(anchor.name) (\(anchor.type))")
print(" ID: \(anchor.id)")
if let uri = anchor.uri {
print(" URI: \(uri)")
}
}
Refreshing Anchors
Trust anchors can become stale as trust lists are updated or certificates rotate. Call refresh() to force a reload from the upstream source:
val refreshed = trustService.refresh()
if (refreshed) {
println("Trust anchors refreshed successfully")
}
The cache TTLs configured in TrustCacheConfig control how often automatic refreshes occur in the background.
Revocation Checking
The framework checks whether certificates have been revoked before establishing trust. The RevocationChecker interface handles this through a composite strategy: it attempts OCSP first, then falls back to CRL if OCSP is unavailable or fails.
How It Works
When checkRevocation is true in a TrustValidationRequest, the validator delegates to a CompositeRevocationChecker that:
- Attempts an OCSP check if
trust.revocation.check-ocspis enabled. - Falls back to CRL checking if OCSP is unavailable and
trust.revocation.check-crlis enabled. - Returns a
RevocationCheckResultwith aRevocationStatusand theRevocationCheckMethodused.
The RevocationStatus enum indicates the outcome:
| Status | Meaning |
|---|---|
GOOD | Certificate is not revoked |
REVOKED | Certificate has been revoked |
UNKNOWN | Revocation status could not be determined |
UNAVAILABLE | Revocation checking endpoints are unreachable |
The RevocationCheckMethod enum indicates which mechanism was used:
| Method | Description |
|---|---|
OCSP | Online Certificate Status Protocol |
CRL | Certificate Revocation List |
OCSP_STAPLING | OCSP response stapled to the TLS handshake |
NONE | No revocation check was performed |
MULTIPLE | Multiple methods were attempted |
Explicit Revocation Checks
You can also check revocation directly using the CheckRevocationCommand or the RevocationChecker interface:
- Android/Kotlin
- iOS/Swift
val revocationChecker = session.graph.revocationChecker
val result = revocationChecker.checkRevocation(
certificate = certificateBytes,
issuerCertificate = issuerCertBytes,
options = RevocationCheckOptions()
)
when (result.status) {
RevocationStatus.GOOD -> println("Certificate is valid")
RevocationStatus.REVOKED -> println("Certificate is revoked: ${result.reason}")
RevocationStatus.UNKNOWN -> println("Revocation status unknown")
RevocationStatus.UNAVAILABLE -> println("Revocation check unavailable")
}
let revocationChecker = session.graph.revocationChecker
let result = try await revocationChecker.checkRevocation(
certificate: certificateBytes,
issuerCertificate: issuerCertBytes,
options: RevocationCheckOptions()
)
if result.status == .good {
print("Certificate is valid")
} else {
print("Revocation status: \(result.status)")
}
Configuration
Trust validation is configured through properties that map to the TrustConfig data class hierarchy. Each section controls a different aspect of the framework.
Validation Settings
# Enable/disable trust validation globally
trust.validation.enabled=true
# Check revocation by default when not specified per-request
trust.validation.default-check-revocation=true
Trust Anchor Sources
Configure which trust frameworks are active and where their anchors come from:
# X.509 CA bundle trust
trust.anchors.x509.enabled=true
trust.anchors.x509.ca-bundle-paths=/path/to/ca-bundle.pem
trust.anchors.x509.trusted-fingerprints=SHA256:...
# ETSI trust lists (EU List of Trusted Lists)
trust.anchors.etsi.enabled=true
trust.anchors.etsi.lotl-url=https://ec.europa.eu/tools/lotl/eu-lotl.xml
# OpenID Federation trust anchors
trust.anchors.oidfed.enabled=true
trust.anchors.oidfed.trust-anchors=https://federation.example.com
# DID-based trust
trust.anchors.did.enabled=true
trust.anchors.did.allowed-methods=web,key
Revocation Settings
# OCSP and CRL configuration
trust.revocation.check-ocsp=true
trust.revocation.check-crl=true
trust.revocation.prefer-ocsp=true
trust.revocation.timeout-ms=5000
Cache TTLs
The framework caches trust lists and revocation responses to avoid repeated network calls. Configure TTLs to balance freshness against performance:
# How long to cache trust lists (ETSI, X.509 bundles)
trust.cache.trust-list-ttl-minutes=60
# How long to cache revocation responses (OCSP, CRL)
trust.cache.revocation-ttl-minutes=15
# How long to cache OpenID Federation entity statements
trust.cache.oidfed-entity-ttl-minutes=30
Full Configuration Reference
The properties above map to the following TrustConfig structure:
TrustConfig
validation: TrustValidationConfig
enabled: Boolean
defaultCheckRevocation: Boolean
anchors: TrustAnchorsConfig
x509: X509TrustConfig
enabled, caBundlePaths, caBundleUrls, trustedFingerprints, maxFailedSources
etsi: EtsiTrustConfig
enabled, lotlUrl, verifySignatures, territories
oidfed: OidfTrustConfig
enabled, trustAnchors, maxChainDepth, requiredTrustMarks
did: DidTrustConfig
enabled, trustedDids, allowedMethods
revocation: RevocationConfig
enabled, checkOcsp, checkCrl, preferOcsp, timeoutMs
cache: TrustCacheConfig
trustListTtlMinutes (default 60)
revocationTtlMinutes (default 15)
oidfedEntityTtlMinutes (default 30)
Next Steps
- ETSI Trust Lists: Work with European trust service status lists
- Certificate Validation: X.509 certificate chain validation and CA bundles