[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