Skip to content
sirosfoundationPublic

About

a "local" trust evaluation engine using AuthZen

Topics

Resources

Contributing

Security policy

Stars

4 stars

Watchers

2 watching

Forks

Repository files navigation

Go-Trust

CI Go Reference OpenSSF Scorecard Coverage Go Version GHCR License

Overview

Go-Trust is a multi-framework AuthZEN Trust Decision Point (PDP) server. It evaluates trust decisions across multiple trust registries including ETSI TS 119 612 Trust Status Lists (TSLs), ETSI TS 119 602 Lists of Trusted Entities (LoTEs), OpenID Federation, and DID Web.

Go-Trust is designed to run inside the same trust domain as the entity that relies on trust evaluation (for instance a wallet unit or an issuer). It promotes interoperability across implementations that rely on ETSI trust status lists such as the EUDI wallet.

Note: For TSL and LoTE processing (load, transform, convert, sign, publish), use tsl-tool from g119612. Go-trust consumes pre-processed TSL/LoTE data.

Features

  • AuthZEN Compliant: Implements the OASIS AuthZEN Trust Registry Profile for policy decisions
  • Multi-Registry Architecture: Parallel evaluation across ETSI TSL, ETSI LoTE, OpenID Federation, and DID Web
  • Flexible Resolution Strategies: First-match, all-registries, best-match, or sequential
  • Circuit Breaking: Graceful handling of registry failures
  • Rate Limiting: Per-IP rate limiting for API protection
  • Prometheus Metrics: Comprehensive observability support
  • Kubernetes Native: Liveness and readiness probes, ConfigMap support
  • Test Server: Embedded test server for integration testing
  • X5C Enrichment: Certificate policy OID validation and RP identity extraction
  • Swagger UI: Built-in API documentation at /swagger/
  • High Quality: >80% test coverage, comprehensive linting, security scanning

Quick Start

Installation

# Clone and build
git clone https://github.com/sirosfoundation/go-trust.git
cd go-trust
make build

# Or install directly
go install github.com/sirosfoundation/go-trust/cmd/gt@latest

Running the Server

# With a PEM certificate bundle (recommended for production)
gt --etsi-cert-bundle /path/to/trusted-certs.pem

# With TSL XML files directly
gt --etsi-tsl-files eu-lotl.xml,se-tsl.xml

# With whitelist registry (for simple deployments)
gt --registry whitelist --whitelist /etc/go-trust/whitelist.yaml

# With always-trusted registry (development/testing only!)
gt --registry always-trusted

# With configuration file
gt --config /etc/go-trust/config.yaml

# With external URL for discovery (behind reverse proxy)
gt --external-url https://pdp.example.com --etsi-cert-bundle certs.pem

# Full configuration
gt \
  --host 0.0.0.0 \
  --port 6001 \
  --etsi-cert-bundle /etc/go-trust/trusted-certs.pem \
  --external-url https://pdp.example.com \
  --log-level info \
  --log-format json

Command-Line Options

Option Description Default
--host Listen address 127.0.0.1
--port Listen port 6001
--external-url External URL for discovery auto-detect
--etsi-cert-bundle PEM file with trusted CA certs -
--etsi-tsl-files Comma-separated TSL XML files -
--registry Registry type: whitelist, always-trusted, never-trusted -
--whitelist Path to whitelist YAML/JSON config file -
--whitelist-watch Watch whitelist file for changes true
--log-level Log level: debug, info, warn, error info
--log-format Log format: text, json text
--config Configuration file (YAML) -

Environment variable: GT_EXTERNAL_URL for external URL.

API Endpoints

Endpoint Description
POST /evaluation AuthZEN trust evaluation
GET /.well-known/authzen-configuration PDP discovery document
GET /healthz Kubernetes liveness probe
GET /readyz Kubernetes readiness probe (503 until TSLs loaded)
GET /readyz?verbose=true Readiness with detailed TSL info
GET /metrics Prometheus metrics
GET /registries List configured trust registries
GET /swagger/* Swagger UI API documentation

AuthZEN Evaluation Request

{
  "subject": {
    "type": "key",
    "id": "https://issuer.example.com"
  },
  "resource": {
    "type": "x5c",
    "id": "https://issuer.example.com",
    "key": ["MIIDQjCCAiqgAwIBAgIUJlq+zz4..."]
  },
  "action": {
    "name": "credential-issuer"
  }
}

Action Parameters

The action.parameters field allows clients to include role-specific constraints in the request. A common use case is specifying which credential types are being evaluated:

{
  "subject": {"type": "key", "id": "https://issuer.example.com"},
  "resource": {"type": "x5c", "key": ["MIIC..."]},
  "action": {
    "name": "credential-issuer",
    "parameters": {
      "credential_types": ["eu.europa.ec.eudi.pid.1", "eu.europa.ec.eudi.mdl.1"]
    }
  }
}

When credential_types is provided, it enables:

  • Audit logging: The requested credential types are included in the requested_credential_types field of the response reason for all supporting registries (ETSI TSL, LoTE, OpenID Federation, whitelist)
  • Policy-based validation: For OpenID Federation, credential types can be mapped to required trust marks via credential_type_trust_marks (see Credential Type to Trust Mark Mapping)
  • Server-side filtering: For ETSI TSL, credential_types can be set in the policy constraints for server-configured audit (see Credential Types in ETSI TSL)

AuthZEN Evaluation Response

{
  "decision": true,
  "context": {
    "reason": {
      "registry": "etsi-tsl",
      "resolution_ms": 12,
      "service_type": "http://uri.etsi.org/TrstSvc/Svctype/CA/QC"
    }
  }
}

Architecture

Multi-Registry Design

Go-Trust implements a flexible multi-registry architecture that allows multiple trust frameworks to be queried simultaneously.

┌─────────────────┐     ┌─────────────────┐
│  AuthZEN Client │────▶│   Go-Trust PDP  │
└─────────────────┘     └────────┬────────┘
                                 │
    ┌────────┬──────┬────────────┼────────────┬──────────┬────────────┐
    ▼        ▼      ▼            ▼            ▼          ▼            ▼
┌──────┐┌──────┐┌─────────┐┌─────────┐┌───────────┐┌────────┐┌────────┐
│ ETSI ││ ETSI ││ OpenID  ││DID Web/ ││ DID JWKS/ ││ mDOC  ││  RP   │
│ TSL  ││ LoTE ││  Fed    ││ WebVH   ││  Key      ││ IACA  ││ Cert  │
└──────┘└──────┘└─────────┘└─────────┘└───────────┘└────────┘└────────┘

TrustRegistry Interface

All trust registries implement the same interface:

type TrustRegistry interface {
    Evaluate(ctx context.Context, req *authzen.EvaluationRequest) (*authzen.EvaluationResponse, error)
    SupportedResourceTypes() []string
    SupportsResolutionOnly() bool
    Info() RegistryInfo
    Healthy() bool
    Refresh(ctx context.Context) error
}

SupportsResolutionOnly() indicates whether the registry only performs DID/key resolution without trust evaluation (e.g. did:key).

Resolution Strategies

Strategy Description
FirstMatch Returns as soon as any registry returns decision=true
AllRegistries Queries all registries and aggregates results
BestMatch Returns the result with highest confidence
Sequential Tries registries in order until one succeeds

Supported Registry Types

ETSI TSL Registry

Evaluates X.509 certificates against ETSI TS 119 612 Trust Status Lists.

Input sources:

  • --etsi-cert-bundle: PEM file with trusted CA certificates (processed by tsl-tool)
  • --etsi-tsl-files: Raw TSL XML files

Supported resource types: x5c, jwk, x509_san_dns

Configuration options (in YAML config file):

  • lotl_signer_bundle: PEM file containing trusted LOTL signer certificates for signature validation
  • require_signature: When true, TSLs must have valid signatures (requires lotl_signer_bundle)

OpenID Federation Registry

Evaluates trust chains in OpenID Federation ecosystems.

Supported resource types: entity, openid_federation

DID Web Registry

Resolves and validates DID Web identifiers per W3C DID specification.

DID Web URL Mapping:

  • did:web:example.com → https://example.com/.well-known/did.json
  • did:web:example.com:users:alice → https://example.com/users/alice/did.json
  • did:web:example.com%3A3000 → https://example.com:3000/.well-known/did.json

Supported resource types: key, jwk

DID Web VH Registry

Resolves and validates DID Web VH (Verifiable History) identifiers with cryptographic integrity verification.

Features:

  • Full DID:web:vh resolution per spec
  • Cryptographic chain verification
  • Version history traversal
  • Pre-rotation key support

Supported resource types: did_document, jwk, verification_method

DID JWKS Registry

Resolves did:jwks identifiers that encode JWKS endpoints directly in the DID. Supports discovery via OAuth2/OIDC metadata endpoints.

Supported resource types: jwk, key

DID Key Registry

Resolves did:key identifiers that encode public keys directly in the DID (no network resolution required). Part of the generic DID resolver framework in pkg/registry/did/.

Supported resource types: jwk, key

mDOC IACA Registry

Dynamically validates mDOC/mDL X.509 certificate chains against IACA (Issuing Authority Certificate Authority) certificates fetched from OpenID4VCI issuers.

Architecture:

  1. Receives trust evaluation with issuer URL (subject.id) and X5C chain (resource.key)
  2. Fetches issuer's OpenID4VCI metadata (discovers mdoc_iacas_uri endpoint)
  3. Fetches IACA certificates from mdoc_iacas_uri
  4. Validates X5C chain against fetched IACAs
  5. Optionally enforces issuer allowlist

Supported resource types: x5c

import "github.com/sirosfoundation/go-trust/pkg/registry/mdociaca"

reg, err := mdociaca.New(&mdociaca.Config{
    Name:            "mdoc-iaca",
    IssuerAllowlist: []string{"https://issuer.example.com"},  // Optional
    CacheTTL:        time.Hour,
})

// Evaluate trust for an mDOC issuer
req := &authzen.EvaluationRequest{
    Subject:  authzen.Subject{Type: "key", ID: "https://issuer.example.com"},
    Resource: authzen.Resource{Type: "x5c", Key: []interface{}{dsB64, iacaB64}},
}
resp, err := reg.Evaluate(ctx, req)

Use cases:

  • Mobile driving license (mDL) issuer validation
  • EUDI wallet mDOC credential issuance
  • Any OpenID4VCI issuer publishing IACA certificates

eMRTD Document Signer Registry

Full reference (config keys, anchor directory format, request/response, deny codes, known parsing limits): docs/EMRTD.md.

Decides whether the Document Signer Certificate (DSC) of an electronic passport or ID card chains to a reviewed Country Signing CA (CSCA) of the claimed issuing state. The PEP verifies the SOD itself and sends only the DSC (plus any other certificates carried in the SOD); the registry never sees the SOD.

  • Anchors are read from anchors_dir/<ALPHA3>/*.pem only (e.g. SWE, DEU); the directory is the country and each certificate's subject C must match it (ISO 3166 table), otherwise that certificate is skipped and logged. Point it at the anchors/ tree of the anchor repository, never at candidates/.
  • Extra certificates in resource.key are untrusted link-certificate candidates, never anchors. The system certificate pool is never used.
  • Validity is checked at context.signing_time (RFC 3339, default now); a malformed value is denied. Brainpool and RSA-PSS are supported, as are CSCAs with explicit EC parameters (via go-cryptoutil ecparams). Optional crls_dir/<ALPHA3>/*.crl enables revocation checks.
  • CRLs are authentic for an issuer when signed by the path issuer or any reviewed anchor of that state with the CRL's issuer name (covers CSCA key rollover). With the all/best_match strategies, all_results now carries each registry's full reason including admin details; intended for service callers.
  • Deny responses carry a machine-readable code in context.reason.code (also context.reason.admin.code): unknown_country, no_anchor, chain_invalid, country_mismatch, expired, not_yet_valid, bad_key_usage, revoked, malformed_request. Allow responses carry csca_sha256, csca_subject and dsc_sha256 in context.reason.admin.
registries:
  emrtd:
    enabled: true
    name: emrtd-csca
    anchors_dir: /etc/go-trust/emrtd/anchors
    crls_dir: /etc/go-trust/emrtd/crls   # optional
    watch: true                          # reload on change
policies:
  policies:
    emrtd-document-signer:
      registries: [emrtd-csca]
      constraints: {require_key_binding: true, allowed_key_types: [x5c]}
{"subject":  {"type": "key", "id": "SWE"},
 "resource": {"type": "x5c", "id": "SWE", "key": ["<DSC base64 DER>", "<extra cert from SOD>"]},
 "action":   {"name": "emrtd-document-signer"},
 "context":  {"signing_time": "2026-09-30T10:00:00Z"}}

FIDO MDS3 Registry

Validates a FIDO2/CTAP2 attestation's X.509 certificate chain against the FIDO Alliance Metadata Service v3 (MDS3) — the official, periodically-updated, cryptographically-signed registry of certified authenticator models (e.g. security keys, platform authenticators) and their certification/revocation status.

Architecture:

  1. Fetches the MDS3 blob (JWT-signed), verifies its signature, and indexes entries by AAGUID
  2. Periodically re-fetches on a configurable interval; a transient fetch failure degrades to stale data rather than blanking the index
  3. Optional disk cache (CachePath) lets a process restart serve the last-known-good, re-verified blob immediately instead of blocking on a live fetch
  4. Evaluate() looks up the AAGUID from subject.id, verifies the resource.key X5C chain against that entry's attestation root certificates, and rejects entries whose StatusReports include an undesired status (e.g. revoked, user-verification-bypass)
  5. Optionally enforces a profile-scoped AAGUID allow/blocklist on top of MDS3 status — see Policy Configuration

Supported resource types: x5c

import "github.com/sirosfoundation/go-trust/pkg/registry/fidomds3"

reg, err := fidomds3.New(fidomds3.Config{
    Name:            "fido-mds3",
    RefreshInterval: 24 * time.Hour, // Optional; 0 disables periodic refresh
    CachePath:       "/var/lib/go-trust/fido-mds3.jwt", // Optional disk cache
})
reg.StartRefreshLoop(ctx) // Optional; only needed if RefreshInterval is set

// Evaluate trust for a FIDO2 authenticator's attestation
req := &authzen.EvaluationRequest{
    Subject:  authzen.Subject{Type: "key", ID: aaguid.String()},
    Resource: authzen.Resource{Type: "x5c", Key: []interface{}{attestationCertB64}},
}
resp, err := reg.Evaluate(ctx, req)

Use cases:

  • WebAuthn/passkey hardware-key attestation verification
  • WSCD (Wallet Secure Cryptographic Device) provisioning, where only AAGUIDs with a confirmed-working feature (e.g. previewSign) should be trusted, tighter than "not revoked"
  • Denying a specific AAGUID with a known issue even though FIDO Alliance hasn't revoked its certification

LoTE Registry (ETSI TS 119 602)

Evaluates trust from ETSI TS 119 602 Lists of Trusted Entities (LoTE) — the JSON-based successor to XML Trust Status Lists. LoTE documents list trusted entities with their digital identities (X.509 certificates, JWK keys, or DIDs) and service descriptions.

Features:

  • Loads LoTE JSON from URLs or local files
  • Indexes entities by ID and digital identity fingerprint
  • X.509 PKIX path validation against entity certificates
  • JWK key matching via SHA-256 fingerprints
  • Optional JWS signature verification of LoTE and LoTL documents against a pinned trust anchor bundle (trust_anchor_bundle); applies to LoTL-resolved sources too
  • Periodic refresh with configurable interval

Supported resource types: x5c, jwk

Configuration (YAML config file):

registries:
  lote:
    enabled: true
    name: "LoTE"
    description: "ETSI TS 119 602 List of Trusted Entities"
    sources:
      - "https://example.com/lote-se.json"
      - "https://example.com/lote-de.json"
      - "/etc/go-trust/local-lote.json"
    # JWS verification (JSON/JWS documents only). verify_jws: true REQUIRES
    # trust_anchor_bundle - go-trust refuses to start without it. Setting
    # trust_anchor_bundle alone also turns verification on.
    verify_jws: true
    trust_anchor_bundle: "/etc/go-trust/lote-anchors.pem"  # PEM, CA certs
    fetch_timeout: "30s"
    refresh_interval: "1h"

When verification is on, every LoTE and LoTL (including nested LoTLs) must carry a JWS whose x5c chain validates to the bundle; unsigned or badly signed documents are never used. A direct source or top-level LoTL that fails verification makes the registry fail to load; a LoTE or nested LoTL reached through a LoTL pointer that fails is logged at error level and skipped, like a pointer that cannot be fetched.

Programmatic usage:

import "github.com/sirosfoundation/go-trust/pkg/registry/lote"

reg, err := lote.New(lote.Config{
    Name:            "lote-eu",
    Sources:         []string{"https://example.com/lote.json"},
    RefreshInterval: time.Hour,
})

// Evaluate trust for an entity
req := &authzen.EvaluationRequest{
    Subject:  authzen.Subject{Type: "key", ID: "https://issuer.example.com"},
    Resource: authzen.Resource{Type: "x5c", Key: []interface{}{certB64}},
}
resp, err := reg.Evaluate(ctx, req)

Use cases:

  • JSON-native trust evaluation for modern credential ecosystems
  • LoTE-based trust where entities are identified by JWK, X.509, or DID
  • Transition from XML TSL to JSON LoTE format

RP Certificate Registry (ETSI TS 119 475)

Validates Relying Party registration certificates per ETSI TS 119 475 and TS 119 411-8. Verifies that a relying party presents a valid registration certificate issued by a recognized authority.

Features:

  • X.509 certificate chain validation for RP certificates
  • Certificate policy OID validation
  • RP identity extraction from certificate Subject DN and SANs

Supported resource types: x5c

Static Trust Registries

The pkg/registry/static package provides simple TrustRegistry implementations for testing, development, and basic use cases:

Registry Description Use Case
AlwaysTrustedRegistry Always returns decision=true Testing, development, trust-all scenarios
NeverTrustedRegistry Always returns decision=false Testing trust rejection, deny-all scenarios
SystemCertPoolRegistry Validates X509 against OS CA bundle Simple TLS trust without ETSI TSL
WhitelistRegistry URL-based whitelist with file watching Simple issuer/verifier allowlisting
import "github.com/sirosfoundation/go-trust/pkg/registry/static"

// Accept all trust requests (testing only!)
registry := static.NewAlwaysTrustedRegistry("test-always-trusted")

// Reject all trust requests (testing only!)
registry := static.NewNeverTrustedRegistry("test-never-trusted")

// Validate against system CA bundle
registry, _ := static.NewSystemCertPoolRegistry(static.SystemCertPoolConfig{
    Name:        "system-ca",
    Description: "System root CA certificates",
})

// URL whitelist from config file (with auto-reload)
registry, _ := static.NewWhitelistRegistryFromFile("/etc/go-trust/whitelist.yaml", true)
defer registry.Close()

// URL whitelist configured programmatically
registry := static.NewWhitelistRegistry()
registry.AddIssuer("https://pid-issuer.example.com")
registry.AddVerifier("https://verifier.example.com")

WhitelistRegistry Configuration

The whitelist registry supports YAML or JSON configuration files:

# whitelist.yaml
issuers:
  - https://pid-issuer.example.com
  - https://issuer.example.org
  - https://trusted-domain.com/*  # Wildcard prefix match

verifiers:
  - https://verifier.example.com
  - https://rp.example.org

trusted_subjects:
  - https://any-role.example.com  # Matches any role

Features:

  • Role-based matching: issuers for credential issuers, verifiers for relying parties
  • Wildcard support: https://example.com/* matches any path under that domain
  • Global wildcard: * matches all subjects (use with caution)
  • File watching: Automatically reloads config when file changes (when watch=true)
  • Runtime updates: AddIssuer(), RemoveIssuer(), AddVerifier(), RemoveVerifier()

Security Note: WhitelistRegistry provides URL-based trust only. Signature verification must be performed at the application layer. For cryptographic trust verification, use ETSI TSL, OpenID Federation, or other advanced registries.

Policy Configuration

Policies map action names (from AuthZEN requests) to registry-specific constraints. This enables role-based trust evaluation where different credential types or participant roles have different requirements.

Configuration Structure

policies:
  # Default policy when action.name doesn't match any specific policy
  default_policy: credential-verifier

  policies:
    # Policy for credential issuers
    credential-issuer:
      description: "Trust requirements for credential issuers"
      
      # ETSI TSL constraints
      etsi:
        service_types:
          - "http://uri.etsi.org/TrstSvc/Svctype/QCert"
          - "http://uri.etsi.org/TrstSvc/Svctype/QCertForESeal"
        service_statuses:
          - "http://uri.etsi.org/TrstSvc/TrustedList/Svcstatus/granted"
      
      # OpenID Federation constraints
      oidfed:
        entity_types:
          - "openid_credential_issuer"
        required_trust_marks:
          - "https://dc4eu.eu/tm/issuer"
      
      # DID constraints (did:web and did:webvh)
      did:
        allowed_domains:
          - "*.eudiw.dev"
          - "*.example.com"
        # DID document must contain ALL of these verificationMethod[].type values
        # (exact match; a DID missing any of them is denied)
        required_verification_methods:
          - "JsonWebKey2020"
        require_verifiable_history: true

    # Policy for wallet providers
    wallet-provider:
      description: "Trust requirements for wallet providers"
      oidfed:
        entity_types:
          - "wallet_provider"
        required_trust_marks:
          - "https://dc4eu.eu/tm/wallet"
      # Override which registries to query
      registries:
        - "oidfed-registry"

    # Policy for the signer of an IETF Token Status List (action.name
    # "status-list-signer", trustapi.RoleStatusListSigner). The signer is often
    # not the credential issuer (e.g. an external status service), so it is
    # evaluated as its own action. go-wallet-backend asks this first and falls
    # back to "credential-issuer".
    status-list-signer:
      description: "Trust requirements for Token Status List signers"
      constraints:
        require_key_binding: true      # a key must be presented; no resolution-only answers
        allowed_key_types: [x5c, jwk]
      registries:
        - "status-signer-anchors"      # e.g. a whitelist registry, see below

    # Policy for mDL issuers
    mdl-issuer:
      description: "Trust requirements for mDL/mDOC issuers"
      mdociaca:
        issuer_allowlist:
          - "https://pid-issuer.eudiw.dev"
        require_iaca_endpoint: true
      registries:
        - "mdoc-iaca"

    # Policy for WSCD previewSign provisioning: only AAGUIDs confirmed to
    # have a working previewSign implementation are trusted, tighter than
    # "not revoked by FIDO Alliance"
    wscd-previewsign-provision:
      description: "Trust requirements for WSCD previewSign provisioning"
      fidomds3:
        allowed_aaguids:
          - "0132d110-bf4e-4208-a403-ab4f5f12efe5"
      registries:
        - "fido-mds3"

Constraint Types

Constraint Type Description Applicable Registries
etsi Service types, statuses, countries, credential types (audit) ETSI TSL
lote Entity types, statuses, territories, credential types (audit) ETSI LoTE
oidfed Entity types, trust marks, credential type mapping OpenID Federation
decentralized_identifier Allowed domains, verifiable history DID Web, DID Web VH
mdociaca Issuer allowlist, IACA endpoint mDOC IACA
fidomds3 AAGUID allowlist/blocklist FIDO MDS3
constraints (require_key_binding, allowed_key_types) Generic; used by emrtd-document-signer eMRTD
emrtd (path_len_mode, path_len_override) Opt-in pathLenConstraint enforcement (default: ignored); see docs/EMRTD.md eMRTD

AAGUID Policy in FIDO MDS3

For the FIDO MDS3 registry, allowed_aaguids/blocked_aaguids are enforced on top of, never instead of, the registry's own MDS3 status-report and X5C chain verification — a request must still pass MDS3 certification checks regardless of policy. If allowed_aaguids is non-empty, only those AAGUIDs are trusted (allowlist_only semantics), even if MDS3 still certifies others. Otherwise, if blocked_aaguids is non-empty, all MDS3-certified AAGUIDs are trusted except those listed (allow_except_blocklist semantics). If both are set, allowed_aaguids takes precedence. If neither is set — including when no policy matches the request's action.name at all — behavior is unchanged: MDS3 status/chain verification only.

Credential Types in ETSI TSL

For ETSI TSL, credential_types can be supplied by the client via action.parameters or set server-side in the etsi policy constraints. When present, they are included in the requested_credential_types field of every evaluation response (both allowed and denied) for audit purposes. Future versions may use them for filtering against TSL service extensions or metadata.

Client-supplied via action.parameters:

{
  "action": {
    "name": "credential-issuer",
    "parameters": {
      "credential_types": ["eu.europa.ec.eudi.pid.1"]
    }
  }
}

Server-side via policy configuration:

policies:
  policies:
    credential-issuer:
      etsi:
        service_types:
          - "http://uri.etsi.org/TrstSvc/Svctype/QCert"
        credential_types:
          - "eu.europa.ec.eudi.pid.1"

Unknown action names

By default a request whose action.name has no policy is judged by default_policy (or, with no default, by all registries unconstrained), and the first occurrence of each unknown name is logged as a warning. To refuse such requests instead, set:

policies:
  fail_closed_on_unknown_action: true

Enable this once every action your clients send has a policy - in particular before go-wallet-backend runs with presentation.status_check set to enforce-revoked or strict, so that a missing status-list-signer policy denies rather than silently falling through to the default.

Trust anchors for status-list-signer

No dedicated registry type is needed for services that publish keys. Status-list signers are ordinary x5c/jwk keys, so the existing registries apply: ETSI TSL / LoTE / OIDF where the service is listed, or a whitelist registry listing the service origin (its iss, or the list URI's origin) so that its JWKS is discovered and the presented key is matched against it. The legacy issuers list only maps issuer-style actions, so use a named list with an explicit action mapping:

lists:
  status-signers:
    - https://status.example.com
actions:
  status-list-signer: status-signers

Not supported: pinning a CA for a private HTTPS status service that publishes no JWKS. The whitelist registry's additional_trusted_roots path only applies to non-HTTP(S) subjects (e.g. x509_san_dns:), and an HTTPS subject with no cached keys is denied. That would need a new pinned-root registry; it is not part of this change.

Credential Types in LoTE

For ETSI LoTE (List of Trusted Entities), credential_types supplied via action.parameters are included in the requested_credential_types field of all evaluation responses (both allowed and denied) for audit purposes.

{
  "action": {
    "name": "credential-issuer",
    "parameters": {
      "credential_types": ["eu.europa.ec.eudi.pid.1"]
    }
  }
}

The response will include:

{
  "decision": true,
  "context": {
    "reason": {
      "admin": "Found ...",
      "requested_credential_types": ["eu.europa.ec.eudi.pid.1"]
    }
  }
}

Credential Type to Trust Mark Mapping (OpenID Federation)

For OpenID Federation, you can configure a mapping from SD-JWT VCT values (credential types) to required trust marks. When a client includes credential_types in action.parameters, the policy will derive the required trust marks from the mapping:

policies:
  policies:
    credential-issuer:
      description: "Validate PID and mDL issuers"
      oidfed:
        entity_types:
          - "openid_credential_issuer"
        # Map credential types to required trust marks
        credential_type_trust_marks:
          "eu.europa.ec.eudi.pid.1":
            - "https://trust.eu/tm/pid-issuer"
          "eu.europa.ec.eudi.mdl.1":
            - "https://trust.eu/tm/mdl-issuer"

When a client requests evaluation with:

{
  "action": {
    "name": "credential-issuer",
    "parameters": {
      "credential_types": ["eu.europa.ec.eudi.pid.1"]
    }
  }
}

The OIDF registry will require the entity to have the https://trust.eu/tm/pid-issuer trust mark, in addition to any statically configured required_trust_marks.

Example Request

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

X5C Enrichment

When evaluating x5c resources, Go-Trust can enrich evaluation responses with additional information extracted from the leaf certificate:

Certificate Policy OID Validation: Require that the leaf certificate contains specific policy OIDs. Configure via policy constraints or request parameters:

policies:
  policies:
    credential-issuer:
      etsi:
        required_cert_policy_oids:
          - "2.16.840.1.101.2.1.11.36"
          - "1.3.6.1.4.1.311.94.1.1"
        extract_rp_identity: true

Or per-request via action.parameters:

{
  "action": {
    "name": "credential-issuer",
    "parameters": {
      "required_cert_policy_oids": ["2.16.840.1.101.2.1.11.36"],
      "extract_rp_identity": true
    }
  }
}

RP Identity Extraction: When enabled, the response includes the relying party's identity extracted from the leaf certificate:

{
  "decision": true,
  "context": {
    "reason": {
      "matched_policy_oids": ["2.16.840.1.101.2.1.11.36"]
    },
    "trust_metadata": {
      "rp_identity": {
        "organization": ["Example Corp"],
        "common_name": "Example RP",
        "country": ["SE"],
        "serial_number": "1234567890",
        "dns_sans": ["rp.example.com"]
      },
      "matched_policy_oids": ["2.16.840.1.101.2.1.11.36"]
    }
  }
}

X5C enrichment is supported by both ETSI TSL and LoTE registries.

Verifier Identifiers (verifier_ids)

A wallet that answers a request with a pseudonym function needs a stable verifier identifier string for the relying party. Go-Trust can return those in the evaluation response context.verifier_ids, derived only from material it has authenticated, so the wallet does not have to parse certificates, resolve DIDs or walk federation chains itself. The member is informational (never critical) and off by default: existing responses are unchanged unless a policy enables it.

policies:
  policies:
    credential-verifier:
      constraints:
        return_verifier_ids: [semantic, x509_san_dns]   # unknown type names are rejected at startup
{
  "decision": true,
  "context": {
    "verifier_ids": [
      {"verifier_id_type": "semantic", "verifier_id": "semantic_id:VATSE-556000000101", "basis": "x5c-chain"},
      {"verifier_id_type": "x509_san_dns", "verifier_id": "rp.example.com", "basis": "client-id-binding"}
    ]
  }
}

The response lists everything the policy enables and go-trust can derive; a type it cannot establish is omitted. A request may narrow the response to one type with the optional request context member verifier_id_type (for example "context": {"verifier_id_type": "x509_san_dns"}). The hint only narrows: it cannot enable a type the policy did not. Clients treat verifier_id_type as opaque; the order of entries carries no meaning.

Types go-trust can derive (the exact value form is fixed per type). Type names follow the OpenID4VP client_id scheme names (x509_san_dns, x509_san_uri, decentralized_identifier, openid_federation); semantic is the only exception:

Type Value Basis Registries
semantic semantic_id: + the ETSI semantic identifier from the access certificate leaf (ETSI TS 119 475 5.1.3 legal persons: organizationIdentifier; 5.1.5 natural persons: serialNumber). Must match <3 letters><2 letters>-<value>; the value is kept verbatim. x5c-chain etsi, lote, and whitelist (system-CA path), system_cert_pool, mdoc_rical (chain validated)
x509_san_dns the client_id of an x509_san_dns request, which must equal a dNSName SAN of the leaf (ASCII case-insensitively): the A-label form, lower case, one trailing dot removed; see the rules below client-id-binding etsi, lote, whitelist, system_cert_pool, mdoc_rical
x509_san_uri the client_id of an x509_san_uri request, byte-for-byte (no case folding, no slash normalisation, not re-serialised through net/url) client-id-binding same as x509_san_dns
decentralized_identifier the full DID of the resolved DID document, without fragment did-resolution didweb, didwebvh, didjwks, did
openid_federation the Entity ID as in the validated trust chain, byte-for-byte oidf-trust-chain oidfed

Rules:

  • Fail closed. Nothing is emitted for a deny decision, when the matched profile's validation failed (normally only a warning), or when the identifier was not verified: x509_san_dns/x509_san_uri need VerifyLeafBinding to pass, decentralized_identifier needs a successful resolution and a matching key, openid_federation needs a validated trust chain and a matching key. An unverified client_id is never echoed. Resolution-only responses carry no verifier_ids.
  • x509_san_dns is the A-label, lower case. Internationalised names are supported: a certificate carries them as A-labels (xn--..., RFC 5280), and a client_id that carries the A-label binds to the SAN and yields the A-label as the value. The client_id is compared with the SAN ASCII case-insensitively (the value emitted is always lower case, one trailing dot removed). A U-label client_id (bücher.example) is not converted: the PDP does no IDNA processing, because IDNA2003, IDNA2008 and UTS #46 differ for some names and a difference would be a different pseudonym, so no entry is emitted and the verifier must send the A-label. Also omitted, never repaired: an empty label (a..b, a leading dot, more than one trailing dot), whitespace or control characters, a label over 63 or a name over 253 octets, a wildcard client_id, and a client_id that binds only through a wildcard SAN (*.example.com), which names one of many hosts under one certificate. Shared test vectors: pkg/registry/testdata/x509_san_dns_verifier_id_vectors.json.
  • semantic comes from the access certificate only. The registration certificate is not an input today, so wallets must not assume registration-certificate precedence. A serialNumber that is not a semantic identifier (for example a plain certificate serial) is never emitted as semantic. The subject kind comes from the WRPAC policy OIDs alone: a certificate with neither a natural- nor a legal-person policy (or with both) yields no semantic, because its organizationIdentifier could be a natural person's affiliation rather than the verifier. The type must fit the subject: natural-person types (PNO, IDC, PAS, TAX, TIN) are read from serialNumber of natural-person certificates only, and refused for legal-person ones.
  • Intermediary requests (allow_intermediaries): the identifier is the end relying party's. semantic is taken from the RP leaf; x509_san_dns and x509_san_uri are omitted because the client_id of a brokered request cannot be shown to name the end relying party. The intermediary's own, unvalidated chain is never a source.
  • Registries that emit. etsi, lote, didweb, didwebvh, didjwks, did, oidfed (as above) and, since the x5c derivation was shared, whitelist, system_cert_pool and mdoc_rical. For these three the rules are exactly those of etsi/lote (same canonical forms, same wildcard and intermediary omissions, same VerifyLeafBinding check). The whitelist has two certificate paths: on the pinned-key path (the x5c leaf key matches a fingerprint fetched from the entity's JWKS) trust rests on the key, not on the certificate, so only x509_san_dns/x509_san_uri are offered, and only when the client_id is also a SAN of the presented leaf; semantic is withheld because nobody vouches for the certificate's own claims. On the system-CA path (trust_x509_via_system_ca, chain validated) semantic is offered too. The x509_hash path, key-only presentations and resolution-only requests carry no verifier_ids. mdociaca and vical (issuer authorities), fidomds3 (authenticators), emrtd (document signers), always_trusted and never_trusted never emit: they establish no verified verifier identity.
  • Strategies and composites. first_match and sequential return the matching registry's own response, so its verifier_ids come through. all and best_match merge the verifier_ids of every registry that answered true (deduplicated by type and value, ordered by type then value; best_match currently aggregates like all). A composite registry (AND, OR, MAJORITY, QUORUM) carries over the verifier_ids of its agreeing children when the composite itself allows, and nothing otherwise. An identifier of a registry that answered false is never carried.
  • Policies and routing. return_verifier_ids belongs to a policy, and a policy is selected by exact action.name. Adding a policies: section for some actions does not change the others as long as default_policy and fail_closed_on_unknown_action are left unset: an action with no policy is evaluated by every registry, without constraints, exactly as without a policies: section (a one-time warning is logged per unknown action name). Setting default_policy applies that policy to every unlisted action and to requests with no action.name at all; setting fail_closed_on_unknown_action: true denies unlisted named actions, but not requests with no action.name, which still use default_policy or unconstrained evaluation. A policy with no registries, allowed_key_types or require_key_binding does not narrow which registries are asked or which requests are accepted. A ready-to-use snippet for a PDP serving a whitelist registry is in docs/examples/gdc-verifier-ids-policies.yaml; a test pins that it only adds verifier_ids.
  • Not offered: did_scid, service and spki_sha256 are not implemented yet and are rejected in return_verifier_ids.

Deployment

Docker

docker build -t go-trust:latest .

docker run -d \
  --name go-trust-server \
  -p 6001:6001 \
  -v /path/to/trusted-certs.pem:/app/certs.pem:ro \
  go-trust:latest --etsi-cert-bundle /app/certs.pem

Kubernetes

apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-trust
spec:
  replicas: 3
  selector:
    matchLabels:
      app: go-trust
  template:
    metadata:
      labels:
        app: go-trust
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "6001"
    spec:
      containers:
      - name: go-trust
        image: go-trust:latest
        args:
        - --host=0.0.0.0
        - --port=6001
        - --etsi-cert-bundle=/config/trusted-certs.pem
        - --log-format=json
        ports:
        - containerPort: 6001
        livenessProbe:
          httpGet:
            path: /healthz
            port: 6001
          initialDelaySeconds: 10
          periodSeconds: 30
        readinessProbe:
          httpGet:
            path: /readyz
            port: 6001
          initialDelaySeconds: 5
          periodSeconds: 10
        volumeMounts:
        - name: config
          mountPath: /config
      volumes:
      - name: config
        configMap:
          name: go-trust-config

Workflow: TSL Processing + PDP Server

For production deployment, use tsl-tool (from g119612) to process TSLs and generate certificate bundles:

# 1. Process TSLs with tsl-tool
tsl-tool --output trusted-certs.pem pipeline.yaml

# 2. Run gt with the generated certificate bundle
gt --etsi-cert-bundle trusted-certs.pem

Run tsl-tool via cron to update TSL data periodically:

# Crontab entry: Update TSLs daily at 2 AM
0 2 * * * /usr/local/bin/tsl-tool --output /etc/go-trust/trusted-certs.pem /etc/tsl-tool/pipeline.yaml

Prometheus Metrics

The /metrics endpoint exposes:

Metric Description
api_requests_total HTTP requests by method, endpoint, status
api_request_duration_seconds Request latency histogram
api_requests_in_flight Current active requests
cert_validation_total Certificate validations by result
cert_validation_duration_seconds Validation latency

Example Prometheus queries:

# Request rate by endpoint
rate(api_requests_total[5m])

# 95th percentile latency
histogram_quantile(0.95, rate(api_request_duration_seconds_bucket[5m]))

# Certificate validation error rate
rate(cert_validation_total{result="error"}[5m])

Embedded Test Server

The testserver package provides an embedded test server for integration testing:

import (
    "testing"
    "github.com/sirosfoundation/go-trust/pkg/testserver"
    "github.com/sirosfoundation/go-trust/pkg/registry/static"
)

func TestMyApplication(t *testing.T) {
    // Create a test server that accepts all trust requests
    srv := testserver.New(testserver.WithAcceptAll())
    defer srv.Close()

    // Or use static registries for more control
    srv := testserver.New(testserver.WithRegistry(static.NewAlwaysTrustedRegistry()))
    defer srv.Close()

    // Use srv.URL() to get the server address
    // Make AuthZEN requests to the server
}

Test Server Options

Option Description
WithAcceptAll() Accept all trust requests
WithRejectAll() Reject all trust requests
WithRegistry(r) Use a specific TrustRegistry implementation
WithMockRegistry(name, decision, types) Add a mock registry
WithDecisionFunc(fn) Dynamic trust decisions

Using Static Registries in Tests

// Test with always-trusted registry
srv := testserver.New(testserver.WithRegistry(static.NewAlwaysTrustedRegistry()))

// Test with never-trusted registry  
srv := testserver.New(testserver.WithRegistry(static.NewNeverTrustedRegistry()))

// Test with system CA validation
srv := testserver.New(testserver.WithRegistry(static.NewSystemCertPoolRegistry()))

// Test with specific whitelist
reg := static.NewWhitelistRegistry()
reg.AddIssuer("https://test-issuer.example.com")
srv := testserver.New(testserver.WithRegistry(reg))

Development

Requirements

  • Go 1.26+ (check go.mod for exact version)
  • CGO enabled (CGO_ENABLED=1)
  • Make for build automation

Building

make build          # Build the binary (./go-trust)
make test           # Run tests with race detection
make coverage       # Generate coverage report
make lint           # Run linters
make quick          # Quick pre-commit checks

Project Structure

go-trust/
├── cmd/gt/            # Main application
├── pkg/
│   ├── api/           # HTTP API implementation (Swagger UI)
│   ├── authzen/       # AuthZEN protocol types
│   ├── authzenclient/ # AuthZEN client library
│   ├── config/        # YAML configuration loading
│   ├── registry/      # Trust registry interface and manager
│   │   ├── etsi/        # ETSI TSL registry (XML, TS 119 612)
│   │   ├── lote/        # ETSI LoTE registry (JSON, TS 119 602)
│   │   ├── oidfed/      # OpenID Federation registry
│   │   ├── didweb/      # DID Web registry
│   │   ├── didwebvh/    # DID Web VH registry
│   │   ├── didjwks/     # DID JWKS registry
│   │   ├── did/         # Generic DID resolver + did:key
│   │   ├── didutil/     # DID utility functions
│   │   ├── mdociaca/    # mDOC IACA registry
│   │   ├── emrtd/       # eMRTD (ICAO 9303) DSC -> CSCA registry
│   │   ├── fidomds3/    # FIDO Alliance MDS3 registry
│   │   ├── rpcert/      # RP Certificate registry (TS 119 475)
│   │   └── static/      # Static registries (always/never/system/whitelist)
│   ├── resilience/    # Resilient HTTP fetcher
│   ├── trustapi/      # Trust API evaluator types
│   └── testserver/    # Embedded test server
├── example/           # Example configurations
└── docs/              # Architecture documentation

Contributing

Contributions are welcome! Please read CONTRIBUTING.md for guidelines.

# Development workflow
git checkout -b feature/amazing-feature
make quick && make test
git commit -m 'feat: Add amazing feature'
git push origin feature/amazing-feature
# Open a Pull Request

Related Projects

License

This project is licensed under the BSD 2-Clause License - see LICENSE.txt for details.

Acknowledgments

About

a "local" trust evaluation engine using AuthZen

Topics

Resources

Contributing

Security policy

Stars

4 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages