[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
Thu May 23 07:51:27 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=1c8d3b2fa1eaf3efe4f073790ea61b41d8f78eea

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

commit 1c8d3b2fa1eaf3efe4f073790ea61b41d8f78eea
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu May 23 10:51:10 2024 +0300

    JOIDC-201 - Support for OAuth 2.0 Demonstrating Proof of Possession (DPoP)
    
    https://shibboleth.atlassian.net/browse/JOIDC-201
    
    Included support for dpop_bound_access_tokens flag in metadata
    - supported in static and dynamic client registration
    - the flag enforces the use of DPoP proofs in the token endpoint
---
 .../impl/AddRemainingClaimsToClientMetadata.java   |  6 +++--
 .../dpop-proof-validation-beans.xml                |  3 +++
 .../idp/flows/oidc/token/token-beans.xml           |  3 +++
 .../op/profile/flow/PushedAuthorizeFlowTest.java   | 11 ++++++++
 .../oidc/op/profile/flow/RegistrationFlowTest.java | 28 +++++++++++++++++++++
 .../plugin/oidc/op/profile/flow/TokenFlowTest.java | 29 ++++++++++++++++++++++
 .../shibboleth/idp/module/conf/relying-party.xml   |  1 +
 7 files changed, 79 insertions(+), 2 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddRemainingClaimsToClientMetadata.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddRemainingClaimsToClientMetadata.java
index 31d3270c..2ef21a9f 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddRemainingClaimsToClientMetadata.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddRemainingClaimsToClientMetadata.java
@@ -34,8 +34,9 @@ import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
- * Adds the remaining claims from the input metadata to the output {@link OIDCClientMetadata}, which haven't been
- * added there already, but which are recognized in the metadata policy.
+ * Adds the remaining recognized claims from the input metadata to the output {@link OIDCClientMetadata}. The static
+ * set of remaining recognized claims contain only 'dpop_bound_access_tokens'. The metadata policy offers a dynamic
+ * way to add any other claims to be recognized and thus included in the output metadata.
  */
 public class AddRemainingClaimsToClientMetadata extends AbstractOIDCClientMetadataPopulationAction {
 
@@ -101,6 +102,7 @@ public class AddRemainingClaimsToClientMetadata extends AbstractOIDCClientMetada
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        getOutputMetadata().setDPoPBoundAccessTokens(getInputMetadata().getDPoPBoundAccessTokens());
         final JSONObject input = getInputMetadata().toJSONObject();
         final JSONObject initialOutput = getOutputMetadata().toJSONObject();
         for (final String claim : input.keySet()) {
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 f2bcdf26..1dfb2bc7 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
@@ -40,6 +40,9 @@
                 <bean class="net.shibboleth.oidc.profile.config.logic.RequireDPoPProofPredicate"/>
                 <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DPoPAccessTokenInRequestCondition"
                     p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"/>
+                <bean parent="shibboleth.Conditions.Expression"
+                    p:customObject="#{getObject('shibboleth.oidc.MetadataEnforcedDPoP')}"
+                    c:expression="#custom != null and #custom.test(#input)" />
             </list>
         </constructor-arg>
     </bean>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
index 157d0308..f62f4b4b 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
@@ -15,6 +15,9 @@
 
     <bean id="shibboleth.oidc.loggingId" class="java.lang.String" c:_0="%{idp.service.logging.oidctoken:OIDC.Token}" />
 
+    <bean id="shibboleth.oidc.MetadataEnforcedDPoP" parent="shibboleth.Conditions.Expression"
+        c:expression="#input.ensureInboundMessageContext().getSubcontext(T(net.shibboleth.oidc.metadata.context.OIDCMetadataContext)) != null and #input.ensureInboundMessageContext().getSubcontext(T(net.shibboleth.oidc.metadata.context.OIDCMetadataContext)).getClientInformation() != null and #input.ensureInboundMessageContext().getSubcontext(T(net.shibboleth.oidc.metadata.context.OIDCMetadataContext)).getClientInformation().getMetadata().getDPoPBoundAccessTokens()" />
+
     <util:constant id="shibboleth.metrics.ProfileCounter"
         static-field="net.shibboleth.oidc.profile.oauth2.config.impl.DefaultOAuth2TokenConfiguration.PROFILE_COUNTER" />
 
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java
index e4ae2607..d5836eba 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java
@@ -77,6 +77,7 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
         removeMetadata(storageService, clientId);
         removeMetadata(storageService, clientIdNotMDDriven);
         removeMetadata(storageService, "mockPublicClientIdPKCEPlainUnforced");
+        removeMetadata(storageService, "mockClientIdDPoPAccessToken");
     }
 
     @Test
@@ -193,6 +194,16 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
         removeMetadata(storageService, "mockClientIdRequestObjectEnforced");
     }
 
+    @Test
+    public void testWithMissingRequiredDPoPProof() throws IOException, SessionException {
+        final String clientId = "mockClientIdDPoPAccessToken";
+        storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+        setBasicAuth(clientId, clientSecret);
+        setHttpFormRequest("POST", createRequestParameters(clientId));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+    }
+
     @Test
     public void testWithInvalidSyntaxDPoPProof() throws IOException, SessionException {
         storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java
index 27243f7c..b1aeacd4 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java
@@ -172,6 +172,34 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
         Assert.assertTrue(storedInfo.getOIDCMetadata().requiresBackChannelLogoutSession());
     }
 
+    @Test
+    public void testUnauthenticated_successWithDpop() throws Exception {
+        final String requestUri = "https://client.example.org/rf.txt#qpXaRLh_n93TTR9F252ValdatUQvQiJi5BDub2BeznA";
+        setJsonRequest("POST", "{ \"redirect_uris\":[\"" + redirectUri + "\"], \"request_uris\":[\"" + requestUri + "\"]," 
+                + "\"dpop_bound_access_tokens\":true }");
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final OIDCClientInformationResponse parsedResponse =
+                parseSuccessResponse(result, OIDCClientInformationResponse.class);
+        final OIDCClientInformation clientInfo = parsedResponse.getOIDCClientInformation();
+        final OIDCClientMetadata metadata = clientInfo.getOIDCMetadata();
+        final String clientId = clientInfo.getID().getValue();
+        assert clientId != null;
+        assert storageService != null;
+        final StorageRecord<String> storageRecord = storageService.read(BaseStorageServiceClientInformationComponent.CONTEXT_NAME, 
+                clientId);
+        assert storageRecord != null;
+        final String record = storageRecord.getValue();
+        Assert.assertNotNull(record);
+        final JSONParser parser = new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE);
+        final OIDCClientInformation storedInfo = OIDCClientInformation.parse((JSONObject) parser.parse(record));
+        Assert.assertEquals(storedInfo.getID(), clientInfo.getID());
+        Assert.assertEquals(storedInfo.getSecret(), clientInfo.getSecret());
+        Assert.assertEquals(storedInfo.getOIDCMetadata().getRedirectionURIStrings(), metadata.getRedirectionURIStrings());
+        Assert.assertEquals(storedInfo.getOIDCMetadata().getRequestObjectURIs(), Set.of(new URI(requestUri)));
+        Assert.assertTrue(metadata.getRedirectionURIStrings().contains(redirectUri));
+        Assert.assertTrue(metadata.getDPoPBoundAccessTokens());
+    }
+
     @Test
     public void testAccessToken_nonCompliantWithProfilePolicy1() throws Exception {
         setJsonRequest("POST", "{ \"redirect_uris\":[\"" + redirectUri + "\"] }");
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
index 30e790d7..d16c4760 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
@@ -25,6 +25,7 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.annotation.Nonnull;
 
@@ -798,6 +799,34 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
     }
 
+    @Test
+    public void testDPoPMetadataEnforcedValidGrantNoProof() throws Exception {
+        final AuthorizeCodeClaimsSet.Builder builder = new AuthorizeCodeClaimsSet.Builder();
+        builder.setJWTID(new SecureRandomIdentifierGenerationStrategy())
+            .setClientID(new ClientID(clientId))
+            .setIssuer("https://op.example.org")
+            .setPrincipal("jdoe")
+            .setSubject("mock")
+            .setIssuedAt(Instant.now())
+            .setExpiresAt(Instant.now().plusSeconds(100))
+            .setAuthenticationTime(Instant.now())
+            .setRedirectURI(new URI(redirectUri))
+            .setScope(scope);
+        final String authorizationCode = builder.build().serialize(getDataSealer());
+        setHttpFormRequest("POST", createRequestParameters(redirectUri,
+                "authorization_code",
+                authorizationCode, clientId));
+        setBasicAuth(clientId, clientSecret);
+        final OIDCClientMetadata metadata = buildMetadataSkeleton();
+        metadata.setRedirectionURIs(Set.of(new URI(redirectUri)));
+        metadata.setScope(scope);
+        metadata.setDPoPBoundAccessTokens(true);
+        storeMetadataObject(storageService, clientId, clientSecret, metadata);
+        storeConsent(storageService, "jdoe", clientId, "mail");
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+    }
+
     @Test
     public void testDPoPEnforcedValidGrantThumbprintIncludedNotMatchingProof() throws Exception {
         final String clientId = clientIdDPoPAccessToken;
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 eb932bfa..c861e172 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
@@ -233,6 +233,7 @@
             <property name="profileConfigurations">
                  <list>
                      <bean parent="OIDC.SSO.MDDriven" p:requireDpopProof="true"/>
+                     <bean parent="OAUTH2.PAR" p:requireDpopProof="true"/>
                  </list>
             </property>
         </bean>

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


More information about the commits mailing list