OpenID4VP / SD-JWT VC verifier (v6.6)
SdJwtVcVerifier validates the dc+sd-jwt presentations: the issuer signature and trust, the selective
disclosures, the holder binding and the credential status. It is registered by default, but trusts no issuer until
you configure trusted issuers.
See also:
▸ Response validation and verifiers
▸ DCQL queries, user profiles and logging
1) Dependencies
The verifier relies on the EUDI SD-JWT library, which is not included by default:
<dependency>
<groupId>eu.europa.ec.eudi</groupId>
<artifactId>eudi-lib-jvm-sdjwt-kt</artifactId>
<version>0.20.1</version>
</dependency>
Without it, SdJwtVcVerifier throws an OpenId4VpException when validating a credential, with a message identifying
the dependency to add. Applications replacing it with their own CredentialVerifier do not need EUDI.
If you also use the mdoc verifier, align the Kotlin dependencies as explained in Kotlin alignment.
2) Quick start
// trustedIssuers: see "Trusted issuers" in Response validation and verifiers
config.addCredentialVerifier(new SdJwtVcVerifier().setTrustedIssuers(trustedIssuers));
config.setDcqlQuery(EudiPidQuery.sdJwtVc(GIVEN_NAME, AGE_OVER_18));
3) What is verified
- Issuer signature and trust:
typ=dc+sd-jwtandvctare required, and anissclaim or anx5cchain, checked by the trusted issuers. - Disclosures: EUDI verifies the disclosure hashes.
iss,vctandaka_vctsmust not be selectively disclosed. - Validity:
expandnbfare checked, without tolerance. - Key-binding JWT, when present: its signature,
typ=kb+jwtandsd_hash, then the holder binding.
4) Holder binding
The key-binding JWT must match the transaction’s saved request:
| Check | URL / QR code flow | Digital Credentials API |
|---|---|---|
nonce |
the request nonce |
the request nonce |
aud |
the full client_id |
origin: followed by one of the saved expected_origins |
iat |
between the transaction creation and now | same |
- Clock skew: the
iatwindow has a 30-second tolerance, configurable throughclockSkewSeconds. - Missing proof: a presentation without key binding is reported as such and rejected by the common DCQL validator,
unless the query explicitly sets
require_cryptographic_holder_bindingto false. - Invalid proof: a present but invalid proof is always rejected.
5) Supported presentations
| Supported | Not supported (rejected) |
|---|---|
| SD-JWT compact serialization, with or without key-binding JWT | other serializations |
asymmetric signature algorithms: ES256 by default, configurable with issuerAlgorithms and holderAlgorithms |
symmetric algorithms |
issuer keys from the trusted issuers: configured keys or x5c chain |
issuer metadata, DID |
| nested objects and array elements disclosures |
- Capabilities: the accepted algorithms are advertised in
vp_formats_supported, assd-jwt_alg_valuesandkb-jwt_alg_values. - Types: besides
vct, the other types the issuer signed inaka_vctsare reported inVerifiedCredential.getAdditionalTypes()and matched against thevct_valuesof the DCQL query. The type metadata is not used.
6) Claims and profile identifier
The claims are the reconstructed JSON payload: nested objects and arrays keep their structure, digests are removed, and an undisclosed claim is absent, which is not an error:
{
"iss": "https://issuer.example",
"vct": "urn:eudi:pid:1",
"given_name": "Erika",
"address": {"locality": "Berlin"}
}
The issuer is the configured iss or, with an x5c chain, the leaf certificate subject (RFC 2253): the iss
claim is then kept as a mere claim.
The default profile identifier,
ProfileIdResolver.issuerAndClaim("sub"), reads a top-level claim and combines it with the issuer; it checks at
initialization that the DCQL query can return that claim. EudiWalletClient has no default: choose a stable claim, such
as ProfileIdResolver.issuerAndClaim(PERSONAL_ADMINISTRATIVE_NUMBER) where the PID provider issues it.
7) Credential status
Credential status is not checked automatically, and no status list is fetched. A credential containing a status
claim is rejected unless setStatusChecker(...) is configured:
- the checker receives the cryptographically verified credential, including its reconstructed claims;
- it must throw if the status is invalid, unsupported or cannot be checked;
- if it uses a status list, it must retrieve it if needed, authenticate it and check its validity and the credential’s status entry.
A credential without a status claim does not invoke the checker.
8) Scope and limits
This is a minimal SD-JWT VC integration, not a complete EUDI trust-list or HAIP validation implementation. EUDI provides the parsing, the disclosure verification and the key-binding JWT signature checks; pac4j supplies the transaction binding, the issuer trust and the profile mapping.