[java-idp-oidc] 24/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:49 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=08ec1b4a28e684ddb3ef34c1fd6ed946367bc08f

commit 08ec1b4a28e684ddb3ef34c1fd6ed946367bc08f
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Sat Oct 12 16:02:28 2024 +0300

    JOIDC-201 - Support for OAuth 2.0 Demonstrating Proof of Possession (DPoP)
    
    https://shibboleth.atlassian.net/browse/JOIDC-201
    
    - Switched DPoP nonces to be tied to the relying party
---
 .../claims/impl/DPoPProofNonceClaimsValidator.java | 36 +++++++++++++++++-
 .../impl/DefaultDPoPProofNonceGenerator.java       | 30 +++++++++++++--
 .../op/profile/flow/AbstractOidcApiFlowTest.java   |  5 ++-
 .../AbstractOidcClientAuthenticationFlowTest.java  | 43 +++++++++++++++-------
 .../op/profile/flow/IntrospectionFlowTest.java     |  2 +-
 .../op/profile/flow/PushedAuthorizeFlowTest.java   |  4 +-
 .../oidc/op/profile/flow/RevocationFlowTest.java   |  2 +-
 .../plugin/oidc/op/profile/flow/TokenFlowTest.java | 30 +++++++--------
 .../plugin/oidc/op/profile/flow/UserInfoTest.java  | 41 +++++++++++++++------
 9 files changed, 141 insertions(+), 52 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/security/jwt/claims/impl/DPoPProofNonceClaimsValidator.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/security/jwt/claims/impl/DPoPProofNonceClaimsValidator.java
index c97179e5..85fb559b 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/security/jwt/claims/impl/DPoPProofNonceClaimsValidator.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/security/jwt/claims/impl/DPoPProofNonceClaimsValidator.java
@@ -33,6 +33,7 @@ import com.nimbusds.jwt.JWTClaimsSet;
 import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
 import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
 import net.shibboleth.oidc.profile.config.navigate.DPoPProofNonceGeneratorLookupFunction;
+import net.shibboleth.profile.context.navigate.RelyingPartyIdLookupFunction;
 import net.shibboleth.shared.annotation.ParameterName;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
@@ -67,6 +68,9 @@ public class DPoPProofNonceClaimsValidator extends AbstractClaimsValidator {
     @Nonnull private Function<ProfileRequestContext, Function<ProfileRequestContext, String>>
         nonceGeneratorLookupStrategy;
 
+    /** Strategy used to obtain the relying party ID. */
+    @Nonnull private Function<ProfileRequestContext,String> relyingPartyIdLookupStrategy;
+
     /**
      * Constructor.
      *
@@ -75,6 +79,7 @@ public class DPoPProofNonceClaimsValidator extends AbstractClaimsValidator {
     public DPoPProofNonceClaimsValidator(@Nonnull @ParameterName(name = "sealer") final DataSealer sealer) {
         dataSealer = Constraint.isNotNull(sealer, "Data sealer cannot be null");
         nonceGeneratorLookupStrategy = new DPoPProofNonceGeneratorLookupFunction();
+        relyingPartyIdLookupStrategy = new RelyingPartyIdLookupFunction();
     }
 
     /**
@@ -83,7 +88,7 @@ public class DPoPProofNonceClaimsValidator extends AbstractClaimsValidator {
      * @param mapper object mapper
      */
     public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
-        ifInitializedThrowUnmodifiabledComponentException();
+        checkSetterPreconditions();
 
         objectMapper = Constraint.isNotNull(mapper, "Object mapper cannot be null");
     }
@@ -95,9 +100,23 @@ public class DPoPProofNonceClaimsValidator extends AbstractClaimsValidator {
      */
     public void setDpopProofNonceGeneratorLookupStrategy(
             @Nonnull final Function<ProfileRequestContext,Function<ProfileRequestContext,String>> strategy) {
+        checkSetterPreconditions();
+
         nonceGeneratorLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
     }
 
+    /**
+     * Set the strategy used to obtain the relying party ID.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setRelyingPartyIdLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+        checkSetterPreconditions();
+
+        relyingPartyIdLookupStrategy = Constraint.isNotNull(strategy,
+                "Relying party ID lookup strategy cannot be null");
+    }
+
     /** {@inheritDoc} */
     protected void doInitialize() throws ComponentInitializationException {
         super.doInitialize();
@@ -126,7 +145,20 @@ public class DPoPProofNonceClaimsValidator extends AbstractClaimsValidator {
             final Map<String, Object> map = objectMapper.readValue(unwrapped, mapType);
             final Instant nonceExp = Instant.ofEpochMilli(Long.parseLong((String) map.get("exp")));
             if (Instant.now().isAfter(nonceExp)) {
-                throw new DPoPProofNonceJWTValidationException("Value is expired");                
+                throw new DPoPProofNonceJWTValidationException("Value is expired");
+            }
+            final String rpIdClaim = (String) map.get("rpId");
+            if (rpIdClaim == null) {
+                throw new DPoPProofNonceJWTValidationException("Relying party ID cannot be resolved from nonce");
+            }
+            final String rpId = relyingPartyIdLookupStrategy.apply(context);
+            if (rpId == null) {
+                log.error("Relying party ID cannot be resolved");
+                throw new DPoPProofNonceJWTValidationException("Relying party ID cannot be resolved");
+            }
+            if (!rpIdClaim.equals(rpId)) {
+                log.warn("Relying party IDs don't match: resolved '{}', nonce contained '{}'", rpId, rpIdClaim);
+                throw new DPoPProofNonceJWTValidationException("Relying party IDs don't match");
             }
         } catch (final DataSealerException e) {
             log.trace("Could not unwrap the nonce data", e);
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/security/jwt/claims/impl/DefaultDPoPProofNonceGenerator.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/security/jwt/claims/impl/DefaultDPoPProofNonceGenerator.java
index 53e4aed9..917eab54 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/security/jwt/claims/impl/DefaultDPoPProofNonceGenerator.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/security/jwt/claims/impl/DefaultDPoPProofNonceGenerator.java
@@ -28,6 +28,7 @@ import org.slf4j.Logger;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
+import net.shibboleth.profile.context.navigate.RelyingPartyIdLookupFunction;
 import net.shibboleth.shared.annotation.ParameterName;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
@@ -64,6 +65,9 @@ public class DefaultDPoPProofNonceGenerator extends AbstractIdentifiableInitiali
     /** Lookup function to supply identifier generation strategy to use. */
     @Nonnull private Function<ProfileRequestContext, IdentifierGenerationStrategy> idGeneratorLookupStrategy;
 
+    /** Strategy used to obtain the relying party ID. */
+    @Nonnull private Function<ProfileRequestContext,String> relyingPartyIdLookupStrategy;
+
     /** Nonce lifetime. */
     @Nonnull private Duration nonceLifetime;
 
@@ -79,6 +83,7 @@ public class DefaultDPoPProofNonceGenerator extends AbstractIdentifiableInitiali
         assert fiveMins != null;
         nonceLifetime = fiveMins;
         idGeneratorLookupStrategy = FunctionSupport.constant(new SecureRandomIdentifierGenerationStrategy());
+        relyingPartyIdLookupStrategy = new RelyingPartyIdLookupFunction();
     }
 
     /**
@@ -87,7 +92,7 @@ public class DefaultDPoPProofNonceGenerator extends AbstractIdentifiableInitiali
      * @param mapper object mapper
      */
     public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
-        ifInitializedThrowUnmodifiabledComponentException();
+        checkSetterPreconditions();
 
         objectMapper = Constraint.isNotNull(mapper, "Object mapper cannot be null");
     }
@@ -98,7 +103,7 @@ public class DefaultDPoPProofNonceGenerator extends AbstractIdentifiableInitiali
      * @param duration nonce lifetime
      */
     public void setNonceLifetime(@Nonnull final Duration duration) {
-        ifInitializedThrowUnmodifiabledComponentException();
+        checkSetterPreconditions();
 
         Constraint.isTrue(!duration.isZero() && !duration.isNegative(), "Nonce lifetime must be greater than 0");
         nonceLifetime = duration;
@@ -111,12 +116,24 @@ public class DefaultDPoPProofNonceGenerator extends AbstractIdentifiableInitiali
      */
     public void setIdentifierGeneratorLookupStrategy(
             @Nonnull final Function<ProfileRequestContext,IdentifierGenerationStrategy> strategy) {
-        ifInitializedThrowUnmodifiabledComponentException();
+        checkSetterPreconditions();
 
         idGeneratorLookupStrategy =
                 Constraint.isNotNull(strategy, "Identifier generation strategy cannot be null");
     }
 
+    /**
+     * Set the strategy used to obtain the relying party ID.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setRelyingPartyIdLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+        checkSetterPreconditions();
+
+        relyingPartyIdLookupStrategy = Constraint.isNotNull(strategy,
+                "Relying party ID lookup strategy cannot be null");
+    }
+
     /** {@inheritDoc} */
     protected void doInitialize() throws ComponentInitializationException {
         super.doInitialize();
@@ -133,9 +150,14 @@ public class DefaultDPoPProofNonceGenerator extends AbstractIdentifiableInitiali
             log.error("Could not resolve idGenerator");
             return null;
         }
+        final String rpId = relyingPartyIdLookupStrategy.apply(profileRequestContext);
+        if (rpId == null) {
+            log.error("Could not resolve relying party ID");
+            return null;
+        }
         final String id = idGenerator.generateIdentifier(false);
         final Instant nonceExp = Instant.now().plus(nonceLifetime);
-        final Map<String, Object> map = Map.of("jti", id, "exp", "" + nonceExp.toEpochMilli());
+        final Map<String, Object> map = Map.of("jti", id, "exp", "" + nonceExp.toEpochMilli(), "rpId", rpId);
         try {
             final String raw = objectMapper.writeValueAsString(map);
             if (raw != null) {
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcApiFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcApiFlowTest.java
index 0d1b38b7..b938522d 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcApiFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcApiFlowTest.java
@@ -203,10 +203,11 @@ public class AbstractOidcApiFlowTest extends AbstractOidcFlowTest {
         throw new JOSEException("Unsupported algorithm " + jwsAlgorithm.getName());
     }
 
-    protected String createValidDPoPNonce() {
+    protected String createValidDPoPNonce(final String rpId) {
         final Instant exp = Instant.now().plusSeconds(300);
         try {
-            return getDataSealer().wrap("{\"exp\":\"" + exp.toEpochMilli() + "\",\"jti\":\"mock\"}");
+            return getDataSealer().wrap("{\"exp\":\"" + exp.toEpochMilli() + "\",\"jti\":\"mock\",\"rpId\":\""
+                    + rpId + "\"}");
         } catch (DataSealerException e) {
             Assert.fail("Could not create nonce", e);
         }
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcClientAuthenticationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcClientAuthenticationFlowTest.java
index 96bf48ae..07687665 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcClientAuthenticationFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcClientAuthenticationFlowTest.java
@@ -670,6 +670,21 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
         }
     }
 
+    @SuppressWarnings("null")
+    @Test
+    public void testInvalidDPoPProof_invalidNonceRpId() throws Exception {
+        for (final String id : clientIds) {
+            request.removeHeader("DPoP");
+            final SignedJWT jwt = createSecretJWT(validClaimsSet(id, "http://localhost/idp/profile/oidc/token"),
+                    clientSecret, JWSAlgorithm.HS256);
+            request.addHeader("DPoP", buildDPoPProof(defaultDPoPProofKey(), JWSAlgorithm.ES256, "POST", jwtAud,
+                    null, createValidDPoPNonce("wrong" + id)).serialize());
+            final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+                    ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+            assertErrorCode(result, OAuth2Error.USE_DPOP_NONCE_CODE);
+        }
+    }
+
     @Test
     public void testInvalidDPoPProof_unsignedProof() throws Exception {
         for (final String id : clientIds) {
@@ -678,7 +693,7 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
                     clientSecret, JWSAlgorithm.HS256);
             final ECKey jwk = defaultDPoPProofKey();
             final PlainJWT dpopProof = buildUnsignedDPoPProof(jwk, "POST", jwtAud,
-                    null, createValidDPoPNonce());
+                    null, createValidDPoPNonce(id));
             final String headerValue = dpopProof.serialize(); assert headerValue != null;
             request.addHeader("DPoP", headerValue);
             final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
@@ -702,7 +717,7 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
                     .jwtID(idGenerator.generateIdentifier())
                     .claim("htu", jwtAud)
                     .claim("htm", "POST")
-                    .claim("nonce", createValidDPoPNonce())
+                    .claim("nonce", createValidDPoPNonce(id))
                     .issueTime(new Date())
                     .build();
             final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
@@ -729,7 +744,7 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
                     .jwtID(idGenerator.generateIdentifier())
                     .claim("htu", jwtAud)
                     .claim("htm", "POST")
-                    .claim("nonce", createValidDPoPNonce())
+                    .claim("nonce", createValidDPoPNonce(id))
                     .build();
             final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
             final String headerValue = dpopProof.serialize(); assert headerValue != null;
@@ -755,7 +770,7 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
                     .jwtID(idGenerator.generateIdentifier())
                     .claim("htu", jwtAud)
                     .claim("htm", "POST")
-                    .claim("nonce", createValidDPoPNonce())
+                    .claim("nonce", createValidDPoPNonce(id))
                     .issueTime(Date.from(Instant.now().minusSeconds(600)))
                     .build();
             final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
@@ -782,7 +797,7 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
                     .jwtID(idGenerator.generateIdentifier())
                     .claim("htu", jwtAud)
                     .claim("htm", "POST")
-                    .claim("nonce", createValidDPoPNonce())
+                    .claim("nonce", createValidDPoPNonce(id))
                     .issueTime(Date.from(Instant.now().plusSeconds(600)))
                     .build();
             final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
@@ -809,7 +824,7 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
                     .jwtID(idGenerator.generateIdentifier())
                     .claim("htu", jwtAud)
                     .claim("htm", "POST")
-                    .claim("nonce", createValidDPoPNonce())
+                    .claim("nonce", createValidDPoPNonce(id))
                     .issueTime(Date.from(Instant.now()))
                     .build();
             final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
@@ -836,7 +851,7 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
             final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                     .jwtID(idGenerator.generateIdentifier())
                     .claim("htm", "POST")
-                    .claim("nonce", createValidDPoPNonce())
+                    .claim("nonce", createValidDPoPNonce(id))
                     .issueTime(Date.from(Instant.now()))
                     .build();
             final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
@@ -863,7 +878,7 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
                     .jwtID(idGenerator.generateIdentifier())
                     .claim("htu", jwtAud + "2")
                     .claim("htm", "POST")
-                    .claim("nonce", createValidDPoPNonce())
+                    .claim("nonce", createValidDPoPNonce(id))
                     .issueTime(Date.from(Instant.now()))
                     .build();
             final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
@@ -889,7 +904,7 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
             final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                     .jwtID(idGenerator.generateIdentifier())
                     .claim("htu", jwtAud)
-                    .claim("nonce", createValidDPoPNonce())
+                    .claim("nonce", createValidDPoPNonce(id))
                     .issueTime(Date.from(Instant.now()))
                     .build();
             final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
@@ -916,7 +931,7 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
                     .jwtID(idGenerator.generateIdentifier())
                     .claim("htu", jwtAud)
                     .claim("htm", "GET")
-                    .claim("nonce", createValidDPoPNonce())
+                    .claim("nonce", createValidDPoPNonce(id))
                     .issueTime(Date.from(Instant.now()))
                     .build();
             final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
@@ -942,7 +957,7 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
             final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                     .claim("htu", jwtAud)
                     .claim("htm", "POST")
-                    .claim("nonce", createValidDPoPNonce())
+                    .claim("nonce", createValidDPoPNonce(id))
                     .issueTime(Date.from(Instant.now()))
                     .build();
             final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
@@ -972,7 +987,7 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
                     .jwtID(idGenerator.generateIdentifier())
                     .claim("htu", jwtAud)
                     .claim("htm", "POST")
-                    .claim("nonce", createValidDPoPNonce())
+                    .claim("nonce", createValidDPoPNonce(id))
                     .issueTime(Date.from(Instant.now()))
                     .build();
             final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
@@ -1003,7 +1018,7 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
                     .jwtID(idGenerator.generateIdentifier())
                     .claim("htu", jwtAud)
                     .claim("htm", "POST")
-                    .claim("nonce", createValidDPoPNonce())
+                    .claim("nonce", createValidDPoPNonce(id))
                     .issueTime(Date.from(Instant.now()))
                     .claim("ath", "invalidContents")
                     .build();
@@ -1035,7 +1050,7 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
                     .jwtID(idGenerator.generateIdentifier())
                     .claim("htu", jwtAud)
                     .claim("htm", "POST")
-                    .claim("nonce", createValidDPoPNonce())
+                    .claim("nonce", createValidDPoPNonce(id))
                     .issueTime(Date.from(Instant.now()))
                     .build();
             final SignedJWT dpopProof = buildDPoPProof(header, claimsSet, jwk.toECPrivateKey());
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IntrospectionFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IntrospectionFlowTest.java
index 518cdc4a..f727fe7a 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IntrospectionFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IntrospectionFlowTest.java
@@ -264,7 +264,7 @@ public class IntrospectionFlowTest extends AbstractOidcClientAuthenticationFlowT
 
         final DPoPAccessToken token = new DPoPAccessToken(claims.serialize(getDataSealer()));
         final SignedJWT dpopProof = buildDPoPProof(dpopProofKey, JWSAlgorithm.ES256, "POST",
-                "http://localhost/idp/profile/oauth2/introspection", token, createValidDPoPNonce());
+                "http://localhost/idp/profile/oauth2/introspection", token, createValidDPoPNonce(clientId));
         request.addHeader("DPoP", dpopProof.serialize());
         setBasicAuth(clientId, clientSecret);
         storeMetadata(storageService, clientId, clientSecret, scope);
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 a6e13577..966ad550 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
@@ -284,7 +284,7 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
         storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
         setBasicAuth(clientId, clientSecret);
         request.addHeader("DPoP", buildDPoPProof("POST", "http://localhost/idp/profile/oauth2/pushed-authorization",
-                createValidDPoPNonce()).serialize());
+                createValidDPoPNonce(clientId)).serialize());
         Map<String, String> requestParameters = createRequestParameters(clientId);
         requestParameters.put("dpop_jkt", "notMatching");
         setHttpFormRequest("POST", requestParameters);
@@ -310,7 +310,7 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
         storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
         setBasicAuth(clientId, clientSecret);
         request.addHeader("DPoP", buildDPoPProof("POST", "http://localhost/idp/profile/oauth2/pushed-authorization",
-                createValidDPoPNonce()).serialize());
+                createValidDPoPNonce(clientId)).serialize());
         setHttpFormRequest("POST", createRequestParameters(clientId));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertSuccessResponse(result, clientId);
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RevocationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RevocationFlowTest.java
index 20f12031..1a4a15a8 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RevocationFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RevocationFlowTest.java
@@ -199,7 +199,7 @@ public class RevocationFlowTest extends AbstractOidcClientAuthenticationFlowTest
 
         final DPoPAccessToken token = new DPoPAccessToken(claims.serialize(getDataSealer()));
         final SignedJWT dpopProof = buildDPoPProof(dpopProofKey, JWSAlgorithm.ES256, "POST",
-                "http://localhost/idp/profile/oauth2/revocation", token, createValidDPoPNonce());
+                "http://localhost/idp/profile/oauth2/revocation", token, createValidDPoPNonce(clientId));
         request.addHeader("DPoP", dpopProof.serialize());
         setBasicAuth(clientId, clientSecret);
         storeMetadata(storageService, clientId, clientSecret, scope);
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 46321966..09c30c48 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
@@ -796,7 +796,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
             .setDpopProofJwkThumbprint("mockJkt");
         final String authorizationCode = builder.build().serialize(getDataSealer());
         request.addHeader("DPoP", buildDPoPProof("POST", "http://localhost/idp/profile/oidc/token",
-                createValidDPoPNonce()).serialize());
+                createValidDPoPNonce(clientId)).serialize());
 
         initializeGrantAndRequest(clientId, createRequestParameters(redirectUri,
                 "authorization_code",
@@ -810,7 +810,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
     public void testDPoPValidGrantThumbprintIncludedMatchingProof() throws Exception {
         final AuthorizeCodeClaimsSet.Builder builder = new AuthorizeCodeClaimsSet.Builder();
         final SignedJWT dpopProof = buildDPoPProof("POST", "http://localhost/idp/profile/oidc/token",
-                createValidDPoPNonce());
+                createValidDPoPNonce(clientId));
         builder.setJWTID(new SecureRandomIdentifierGenerationStrategy())
             .setClientID(new ClientID(clientId))
             .setIssuer("https://op.example.org")
@@ -842,7 +842,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         final String clientId = clientIdAlwaysBearerAccessToken;
         final AuthorizeCodeClaimsSet.Builder builder = new AuthorizeCodeClaimsSet.Builder();
         final SignedJWT dpopProof = buildDPoPProof("POST", "http://localhost/idp/profile/oidc/token",
-                createValidDPoPNonce());
+                createValidDPoPNonce(clientId));
         final String jkt = dpopProof.getHeader().getJWK().computeThumbprint().toString();
         builder.setJWTID(new SecureRandomIdentifierGenerationStrategy())
             .setClientID(new ClientID(clientId))
@@ -883,7 +883,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         final String clientId = clientIdPkcePlainPublic;
         final AuthorizeCodeClaimsSet.Builder builder = new AuthorizeCodeClaimsSet.Builder();
         final SignedJWT dpopProof = buildDPoPProof("POST", "http://localhost/idp/profile/oidc/token",
-                createValidDPoPNonce());
+                createValidDPoPNonce(clientId));
         builder.setJWTID(new SecureRandomIdentifierGenerationStrategy())
             .setClientID(new ClientID(clientId))
             .setIssuer("https://op.example.org")
@@ -916,7 +916,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         final String clientId = clientIdPkcePlainPublic;
         final RefreshTokenClaimsSet.Builder builder = new RefreshTokenClaimsSet.Builder();
         final SignedJWT dpopProof = buildDPoPProof("POST", "http://localhost/idp/profile/oidc/token",
-                createValidDPoPNonce());
+                createValidDPoPNonce(clientId));
         builder.setJWTID(new SecureRandomIdentifierGenerationStrategy())
             .setClientID(new ClientID(clientId))
             .setIssuer("https://op.example.org")
@@ -947,7 +947,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         final String clientId = clientIdPkcePlainPublic;
         final RefreshTokenClaimsSet.Builder builder = new RefreshTokenClaimsSet.Builder();
         final SignedJWT dpopProof = buildDPoPProof("POST", "http://localhost/idp/profile/oidc/token",
-                createValidDPoPNonce());
+                createValidDPoPNonce(clientId));
         builder.setJWTID(new SecureRandomIdentifierGenerationStrategy())
             .setClientID(new ClientID(clientId))
             .setIssuer("https://op.example.org")
@@ -973,7 +973,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
     public void testDPoPValidGrantThumbprintIncludedNonMatchingProof_confidentialClientRefresh() throws Exception {
         final RefreshTokenClaimsSet.Builder builder = new RefreshTokenClaimsSet.Builder();
         final SignedJWT dpopProof = buildDPoPProof("POST", "http://localhost/idp/profile/oidc/token",
-                createValidDPoPNonce());
+                createValidDPoPNonce(clientId));
         builder.setJWTID(new SecureRandomIdentifierGenerationStrategy())
             .setClientID(new ClientID(clientId))
             .setIssuer("https://op.example.org")
@@ -1014,7 +1014,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
             .setScope(scope);
         final String authorizationCode = builder.build().serialize(getDataSealer());
         request.addHeader("DPoP", buildDPoPProof("POST", "http://localhost/idp/profile/oidc/token",
-                createValidDPoPNonce()).serialize());
+                createValidDPoPNonce(clientId)).serialize());
 
         initializeGrantAndRequest(clientId, createRequestParameters(redirectUri,
                 "authorization_code",
@@ -1096,7 +1096,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
             .setDpopProofJwkThumbprint("mockJkt");
         final String authorizationCode = builder.build().serialize(getDataSealer());
         request.addHeader("DPoP", buildDPoPProof("POST", "http://localhost/idp/profile/oidc/token",
-                createValidDPoPNonce()).serialize());
+                createValidDPoPNonce(clientId)).serialize());
 
         initializeGrantAndRequest(clientId, createRequestParameters(redirectUri,
                 "authorization_code",
@@ -1111,7 +1111,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         final String clientId = clientIdDPoPAccessToken;
         final AuthorizeCodeClaimsSet.Builder builder = new AuthorizeCodeClaimsSet.Builder();
         final SignedJWT dpopProof = buildDPoPProof("POST", "http://localhost/idp/profile/oidc/token",
-                createValidDPoPNonce());
+                createValidDPoPNonce(clientId));
         builder.setJWTID(new SecureRandomIdentifierGenerationStrategy())
             .setClientID(new ClientID(clientId))
             .setIssuer("https://op.example.org")
@@ -1148,7 +1148,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         final String clientId = clientIdDPoPAccessToken;
         final AuthorizeCodeClaimsSet.Builder builder = new AuthorizeCodeClaimsSet.Builder();
         final SignedJWT dpopProof = buildDPoPProof("POST", "http://localhost/idp/profile/oidc/token",
-                createValidDPoPNonce());
+                createValidDPoPNonce(clientId));
         builder.setJWTID(new SecureRandomIdentifierGenerationStrategy())
             .setClientID(new ClientID(clientId))
             .setIssuer("https://op.example.org")
@@ -1219,7 +1219,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
             .setDpopProofJwkThumbprint("mockJkt");
         final String authorizationCode = builder.build().serialize(getDataSealer());
         request.addHeader("DPoP", buildDPoPProof("POST", "http://localhost/idp/profile/oidc/token",
-                createValidDPoPNonce()).serialize());
+                createValidDPoPNonce(clientId)).serialize());
 
         initializeGrantAndRequest(clientId, createRequestParameters(redirectUri,
                 "authorization_code",
@@ -1261,7 +1261,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         final String clientId = clientIdDPoPJwtAccessToken;
         final AuthorizeCodeClaimsSet.Builder builder = new AuthorizeCodeClaimsSet.Builder();
         final SignedJWT dpopProof = buildDPoPProof("POST", "http://localhost/idp/profile/oidc/token",
-                createValidDPoPNonce());
+                createValidDPoPNonce(clientId));
         builder.setJWTID(new SecureRandomIdentifierGenerationStrategy())
             .setClientID(new ClientID(clientId))
             .setIssuer("https://op.example.org")
@@ -1298,7 +1298,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         final String clientId = clientIdDPoPJwtBearerAccessToken;
         final AuthorizeCodeClaimsSet.Builder builder = new AuthorizeCodeClaimsSet.Builder();
         final SignedJWT dpopProof = buildDPoPProof("POST", "http://localhost/idp/profile/oidc/token",
-                createValidDPoPNonce());
+                createValidDPoPNonce(clientId));
         builder.setJWTID(new SecureRandomIdentifierGenerationStrategy())
             .setClientID(new ClientID(clientId))
             .setIssuer("https://op.example.org")
@@ -1337,7 +1337,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         final String clientId = clientIdDPoPJwtAccessToken;
         final AuthorizeCodeClaimsSet.Builder builder = new AuthorizeCodeClaimsSet.Builder();
         final SignedJWT dpopProof = buildDPoPProof("POST", "http://localhost/idp/profile/oidc/token",
-                createValidDPoPNonce());
+                createValidDPoPNonce(clientId));
         builder.setJWTID(new SecureRandomIdentifierGenerationStrategy())
             .setClientID(new ClientID(clientId))
             .setIssuer("https://op.example.org")
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 c44c4714..de743304 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
@@ -290,7 +290,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         final AccessTokenClaimsSet claims = buildDPoPAccessTokenClaimsSet("mockId");
         final DPoPAccessToken token = new DPoPAccessToken(claims.serialize(getDataSealer()));
         request.addHeader("DPoP", buildDPoPProof(request.getMethod(), "http://localhost/idp/profile/oidc/userinfo",
-                createValidDPoPNonce()).serialize());
+                createValidDPoPNonce(clientId)).serialize());
 
         storeMetadata(storageService, clientId, "mockSecret", scope);
         request.addHeader("Authorization", getTokenHeaderValue(token));
@@ -306,7 +306,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         final AccessTokenClaimsSet claims = buildDPoPAccessTokenClaimsSet("mockId");
         final DPoPAccessToken token = buildJWTDPoPToken(claims, signingKey.getPrivateKey(), "RS256");
         request.addHeader("DPoP", buildDPoPProof(request.getMethod(), "http://localhost/idp/profile/oidc/userinfo",
-                createValidDPoPNonce()).serialize());
+                createValidDPoPNonce(clientId)).serialize());
 
         storeMetadata(storageService, clientId, "mockSecret", scope);
         request.addHeader("Authorization", getTokenHeaderValue(token));
@@ -319,7 +319,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
     public void testFailWithDPoP_noAth() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
         ComponentInitializationException, IOException, com.nimbusds.oauth2.sdk.ParseException, JOSEException {
         final SignedJWT dpopProof = buildDPoPProof(request.getMethod(), "http://localhost/idp/profile/oidc/userinfo",
-                createValidDPoPNonce());
+                createValidDPoPNonce(clientId));
         final AccessTokenClaimsSet claims =
                 buildDPoPAccessTokenClaimsSet((dpopProof.getHeader().getJWK().computeThumbprint().toString()));
 
@@ -337,7 +337,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
     public void testFailWithDPoP_nonMatchingAth() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
         ComponentInitializationException, IOException, com.nimbusds.oauth2.sdk.ParseException, JOSEException {
         final SignedJWT dpopProof = buildDPoPProof(request.getMethod(), "http://localhost/idp/profile/oidc/userinfo",
-                new DPoPAccessToken("mock"), createValidDPoPNonce());
+                new DPoPAccessToken("mock"), createValidDPoPNonce(clientId));
         final AccessTokenClaimsSet claims =
                 buildDPoPAccessTokenClaimsSet((dpopProof.getHeader().getJWK().computeThumbprint().toString()));
 
@@ -369,6 +369,25 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         assertErrorCode(result, OAuth2Error.USE_DPOP_NONCE_CODE);
     }
 
+    @SuppressWarnings("null")
+    @Test
+    public void testFailWithDPoP_invlidNonceRpId() 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("wrongId"));
+        request.addHeader("DPoP", dpopProof.serialize());
+
+        storeMetadata(storageService, clientId, "mockSecret", scope);
+        request.addHeader("Authorization", getTokenHeaderValue(token));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.USE_DPOP_NONCE_CODE);
+    }
+
     @SuppressWarnings("null")
     @Test
     public void testSuccessOnlySubjectWithDPoP() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
@@ -379,7 +398,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
 
         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());
+                "http://localhost/idp/profile/oidc/userinfo", token, createValidDPoPNonce(clientId));
         request.addHeader("DPoP", dpopProof.serialize());
 
         storeMetadata(storageService, clientId, "mockSecret", scope);
@@ -411,7 +430,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
     public void testFailWithJWTDPoP_noAth() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
         ComponentInitializationException, IOException, com.nimbusds.oauth2.sdk.ParseException, JOSEException {
         final SignedJWT dpopProof = buildDPoPProof(request.getMethod(), "http://localhost/idp/profile/oidc/userinfo",
-                createValidDPoPNonce());
+                createValidDPoPNonce(clientId));
         final AccessTokenClaimsSet claims =
                 buildDPoPAccessTokenClaimsSet((dpopProof.getHeader().getJWK().computeThumbprint().toString()));
 
@@ -429,7 +448,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
     public void testFailWithJWTDPoP_nonMatchingAth() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
         ComponentInitializationException, IOException, com.nimbusds.oauth2.sdk.ParseException, JOSEException {
         final SignedJWT dpopProof = buildDPoPProof(request.getMethod(), "http://localhost/idp/profile/oidc/userinfo",
-                new DPoPAccessToken("mock"), createValidDPoPNonce());
+                new DPoPAccessToken("mock"), createValidDPoPNonce(clientId));
         final AccessTokenClaimsSet claims =
                 buildDPoPAccessTokenClaimsSet((dpopProof.getHeader().getJWK().computeThumbprint().toString()));
 
@@ -471,7 +490,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
 
         final DPoPAccessToken token = buildJWTDPoPToken(claims, signingKey.getPrivateKey(), "RS256");
         final SignedJWT dpopProof = buildDPoPProof(dpopProofKey, JWSAlgorithm.ES256, "POST",
-                "http://localhost/idp/profile/oidc/userinfo", token, createValidDPoPNonce());
+                "http://localhost/idp/profile/oidc/userinfo", token, createValidDPoPNonce(clientId));
         request.addHeader("DPoP", dpopProof.serialize());
 
         storeMetadata(storageService, clientId, "mockSecret", scope);
@@ -492,7 +511,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
 
         final DPoPAccessToken token = buildJWTDPoPToken(claims, signingKey.getPrivateKey(), "RS256");
         final SignedJWT dpopProof = buildDPoPProof(dpopProofKey, JWSAlgorithm.ES256, "GET",
-                "http://localhost/idp/profile/oidc/userinfo", token, createValidDPoPNonce());
+                "http://localhost/idp/profile/oidc/userinfo", token, createValidDPoPNonce(clientId));
         request.addHeader("DPoP", dpopProof.serialize());
 
         storeMetadata(storageService, clientId, "mockSecret", scope);
@@ -511,7 +530,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
 
         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());
+                "http://localhost/idp/profile/oidc/userinfo", token, createValidDPoPNonce(clientId));
         request.addHeader("DPoP", dpopProof.serialize());
 
         storeMetadata(storageService, clientId, "mockSecret", scope);
@@ -538,7 +557,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
 
         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());
+                "http://localhost/idp/profile/oidc/userinfo", token, createValidDPoPNonce(clientId));
         request.addHeader("DPoP", dpopProof.serialize());
 
         storeMetadata(storageService, clientId, "mockSecret", scope);

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


More information about the commits mailing list