Skip to content

docs: add verifier public-key sourcing guide#370

Open
oliviermeunier wants to merge 2 commits into
openwallet-foundation:mainfrom
oliviermeunier:docs/verifier-public-key-sourcing-307
Open

docs: add verifier public-key sourcing guide#370
oliviermeunier wants to merge 2 commits into
openwallet-foundation:mainfrom
oliviermeunier:docs/verifier-public-key-sourcing-307

Conversation

@oliviermeunier

Copy link
Copy Markdown

Closes #307

This PR adds a documentation guide on the four common approaches a verifier can use to fetch the issuer's signing public key when validating an SD-JWT-VC.

Scope

Following the green light from @cre8 in the issue thread, the guide:

  • describes each approach neutrally, without recommending one over another
  • lists what the issuer must expose and what the verifier needs to implement, per approach
  • contains no working code or new dependencies
  • includes the X.509-by-reference variant (HAIP x509_hash and related) that @cre8 raised as an emerging pattern

Approaches covered

  1. JWKS via issuer URLiss-based, well-known JWKS endpoint per the SD-JWT-VC draft "JWT VC Issuer Metadata" rules
  2. Embedded X.509 chainx5c JWT header
  3. X.509 certificate by reference — HAIP x509_hash (mostly seen in the enclosing OID4VP JAR) and URI-based variants
  4. DID resolution — kept short; method-specific details deferred to the corresponding DID method specs

Cross-cutting operational notes

JWKS / certificate caching, key rotation, clock skew, rate limiting, trust-anchor bootstrapping.

Two distinctions made explicit

  • Key discovery vs trust validation — the doc separates these from the start, since they have different threat models and implementation surfaces
  • Per-approach symmetry — every approach lists "what the issuer must expose" and "what the verifier needs to implement" in the same shape, so readers can compare side-by-side

Placement

Placed at docs/0.x/verifier-public-key-sourcing.md and linked from docs/0.x/README.md (between Verify API and Encode & Decode) so the new guide is discoverable from the existing 0.x docs index. Happy to move it elsewhere (e.g. directly under docs/) if maintainers prefer.

On the DID section

In the original issue thread I mentioned I might either cover DID briefly or defer it entirely. I went with a brief, honest section that points to the DID method specs for method-specific details — keeps the four approaches symmetric without overreaching into deployment patterns I haven't implemented. Easy to expand later if a DID-focused contributor wants to flesh it out.

Test plan

Documentation-only change. No code paths affected; no new dependencies; no test or build impact expected.

Adds a neutral overview of the four common approaches verifiers use to
fetch the issuer's signing public key when validating an SD-JWT-VC:

- JWKS via issuer URL (`iss`-based)
- Embedded X.509 chain (`x5c` header)
- X.509 certificate by reference (HAIP `x509_hash` and related)
- DID resolution

Each section lists what the issuer must expose and what the verifier
needs to implement, plus cross-cutting operational notes (caching, key
rotation, clock skew, rate limiting, trust-anchor bootstrapping).

The guide is description-only with no code or added dependencies, per
the constraint discussed on the issue thread. It is linked from
docs/0.x/README.md so it stays discoverable from the existing index.

Closes openwallet-foundation#307

Signed-off-by: Olivier Meunier <oli.meunier@gmail.com>

## Overview

To validate an SD-JWT-VC, the verifier needs the issuer's signing public key. The key itself is not transported inside the JWT payload — the JWT header carries a *pointer* (or fingerprint) to the key. Several pointer schemes coexist in the SD-JWT-VC ecosystem, each with different operational properties. This document describes the main approaches neutrally.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct. In the case of x5c the singing certificate is included, but the root of trust is not and has to be pre fetched.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 78e04e5. The overview now distinguishes between identifier-based approaches (Approach 1 / 4, where the JWT carries iss + kid or a DID URL and the key is resolved out-of-band) and certificate-material approaches (Approach 2 / 3, where the certificate itself is embedded or referenced and the public key is extracted directly). I also made explicit that the trust anchor lives outside the JWT in every case.

**Operational notes**

- Requires network access at verification time unless the JWKS is cached.
- Key rotation is driven by the issuer; the verifier may cache the JWKS with a TTL.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For key rotation it should be mentioned that the key for the previous issued credentials still needs to provided there. Otherwise the signature cannot be validated

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 78e04e5. Added a bullet to "What the issuer must expose" stating that previously active signing keys need to remain published in the JWKS as long as any non-expired credentials were signed with them. The operational note on rotation windows was also tightened.

**What the verifier needs to implement**

- Trust-anchor store: root and intermediate certificates the verifier considers authoritative.
- Chain validation: signature, validity period, Extended Key Usage constraints, path constraints.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also include revocation of the certificate, this should be done for the whole chain, not just the signing certificate of the issuer

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 78e04e5. Both chain validation and revocation checking now state explicitly "for each certificate in the chain, not just the leaf". The configured trust anchor itself is excluded from chain-level revocation since it has its own out-of-band lifecycle.


**Operational notes**

- Self-contained: no runtime network dependency if revocation is skipped or cached.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove the part if revocation is skipped? This is could end up in insecure implementation :)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in 78e04e5. The operational note no longer mentions skipping revocation. It now states that revocation lookups (CRL or OCSP) typically still require network access at verification time, with stapling or caching as latency mitigations.


- Self-contained: no runtime network dependency if revocation is skipped or cached.
- Larger JWTs compared to key-pointer approaches (the certificates travel with every token).
- Key rotation requires re-issuing credentials with the updated chain.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is not true, new credentials can be issued with the new key without the requirement to reissue new ones.
This may only be the case when the current private key is leaked, which can forces a reissuance independent of the used key handling (so same problem for dids, etc)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, that was an error on my side — fixed in 78e04e5. The note now reads: new credentials are signed under whichever certificate chain is current at issuance; previously issued credentials remain valid under their original chain until expiry or revocation. Mass re-issuance is only required if a private key is compromised, which forces re-issuance regardless of the sourcing scheme.


## Approach 3 — X.509 certificate by reference

Instead of embedding the chain, only a reference to the certificate travels with the request. The certificate itself is retrieved out-of-band, usually provisioned ahead of time. The reference may be a hash (HAIP `x509_hash`), a URI, or a registry identifier.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not how the haip approach works. It only uses the client id hash approach for oid4vc which is using jwts
What you mean is the x5u approach where you link to a hosted x509 cert instead of embedding it + the hash for integrity.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, the previous draft conflated two layers. Refactored in 78e04e5. Approach 3 is now built around the standard JOSE x5u (RFC 7515 §4.1.5) and x5t#S256 (§4.1.8) headers — link to the hosted PEM cert + optional SHA-256 thumbprint for integrity, as you described. I added a separate note clarifying that the HAIP client_id_prefix: x509_hash mechanism (OID4VP §5.9.3, referenced by HAIP §5.2.3) lives in the authorization request layer, authenticates the verifier to the wallet, and is out of scope for issuer key sourcing.

@cre8

cre8 commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Thank you for the PR, I havent looked at everything yet, but here is the first wave of feedback

- Overview: replace the universal "pointer (or fingerprint)" framing
  with a clearer distinction between identifier-based approaches
  (`iss` + `kid`, DID URL) and certificate-material approaches
  (`x5c` embedded, `x5u` referenced); make explicit that the trust
  anchor lives outside the JWT in every case.

- Approach 1 (JWKS): clarify that previously active signing keys
  need to remain published in the JWKS as long as credentials
  signed with them are still valid; tighten the operational note
  on rotation windows.

- Approach 2 (x5c): chain validation and revocation checking now
  apply to each certificate in the chain (not only the leaf);
  remove the "if revocation is skipped" framing from the
  operational notes; correct the rotation note — programmed key
  rotation does not require re-issuing existing credentials, only
  a private-key compromise does (regardless of sourcing scheme).

- Approach 3: rewrite around the standard JOSE x5u
  (RFC 7515 §4.1.5) and x5t#S256 (§4.1.8) headers. The previous
  draft conflated this with HAIP client_id_prefix: x509_hash,
  which lives in the OID4VP authorization request layer and
  authenticates the verifier to the wallet — a different layer
  of the protocol stack. A note clarifies this distinction and
  points to OID4VP §5.9.3 / HAIP §5.2.3.

- Cross-cutting: key-rotation note now distinguishes signal
  sources per approach (keyset removal vs certificate revocation).

Signed-off-by: Olivier Meunier <oli.meunier@gmail.com>
@oliviermeunier

Copy link
Copy Markdown
Author

Thanks for the detailed review @cre8 — addressed all six points in 78e04e5. Happy to iterate when you've had a chance to look at the rest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Give implementation guide for good verifier functions

2 participants