[java-idp-plugin-vci] 07/07: Credentials error response class and configuration

Codeberg noreply at shibboleth.net
Thu Sep 24 13:16:32 UTC 2026


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

codeberg pushed a commit to branch main
in repository java-idp-plugin-vci.

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

commit 479f8eabe9814760897dcda24ba43b674dc5a9eb
Author: Janne Lauros <janne.lauros at csc.fi>
AuthorDate: Thu Sep 24 16:16:05 2026 +0300

    Credentials error response class and configuration
---
 .../profile/CredentialsErrorObjectMapping.java     |  25 ++++
 .../messaging/impl/CredentialErrorResponse.java    |  69 ++++++++++
 .../BuildCredentialErrorResponseFromEvent.java     | 149 +++++++++++++++++++++
 3 files changed, 243 insertions(+)

diff --git a/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/CredentialsErrorObjectMapping.java b/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/CredentialsErrorObjectMapping.java
new file mode 100644
index 0000000..03b2e2a
--- /dev/null
+++ b/openid-vci-api/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/CredentialsErrorObjectMapping.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+import net.shibboleth.idp.plugin.oidc.op.profile.AbstractErrorObjectMapping;
+
+/**
+ * An error mapping of the Credential Endpoint.
+ */
+public class CredentialsErrorObjectMapping extends AbstractErrorObjectMapping {
+
+}
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialErrorResponse.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialErrorResponse.java
new file mode 100644
index 0000000..d9681b7
--- /dev/null
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialErrorResponse.java
@@ -0,0 +1,69 @@
+/*
+ * 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.messaging.impl;
+
+import javax.annotation.Nonnull;
+
+import com.nimbusds.oauth2.sdk.ErrorObject;
+import com.nimbusds.oauth2.sdk.ErrorResponse;
+import com.nimbusds.oauth2.sdk.http.HTTPResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
+
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Error response of the Credential Endpoint, OpenID4VCI 1.0 section 8.4.
+ *
+ * <p>An {@link ErrorObject} that is a token error is rendered as a challenge of RFC 6750 section 3
+ * or RFC 9449 section 7.1, any other one as the JSON body of an error response. The rendering is
+ * that of {@link UserInfoErrorResponse}, the two endpoints answering an access token alike.</p>
+ */
+public class CredentialErrorResponse implements ErrorResponse {
+
+    /** Error carried by this response. */
+    @Nonnull
+    private final ErrorObject error;
+
+    /**
+     * Constructor.
+     *
+     * @param errorObject error to carry
+     */
+    public CredentialErrorResponse(@Nonnull final ErrorObject errorObject) {
+        error = Constraint.isNotNull(errorObject, "Error object cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull
+    public ErrorObject getErrorObject() {
+        return error;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean indicatesSuccess() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull
+    public HTTPResponse toHTTPResponse() {
+        return new UserInfoErrorResponse(error).toHTTPResponse();
+    }
+
+}
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/BuildCredentialErrorResponseFromEvent.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/BuildCredentialErrorResponseFromEvent.java
new file mode 100644
index 0000000..fe48177
--- /dev/null
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/BuildCredentialErrorResponseFromEvent.java
@@ -0,0 +1,149 @@
+/*
+ * 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.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.geant.shibboleth.plugin.openidvci.messaging.impl.CredentialErrorResponse;
+import org.geant.shibboleth.plugin.openidvci.profile.CredentialsErrorObjectMapping;
+import org.opensaml.profile.context.EventContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.xmlsec.algorithm.AlgorithmSupport;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.oauth2.sdk.ErrorObject;
+import com.nimbusds.oauth2.sdk.token.DPoPTokenError;
+
+import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractBuildErrorResponseFromEvent;
+import net.shibboleth.oidc.security.jose.SignatureValidationParameters;
+import net.shibboleth.oidc.security.jose.context.SecurityParametersContext;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * Action that reads an event from the configured {@link EventContext} lookup strategy, constructs a
+ * {@link CredentialErrorResponse} and attaches it as the outbound message.
+ *
+ * <p>Derived from {@code net.shibboleth.idp.plugin.oidc.op.userinfo.profile.impl
+ * .BuildUserInfoErrorResponseFromEvent} of the OpenID Connect Provider plugin, building the error response of
+ * the Credential Endpoint rather than the one of the UserInfo endpoint.</p>
+ */
+public class BuildCredentialErrorResponseFromEvent
+        extends AbstractBuildErrorResponseFromEvent<CredentialErrorResponse, CredentialsErrorObjectMapping> {
+
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(BuildCredentialErrorResponseFromEvent.class);
+
+    /** Strategy used to look up the {@link SecurityParametersContext}. */
+    @NonnullAfterInit
+    private Function<ProfileRequestContext, SecurityParametersContext> securityParametersContextLookupStrategy;
+
+    /** Algorithm candidates to be verified against the security configuration. */
+    @NonnullAfterInit
+    private List<JWSAlgorithm> algorithmCandidates;
+
+    /**
+     * Constructor.
+     *
+     * @param freeObjects free-standing objects to add
+     */
+    @Autowired
+    public BuildCredentialErrorResponseFromEvent(
+            @Nullable final Collection<CredentialsErrorObjectMapping> freeObjects) {
+        super(freeObjects);
+    }
+
+    /**
+     * Set the strategy used to look up the {@link SecurityParametersContext} to set the parameters for.
+     *
+     * @param strategy lookup strategy
+     */
+    public void setSecurityParametersContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, SecurityParametersContext> strategy) {
+        ifInitializedThrowUnmodifiabledComponentException();
+
+        securityParametersContextLookupStrategy = Constraint.isNotNull(strategy,
+                "SecurityParametersContext lookup strategy cannot be null");
+    }
+
+    /**
+     * Set algorithm candidates to be verified against the security configuration.
+     *
+     * @param candidates collection of algorithm candidates
+     */
+    public void setAlgorithmCandidates(@Nonnull @NotEmpty final Collection<String> candidates) {
+        final List<String> strings = List.copyOf(StringSupport.normalizeStringCollection(
+                Constraint.isNotEmpty(candidates, "The list of algorithm candidates cannot be empty")));
+        final List<JWSAlgorithm> algorithms = strings.stream().map(JWSAlgorithm::parse).toList();
+        assert algorithms != null;
+        algorithmCandidates = algorithms;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+
+        if (securityParametersContextLookupStrategy == null) {
+            throw new ComponentInitializationException("SecurityParametersContext lookup strategy cannot be null");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected CredentialErrorResponse buildErrorResponse(final ErrorObject error,
+            final ProfileRequestContext profileRequestContext) {
+        if (error instanceof DPoPTokenError dpopError) {
+            log.debug("{} DPoP error {}", getLogPrefix(), dpopError.getCode());
+            final SecurityParametersContext securityParameters =
+                    securityParametersContextLookupStrategy.apply(profileRequestContext);
+            final SignatureValidationParameters validationParameters =
+                    securityParameters.getSignatureValidationParameters();
+            final List<JWSAlgorithm> algorithms = new ArrayList<>();
+            if (validationParameters != null) {
+                for (final JWSAlgorithm candidate : algorithmCandidates) {
+                    final String algorithmUri = candidate == null ? null : candidate.toString();
+                    if (algorithmUri != null && !AlgorithmSupport.validateAlgorithmURI(algorithmUri,
+                            validationParameters.getIncludedAlgorithms(),
+                            validationParameters.getExcludedAlgorithms())) {
+                        log.debug("Algorithm failed include/exclude validation: {}", candidate);
+                        continue;
+                    }
+                    algorithms.add(candidate);
+                }
+            }
+            return new CredentialErrorResponse(
+                    dpopError.setJWSAlgorithms(Set.of(algorithms.toArray(new JWSAlgorithm[] {}))));
+        }
+
+        return new CredentialErrorResponse(error);
+    }
+
+}

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


More information about the commits mailing list