[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