Skip to main content

Managing Trust Infrastructure

This guide covers how to set up and manage trust infrastructure for a wallet deployment. Trust infrastructure is the foundation that enables issuers, verifiers, and wallets to establish and verify trust relationships.

Prerequisites

Before setting up trust infrastructure, ensure you understand:

Choosing a Trust Framework​

Two primary trust frameworks are commonly used in credential ecosystems, with a third option emerging:

FrameworkBest ForComplexityStandards
ETSI TSL (X.509)EU/eIDAS compliance, government deploymentsMediumETSI TS 119 612
ETSI LoTE (JSON)Modern JSON-native ecosystems, JWK/DID identitiesLow–MediumETSI TS 119 602
OpenID FederationDynamic ecosystems, OAuth/OIDC integrationHigherOpenID Federation 1.0

You can also use multiple frameworks simultaneously with Go-Trust acting as the unifying abstraction layer.


X.509 / ETSI Trust Status Lists​

ETSI Trust Status Lists (TSLs) provide a standardized way to publish and consume trust information based on X.509 certificates. This approach is mandated by eIDAS and the EU Digital Identity framework.

Architecture Overview​

Components Required​

  1. Certificate Authority (CA) – Issues certificates to ecosystem participants
  2. Trust Status List (TSL) – XML document listing trusted services and their certificates
  3. TSL Signing Key – Private key used to sign the TSL (typically an HSM)
  4. Distribution Point – Web server or CDN to publish the TSL

Setting Up a Certificate Authority​

For production, use an established CA or set up a proper PKI. For development/testing:

# Generate CA private key
openssl ecparam -genkey -name prime256v1 -out ca-key.pem

# Generate CA certificate
openssl req -new -x509 -key ca-key.pem -out ca-cert.pem -days 365 \
-subj "/CN=My Trust Anchor CA/O=Example Org/C=SE"

# Issue a certificate for an issuer
openssl ecparam -genkey -name prime256v1 -out issuer-key.pem
openssl req -new -key issuer-key.pem -out issuer.csr \
-subj "/CN=issuer.example.com/O=Example Issuer/C=SE"
openssl x509 -req -in issuer.csr -CA ca-cert.pem -CAkey ca-key.pem \
-CAcreateserial -out issuer-cert.pem -days 365

Generating Trust Status Lists with tsl-tool​

The g119612 project provides tsl-tool, a command-line tool for generating and processing ETSI TS 119 612 Trust Status Lists.

Installation​

# Clone and build
git clone https://github.com/sirosfoundation/g119612.git
cd g119612
make build

# The binary is at ./tsl-tool

Creating a TSL Pipeline​

The generate pipeline step creates a TSL from a directory of YAML metadata and certificate files:

tsl-source/
├── scheme.yaml # TSL scheme metadata
└── providers/ # One subdirectory per trust service provider
└── my-provider/
├── provider.yaml # Provider metadata
├── cert1.pem # X.509 certificate (PEM)
└── cert1.yaml # Service metadata for cert1.pem

scheme.yaml:

operatorNames:
- language: en
value: "Trust List Operator"
type: "http://uri.etsi.org/TrstSvc/TrustedList/TSLType/EUgeneric"
sequenceNumber: 1 # Optional, defaults to 1
id: "MY-TSL-001" # Optional, defaults to "TSL-NNN"

provider.yaml:

names:
- language: en
value: "Example Trust Service Provider"
address: # Optional
postal:
streetAddress: "Example Street 123"
locality: "Example City"
postalCode: "12345"
countryName: "SE"
electronic:
- "https://example.com"
- "mailto:contact@example.com"
tradeName: # Optional
- language: en
value: "Example Corp"
informationURI: # Optional
- language: en
value: "https://example.com/info"

cert.yaml (must match a .pem file by base name, e.g. cert1.yaml ↔ cert1.pem):

serviceNames:
- language: en
value: "Example Signing Service"
serviceType: "http://uri.etsi.org/TrstSvc/Svctype/CA/QC"
status: "http://uri.etsi.org/TrstSvc/TrustedList/Svcstatus/granted"
serviceDigitalId: # Optional additional digital IDs
digitalIds:
- "base64-encoded-cert..."

Pipeline configuration:

- generate:
- /path/to/tsl-source
- publish:
- /var/www/html/tsl
- /path/to/signing-cert.pem
- /path/to/signing-key.pem

Or with PKCS#11/HSM signing:

- generate:
- /path/to/tsl-source
- publish:
- /var/www/html/tsl
- "pkcs11:module=/usr/lib/softhsm/libsofthsm2.so;pin=1234;token=trust-lists"
- signing-key
- signing-cert
- "01"

To also produce a LoTE JSON conversion alongside the XML:

- generate:
- /path/to/tsl-source
- publish:
- /var/www/html/tsl
- /path/to/signing-cert.pem
- /path/to/signing-key.pem
- convert-to-lote: []
- publish-lote:
- /var/www/html/tsl
- /path/to/signing-cert.pem
- /path/to/signing-key.pem
- xml
# Run the pipeline
./tsl-tool --log-level info pipeline.yaml

Processing Existing TSLs​

You can also use tsl-tool to fetch, transform, and republish existing TSLs:

# process-eu-tsl.yaml

# Configure HTTP client
- set-fetch-options:
- user-agent: TSL-Tool/1.0
- timeout: 60s

# Load the EU List of Trusted Lists
- load:
- https://ec.europa.eu/tools/lotl/eu-lotl.xml

# Follow references to member state TSLs
- select:
- reference-depth: 2

# Generate HTML documentation
- transform:
- embedded: tsl-to-html.xslt
- /var/www/html/trust-lists
- html

# Create an index page
- generate_index:
- /var/www/html/trust-lists
- "EU Trust Lists"

Publishing Your TSL​

  1. Host on a reliable endpoint – Use HTTPS with a valid certificate
  2. Enable caching – TSLs change infrequently; set appropriate cache headers
  3. Consider a CDN – For high-availability deployments
  4. Set up monitoring – Alert on expiring TSLs or certificates
# Example nginx configuration
location /trust-list.xml {
root /var/www/html/tsl;
add_header Cache-Control "public, max-age=3600";
add_header Content-Type "application/xml";
}

Configuring Go-Trust to Use Your TSL​

# go-trust config.yaml
registries:
etsi:
enabled: true
name: "ETSI-TSL"
description: "European Trust Status List"
cert_bundle: "/etc/go-trust/trusted-certs.pem"
# Or load from TSL URL (requires allow_network_access: true)
# tsl_urls:
# - "https://tsl.example.org/trust-list.xml"

Role-to-Service-Type Mapping​

When vc and go-wallet-backend make trust evaluation requests, they send an action.name derived from an application-level role like issuer or verifier and, where known, the credential type or mdoc doctype. Go-Trust uses policies to map these roles to ETSI service types.

How Role Mapping Works​

Standard Roles​

note

The action name is what a policy key must match. If no policy matches, Go-Trust uses default_policy, or, with policies.fail_closed_on_unknown_action: true, denies the request. See Action Names.

The vc services derive these action names:

Action nameDescriptionTypical ETSI Service Types
pid-providerPID (Person ID) issuerCA/QC (with PID constraints)
credential-issuerOpenID4VCI issuer, with a credential typeCA/QC
credential-verifierOpenID4VP verifierEDS/Q, TSA/QTST
mdl-issuer, mdoc-issuermdoc issuer (mDL docType / other docType)mDOC IACA, VICAL
mdl-verifier, mdoc-verifiermdoc verifier / readerRICAL
issuer, verifierBare role, when no credential type or docType is knownas configured
wallet_providerWallet unit attestationCA/QC
status-list-signerSigner of a Token Status List (go-wallet-backend)whitelist or CA/QC
emrtd-document-signerePassport Document Signer CertificateeMRTD CSCA anchors

When OpenID4VP verifiers send trust evaluation requests, the subject.id may use the client_id_scheme prefix format (e.g. x509_san_dns:verifier.example.com). Go-Trust automatically normalizes these to standard HTTPS URLs (https://verifier.example.com) before registry matching. See Subject ID Normalization for the full mapping.

:::

Configuring Role-Based Policies​

Define policies in Go-Trust to map roles to registry-specific constraints:

# go-trust config.yaml
policies:
# Default policy when no role matches
default_policy: credential-verifier

policies:
# Policy for credential issuers
credential-issuer:
description: "Validates credential issuers against qualified certificates"
etsi:
service_types:
- "http://uri.etsi.org/TrstSvc/Svctype/CA/QC"
service_statuses:
- "http://uri.etsi.org/TrstSvc/TrustedList/Svcstatus/granted"
oidfed:
entity_types:
- "openid_credential_issuer"
required_trust_marks:
- "https://dc4eu.eu/tm/issuer"
did:
allowed_domains:
- "*.eudiw.dev"
- "*.example.com"
require_verifiable_history: true

# Policy for PID providers (stricter)
pid-provider:
description: "Validates PID providers with qualified certificate requirements"
etsi:
service_types:
- "http://uri.etsi.org/TrstSvc/Svctype/CA/QC"
countries:
- "DE"
- "FR"
- "SE"

# Policy for verifiers
credential-verifier:
description: "Validates relying parties"
etsi:
service_types:
- "http://uri.etsi.org/TrstSvc/Svctype/EDS/Q"
- "http://uri.etsi.org/TrstSvc/Svctype/TSA/QTST"
service_statuses:
- "http://uri.etsi.org/TrstSvc/TrustedList/Svcstatus/granted"
oidfed:
entity_types:
- "openid_relying_party"
required_trust_marks:
- "https://dc4eu.eu/tm/verifier"
did:
allowed_domains:
- "*.eudiw.dev"
- "*.example.com"

# Policy for mDL issuers
mdl-issuer:
description: "Validates mDL/mDOC issuers via IACA"
mdociaca:
issuer_allowlist:
- "https://pid-issuer.eudiw.dev"
- "https://mdl-issuer.example.com"
require_iaca_endpoint: true
registries:
- "mdoc-iaca"

ETSI Service Type Reference​

Common ETSI TS 119 612 service types:

URIDescription
http://uri.etsi.org/TrstSvc/Svctype/CA/QCCA issuing qualified certificates
http://uri.etsi.org/TrstSvc/Svctype/EDS/QQualified electronic delivery service
http://uri.etsi.org/TrstSvc/Svctype/TSA/QTSTQualified timestamp authority
http://uri.etsi.org/TrstSvc/Svctype/TSA/TSS-QCTimestamp for qualified certificates
http://uri.etsi.org/TrstSvc/Svctype/PSES/QQualified preservation service

Client-Side Usage​

Applications don't need to know about ETSI service types. The vc services send the evaluation request with the role (and credential type or doctype), and the request's action.name selects the policy. For example, a raw request:

curl -X POST http://go-trust:6001/evaluation \
-H "Content-Type: application/json" \
-d '{
"subject": {"type": "key", "id": "https://issuer.example.com"},
"resource": {"type": "x5c", "id": "https://issuer.example.com", "key": ["MIIC..."]},
"action": {"name": "credential-issuer"}
}'

The Go-Trust server maps the action name to the appropriate policy and ETSI constraints.


OpenID Federation​

OpenID Federation provides dynamic, decentralized trust management where entities publish their own metadata and trust relationships are established through trust chains.

Architecture Overview​

Key Concepts​

TermDescription
Trust AnchorRoot of trust; publishes entity configuration and subordinate statements
IntermediateOptional organizational unit; can issue subordinate statements
Leaf EntityEnd entity (issuer, verifier, wallet) with metadata
Entity ConfigurationSelf-signed JWT describing an entity's metadata
Subordinate StatementJWT from superior entity attesting to a subordinate
Trust ChainChain of statements from leaf to trust anchor
Trust MarkAttestation that an entity meets certain criteria

Components Required​

  1. Trust Anchor Service – Hosts federation endpoints and issues subordinate statements
  2. Entity Registration System – Manages onboarding of participants
  3. Key Management – Secure storage for signing keys
  4. Federation Endpoints – Well-known endpoints for metadata discovery

Running a Federation with Inmor​

Inmor is an open-source OpenID Federation implementation that can be used to run trust anchor and intermediate entity services.

Installation​

# Clone the repository
git clone https://github.com/SUNET/inmor.git
cd inmor

# Follow the installation instructions in the README
# Typically involves:
# - Setting up a Python environment
# - Configuring the database
# - Setting up signing keys

Basic Configuration​

Inmor requires configuration for:

  1. Entity ID – The URL identifier for your trust anchor
  2. Signing Keys – Keys for signing entity configurations and subordinate statements
  3. Storage – Database for managing subordinates and trust marks
  4. Federation Policy – Rules for what metadata policies to apply

Federation Endpoints​

An OpenID Federation entity publishes its Entity Configuration at /.well-known/openid-federation. Trust anchors and intermediates additionally expose federation endpoints (fetch, list, resolve, trust mark status). Their URLs are advertised in the federation_entity metadata of the entity configuration (for example federation_fetch_endpoint), and their paths depend on the implementation. Consult the Inmor documentation for the paths your deployment serves rather than hard-coding them.

Registering Entities​

To add an entity (issuer, verifier, wallet) to your federation:

  1. Entity provides their Entity Configuration – A self-signed JWT with their metadata
  2. Verify entity identity – Out-of-band verification of ownership
  3. Issue Subordinate Statement – Sign a statement attesting to the entity
  4. Entity publishes their configuration – At their /.well-known/openid-federation

Configuring Go-Trust for OpenID Federation​

# go-trust config.yaml
registries:
oidfed:
enabled: true
trust_anchors:
- entity_id: "https://trust-anchor.example.org"
# Optional: pin the trust anchor's keys (a JWKS document as a JSON
# string, not a URL)
# jwks: '{"keys":[...]}'
cache_ttl: "5m"
max_chain_depth: 5

Combining Trust Frameworks​

For production deployments, you often need to support multiple trust frameworks simultaneously. Registries are a map keyed by registry type; enable each one you need:

# go-trust config.yaml with multiple frameworks
registries:
# ETSI TSL for EU compliance
etsi:
enabled: true
allow_network_access: true
tsl_urls:
- "https://ec.europa.eu/tools/lotl/eu-lotl.xml"
refresh_interval: "6h"

# OpenID Federation for dynamic trust
oidfed:
enabled: true
trust_anchors:
- entity_id: "https://federation.example.org"

# Whitelist for known partners
whitelist:
enabled: true
config_file: "/config/trusted-entities.yaml"

# How answers from several registries are combined (default: first_match)
strategy: first_match

policies:
policies:
# Route by key type: x5c to the ETSI TSL, JWKs to federation and whitelist
credential-issuer:
constraints:
allowed_key_types: ["x5c"]
registries: ["ETSI-TSL"]
wallet_provider:
constraints:
allowed_key_types: ["jwk"]
registries: ["oidfed-registry", "whitelist"]

Policies refer to registries by their reported name; see Default registry names.


Operational Considerations​

Certificate/Key Lifecycle​

AssetTypical ValidityRenewal Strategy
CA Root Certificate10-20 yearsPlan succession well in advance
Intermediate CA5-10 yearsRotate before expiry
TSL Signing Key2-5 yearsHSM-protected, ceremony for rotation
Entity Certificates1-2 yearsAutomated renewal (ACME)
Federation Signing Keys1-2 yearsKey rollover with overlap period

Monitoring and Alerting​

  • Certificate expiry – Alert 30, 14, 7 days before expiry
  • TSL validity – Monitor nextUpdate field
  • Federation endpoint availability – Health checks on well-known endpoints
  • Trust chain resolution failures – Log and alert on resolution errors

High Availability​

Disaster Recovery​

  1. Backup signing keys – Secure, offline backup with ceremony for recovery
  2. TSL snapshots – Keep historical versions
  3. Federation database backups – Regular backups of subordinate registrations
  4. Documented recovery procedures – Test periodically

Next Steps​