ETSI Trust Lists
The IDK supports two ETSI trust list standards for validating trust service providers within the European Union and beyond. Trust lists publish structured information about which organizations are authorized to provide trust services such as issuing qualified certificates, timestamps, and electronic seals.
Standards Overview
The IDK implements two complementary ETSI standards:
| Standard | Format | Description |
|---|---|---|
| ETSI TS 119 612 | XML (TSL) | Trust Service Status Lists, the original XML-based format used by EU member states |
| ETSI TS 119 602 | JSON (LoTE) | List of Trusted Entities, a newer JSON-based format for publishing trusted entity information |
Both formats describe the same conceptual model: a hierarchy of trust lists that enumerate authorized trust service providers, their services, and the current status of each service. The ETSITrustListResolver loads either format and normalizes it into the ETSILoTE domain model.
Trust List Hierarchy
The European Commission publishes a List of Trusted Lists (LOTL), a master index that points to each EU member state's individual trusted list. Each member state list enumerates the qualified trust service providers (QTSPs) operating under that country's supervision.
The well-known URL for the EU LOTL is:
https://ec.europa.eu/tools/lotl/eu-lotl.xml
Architecture
The ETSI trust integration consists of two main components:
- ETSITrustValidator: a trust validation service that extends
AbstractTrustValidationService("etsi_tsl")and is contributed into the session scope via@ContributesIntoSet(SessionScope::class). It usesTrustContext.TYPE_ETSI_TSLto identify itself within the trust framework. - ETSITrustListResolver: loads and parses trust lists from HTTP/HTTPS URIs. For XML-format lists (TS 119 612), it verifies XML signatures before processing. Returns the parsed data as an
ETSILoTEdomain model regardless of the source format.
Loading Trust Lists
The ResolveEtsiTrustListCommand (command ID: trust.etsi.resolve) loads trust lists from a URI and optionally resolves the full LOTL hierarchy.
- Android/Kotlin
- iOS/Swift
val trustService = session.graph.etsiTrustValidator
// Resolve the EU LOTL and all member state lists
val result = trustService.resolve(
trustListUri = "https://ec.europa.eu/tools/lotl/eu-lotl.xml",
resolveLotl = true,
territories = listOf("NL", "DE", "FR"), // Optional: filter by country
verifySignatures = true
)
println("Trust lists loaded: ${result.trustListCount}")
println("Territories covered: ${result.territories}")
println("Total services found: ${result.totalServices}")
let trustService = session.graph.etsiTrustValidator
// Resolve the EU LOTL and all member state lists
let result = try await trustService.resolve(
trustListUri: "https://ec.europa.eu/tools/lotl/eu-lotl.xml",
resolveLotl: true,
territories: ["NL", "DE", "FR"], // Optional: filter by country
verifySignatures: true
)
print("Trust lists loaded: \(result.trustListCount)")
print("Territories covered: \(result.territories)")
print("Total services found: \(result.totalServices)")
The ResolveEtsiTrustListResult returned by the resolve operation contains:
| Field | Type | Description |
|---|---|---|
trustListCount | Int | Number of trust lists loaded |
territories | List<String> | ISO 3166-1 alpha-2 country codes covered |
totalServices | Int | Total number of trust services across all lists |
When resolveLotl is true, the resolver follows the LOTL pointers to download each member state list. When false, only the single list at trustListUri is loaded. The optional territories filter limits which member state lists are fetched, which reduces load time when you only need a subset of countries.
Validating Against Trust Lists
The ValidateEtsiTrustCommand (command ID: trust.etsi.validate) checks whether a given identifier is present in a loaded trust list and whether the associated service has an acceptable status.
- Android/Kotlin
- iOS/Swift
val trustService = session.graph.etsiTrustValidator
// Validate an identifier against ETSI trust lists
val validationResult = trustService.validate(
trustListUri = "https://ec.europa.eu/tools/lotl/eu-lotl.xml",
identifierJson = identifierJsonString,
checkRevocation = true,
territory = "DE" // Optional: restrict to a specific territory
)
if (validationResult.trusted) {
println("Entity is trusted")
println("Provider: ${validationResult.providerName}")
println("Service status: ${validationResult.serviceStatus}")
} else {
println("Entity not found in trust lists")
}
let trustService = session.graph.etsiTrustValidator
// Validate an identifier against ETSI trust lists
let validationResult = try await trustService.validate(
trustListUri: "https://ec.europa.eu/tools/lotl/eu-lotl.xml",
identifierJson: identifierJsonString,
checkRevocation: true,
territory: "DE" // Optional: restrict to a specific territory
)
if validationResult.trusted {
print("Entity is trusted")
print("Provider: \(validationResult.providerName)")
print("Service status: \(validationResult.serviceStatus)")
} else {
print("Entity not found in trust lists")
}
Identifier Resolution Integration
The ETSI trust module integrates with the IDK's identifier resolution system through two identifier methods:
| Identifier Method | Purpose |
|---|---|
ETSI_TSL | Resolve identifiers against ETSI Trust Service Status Lists |
ETSI_TSP | Resolve identifiers for specific Trust Service Providers |
These methods use ExternalIdentifierETSITslOpts for input configuration and return ExternalIdentifierETSITslResult with the matched trust information. This allows ETSI trust lookups to be composed with other identifier resolution strategies in a uniform way.
Service Status Values
Each trust service in a list carries a status value indicating whether it is currently authorized to operate. The IDK recognizes the following ETSI-defined status values:
| Status | Meaning |
|---|---|
GRANTED | The service is currently authorized and active. This is the primary "good" status. |
WITHDRAWN | The service authorization has been permanently withdrawn by the supervisory body. |
DEPRECATED | The service is deprecated. It may still be technically valid but is no longer recommended. |
RECOGNISED_NATIONAL_LEVEL | The service is recognized at the national level but does not hold EU-wide qualified status. |
REVOKED | The service has been revoked due to a compliance or security issue. |
SUSPENDED | The service is temporarily suspended pending investigation or remediation. |
When validating, you should generally treat GRANTED as trusted. Whether to accept RECOGNISED_NATIONAL_LEVEL depends on your application's requirements; it indicates national-level trust without full EU qualification.
- Android/Kotlin
- iOS/Swift
// Check the status of a resolved service
when (service.status) {
ServiceStatus.GRANTED -> {
// Fully authorized - safe to trust
}
ServiceStatus.RECOGNISED_NATIONAL_LEVEL -> {
// National trust only - decide based on your policy
}
ServiceStatus.WITHDRAWN,
ServiceStatus.REVOKED,
ServiceStatus.SUSPENDED -> {
// Do not trust
}
ServiceStatus.DEPRECATED -> {
// Valid but discouraged - check validity period
}
}
// Check the status of a resolved service
switch service.status {
case .granted:
// Fully authorized - safe to trust
break
case .recognisedNationalLevel:
// National trust only - decide based on your policy
break
case .withdrawn, .revoked, .suspended:
// Do not trust
break
case .deprecated:
// Valid but discouraged - check validity period
break
}
XML Signature Verification
Trust lists published in the ETSI TS 119 612 XML format are digitally signed by the scheme operator. The IDK verifies these signatures automatically during loading to prevent tampering.
The XmlSignatureVerifier interface (implemented by XmlUtilSignatureVerifier) supports two signature formats:
| Format | Standard | Description |
|---|---|---|
| XAdES | ETSI TS 101 903 | XML Advanced Electronic Signatures, the format used by most EU trust lists |
| XmlDSig | W3C XML Signature | Base XML Digital Signature format |
Signature verification produces an XmlSignatureVerificationResult:
| Field | Type | Description |
|---|---|---|
valid | Boolean | Whether the signature is cryptographically valid |
signaturePresent | Boolean | Whether a signature was found in the document |
signingTime | Instant? | When the document was signed, if available |
xadesProperties | XadesProperties? | Additional XAdES properties such as signing certificate info |
Signature verification is enabled by default. You can disable it for development or testing by setting verifySignatures = false in the resolve call, but this should never be done in production.
Configuration
Configure ETSI trust list behavior through the EtsiTrustConfig properties:
# Enable ETSI trust list support (disabled by default)
trust.anchors.etsi.enabled=false
# URL for the EU List of Trusted Lists
trust.anchors.etsi.lotl-url=https://ec.europa.eu/tools/lotl/eu-lotl.xml
# Verify XML signatures on trust lists (recommended: true)
trust.anchors.etsi.verify-signatures=true
# Optional: restrict to specific territories (comma-separated ISO 3166-1 alpha-2 codes)
trust.anchors.etsi.territories=NL,DE,FR
Caching
Trust lists are large XML documents that change infrequently (typically updated every 6 months by member states). The IDK caches loaded trust lists with a default TTL of 60 minutes. After the TTL expires, the next access triggers a background reload.
This means the first load incurs network latency, but subsequent validations within the TTL window are served from the in-memory cache without any HTTP requests.
Module Dependencies
To use ETSI trust list functionality, include the trust module in your project:
- Android/Kotlin
- iOS/Swift
// build.gradle.kts
dependencies {
implementation("com.sphereon.idk:lib-trust-etsi:<version>")
}
The ETSI trust module is included in the main IDK Swift package. No additional dependency is needed.
The ETSI trust module depends on:
lib-trust-api: core trust validation interfaces (AbstractTrustValidationService,TrustContext)lib-crypto-core-public/lib-crypto-core-impl: cryptographic primitives for signature verificationlib-http-client: HTTP client for downloading trust lists from remote URIs