Documentation
¶
Overview ¶
Package rpcert implements the regulatory-identity layer of an EUDI Wallet relying party (verifier): WRPAC access-certificate profile checks and trust-anchor validation (ETSI TS 119 411-8 V1.1.1), WRPRC registration certificates in JWT and CWT form (ETSI TS 119 475 V1.2.1), the ARF RPRC_19a registration reference embedded in every authorization request, and a typed client for the ARF TS5 v1.3 Registrar API (CIR (EU) 2025/848).
Dual path: validate + attach WRPRCs where a Member State issues them; always embed the RegistrationRef; consume the Registrar API directly for onboarding verification, intended-use lifecycle monitoring and intermediary-linkage checks.
Index ¶
- Constants
- Variables
- func IntendedUseActive(iu ts5.IntendedUse, at time.Time) (bool, error)
- type Doer
- type IntendedUseQuery
- type IntermediaryRef
- type KeyProvenance
- type LangValue
- type LinkageResult
- type PinnedRegistrarKeys
- type Provenance
- type RegisteredCredential
- type RegistrarClient
- func (c *RegistrarClient) CheckIntendedUse(ctx context.Context, registryURI string, q IntendedUseQuery) (bool, error)
- func (c *RegistrarClient) GetWRP(ctx context.Context, registryURI string, q WRPQuery) ([]ts5.WalletRelyingParty, error)
- func (c *RegistrarClient) GetWRPByID(ctx context.Context, registryURI, identifier string) (*ts5.WalletRelyingParty, error)
- func (c *RegistrarClient) IntendedUseStatus(ctx context.Context, registryURI, rpIdentifier, intendedUseID string) error
- func (c *RegistrarClient) LastProvenance() Provenance
- func (c *RegistrarClient) VerifyIntermediaryLinkage(ctx context.Context, registryURI, clientIdentifier, operatorIdentifier string) (LinkageResult, error)
- type RegistrarKeys
- type RegistrarOption
- type RegistrationRef
- type StatusRef
- type SupervisoryContact
- type WRPAC
- type WRPQuery
- type WRPRC
Constants ¶
const ( EntitlementServiceProvider = "https://uri.etsi.org/19475/Entitlement/Service_Provider" EntitlementQEAAProvider = "https://uri.etsi.org/19475/Entitlement/QEAA_Provider" EntitlementNonQEAAProvider = "https://uri.etsi.org/19475/Entitlement/Non_Q_EAA_Provider" EntitlementPubEAAProvider = "https://uri.etsi.org/19475/Entitlement/PUB_EAA_Provider" EntitlementPIDProvider = "https://uri.etsi.org/19475/Entitlement/PID_Provider" EntitlementQCertESealProvider = "https://uri.etsi.org/19475/Entitlement/QCert_for_ESeal_Provider" EntitlementQCertESigProvider = "https://uri.etsi.org/19475/Entitlement/QCert_for_ESig_Provider" EntitlementRQSealCDsProvider = "https://uri.etsi.org/19475/Entitlement/rQSealCDs_Provider" EntitlementRQSigCDsProvider = "https://uri.etsi.org/19475/Entitlement/rQSigCDs_Provider" EntitlementESigESealCreationProvider = "https://uri.etsi.org/19475/Entitlement/ESig_ESeal_Creation_Provider" )
Entitlement URIs — ETSI TS 119 475 V1.2.1 Annex A.2 (exhaustive; also the vocabulary of ARF TS5 v1.3 `entitlements`).
const ( FormatJWT = "jwt" FormatCWT = "cwt" )
WRPRC serialization formats (public interface).
const ( CredentialFormatSDJWTVC = "dc+sd-jwt" //nolint:gosec // G101: OpenID4VP data-format identifier, not a credential (name matches gosec's "cred" heuristic) CredentialFormatMdoc = "mso_mdoc" )
Credential format identifiers used in WRPRC/ARF TS5 credential entries — OpenID4VP 1.0 Annex B format ids (data formats, not crypto algorithms; the no-algorithm-literal rule concerns algorithms only).
const WRPRCPolicyOID = "0.4.0.19475.3.1"
WRPRCPolicyOID — [ETSI TS 119 475 §6.1.3]: itu-t(0) identified-organization(4) etsi(0) eudiwrpa(19475) policy-identifiers(3) wrprc(1). policy_id must reference it (OVR-6.1.3-01/-02).
Variables ¶
var ( ErrMalformed = errors.New("rpcert: malformed input") // WRPAC profile checks ([ETSI TS 119 411-8 §6.6.1] / TS 119 475 Annex A) ErrChainEmpty = errors.New("rpcert: certificate chain is empty") ErrPolicyOID = errors.New("rpcert: no eudiwrp certificate policy OID (TS 119 411-8 GEN-6.6.1-03)") ErrContactSAN = errors.New("rpcert: no contact SAN - URI, email or telephone otherName (TS 119 411-8 GEN-6.6.1-07)") ErrKeyUsage = errors.New("rpcert: keyUsage lacks digitalSignature (WP-07 Decision 3)") ErrExtKeyUsage = errors.New("rpcert: extended key usage restricts wallet-facing use (WP-07 Decision 3)") ErrUnknownEntitlement = errors.New("rpcert: unknown OID under id-etsi-wrpa-entitlement arc (TS 119 475 Annex A.2)") // Trust-anchor validation (fail closed — trust is anchored, never from the token) ErrNoTrustPath = errors.New("rpcert: no chain to a valid trust anchor") // WRPRC ([ETSI TS 119 475 §5.2 / §6.1.3]) ErrWRPRCFormat = errors.New("rpcert: WRPRC is neither a compact JWS nor a COSE_Sign1 (GEN-5.2.1-01)") ErrWRPRCType = errors.New("rpcert: WRPRC typ header mismatch (GEN-5.2.2-01 / GEN-5.2.3-01)") ErrClaimMissing = errors.New("rpcert: required WRPRC claim missing (GEN-5.2.4-01/-02)") ErrSignature = errors.New("rpcert: WRPRC signature verification failed") ErrExpired = errors.New("rpcert: WRPRC expired") ErrValidity = errors.New("rpcert: WRPRC validity window violated (GEN-5.2.4-08)") ErrEntitlement = errors.New("rpcert: WRPRC lacks a TS 119 475 Annex A.2 entitlement (GEN-5.2.4-03)") ErrPolicyID = errors.New("rpcert: WRPRC policy_id lacks the wrprc policy OID (TS 119 475 OVR-6.1.3-01)") // RegistrationRef (ARF RPRC_19a) ErrRegistrationRef = errors.New("rpcert: incomplete registration reference") // ARF TS5 Registrar API client ErrNoRegistrarKey = errors.New("rpcert: no verification key configured for registry") ErrResponseSignature = errors.New("rpcert: registrar response JWS verification failed") ErrStaleResponse = errors.New("rpcert: registrar response too old") ErrRegistrarStatus = errors.New("rpcert: registrar returned an error status") ErrWRPNotFound = errors.New("rpcert: wallet-relying party not found in registry") ErrPagination = errors.New("rpcert: inconsistent pagination in registrar response") ErrTooManyPages = errors.New("rpcert: registrar pagination exceeded page cap") ErrIntendedUseNotFound = errors.New("rpcert: intended use not found on registered WRP") ErrIntendedUseRevoked = errors.New("rpcert: intended use revoked or not yet active") ErrNotLinked = errors.New("rpcert: client does not list operator as intermediary (TS5 v1.3 usesIntermediary)") )
Sentinel errors. Services map them to err:domain:reason problem codes ErrIntendedUseRevoked → err:registrar:intended-use-revoked; ErrRegistrarUnavailable/-Status → err:registrar:unavailable; ErrWRPNotFound/ErrNotLinked → err:client:not-registered; ErrNoTrustPath (or a propagated trust.ErrCacheExpired) → err:trust:anchor-unavailable. No HTTP semantics here (framework-free); no attribute values in messages, ever. NOTE: this var block is the complete sentinel set for the whole library. The WRPRC/registrar-linkage paths consume the sentinels already declared below — do not redefine ErrRegistrationRef or replace this file.
Functions ¶
func IntendedUseActive ¶
IntendedUseActive evaluates the intended-use lifecycle window (ETSI TS 119 475 / ARF TS5 IntendedUse): active iff createdAt <= at and (revokedAt absent OR at < revokedAt-day-midnight). Revocation is effective from 00:00:00 UTC of revokedAt (fail-closed reading of "end date for the validity"): the entire revokedAt calendar day already counts as revoked. A revoked/not-yet-active use returns (false, ErrIntendedUseRevoked); malformed dates — including an empty createdAt — fail closed with ErrMalformed (never silently treated as active; RevokedAt stays optional and absent-means-not-revoked).
Types ¶
type Doer ¶
Doer is the injected HTTP client (framework-free: no global http.DefaultClient). Services wire the platform-kit correlation-propagating client here; tests wire httptest.Server.Client().
type IntendedUseQuery ¶
type IntendedUseQuery struct {
RPIdentifier string // rpidentifier (required)
IntendedUseIdentifier string // intendeduseidentifier
CredentialFormat string // credentialformat
ClaimPath string // claimpath
CredentialMeta string // credentialmeta
PolicyURL string // policyurl
}
IntendedUseQuery mirrors GET /wrp/check-intended-use (ARF TS5 v1.3 OpenAPI): rpidentifier is mandatory; the other five are optional narrowing filters.
type IntermediaryRef ¶
IntermediaryRef identifies the intermediary the WRP acts through (TS 119 475 Table 10 `intermediary`; Annex C uses subfield "name", Table 10 says "sname" — both accepted, see README corrections).
type KeyProvenance ¶
type KeyProvenance string
KeyProvenance records how a registrar verification key was obtained, for the verification report's provenance (per-registry key pinning).
const ( // ProvenancePinned — key came from explicit per-registry config // (until MS registrars publish keys uniformly). ProvenancePinned KeyProvenance = "pinned" // ProvenanceTrustService — key resolved via the trust service // wrprc_issuer/registrar metadata (wired by the verifier service). ProvenanceTrustService KeyProvenance = "trust-service" )
type LangValue ¶
LangValue is a [ETSI TS 119 475 §5.2.4] localized string ({lang, value} — note: the WRPRC uses "value" where ARF TS5's MultiLangString uses "content").
type LinkageResult ¶
type LinkageResult struct {
Linked bool
OperatorID string
ClientID string
Provenance Provenance
}
LinkageResult reports whether a client registers the operator as one of its intermediaries ([ARF TS5 v1.3 §2.1] usesIntermediary). Provenance carries the registry + key trust basis for the verification report.
type PinnedRegistrarKeys ¶
type PinnedRegistrarKeys struct {
// contains filtered or unexported fields
}
PinnedRegistrarKeys is the config-driven pinning implementation: one public key per registry base URL ([ARF TS5 §3.2.2] leaves key discovery to deployment).
func NewPinnedRegistrarKeys ¶
func NewPinnedRegistrarKeys(keys map[string]crypto.PublicKey) *PinnedRegistrarKeys
NewPinnedRegistrarKeys copies the provided registry-URI → key map.
func (*PinnedRegistrarKeys) KeyFor ¶
func (p *PinnedRegistrarKeys) KeyFor(registryURI, _ string) (crypto.PublicKey, KeyProvenance, error)
KeyFor returns the pinned key for registryURI (kid ignored — one key per registry in the pinned model).
type Provenance ¶
type Provenance struct {
RegistryURI string
KeyProvenance KeyProvenance
}
Provenance is attached to the verification report so an operator can see which registry answered and how its signing key was trusted.
type RegisteredCredential ¶
type RegisteredCredential struct {
Format string
DoctypesOrVCTs []string
AllClaims bool
Claims []ts5.ClaimPath
}
RegisteredCredential mirrors one WRPRC credentials/provides_attestations entry (TS 119 475 Tables 8/9). It maps 1:1 onto dcql.RegisteredCredential (go-dcql) for WithinScope checks; AllClaims=true when the claim list is absent ([OID4VP §6.1] semantics).
type RegistrarClient ¶
type RegistrarClient struct {
// contains filtered or unexported fields
}
RegistrarClient is a typed ARF TS5 v1.3 Registrar API client with JWS response verification. Framework-free: injected Doer + clock.
func NewRegistrarClient ¶
func NewRegistrarClient(doer Doer, keys RegistrarKeys, opts ...RegistrarOption) *RegistrarClient
NewRegistrarClient builds the client.
func (*RegistrarClient) CheckIntendedUse ¶
func (c *RegistrarClient) CheckIntendedUse(ctx context.Context, registryURI string, q IntendedUseQuery) (bool, error)
CheckIntendedUse calls GET /wrp/check-intended-use and returns the JWS-signed boolean result ([ARF TS5 v1.3 §3.2.2]: "JWS-signed boolean TRUE or FALSE response, based on if the queried parameter set can be found in the Registrar's Intended use information"). Verification reuses the same fetch/verifyResponse wiring as GetWRP/GetWRPByID — one trust decision path for every registrar endpoint (alg selection stays inside eudicrypto.VerifyJWS via c.fetch).
func (*RegistrarClient) GetWRP ¶
func (c *RegistrarClient) GetWRP(ctx context.Context, registryURI string, q WRPQuery) ([]ts5.WalletRelyingParty, error)
GetWRP queries GET /wrp and joins all cursor pages ([ARF TS5 v1.3 §3.2.2]).
func (*RegistrarClient) GetWRPByID ¶
func (c *RegistrarClient) GetWRPByID(ctx context.Context, registryURI, identifier string) (*ts5.WalletRelyingParty, error)
GetWRPByID queries GET /wrp/{identifier} ([ARF TS5 v1.3 §3.2.2]).
func (*RegistrarClient) IntendedUseStatus ¶
func (c *RegistrarClient) IntendedUseStatus(ctx context.Context, registryURI, rpIdentifier, intendedUseID string) error
IntendedUseStatus resolves the WRP via GET /wrp/{identifier}, locates the named intended use and evaluates its lifecycle. Returns nil when active, ErrIntendedUseRevoked when revoked/not-yet-active (-> err:registrar:intended-use-revoked), ErrIntendedUseNotFound when the id is absent. Used by the portal's periodic monitoring.
func (*RegistrarClient) LastProvenance ¶
func (c *RegistrarClient) LastProvenance() Provenance
LastProvenance returns the provenance of the most recent verified response (for the report). Safe for concurrent use.
func (*RegistrarClient) VerifyIntermediaryLinkage ¶
func (c *RegistrarClient) VerifyIntermediaryLinkage(ctx context.Context, registryURI, clientIdentifier, operatorIdentifier string) (LinkageResult, error)
VerifyIntermediaryLinkage confirms, via the Registrar API, that the client's usesIntermediary array lists the operator by identifier. Per [ARF TS5 v1.3 §2.1] (Note: "isIntermediary … available for verification only via the Registrar's API") this relationship is NOT in any certificate, so this API call is the authoritative check (supersedes WRPAC.IsIntermediaryCapable). Fail closed: ErrNotLinked when the operator is absent, ErrWRPNotFound when the client is unknown.
type RegistrarKeys ¶
type RegistrarKeys interface {
KeyFor(registryURI, kid string) (crypto.PublicKey, KeyProvenance, error)
}
RegistrarKeys resolves a registrar's JWS verification key. kid is the JWS "kid" header (may be empty when a registry publishes a single key).
type RegistrarOption ¶
type RegistrarOption func(*RegistrarClient)
RegistrarOption configures a RegistrarClient.
func WithClock ¶
func WithClock(clock func() time.Time) RegistrarOption
WithClock injects the clock (no ambient time).
func WithMaxPages ¶
func WithMaxPages(n int) RegistrarOption
WithMaxPages caps cursor pagination (fail closed against a runaway registrar).
func WithMaxResponseAge ¶
func WithMaxResponseAge(d time.Duration) RegistrarOption
WithMaxResponseAge sets the freshness window for the response iat.
type RegistrationRef ¶
type RegistrationRef struct {
ClientName string
ClientID string
RegistryURI string
IntendedUseID string
}
RegistrationRef is the ARF RPRC_19a registration reference embedded in EVERY authorization request — with or without an attached WRPRC — so wallets can query the registrar themselves: the client's display name, its registered unique identifier, the national registry API URL (CIR (EU) 2025/848 Art. 3(5)) and the intended-use identifier.
func NewRegistrationRef ¶
func NewRegistrationRef(clientName, clientID, registryURI, intendedUseID string) (RegistrationRef, error)
NewRegistrationRef validates and builds the reference. All four fields are required (all four fields); an incomplete reference cannot be built, so the request builder can guarantee "always present" structurally.
func (RegistrationRef) Claims ¶
func (r RegistrationRef) Claims() (map[string]any, error)
Claims returns the reference as request-object claim values for the request builder (ARF RPRC_19a extension).
func (RegistrationRef) MarshalJSON ¶
func (r RegistrationRef) MarshalJSON() ([]byte, error)
MarshalJSON serializes the reference; an incomplete reference fails, so a half-built RegistrationRef can never reach a request object.
func (*RegistrationRef) UnmarshalJSON ¶
func (r *RegistrationRef) UnmarshalJSON(data []byte) error
UnmarshalJSON parses the wire shape written by MarshalJSON and validates completeness, so a corrupted or truncated stored value fails loudly rather than silently decoding to a zero-value reference (fail closed, fail closed) — MarshalJSON alone made RegistrationRef write-only under encoding/json (the default field-name-matching reflection cannot map "registry_uri" etc. back onto ClientName/ClientID/RegistryURI/ IntendedUseID), which broke the verifier's Session/SessionStore JSON round-trip contract (Session embeds RegistrationRef directly).
func (RegistrationRef) Validate ¶
func (r RegistrationRef) Validate() error
Validate re-checks completeness (called before request build).
type StatusRef ¶
StatusRef points into the issuer's Token Status List (TS 119 475 GEN-6.2.6.1-04: status.status_list.{idx,uri}). Checked by the service via go-statuslist — revocation is surfaced, not resolved, here.
type SupervisoryContact ¶
type SupervisoryContact struct {
Email string `json:"email,omitempty"`
Phone string `json:"phone,omitempty"`
URI string `json:"uri,omitempty"`
}
SupervisoryContact — TS 119 475 Table 7 supervisory_authority.
type WRPAC ¶
type WRPAC struct {
Chain []*x509.Certificate
Entitlements []string
IsIntermediaryCapable bool
}
WRPAC is the operator's (or an intermediary's) wallet-relying party access certificate (CIR (EU) 2025/848 Art. 7 / Annex IV).
func LoadWRPAC ¶
LoadWRPAC parses a WRPAC chain (leaf first; each element PEM or DER) and runs the TS 119 411-8 profile checks. Each broken element yields its own sentinel:
- certificatePolicies includes an eudiwrp policy OID (TS 119 411-8 GEN-6.6.1-03, OIDs from [ETSI TS 119 411-8 §5.3]) → ErrPolicyOID
- contact SAN: URI / rfc822Name / telephone otherName (TS 119 411-8 GEN-6.6.1-07 [CHOICE]) → ErrContactSAN
- keyUsage includes digitalSignature ( the WRPAC signs OID4VP request objects) → ErrKeyUsage
- EKU absent, anyExtendedKeyUsage or clientAuth → ErrExtKeyUsage
- entitlement OIDs under 0.4.0.19475.1 map to Annex A.2 URIs; unknown arc children rejected (fail closed) → ErrUnknownEntitlement
func (*WRPAC) ValidateAgainst ¶
func (w *WRPAC) ValidateAgainst(src trust.AnchorSource) error
ValidateAgainst chains the WRPAC to AccessCA trust anchors from the trust service (CIR (EU) 2025/848 Art. 7 — access-certificate providers operate under Member State supervision, their CAs are the AccessCA anchor set). Uses the package clock (clock.go).
type WRPQuery ¶
type WRPQuery struct {
Identifier string
LegalName string
TradeName string
Policy string
Entitlement string
CredentialMeta string
CredentialFormat string
UsesIntermediary string
IsIntermediary *bool
IntendedUseIdentifier string
Limit int
}
WRPQuery mirrors the GET /wrp query parameters (ARF TS5 v1.3 OpenAPI). Empty fields are omitted; IsIntermediary is a *bool so "unset" ≠ "false".
type WRPRC ¶
type WRPRC struct {
Raw []byte
Format string // FormatJWT | FormatCWT
Chain []*x509.Certificate
Name string
LegalName string // sub_ln (legal person)
GivenName string // sub_gn (natural person)
FamilyName string // sub_fn (natural person)
Subject string // sub — registered WRP identifier (GEN-5.2.4-02)
Country string
RegistryURI string
SrvDescription [][]LangValue
Entitlements []string
PrivacyPolicy string
InfoURI string
SupportURI string
SupervisoryAuthority SupervisoryContact
PolicyIDs []string
CertificatePolicy string
PublicBody bool
IssuedAt time.Time
ExpiresAt time.Time // zero = no exp claim
Status *StatusRef
Purpose []LangValue
IntendedUseID string
RegisteredCredentials []RegisteredCredential
ProvidesAttestations []RegisteredCredential
Intermediary *IntermediaryRef
}
WRPRC is a parsed wallet-relying party registration certificate ([ETSI TS 119 475 §5.2]; CIR (EU) 2025/848 Art. 8 / Annex V).
func ParseWRPRC ¶
ParseWRPRC parses a WRPRC in JWT or CWT form WITHOUT verifying its signature — call (*WRPRC).Verify next (TS 119 475 GEN-5.2.1-01: "signed JSON Web Token (JWT) or CBOR Web Token (CWT)").
func (*WRPRC) Verify ¶
Verify validates the WRPRC end-to-end, fail closed:
- signer chain (x5c/x5chain, leaf first) to WRPRCIssuer anchors — trust anchored, never from the token; ETSI TS 119 475 GEN-5.2.1-03 (the provider's signing certificate is published on a trusted list)
- token signature with the leaf key via go-eudi-crypto — GEN-5.2.1-04 (JAdES B-B) / GEN-5.2.1-05 (COSE per RFC 9052/9360)
- validity window — GEN-5.2.4-01 iat, GEN-5.2.4-07/-08 exp ≤ iat+12mo
- entitlements include ≥1 Annex A.2 entitlement — GEN-5.2.4-03
- policy_id references the wrprc policy OID — OVR-6.1.3-01/-02
Verify-before-trust: no payload-derived field (validity, entitlements, policy_id, credential list) is acted upon until the chain (1) and the cryptographic signature (2) both pass — the extraction that ParseWRPRC performed is only trustworthy after this point.
Status-list state (status.status_list) is NOT checked here — services check it via go-statuslist; Verify only surfaces StatusRef (revocation is surfaced, not resolved, here).
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
testpki
Package testpki generates the test PKI at test runtime: an access-CA / WRPRC-issuer CA and end-entity certificates with every TS 119 411-8 profile knob the certificate-profile matrix mutates.
|
Package testpki generates the test PKI at test runtime: an access-CA / WRPRC-issuer CA and end-entity certificates with every TS 119 411-8 profile knob the certificate-profile matrix mutates. |
|
Package ts5 models the ARF TS5 v1.3 "Common formats and API for Relying Party Registration information" data model ([ARF TS5 §2]) and the JWS payload envelopes of its Registrar API (Annex A OpenAPI).
|
Package ts5 models the ARF TS5 v1.3 "Common formats and API for Relying Party Registration information" data model ([ARF TS5 §2]) and the JWS payload envelopes of its Registrar API (Annex A OpenAPI). |