[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