[java-idp-oidc] branch main updated: JOIDC-201 - Support for OAuth 2.0 Demonstrating Proof of Possession (DPoP)

Henri Mikkonen henri.mikkonen at iki.fi
Mon May 27 12:06:29 UTC 2024


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

hjmikkon pushed a commit to branch main
in repository java-idp-oidc.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=a8157b6ac0d09ddd1f23da6f15ba41c072c75cd3

The following commit(s) were added to refs/heads/main by this push:
     new a8157b6a JOIDC-201 - Support for OAuth 2.0 Demonstrating Proof of Possession (DPoP)
a8157b6a is described below

commit a8157b6ac0d09ddd1f23da6f15ba41c072c75cd3
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Mon May 27 14:59:43 2024 +0300

    JOIDC-201 - Support for OAuth 2.0 Demonstrating Proof of Possession (DPoP)
    
    https://shibboleth.atlassian.net/browse/JOIDC-201
    
    - Refactored user info to use 'WWW-Authenticate:DPoP..' challenges
    
    - Refactored the dpop-validation flow to run PopulateDPoPProofSignatureValidationParameters first
      - It's needed for building the security configuration to be used for error response building
      - The error response now contains algs-claim
        - Its contents are built with the list that can be customized via 'idp.outh2.dpop.proofAlgorithms'
        - Default value is 'RS256,RS384,RS512,PS256,PS384,PS512,ES256,ES384,ES512'
        - Security configuration policy (includes/excludes) filters the list
    
    - New global bean 'shibboleth.oidc.DefaultOAuth2ProtectedApiMappedErrors' (map) extends 'shibboleth.oidc.DefaultApiMappedErrors'
      - May be overridden via shibboleth.oidc.userinfo.MappedErrors'
    
    - Wired 'requireDpopProof' flow tests for PAR, token, UserInfo, revocation and introspection endpoints
---
 .../impl/AbstractBuildErrorResponseFromEvent.java  |  3 +-
 .../impl/BuildUserInfoErrorResponseFromEvent.java  | 92 +++++++++++++++++++++-
 .../META-INF/net.shibboleth.idp/postconfig.xml     | 15 ++++
 .../dpop-proof-validation-beans.xml                |  6 +-
 .../dpop-proof-validation-flow.xml                 |  2 +-
 .../idp/flows/oidc/userinfo/userinfo-beans.xml     |  5 +-
 .../idp/service/relying-party/postconfig.xml       | 33 +++++---
 .../AbstractOidcClientAuthenticationFlowTest.java  | 24 ++++++
 .../plugin/oidc/op/profile/flow/UserInfoTest.java  | 49 +++++++++---
 .../shibboleth/idp/module/conf/relying-party.xml   | 11 +++
 10 files changed, 211 insertions(+), 29 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractBuildErrorResponseFromEvent.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractBuildErrorResponseFromEvent.java
index 7f2c0518..34242961 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractBuildErrorResponseFromEvent.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractBuildErrorResponseFromEvent.java
@@ -162,8 +162,7 @@ public abstract class AbstractBuildErrorResponseFromEvent<T extends ErrorRespons
         assert error != null;
         final ErrorResponse errorResponse = buildErrorResponse(error, profileRequestContext);
         if (errorResponse != null) {
-            profileRequestContext.ensureOutboundMessageContext()
-                    .setMessage(buildErrorResponse(error, profileRequestContext));
+            profileRequestContext.ensureOutboundMessageContext().setMessage(errorResponse);
             log.debug("{} ErrorResponse successfully set as the outbound message", getLogPrefix());
         } else {
             log.debug("{} Error response not formed", getLogPrefix());
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/BuildUserInfoErrorResponseFromEvent.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/BuildUserInfoErrorResponseFromEvent.java
index 772db7ea..40d9e082 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/BuildUserInfoErrorResponseFromEvent.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/BuildUserInfoErrorResponseFromEvent.java
@@ -14,13 +14,33 @@
 
 package net.shibboleth.idp.plugin.oidc.op.userinfo.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 org.opensaml.profile.context.EventContext;
 import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.xmlsec.algorithm.AlgorithmSupport;
+import org.slf4j.Logger;
 
+import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.oauth2.sdk.ErrorObject;
+import com.nimbusds.oauth2.sdk.token.DPoPTokenError;
 import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
 
 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;
 
 /**
  * This action reads an event from the configured {@link EventContext} lookup strategy, constructs an OIDC user info
@@ -28,11 +48,81 @@ import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractBuildErrorResponse
  */
 public class BuildUserInfoErrorResponseFromEvent extends AbstractBuildErrorResponseFromEvent<UserInfoErrorResponse> {
 
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(BuildUserInfoErrorResponseFromEvent.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;
+
+    /**
+     * Set the strategy used to look up the {@link SecurityParametersContext} to set the parameters for.
+     * 
+     * @param strategy lookup strategy
+     * 
+     * @since 4.2.0
+     */
+    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
+     * 
+     * @since 4.2.0
+     */
+    public void setAlgorithmCandidates(@Nonnull @NotEmpty final Collection<String> candidates) {
+        final List<String> strings = List.copyOf(StringSupport.normalizeStringCollection(
+                Constraint.isNotEmpty(candidates, "The list of algorihtm candidates cannot be empty")));
+        final List<JWSAlgorithm> algorithms = strings.stream().map(JWSAlgorithm::parse).toList();
+        assert algorithms != null;
+        algorithmCandidates = algorithms;
+    }
+
+    /** {@inheritDoc} */
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        if (securityParametersContextLookupStrategy == null) {
+            throw new ComponentInitializationException("SecurityParametersContext lookup strategy cannot be null");
+        }
+    }
+
     /** {@inheritDoc} */
     @Override
     protected UserInfoErrorResponse 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 UserInfoErrorResponse(
+                    dpopError.setJWSAlgorithms(Set.of(algorithms.toArray(new JWSAlgorithm[] {}))));
+        }
+
         return new UserInfoErrorResponse(error);
     }
-
 }
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 8ad10c2b..9cbca38f 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -700,6 +700,21 @@
         </property>
     </bean>
 
+    <bean id="shibboleth.oidc.DefaultOAuth2ProtectedApiMappedErrors"
+            parent="shibboleth.oidc.DefaultApiMappedErrors"
+            class="org.springframework.beans.factory.config.MapFactoryBean">
+        <property name="sourceMap">
+            <map merge="true" value-type="com.nimbusds.oauth2.sdk.ErrorObject">
+                <entry key="#{T(net.shibboleth.oidc.profile.core.OidcEventIds).INVALID_DPOP_PROOF}"
+                    value="#{T(com.nimbusds.oauth2.sdk.token.DPoPTokenError).INVALID_DPOP_PROOF}" />
+                <entry key="#{T(net.shibboleth.oidc.profile.core.OidcEventIds).MISSING_DPOP_PROOF}"
+                    value="#{T(com.nimbusds.oauth2.sdk.token.DPoPTokenError).INVALID_DPOP_PROOF}" />
+                <entry key="#{T(net.shibboleth.oidc.profile.core.OidcEventIds).INVALID_DPOP_NONCE}"
+                    value="#{T(com.nimbusds.oauth2.sdk.token.DPoPTokenError).USE_DPOP_NONCE}" />
+            </map>
+        </property>
+    </bean>
+
     <bean id="shibboleth.oidc.DefaultUnregisteredClientPolicyFilename" class="java.lang.String" factory-method="valueOf">
         <constructor-arg value="%{idp.oidc.DefaultUnregisteredClientPolicyFile:%{idp.home}/conf/oidc-unregistered-client-policy.json}" />
     </bean>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/dpop-proof-validation/dpop-proof-validation-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/dpop-proof-validation/dpop-proof-validation-beans.xml
index 1dfb2bc7..387cddf9 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/dpop-proof-validation/dpop-proof-validation-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/dpop-proof-validation/dpop-proof-validation-beans.xml
@@ -30,7 +30,8 @@
         </constructor-arg>
         <constructor-arg name="f">
             <bean class="org.opensaml.messaging.context.navigate.ChildContextLookup"
-                c:type="#{ T(net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2DPoPProofContext) }"/>
+                c:type="#{ T(net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2DPoPProofContext) }"
+                c:createContext="true" />
         </constructor-arg>
     </bean>
 
@@ -63,8 +64,7 @@
             class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureValidationParameters"
             scope="prototype"
             c:strategy-ref="shibboleth.MessageContextLookup.Inbound"
-            p:securityParametersContextLookupStrategy-ref="DPoPSecurityParametersContextProfileRequestContextLookup"
-            p:activationCondition-ref="DPoPProofRequiredOrExists">
+            p:securityParametersContextLookupStrategy-ref="DPoPSecurityParametersContextProfileRequestContextLookup">
         <property name="configurationLookupStrategy">
             <bean class="net.shibboleth.oidc.profile.config.navigate.DPoPProofSignatureValidationConfigurationLookupFunction" />
         </property>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/dpop-proof-validation/dpop-proof-validation-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/dpop-proof-validation/dpop-proof-validation-flow.xml
index 2196b17b..98678506 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/dpop-proof-validation/dpop-proof-validation-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/dpop-proof-validation/dpop-proof-validation-flow.xml
@@ -4,8 +4,8 @@
     abstract="true">
 
     <action-state id="DoDPoPProofValidation">
-        <evaluate expression="InitializeDPoPProofContext" />
         <evaluate expression="PopulateDPoPProofSignatureValidationParameters" />
+        <evaluate expression="InitializeDPoPProofContext" />
         <evaluate expression="ValidateDPoPProofSignature" />
         <evaluate expression="ValidateDPoPProof" />
         <evaluate expression="'proceed'" />
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
index e60025e0..dd142ff3 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
@@ -228,7 +228,10 @@
     <bean id="BuildErrorResponseFromEvent"
         class="net.shibboleth.idp.plugin.oidc.op.userinfo.profile.impl.BuildUserInfoErrorResponseFromEvent" scope="prototype"
         p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier"
-        p:mappedErrors="#{getObject('shibboleth.oidc.userinfo.MappedErrors') ?: getObject('shibboleth.oidc.DefaultApiMappedErrors')}">
+        p:mappedErrors="#{getObject('shibboleth.oidc.userinfo.MappedErrors') ?: getObject('shibboleth.oidc.DefaultOAuth2ProtectedApiMappedErrors')}"
+        p:securityParametersContextLookupStrategy-ref="DPoPSecurityParametersContextProfileRequestContextLookup"
+        p:algorithmCandidates="%{idp.outh2.dpop.proofAlgorithms:RS256,RS384,RS512,PS256,PS384,PS512,ES256,ES384,ES512}"
+        >
         <property name="eventContextLookupStrategy">
             <bean class="net.shibboleth.idp.profile.context.navigate.WebFlowCurrentEventLookupFunction" />
         </property>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index 37727355..07b74c47 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -27,7 +27,7 @@
         p:deniedUserInfoAttributes="%{idp.oidc.deniedUserInfoAttributes:}"
         p:issuedClaimsValidator-ref="DefaultUserInfoJWTClaimsValidator"
         p:unregisteredClientPolicy="#{getObject('shibboleth.oidc.DefaultUnregisteredClientPolicy')}"
-        p:dpopProofClaimsValidator-ref="DefaultUserInfoDPoPProofClaimsValidator"
+        p:dpopProofClaimsValidator-ref="DefaultDPoPWithAthProofClaimsValidator"
         p:dpopProofSignatureValidationConfiguration-ref="DPoPSignatureValidationConfiguration"
         p:dpopProofNonceGenerator-ref="DefaultOAuth2DPoPNonceGenerator" />
         
@@ -63,7 +63,10 @@
         p:tokenEndpointAuthMethods="%{idp.oidc.dynreg.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}"
         p:claimsValidator-ref="DefaultJWTClaimsValidator"
         p:issuedClaimsValidator-ref="DefaultIntrospectionJWTClaimsValidator"
-        p:unregisteredClientPolicy="#{getObject('shibboleth.oidc.DefaultUnregisteredClientPolicy')}" />
+        p:unregisteredClientPolicy="#{getObject('shibboleth.oidc.DefaultUnregisteredClientPolicy')}"
+        p:dpopProofClaimsValidator-ref="DefaultDPoPWithAthProofClaimsValidator"
+        p:dpopProofSignatureValidationConfiguration-ref="DPoPSignatureValidationConfiguration"
+        p:dpopProofNonceGenerator-ref="DefaultOAuth2DPoPNonceGenerator" />
 
     <bean id="OAUTH2.Revocation" parent="AbstractOIDCProfile" lazy-init="true"
         class="net.shibboleth.oidc.profile.oauth2.config.impl.DefaultOAuth2TokenRevocationConfiguration"
@@ -73,7 +76,10 @@
         p:issuedClaimsValidator-ref="DefaultRevocationJWTClaimsValidator"
         p:revocationMethod="%{idp.oauth2.revocationMethod:CHAIN}"
         p:revocationLifetime="%{idp.oidc.revocationCache.authorizeCode.lifetime:PT6H}"
-        p:unregisteredClientPolicy="#{getObject('shibboleth.oidc.DefaultUnregisteredClientPolicy')}" />
+        p:unregisteredClientPolicy="#{getObject('shibboleth.oidc.DefaultUnregisteredClientPolicy')}"
+        p:dpopProofClaimsValidator-ref="DefaultDPoPWithAthProofClaimsValidator"
+        p:dpopProofSignatureValidationConfiguration-ref="DPoPSignatureValidationConfiguration"
+        p:dpopProofNonceGenerator-ref="DefaultOAuth2DPoPNonceGenerator" />
 
     <bean id="OIDC.Logout" parent="AbstractOIDCProfile" lazy-init="true"
           class="net.shibboleth.oidc.profile.config.impl.DefaultOIDCLogoutConfiguration"
@@ -92,8 +98,7 @@
         p:tokenEndpointAuthMethods="%{idp.oidc.par.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}"
         p:claimsValidator-ref="DefaultJWTClaimsValidator"
         p:dpopProofClaimsValidator-ref="DefaultDPoPProofClaimsValidator"
-        p:unregisteredClientPolicy="#{getObject('shibboleth.oidc.DefaultUnregisteredClientPolicy')}"
-        p:dpopProofSignatureValidationConfiguration-ref="DPoPSignatureValidationConfiguration" />
+        p:unregisteredClientPolicy="#{getObject('shibboleth.oidc.DefaultUnregisteredClientPolicy')}"/>
 
     <bean id="DefaultLogoutHintMatchingPredicate"
           class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultLogoutHintMatchingPredicate"/>
@@ -139,6 +144,16 @@
             <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="messageHandler"
                 p:propertyType="#{T(java.util.function.Function)}" />
         </property>
+        <property name="dpopProofSignatureValidationConfigurationLookupStrategy">
+            <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="dpopProofSignatureValidationConfiguration"
+                p:propertyType="#{T(net.shibboleth.oidc.security.jose.SignatureValidationConfiguration)}"
+                p:defaultValue-ref="DPoPSignatureValidationConfiguration" />
+        </property>
+        <property name="dpopProofNonceGeneratorLookupStrategy">
+            <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="dpopProofNonceGenerator"
+                p:propertyType="#{T(java.util.function.Function)}"
+                p:defaultValue-ref="DefaultOAuth2DPoPNonceGenerator" />
+        </property>
     </bean>
 
     <bean id="AbstractMDDrivenOAuthTokenValidatingProfile" parent="AbstractMDDrivenOAuthClientAuthenticatableProfile" abstract="true">
@@ -432,7 +447,7 @@
         <property name="dpopProofClaimsValidatorLookupStrategy">
             <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="dpopProofClaimsValidator"
                 p:propertyType="#{T(net.shibboleth.oidc.jwt.claims.ClaimsValidator)}"
-                p:defaultValue-ref="DefaultUserInfoDPoPProofClaimsValidator" />
+                p:defaultValue-ref="DefaultDPoPWithAthProofClaimsValidator" />
         </property>
         <property name="dpopProofSignatureValidationConfigurationLookupStrategy">
             <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="dpopProofSignatureValidationConfiguration"
@@ -1015,9 +1030,9 @@
         class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
         p:claimValidators-ref="DPoPProofClaimsValidators" />
 
-    <bean id="DefaultUserInfoDPoPProofClaimsValidator"
+    <bean id="DefaultDPoPWithAthProofClaimsValidator"
         class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
-        p:claimValidators-ref="UserInfoDPoPProofClaimsValidators" />
+        p:claimValidators-ref="DPoPProofWithAthClaimsValidators" />
 
     <util:list id="DPoPProofClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
         <ref bean="ExpiryClaimsValidator" />
@@ -1046,7 +1061,7 @@
         </bean>
     </util:list>
 
-    <bean id="UserInfoDPoPProofClaimsValidators" parent="DPoPProofClaimsValidators"
+    <bean id="DPoPProofWithAthClaimsValidators" parent="DPoPProofClaimsValidators"
         class="org.springframework.beans.factory.config.ListFactoryBean">
         <property name="sourceList">
             <list merge="true">
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcClientAuthenticationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcClientAuthenticationFlowTest.java
index 99b78738..c0b90cef 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcClientAuthenticationFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcClientAuthenticationFlowTest.java
@@ -36,6 +36,7 @@ import com.nimbusds.jwt.JWT;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.PlainJWT;
 import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.OAuth2Error;
 import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
 import com.nimbusds.oauth2.sdk.auth.ClientSecretJWT;
 import com.nimbusds.oauth2.sdk.auth.JWTAuthentication;
@@ -56,6 +57,7 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
     String clientSecret = "mockClientSecretmockClientSecretmockClientSecretmockClientSecretmockClientSecret";
     String clientIdSaml = "mockSamlClientId";
     String clientSecretSaml = "mockClientSecretmockClientSecretmockClientSecret";
+    String clientIdDPoPProofEnforced = "mockClientIdDPoPProofEnforced";
 
     String jwtAud;
     String issuer = "https://op.example.org";
@@ -651,6 +653,28 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
         }
     }
 
+    @Test
+    public void testInvalidDPoPProof() throws Exception {
+        for (final String id : clientIds) {
+            final SignedJWT jwt = createSecretJWT(validClaimsSet(id, "http://localhost/idp/profile/oidc/token"),
+                    clientSecret, JWSAlgorithm.HS256);
+            request.addHeader("DPoP", buildDPoPProof(super.defaultDPoPProofKey(), JWSAlgorithm.ES256, "POST", jwtAud,null, null));
+            final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+                    ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+            assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+        }
+    }
+
+    @Test
+    public void testMissingMandatoryDPoPProof() throws Exception {
+        final ECKey ecKey = initializeECKey(Curve.P_521, "521");
+        final String clientId = clientIdDPoPProofEnforced;
+        final SignedJWT jwt = createPrivateKeyJWT(validClaimsSet(clientId), ecKey.toECPrivateKey(), JWSAlgorithm.ES512);
+        final FlowExecutionResult result = launchWithJwtAuthentication(jwt, JWSAlgorithm.ES512,
+                ClientAuthenticationMethod.PRIVATE_KEY_JWT, ecKey.toECPublicKey());
+        assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+    }
+
     protected JWTClaimsSet claimsSetMissingSub(final String clientId) {
         return new JWTClaimsSet.Builder()
                 .issuer(clientId)
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java
index 2e16c2bc..e90d1fb2 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java
@@ -40,6 +40,7 @@ import com.nimbusds.jwt.JWT;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.OAuth2Error;
+import com.nimbusds.oauth2.sdk.Response;
 import com.nimbusds.oauth2.sdk.Scope;
 import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
 import com.nimbusds.oauth2.sdk.id.ClientID;
@@ -48,6 +49,7 @@ import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
 import com.nimbusds.oauth2.sdk.token.BearerTokenError;
 import com.nimbusds.oauth2.sdk.token.DPoPAccessToken;
 import com.nimbusds.oauth2.sdk.token.DPoPTokenError;
+import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
 import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
 import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
 import com.nimbusds.openid.connect.sdk.claims.UserInfo;
@@ -110,7 +112,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
 
     @Test
     public void testUnparseableDPoPAccessToken() {
-        request.addHeader("Authorization", "Bearer mockAccessToken");
+        request.addHeader("Authorization", "DPoP mockAccessToken");
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertErrorCode(result, DPoPTokenError.INVALID_TOKEN.getCode());
     }
@@ -241,7 +243,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         storeMetadata(storageService, clientId, "mockSecret", scope);
         request.addHeader("Authorization", getTokenHeaderValue(token));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+        assertInvalidDPoPToken(result);
     }
     
     @Test
@@ -252,7 +254,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         storeMetadata(storageService, clientId, "mockSecret", scope);
         request.addHeader("Authorization", getTokenHeaderValue(token));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+        assertInvalidDPoPToken(result);
     }
 
     @SuppressWarnings("null")
@@ -279,7 +281,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         storeMetadata(storageService, clientId, "mockSecret", scope);
         request.addHeader("Authorization", getTokenHeaderValue(token));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+        assertInvalidDPoPToken(result);
     }
     
     @Test
@@ -290,7 +292,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         storeMetadata(storageService, clientId, "mockSecret", scope);
         request.addHeader("Authorization", getTokenHeaderValue(token));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+        assertInvalidDPoPToken(result);
     }
 
     @SuppressWarnings("null")
@@ -306,7 +308,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         storeMetadata(storageService, clientId, "mockSecret", scope);
         request.addHeader("Authorization", getTokenHeaderValue(token));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+        assertInvalidDPoPToken(result);
     }
 
     @SuppressWarnings("null")
@@ -322,7 +324,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         storeMetadata(storageService, clientId, "mockSecret", scope);
         request.addHeader("Authorization", getTokenHeaderValue(token));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+        assertInvalidDPoPToken(result);
     }
 
     @SuppressWarnings("null")
@@ -340,7 +342,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         storeMetadata(storageService, clientId, "mockSecret", scope);
         request.addHeader("Authorization", getTokenHeaderValue(token));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+        assertInvalidDPoPToken(result);
     }
 
     @SuppressWarnings("null")
@@ -358,7 +360,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         storeMetadata(storageService, clientId, "mockSecret", scope);
         request.addHeader("Authorization", getTokenHeaderValue(token));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+        assertInvalidDPoPToken(result);
     }
 
     @SuppressWarnings("null")
@@ -420,7 +422,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         storeMetadata(storageService, clientId, "mockSecret", scope);
         request.addHeader("Authorization", getTokenHeaderValue(token));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+        assertInvalidDPoPToken(result);
     }
 
     @SuppressWarnings("null")
@@ -438,7 +440,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         storeMetadata(storageService, clientId, "mockSecret", scope);
         request.addHeader("Authorization", getTokenHeaderValue(token));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+        assertInvalidDPoPToken(result);
     }
 
     @SuppressWarnings("null")
@@ -457,7 +459,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         storeMetadata(storageService, clientId, "mockSecret", scope);
         request.addHeader("Authorization", getTokenHeaderValue(token));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        assertErrorCode(result, OAuth2Error.USE_DPOP_NONCE_CODE);
+        assertErrorCode(result, DPoPTokenError.USE_DPOP_NONCE.getCode());
     }
 
     @SuppressWarnings("null")
@@ -714,4 +716,27 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         assert value != null;
         return value;
     }
+
+    protected void assertInvalidDPoPToken(final FlowExecutionResult result) {
+        final UserInfoErrorResponse errorResponse = parseUserInfoErrorResponse(result);
+        final DPoPTokenError dpopTokenError = (DPoPTokenError) errorResponse.getErrorObject();
+        assertErrorCode(result, DPoPTokenError.INVALID_DPOP_PROOF.getCode());
+        Assert.assertNotNull(dpopTokenError.getJWSAlgorithms());
+        Assert.assertTrue(dpopTokenError.getJWSAlgorithms().contains(JWSAlgorithm.parse("RS256")));
+        Assert.assertTrue(dpopTokenError.getJWSAlgorithms().contains(JWSAlgorithm.parse("RS384")));
+        Assert.assertTrue(dpopTokenError.getJWSAlgorithms().contains(JWSAlgorithm.parse("RS512")));
+        Assert.assertTrue(dpopTokenError.getJWSAlgorithms().contains(JWSAlgorithm.parse("PS256")));
+        Assert.assertTrue(dpopTokenError.getJWSAlgorithms().contains(JWSAlgorithm.parse("PS384")));
+        Assert.assertTrue(dpopTokenError.getJWSAlgorithms().contains(JWSAlgorithm.parse("PS512")));
+        Assert.assertTrue(dpopTokenError.getJWSAlgorithms().contains(JWSAlgorithm.parse("ES256")));
+        Assert.assertFalse(dpopTokenError.getJWSAlgorithms().contains(JWSAlgorithm.parse("ES384")));
+        Assert.assertTrue(dpopTokenError.getJWSAlgorithms().contains(JWSAlgorithm.parse("ES512")));
+    }
+
+    protected UserInfoErrorResponse parseUserInfoErrorResponse(final FlowExecutionResult result) {
+        final Response response = parseResponse(result);
+        Assert.assertFalse(response.indicatesSuccess());
+        Assert.assertTrue(response instanceof UserInfoErrorResponse);
+        return (UserInfoErrorResponse) response;
+    }
 }
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
index c861e172..94eb2130 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
@@ -131,6 +131,17 @@
                  </list>
             </property>
         </bean>
+        <bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdDPoPProofEnforced">
+            <property name="profileConfigurations">
+                 <list>
+                     <bean parent="OAUTH2.PAR.MDDriven" p:requireDpopProof="true" />
+                     <bean parent="OAUTH2.Token.MDDriven" p:requireDpopProof="true" />
+                     <bean parent="OIDC.UserInfo.MDDriven" p:requireDpopProof="true" />
+                     <bean parent="OAUTH2.Revocation.MDDriven" p:requireDpopProof="true"/>
+                     <bean parent="OAUTH2.Introspection.MDDriven" p:requireDpopProof="true"/>
+                 </list>
+            </property>
+        </bean>
         <bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdRequestObjectEnforced">
             <property name="profileConfigurations">
                  <list>

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


More information about the commits mailing list