model

package
v0.7.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 1, 2026 License: BSD-2-Clause Imports: 30 Imported by: 0

Documentation

Index

Examples

Constants

View Source
const (
	AuthProviderSAML      = "saml"
	AuthProviderOIDC      = "oidc"
	AuthProviderOpenID4VP = "openid4vp"
	AuthProviderDatastore = "datastore"
)
View Source
const (
	CredentialTypeUrnEudiEhic1            = "urn:eudi:ehic:1"             // #nosec G101
	CredentialTypeUrnEudiPda11            = "urn:eudi:pda1:1"             // #nosec G101
	CredentialTypeUrnEudiPid1             = "urn:eudi:pid:1"              // #nosec G101
	CredentialTypeUrnEudiDiploma1         = "urn:eudi:diploma:1"          // #nosec G101
	CredentialTypeUrnEudiElm1             = "urn:eudi:elm:1"              // #nosec G101
	CredentialTypeUrnEudiMicroCredential1 = "urn:eudi:micro_credential:1" // #nosec G101
	CredentialTypeUrnEduID1               = "urn:credential:eduid:1"      // #nosec G101
)
View Source
const DefaultRegistrationCertificateFormat = rpcert.WRPRCTyp

DefaultRegistrationCertificateFormat is the verifier_info format identifier for a WRPRC, matching its JWT media type.

Variables

View Source
var ErrNoAccessCertificate = errors.New("access_certificate.validate is set but no signing certificate was loaded from key_config")

ErrNoAccessCertificate reports that WRPAC validation was requested but no certificate was loaded to validate.

Functions

func BoolPtr

func BoolPtr(v bool) *bool

BoolPtr returns a pointer to the given bool value. Useful for initializing *bool fields in struct literals.

Example
package main

import (
	"fmt"

	"github.com/SUNET/vc/pkg/model"
)

func main() {
	p := model.BoolPtr(true)
	fmt.Println(*p)
}
Output:
true

func BoolVal

func BoolVal(b *bool, fallback bool) bool

BoolVal safely dereferences a *bool, returning the pointed-to value or the supplied fallback when the pointer is nil.

Example
package main

import (
	"fmt"

	"github.com/SUNET/vc/pkg/model"
)

func main() {
	t := true
	fmt.Println(model.BoolVal(&t, false))
	fmt.Println(model.BoolVal(nil, false))
	fmt.Println(model.BoolVal(nil, true))
}
Output:
true
false
true

func ExtractIdentityClaims added in v0.6.5

func ExtractIdentityClaims(claims map[string]any, required []string) (map[string]string, error)

ExtractIdentityClaims extracts identity field values from a claims map using the provided required claim names. The claim names are used directly as BSON field names in the datastore query (e.g. "given_name" → identities.given_name). Returns an error if any required claim is missing or not a string value.

Types

type APIAuth

type APIAuth struct {
	// JWKS holds the static JWKS Bearer token authentication configuration
	// When enabled, requests are validated against a manually configured JWKS URL
	JWKS APIAuthJWKS `yaml:"jwks"`
	// OIDC holds the OIDC Bearer token authentication configuration
	// When enabled, the JWKS endpoint is auto-discovered from the issuer's
	// .well-known/openid-configuration and Bearer JWTs are validated locally
	// The RP fields (client_id, redirect_uri, etc.) also enable the admin UI
	// login flow via OIDC redirect
	OIDC APIAuthOIDC `yaml:"oidc"`
	// Rules are SPOCP S-expression authorization rules loaded into an in-process engine.
	// All six parts (service, method, path, subject, authentic_source, scope) are mandatory
	// in every rule — use * for wildcards.
	// Rules apply regardless of whether JWKS or OIDC is the active auth method
	Rules []string `` /* 149-byte string literal not displayed */
	// RulesFile is an optional path to a file containing SPOCP rules (one per line)
	// Rules from this file are loaded in addition to the inline Rules list
	RulesFile string `yaml:"rules_file,omitempty"`
}

APIAuth configures authentication for the API route group (datastore, identity mapping, admin UI) JWKS and OIDC are mutually exclusive If neither is enabled, no authentication is applied (open access)

When Rules (and/or RulesFile) are configured, each authenticated request is checked against a SPOCP engine. A query of the form

(vc (service <SERVICE>)(method <HTTP_METHOD>)(path <REQUEST_PATH>)(subject <JWT_SUBJECT>)(authentic_source <SOURCE>)(scope <SCOPE>))

is evaluated; the request is allowed only if a matching rule exists. All six parts are required in every rule. Use * as wildcard for fields you don't want to restrict. The <SERVICE> value is supplied by the calling service at middleware registration time. When two services share endpoints, rules for one service do not grant access to the other. When no rules are configured, any valid Bearer JWT grants access.

type APIAuthJWKS added in v0.5.7

type APIAuthJWKS struct {
	// Enable enables static JWKS Bearer token authentication
	Enable bool `yaml:"enable" default:"false"`
	// JWKSURL is the URL of the JSON Web Key Set used to validate token signatures.
	JWKSURL string `` /* 132-byte string literal not displayed */
	// JWKSFilePath is a local file path to a JWKS JSON file used to validate token signatures.
	JWKSFilePath string `yaml:"jwks_file_path" validate:"excluded_with=JWKSURL,omitempty"`
	// Issuer is the expected "iss" claim. Tokens with a different issuer are rejected
	Issuer string `yaml:"issuer" validate:"required_if=Enable true"`
	// Audience is the expected "aud" claim. Tokens that do not contain this audience are rejected
	Audience string `yaml:"audience" validate:"required_if=Enable true"`
}

APIAuthJWKS holds the configuration for static JWKS Bearer token authentication

type APIAuthOIDC added in v0.5.7

type APIAuthOIDC struct {
	// Enable enables OIDC authentication
	Enable bool `yaml:"enable" default:"false"`
	// IssuerURL is the OIDC provider's issuer URL used for discovery and "iss" claim validation.
	IssuerURL string `yaml:"issuer_url" validate:"required_if=Enable true,omitempty,url" doc_example:"\"https://auth.example.com\""`
	// Audience is the expected "aud" claim. Tokens that do not contain this audience are rejected.
	Audience string `yaml:"audience" validate:"required_if=Enable true"`
	// ClientID is the OAuth2 client identifier registered with the OIDC provider.
	ClientID string `yaml:"client_id" validate:"required_if=Enable true"`
	// ClientSecret is the OAuth2 client secret. May be empty for public clients.
	ClientSecret string `yaml:"client_secret"`
	// RedirectURI is the callback URL for the admin UI OIDC login flow.
	RedirectURI string `yaml:"redirect_uri" validate:"required_if=Enable true,omitempty,url" doc_example:"\"https://apigw.example.com/ui/callback\""`
	// Scopes are the OAuth2/OIDC scopes to request (default: ["openid"]).
	Scopes []string `yaml:"scopes"`
}

APIAuthOIDC holds the configuration for OIDC-based authentication. It serves two purposes:

  • API auth: Bearer JWTs in Authorization headers are validated locally against the provider's JWKS (auto-discovered from IssuerURL).
  • Admin UI login: the RP fields (ClientID, RedirectURI, Scopes) enable an authorization-code redirect flow so admins log in via the OIDC provider.

type APIAuthSecrets

type APIAuthSecrets struct {
	OIDC OIDCAuthSecrets `yaml:"oidc,omitempty"`
}

APIAuthSecrets holds secrets for the api_auth section

type APIGW

type APIGW struct {
	// APIServer is the HTTP API server configuration
	APIServer APIServer `yaml:"api_server" validate:"required"`
	// AdminUIEnable enables the admin web UI. When false (default), the /ui routes are not registered.
	// This must be explicitly set to true to enable the admin interface.
	AdminUIEnable bool `yaml:"admin_ui_enable" default:"false"`
	// KeyConfig is the signing key configuration
	KeyConfig *pki.KeyConfig `yaml:"key_config" validate:"required"`
	// DataSources maps credential types to their data sources
	DataSources DataSources `yaml:"data_sources,omitempty" validate:"required"`
	// AuthProviders configures how users authenticate (SAML, OIDC)
	AuthProviders APIGWAuthProviders `yaml:"auth_providers,omitempty"`
	// Remotes defines named external API connections referenced by DataSources.ExternalAPI
	Remotes map[string]Remote `yaml:"remotes,omitempty" doc_key:"remote name" doc_example:"\"ladok\""`
	// Delivery groups credential delivery to wallets (OpenID4VCI, credential offers)
	Delivery APIGWDelivery `yaml:"delivery" validate:"required"`
	// IssuerMetadata holds the OpenID4VCI issuer metadata
	IssuerMetadata IssuerMetadata `yaml:"issuer_metadata" validate:"omitempty"`
	// PublicURL is the public URL of this service (must be valid HTTP/HTTPS URL)
	PublicURL string `yaml:"public_url" validate:"required,httpurl" doc_example:"\"https://issuer.sunet.se\""`
	// IssuerClient is the gRPC client config for issuer
	IssuerClient GRPCClientTLS `yaml:"issuer_client" validate:"required"`
	// RegistryClient is the gRPC client config for registry
	RegistryClient GRPCClientTLS `yaml:"registry_client" validate:"required"`
	// IdentityMappingImport configures automatic import of identity mappings from JSON files at startup.
	// When configured, APIGW reads JSON files and imports them into the
	// identity mappings collection on first startup (skipped if data already exists).
	IdentityMappingImport *IdentityMappingImport `yaml:"identity_mapping_import,omitempty"`
	// Trust holds the trust evaluation configuration for OpenID4VP credential validation.
	// When configured, credentials presented via VP are validated against a PDP.
	Trust TrustConfig `yaml:"trust,omitempty"`
	// OpenIDFederation holds the OpenID Federation entity configuration.
	// When enabled, serves /.well-known/openid-federation as a self-signed JWT.
	OpenIDFederation *openidfederation.Config `yaml:"federation,omitempty"`
	// RateLimit configures per-endpoint rate limiting for the APIGW.
	RateLimit *APIGWRateLimit `yaml:"rate_limit,omitempty"`
}

APIGW holds the configuration for the API Gateway service that handles credential issuance requests

type APIGWAuthProviders added in v0.5.7

type APIGWAuthProviders struct {
	// SAML configures the SAML SP auth provider
	SAML SAMLSP `yaml:"saml,omitempty" validate:"omitempty"`
	// OIDC configures the OIDC RP auth provider
	OIDC OIDCRP `yaml:"oidc,omitempty" validate:"omitempty"`
}

APIGWAuthProviders groups the authentication provider configurations.

type APIGWDelivery added in v0.5.7

type APIGWDelivery struct {
	// OpenID4VCI configures the OpenID4VCI Authorization Server for wallet credential issuance
	OpenID4VCI OAuthServer `yaml:"openid4vci" validate:"required"`
	// CredentialOffers holds credential offer wallet configurations
	CredentialOffers CredentialOffers `yaml:"credential_offers" validate:"required"`
}

APIGWDelivery groups credential delivery configuration (wallets, offers).

type APIGWRateLimit added in v0.7.0

type APIGWRateLimit struct {
	// TokenRequestsPerMinute is the maximum token endpoint requests per minute per IP. Default: 20
	TokenRequestsPerMinute int `yaml:"token_requests_per_minute" default:"20"`
	// CredentialRequestsPerMinute is the maximum credential endpoint requests per minute per IP. Default: 30
	CredentialRequestsPerMinute int `yaml:"credential_requests_per_minute" default:"30"`
	// DatastoreRequestsPerMinute is the maximum datastore endpoint requests per minute per IP. Default: 60
	DatastoreRequestsPerMinute int `yaml:"datastore_requests_per_minute" default:"60"`
}

APIGWRateLimit holds per-endpoint rate limit settings for the APIGW.

type APIGWSecrets

type APIGWSecrets struct {
	APIServer     APIServerSecrets     `yaml:"api_server,omitempty"`
	AuthProviders AuthProvidersSecrets `yaml:"auth_providers,omitempty"`
}

APIGWSecrets holds API gateway secrets

type APIServer

type APIServer struct {
	// Addr is the listen address for the HTTP server
	Addr string `yaml:"addr" validate:"required" default:":8080"`
	// ServedByHeader sets the X-Served-By response header value for HA troubleshooting.
	// Empty (default): header is not set. "hostname": uses os.Hostname().
	// Any other value is used as-is.
	ServedByHeader string  `yaml:"served_by_header,omitempty"`
	TLS            TLS     `yaml:"tls" validate:"omitempty"`
	APIAuth        APIAuth `yaml:"api_auth"`
	CORS           *CORS   `yaml:"cors,omitempty" validate:"omitempty"`
	// TrustProxyTLS forces the Secure flag on session cookies even when TLS is not
	// enabled on this server. Use this when running behind a TLS-terminating reverse proxy.
	TrustProxyTLS bool `yaml:"trust_proxy_tls" default:"false"`
}

APIServer holds the HTTP API server configuration

type APIServerSecrets

type APIServerSecrets struct {
	APIAuth APIAuthSecrets `yaml:"api_auth,omitempty"`
}

APIServerSecrets holds API server secrets (basic auth passwords)

type AccessCertificate added in v0.7.4

type AccessCertificate struct {
	// Validate enforces the WRPAC certificate profile at startup: keyUsage
	// must include nonRepudiation (contentCommitment), subjectAltName must
	// carry contact information (URI or email), and certificatePolicies must
	// contain a WRPAC policy OID. Startup fails when the certificate does
	// not conform.
	Validate bool `yaml:"validate,omitempty"`
	// AllowedPolicyOIDs optionally narrows which WRPAC certificate policy
	// OIDs are accepted, for a deployment that must assert a specific
	// assurance level (e.g. only the qualified policies). When empty, all
	// four TS 119 411-8 WRPAC policy OIDs are accepted.
	AllowedPolicyOIDs []string `yaml:"allowed_policy_oids,omitempty" doc_example:"[\"0.4.0.194118.1.3\",\"0.4.0.194118.1.4\"]"`
}

AccessCertificate configures validation of the verifier's own wallet-facing certificate as an EUDI Relying Party access certificate (WRPAC, ETSI TS 119 411-8).

This validates the certificate the verifier already signs request objects with - it does not introduce a second certificate. Deployments not participating in an ARF trust framework can leave it disabled and are unaffected.

func (*AccessCertificate) ValidateCertificate added in v0.7.4

func (a *AccessCertificate) ValidateCertificate(leaf *x509.Certificate, now time.Time) error

ValidateCertificate checks a certificate against the WRPAC profile and its own validity window.

This is the shared implementation behind both the verifier's and the issuer's startup check. Under CIR (EU) 2025/848 a PID or attestation provider is a registered wallet-relying party in its own right, so the same profile governs the certificate an issuer presents - one rule, not two copies that drift.

A nil receiver, or one with Validate unset, passes: a deployment outside an ARF trust framework is unaffected.

type AdminGUI

type AdminGUI struct {
	// Enable enables the admin GUI
	Enable *bool `yaml:"enable" default:"false"`
	// Username is the admin username
	Username string `yaml:"username" validate:"required_if=Enable true" default:"admin"`
	// Password is the admin password
	Password string `yaml:"password" validate:"required_if=Enable true"`
}

AdminGUI holds the admin GUI configuration

type AdminGUISecrets

type AdminGUISecrets struct {
	// Password is the admin GUI login password
	Password string `yaml:"password"`
}

AdminGUISecrets holds admin GUI secrets

type AssertionConfig added in v0.5.7

type AssertionConfig struct {
	// Scopes maps credential scope names to their assertion configuration
	Scopes map[string]AssertionScope `yaml:"scopes,omitempty" doc_key:"credential scope"`
}

AssertionConfig groups assertion credential scopes.

type AssertionScope added in v0.5.7

type AssertionScope struct {
	// AuthProvider is the auth provider for this credential type (saml or oidc)
	AuthProvider string `yaml:"auth_provider" validate:"required,oneof=saml oidc"`
}

AssertionScope configures a credential type backed by authentication assertions. The data comes directly from the SAML attributes or OIDC claims.

type AttributeConfig

type AttributeConfig struct {
	// Claim is the target claim name (supports dot-notation for nesting)
	Claim string `yaml:"claim" validate:"required" doc_example:"\"identity.given_name\""`

	// Required indicates if this attribute must be present in the assertion/response
	Required bool `yaml:"required" default:"false"`

	// Transform is an optional transformation to apply
	// Supported: "lowercase", "uppercase", "trim", "country_alpha2", "country_alpha3"
	Transform string `yaml:"transform,omitempty" validate:"omitempty,oneof=lowercase uppercase trim country_alpha2 country_alpha3"`

	// Default is an optional default value if attribute is missing
	Default string `yaml:"default,omitempty"`

	// AsArray wraps a scalar value in a single-element array before setting the claim.
	// No-op when the value is already a slice (e.g. multi-valued OIDC claim).
	AsArray bool `yaml:"as_array,omitempty"`
}

AttributeConfig defines how a single external attribute maps to a credential claim Generic across protocols (SAML, OIDC, etc.) - uses protocol-specific identifiers as keys

type AttributeMapping added in v0.5.7

type AttributeMapping map[string]AttributeConfig

AttributeMapping maps external attribute names to claim configurations. Keys are protocol-specific identifiers (SAML OIDs, OIDC claim names, etc.), values define how each attribute maps to a credential claim.

type AuditLog

type AuditLog struct {
	// Enable enables audit logging
	Enable bool `yaml:"enable" default:"false"`
	// Destinations is the list of log destinations (console/stdout, file path, or HTTP URL)
	Destinations []string `` /* 147-byte string literal not displayed */
	// FileSyncInterval controls fsync behavior for file destinations.
	// 0 = fsync after every write (strict durability, lower throughput).
	// >0 = periodic batched fsync at the given interval (better throughput, bounded data-loss window).
	// Has no effect on console or webhook destinations.
	FileSyncInterval time.Duration `yaml:"file_sync_interval" default:"5s"`
}

AuditLog holds audit log configuration for multiple destinations

type AuthProvidersSecrets added in v0.5.7

type AuthProvidersSecrets struct {
	OIDC OIDCRPSecrets `yaml:"oidc,omitempty"`
}

AuthProvidersSecrets holds secrets for auth providers

type AuthScopeEntry added in v0.6.5

type AuthScopeEntry struct {
	// AuthClaims lists the identity claims to extract from this credential type.
	AuthClaims []string `yaml:"auth_claims" validate:"required,min=1" doc_example:"[given_name, family_name, birth_date]"`
}

AuthScopeEntry configures per-scope authentication requirements for OpenID4VP. Each entry represents one acceptable credential type the wallet can present.

type AuthorizationPageCSSConfig

type AuthorizationPageCSSConfig struct {
	// CustomCSS is inline CSS that will be injected into the authorization page
	// Allows deployers to override default styling without modifying templates
	CustomCSS string `yaml:"custom_css,omitempty"`

	// CSSFile is a path to an external CSS file to include
	// If both CustomCSS and CSSFile are provided, both are included
	CSSFile string `yaml:"css_file,omitempty"`

	// Theme sets predefined color scheme: "light" (default), "dark", "blue", "purple"
	Theme string `yaml:"theme,omitempty" validate:"omitempty,oneof=light dark blue purple" default:"light"`

	// PrimaryColor overrides the primary brand color
	PrimaryColor string `yaml:"primary_color,omitempty" doc_example:"\"#667eea\""`

	// SecondaryColor overrides the secondary brand color
	SecondaryColor string `yaml:"secondary_color,omitempty" doc_example:"\"#764ba2\""`

	// LogoURL provides a URL to a custom logo image
	LogoURL string `yaml:"logo_url,omitempty"`

	// Title overrides the page title (default: "Wallet Authorization")
	Title string `yaml:"title,omitempty"`

	// Subtitle overrides the page subtitle
	Subtitle string `yaml:"subtitle,omitempty"`
}

AuthorizationPageCSSConfig allows deployers to customize the authorization page styling

type Branding

type Branding struct {
	// LogoPath is the file path to a custom logo PNG image; when empty, the built-in SUNET logo is used
	LogoPath string `yaml:"logo_path,omitempty" validate:"omitempty,image_png"`
	// FaviconPath is the file path to a custom favicon PNG image; when empty, the built-in SUNET favicon is used
	FaviconPath string `yaml:"favicon_path,omitempty" validate:"omitempty,image_png"`
}

Branding holds custom branding paths for logo and favicon

type CORS

type CORS struct {
	// AllowedOrigins is the list of allowed CORS origins
	AllowedOrigins []string `` /* 126-byte string literal not displayed */
}

CORS holds the CORS configuration

type Cfg

type Cfg struct {
	Common   *Common   `yaml:"common"`
	APIGW    *APIGW    `yaml:"apigw" validate:"omitempty"`
	Issuer   *Issuer   `yaml:"issuer" validate:"omitempty"`
	Verifier *Verifier `yaml:"verifier" validate:"omitempty"`
	Registry *Registry `yaml:"registry" validate:"omitempty"`
}

Cfg is the main configuration structure for this application

func (*Cfg) ApplySecrets

func (cfg *Cfg) ApplySecrets(secrets *Secrets)

ApplySecrets updates the configuration with values from the secrets file. Secret fields (OIDC client secrets, passwords, salts) in the main config are cleared and replaced by values from the secrets file. The Mongo URI is only set from the secrets file when the main config leaves it empty; TLS and certificate paths are not secrets and should always be defined in the main config file.

func (*Cfg) GetCredentialMetadata added in v0.5.7

func (c *Cfg) GetCredentialMetadata(scope string) *CredentialMetadata

GetCredentialMetadata returns the credential constructor for a given scope

func (*Cfg) GetFormatForScope

func (c *Cfg) GetFormatForScope(scope string) string

GetFormatForScope returns the credential format for the given scope key. Returns empty string if the scope is not found in credentials.

func (*Cfg) GetOpenID4VPAuth added in v0.5.7

func (c *Cfg) GetOpenID4VPAuth(scope string) *OpenID4VPCredentialAuth

GetOpenID4VPAuth returns the OpenID4VP authentication config for a credential type, or nil if not found.

func (*Cfg) LookupCredentialSources added in v0.5.7

func (c *Cfg) LookupCredentialSources(scope string) ([]CredentialSource, error)

LookupCredentialSources returns full data source information for a credential type across all data sources where it is configured. Returns an error if the credential type is not configured in any DataSource.

func (*Cfg) ResolveVCTUrls

func (cfg *Cfg) ResolveVCTUrls(apigwPublicURL string) error

ResolveVCTUrls computes the URL-based VCT for each credential metadata entry and stores it in VCTURL. VCTM.VCT, VCTMRaw, and Integrity are left unchanged — the served VCTM document preserves the original VCT identifier from the VCTM file (e.g. a URN). For local VCTMs the URL is built from apigwPublicURL + /type-metadata/{scope}. For external VCTMs the VCTMUrl is used.

func (*Cfg) VCTIdentifiersForScopes

func (c *Cfg) VCTIdentifiersForScopes(scopes []string) []string

VCTIdentifiersForScopes resolves a list of scope keys to the vct value actually embedded in issued credentials for that scope -- BuildCredentialWithSigner (pkg/sdjwtvc/methods.go) sets body["vct"] = vctm.VCT, the VCTM's own declared "vct" field, not the published type-metadata URL VCTUrlsForScopes returns (that URL only appears in credential_configurations_supported's issuer-metadata "vct", a different, cosmetic value from what's actually embedded in a credential). DCQL queries built from VCTUrlsForScopes instead of this never matched any real issued credential — confirmed live via a fresh test issuance (lpidproto PLAN.md workstream 7 task 7.5, finding 16).

func (*Cfg) VCTUrlsForScopes

func (c *Cfg) VCTUrlsForScopes(scopes []string) []string

VCTUrlsForScopes resolves a list of scope keys to their resolved VCT URLs. Scopes without a loaded VCTM are silently skipped.

type Common

type Common struct {
	// Production enables production mode
	Production *bool `yaml:"production" default:"true"`
	// Log is the logging configuration
	Log Log `yaml:"log"`
	// Mongo is the MongoDB configuration
	Mongo Mongo `yaml:"mongo" validate:"omitempty"`
	// SQL is the relational database configuration, used by services that
	// support a relational storage backend as an alternative to MongoDB.
	SQL sqlstore.SQL `yaml:"sql" validate:"omitempty"`
	// Tracing is the OpenTelemetry tracing configuration
	Tracing OTEL `yaml:"tracing" validate:"omitempty"`
	// Metrics is the OpenTelemetry metrics configuration
	Metrics OTEL `yaml:"metrics" validate:"omitempty"`
	// Kafka is the Kafka message broker configuration
	Kafka Kafka `yaml:"kafka" validate:"omitempty"`
	// SecretFilePath is the path to a separate YAML file containing secrets; when set, secret values in config.yaml are cleared and only non-empty fields from the secrets file are applied.
	SecretFilePath string `yaml:"secret_file_path,omitempty" doc_example:"\"/etc/vc/secrets.yaml\""`
	// SkipSecretsPermCheck disables file permission validation on the secrets file. Required for platforms like Fly.io that mount files as 0755.
	SkipSecretsPermCheck bool `yaml:"skip_secrets_perm_check" default:"false"`
	// HA configures high-availability mode. When Enable is true, caches use MongoDB
	// (Common.Mongo.URI) instead of in-memory storage so state is shared across instances.
	HA HAConfig `yaml:"ha" validate:"omitempty"`
	// CredentialRegistry configures an optional TS11 credential metadata
	// registry client, used as an add-on to (not a replacement for) the
	// existing vctm_file_path/vctm_url/mddl_file_path/mddl_url per-scope
	// configuration: disabled by default, so existing deployments are
	// unaffected until this is explicitly enabled and at least one scope
	// sets vct or doctype instead of a file/URL.
	CredentialRegistry CredentialRegistry `yaml:"credential_registry" validate:"omitempty"`

	// Branding holds custom branding configuration (logo and favicon paths)
	Branding Branding `yaml:"branding"`

	// CredentialMetadata maps OAuth2 scope values to their credential configuration, required by apigw, issuer, and verifier
	// Key: OAuth2 scope (e.g., "pid", "ehic", "diploma") - matches AuthorizationContext.Scope
	// Each entry contains the VCTM reference, format, and other configuration for that credential type
	CredentialMetadata map[string]*CredentialMetadata `yaml:"credential_metadata" validate:"omitempty,dive" doc_key:"credential scope"`
}

Common holds the shared configuration used across all services

type CommonSecrets

type CommonSecrets struct {
	Mongo MongoSecrets `yaml:"mongo,omitempty"`
	SQL   SQLSecrets   `yaml:"sql,omitempty"`
}

CommonSecrets holds secrets from the common section

type CompleteDocument

type CompleteDocument struct {
	Meta               *MetaData      `json:"meta,omitempty" bson:"meta" validate:"required"`
	IdentityMappingIDs []string       `json:"identity_mapping_ids,omitempty" bson:"identity_mapping_ids" validate:"required,min=1"`
	DocumentData       map[string]any `json:"document_data,omitempty" bson:"document_data" validate:"required"`
}

CompleteDocument is a generic type for upload

type CredentialDisplayConfig

type CredentialDisplayConfig struct {
	// Enable allows users to optionally view credential details before completing authorization
	// When enabled, a checkbox appears on the authorization page
	Enable bool `yaml:"enable" default:"false"`

	// RequireConfirmation forces users to review credentials before proceeding
	// When true, the credential display step is mandatory (checkbox is pre-checked and disabled)
	RequireConfirmation bool `yaml:"require_confirmation" default:"false"`

	// ShowRawCredential displays the raw VP token/credential in the display page
	// Useful for debugging and technical users
	ShowRawCredential bool `yaml:"show_raw_credential" default:"false"`

	// ShowClaims displays the parsed claims that will be sent to the RP
	// Recommended for transparency and user consent
	ShowClaims *bool `yaml:"show_claims" default:"true"`

	// AllowEdit allows users to redact certain claims before sending to RP (future feature)
	// Currently not implemented
	AllowEdit bool `yaml:"allow_edit,omitempty" default:"false"`
}

CredentialDisplayConfig controls whether and how credentials are displayed before being sent to RP

type CredentialMetadata added in v0.5.7

type CredentialMetadata struct {
	// VCTMFilePath is the path to a local VCTM JSON file.
	// When set, apigw will publish the VCTM at /type-metadata/:scope.
	// Used for every format except mso_mdoc.
	VCTMFilePath string `yaml:"vctm_file_path" json:"-" validate:"required_without_all=VCTMUrl MDDLFilePath MDDLUrl VCT Doctype"`
	// VCTMUrl is the URL where the VCTM is already published externally.
	// When set, the VCTM is fetched from this URL at startup for internal use
	// but NOT re-published by apigw.
	// Used for every format except mso_mdoc.
	VCTMUrl string `yaml:"vctm_url" json:"-" validate:"required_without_all=VCTMFilePath MDDLFilePath MDDLUrl VCT Doctype,omitempty,url"`

	// VCT is the vct claim value to resolve via Common.CredentialRegistry
	// (a TS11 registry client), used only when neither VCTMFilePath nor
	// VCTMUrl is set. Requires Common.CredentialRegistry.Enable - this
	// field being present in a scope's config does not itself turn
	// registry lookups on. Used for every format except mso_mdoc.
	VCT string `yaml:"vct,omitempty" json:"-" validate:"required_without_all=VCTMFilePath VCTMUrl MDDLFilePath MDDLUrl Doctype"`

	VCTM *sdjwtvc.VCTM `yaml:"-" json:"-"`

	// MDDLFilePath is the path to a local MDDL (mso_mdoc) schema JSON file,
	// as produced by registry-cli's mddl format generator.
	MDDLFilePath string `yaml:"mddl_file_path" json:"-" validate:"required_without_all=VCTMFilePath VCTMUrl MDDLUrl VCT Doctype"`
	// MDDLUrl is the URL where the MDDL schema is already published
	// externally. The mso_mdoc analogue of vctm_url.
	MDDLUrl string `yaml:"mddl_url" json:"-" validate:"required_without_all=VCTMFilePath VCTMUrl MDDLFilePath VCT Doctype,omitempty,url"`

	// Doctype is the mdoc doctype value to resolve via
	// Common.CredentialRegistry, used only when neither MDDLFilePath nor
	// MDDLUrl is set. Requires Common.CredentialRegistry.Enable, same as
	// VCT. Used only for mso_mdoc.
	Doctype string `yaml:"doctype,omitempty" json:"-" validate:"required_without_all=VCTMFilePath VCTMUrl MDDLFilePath MDDLUrl VCT"`

	MDDL *mdoc.MDDLSchema `yaml:"-" json:"-"`

	// MDDLRaw holds the raw JSON bytes of the MDDL document, passed inline
	// to the issuer at issuance time (mirrors VCTMRaw/VCTM for sd-jwt).
	MDDLRaw []byte `yaml:"-" json:"-"`

	// Format is the credential format to issue
	Format string `yaml:"format" json:"format" validate:"required" default:"dc+sd-jwt" doc_example:"\"dc+sd-jwt\""`
	// DisclosurePolicy configures the embedded disclosure policy for this credential type.
	// Per ARF 3.0 §6.6.2.8 and CIR 2024/2979 Annex III. Only applicable to QEAAs and PuB-EAAs (not PIDs).
	// When omitted, the metadata publishes policy_type "none" (no restrictions).
	DisclosurePolicy *openid4vci.EmbeddedDisclosurePolicy `yaml:"disclosure_policy,omitempty" json:"-" validate:"omitempty"`
	// Attributes maps claim names to their source fields and transformation rules for credential issuance
	Attributes map[string]map[string][]*string `yaml:"attributes" json:"attributes_v2" validate:"omitempty,dive,required"`

	// VCTMRaw holds the raw JSON bytes of the VCTM document for serving
	// via /type-metadata/:scope. Only populated for local VCTMs (VCTMFilePath).
	VCTMRaw []byte `yaml:"-" json:"-"`

	// Integrity is the SRI hash of the VCTM or MDDL document (e.g. "sha256-...").
	// Computed once in LoadCredentialSchema and used for vct#integrity in issued credentials.
	Integrity string `yaml:"-" json:"-"`

	// VCTURL is the published URL where the VCTM is served.
	// Set by ResolveVCTUrls for both local and external VCTMs.
	VCTURL string `yaml:"-" json:"-"`
	// contains filtered or unexported fields
}

func (*CredentialMetadata) DeclaredClaimNames added in v0.7.4

func (c *CredentialMetadata) DeclaredClaimNames() (map[string]bool, bool)

DeclaredClaimNames returns the top-level claim names this credential type declares, across both VCTM and MDDL. The boolean reports whether any metadata was available to derive them from - an empty set from a loaded document means "declares nothing", which is different from "nothing loaded", and callers must not treat the two alike.

Names are top-level because the claim maps produced by external identity providers are flat. For a VCTM the name is the first path segment, so a nested claim like ["address","street_address"] admits an "address" object and lets the existing pipeline handle its interior. For an MDDL it is the element ID, since document data is keyed by element directly rather than nested under the mdoc namespace - see MDDLSchema.Presentation.

func (*CredentialMetadata) GetAttributes added in v0.5.7

func (c *CredentialMetadata) GetAttributes() map[string]map[string][]*string

GetAttributes returns the derived attributes under a read lock.

func (*CredentialMetadata) GetIntegrity added in v0.5.7

func (c *CredentialMetadata) GetIntegrity() string

GetIntegrity returns the SRI integrity hash of the VCTM or MDDL document under a read lock.

func (*CredentialMetadata) GetMDDL added in v0.7.0

func (c *CredentialMetadata) GetMDDL() *mdoc.MDDLSchema

GetMDDL returns the cached MDDL schema under a read lock so it is safe to call concurrently with the background refresh loop.

func (*CredentialMetadata) GetMDDLRaw added in v0.7.0

func (c *CredentialMetadata) GetMDDLRaw() []byte

GetMDDLRaw returns the raw MDDL JSON bytes under a read lock.

func (*CredentialMetadata) GetVCTM added in v0.5.7

func (c *CredentialMetadata) GetVCTM() *sdjwtvc.VCTM

GetVCTM returns the cached VCTM under a read lock so it is safe to call concurrently with the background refresh loop.

func (*CredentialMetadata) GetVCTMRaw added in v0.5.7

func (c *CredentialMetadata) GetVCTMRaw() []byte

GetVCTMRaw returns the raw VCTM JSON bytes under a read lock.

func (*CredentialMetadata) GetVCTURL added in v0.5.7

func (c *CredentialMetadata) GetVCTURL() string

GetVCTURL returns the published URL where the VCTM is served.

func (*CredentialMetadata) IsLocalMDDL added in v0.7.0

func (c *CredentialMetadata) IsLocalMDDL() bool

IsLocalMDDL returns true when the MDDL schema is loaded from a local file.

func (*CredentialMetadata) IsLocalVCTM added in v0.5.7

func (c *CredentialMetadata) IsLocalVCTM() bool

IsLocalVCTM returns true when the VCTM is loaded from a local file (i.e. apigw should publish it at /type-metadata/:scope).

func (*CredentialMetadata) LoadCredentialSchema added in v0.7.0

func (c *CredentialMetadata) LoadCredentialSchema(ctx context.Context, scope string, registry ts11client.Client) error

LoadCredentialSchema loads this scope's credential schema — a VCTM for every format except mso_mdoc, which instead loads an MDDL schema (registry-cli's mso_mdoc analogue of VCTM). The scope parameter is used only for error messages. registry is consulted only when this scope has no VCTMFilePath/VCTMUrl (or MDDLFilePath/MDDLUrl) and instead sets VCT (or Doctype) - it may be nil, in which case a scope relying on VCT/Doctype fails with a clear error rather than silently having no schema.

type CredentialOfferWallets

type CredentialOfferWallets struct {
	// Label is the display label for the wallet
	Label string `yaml:"label" validate:"required"`
	// RedirectURI is the wallet redirect URI
	RedirectURI string `yaml:"redirect_uri" validate:"required" doc_example:"\"eudi-wallet://credential-offer\""`
}

CredentialOfferWallets holds wallet redirect configuration

type CredentialOffers

type CredentialOffers struct {
	// IssuerURL is the issuer URL for credential offers
	IssuerURL string `yaml:"issuer_url" validate:"required"`
	// Wallets holds wallet redirect configurations
	Wallets map[string]CredentialOfferWallets `yaml:"wallets" validate:"required" doc_key:"wallet name"`
}

CredentialOffers holds credential offer configurations

type CredentialRegistry added in v0.7.1

type CredentialRegistry struct {
	// Enable turns on registry-backed resolution for any scope that sets
	// vct or doctype instead of a local file/URL. Existing
	// vctm_file_path/vctm_url/mddl_file_path/mddl_url-configured scopes
	// are entirely unaffected either way.
	Enable bool `yaml:"enable" default:"false"`
	// Registries is an ordered list of logical (independent) registries.
	// A later entry overrides an earlier one for the same vct/doctype.
	// Required if Enable is true.
	Registries []CredentialRegistryLogical `yaml:"registries" validate:"required_if=Enable true,omitempty,dive"`
	// RefreshInterval controls how long a registry's discovery index is
	// trusted before being re-fetched. Zero means fetch once and cache
	// forever for the lifetime of this process.
	RefreshInterval time.Duration `yaml:"refresh_interval" default:"1h"`
}

CredentialRegistry configures an optional TS11 credential metadata registry client (github.com/sirosfoundation/go-ts11client), disabled by default. When enabled, Registries is an ordered list of logical registries: a later entry overrides an earlier one for the same vct/doctype, so distinct registries are tried in that order rather than raced - only the mirrors within a single logical registry are queried concurrently, first hit wins, since only mirrors are expected to hold identical content.

func (*CredentialRegistry) NewClient added in v0.7.1

func (cr *CredentialRegistry) NewClient() (ts11client.Client, error)

NewClient builds a ts11client.Client from this configuration, or returns (nil, nil) when Enable is false - callers pass the (possibly nil) result straight through to CredentialMetadata.LoadCredentialSchema, which treats a nil registry as "not configured" rather than a special case.

type CredentialRegistryEndpoint added in v0.7.1

type CredentialRegistryEndpoint struct {
	// BaseURL is the registry's origin, e.g. "https://registry.siros.org".
	BaseURL string `yaml:"base_url" validate:"required,url" doc_example:"\"https://registry.siros.org\""`
	// Timeout bounds each HTTP request to this registry.
	Timeout time.Duration `yaml:"timeout" default:"10s"`
}

CredentialRegistryEndpoint identifies one TS11 registry endpoint to query.

type CredentialRegistryLogical added in v0.7.1

type CredentialRegistryLogical struct {
	// Mirrors is the set of endpoints serving this logical registry's
	// content. At least one is required.
	Mirrors []CredentialRegistryEndpoint `yaml:"mirrors" validate:"required,min=1,dive"`
}

CredentialRegistryLogical is one independent TS11 registry, optionally served by more than one mirror endpoint holding equivalent content, queried concurrently and raced - first hit wins.

type CredentialSource added in v0.5.7

type CredentialSource struct {
	DataSource   DataSourceType
	AuthProvider string
	RemoteName   string // only for external_api
}

CredentialSource describes where a credential's data comes from and how the user authenticates.

type DataSourceType added in v0.5.7

type DataSourceType string

DataSourceType identifies which data source a credential type belongs to.

const (
	DataSourceDatastore   DataSourceType = "datastore"
	DataSourceAssertion   DataSourceType = "assertion"
	DataSourceExternalAPI DataSourceType = "external_api"
)

type DataSources added in v0.5.7

type DataSources struct {
	// Datastore configures credential types backed by a pre-loaded datastore (e.g. MongoDB)
	Datastore DatastoreConfig `yaml:"datastore,omitempty"`

	// Assertion configures credential types backed by authentication assertions
	// (SAML attributes or OIDC claims)
	Assertion AssertionConfig `yaml:"assertion,omitempty"`

	// ExternalAPI configures credential types backed by an external API
	// Each credential references a named remote defined in APIGW.Remotes
	ExternalAPI ExternalAPIConfig `yaml:"external_api,omitempty"`
}

DataSources groups all data source configurations for credential issuance. Each key under a data source is a credential type.

func (*DataSources) LookupCredentialSources added in v0.5.7

func (ds *DataSources) LookupCredentialSources(credentialType string) ([]CredentialSource, error)

LookupCredentialSources finds all data sources where a credential type is configured. A credential type can appear in multiple data sources with different auth providers. Returns an error if the credential type is not found in any data source.

func (*DataSources) ResolveDataSource added in v0.5.7

func (ds *DataSources) ResolveDataSource(credentialType, authProvider string) (CredentialSource, error)

ResolveDataSource returns the data source for a credential type given the auth provider that was used. A credential can exist in multiple data sources but only one will have the matching auth provider.

type DatastoreConfig added in v0.5.7

type DatastoreConfig struct {
	// Scopes maps credential scope names to their datastore configuration
	Scopes map[string]DatastoreScope `yaml:"scopes,omitempty" doc_key:"credential scope"`

	// Import configures automatic data import from JSON files at startup.
	// When configured, APIGW reads JSON files and imports them into the
	// datastore on first startup (skipped if data already exists).
	Import *DatastoreImport `yaml:"import,omitempty"`
}

DatastoreConfig groups datastore credential scopes and optional data import settings.

type DatastoreImport added in v0.5.7

type DatastoreImport struct {
	// FilePaths lists JSON files to import into the datastore.
	// Each JSON file should contain a map of person IDs to CompleteDocument objects.
	// Import is skipped if the datastore already contains data.
	FilePaths []string `yaml:"file_paths" validate:"required,min=1" doc_example:"[\"./bootstrapping/pid.json\", \"./bootstrapping/ehic.json\"]"`

	// Users limits which person IDs to import. If empty, all persons are imported.
	Users []string `yaml:"users,omitempty" doc_example:"[\"100\", \"102\"]"`
}

DatastoreImport configures automatic import of JSON fixture data into the datastore.

type DatastoreScope added in v0.5.7

type DatastoreScope struct {
	// AuthProvider is the auth provider for this credential type (openid4vp, saml, or oidc)
	AuthProvider string `yaml:"auth_provider" validate:"required,oneof=openid4vp saml oidc"`

	// AuthClaims lists the normalized claim names used for datastore identity lookup
	// when auth_provider is saml or oidc. Not used for openid4vp (use AuthScopes instead).
	// These names must match the BSON field names under "identities." in the datastore.
	// Use attribute_mappings (in auth_providers) to normalize provider-specific attribute
	// names (e.g. SAML urn:oid:2.5.4.42, eIDAS date_of_birth) to these canonical names.
	// Available identity fields: given_name, family_name, birth_date, birth_place,
	// authentic_source_person_id, personal_administrative_number.
	AuthClaims []string `yaml:"auth_claims,omitempty" doc_example:"[given_name, family_name, birth_date]"`

	// AuthScopes maps credential scope keys to their per-scope authentication config.
	// Used only for openid4vp: the wallet must present a credential matching any one
	// of the listed scopes (OR logic). Each entry specifies which claims to extract
	// from that particular credential type.
	AuthScopes map[string]AuthScopeEntry `yaml:"auth_scopes,omitempty"`
}

DatastoreScope configures a credential type backed by the datastore.

func (*DatastoreScope) AuthScopeNames added in v0.6.5

func (d *DatastoreScope) AuthScopeNames() []string

AuthScopeNames returns the list of scope keys from AuthScopes.

type DigitalCredentialsConfig

type DigitalCredentialsConfig struct {
	// Enable toggles W3C Digital Credentials API support in browser
	Enable bool `yaml:"enable" default:"false"`

	// UseJAR enables JWT Authorization Request (JAR) for wallet communication
	// When true, request objects are signed JWTs instead of plain JSON
	UseJAR bool `yaml:"use_jar" default:"false"`

	// PreferredFormats specifies the order of preference for credential formats
	// Supported values: "vc+sd-jwt", "dc+sd-jwt", "mso_mdoc"
	// Default: ["vc+sd-jwt", "dc+sd-jwt", "mso_mdoc"]
	PreferredFormats []string `yaml:"preferred_formats,omitempty" default:"[\"vc+sd-jwt\", \"dc+sd-jwt\", \"mso_mdoc\"]"`

	// ResponseMode specifies the OpenID4VP response mode for DC API flows
	// Supported values: "dc_api.jwt" (encrypted), "direct_post.jwt" (signed), "direct_post"
	// Default: "dc_api.jwt"
	ResponseMode string `yaml:"response_mode,omitempty" validate:"omitempty,oneof=dc_api.jwt direct_post.jwt direct_post" default:"dc_api.jwt"`

	// AllowQRFallback enables automatic fallback to QR code if DC API is unavailable
	// Default: true
	AllowQRFallback *bool `yaml:"allow_qr_fallback" default:"true"`

	// DeepLinkScheme for mobile wallet integration
	DeepLinkScheme string `yaml:"deep_link_scheme,omitempty" doc_example:"\"eudi-wallet://\""`
}

DigitalCredentialsConfig holds W3C Digital Credentials API configuration

type Document

type Document struct {
	Meta         *MetaData `json:"meta,omitempty" bson:"meta" validate:"required"`
	DocumentData any       `json:"document_data" bson:"document_data" validate:"required"`
}

Document is a generic type for get document

type DocumentList

type DocumentList struct {
	Meta *MetaData `json:"meta,omitempty" bson:"meta" validate:"required"`
}

DocumentList is a generic type for document list

type ExternalAPIConfig added in v0.5.7

type ExternalAPIConfig struct {
	// Scopes maps credential scope names to their external API configuration
	Scopes map[string]ExternalAPIScope `yaml:"scopes,omitempty" doc_key:"credential scope"`
}

ExternalAPIConfig groups external API credential scopes.

type ExternalAPIScope added in v0.5.7

type ExternalAPIScope struct {
	// Remote is the name of a remote defined in Remotes
	Remote string `yaml:"remote" validate:"required"`

	// AuthProvider is the auth provider to identify the user (saml or oidc)
	AuthProvider string `yaml:"auth_provider" validate:"required,oneof=saml oidc"`

	// AttributeMapping defines how to map API response data to credential claims
	AttributeMapping AttributeMapping `yaml:"attribute_mapping,omitempty" doc_key:"attribute"`
}

ExternalAPIScope configures a credential type backed by an external API.

type GRPCClientTLS

type GRPCClientTLS struct {
	// Addr is the gRPC server address
	Addr string `yaml:"addr" validate:"required" doc_example:"\"issuer:8090\""`
	// TLS enables TLS
	TLS bool `yaml:"tls" default:"false"`
	// CertFilePath is the client certificate for mTLS
	CertFilePath string `yaml:"cert_file_path"`
	// KeyFilePath is the client private key for mTLS
	KeyFilePath string `yaml:"key_file_path"`
	// CAFilePath is the CA certificate to verify the server
	CAFilePath string `yaml:"ca_file_path"`
	// ServerName is the server name for TLS verification (optional)
	ServerName string `yaml:"server_name"`
}

GRPCClientTLS holds mTLS configuration for gRPC client connections

type GRPCServer

type GRPCServer struct {
	// Addr is the gRPC server listen address
	Addr string `yaml:"addr" validate:"required" default:":8090"`
	// TLS holds the mTLS configuration
	TLS GRPCTLS `yaml:"tls,omitempty"`
}

GRPCServer holds the gRPC server configuration

type GRPCTLS

type GRPCTLS struct {
	Enable                    bool              `yaml:"enable" default:"false"`
	CertFilePath              string            `yaml:"cert_file_path" validate:"required_if=Enable true" default:"/pki/grpc_server.crt"` // Server certificate
	KeyFilePath               string            `yaml:"key_file_path" validate:"required_if=Enable true" default:"/pki/grpc_server.key"`  // Server private key
	ClientCAPath              string            `yaml:"client_ca_path" validate:"required_if=Enable true" default:"/pki/client_ca.crt"`   // CA to verify client certificates (for mTLS)
	AllowedClientFingerprints map[string]string `yaml:"allowed_client_fingerprints" doc_example:"a1b2c3...: issuer-prod"`                 // SHA256 fingerprint -> friendly name
	AllowedClientDNs          map[string]string `yaml:"allowed_client_dns" doc_example:"apigw-prod: CN=apigw,O=SUNET"`                    // Friendly name -> Certificate Subject DN
}

GRPCTLS holds the mTLS configuration for gRPC server

type HAConfig

type HAConfig struct {
	// Enable enables HA mode; when true caches are backed by MongoDB instead of in-memory storage.
	Enable bool `yaml:"enable" default:"false"`
	// CacheDatabaseName is the MongoDB database name used for caches.
	CacheDatabaseName string `yaml:"cache_database_name" default:"vc_cache"`
}

HAConfig holds the high-availability configuration

type Identity

type Identity struct {
	// required: true
	// example: 65636cbc-c03f-11ee-8dc4-67135cc9bd8a
	AuthenticSourcePersonID string `json:"authentic_source_person_id,omitempty" bson:"authentic_source_person_id" validate:"required,max=128,printascii"`

	// required: true
	// example: Svensson
	FamilyName string `json:"family_name" bson:"family_name" validate:"required,min=1,max=100,printascii"`

	// required: true
	// example: Magnus
	GivenName string `json:"given_name" bson:"given_name" validate:"required,min=1,max=100,printascii"`

	// required: true
	// example: 1970-01-01 TODO: Day, month, and year?
	BirthDate string `json:"birth_date" bson:"birth_date" validate:"required,datetime=2006-01-02,printascii"`

	// required: true
	// example: Stockholm
	BirthPlace string `json:"birth_place,omitempty" bson:"birth_place,omitempty" validate:"omitempty,min=2,max=100,printascii"`

	// required: true
	// example: SE
	Nationality []string `json:"nationality,omitempty" bson:"nationality,omitempty" validate:"omitempty,dive,iso3166_1_alpha2"`

	// required: false
	// example: <personnummer>
	PersonalAdministrativeNumber string `` /* 140-byte string literal not displayed */

	// required: false
	// example: facial image compliant with ISO 19794-5 or ISO 39794 specifications
	Picture string `json:"picture,omitempty" bson:"picture,omitempty"`

	BirthFamilyName string `json:"birth_family_name,omitempty" bson:"birth_family_name,omitempty" validate:"omitempty,min=1,max=100,printascii"`

	BirthGivenName string `json:"birth_given_name,omitempty" bson:"birth_given_name,omitempty" validate:"omitempty,min=1,max=100,printascii"`

	// required: false
	// example: 0 = not known, 1 = male, 2 = female, ...
	Sex string `json:"sex,omitempty" bson:"sex,omitempty" validate:"omitempty,oneof=0 1 2 3 4 5 6 7 8 9"`

	// required: false
	// example: <email-address>
	EmailAddress string `json:"email_address,omitempty" bson:"email_address,omitempty" validate:"omitempty,email"`

	// required: false
	// example: <+mobile-phone-number>
	MobilePhoneNumber string `json:"mobile_phone_number,omitempty" bson:"mobile_phone_number,omitempty" validate:"omitempty,e164"`

	// required: false
	// example: 221b Baker street
	ResidentAddress string `json:"resident_address,omitempty" bson:"resident_address,omitempty" validate:"omitempty,printascii"`

	// required: false
	// example: Baker street
	ResidentStreetAddress string `` /* 127-byte string literal not displayed */

	// required: false
	// example: 221b
	ResidentHouseNumber string `json:"resident_house_number,omitempty" bson:"resident_house_number,omitempty" validate:"omitempty,printascii"`

	// required: false
	// example: W1U 6SG
	ResidentPostalCode string `json:"resident_postal_code,omitempty" bson:"resident_postal_code,omitempty" validate:"omitempty,printascii"`

	// required: false
	// example: London
	ResidentCity string `json:"resident_city,omitempty" bson:"resident_city,omitempty" validate:"omitempty,printascii"`
	// required: false
	// example: england
	ResidentState string `json:"resident_state,omitempty" bson:"resident_state,omitempty" validate:"omitempty,printascii"`
	// required: false
	// example: England
	ResidentCountry string `json:"resident_country,omitempty" bson:"resident_country,omitempty" validate:"omitempty,iso3166_1_alpha2"`

	AgeOver14 string `json:"age_over_14,omitempty" bson:"age_over_14,omitempty"`

	AgeOver16 bool `json:"age_over_16,omitempty" bson:"age_over_16,omitempty"`

	AgeOver18 bool `json:"age_over_18,omitempty" bson:"age_over_18,omitempty"`

	AgeOver21 bool `json:"age_over_21,omitempty" bson:"age_over_21,omitempty"`

	AgeOver65 bool `json:"age_over_65,omitempty" bson:"age_over_65,omitempty"`

	AgeInYears int `json:"age_in_years,omitempty" bson:"age_in_years,omitempty"`

	AgeBirthYear int `json:"age_birth_year,omitempty" bson:"age_birth_year,omitempty"`

	// required: false
	// example:
	IssuingAuthority string `json:"issuing_authority,omitempty" bson:"issuing_authority,omitempty" validate:"omitempty,printascii"`
	// required: false
	// example:
	IssuingCountry string `json:"issuing_country,omitempty" bson:"issuing_country,omitempty" validate:"omitempty,iso3166_1_alpha2"`

	// required: false
	// example: Date (and if possible time)
	ExpiryDate string `json:"expiry_date,omitempty" bson:"expiry_date,omitempty" validate:"omitempty,datetime=2006-01-02"`

	IssuanceDate string `json:"issuance_date,omitempty" bson:"issuance_date,omitempty"`

	// required: false
	// example:
	DocumentNumber string `json:"document_number,omitempty" bson:"document_number,omitempty" validate:"omitempty,max=128,printascii"`

	// required: false
	// example:
	IssuingJurisdiction string `json:"issuing_jurisdiction,omitempty" bson:"issuing_jurisdiction,omitempty" validate:"omitempty,max=128,printascii"`
}

Identity identifies a person

func (*Identity) GetAgeInYears

func (i *Identity) GetAgeInYears() (int, error)

func (*Identity) GetOver14

func (i *Identity) GetOver14() (bool, error)

func (*Identity) GetOver16

func (i *Identity) GetOver16() (bool, error)

func (*Identity) GetOver18

func (i *Identity) GetOver18() (bool, error)

func (*Identity) GetOver21

func (i *Identity) GetOver21() (bool, error)

func (*Identity) GetOver65

func (i *Identity) GetOver65() (bool, error)

func (*Identity) Marshal

func (i *Identity) Marshal() (map[string]any, error)

Marshal marshals the document to a map

Example
package main

import (
	"fmt"

	"github.com/SUNET/vc/pkg/model"
)

func main() {
	identity := &model.Identity{
		FamilyName: "Svensson",
		GivenName:  "Magnus",
		BirthDate:  "1970-01-01",
	}

	doc, err := identity.Marshal()
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Println("family_name:", doc["family_name"])
	fmt.Println("given_name:", doc["given_name"])
	fmt.Println("birth_date:", doc["birth_date"])
}
Output:
family_name: Svensson
given_name: Magnus
birth_date: 1970-01-01

type IdentityMapping added in v0.5.7

type IdentityMapping struct {
	// AuthenticSourcePersonID is the unique identifier for this entity within the authentic source
	AuthenticSourcePersonID string `json:"authentic_source_person_id" bson:"authentic_source_person_id" validate:"required,max=128,printascii"`

	// AuthenticSource is the source system that owns this identity
	AuthenticSource string `json:"authentic_source" bson:"authentic_source" validate:"required,max=128,printascii"`

	// Attributes holds identity attributes used for resolution (e.g. family_name, given_name, birth_date)
	Attributes map[string]string `json:"attributes,omitempty" bson:"attributes" validate:"omitempty,dive,keys,safe_key,endkeys"`

	// CreatedAt is the timestamp when the mapping was created
	CreatedAt time.Time `json:"created_at" bson:"created_at"`
}

IdentityMapping represents an identity stored in the "identity_mappings" collection. Documents reference these by AuthenticSourcePersonID in their IdentityMappingIDs []string field.

type IdentityMappingImport added in v0.5.7

type IdentityMappingImport struct {
	// FilePaths lists JSON files containing identity mappings to import.
	// Each JSON file should contain a map of person IDs to arrays of IdentityMapping objects.
	// Import is skipped if the identity mappings collection already contains data.
	FilePaths []string `yaml:"file_paths" validate:"required,min=1" doc_example:"[\"./bootstrapping/identity_mappings.json\"]"`

	// Users limits which person IDs to import. If empty, all persons are imported.
	Users []string `yaml:"users,omitempty" doc_example:"[\"100\", \"102\"]"`
}

IdentityMappingImport configures automatic import of identity mappings at startup.

type Issuer

type Issuer struct {
	// APIServer is the HTTP API server configuration
	APIServer APIServer `yaml:"api_server" validate:"required"`
	// GRPCServer is the gRPC server configuration
	GRPCServer GRPCServer `yaml:"grpc_server" validate:"required"`
	// KeyConfig is the signing key configuration
	KeyConfig *pki.KeyConfig `yaml:"key_config" validate:"required"`
	// JWTAttribute holds the JWT credential attribute configuration
	JWTAttribute JWTAttribute `yaml:"jwt_attribute" validate:"required"`
	// IssuerURL is the issuer identifier URL
	IssuerURL string `yaml:"issuer_url" validate:"required" doc_example:"\"https://issuer.sunet.se\""`
	// RegistryClient is the registry gRPC client config
	RegistryClient GRPCClientTLS `yaml:"registry_client" validate:"omitempty"`
	// MDoc holds mDL/mdoc configuration
	MDoc *MDocConfig `yaml:"mdoc" validate:"omitempty"`
	// AuditLog holds audit log configuration
	AuditLog *AuditLog `yaml:"audit_log" validate:"omitempty"`
	// SignMetadataRateLimit configures the rate limiter for the SignMetadata gRPC endpoint.
	// In HA setups each APIGW node refreshes two documents (VCI+OAuth2), so the defaults
	// should accommodate the expected cluster size. Default: 2 req/s, burst 20.
	SignMetadataRateLimit SignMetadataRateLimitConfig `yaml:"sign_metadata_rate_limit"`
	// PseudonymSeed, if true, makes the issuer attach a random seed as the pseudonym_seed claim.
	PseudonymSeed *bool `yaml:"pseudonym_seed" validate:"omitempty"`
	// AccessCertificate configures the EUDI access certificate (WRPAC) the issuer presents to wallets, optionally with its own key separate from KeyConfig. Off by default; deployments outside an ARF trust framework are unaffected.
	AccessCertificate *IssuerAccessCertificate `yaml:"access_certificate,omitempty"`
}

Issuer holds the configuration for the Issuer service that signs and issues verifiable credentials

func (*Issuer) AccessCertificateKeyConfig added in v0.7.4

func (i *Issuer) AccessCertificateKeyConfig() *pki.KeyConfig

AccessCertificateKeyConfig returns the access certificate's own key configuration, or nil when the issuer has not been given one.

A nil result is the signal to fall back to the credential key, which is a degraded but supported configuration - see IssuerAccessCertificate.

func (*Issuer) ValidateAccessCertificate added in v0.7.4

func (i *Issuer) ValidateAccessCertificate(leaf *x509.Certificate, now time.Time) error

ValidateAccessCertificate checks the certificate the issuer signs metadata with against the WRPAC profile.

now is injected so the validity-window check is testable; callers pass time.Now().

type IssuerAccessCertificate added in v0.7.4

type IssuerAccessCertificate struct {
	// Validate enforces the WRPAC certificate profile at startup: keyUsage
	// must include nonRepudiation (contentCommitment), subjectAltName must
	// carry contact information (URI or email), and certificatePolicies must
	// contain a WRPAC policy OID. Startup fails when the certificate does
	// not conform.
	Validate bool `yaml:"validate,omitempty"`
	// AllowedPolicyOIDs optionally narrows which WRPAC certificate policy
	// OIDs are accepted, for a deployment that must assert a specific
	// assurance level. When empty, all four TS 119 411-8 WRPAC policy OIDs
	// are accepted.
	AllowedPolicyOIDs []string `yaml:"allowed_policy_oids,omitempty" doc_example:"[\"0.4.0.194118.1.3\",\"0.4.0.194118.1.4\"]"`
	// KeyConfig is the signing key and certificate chain for the access
	// certificate. When set, issuer metadata is signed with this key and
	// the chain is advertised in the JWT's x5c header; credentials continue
	// to be signed with Issuer.KeyConfig.
	KeyConfig *pki.KeyConfig `yaml:"key_config,omitempty" validate:"omitempty"`
}

IssuerAccessCertificate configures the EUDI Relying Party access certificate (WRPAC, ETSI TS 119 411-8) the issuer presents to wallets, and optionally gives it its own key.

Under CIR (EU) 2025/848 a PID or attestation provider is a registered wallet-relying party in its own right, so the certificate that authenticates the issuer to a wallet is a WRPAC, governed by the same profile the verifier uses.

The access certificate is kept separate from Issuer.KeyConfig on purpose. The credential key is published in /jwks and signs credentials; an mdoc document-signer certificate chains to an IACA under an entirely different profile; and the two have independent rotation lifecycles. Conflating them means a WRPAC rotation forces a credential-key rotation.

When KeyConfig is unset the issuer falls back to signing metadata with the credential key, logging a warning. That keeps an existing single-key deployment booting across an upgrade rather than failing on start.

type IssuerMetadata

type IssuerMetadata struct {
	// RegistrationCertificate optionally points at a Registrar-issued WRPRC to advertise in the issuer_info metadata parameter, attesting what this Credential Issuer is registered to provide.
	//
	// Under CIR (EU) 2025/848 a PID or attestation provider is a registered
	// wallet-relying party in its own right, so the document is the same kind
	// a verifier presents in verifier_info - see Verifier.RegistrationCertificate.
	// The signature and the issuing chain are verified at startup, exactly as
	// on the verifier. The ARF RPRC_16 binding is not: it compares this
	// document against the presenting party's access certificate, which the
	// issuer service holds rather than the apigw, and the rule is not settled
	// enough to justify a cross-service check. A correctly-signed certificate
	// naming a different organisation would therefore be accepted, so
	// configure one that describes this deployment.
	//
	// Left unset by deployments outside an ARF trust framework.
	RegistrationCertificate *RegistrationCertificate `yaml:"registration_certificate,omitempty"`
	// AuthorizationServers lists the authorization server URLs
	AuthorizationServers []string `yaml:"authorization_servers" validate:"omitempty"`
	// DeferredCredentialEndpoint is the deferred credential endpoint
	DeferredCredentialEndpoint string `yaml:"deferred_credential_endpoint" validate:"omitempty"`
	// NotificationEndpoint is the notification endpoint
	NotificationEndpoint string `yaml:"notification_endpoint" validate:"omitempty"`
	// CryptographicBindingMethodsSupported lists the supported binding methods
	CryptographicBindingMethodsSupported []string `yaml:"cryptographic_binding_methods_supported" validate:"omitempty"`
	// CredentialSigningAlgValuesSupported lists the supported signing algorithms
	CredentialSigningAlgValuesSupported []string `yaml:"credential_signing_alg_values_supported" validate:"omitempty"`
	// ProofSigningAlgValuesSupported lists the supported proof algorithms
	ProofSigningAlgValuesSupported []string `yaml:"proof_signing_alg_values_supported" validate:"omitempty"`
	// CredentialResponseEncryption holds the response encryption configuration
	CredentialResponseEncryption *openid4vci.MetadataCredentialResponseEncryption `yaml:"credential_response_encryption" validate:"omitempty"`
	// BatchCredentialIssuance holds the batch issuance configuration
	BatchCredentialIssuance *openid4vci.BatchCredentialIssuance `yaml:"batch_credential_issuance" validate:"omitempty"`
	// Display holds the display metadata
	Display []openid4vci.MetadataDisplay `yaml:"display" validate:"omitempty"`
	// MdocIacasURI is the URL where IACA certificates are published for mDOC verification.
	// When configured, this is included in .well-known/openid-credential-issuer metadata
	// so verifiers can dynamically discover trust anchors for ISO 18013-5 credentials.
	MdocIacasURI string `yaml:"mdoc_iacas_uri" validate:"omitempty,url"`
}

IssuerMetadata holds the OpenID4VCI issuer metadata configuration

func (*IssuerMetadata) Generate

Generate generates issuer metadata from configuration. Returns unsigned metadata that should be signed on-demand in the endpoint handler for freshness.

type JWTAttribute

type JWTAttribute struct {
	// Issuer of the token
	Issuer string `yaml:"issuer" validate:"required" doc_example:"https://issuer.sunet.se"`

	// StaticHost is the static host of the issuer, expose static files, like pictures.
	StaticHost string `yaml:"static_host" validate:"omitempty"`

	// EnableNotBefore states the time not before which the token is valid
	EnableNotBefore bool `yaml:"enable_not_before" default:"false"`

	// Valid duration of the token in seconds
	ValidDuration int64 `yaml:"valid_duration" validate:"required_with=EnableNotBefore" default:"3600"`

	// VerifiableCredentialType URL
	VerifiableCredentialType string `yaml:"verifiable_credential_type" validate:"required" doc_example:"https://credential.sunet.se/identity_credential"`

	// Status status of the Verifiable Credential
	Status string `yaml:"status"`

	// Kid key id of the signing key
	Kid string `yaml:"kid"`
}

JWTAttribute holds the jwt attribute configuration. In a later state this should be placed under authentic source in order to issue credentials based on that configuration.

type Kafka

type Kafka struct {
	// Enable enables Kafka integration
	Enable bool `yaml:"enable" default:"false"`
	// Brokers is the list of Kafka broker addresses
	Brokers []string `yaml:"brokers" validate:"required_if=Enable true" doc_example:"[\"kafka0:9092\", \"kafka1:9092\"]"`
	// SASL configures SASL authentication for Kafka connections
	SASL *KafkaSASL `yaml:"sasl,omitempty"`
	// MTLS configures mutual TLS (mTLS) for Kafka broker connections
	MTLS MTLS `yaml:"mtls" validate:"omitempty"`
}

Kafka holds the Kafka message broker configuration

type KafkaSASL added in v0.6.4

type KafkaSASL struct {
	// Enable activates SASL authentication
	Enable bool `yaml:"enable" default:"false"`
	// Mechanism is the SASL mechanism (PLAIN, SCRAM-SHA-256, SCRAM-SHA-512)
	Mechanism string `yaml:"mechanism" validate:"required_if=Enable true,omitempty,oneof=PLAIN SCRAM-SHA-256 SCRAM-SHA-512" default:"SCRAM-SHA-512"`
	// Username is the SASL username
	Username string `yaml:"username" validate:"required_if=Enable true"`
	// Password is the SASL password
	Password string `yaml:"password" validate:"required_if=Enable true"`
}

KafkaSASL holds SASL authentication settings for Kafka

type LoadedRegistrationCertificate added in v0.7.4

type LoadedRegistrationCertificate struct {
	// JWT is the compact rc-wrp+jwt exactly as issued by the Registrar.
	JWT string
	// Format is the verifier_info format identifier to advertise.
	Format string
	// Claims is what the Registrar attested, extracted after the signature
	// was verified. Always populated for a successfully loaded certificate.
	Claims *WRPRCClaims
	// TrustEvaluated reports whether the issuing chain was actually checked
	// against configured Registrar roots. False means the document is
	// authentic but nothing establishes that we accept its issuer.
	TrustEvaluated bool
}

LoadedRegistrationCertificate holds a registration certificate read at startup, ready to be attached to outgoing authorization requests.

func (*LoadedRegistrationCertificate) IssuerInfo added in v0.7.4

IssuerInfo renders the certificate as OpenID4VCI issuer_info entries.

Same document and same shape as VerifierInfo; only the metadata parameter carrying it differs. A credential issuer is a registered wallet-relying party in its own right under CIR (EU) 2025/848, so what it presents to a wallet is the same registration certificate a verifier presents.

func (*LoadedRegistrationCertificate) VerifierInfo added in v0.7.4

VerifierInfo renders the certificate as OpenID4VP verifier_info entries.

No credential_ids are set: a registration certificate describes the Relying Party as a whole rather than any single requested credential, and per OpenID4VP an omitted credential_ids means the attestation applies to every credential in the request.

type Log

type Log struct {
	// FolderPath is the path to the log folder
	FolderPath string `yaml:"folder_path" doc_example:"\"/var/log/vc\""`
}

Log holds the logging configuration

type MDocConfig

type MDocConfig struct {
	// CertificateChainPath is the path to the PEM certificate chain
	// TODO(pki): Consider folding into pki.KeyConfig.ChainPath to unify certificate
	// chain loading with the standard key material configuration pattern.
	CertificateChainPath string `yaml:"certificate_chain_path" validate:"required"`
	// DefaultValidity is the default credential validity (default: 365 days)
	DefaultValidity time.Duration `yaml:"default_validity" default:"8760h"`
	// DigestAlgorithm is the digest algorithm: "SHA-256", "SHA-384", or "SHA-512"
	DigestAlgorithm string `yaml:"digest_algorithm" default:"SHA-256"`
}

MDocConfig holds mDL (ISO 18013-5) issuer configuration

type MTLS added in v0.6.4

type MTLS struct {
	// Enable enables mTLS for the connection
	Enable bool `yaml:"enable" default:"false"`
	// CACertPath is the path to a CA certificate for verifying the remote peer (optional; uses system roots if empty)
	CACertPath string `yaml:"ca_cert_path,omitempty"`
	// CertFilePath is the path to a client certificate for mutual authentication
	CertFilePath string `yaml:"cert_file_path" validate:"required_if=Enable true"`
	// KeyFilePath is the path to the client private key
	KeyFilePath string `yaml:"key_file_path" validate:"required_if=Enable true"`
	// InsecureSkipVerify disables certificate verification (TESTING ONLY — never use in production)
	InsecureSkipVerify bool `yaml:"insecure_skip_verify" default:"false"`
}

MTLS holds mutual TLS configuration for client connections (verifying peer + presenting own cert)

type MariaDBSecrets added in v0.7.1

type MariaDBSecrets struct {
	// Password is the MariaDB connection password
	Password string `yaml:"password"`
}

MariaDBSecrets holds the MariaDB connection password

type MetaData

type MetaData struct {
	// required: true
	// example: SUNET
	AuthenticSource string `json:"authentic_source,omitempty" bson:"authentic_source" validate:"required,max=128,printascii"`

	// Scope is the credential configuration ID scope
	// required: false
	// example: "ehic", "pda1"
	Scope string `json:"scope,omitempty" bson:"scope" validate:"required,max=128,printascii"`

	// required: false
	// example: 5e7a981c-c03f-11ee-b116-9b12c59362b9
	DocumentID string `json:"document_id,omitempty" bson:"document_id" validate:"omitempty,max=128,printascii"`

	// required: false
	// example: file://path/to/schema.json or http://example.com/schema.json
	// format: string
	DocumentDataValidationRef string `json:"document_data_validation,omitempty" bson:"document_data_validation" validate:"omitempty,max=128,printascii"`

	// CreatedAt is the timestamp when the document was created
	CreatedAt time.Time `json:"created_at" bson:"created_at"`

	// ValidNotAfter is an optional expiration timestamp for administrative purposes.
	// Documents past this time should not be used.
	ValidNotAfter *time.Time `json:"valid_not_after,omitempty" bson:"valid_not_after,omitempty"`
}

MetaData is a generic type for metadata

type Mongo

type Mongo struct {
	// URI is the MongoDB connection URI. Required when Common.SQL.Backend is
	// "mongo" (the default primary-store backend) or when Common.HA.Enable is
	// true (pkg/cache has no relational backend yet, so HA caching always
	// uses Mongo regardless of the primary store's backend). Enforced by a
	// Common-level struct validation rather than a plain "required" tag here,
	// since the requirement depends on sibling fields of Common, not of Mongo.
	URI string `yaml:"uri" validate:"omitempty" doc_example:"\"mongodb://user:password@mongo:27017/vc\""`
	// TLS enables TLS for the MongoDB connection.
	// Can also be enabled via the connection URI parameter "tls=true".
	TLS bool `yaml:"tls" default:"false"`
	// CAFilePath is the path to a PEM-encoded CA certificate used to verify
	// the MongoDB server's certificate. When empty, the system root CAs are used.
	CAFilePath string `yaml:"ca_file_path" validate:"omitempty"`
	// CertFilePath is the path to a PEM-encoded client certificate for mutual TLS (mTLS).
	CertFilePath string `yaml:"cert_file_path" validate:"required_with=KeyFilePath"`
	// KeyFilePath is the path to a PEM-encoded client private key for mutual TLS (mTLS).
	KeyFilePath string `yaml:"key_file_path" validate:"required_with=CertFilePath"`
}

Mongo holds the MongoDB configuration

func (*Mongo) MongoClientOptions

func (m *Mongo) MongoClientOptions() (*options.ClientOptions, error)

MongoClientOptions returns a *options.ClientOptions configured from the Mongo settings. It applies the connection URI and, when TLS is enabled, builds the appropriate *tls.Config (CA verification and/or mTLS client certificate).

type MongoSecrets

type MongoSecrets struct {
	// URI is the MongoDB connection string, which may include authentication credentials
	URI string `yaml:"uri"`
}

MongoSecrets holds the mongo connection URI (may contain credentials)

type OAuthServer

type OAuthServer struct {
	// TokenEndpoint is the OAuth2 token endpoint URL
	TokenEndpoint string `yaml:"token_endpoint" validate:"required" doc_example:"\"https://verifier.sunet.se/token\""`
	// Clients holds the OAuth2 client configurations
	Clients oauth2.Clients `yaml:"clients" validate:"required" doc_key:"client id"`
	// AllowUnverifiedClientAssertion enables accepting client_assertion (private_key_jwt)
	// WITHOUT signature verification. This is INSECURE and only intended for conformance
	// testing environments. When false (default), client_assertion is rejected.
	// TODO(security): Remove this flag once full RFC 7523 verification is implemented.
	AllowUnverifiedClientAssertion bool `yaml:"allow_unverified_client_assertion" default:"false"`
	// GrantTypes is the list of grant types this issuer supports.
	// Supported values: authorization_code, urn:ietf:params:oauth:grant-type:pre-authorized_code, refresh_token
	GrantTypes []string `` /* 239-byte string literal not displayed */
	// RefreshTokenDuration is the refresh token duration in seconds.
	// Only applicable when grant_types includes "refresh_token".
	RefreshTokenDuration int `yaml:"refresh_token_duration,omitempty" default:"86400"`
}

OAuthServer holds the OAuth2 server configuration

func (*OAuthServer) GenerateMetadata

func (cfg *OAuthServer) GenerateMetadata(ctx context.Context, issuerURL string) *oauth2.AuthorizationServerMetadata

GenerateMetadata generates OAuth2 metadata from configuration. Returns unsigned metadata that should be signed on-demand in the endpoint handler for freshness.

type OIDCAuthSecrets added in v0.5.7

type OIDCAuthSecrets struct {
	// ClientSecret is the OAuth2 client secret for the OIDC provider
	ClientSecret string `yaml:"client_secret,omitempty"`
}

OIDCAuthSecrets holds OIDC client secret for API auth

type OIDCOP added in v0.5.7

type OIDCOP struct {
	// Issuer is the OIDC Provider identifier that appears in ID tokens and discovery metadata.
	// This identifies the verifier as an OpenID Provider.
	// Must match the 'iss' claim in all issued ID tokens.
	Issuer string `yaml:"issuer" validate:"required" doc_example:"\"https://verifier.sunet.se\""`
	// SessionDuration is the session duration in seconds
	SessionDuration int `yaml:"session_duration" validate:"required" default:"3600"`
	// CodeDuration is the authorization code duration in seconds
	CodeDuration int `yaml:"code_duration" validate:"required" default:"300"`
	// AccessTokenDuration is the access token duration in seconds
	AccessTokenDuration int `yaml:"access_token_duration" validate:"required" default:"3600"`
	// IDTokenDuration is the ID token duration in seconds
	IDTokenDuration int `yaml:"id_token_duration" validate:"required" default:"3600"`
	// RefreshTokenDuration is the refresh token duration in seconds
	RefreshTokenDuration int `yaml:"refresh_token_duration" validate:"required" default:"86400"`
	// SubjectType is the subject type: "public" or "pairwise"
	SubjectType string `yaml:"subject_type" validate:"required,oneof=public pairwise"`
	// SubjectSalt is the salt for pairwise subject generation
	SubjectSalt string `yaml:"subject_salt" validate:"required"`
	// EnableUserInfo controls whether the verifier-OP advertises a userinfo_endpoint
	// in its discovery metadata and issues JWT access tokens (RFC 9068 at+jwt).
	// When true (default), the OP advertises userinfo_endpoint in discovery and
	// returns an access token alongside the ID token. The userinfo endpoint
	// is stateless: it validates the JWT signature and returns the embedded claims.
	// When false, only ID tokens are returned — no access_token or userinfo endpoint.
	EnableUserInfo bool `yaml:"enable_userinfo" default:"true"`
	// StaticClients is a list of pre-configured OIDC clients
	// These clients are checked in addition to dynamically registered clients
	StaticClients []StaticOIDCClient `yaml:"static_clients,omitempty"`
}

OIDCConfig holds OIDC-specific configuration for the verifier's role as an OpenID Provider. This configures how the verifier issues ID tokens and access tokens to relying parties. Note: This is NOT related to verifiable credential issuance (see IssuerConfig for VC issuance). The signing key is shared from the parent Verifier.KeyConfig.

type OIDCOPSecrets

type OIDCOPSecrets struct {
	// SubjectSalt is a secret value used to derive pairwise subject identifiers for OIDC clients
	SubjectSalt string `yaml:"subject_salt"`
	// StaticClients maps client_id to client_secret for static OIDC clients.
	// Only clients listed here will have their secrets applied; clients not
	// present in this map keep whatever value the main config provides (which
	// will be empty after ApplySecrets clears them).
	StaticClients map[string]string `yaml:"static_clients,omitempty" doc_example:"<client_id>: \"<client_secret>\""`
}

OIDCOPSecrets holds OIDC OP configuration secrets

type OIDCRP added in v0.5.7

type OIDCRP struct {
	// Enable turns on OIDC RP support (default: false)
	Enable bool `yaml:"enable" default:"false"`

	// Registration configures how the client obtains credentials from the OIDC Provider.
	// Exactly one of preconfigured or dynamic must be set:
	//   - preconfigured: pre-registered client_id and client_secret
	//   - dynamic: RFC 7591 dynamic client registration (credentials obtained at startup)
	Registration *OIDCRPRegistrationConfig `yaml:"registration" validate:"required_if=Enable true"`

	// RedirectURI is the callback URL where the OIDC Provider sends the authorization response
	RedirectURI string `yaml:"redirect_uri" validate:"required_if=Enable true" doc_example:"\"https://issuer.sunet.se/oidcrp/callback\""`

	// IssuerURL is the OIDC Provider's issuer URL for discovery
	// Used for .well-known/openid-configuration discovery
	IssuerURL string `yaml:"issuer_url" validate:"required_if=Enable true" doc_example:"\"https://accounts.google.com\""`

	// Scopes are the OAuth2/OIDC scopes to request
	Scopes []string `yaml:"scopes" validate:"required,min=1,dive,required" default:"[\"openid\", \"profile\", \"email\"]"`

	// SessionDuration is the maximum time in seconds an in-flight OIDC authorization flow
	// (state, nonce, PKCE verifier) may remain active before it expires
	SessionDuration int `yaml:"session_duration" validate:"required" default:"300"`

	// ClientName is a human-readable name for the OIDC client, shown during dynamic registration or consent
	ClientName string `yaml:"client_name,omitempty"`
	// ClientURI is a URL to the client's homepage, used for display during consent
	ClientURI string `yaml:"client_uri,omitempty"`
	// LogoURI is a URL to the client's logo image, shown during consent screens
	LogoURI string `yaml:"logo_uri,omitempty"`
	// Contacts is a list of email addresses for responsible parties of this client
	Contacts []string `yaml:"contacts,omitempty"`
	// TosURI is a URL to the client's Terms of Service document
	TosURI string `yaml:"tos_uri,omitempty"`
	// PolicyURI is a URL to the client's Privacy Policy document
	PolicyURI string `yaml:"policy_uri,omitempty"`

	// AttributeMapping normalizes OIDC claim names to canonical claim names.
	// Optional: when omitted, OIDC claims pass through as-is (standard names already match).
	// Which normalized attributes are used depends on the data source:
	//   - assertion: VCTM determines which go into the credential
	//   - datastore: auth_claims determines which are used for DB identity lookup
	AttributeMapping AttributeMapping `yaml:"attribute_mapping,omitempty" doc_key:"attribute"`
}

OIDCRP holds OIDC Relying Party configuration for credential issuance.

type OIDCRPDynamicRegistrationConfig

type OIDCRPDynamicRegistrationConfig struct {
	// Enable activates dynamic client registration
	Enable bool `yaml:"enable"`

	// InitialAccessToken is a bearer token for registration
	// Required by some OIDC Providers (e.g., Keycloak)
	InitialAccessToken string `yaml:"initial_access_token,omitempty" validate:"required_if=Enable true"`
}

OIDCRPDynamicRegistrationConfig configures RFC 7591 dynamic client registration. When set, client credentials are obtained automatically at startup and persisted in the database.

type OIDCRPDynamicSecrets

type OIDCRPDynamicSecrets struct {
	// InitialAccessToken is the bearer token required by the OP for dynamic client registration
	InitialAccessToken string `yaml:"initial_access_token"`
}

OIDCRPDynamicSecrets holds dynamic registration secrets

type OIDCRPPreconfiguredConfig

type OIDCRPPreconfiguredConfig struct {
	// Enable activates preconfigured client credentials
	Enable bool `yaml:"enable"`

	// ClientID is the OIDC client identifier
	ClientID string `yaml:"client_id" validate:"required_if=Enable true"`

	// ClientSecret is the OIDC client secret
	ClientSecret string `yaml:"client_secret" validate:"required_if=Enable true"`
}

OIDCRPPreconfiguredConfig holds pre-registered client credentials.

type OIDCRPPreconfiguredSecrets

type OIDCRPPreconfiguredSecrets struct {
	// ClientSecret is the shared secret for the pre-configured OIDC RP client
	ClientSecret string `yaml:"client_secret"`
}

OIDCRPPreconfiguredSecrets holds pre-registered client secrets

type OIDCRPRegistrationConfig

type OIDCRPRegistrationConfig struct {
	// Preconfigured uses pre-registered client credentials.
	// Set this when the client is already registered with the OIDC Provider.
	Preconfigured *OIDCRPPreconfiguredConfig `yaml:"preconfigured,omitempty" validate:"required_without=Dynamic,excluded_with=Dynamic"`

	// Dynamic uses RFC 7591 dynamic client registration.
	// Set this when the client should register itself at startup.
	Dynamic *OIDCRPDynamicRegistrationConfig `yaml:"dynamic,omitempty" validate:"required_without=Preconfigured,excluded_with=Preconfigured"`
}

OIDCRPRegistrationConfig configures how the client obtains its credentials.

type OIDCRPRegistrationSecrets

type OIDCRPRegistrationSecrets struct {
	Preconfigured *OIDCRPPreconfiguredSecrets `yaml:"preconfigured,omitempty"`
	Dynamic       *OIDCRPDynamicSecrets       `yaml:"dynamic,omitempty"`
}

OIDCRPRegistrationSecrets holds registration secrets

type OIDCRPSecrets

type OIDCRPSecrets struct {
	Registration OIDCRPRegistrationSecrets `yaml:"registration,omitempty"`
}

OIDCRPSecrets holds OIDC Relying Party secrets

type OTEL

type OTEL struct {
	// Enable activates OpenTelemetry tracing
	Enable bool `yaml:"enable" default:"false"`
	// Addr is the OTEL collector address
	Addr string `yaml:"addr" validate:"required_if=Enable true" doc_example:"\"jaeger:4318\""`
	// Timeout is the timeout in seconds
	Timeout int64 `yaml:"timeout" default:"10"`
}

OTEL holds the OpenTelemetry tracing configuration

type OpenID4VPConfig

type OpenID4VPConfig struct {
	// PresentationTimeout is the presentation timeout in seconds
	PresentationTimeout int `yaml:"presentation_timeout" validate:"required" default:"300"`
	// SupportedCredentials holds the supported credential configurations
	SupportedCredentials []SupportedCredentialConfig `yaml:"supported_credentials" validate:"required"`
	// PresentationRequestsDir is an optional directory with presentation request templates
	PresentationRequestsDir string `yaml:"presentation_requests_dir,omitempty"`
	// TokenEndpoint is the OAuth2 token endpoint URL used for VP token exchange
	TokenEndpoint string `yaml:"token_endpoint" validate:"required" doc_example:"\"https://verifier.sunet.se/token\""`
	// Clients holds the OAuth2 client configurations for RP interactions
	Clients oauth2.Clients `yaml:"clients" validate:"required" doc_key:"client id"`
}

OpenID4VPConfig holds OpenID4VP-specific configuration

func (*OpenID4VPConfig) GenerateMetadata added in v0.5.7

func (c *OpenID4VPConfig) GenerateMetadata(ctx context.Context, issuerURL string) *oauth2.AuthorizationServerMetadata

GenerateMetadata generates OAuth2 metadata from the OpenID4VP configuration. Returns unsigned metadata that should be signed on-demand in the endpoint handler for freshness.

func (*OpenID4VPConfig) GetPresentationRequestsDir

func (c *OpenID4VPConfig) GetPresentationRequestsDir() string

GetPresentationRequestsDir returns the presentation requests directory, or empty string if the config is nil.

func (*OpenID4VPConfig) GetSupportedCredentials

func (c *OpenID4VPConfig) GetSupportedCredentials() []SupportedCredentialConfig

GetSupportedCredentials returns the supported credentials, or nil if the config is nil.

type OpenID4VPCredentialAuth added in v0.5.7

type OpenID4VPCredentialAuth struct {
	// AuthScopes maps credential scope keys to per-scope auth config (claims to extract)
	AuthScopes map[string]AuthScopeEntry
}

OpenID4VPCredentialAuth holds per-credential OpenID4VP authentication requirements

type PKCS11

type PKCS11 struct {
	// ModulePath is the path to the PKCS#11 module
	ModulePath string `yaml:"module_path" default:"/usr/lib/softhsm/libsofthsm2.so"`
	// SlotID is the HSM slot ID
	SlotID uint `yaml:"slot_id" default:"0"`
	// PIN is the PIN for HSM access
	PIN string `yaml:"pin" validate:"required"`
	// KeyLabel is the key label in HSM
	KeyLabel string `yaml:"key_label" validate:"required"`
	// KeyID is the key ID in HSM
	KeyID string `yaml:"key_id" validate:"required"`
}

PKCS11 holds PKCS#11 HSM configuration for hardware security module integration

type PostgresSecrets added in v0.7.1

type PostgresSecrets struct {
	// Password is the Postgres connection password
	Password string `yaml:"password"`
}

PostgresSecrets holds the Postgres connection password

type RegistrationCertificate added in v0.7.4

type RegistrationCertificate struct {
	// FilePath is the path to the Registrar-issued WRPRC, a compact JWT with
	// media type "rc-wrp+jwt".
	FilePath string `yaml:"file_path,omitempty" doc_example:"\"/etc/vc/registration-certificate.jwt\""`
	// Format is the format identifier advertised alongside the certificate
	// in the verifier_info parameter. Defaults to "rc-wrp+jwt"; override
	// only for an ecosystem that has profiled a different identifier for
	// the same document.
	Format string `yaml:"format,omitempty" doc_example:"\"rc-wrp+jwt\""`
	// TrustedRootsPath optionally points at a PEM bundle of the Registrar's
	// root certificates. When set, the certificate's own x5c chain is
	// evaluated against it at startup. When unset, the document is still
	// signature-checked and parsed, but nothing establishes that its issuer
	// is a Registrar we accept.
	//
	// The ARF RPRC_16 binding needs both this and an access certificate:
	// it compares the two documents' organisation identifiers, so it is
	// skipped when key_config supplies no certificate chain to compare
	// against.
	TrustedRootsPath string `yaml:"trusted_roots_path,omitempty"`
}

RegistrationCertificate configures the EUDI registration certificate (WRPRC, ETSI TS 119 475) a verifier or a credential issuer presents to wallets.

vc does not issue these. A national Registrar in the eIDAS ecosystem issues a WRPRC out of band, attesting what the party is registered to do; this configuration points at the resulting file.

The same document travels in both directions, which is why one type serves both:

  • a verifier conveys it in the OpenID4VP verifier_info request parameter, attesting what it is registered to request;
  • a credential issuer conveys it in the OpenID4VCI issuer_info metadata parameter, attesting what it is registered to provide.

Either way it informs the wallet's consent dialog and policy checks.

func (*RegistrationCertificate) Load added in v0.7.4

Load reads, verifies and extracts the certificate this configuration points at, following the same three steps described on Verifier.LoadRegistrationCertificate.

It hangs off the configuration rather than off a service because the document says the same thing wherever it is presented: a verifier conveys it in verifier_info, a credential issuer in issuer_info, and neither changes how it is validated.

type Registry

type Registry struct {
	// APIServer is the HTTP API server configuration
	APIServer APIServer `yaml:"api_server" validate:"required"`
	// PublicURL is the public URL of this service (must be valid HTTP/HTTPS URL)
	PublicURL string `yaml:"public_url" validate:"required,httpurl" doc_example:"\"https://registry.sunet.se\""`
	// GRPCServer is the gRPC server configuration
	GRPCServer GRPCServer `yaml:"grpc_server" validate:"required"`
	// TokenStatusLists holds the Token Status List configuration
	TokenStatusLists *TokenStatusLists `yaml:"token_status_lists" validate:"required"`
	// AdminGUI holds the admin GUI configuration
	AdminGUI AdminGUI `yaml:"admin_gui,omitempty" validate:"omitempty"`
}

Registry holds the configuration for the Registry service that manages credential status

type RegistrySecrets

type RegistrySecrets struct {
	AdminGUI AdminGUISecrets `yaml:"admin_gui,omitempty"`
}

RegistrySecrets holds registry secrets

type Remote added in v0.5.7

type Remote struct {
	// Type is the API protocol type
	Type RemoteType `yaml:"type" validate:"required,oneof=eduapi ooapi"`

	// BaseURL is the base URL of the API endpoint
	BaseURL string `yaml:"base_url" validate:"required,url" doc_example:"\"https://api.ladok.se/eduapi\""`

	// TokenURL is the OAuth 2.0 token endpoint for Client Credentials Grant
	TokenURL string `yaml:"token_url" validate:"required,url" doc_example:"\"https://api.ladok.se/oauth2/token\""`

	// ClientID is the OAuth 2.0 client identifier
	ClientID string `yaml:"client_id" validate:"required"`

	// ClientSecret is the OAuth 2.0 client secret
	ClientSecret string `yaml:"client_secret" validate:"required"`

	// Scopes are the OAuth 2.0 scopes to request
	Scopes []string `yaml:"scopes,omitempty"`

	// Timeout is the HTTP client timeout
	Timeout time.Duration `yaml:"timeout" default:"10s"`
}

Remote defines an external API connection.

type RemoteType added in v0.5.7

type RemoteType string

RemoteType identifies the protocol type of an external API connection.

const (
	RemoteTypeEduAPI RemoteType = "eduapi"
	RemoteTypeOOAPI  RemoteType = "ooapi"
)

type RevocationConfig added in v0.7.0

type RevocationConfig struct {
	// Enabled activates revocation status checking for presented credentials.
	Enabled bool `yaml:"enabled" json:"enabled"`
	// CacheTTL is the duration in seconds to cache fetched status list tokens.
	CacheTTL int `yaml:"cache_ttl" json:"cache_ttl" default:"300"`
	// FailOpen determines behavior when the status list is unreachable or unparseable:
	//   - true: log warning and allow the credential through (fail-open)
	//   - false: reject the credential (fail-closed)
	// Note: explicitly revoked/suspended credentials are always rejected regardless of this setting.
	FailOpen bool `yaml:"fail_open" json:"fail_open" default:"true"`
	// SkipScopes lists credential scopes exempt from revocation checking
	// (e.g., short-lived credentials valid < 24 hours per ARF 3.0 §6.6.3.7).
	SkipScopes []string `yaml:"skip_scopes,omitempty" json:"skip_scopes,omitempty"`
}

RevocationConfig configures credential revocation verification at presentation time.

type SAMLSP added in v0.5.7

type SAMLSP struct {
	// Enable turns on SAML support (default: false)
	Enable bool `yaml:"enable" default:"false"`

	// EntityID is the SAML SP entity identifier (typically the metadata URL)
	EntityID string `yaml:"entity_id" validate:"required_if=Enable true" doc_example:"\"https://issuer.sunet.se/saml/metadata\""`

	// MetadataURL is the public URL where SP metadata is served (optional, auto-generated if empty)
	MetadataURL string `yaml:"metadata_url,omitempty"`

	// MDQServer is the base URL for MDQ (Metadata Query Protocol) server (must end with /)
	MDQServer string `yaml:"mdq_server,omitempty" validate:"excluded_with=StaticIDPMetadata" doc_example:"\"https://md.sunet.se/entities/\""`

	// StaticIDPMetadata configures a single static IdP as alternative to MDQ
	StaticIDPMetadata *StaticIDPConfig `yaml:"static_idp_metadata,omitempty" validate:"excluded_with=MDQServer"`

	// CertificatePath is the path to X.509 certificate for SAML signing/encryption
	// TODO(pki): Migrate to pki.KeyConfig for consistency with other services and
	// to enable HSM-backed SAML signing keys in the future.
	CertificatePath string `yaml:"certificate_path" validate:"required_if=Enable true"`

	// PrivateKeyPath is the path to private key for SAML signing/encryption
	// TODO(pki): See CertificatePath TODO — both fields would be replaced by a single KeyConfig.
	PrivateKeyPath string `yaml:"private_key_path" validate:"required_if=Enable true"`

	// ACSEndpoint is the Assertion Consumer Service URL where IdP sends SAML responses
	ACSEndpoint string `yaml:"acs_endpoint" validate:"required_if=Enable true" doc_example:"\"https://issuer.sunet.se/saml/acs\""`

	// SessionDuration is the maximum time in seconds an in-flight SAML authentication flow
	// (AuthnRequest → Response) may remain active before it expires
	SessionDuration int `yaml:"session_duration" validate:"required" default:"300"`

	// AttributeMapping normalizes provider-specific attribute names (e.g. SAML OIDs)
	// to canonical claim names. Applied to ALL attributes in the assertion.
	// Which normalized attributes are used depends on the data source:
	//   - assertion: VCTM determines which go into the credential
	//   - datastore: auth_claims determines which are used for DB identity lookup
	AttributeMapping AttributeMapping `yaml:"attribute_mapping" validate:"required_if=Enable true" doc_key:"attribute"`

	// MetadataSigningCertPath is the path to the X.509 certificate used to verify
	// metadata signatures. When set, all fetched metadata (MDQ and static) must
	// carry a valid XML signature from this certificate.
	MetadataSigningCertPath string `yaml:"metadata_signing_cert_path,omitempty"`

	// AllowUnsignedMetadata permits MDQ/URL metadata without signature verification.
	// This is INSECURE (MITM → fake IdP) and should only be used in development.
	// When false (default), MDQ and URL metadata sources require MetadataSigningCertPath.
	// Local metadata files are allowed unsigned regardless (with a startup warning).
	AllowUnsignedMetadata bool `yaml:"allow_unsigned_metadata" default:"false"`

	// MetadataCacheTTL in seconds (default: 3600) - how long to cache IdP metadata from MDQ
	MetadataCacheTTL int `yaml:"metadata_cache_ttl"`
}

SAMLSP holds SAML Service Provider configuration for the issuer

type SQLSecrets added in v0.7.1

type SQLSecrets struct {
	// Postgres holds the Postgres connection password
	Postgres PostgresSecrets `yaml:"postgres,omitempty"`
	// MariaDB holds the MariaDB connection password
	MariaDB MariaDBSecrets `yaml:"mariadb,omitempty"`
}

SQLSecrets holds relational database passwords, keyed by backend.

type SearchDocumentsReply

type SearchDocumentsReply struct {
	Documents      []*CompleteDocument `json:"documents"`
	HasMoreResults bool                `json:"has_more_results"`
}

SearchDocumentsReply the reply from search documents

type SearchDocumentsRequest

type SearchDocumentsRequest struct {
	AuthenticSource string `json:"authentic_source,omitempty" validate:"omitempty,max=1000,excludesall=${}[]"`
	Scope           string `json:"scope,omitempty" validate:"omitempty,max=1000,excludesall=${}[]"`
	DocumentID      string `json:"document_id,omitempty" validate:"omitempty,max=1000,excludesall=${}[]"`
	CollectID       string `json:"collect_id,omitempty" validate:"omitempty,max=1000,excludesall=${}[]"`

	AuthenticSourcePersonID string `json:"authentic_source_person_id,omitempty" validate:"omitempty,max=1000,excludesall=${}[]"`

	Limit      int64          `json:"limit,omitempty" validate:"omitempty,min=0,max=1000"`
	Fields     []string       `json:"fields,omitempty" validate:"omitempty,dive,max=100,excludesall=${}[]"`
	SortFields map[string]int `json:"sort_fields,omitempty" validate:"omitempty,dive,keys,max=100,endkeys,oneof=1 -1"`
}

SearchDocumentsRequest the request to search for documents

type Secrets

type Secrets struct {
	Common   *CommonSecrets   `yaml:"common,omitempty"`
	APIGW    *APIGWSecrets    `yaml:"apigw,omitempty"`
	Registry *RegistrySecrets `yaml:"registry,omitempty"`
	Verifier *VerifierSecrets `yaml:"verifier,omitempty"`
}

Secrets defines the structure of the separate secrets file. When Common.SecretFilePath is set, ApplySecrets merges these values into the main config: the Mongo URI is only used when the main config has none. For each service section (apigw, registry, verifier) that is present in the secrets file, the corresponding secret fields in the main config are cleared and replaced by the secrets-file values. Sections omitted from the secrets file are left untouched.

type SignMetadataRateLimitConfig added in v0.6.0

type SignMetadataRateLimitConfig struct {
	// RequestsPerSecond is the sustained rate limit in requests per second. Default: 2
	RequestsPerSecond float64 `yaml:"requests_per_second" default:"2" validate:"gt=0"`
	// Burst is the maximum number of requests allowed in a single burst. Default: 20
	Burst int `yaml:"burst" default:"20" validate:"gt=0"`
}

SignMetadataRateLimitConfig configures the SignMetadata gRPC rate limiter.

type StaticIDPConfig

type StaticIDPConfig struct {
	// EntityID is the IdP entity identifier
	EntityID string `yaml:"entity_id" validate:"required"`

	// MetadataPath is the file path to IdP metadata XML
	MetadataPath string `yaml:"metadata_path,omitempty" validate:"required_without=MetadataURL,excluded_with=MetadataURL"`

	// MetadataURL is the HTTP(S) URL to fetch IdP metadata from (mutually exclusive with MetadataPath)
	MetadataURL string `yaml:"metadata_url,omitempty"`
}

StaticIDPConfig holds configuration for a single static IdP connection

type StaticOIDCClient

type StaticOIDCClient struct {
	// ClientID is the unique identifier for the client
	ClientID string `yaml:"client_id" validate:"required"`
	// ClientSecret is the client secret for authentication.
	// Can be defined in the secrets file under verifier.oidc_op.static_clients
	// as a map of client_id to client_secret.
	ClientSecret string `yaml:"client_secret" validate:"required_unless=TokenEndpointAuthMethod none"`
	// RedirectURIs is the list of allowed redirect URIs for this client
	RedirectURIs []string `yaml:"redirect_uris" validate:"required,min=1,dive,redirect_uri"`
	// AllowedScopes is the list of scopes this client is allowed to request.
	// If empty, defaults to standard OIDC scopes (openid, profile, email, address, phone).
	AllowedScopes []string `yaml:"allowed_scopes,omitempty"`
	// TokenEndpointAuthMethod is the authentication method for the token endpoint.
	// Supported values: client_secret_basic, client_secret_post, none (public client)
	// Default: "client_secret_basic"
	TokenEndpointAuthMethod string `` /* 144-byte string literal not displayed */
	// GrantTypes is the list of allowed grant types.
	// Supported values: authorization_code, refresh_token
	// Default: ["authorization_code"]
	GrantTypes []string `` /* 128-byte string literal not displayed */
	// ResponseTypes is the list of allowed response types.
	// Supported values: code
	// Default: ["code"]
	ResponseTypes []string `yaml:"response_types,omitempty" default:"[\"code\"]" validate:"omitempty,dive,oneof=code"`
	// ClientName is an optional human-readable name for the client
	ClientName string `yaml:"client_name,omitempty"`
}

StaticOIDCClient defines a pre-configured OIDC client for the verifier's OIDC Provider. Static clients are configured in YAML and do not require dynamic registration. These clients are checked in addition to dynamically registered clients stored in the database.

type SupportedCredentialConfig

type SupportedCredentialConfig struct {
	// VCT is the verifiable credential type
	VCT string `yaml:"vct" validate:"required" doc_example:"\"urn:eudi:pid:1\""`
	// Scopes are the OIDC scopes that grant access to this credential
	Scopes []string `yaml:"scopes" validate:"required"`
}

SupportedCredentialConfig maps credential types to OIDC scopes

type TLS

type TLS struct {
	// Enable enables TLS
	Enable bool `yaml:"enable" default:"false"`
	// CertFilePath is the path to the TLS certificate
	CertFilePath string `yaml:"cert_file_path" validate:"required_if=Enable true"`
	// KeyFilePath is the path to the TLS private key
	KeyFilePath string `yaml:"key_file_path" validate:"required_if=Enable true"`
}

TLS holds server-side TLS configuration (presenting a certificate to clients)

type TokenStatusLists

type TokenStatusLists struct {
	// KeyConfig holds the key configuration for signing Token Status List tokens.
	KeyConfig *pki.KeyConfig `yaml:"key_config" validate:"required"`
	// TokenRefreshInterval is how often (in seconds) new Token Status List tokens are generated. Default: 43200 (12 hours). Min: 301 (>5 minutes), Max: 86400 (24 hours)
	TokenRefreshInterval int64 `yaml:"token_refresh_interval" validate:"min=301,max=86400" default:"43200"`
	// SectionSize is the number of entries (decoys) per section. Default: 1000000 (1 million)
	SectionSize int64 `yaml:"section_size" default:"1000000"`
	// RateLimitRequestsPerMinute is the maximum requests per minute per IP for token status list endpoints. Default: 60
	RateLimitRequestsPerMinute int `yaml:"rate_limit_requests_per_minute" default:"60"`
}

TokenStatusLists holds the configuration for Token Status List per draft-ietf-oauth-status-list

type TrustConfig

type TrustConfig struct {
	// PDPURL is the URL of the AuthZEN PDP (Policy Decision Point) service for trust evaluation.
	// When set, operates in "default deny" mode - trust decisions require PDP approval.
	// When empty, operates in "allow all" mode - resolved keys are always considered trusted.
	PDPURL string `yaml:"pdp_url,omitempty" doc_example:"\"https://trust.sunet.se/pdp\""`

	// LocalDIDMethods specifies which DID methods can be resolved locally without go-trust.
	// Self-contained methods like "did:key" and "did:jwk" are always resolved locally.
	LocalDIDMethods []string `yaml:"local_did_methods,omitempty" default:"[\"did:key\", \"did:jwk\"]"`

	// TrustPolicies configures per-role trust evaluation policies.
	// The key is the role (e.g., "issuer", "verifier") and the value contains policy settings.
	TrustPolicies map[string]TrustPolicyConfig `yaml:"trust_policies,omitempty" doc_key:"role"`

	// AllowedSignatureAlgorithms restricts which JWT signature algorithms are accepted.
	// If empty, defaults to a secure set: ES256, ES384, ES512, RS256, RS384, RS512, PS256, PS384, PS512, EdDSA.
	// The "none" algorithm is NEVER allowed regardless of configuration.
	AllowedSignatureAlgorithms []string `yaml:"allowed_signature_algorithms,omitempty" doc_example:"[\"ES256\", \"ES384\", \"ES512\", \"EdDSA\"]"`

	// WalletAttestation configures wallet attestation-based client authentication.
	// This is a trust-evaluation mechanism (delegates to the PDP above), so it
	// lives here rather than under delivery.openid4vci.
	WalletAttestation WalletAttestationConfig `yaml:"wallet_attestation,omitempty"`
}

TrustConfig holds configuration for key resolution and trust evaluation via go-trust. This is used for validating W3C VC Data Integrity proofs and other trust-related operations.

Trust evaluation operates in one of two modes:

  • When PDPURL is configured: "default deny" mode - all trust decisions go through the PDP
  • When PDPURL is empty: "allow all" mode - keys are resolved but always considered trusted

type TrustPolicyConfig

type TrustPolicyConfig struct {
	// TrustFrameworks lists the accepted trust frameworks for this role.
	TrustFrameworks []string `yaml:"trust_frameworks,omitempty" doc_example:"[\"did:web\", \"did:ebsi\", \"etsi-tl\", \"openid-federation\", \"x509\"]"`

	// TrustAnchors specifies trusted root entities for this role.
	// Format depends on the trust framework (e.g., DID for did:web, federation entity for OpenID Fed).
	TrustAnchors []string `yaml:"trust_anchors,omitempty"`

	// RequireRevocationCheck enforces revocation status checking for this role.
	// Default: false
	RequireRevocationCheck bool `yaml:"require_revocation_check,omitempty" default:"false"`
}

TrustPolicyConfig defines trust policy settings for a specific role.

type VerificationPreset added in v0.6.0

type VerificationPreset map[string]*VerificationPresetScope

VerificationPreset is a map of scope name to optional credential query overrides. The preset's map key (in the parent Presets map) serves as the human-readable label. Each key in this map references a credential_metadata scope (e.g., "pid", "ehic"). A nil value means "request all VCTM claims with no overrides".

type VerificationPresetClaim added in v0.6.0

type VerificationPresetClaim struct {
	// Path is the claim path segments
	Path []string `yaml:"path" validate:"required,min=1,dive,required" doc_example:"[\"birthdate\"], [\"address\", \"locality\"]"`
}

VerificationPresetClaim defines a claim path to request within a credential.

type VerificationPresetScope added in v0.6.0

type VerificationPresetScope struct {
	// Claims lists specific claims to request. If empty, all VCTM claims are used.
	Claims []VerificationPresetClaim `yaml:"claims,omitempty" validate:"omitempty,dive"`
	// ExcludeClaims lists claims to exclude from the DCQL query.
	ExcludeClaims []VerificationPresetClaim `yaml:"exclude_claims,omitempty" validate:"omitempty,dive"`
	// Validations are optional rules applied server-side after claims extraction
	Validations []openid4vp.ClaimValidation `yaml:"validations,omitempty" validate:"omitempty,dive"`
}

VerificationPresetScope defines optional overrides for a credential query within a preset.

type Verifier

type Verifier struct {
	// APIServer is the HTTP API server configuration
	APIServer APIServer `yaml:"api_server" validate:"required"`
	// PublicURL is the public URL of this service (must be valid HTTP/HTTPS URL)
	PublicURL string `yaml:"public_url" validate:"required,httpurl" doc_example:"\"https://verifier.sunet.se\""`
	// KeyConfig is the signing key configuration
	KeyConfig *pki.KeyConfig `yaml:"key_config" validate:"required"`
	// ClientIDScheme determines how the verifier identifies itself to wallets.
	// Supported values: "x509_san_dns" (default), "x509_hash", "did".
	// When "did", the DID field must be set and /.well-known/did.json is served.
	// When "x509_hash" (the scheme the EUDI ARF mandates for Relying Party
	// authentication), the client_id is the base64url SHA-256 of the signing
	// certificate, so key_config must supply one and it is always sent in x5c.
	ClientIDScheme string `yaml:"client_id_scheme,omitempty" default:"x509_san_dns" validate:"omitempty,oneof=x509_san_dns x509_hash did"`
	// DID is the verifier's DID identity.
	DID string `yaml:"did,omitempty" validate:"required_if=ClientIDScheme did" doc_example:"\"did:web:verifier.example.com\""`
	// AccessCertificate validates the verifier's own wallet-facing certificate as an EUDI Relying Party access certificate (WRPAC, ETSI TS 119 411-8). Off by default; deployments outside an ARF trust framework are unaffected.
	AccessCertificate *AccessCertificate `yaml:"access_certificate,omitempty"`
	// RegistrationCertificate points at a Relying Party registration certificate (WRPRC, ETSI TS 119 475) issued to this verifier by a national Registrar, to be presented to wallets in the OpenID4VP verifier_info parameter. vc does not issue these.
	RegistrationCertificate *RegistrationCertificate `yaml:"registration_certificate,omitempty"`
	// PreferredVPFormats specifies informational VP formats and algorithms supported by wallets
	PreferredVPFormats *openid4vp.VPFormatsSupported `yaml:"preferred_vp_formats,omitempty"`
	// SupportedWallets holds supported wallet configurations
	SupportedWallets map[string]string `yaml:"supported_wallets" validate:"omitempty"`
	// Inbound groups inbound credential verification
	Inbound VerifierInbound `yaml:"inbound,omitempty"`
	// Outbound groups outbound identity assertion
	Outbound VerifierOutbound `yaml:"outbound,omitempty"`
	// DigitalCredentials holds the W3C Digital Credentials API configuration
	DigitalCredentials DigitalCredentialsConfig `yaml:"digital_credentials,omitempty"`
	// AuthorizationPageCSS holds the authorization page styling configuration
	AuthorizationPageCSS AuthorizationPageCSSConfig `yaml:"authorization_page_css,omitempty"`
	// CredentialDisplay holds the credential display settings
	CredentialDisplay CredentialDisplayConfig `yaml:"credential_display,omitempty"`
	// Trust holds the trust evaluation configuration
	Trust TrustConfig `yaml:"trust,omitempty"`
	// OpenIDFederation holds the OpenID Federation entity configuration.
	// When enabled, serves /.well-known/openid-federation as a self-signed JWT.
	OpenIDFederation *openidfederation.Config `yaml:"federation,omitempty"`
	// Presets holds predefined verification request presets shown in the UI.
	// The map key is the human-readable label.
	// Each preset maps credential_metadata scopes to optional claim overrides.
	// A nil scope value requests all VCTM claims; use claims/exclude_claims to narrow.
	Presets map[string]VerificationPreset `` /* 181-byte string literal not displayed */
	// CombinedPresentation configures combined presentation verification (ARF 3.0 §6.6.3.10).
	// When multiple credentials are presented, this verifies they belong to the same holder.
	CombinedPresentation *openid4vp.CombinedPresentationConfig `yaml:"combined_presentation,omitempty"`
	// Revocation configures credential revocation checking at presentation time (ARF 3.0 §6.6.3.7).
	// When enabled, the Verifier checks Token Status List references in presented credentials.
	Revocation *RevocationConfig `yaml:"revocation,omitempty"`
	// ZkCircuits configures the zk-circuits catalog service used to resolve
	// "mso_mdoc_zk" (Longfellow ZK/PPID) proof circuits for native
	// verification. Only consulted by builds with the "zknative" Go build
	// tag (see pkg/mdoc/zk_native_cgo.go) - ignored by the default build.
	ZkCircuits ZkCircuitsConfig `yaml:"zk_circuits,omitempty"`
}

Verifier holds the configuration for the Verifier service that verifies credentials and acts as an OIDC Provider

func (*Verifier) CheckPublicURLMatchesCertificate added in v0.7.4

func (v *Verifier) CheckPublicURLMatchesCertificate(leaf *x509.Certificate) error

CheckPublicURLMatchesCertificate reports whether the host in PublicURL appears in the certificate's DNS SANs.

This only matters for client_id_scheme "x509_san_dns", where the client_id is that host: a wallet resolves it against the certificate's DNS SANs, so a mismatch means every request is rejected. "x509_hash" pins the certificate bytes instead and "did" does not involve the certificate at all, so both are reported as matching.

It returns a descriptive error rather than a bool so the caller can log or fail with the specific mismatch. Callers treat this as a warning by default - an existing deployment may have been running this way - and only as fatal when access_certificate.validate is set.

func (*Verifier) LoadRegistrationCertificate added in v0.7.4

func (v *Verifier) LoadRegistrationCertificate(accessCert *x509.Certificate) (*LoadedRegistrationCertificate, error)

LoadRegistrationCertificate reads, verifies and extracts the configured registration certificate. It returns (nil, nil) when none is configured, so a deployment outside an ARF trust framework is unaffected.

The three steps of validating a signed object are kept distinct:

  1. verify the signature against the certificate in the document's own x5c;
  2. extract the attested claims - the only step delegated, to go-trust's rpcert.ParseWRPRCClaims, which decodes the wire format and decides nothing;
  3. evaluate whether that issuer is one we trust - the only step that is a trust decision, and it stays here: evaluateWRPRCChain plus the binding check below.

accessCert is the verifier's own access certificate, used in step 3 to confirm both documents describe the same organisation (ARF RPRC_16). Pass nil when none is loaded; the binding check is then skipped.

func (*Verifier) ValidateAccessCertificate added in v0.7.4

func (v *Verifier) ValidateAccessCertificate(leaf *x509.Certificate, now time.Time) error

ValidateAccessCertificate checks the verifier's own signing certificate against the WRPAC profile and its own validity window.

It is deliberately offline: every check reads the certificate's own attributes, so this is profile conformance, not a trust decision. Deciding whether some *other* party's certificate is trusted belongs to the PDP.

now is injected so the validity-window check is testable; callers pass time.Now().

func (*Verifier) ValidateClientIDMaterial added in v0.7.4

func (v *Verifier) ValidateClientIDMaterial(leaf *x509.Certificate, chain []string) error

ValidateClientIDMaterial checks that the key material loaded at startup can actually satisfy the configured client_id_scheme, so a misconfiguration surfaces as a boot failure rather than as requests no wallet can validate.

Only "x509_hash" constrains the material: it pins the exact leaf certificate, so that certificate must both exist and travel with every request in x5c - the wallet has nothing else to hash. "x509_san_dns" and "did" authenticate without one, so both are accepted with nil/empty input.

func (*Verifier) VerifierClientID added in v0.7.0

func (v *Verifier) VerifierClientID(leaf *x509.Certificate) (string, error)

VerifierClientID returns the client_id value the verifier uses in OID4VP requests. For "x509_san_dns" (default): returns "x509_san_dns:{hostname}". For "x509_hash": returns "x509_hash:{base64url(SHA-256(DER))}" of leaf. For "did": returns the configured DID value directly.

leaf is the verifier's own signing certificate. It is required for "x509_hash" and ignored by every other scheme, so callers that have not loaded a certificate may pass nil as long as they are not configured for "x509_hash".

type VerifierInbound added in v0.5.7

type VerifierInbound struct {
	// OpenID4VP holds the OpenID4VP configuration for accepting wallet presentations
	OpenID4VP *OpenID4VPConfig `yaml:"openid4vp" validate:"required"`
}

VerifierInbound groups inbound credential verification configuration

type VerifierOutbound added in v0.5.7

type VerifierOutbound struct {
	// OIDCProvider holds the OIDC Provider configuration for asserting verified identity to downstream RPs
	OIDCProvider *OIDCOP `yaml:"oidc_provider,omitempty" validate:"omitempty"`
}

VerifierOutbound groups outbound identity assertion configuration

type VerifierOutboundSecrets added in v0.5.7

type VerifierOutboundSecrets struct {
	OIDCProvider OIDCOPSecrets `yaml:"oidc_provider,omitempty"`
}

VerifierOutboundSecrets holds outbound OIDC provider secrets

type VerifierSecrets

type VerifierSecrets struct {
	Outbound VerifierOutboundSecrets `yaml:"outbound,omitempty"`
}

VerifierSecrets holds verifier secrets

type WRPRCClaims added in v0.7.4

type WRPRCClaims struct {
	// SubjectID is the semantic organisation identifier, e.g.
	// "NTRDE-BD7070256AF93987". It is what binds this document to an access
	// certificate.
	SubjectID string
	// LegalName is the registered legal name of the Relying Party.
	LegalName string
	// TradeName is the user-facing name, from the `name` claim.
	TradeName string
	// Country is the ISO 3166-1 code where the RP is established.
	Country string
	// EntitlementURIs are the Annex A role entitlements the Registrar granted.
	EntitlementURIs []string
	// AllowedAttributes are the top-level claim names this RP is registered
	// to request, flattened from the DCQL credential queries. Used for
	// over-request detection.
	AllowedAttributes []string
	// Purpose holds the multi-language purpose descriptions shown to users.
	Purpose []WRPRCLocalizedText
	// PrivacyPolicyURI, InfoURI and RegistryURI are display/reference links.
	PrivacyPolicyURI string
	InfoURI          string
	RegistryURI      string
}

WRPRCClaims is the registration-certificate payload reduced to the fields vc acts on.

Validating a signed object has three steps - verify the signature, extract the trust information, evaluate it - and vc owns the first and third: verifyWRPRCSignature and evaluateWRPRCChain below, plus the ARF RPRC_16 binding. The second is delegated to go-trust's rpcert.ParseWRPRCClaims, which decodes the wire format and nothing else.

It was originally implemented here, because tolerating what Registrars actually emit needed somewhere to live and go-trust had no parser. That decoder has since been upstreamed along with the German sandbox fixture that motivated it, so the tolerance is shared rather than duplicated - see extractWRPRCClaims.

type WRPRCLocalizedText added in v0.7.4

type WRPRCLocalizedText struct {
	Lang  string `json:"lang"`
	Value string `json:"value"`
}

WRPRCLocalizedText is one language variant of a displayable string.

type WalletAttestationConfig added in v0.7.0

type WalletAttestationConfig struct {
	// Enabled enables wallet attestation-based authentication.
	// When true and PDPURL is configured, wallets can authenticate using
	// a provider-signed attestation JWT instead of pre-registration in Clients.
	// The PDP validates the wallet provider against configured trust lists/federation.
	// PKCE remains mandatory as the primary code-binding mechanism.
	Enabled bool `yaml:"enabled" default:"false"`

	// Policy configures SPOCP-based authorization for wallet attestation.
	// When configured, after the PDP validates the wallet provider, the SPOCP engine
	// checks whether the attestation tier (attestation_source) is authorized for the
	// requested scope. When empty, all trusted wallets are authorized (default open).
	Policy WalletAttestationPolicy `yaml:"policy,omitempty"`

	// Mode restricts which WIA trust model this deployment accepts, matching
	// the same "etsi"/"ietf" terminology used by go-wallet-backend's
	// WIAConfig.Mode:
	//
	//   - "etsi": require x5c (EC TS03 v1.5.2 / ETSI TS 119 472-3 model,
	//     identity verified against the Trusted List for Wallet Providers).
	//     A WIA without x5c is rejected before signature verification.
	//   - "ietf": require iss + no x5c (the plain IETF
	//     draft-ietf-oauth-attestation-based-client-auth format, resolved via
	//     JWKS discovery — no ARF/ETSI counterpart). A WIA with x5c is
	//     rejected before signature verification.
	//   - "" (default): accept either format, as determined by whether the
	//     WIA carries an x5c header or an iss claim — preserves pre-Mode
	//     behavior for deployments that haven't opted into pinning one trust
	//     model.
	//
	// Any other value is treated the same as "" (a warning is logged, not a
	// startup failure — this package has no config.Validate() convention to
	// hard-fail against).
	//
	// Pinning this matters beyond documentation: without it, an operator
	// expecting only ARF-conformant ("etsi") wallets would still silently
	// accept an iss/JWKS-based ("ietf") WIA from a misconfigured or
	// malicious wallet, trusting a JWKS discovery chain instead of the
	// Trusted List for Wallet Providers PKI anchor.
	Mode string `yaml:"mode,omitempty"`
}

WalletAttestationConfig configures wallet attestation-based client authentication.

type WalletAttestationPolicy added in v0.7.0

type WalletAttestationPolicy struct {
	// Rules are inline SPOCP rules.
	Rules []string `yaml:"rules,omitempty"`
	// RulesFile is a path to a file containing SPOCP rules (one per line, # comments).
	RulesFile string `yaml:"rules_file,omitempty"`
}

WalletAttestationPolicy configures SPOCP-based tier authorization for wallet attestation. Each rule is an S-expression of the form:

(wallet (attestation_source <tier>)(scope <scope>)(issuer <provider>))

Use * as wildcard. When no rules are configured, any trusted wallet is authorized. Example rules:

(wallet (attestation_source ios_app_attest)(scope pid)(issuer *))       — allow iOS Tier 4+ for PID
(wallet (attestation_source android_play_integrity)(scope pid)(issuer *)) — allow Android Tier 4+ for PID
(wallet (attestation_source *)(scope ehic)(issuer *))                   — allow any tier for EHIC

type ZkCircuitsConfig added in v0.7.1

type ZkCircuitsConfig struct {
	// Sources are zk-circuits catalog mirror base URLs, tried in order
	// until one succeeds (see pkg/mdoc/zkcircuit.Client - these are
	// mirrors of the SAME catalog, not distinct registries). Defaults to
	// the live deployed service if empty.
	Sources []string `yaml:"sources,omitempty" default:"[\"https://zk-circuits.fly.dev\"]" doc_example:"[\"https://zk-circuits.fly.dev\"]"`
}

ZkCircuitsConfig configures the zk-circuits catalog client (pkg/mdoc/zkcircuit) used to resolve a presented "mso_mdoc_zk" document's zkSystemId to a downloadable circuit artifact.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL