[java-idp-oidc] 23/35: JOIDC-201 - Support for OAuth 2.0 Demonstrating Proof of Possession (DPoP)

Henri Mikkonen henri.mikkonen at iki.fi
Thu Oct 24 14:01:48 UTC 2024


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

hjmikkon pushed a commit to branch dev/JOIDC-222
in repository java-idp-oidc.

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

commit 3b42409e39f28549da0166fdf036f82f0826a0da
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Oct 11 17:18:24 2024 +0300

    JOIDC-201 - Support for OAuth 2.0 Demonstrating Proof of Possession (DPoP)
    
    https://shibboleth.atlassian.net/browse/JOIDC-201
    
    - Enforce the use of DPoP proof JWT when dpop_jkt -parameter is used
    - Improved testing
---
 .../FormOutboundIntrospectionResponseMessage.java  |  3 +-
 .../plugin/oidc/op/profile/impl/ValidateGrant.java | 10 ++++++
 .../op/profile/flow/PushedAuthorizeFlowTest.java   | 42 ++++++++++++++++++++--
 .../plugin/oidc/op/profile/flow/TokenFlowTest.java | 23 ++++++++++++
 4 files changed, 75 insertions(+), 3 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundIntrospectionResponseMessage.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundIntrospectionResponseMessage.java
index 7cecee3b..485c6b9f 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundIntrospectionResponseMessage.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundIntrospectionResponseMessage.java
@@ -96,7 +96,8 @@ public class FormOutboundIntrospectionResponseMessage extends AbstractProfileAct
 
             }
 
-            if (jktCnf != null && "at".equals(tokenClaimsSet.getStringClaim(TokenClaimsSet.KEY_TYPE))) {
+            final String type = tokenClaimsSet.getStringClaim(TokenClaimsSet.KEY_TYPE);
+            if (jktCnf != null && (type == null || "at".equals(type))) {
                 builder.jwkThumbprintConfirmation(jktCnf);
                 builder.tokenType(AccessTokenType.DPOP);
             } else {
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java
index 3c2c087c..5701b5aa 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java
@@ -298,6 +298,16 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
                         ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
                         return;
                     }
+                    final String dpopJkt = authzCodeClaimsSet.getDpopProofJwkThumbprint();
+                    if (dpopJkt != null) {
+                        final String proofThumbprint = dpopProofThumbprintLookupStrategy.apply(profileRequestContext);
+                        if (!dpopJkt.equals(proofThumbprint)) {
+                            log.warn("{} The DPoP jkt in claims set '{}' did not match with the DPoP proof JWT '{}'",
+                                    getLogPrefix(), dpopJkt, proofThumbprint);
+                            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_DPOP_PROOF);
+                            return;
+                        }
+                    }
                     tokenClaimsSet = authzCodeClaimsSet;
                 } catch (final DataSealerException | ParseException e) {
                     log.warn("{} Unwrapping authz code failed: {}", getLogPrefix(), e.getMessage());
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 ffb516f2..a6e13577 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
@@ -18,6 +18,7 @@ import java.io.IOException;
 import java.net.URISyntaxException;
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
+import java.text.ParseException;
 import java.time.Instant;
 import java.util.HashMap;
 import java.util.List;
@@ -49,6 +50,7 @@ import com.nimbusds.oauth2.sdk.Scope;
 import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
 
 import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultPushedAuthorizationRequestUriDeserializationFunction;
+import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
 import net.shibboleth.idp.session.SessionException;
 import net.shibboleth.oidc.security.credential.JWKCredential;
 import net.shibboleth.shared.collection.Pair;
@@ -227,6 +229,23 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
         verifyAuthorizeEndpoint(clientIdSaml, response.getRequestURI().toString());
     }
 
+    @Test
+    public void testSuccessWithJkt() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException {
+        for (final String clientId : clientIds) {
+            storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+            setBasicAuth(clientId, clientSecret);
+            final Map<String, String> requestParams = createRequestParameters(clientId);
+            requestParams.put("dpop_jkt", "mockJkt");
+            setHttpFormRequest("POST", requestParams);
+            final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+            assertSuccessResponse(result, clientId);
+            final PushedAuthorizationSuccessResponse response =
+                    parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
+            verifyAuthorizeEndpoint(clientId, response.getRequestURI().toString(), "mockJkt");
+        }
+    }
+
     @Test
     public void testWithAuthorizationCodeFlowRequestObjectEnforcedNoRO() throws IOException, SessionException {
         final String clientId = "mockClientIdRequestObjectEnforced";
@@ -860,6 +879,10 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
     }
 
     protected void verifyAuthorizeEndpoint(final String clientId, final String requestUri) {
+        verifyAuthorizeEndpoint(clientId, requestUri, null);
+    }
+
+    protected void verifyAuthorizeEndpoint(final String clientId, final String requestUri, final String jkt) {
         initializeMocks();
         initializeThreadLocals();
         setBasicAuth("jdoe", "changeit");
@@ -867,16 +890,31 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
         final String redirectUri = "https://example.org/cb";
 
         AuthorizeFlowTest.setRequestParameters(request, List.of(new Pair<>("client_id", clientId),
-                new Pair<>("response_type", "code"),
-                new Pair<>("redirect_uri", redirectUri),
                 new Pair<>("request_uri", requestUri)));
 
         final FlowExecutionResult result = flowExecutor.launchExecution("oidc/authorize", null, externalContext);
         Assert.assertEquals(result.getOutcome().getId(), END_STATE_ID);
+        if (jkt != null) {
+            final String url = response.getRedirectedUrl();
+            assert url != null;
+            final String code = url.substring(url.indexOf("code=") + 5);
+            assert code != null;
+            try {
+                final AuthorizeCodeClaimsSet claimsSet = AuthorizeCodeClaimsSet.parse(code, getDataSealer());
+                assert claimsSet != null;
+                Assert.assertEquals(claimsSet.getDpopProofJwkThumbprint(), jkt);
+            } catch (ParseException | DataSealerException e) {
+                Assert.fail(e.getMessage());
+            }
+        }
 
         initializeMocks();
         initializeThreadLocals();
 
+        request.setMethod("GET");
+        AuthorizeFlowTest.setRequestParameters(request, List.of(new Pair<>("client_id", clientId),
+                new Pair<>("redirect_uri", redirectUri),
+                new Pair<>("request_uri", requestUri)));
         final FlowExecutionResult replayResult = flowExecutor.launchExecution("oidc/authorize", null, externalContext);
         Assert.assertEquals(replayResult.getOutcome().getId(), "ErrorView");
 
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 86e6beb3..46321966 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
@@ -757,6 +757,29 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         Assert.assertNotNull(idToken.getJWTClaimsSet().getClaim("at_hash"));
     }
 
+    @Test
+    public void testDPoPValidGrantThumbprintIncludedNoProof() 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)
+            .setDpopProofJwkThumbprint("mockJkt");
+        final String authorizationCode = builder.build().serialize(getDataSealer());
+        initializeGrantAndRequest(clientId, createRequestParameters(redirectUri,
+                "authorization_code",
+                authorizationCode, clientId));
+        storeConsent(storageService, "jdoe", clientId, "mail");
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_DPOP_PROOF_CODE);
+    }
+
     @Test
     public void testDPoPValidGrantThumbprintIncludedNotMatchingProof() throws Exception {
         final AuthorizeCodeClaimsSet.Builder builder = new AuthorizeCodeClaimsSet.Builder();

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


More information about the commits mailing list