docs: add verifier public-key sourcing guide#370
Conversation
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Also include revocation of the certificate, this should be done for the whole chain, not just the signing certificate of the issuer
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Can we remove the part if revocation is skipped? This is could end up in insecure implementation :)
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
|
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>
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:
x509_hashand related) that @cre8 raised as an emerging patternApproaches covered
iss-based, well-known JWKS endpoint per the SD-JWT-VC draft "JWT VC Issuer Metadata" rulesx5cJWT headerx509_hash(mostly seen in the enclosing OID4VP JAR) and URI-based variantsCross-cutting operational notes
JWKS / certificate caching, key rotation, clock skew, rate limiting, trust-anchor bootstrapping.
Two distinctions made explicit
Placement
Placed at
docs/0.x/verifier-public-key-sourcing.mdand linked fromdocs/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 underdocs/) 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.