[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