[java-idp-plugin-vci] 02/07: Validate access token binding
Codeberg
noreply at shibboleth.net
Thu Sep 24 13:16:27 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/347aa2f91bcdfdb772b631723db347f9a19d055c
commit 347aa2f91bcdfdb772b631723db347f9a19d055c
Author: Janne Lauros <janne.lauros at csc.fi>
AuthorDate: Thu Sep 24 16:07:53 2026 +0300
Validate access token binding
---
.../profile/impl/ValidateAccessTokenBinding.java | 97 ++++++++++++++++++++++
.../openid/vci/credentials/credentials-beans.xml | 10 ++-
.../openid/vci/credentials/credentials-flow.xml | 1 +
3 files changed, 105 insertions(+), 3 deletions(-)
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidateAccessTokenBinding.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidateAccessTokenBinding.java
new file mode 100644
index 0000000..d4f6b35
--- /dev/null
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidateAccessTokenBinding.java
@@ -0,0 +1,97 @@
+/*
+ * 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.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.geant.shibboleth.plugin.openidvci.messaging.impl.AbstractOpenIDVCICredentialsValidationResponseAction;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultDPoPProofThumbprintLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Action that validates the access token of the request to be bound to the key the DPoP proof of
+ * the request is signed with, as required by RFC 9449 section 7.1.
+ *
+ * <p>Runs after DPoP proof validation, which is what makes the thumbprint of the proof key
+ * available.</p>
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link OidcEventIds#INVALID_ACCESS_TOKEN}
+ * @event {@link OidcEventIds#INVALID_DPOP_PROOF}
+ */
+public class ValidateAccessTokenBinding extends AbstractOpenIDVCICredentialsValidationResponseAction {
+
+ /** Class logger. */
+ @Nonnull
+ private Logger log = LoggerFactory.getLogger(ValidateAccessTokenBinding.class);
+
+ /** Strategy used to read the thumbprint of the validated DPoP proof. */
+ @Nonnull
+ private Function<ProfileRequestContext, String> dpopProofThumbprintLookupStrategy =
+ new DefaultDPoPProofThumbprintLookupFunction();
+
+ /**
+ * Set the strategy used to read the thumbprint of the validated DPoP proof.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setDpopProofThumbprintLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, String> strategy) {
+ checkSetterPreconditions();
+
+ dpopProofThumbprintLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final OIDCAuthenticationResponseContext oidcResponseContext = getOidcResponseContext();
+ assert oidcResponseContext != null;
+ final TokenClaimsSet tokenClaims = oidcResponseContext.getAuthorizationGrantClaimsSet();
+ if (tokenClaims == null) {
+ log.error("{} No access token claims set to validate", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_ACCESS_TOKEN);
+ return;
+ }
+
+ final String claimsSetThumbprint = tokenClaims.getDpopProofJwkThumbprint();
+ final String proofThumbprint = dpopProofThumbprintLookupStrategy.apply(profileRequestContext);
+ if (claimsSetThumbprint != null || proofThumbprint != null) {
+ if (claimsSetThumbprint == null || proofThumbprint == null
+ || !claimsSetThumbprint.equals(proofThumbprint)) {
+ log.warn("{} DPoP proof of client {} is not the key access token {} is bound to", getLogPrefix(),
+ tokenClaims.getClientID(), tokenClaims.getID());
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_DPOP_PROOF);
+ return;
+ }
+ }
+
+ log.debug("{} Access token {} is bound to the key of the DPoP proof", getLogPrefix(), tokenClaims.getID());
+ }
+
+}
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 7b867fb..226ae40 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
@@ -32,6 +32,9 @@
</property>
</bean>
+ <bean id="ValidateAccessTokenBinding"
+ class="org.geant.shibboleth.plugin.openidvci.profile.impl.ValidateAccessTokenBinding" scope="prototype" />
+
<bean id="ValidateRequestedCredential"
class="org.geant.shibboleth.plugin.openidvci.profile.impl.ValidateRequestedCredential"
p:credentialConfigurationsResolver-ref="openidvci.CredentialConfigurationsResolver" scope="prototype" />
@@ -138,7 +141,8 @@
</bean>
<bean id="ResolveCredentialLifetime" class="org.geant.shibboleth.plugin.openidvci.profile.impl.ResolveCredentialLifetime"
- scope="prototype" />
+ scope="prototype"
+ p:timestampPrecision="%{openidvci.credentialTimestampPrecision:PT1H}" />
<bean id="openidvci.IssuerLookupStrategy"
class="org.geant.shibboleth.plugin.openidvci.profile.logic.CredentialIssuerLookupFunction"
@@ -202,9 +206,9 @@
</bean>
<bean id="BuildErrorResponseFromEvent"
- class="net.shibboleth.idp.plugin.oidc.op.userinfo.profile.impl.BuildUserInfoErrorResponseFromEvent" scope="prototype"
+ class="org.geant.shibboleth.plugin.openidvci.profile.impl.BuildCredentialErrorResponseFromEvent" scope="prototype"
p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier"
- p:mappedErrors="#{getObject('shibboleth.oidc.userinfo.MappedErrors') ?: getObject('openidvci.credentials.MappedErrors')}"
+ p:mappedErrors="#{getObject('openidvci.credentials.MappedErrors')}"
p:securityParametersContextLookupStrategy-ref="DPoPSecurityParametersContextProfileRequestContextLookup"
p:algorithmCandidates="%{idp.oauth2.dpop.proofAlgorithms:RS256,RS384,RS512,PS256,PS384,PS512,ES256,ES384,ES512}"
>
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 46d71fd..ac86c21 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
@@ -28,6 +28,7 @@
</action-state>
<action-state id="ResumeAfterDoDPoPProofValidation">
+ <evaluate expression="ValidateAccessTokenBinding" />
<evaluate expression="ParseProof" />
<evaluate expression="PopulateProofSignatureValidationParameters" />
<evaluate expression="ValidateProofSignature" />
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list