[java-idp-plugin-vci] 01/01: W3C VCDM SD JWT credential support improved
Codeberg
noreply at shibboleth.net
Mon Apr 27 16:07:55 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch dev/VCDM
in repository java-idp-plugin-vci.
View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-vci/commit/46418683ffffd40f8444596e69e398a3f6d1753d
commit 46418683ffffd40f8444596e69e398a3f6d1753d
Author: Janne Lauros <janne.lauros at csc.fi>
AuthorDate: Mon Apr 27 19:07:18 2026 +0300
W3C VCDM SD JWT credential support improved
---
.../openidvci/profile/impl/AddCredentialShell.java | 40 ++++++++++++++++++----
...FormJsonLdSelectiveDisclosureJWTCredential.java | 38 +++++++++++---------
2 files changed, 55 insertions(+), 23 deletions(-)
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/AddCredentialShell.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/AddCredentialShell.java
index 867f040..5d9a082 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/AddCredentialShell.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/AddCredentialShell.java
@@ -15,10 +15,13 @@
*/
package org.geant.shibboleth.plugin.openidvci.profile.impl;
+import java.nio.charset.StandardCharsets;
+import java.text.ParseException;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
+import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -46,6 +49,7 @@ import org.slf4j.Logger;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.oauth2.sdk.id.Audience;
import com.nimbusds.oauth2.sdk.id.Issuer;
import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
@@ -186,8 +190,25 @@ public class AddCredentialShell extends AbstractProfileAction {
ClaimsSet shell = createShell();
Map<String, Object> cnfKid = new HashMap<>();
try {
- cnfKid.put("jwk",
- new ObjectMapper().readValue(proof.getHeader().getJWK().toJSONString(), Object.class));
+ // This is actually work that has already been done by credential resolver.
+ // TODO common util for parsing for this here and in resolver!
+ if (proof.getHeader().getJWK() != null) {
+ cnfKid.put("jwk",
+ new ObjectMapper().readValue(proof.getHeader().getJWK().toJSONString(), Object.class));
+ } else if (proof.getHeader().getKeyID() != null) {
+ String kid = proof.getHeader().getKeyID();
+ if (kid != null && kid.startsWith("did:jwk:") && kid.endsWith("#0")) {
+ String encoded = kid.substring("did:jwk:".length(), kid.indexOf('#'));
+ try {
+ final JWK didJwk = JWK.parse(
+ new String(Base64.getUrlDecoder().decode(encoded), StandardCharsets.UTF_8));
+ cnfKid.put("jwk", new ObjectMapper().readValue(didJwk.toJSONString(), Object.class));
+ ((Map) cnfKid.get("jwk")).put("kid", kid);
+ } catch (ParseException e) {
+ log.warn("Failed parsing kid {} as did:jwk", kid, e);
+ }
+ }
+ }
shell.setClaim("cnf", cnfKid);
} catch (JsonProcessingException e1) {
log.error("{} Parsing failed", getLogPrefix(), e1);
@@ -213,11 +234,16 @@ public class AddCredentialShell extends AbstractProfileAction {
shell.setIssuer(new Issuer(issuerId));
shell.setAudience(new Audience(rpCtx.getRelyingPartyId()));
ZonedDateTime now = ZonedDateTime.now();
- shell.setClaim("iat", now.toEpochSecond());
- shell.setClaim("exp", now.toEpochSecond() + expiration.toSeconds());
- shell.setClaim("validFrom", DateTimeFormatter.ISO_DATE_TIME.format(now));
- shell.setClaim("validUntil", DateTimeFormatter.ISO_DATE_TIME.format(now.plusSeconds(expiration.getSeconds())));
- shell.setClaim("vct", ctx.getCredentialIdentifier());
+ if ("dc+sd-jwt".equals(ctx.getCredentialConfiguration().getFormat())) {
+ shell.setClaim("iat", now.toEpochSecond());
+ shell.setClaim("exp", now.toEpochSecond() + expiration.toSeconds());
+ shell.setClaim("vct", ctx.getCredentialIdentifier());
+ }
+ if ("vc+sd-jwt".equals(ctx.getCredentialConfiguration().getFormat())) {
+ shell.setClaim("validFrom", DateTimeFormatter.ISO_DATE_TIME.format(now));
+ shell.setClaim("validUntil",
+ DateTimeFormatter.ISO_DATE_TIME.format(now.plusSeconds(expiration.getSeconds())));
+ }
return shell;
}
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormJsonLdSelectiveDisclosureJWTCredential.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormJsonLdSelectiveDisclosureJWTCredential.java
index 7be8479..1c8f217 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormJsonLdSelectiveDisclosureJWTCredential.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormJsonLdSelectiveDisclosureJWTCredential.java
@@ -42,11 +42,11 @@ import com.nimbusds.oauth2.sdk.ParseException;
/**
* Action that forms
- * {@link https://datatracker.ietf.org/doc/draft-ietf-oauth-sd-jwt-vc/13/}.
+ * {@link hhttps://www.w3.org/TR/2025/REC-vc-jose-cose-20250515/#with-sd-jwt}.
*
*
- * Action verifies first that we are indeed forming 'jwt_vc_json-ld' type
- * credential and then combines content for disclosure
+ * Action verifies first that we are indeed forming 'vc+sd-jwt' type credential
+ * and then combines content for disclosure
* {@link CredentialsContext#getCredentialContent()} with credential shells
* {@link CredentialsContext#getCredentialShells()}. The outcome is a list of
* JWTs ready to be signed. The list is stored to
@@ -83,8 +83,8 @@ public class FormJsonLdSelectiveDisclosureJWTCredential extends AbstractProfileA
return false;
}
if (ctx.getCredentialConfiguration() == null
- || !"jwt_vc_json-ld".equals(ctx.getCredentialConfiguration().getFormat())) {
- log.debug("{} No reason to continue. Credential configuration is not 'jwt_vc_json-ld'", getLogPrefix());
+ || !"vc+sd-jwt".equals(ctx.getCredentialConfiguration().getFormat())) {
+ log.debug("{} No reason to continue. Credential configuration is not 'vc+sd-jwt'", getLogPrefix());
return false;
}
credential = ctx.getCredentialContent();
@@ -104,22 +104,28 @@ public class FormJsonLdSelectiveDisclosureJWTCredential extends AbstractProfileA
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ Map<List<String>, Map<String, Object>> sdMaps = new HashMap<>();
+ for (CredentialOfferRequestedClaim claim : credential.getRequestedCredential()) {
+ List<String> path = new ArrayList<>(claim.getPath());
+ path.remove(path.size() - 1);
+ if (sdMaps.get(path) == null) {
+ sdMaps.put(path, new HashMap<>());
+ }
+ sdMaps.get(path).put(claim.getPath().get(claim.getPath().size() - 1), claim.getValue());
+ }
String disclosures = null;
Map<String, Object> credentialSubject = new HashMap<>();
- for (CredentialOfferRequestedClaim claim : credential.getRequestedCredential()) {
+ for (List<String> path : sdMaps.keySet()) {
Map<String, Object> map = credentialSubject;
- for (int i = 0; i < claim.getPath().size() - 1; i++) {
- if (!map.containsKey(claim.getPath().get(i))) {
- map.put(claim.getPath().get(i), new HashMap<String, Object>());
+ for (int i = 1; i < path.size() - 1; i++) {
+ if (!map.containsKey(path.get(i))) {
+ map.put(path.get(i), new HashMap<String, Object>());
}
- map = ((Map<String, Object>) map.get(claim.getPath().get(i)));
+ map = ((Map<String, Object>) map.get(path.get(i)));
+ log.info("map is the value of {}", path.get(i));
}
- Map<String, Object> mapClaim = new HashMap<>();
- mapClaim.put(claim.getPath().get(claim.getPath().size() - 1), claim.getValue());
- SelectiveDisclosureClaimSetUtil sdActClaims = new SelectiveDisclosureClaimSetUtil(mapClaim);
- Map<String, Object> item = new HashMap<>();
- item.put("_sd", sdActClaims.get_sd());
- map.put(claim.getPath().get(claim.getPath().size() - 1), item);
+ SelectiveDisclosureClaimSetUtil sdActClaims = new SelectiveDisclosureClaimSetUtil(sdMaps.get(path));
+ map.put("_sd", sdActClaims.get_sd());
disclosures = (disclosures == null ? sdActClaims.getFormattedDisclosures()
: disclosures + "~" + sdActClaims.getFormattedDisclosures());
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list