[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