[java-idp-plugin-vci] 02/02: Refactor forming of credential to make it possible to add support new credential types like 'jwt_vc_json-ld'

Codeberg noreply at shibboleth.net
Mon Jan 5 12:20:12 UTC 2026


This is an automated email from the git hooks/post-receive script.

codeberg pushed a commit to branch dev/W3CCred
in repository java-idp-plugin-vci.

View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-vci/commit/6e6c6903fe13d318f9659990d065a712b232d794

commit 6e6c6903fe13d318f9659990d065a712b232d794
Author: jlauros <janne.lauros at csc.fi>
AuthorDate: Mon Jan 5 14:19:57 2026 +0200

    Refactor forming of credential to make it possible to add support new credential types like 'jwt_vc_json-ld'
---
 .../messaging/context/CredentialsContext.java      | 150 ++++++++++++++-----
 .../openidvci/profile/impl/AddCredentialShell.java |   2 +-
 .../FormOutboundCredentialsResponseMessage.java    | 161 ++-------------------
 .../impl/FormSelectiveDisclosureJWTCredential.java | 125 ++++++++++++++++
 ...ResponseMessage.java => SignJWTCredential.java} |  77 ++++------
 .../profile/impl/ValidateRequestedCredential.java  |   4 +-
 .../openid/vci/credentials/credentials-beans.xml   |  12 +-
 .../openid/vci/credentials/credentials-flow.xml    |   2 +
 8 files changed, 289 insertions(+), 244 deletions(-)

diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/context/CredentialsContext.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/context/CredentialsContext.java
index c2ab09a..4f50726 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/context/CredentialsContext.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/context/CredentialsContext.java
@@ -16,6 +16,7 @@
 
 package org.geant.shibboleth.plugin.openidvci.messaging.context;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import javax.annotation.Nullable;
@@ -25,6 +26,7 @@ import org.geant.shibboleth.plugin.openidvci.messaging.impl.CredentialOfferReque
 import org.opensaml.messaging.context.BaseContext;
 
 import com.nimbusds.jose.JWSObject;
+import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
 
 /**
@@ -33,13 +35,13 @@ import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
  */
 public class CredentialsContext extends BaseContext {
 
-    /** Credential content to be returned in response. */
-    private CredentialOfferRequestedCredential credential;
+    /** Credential content to for the final credential. */
+    private CredentialOfferRequestedCredential credentialContent;
 
-    /** Credential configuration for the credential to be returned in response. */
-    private CredentialConfiguration configuration;
+    /** Credential configuration for the final credential. */
+    private CredentialConfiguration credentialConfiguration;
 
-    /** Validated identifier for credential. */
+    /** Validated identifier for credential configuration. */
     @Nullable
     private String credentialIdentifier;
 
@@ -47,14 +49,26 @@ public class CredentialsContext extends BaseContext {
     @Nullable
     private List<JWSObject> proofs;
 
-    /** Credential claims. */
+    /** Credential shell(s) per proof. */
     @Nullable
-    private List<ClaimsSet> credentialClaimsSet;
+    private List<ClaimsSet> credentialShells;
+
+    /** JWT based credential per proof ready to be signed. */
+    @Nullable
+    private List<JWTClaimsSet> jwtCredential = new ArrayList<>();
+
+    /** Per proof signed credential. */
+    @Nullable
+    private List<String> signedJWTCredential;
+
+    /** Disclosures of JWT based credential. */
+    @Nullable
+    private String disclosures;
 
     /**
      * Get validated proofs of wallet.
      * 
-     * @return Validated proofs of walle
+     * @return Validated proofs of wallet
      */
     @Nullable
     public List<JWSObject> getProofs() {
@@ -71,47 +85,49 @@ public class CredentialsContext extends BaseContext {
     }
 
     /**
-     * Get credential content to be returned in response.
+     * Get credential content to for the final credential.
      * 
-     * @return Credential content to be returned in response
+     * @return Credential content to for the final credential
      */
-    public CredentialOfferRequestedCredential getCredential() {
-        return credential;
+    @Nullable
+    public CredentialOfferRequestedCredential getCredentialContent() {
+        return credentialContent;
     }
 
     /**
-     * Set credential content to be returned in response.
+     * Set credential content to for the final credential.
      * 
-     * @param credential Credential content to be returned in response
+     * @param credentialContent Credential content to for the final credential
      */
-    public void setCredential(CredentialOfferRequestedCredential credential) {
-        this.credential = credential;
+    public void setCredentialContent(@Nullable CredentialOfferRequestedCredential credentialContent) {
+        this.credentialContent = credentialContent;
     }
 
     /**
-     * Get credential configuration for the credential to be returned in response.
+     * Get credential configuration for the final credential.
      * 
      * @return Credential configuration for the credential to be returned in
      *         response
      */
-    public CredentialConfiguration getConfiguration() {
-        return configuration;
+    @Nullable
+    public CredentialConfiguration getCredentialConfiguration() {
+        return credentialConfiguration;
     }
 
     /**
-     * Set credential configuration for the credential to be returned in response.
+     * Set credential configuration for the final credential.
      * 
-     * @param configuration Credential configuration for the credential to be
-     *                      returned in response
+     * @param credentialConfiguration Credential configuration for the credential to
+     *                                be returned in response
      */
-    public void setConfiguration(CredentialConfiguration configuration) {
-        this.configuration = configuration;
+    public void setCredentialConfiguration(@Nullable CredentialConfiguration credentialConfiguration) {
+        this.credentialConfiguration = credentialConfiguration;
     }
 
     /**
-     * Set validated identifier for credential.
+     * Set validated identifier for credential configuration.
      * 
-     * @return Validated identifier for credential
+     * @return Validated identifier for credential configuration
      */
     @Nullable
     public String getCredentialIdentifier() {
@@ -119,30 +135,88 @@ public class CredentialsContext extends BaseContext {
     }
 
     /**
-     * Get validated identifier for credential.
+     * Get validated identifier for credential configuration.
+     * 
+     * @param credentialIdentifier Validated identifier for credential configuration
+     */
+    public void setCredentialIdentifier(@Nullable String credentialIdentifier) {
+        this.credentialIdentifier = credentialIdentifier;
+    }
+
+    /**
+     * Get credential shell(s) per proof.
+     * 
+     * @return Credential shell(s) per proof
+     */
+    @Nullable
+    public List<ClaimsSet> getCredentialShells() {
+        return credentialShells;
+    }
+
+    /**
+     * Set credential shell(s) per proof.
+     * 
+     * @param credentialShells Credential shell(s) per proof
+     */
+    public void setCredentialShells(@Nullable List<ClaimsSet> credentialShells) {
+        this.credentialShells = credentialShells;
+    }
+
+    /**
+     * Get JWT based credential per proof ready to be signed.
      * 
-     * @param identifier Validated identifier for credential
+     * @return JWT based credential per proof ready to be signed
      */
-    public void setCredentialIdentifier(@Nullable String identifier) {
-        credentialIdentifier = identifier;
+    @Nullable
+    public List<JWTClaimsSet> getJWTCredentials() {
+        return jwtCredential;
     }
 
     /**
-     * Get credential claims.
+     * Set JWT based credential per proof ready to be signed
      * 
-     * @return Credential claims
+     * @param jwtCredential JWT based credential per proof ready to be signed
      */
-    public List<ClaimsSet> getCredentialClaimsSet() {
-        return credentialClaimsSet;
+    public void setJWTCredentials(List<JWTClaimsSet> jwtCredential) {
+        this.jwtCredential = jwtCredential;
     }
 
     /**
-     * Set credential claims.
+     * Get Disclosures of JWT based credential.
      * 
-     * @param credentialClaimsSet Credential claims
+     * @return Disclosures of JWT based credential
      */
-    public void setCredentialClaimsSet(List<ClaimsSet> credentialClaimsSet) {
-        this.credentialClaimsSet = credentialClaimsSet;
+    public String getDisclosures() {
+        return disclosures;
+    }
+
+    /**
+     * Set Disclosures of JWT based credential
+     * 
+     * @param disclosures Disclosures of JWT based credential
+     */
+    public void setDisclosures(String disclosures) {
+        this.disclosures = disclosures;
+    }
+
+    /**
+     * Get per proof signed credential.
+     * 
+     * @return Per proof signed credential
+     */
+    @Nullable
+    public List<String> getSignedCredential() {
+        return signedJWTCredential;
+    }
+
+    /**
+     * Set per proof signed credential.
+     * 
+     * @param signedCredential Per proof signed credential
+     */
+    @Nullable
+    public void setSignedCredential(List<String> signedCredential) {
+        this.signedJWTCredential = signedCredential;
     }
 
 }
\ No newline at end of file
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 6443aab..867f040 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
@@ -200,7 +200,7 @@ public class AddCredentialShell extends AbstractProfileAction {
         } else {
             shells.add(createShell());
         }
-        ctx.setCredentialClaimsSet(shells);
+        ctx.setCredentialShells(shells);
     }
 
     /**
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundCredentialsResponseMessage.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundCredentialsResponseMessage.java
index 8279494..fdbc175 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundCredentialsResponseMessage.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundCredentialsResponseMessage.java
@@ -17,47 +17,23 @@
 package org.geant.shibboleth.plugin.openidvci.profile.impl;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
 
 import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import java.security.interfaces.ECPrivateKey;
 
 import org.geant.shibboleth.plugin.openidvci.messaging.context.CredentialsContext;
-import org.geant.shibboleth.plugin.openidvci.messaging.impl.CredentialOfferRequestedCredential;
 import org.geant.shibboleth.plugin.openidvci.messaging.impl.CredentialSuccessResponse;
 import org.geant.shibboleth.plugin.openidvci.messaging.impl.OpenIDVCICredentialsRequest;
 import org.geant.shibboleth.plugin.openidvci.profile.impl.stolen.AbstractOIDCResponseAction;
-import org.geant.shibboleth.plugin.openidvci.util.SelectiveDisclosureClaimSetUtil;
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
-import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import net.shibboleth.oidc.security.jose.SignatureSigningParameters;
-import net.shibboleth.oidc.security.jose.context.SecurityParametersContext;
-import net.shibboleth.profile.context.navigate.IssuerLookupFunction;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.logic.FunctionSupport;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
-import com.nimbusds.jose.JOSEException;
-import com.nimbusds.jose.JOSEObjectType;
-import com.nimbusds.jose.JWSAlgorithm;
-import com.nimbusds.jose.JWSHeader;
-import com.nimbusds.jose.crypto.ECDSASigner;
-import com.nimbusds.jose.util.Base64;
-import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.jwt.JWTClaimsSet.Builder;
-import com.nimbusds.jwt.SignedJWT;
-import com.nimbusds.oauth2.sdk.ParseException;
 
 /**
  * Action forming {@link CredentialSuccessResponse}
@@ -68,78 +44,8 @@ public class FormOutboundCredentialsResponseMessage extends AbstractOIDCResponse
     @Nonnull
     private Logger log = LoggerFactory.getLogger(FormOutboundCredentialsResponseMessage.class);
 
-    /**
-     * Strategy used to locate the {@link SecurityParametersContext} to use for
-     * signing.
-     */
-    @Nonnull
-    private Function<ProfileRequestContext, SecurityParametersContext> securityParametersLookupStrategy;
-
-    /** Strategy used to obtain the response issuer value. */
-    @Nonnull
-    private Function<ProfileRequestContext, String> issuerLookupStrategy;
-
-    /** The signature signing parameters. */
-    @Nullable
-    private SignatureSigningParameters signatureSigningParameters;
-
-    /** Strategy used to obtain the certificate chain for signing key. */
-    @Nonnull
-    private Function<SignatureSigningParameters, List<Base64>> certificateChainLookupStrategy;
-
-    public FormOutboundCredentialsResponseMessage() {
-        final Function<ProfileRequestContext, SecurityParametersContext> splcs = new ChildContextLookup<>(
-                SecurityParametersContext.class).compose(new OutboundMessageContextLookup());
-        assert splcs != null;
-        securityParametersLookupStrategy = splcs;
-        issuerLookupStrategy = (Function<ProfileRequestContext, String>) new IssuerLookupFunction();
-        certificateChainLookupStrategy = FunctionSupport.constant(null);
-    }
-
-    /**
-     * Set the strategy used to locate the {@link SecurityParametersContext} to use.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setSecurityParametersLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, SecurityParametersContext> strategy) {
-        ifInitializedThrowUnmodifiabledComponentException();
-
-        securityParametersLookupStrategy = Constraint.isNotNull(strategy,
-                "SecurityParameterContext lookup strategy cannot be null");
-    }
-
-    /**
-     * Set the strategy used to locate the issuer value to use.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setIssuerLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
-        checkSetterPreconditions();
-        issuerLookupStrategy = Constraint.isNotNull(strategy, "IssuerLookupStrategy lookup strategy cannot be null");
-    }
-
-    /**
-     * Set the strategy used to locate the issuer value to use.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setCertificateChainLookupStrategy(
-            @Nonnull Function<SignatureSigningParameters, List<Base64>> strategy) {
-        checkSetterPreconditions();
-        certificateChainLookupStrategy = Constraint.isNotNull(strategy,
-                "CertificateChainLookupStrategy lookup strategy cannot be null");
-    }
-
-    /**
-     * Get the signing parameters to apply.
-     * 
-     * @return the signing parameters
-     */
-    @Nullable
-    public SignatureSigningParameters getSignatureSigningParameters() {
-        return signatureSigningParameters;
-    }
+    @NonnullBeforeExec
+    private CredentialsContext ctx;
 
     /** {@inheritDoc} */
     @Override
@@ -149,22 +55,13 @@ public class FormOutboundCredentialsResponseMessage extends AbstractOIDCResponse
             return false;
         }
 
-        final SecurityParametersContext secParamCtx = securityParametersLookupStrategy.apply(profileRequestContext);
-        if (secParamCtx == null) {
-            log.debug("{} no security parameters context is available", getLogPrefix());
+        ctx = profileRequestContext.getInboundMessageContext().getSubcontext(CredentialsContext.class);
+        if (ctx == null) {
+            log.error("{} No credentials context", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return false;
         }
 
-        signatureSigningParameters = secParamCtx.getSignatureSigningParameters();
-        if (signatureSigningParameters == null) {
-            log.debug("{} no signature signing parameters available", getLogPrefix());
-            return false;
-        }
-        assert signatureSigningParameters != null;
-        if (signatureSigningParameters.getSigningCredential() == null) {
-            log.debug("{} no signature signing credentials available", getLogPrefix());
-            return false;
-        }
         return true;
     }
 
@@ -178,52 +75,14 @@ public class FormOutboundCredentialsResponseMessage extends AbstractOIDCResponse
             return;
         }
         try {
-            CredentialsContext ctx = profileRequestContext.getInboundMessageContext()
-                    .getSubcontext(CredentialsContext.class);
-
-            CredentialOfferRequestedCredential credential = ctx.getCredential();
-            log.info("Credentials stored credential {} {} ", ctx.getCredentialIdentifier(), credential.serialize());
-            log.info("Key used, algorithm {}", signatureSigningParameters.getSignatureAlgorithm());
-
-            ECPrivateKey key = ((ECPrivateKey) signatureSigningParameters.getSigningCredential().getPrivateKey());
-            ECDSASigner signer = new ECDSASigner(key);
-
-            // Selective Disclosure
-            Map<String, Object> claims = new HashMap<String, Object>();
-            credential.getRequestedCredential().forEach(claim -> claims.put(claim.getPath().get(0), claim.getValue()));
-            SelectiveDisclosureClaimSetUtil sdClaims = new SelectiveDisclosureClaimSetUtil(claims);
-
             List<String> credentials = new ArrayList<>();
-            ctx.getCredentialClaimsSet().forEach(cred -> {
-                Builder build = null;
-                try {
-                    build = new JWTClaimsSet.Builder(cred.toJWTClaimsSet()).claim("_sd", sdClaims.get_sd())
-                            .claim("_sd_alg", sdClaims.get_alg());
-                } catch (ParseException e1) {
-                    // TODO Auto-generated catch block
-                    e1.printStackTrace();
-                }
-                JWTClaimsSet claimsSet = build.build();
-                SignedJWT signedJWT = new SignedJWT(
-                        new JWSHeader.Builder(new JWSAlgorithm(signatureSigningParameters.getSignatureAlgorithm()))
-                                .x509CertChain(certificateChainLookupStrategy.apply(signatureSigningParameters))
-                                .type(new JOSEObjectType("dc+sd-jwt")).keyID("signing").build(),
-                        claimsSet);
-                try {
-                    signedJWT.sign(signer);
-                } catch (JOSEException e) {
-                    // TODO Auto-generated catch block
-                    e.printStackTrace();
-                }
-                String v15 = signedJWT.serialize();
-                // Add the plain text claims
-                v15 += "~" + sdClaims.getFormattedDisclosures() + "~";
-                credentials.add(v15);
+            ctx.getSignedCredential().forEach(cred -> {
+                credentials.add(cred + (ctx.getDisclosures() != null ? "~" + ctx.getDisclosures() + "~" : ""));
             });
             CredentialSuccessResponse response = new CredentialSuccessResponse(credentials);
             log.info("Setting response as {}", response.toOffer());
             profileRequestContext.ensureOutboundMessageContext().setMessage(response);
-        } catch (JOSEException | JsonProcessingException e) {
+        } catch (JsonProcessingException e) {
             log.error("{} Error occurred while parsing objects {}", getLogPrefix(), e);
             ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
         }
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormSelectiveDisclosureJWTCredential.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormSelectiveDisclosureJWTCredential.java
new file mode 100644
index 0000000..5963413
--- /dev/null
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormSelectiveDisclosureJWTCredential.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2025, GÉANT
+ *
+ * Licensed under the Apache License, Version 2.0 (the “License”); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an “AS IS” BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.geant.shibboleth.plugin.openidvci.profile.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import org.geant.shibboleth.plugin.openidvci.messaging.context.CredentialsContext;
+import org.geant.shibboleth.plugin.openidvci.messaging.impl.CredentialOfferRequestedCredential;
+import org.geant.shibboleth.plugin.openidvci.profile.OpenIDVCIEventIds;
+import org.geant.shibboleth.plugin.openidvci.util.SelectiveDisclosureClaimSetUtil;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.JWTClaimsSet.Builder;
+import com.nimbusds.oauth2.sdk.ParseException;
+
+/**
+ * Action that forms
+ * {@link https://datatracker.ietf.org/doc/draft-ietf-oauth-sd-jwt-vc/13/}.
+ * 
+ * 
+ * Action verifies first that we are indeed forming 'dc+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
+ * {@link CredentialsContext#setJWTCredentials}. The disclosure data is stored
+ * to {@link CredentialsContext#setDisclosures}
+ * 
+ */
+public class FormSelectiveDisclosureJWTCredential extends AbstractProfileAction {
+
+    /** Class logger. */
+    @Nonnull
+    private Logger log = LoggerFactory.getLogger(FormSelectiveDisclosureJWTCredential.class);
+
+    @NonnullBeforeExec
+    private CredentialsContext ctx;
+
+    @NonnullBeforeExec
+    CredentialOfferRequestedCredential credential;
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+        ctx = profileRequestContext.getInboundMessageContext().getSubcontext(CredentialsContext.class);
+        if (ctx == null) {
+            log.error("{} No credentials context", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+        if (ctx.getCredentialConfiguration() == null
+                || !"dc+sd-jwt".equals(ctx.getCredentialConfiguration().getFormat())) {
+            log.debug("{} No reason to continue. Credential configuration is not 'dc+sd-jwt'", getLogPrefix());
+            return true;
+        }
+        credential = ctx.getCredentialContent();
+        if (credential == null) {
+            log.warn("{} No credential content in context, nothingh to do", getLogPrefix());
+            return false;
+        }
+        if (ctx.getCredentialShells() == null || ctx.getCredentialShells().isEmpty()) {
+            log.warn("{} No shells stored to context, nothing to do", getLogPrefix());
+            return false;
+        }
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+        Map<String, Object> claims = new HashMap<String, Object>();
+        // TODO: support for depth of more than 1.
+        credential.getRequestedCredential().forEach(claim -> claims.put(claim.getPath().get(0), claim.getValue()));
+        SelectiveDisclosureClaimSetUtil sdClaims = new SelectiveDisclosureClaimSetUtil(claims);
+        List<JWTClaimsSet> credentials = new ArrayList<>();
+        // add selective disclosure claims to each shell
+        ctx.getCredentialShells().forEach(cred -> {
+            Builder build = null;
+            try {
+                build = new JWTClaimsSet.Builder(cred.toJWTClaimsSet()).claim("_sd", sdClaims.get_sd()).claim("_sd_alg",
+                        sdClaims.get_alg());
+            } catch (ParseException e) {
+                log.error("{} Parsing credential failed", getLogPrefix(), e);
+                ActionSupport.buildEvent(profileRequestContext, OpenIDVCIEventIds.INVALID_CREDENTIAL);
+                return;
+            }
+            credentials.add(build.build());
+
+        });
+        ctx.setJWTCredentials(credentials);
+        ctx.setDisclosures(sdClaims.getFormattedDisclosures());
+    }
+}
\ No newline at end of file
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundCredentialsResponseMessage.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/SignJWTCredential.java
similarity index 72%
copy from openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundCredentialsResponseMessage.java
copy to openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/SignJWTCredential.java
index 8279494..9dac333 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/FormOutboundCredentialsResponseMessage.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/SignJWTCredential.java
@@ -17,9 +17,7 @@
 package org.geant.shibboleth.plugin.openidvci.profile.impl;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
@@ -28,11 +26,9 @@ import javax.annotation.Nullable;
 import java.security.interfaces.ECPrivateKey;
 
 import org.geant.shibboleth.plugin.openidvci.messaging.context.CredentialsContext;
-import org.geant.shibboleth.plugin.openidvci.messaging.impl.CredentialOfferRequestedCredential;
-import org.geant.shibboleth.plugin.openidvci.messaging.impl.CredentialSuccessResponse;
 import org.geant.shibboleth.plugin.openidvci.messaging.impl.OpenIDVCICredentialsRequest;
+import org.geant.shibboleth.plugin.openidvci.profile.OpenIDVCIEventIds;
 import org.geant.shibboleth.plugin.openidvci.profile.impl.stolen.AbstractOIDCResponseAction;
-import org.geant.shibboleth.plugin.openidvci.util.SelectiveDisclosureClaimSetUtil;
 import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
@@ -44,29 +40,31 @@ import org.slf4j.LoggerFactory;
 import net.shibboleth.oidc.security.jose.SignatureSigningParameters;
 import net.shibboleth.oidc.security.jose.context.SecurityParametersContext;
 import net.shibboleth.profile.context.navigate.IssuerLookupFunction;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.logic.FunctionSupport;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
 import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.JOSEObjectType;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.JWSHeader;
 import com.nimbusds.jose.crypto.ECDSASigner;
 import com.nimbusds.jose.util.Base64;
-import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.jwt.JWTClaimsSet.Builder;
 import com.nimbusds.jwt.SignedJWT;
-import com.nimbusds.oauth2.sdk.ParseException;
 
 /**
- * Action forming {@link CredentialSuccessResponse}
+ * Action that signs all jwt verifiable credentials
+ * {@link CredentialsContext#getJWTCredentials} storing the result to
+ * {@link CredentialsContext#setSignedCredential}
  */
-public class FormOutboundCredentialsResponseMessage extends AbstractOIDCResponseAction {
+public class SignJWTCredential extends AbstractOIDCResponseAction {
 
     /** Class logger. */
     @Nonnull
-    private Logger log = LoggerFactory.getLogger(FormOutboundCredentialsResponseMessage.class);
+    private Logger log = LoggerFactory.getLogger(SignJWTCredential.class);
+
+    @NonnullBeforeExec
+    private CredentialsContext ctx;
 
     /**
      * Strategy used to locate the {@link SecurityParametersContext} to use for
@@ -87,7 +85,7 @@ public class FormOutboundCredentialsResponseMessage extends AbstractOIDCResponse
     @Nonnull
     private Function<SignatureSigningParameters, List<Base64>> certificateChainLookupStrategy;
 
-    public FormOutboundCredentialsResponseMessage() {
+    public SignJWTCredential() {
         final Function<ProfileRequestContext, SecurityParametersContext> splcs = new ChildContextLookup<>(
                 SecurityParametersContext.class).compose(new OutboundMessageContextLookup());
         assert splcs != null;
@@ -148,7 +146,12 @@ public class FormOutboundCredentialsResponseMessage extends AbstractOIDCResponse
         if (!super.doPreExecute(profileRequestContext)) {
             return false;
         }
-
+        ctx = profileRequestContext.getInboundMessageContext().getSubcontext(CredentialsContext.class);
+        if (ctx == null) {
+            log.error("{} No credentials context", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
         final SecurityParametersContext secParamCtx = securityParametersLookupStrategy.apply(profileRequestContext);
         if (secParamCtx == null) {
             log.debug("{} no security parameters context is available", getLogPrefix());
@@ -178,52 +181,28 @@ public class FormOutboundCredentialsResponseMessage extends AbstractOIDCResponse
             return;
         }
         try {
-            CredentialsContext ctx = profileRequestContext.getInboundMessageContext()
-                    .getSubcontext(CredentialsContext.class);
-
-            CredentialOfferRequestedCredential credential = ctx.getCredential();
-            log.info("Credentials stored credential {} {} ", ctx.getCredentialIdentifier(), credential.serialize());
-            log.info("Key used, algorithm {}", signatureSigningParameters.getSignatureAlgorithm());
-
             ECPrivateKey key = ((ECPrivateKey) signatureSigningParameters.getSigningCredential().getPrivateKey());
             ECDSASigner signer = new ECDSASigner(key);
-
-            // Selective Disclosure
-            Map<String, Object> claims = new HashMap<String, Object>();
-            credential.getRequestedCredential().forEach(claim -> claims.put(claim.getPath().get(0), claim.getValue()));
-            SelectiveDisclosureClaimSetUtil sdClaims = new SelectiveDisclosureClaimSetUtil(claims);
-
             List<String> credentials = new ArrayList<>();
-            ctx.getCredentialClaimsSet().forEach(cred -> {
-                Builder build = null;
-                try {
-                    build = new JWTClaimsSet.Builder(cred.toJWTClaimsSet()).claim("_sd", sdClaims.get_sd())
-                            .claim("_sd_alg", sdClaims.get_alg());
-                } catch (ParseException e1) {
-                    // TODO Auto-generated catch block
-                    e1.printStackTrace();
-                }
-                JWTClaimsSet claimsSet = build.build();
+            ctx.getJWTCredentials().forEach(credential -> {
                 SignedJWT signedJWT = new SignedJWT(
                         new JWSHeader.Builder(new JWSAlgorithm(signatureSigningParameters.getSignatureAlgorithm()))
                                 .x509CertChain(certificateChainLookupStrategy.apply(signatureSigningParameters))
-                                .type(new JOSEObjectType("dc+sd-jwt")).keyID("signing").build(),
-                        claimsSet);
+                                .type(new JOSEObjectType(ctx.getCredentialConfiguration().getFormat())).keyID("signing")
+                                .build(),
+                        credential);
                 try {
                     signedJWT.sign(signer);
                 } catch (JOSEException e) {
-                    // TODO Auto-generated catch block
-                    e.printStackTrace();
+                    log.error("{} Signing credential failed", getLogPrefix(), e);
+                    ActionSupport.buildEvent(profileRequestContext, OpenIDVCIEventIds.INVALID_CREDENTIAL);
+                    return;
                 }
-                String v15 = signedJWT.serialize();
-                // Add the plain text claims
-                v15 += "~" + sdClaims.getFormattedDisclosures() + "~";
-                credentials.add(v15);
+                credentials.add(signedJWT.serialize());
             });
-            CredentialSuccessResponse response = new CredentialSuccessResponse(credentials);
-            log.info("Setting response as {}", response.toOffer());
-            profileRequestContext.ensureOutboundMessageContext().setMessage(response);
-        } catch (JOSEException | JsonProcessingException e) {
+            ctx.setSignedCredential(credentials);
+            log.info("Signed {} credentials", credentials.size());
+        } catch (JOSEException e) {
             log.error("{} Error occurred while parsing objects {}", getLogPrefix(), e);
             ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
         }
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidateRequestedCredential.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidateRequestedCredential.java
index 5288bb2..be81f57 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidateRequestedCredential.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidateRequestedCredential.java
@@ -175,8 +175,8 @@ public class ValidateRequestedCredential extends AbstractCredentialValidationAct
         // TODO: Move context creation to initialization phase
         CredentialsContext ctx = profileRequestContext.getInboundMessageContext()
                 .ensureSubcontext(CredentialsContext.class);
-        ctx.setCredential(credential);
-        ctx.setConfiguration(configuration);
+        ctx.setCredentialContent(credential);
+        ctx.setCredentialConfiguration(configuration);
         // TODO: This should be value in configuration. Fix.
         ctx.setCredentialIdentifier(idInRequest);
     }
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credentials/credentials-beans.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credentials/credentials-beans.xml
index c01f354..88263c3 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credentials/credentials-beans.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credentials/credentials-beans.xml
@@ -73,15 +73,21 @@
     
   <bean id="AddCredentialShell" class="org.geant.shibboleth.plugin.openidvci.profile.impl.AddCredentialShell"
         scope="prototype" />
-          
-  <bean id="FormOutboundMessage" class="org.geant.shibboleth.plugin.openidvci.profile.impl.FormOutboundCredentialsResponseMessage"
+        
+  <bean id="OptionallyFormSelectiveDisclosureJWTCredential" class="org.geant.shibboleth.plugin.openidvci.profile.impl.FormSelectiveDisclosureJWTCredential"
+        scope="prototype" />
+  
+  <bean id="SignJWTCredential" class="org.geant.shibboleth.plugin.openidvci.profile.impl.SignJWTCredential"
         scope="prototype" p:issuerLookupStrategy-ref="shibboleth.ResponderIdLookup.Simple" >
         <property name="securityParametersLookupStrategy">
             <bean parent="shibboleth.Functions.Compose"
                 c:g-ref="shibboleth.oidc.ChildLookup.JWTSecurityParameters"
                 c:f-ref="shibboleth.MessageContextLookup.Outbound" />
         </property>
-  </bean>      
+  </bean>
+          
+  <bean id="FormOutboundMessage" class="org.geant.shibboleth.plugin.openidvci.profile.impl.FormOutboundCredentialsResponseMessage"
+        scope="prototype"  />
 
   <bean id="BuildErrorResponseFromEvent"
         class="net.shibboleth.idp.plugin.oidc.op.userinfo.profile.impl.BuildUserInfoErrorResponseFromEvent" scope="prototype"
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credentials/credentials-flow.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credentials/credentials-flow.xml
index 3d3870d..14a41d3 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credentials/credentials-flow.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/credentials/credentials-flow.xml
@@ -37,6 +37,8 @@
   
   <action-state id="BuildResponse">
     <evaluate expression="AddCredentialShell" />
+    <evaluate expression="OptionallyFormSelectiveDisclosureJWTCredential" />
+    <evaluate expression="SignJWTCredential" />
     <evaluate expression="'proceed'"/>
     <transition on="proceed" to="BuildResponseMessage"/>
   </action-state>

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


More information about the commits mailing list