[java-idp-plugin-vci] 02/03: For 'vc+sd-jwt' credentials use issuer from credential definition

Codeberg noreply at shibboleth.net
Tue Apr 28 11:21: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/37ebe56f864ea85aa1933b885e16bd9138fd93a8

commit 37ebe56f864ea85aa1933b885e16bd9138fd93a8
Author: Janne Lauros <janne.lauros at csc.fi>
AuthorDate: Tue Apr 28 14:20:11 2026 +0300

    For 'vc+sd-jwt' credentials use issuer from credential definition
---
 .../openidvci/profile/impl/AddCredentialShell.java | 88 ++++++++++++++--------
 1 file changed, 57 insertions(+), 31 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 9478dd5..c7e54ba 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
@@ -48,9 +48,8 @@ import org.opensaml.profile.context.ProfileRequestContext;
 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.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.id.Issuer;
 import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
 
@@ -186,38 +185,20 @@ public class AddCredentialShell extends AbstractProfileAction {
 
         List<ClaimsSet> shells = new ArrayList<>();
         if (ctx.getProofs() != null && !ctx.getProofs().isEmpty()) {
-            ctx.getProofs().forEach(proof -> {
+            for (SignedJWT proof : ctx.getProofs()) {
                 ClaimsSet shell = createShell();
-                Map<String, Object> cnfKid = new HashMap<>();
                 try {
-                    // 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);
-                            }
-                        }
+                    Map<String, Object> cnf = buildCnf(proof);
+                    if (cnf != null) {
+                        shell.setClaim("cnf", cnf);
                     }
-                    shell.setClaim("cnf", cnfKid);
-                } catch (JsonProcessingException e1) {
-                    log.error("{} Parsing failed", getLogPrefix(), e1);
+                } catch (Exception e) {
+                    log.error("{} Failed to build cnf", getLogPrefix(), e);
                     ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
                     return;
                 }
                 shells.add(shell);
-
-            });
+            }
         } else {
             shells.add(createShell());
         }
@@ -225,20 +206,65 @@ public class AddCredentialShell extends AbstractProfileAction {
     }
 
     /**
-     * Creates shell without proof.
-     * 
-     * @return Shell without proof
+     * Build cnf claim for response. Content is derived either from proof jwk header or kid value.
+     * @param proof previously verified proof
+     * @return cnf claim
+     * @throws JsonProcessingException if something unexpected occurs.
+     * @throws ParseException if something unexpected occurs.
+     */
+    private Map<String, Object> buildCnf(SignedJWT proof) throws JsonProcessingException, ParseException {
+
+        Map<String, Object> cnf = new HashMap<>();
+        Map<String, Object> jwkMap = null;
+        if (proof.getHeader().getJWK() != null) {
+            jwkMap = proof.getHeader().getJWK().toJSONObject();
+        } else if (proof.getHeader().getKeyID() != null) {
+            jwkMap = parseDidJwk(proof.getHeader().getKeyID());
+        }
+        if (jwkMap != null) {
+            cnf.put("jwk", jwkMap);
+            return cnf;
+        }
+        return null;
+    }
+
+    /**
+     * Parses did:jwk from keyId and created jwk map from it.
+     * @param kid containing did:jwk
+     * @return jwk map or null.
+     * @throws ParseException if something unexpected occurs.
+     */
+    private Map<String, Object> parseDidJwk(String kid) throws ParseException {
+
+        if (!kid.startsWith("did:jwk:") || !kid.endsWith("#0")) {
+            return null;
+        }
+        String encoded = kid.substring("did:jwk:".length(), kid.indexOf('#'));
+        String json = new String(Base64.getUrlDecoder().decode(encoded), StandardCharsets.UTF_8);
+        JWK jwk = JWK.parse(json);
+        Map<String, Object> jwkMap = jwk.toJSONObject();
+        jwkMap.put("kid", kid);
+        return jwkMap;
+    }
+
+    /**
+     * Create the shell for credential.
+     * @return credential shell.
      */
     private ClaimsSet createShell() {
         ClaimsSet shell = new ClaimsSet();
-        shell.setIssuer(new Issuer(issuerId));
+
         ZonedDateTime now = ZonedDateTime.now();
         if ("dc+sd-jwt".equals(ctx.getCredentialConfiguration().getFormat())) {
+            shell.setIssuer(new Issuer(issuerId));
             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())) {
+            String issuer = ctx.getCredentialConfiguration().getCredentialDefinition().getIssuer();
+            issuer = issuer == null ? issuerId : issuer;
+            shell.setIssuer(new Issuer(issuer));
             shell.setClaim("validFrom", DateTimeFormatter.ISO_INSTANT.format(now));
             shell.setClaim("validUntil",
                     DateTimeFormatter.ISO_INSTANT.format(now.plusSeconds(expiration.getSeconds())));

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list