[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