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

Henri Mikkonen henri.mikkonen at iki.fi
Mon Oct 21 16:05:09 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=2598960f5677ec126a92b049c5816d714caa7b8a

commit 2598960f5677ec126a92b049c5816d714caa7b8a
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Mon Oct 21 19:03:55 2024 +0300

    JOIDC-201 - Support for OAuth 2.0 Demonstrating Proof of Possession (DPoP)
    
    https://shibboleth.atlassian.net/browse/JOIDC-201
    
    - Modified 'ValidateGrant' to populate DPoP proof thumbprint to context with client credentials grant
    - Included 'ValidateGrant' to the client credentials flow
    - Improved tests to cover DPoP issuance with the client credentials flow
    - Add test to cover use of DPoP token as Bearer token
---
 .../plugin/oidc/op/profile/impl/ValidateGrant.java |  6 ++++
 .../shibboleth/idp/flows/oidc/token/token-flow.xml |  1 +
 .../flow/ClientCredentialsTokenFlowTest.java       | 32 +++++++++++++++++++++-
 .../plugin/oidc/op/profile/flow/UserInfoTest.java  | 19 +++++++++++++
 4 files changed, 57 insertions(+), 1 deletion(-)

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 5701b5aa..70f41ea0 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
@@ -383,6 +383,12 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
             }
 
         } else if (GrantType.CLIENT_CREDENTIALS.equals(grant.getType())) {
+            final String proofThumbprint = dpopProofThumbprintLookupStrategy.apply(profileRequestContext);
+            if (proofThumbprint != null) {
+                final OIDCAuthenticationResponseContext oidcResponseContext = getOidcResponseContext();
+                assert oidcResponseContext != null;
+                oidcResponseContext.setDpopProofJwkThumbprint(proofThumbprint);
+            }
             return;
         }
         
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml
index b899bb64..98e1d836 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml
@@ -68,6 +68,7 @@
 
     <!-- These steps apply to grants that are self-contained on this endpoint. -->
     <action-state id="ClientCredentialsGrantProcessing">
+        <evaluate expression="ValidateGrant" />
         <evaluate expression="SetAuthenticationContextClassReferenceToResponseContext" />
         <evaluate expression="SetAuthenticationTimeToResponseContext" />
         <evaluate expression="SetSubjectFromSubjectContextToResponseContext" />
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/ClientCredentialsTokenFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/ClientCredentialsTokenFlowTest.java
index fff13395..2459f13a 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/ClientCredentialsTokenFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/ClientCredentialsTokenFlowTest.java
@@ -46,6 +46,7 @@ import com.nimbusds.jose.JWEAlgorithm;
 import com.nimbusds.jose.JWEDecrypter;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.crypto.RSADecrypter;
+import com.nimbusds.jose.jwk.ECKey;
 import com.nimbusds.jwt.EncryptedJWT;
 import com.nimbusds.jwt.JWT;
 import com.nimbusds.jwt.JWTClaimsSet;
@@ -355,7 +356,36 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
         verifyClaims("JWT", response.getTokens().getBearerAccessToken(), clientIdSaml, scope,
                 Collections.singletonList(resourceSaml), "customEmailName", "eduPersonScopedAffiliation");
     }
-    
+
+    @Test
+    public void testSamlDPoP() throws Exception {
+        setHttpFormRequest("POST", createRequestParameters(clientIdSaml, scope, resourceSaml));
+        setBasicAuth(clientIdSaml, clientSecretSaml);
+        final ECKey dpopProofKey = defaultDPoPProofKey();
+        final SignedJWT dpopProof = buildDPoPProof(dpopProofKey, JWSAlgorithm.ES256, request.getMethod(),
+                "http://localhost/idp/profile/oidc/token", null, createValidDPoPNonce(clientIdSaml));
+        request.addHeader("DPoP", dpopProof.serialize());
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final AccessTokenResponse response = parseSuccessResponse(result, AccessTokenResponse.class);
+        Assert.assertNotNull(response.getTokens().getDPoPAccessToken());
+        Assert.assertEquals(response.getTokens().getDPoPAccessToken().getLifetime(), 600);
+        Assert.assertEquals(response.getTokens().getDPoPAccessToken().getScope(), scope);
+        verifyClaims("JWT", response.getTokens().getDPoPAccessToken(), clientIdSaml, scope,
+                Collections.singletonList(resourceSaml), "customEmailName", "eduPersonScopedAffiliation");
+    }
+
+    @Test
+    public void testSamlDPoP_noMandatoryNonce() throws Exception {
+        setHttpFormRequest("POST", createRequestParameters(clientIdSaml, scope, resourceSaml));
+        setBasicAuth(clientIdSaml, clientSecretSaml);
+        final ECKey dpopProofKey = defaultDPoPProofKey();
+        final SignedJWT dpopProof = buildDPoPProof(dpopProofKey, JWSAlgorithm.ES256, request.getMethod(),
+                "http://localhost/idp/profile/oidc/token", null, null);
+        request.addHeader("DPoP", dpopProof.serialize());
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.USE_DPOP_NONCE_CODE);
+    }
+
     protected FlowExecutionResult launchWithJwtAuthentication(final JWTAuthentication authnMethod,
             final JWSAlgorithm algorithm) throws Exception {
         final String clientId = authnMethod.getClientID().getValue();
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 de743304..ab976b4a 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
@@ -413,6 +413,25 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         Assert.assertNull(response.getUserInfoJWT());
     }
 
+    @SuppressWarnings("null")
+    @Test
+    public void testFailWithBearerTokenWithDPoP() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
+        ComponentInitializationException, IOException, com.nimbusds.oauth2.sdk.ParseException, JOSEException {
+        final ECKey dpopProofKey = defaultDPoPProofKey();
+        final AccessTokenClaimsSet claims =
+                buildDPoPAccessTokenClaimsSet(dpopProofKey.computeThumbprint().toString());
+
+        final DPoPAccessToken token = new DPoPAccessToken(claims.serialize(getDataSealer()));
+        final SignedJWT dpopProof = buildDPoPProof(dpopProofKey, JWSAlgorithm.ES256, request.getMethod(),
+                "http://localhost/idp/profile/oidc/userinfo", token, createValidDPoPNonce(clientId));
+        request.addHeader("DPoP", dpopProof.serialize());
+
+        storeMetadata(storageService, clientId, "mockSecret", scope);
+        request.addHeader("Authorization", getTokenHeaderValue(token).replace("DPoP ", "Bearer "));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertInvalidDPoPToken(result);
+    }
+
     @Test
     public void testFailWithBearerTokenWhenDPoPEnforced() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
         ComponentInitializationException, IOException {

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


More information about the commits mailing list