[java-idp-oidc] 01/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:26 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=bb0adf927c2b6c01c5a03de73745995a554a9a9d

commit bb0adf927c2b6c01c5a03de73745995a554a9a9d
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Tue Sep 10 10:47:41 2024 +0300

    JOIDC-201 - Support for OAuth 2.0 Demonstrating Proof of Possession (DPoP)
    
    https://shibboleth.atlassian.net/browse/JOIDC-201
    
    - The 'htm' claim validator for DPoP proof JWTs was accidentally hardcoded into POST
      - Now the value is taken from the HttpServletRequest
    - Improved testing
---
 .../idp/service/relying-party/postconfig.xml       |   3 +-
 .../plugin/oidc/op/profile/flow/UserInfoTest.java  | 109 +++++++++++++++++++--
 2 files changed, 101 insertions(+), 11 deletions(-)

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 6063ec50..9f3363ec 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
@@ -1071,7 +1071,8 @@
             p:replayCacheRecordLifetime="%{idp.oauth2.dpop.replayCacheLifetime:PT5M}" />
         <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator" p:claimName="htm">
             <property name="valueToMatchLookupStrategy">
-                <bean parent="shibboleth.BiFunctions.Constant" c:target="POST" />
+                <bean parent="shibboleth.BiFunctions.Expression" c:expression="#custom.get().getMethod()"
+                    p:customObject-ref="shibboleth.HttpServletRequestSupplier"/>
             </property>
         </bean>
         <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator" p:claimName="htu">
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 74d61b2c..8bc83da6 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
@@ -33,6 +33,7 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Factory;
 import org.testng.annotations.Test;
 
+import com.nimbusds.common.contenttype.ContentType;
 import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.jwk.ECKey;
@@ -302,7 +303,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         JOSEException {
         final AccessTokenClaimsSet claims = buildDPoPAccessTokenClaimsSet("mockId");
         final DPoPAccessToken token = new DPoPAccessToken(claims.serialize(getDataSealer()));
-        request.addHeader("DPoP", buildDPoPProof("POST", "http://localhost/idp/profile/oidc/userinfo",
+        request.addHeader("DPoP", buildDPoPProof(request.getMethod(), "http://localhost/idp/profile/oidc/userinfo",
                 createValidDPoPNonce()).serialize());
 
         storeMetadata(storageService, clientId, "mockSecret", scope);
@@ -318,7 +319,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         JOSEException {
         final AccessTokenClaimsSet claims = buildDPoPAccessTokenClaimsSet("mockId");
         final DPoPAccessToken token = buildJWTDPoPToken(claims, signingKey.getPrivateKey(), "RS256");
-        request.addHeader("DPoP", buildDPoPProof("POST", "http://localhost/idp/profile/oidc/userinfo",
+        request.addHeader("DPoP", buildDPoPProof(request.getMethod(), "http://localhost/idp/profile/oidc/userinfo",
                 createValidDPoPNonce()).serialize());
 
         storeMetadata(storageService, clientId, "mockSecret", scope);
@@ -331,7 +332,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
     @Test
     public void testFailWithDPoP_noAth() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
         ComponentInitializationException, IOException, com.nimbusds.oauth2.sdk.ParseException, JOSEException {
-        final SignedJWT dpopProof = buildDPoPProof("POST", "http://localhost/idp/profile/oidc/userinfo",
+        final SignedJWT dpopProof = buildDPoPProof(request.getMethod(), "http://localhost/idp/profile/oidc/userinfo",
                 createValidDPoPNonce());
         final AccessTokenClaimsSet claims =
                 buildDPoPAccessTokenClaimsSet((dpopProof.getHeader().getJWK().computeThumbprint().toString()));
@@ -349,7 +350,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
     @Test
     public void testFailWithDPoP_nonMatchingAth() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
         ComponentInitializationException, IOException, com.nimbusds.oauth2.sdk.ParseException, JOSEException {
-        final SignedJWT dpopProof = buildDPoPProof("POST", "http://localhost/idp/profile/oidc/userinfo",
+        final SignedJWT dpopProof = buildDPoPProof(request.getMethod(), "http://localhost/idp/profile/oidc/userinfo",
                 new DPoPAccessToken("mock"), createValidDPoPNonce());
         final AccessTokenClaimsSet claims =
                 buildDPoPAccessTokenClaimsSet((dpopProof.getHeader().getJWK().computeThumbprint().toString()));
@@ -372,7 +373,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
                 buildDPoPAccessTokenClaimsSet(dpopProofKey.computeThumbprint().toString());
 
         final DPoPAccessToken token = new DPoPAccessToken(claims.serialize(getDataSealer()));
-        final SignedJWT dpopProof = buildDPoPProof(dpopProofKey, JWSAlgorithm.ES256, "POST",
+        final SignedJWT dpopProof = buildDPoPProof(dpopProofKey, JWSAlgorithm.ES256, request.getMethod(),
                 "http://localhost/idp/profile/oidc/userinfo", token, null);
         request.addHeader("DPoP", dpopProof.serialize());
 
@@ -391,7 +392,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
                 buildDPoPAccessTokenClaimsSet(dpopProofKey.computeThumbprint().toString());
 
         final DPoPAccessToken token = new DPoPAccessToken(claims.serialize(getDataSealer()));
-        final SignedJWT dpopProof = buildDPoPProof(dpopProofKey, JWSAlgorithm.ES256, "POST",
+        final SignedJWT dpopProof = buildDPoPProof(dpopProofKey, JWSAlgorithm.ES256, request.getMethod(),
                 "http://localhost/idp/profile/oidc/userinfo", token, createValidDPoPNonce());
         request.addHeader("DPoP", dpopProof.serialize());
 
@@ -423,7 +424,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
     @Test
     public void testFailWithJWTDPoP_noAth() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
         ComponentInitializationException, IOException, com.nimbusds.oauth2.sdk.ParseException, JOSEException {
-        final SignedJWT dpopProof = buildDPoPProof("POST", "http://localhost/idp/profile/oidc/userinfo",
+        final SignedJWT dpopProof = buildDPoPProof(request.getMethod(), "http://localhost/idp/profile/oidc/userinfo",
                 createValidDPoPNonce());
         final AccessTokenClaimsSet claims =
                 buildDPoPAccessTokenClaimsSet((dpopProof.getHeader().getJWK().computeThumbprint().toString()));
@@ -441,7 +442,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
     @Test
     public void testFailWithJWTDPoP_nonMatchingAth() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
         ComponentInitializationException, IOException, com.nimbusds.oauth2.sdk.ParseException, JOSEException {
-        final SignedJWT dpopProof = buildDPoPProof("POST", "http://localhost/idp/profile/oidc/userinfo",
+        final SignedJWT dpopProof = buildDPoPProof(request.getMethod(), "http://localhost/idp/profile/oidc/userinfo",
                 new DPoPAccessToken("mock"), createValidDPoPNonce());
         final AccessTokenClaimsSet claims =
                 buildDPoPAccessTokenClaimsSet((dpopProof.getHeader().getJWK().computeThumbprint().toString()));
@@ -464,7 +465,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
                 buildDPoPAccessTokenClaimsSet(dpopProofKey.computeThumbprint().toString());
 
         final DPoPAccessToken token = buildJWTDPoPToken(claims, signingKey.getPrivateKey(), "RS256");
-        final SignedJWT dpopProof = buildDPoPProof(dpopProofKey, JWSAlgorithm.ES256, "POST",
+        final SignedJWT dpopProof = buildDPoPProof(dpopProofKey, JWSAlgorithm.ES256, request.getMethod(),
                 "http://localhost/idp/profile/oidc/userinfo", token, null);
         request.addHeader("DPoP", dpopProof.serialize());
 
@@ -476,7 +477,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
 
     @SuppressWarnings("null")
     @Test
-    public void testSuccessOnlySubjectWithDPoPJWTNoAudience() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
+    public void testFailWithJWTDPoP_wrongHtm() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
         ComponentInitializationException, IOException, com.nimbusds.oauth2.sdk.ParseException, JOSEException {
         final ECKey dpopProofKey = defaultDPoPProofKey();
         final AccessTokenClaimsSet claims =
@@ -487,6 +488,73 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
                 "http://localhost/idp/profile/oidc/userinfo", token, createValidDPoPNonce());
         request.addHeader("DPoP", dpopProof.serialize());
 
+        storeMetadata(storageService, clientId, "mockSecret", scope);
+        request.addHeader("Authorization", getTokenHeaderValue(token));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertInvalidDPoPToken(result);
+    }
+
+    @SuppressWarnings("null")
+    @Test
+    public void testPostFailWithJWTDPoP_wrongHtm() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
+        ComponentInitializationException, IOException, com.nimbusds.oauth2.sdk.ParseException, JOSEException {
+        request.setMethod("POST");
+        request.setContentType(ContentType.APPLICATION_URLENCODED.toString());
+        final ECKey dpopProofKey = defaultDPoPProofKey();
+        final AccessTokenClaimsSet claims =
+                buildDPoPAccessTokenClaimsSet(dpopProofKey.computeThumbprint().toString());
+
+        final DPoPAccessToken token = buildJWTDPoPToken(claims, signingKey.getPrivateKey(), "RS256");
+        final SignedJWT dpopProof = buildDPoPProof(dpopProofKey, JWSAlgorithm.ES256, "GET",
+                "http://localhost/idp/profile/oidc/userinfo", token, createValidDPoPNonce());
+        request.addHeader("DPoP", dpopProof.serialize());
+
+        storeMetadata(storageService, clientId, "mockSecret", scope);
+        request.addHeader("Authorization", getTokenHeaderValue(token));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertInvalidDPoPToken(result);
+    }
+
+    @SuppressWarnings("null")
+    @Test
+    public void testSuccessOnlySubjectWithDPoPJWTNoAudience() 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 = buildJWTDPoPToken(claims, signingKey.getPrivateKey(), "RS256");
+        final SignedJWT dpopProof = buildDPoPProof(dpopProofKey, JWSAlgorithm.ES256, request.getMethod(),
+                "http://localhost/idp/profile/oidc/userinfo", token, createValidDPoPNonce());
+        request.addHeader("DPoP", dpopProof.serialize());
+
+        storeMetadata(storageService, clientId, "mockSecret", scope);
+        request.addHeader("Authorization", getTokenHeaderValue(token));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final UserInfoSuccessResponse response = parseSuccessResponse(result, UserInfoSuccessResponse.class);
+        Assert.assertEquals(response.getUserInfo().getSubject().getValue(), subject);
+        final UserInfo userInfo = response.getUserInfo();
+        Assert.assertNotNull(userInfo);
+        Assert.assertNull(userInfo.getEmailAddress());
+        Assert.assertNull(userInfo.getNickname());
+        Assert.assertNull(response.getUserInfoJWT());
+    }
+
+    @SuppressWarnings("null")
+    @Test
+    public void testSuccessPostOnlySubjectWithDPoPJWTNoAudience() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
+        ComponentInitializationException, IOException, com.nimbusds.oauth2.sdk.ParseException, JOSEException {
+        request.setMethod("POST");
+        request.setContentType(ContentType.APPLICATION_URLENCODED.toString());
+        final ECKey dpopProofKey = defaultDPoPProofKey();
+        final AccessTokenClaimsSet claims =
+                buildDPoPAccessTokenClaimsSet(dpopProofKey.computeThumbprint().toString());
+
+        final DPoPAccessToken token = buildJWTDPoPToken(claims, signingKey.getPrivateKey(), "RS256");
+        final SignedJWT dpopProof = buildDPoPProof(dpopProofKey, JWSAlgorithm.ES256, request.getMethod(),
+                "http://localhost/idp/profile/oidc/userinfo", token, createValidDPoPNonce());
+        request.addHeader("DPoP", dpopProof.serialize());
+
         storeMetadata(storageService, clientId, "mockSecret", scope);
         request.addHeader("Authorization", getTokenHeaderValue(token));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
@@ -517,6 +585,27 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         Assert.assertNull(response.getUserInfoJWT());
     }
 
+    @Test
+    public void testSuccessPostOnlySubjectWithJWTAudience() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
+        ComponentInitializationException, IOException, com.nimbusds.oauth2.sdk.ParseException, JOSEException {
+        request.setMethod("POST");
+        request.setContentType(ContentType.APPLICATION_URLENCODED.toString());
+        final BearerAccessToken token = buildJWTToken(clientId, subject , new Scope("openid"),
+                List.of("https://sp.example.org", "https://op.example.org"), signingKey.getPrivateKey(), "RS256");
+
+        storeMetadata(storageService, clientId, "mockSecret", scope);
+        Assert.assertNull(request.getHeader("Authorization"));
+        request.addHeader("Authorization", getTokenHeaderValue(token));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final UserInfoSuccessResponse response = parseSuccessResponse(result, UserInfoSuccessResponse.class);
+        Assert.assertEquals(response.getUserInfo().getSubject().getValue(), subject);
+        final UserInfo userInfo = response.getUserInfo();
+        Assert.assertNotNull(userInfo);
+        Assert.assertNull(userInfo.getEmailAddress());
+        Assert.assertNull(userInfo.getNickname());
+        Assert.assertNull(response.getUserInfoJWT());
+    }
+
     @Test
     public void testFailsWhenWrongJWTAudience() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
         ComponentInitializationException, IOException, com.nimbusds.oauth2.sdk.ParseException, JOSEException {

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


More information about the commits mailing list