[java-idp-oidc] branch main updated: JOIDC-68 Request object (JWT) validation is incomplete

Scott Cantor cantor.2 at osu.edu
Thu Dec 30 20:46:16 UTC 2021


This is an automated email from the git hooks/post-receive script.

scantor 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=01f6ab2da1ee2be401025856431b807bb855a02d

The following commit(s) were added to refs/heads/main by this push:
     new 01f6ab2d JOIDC-68 Request object (JWT) validation is incomplete
01f6ab2d is described below

commit 01f6ab2da1ee2be401025856431b807bb855a02d
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu Dec 30 16:46:43 2021 +0200

    JOIDC-68 Request object (JWT) validation is incomplete
    
    https://shibboleth.atlassian.net/browse/JOIDC-68
    
    Added ClaimValidators for verifying exp and nbf claims for all request objects - if
    they're present in the JWT. For signed request objects: also iss and aud are required and
    verified.
---
 .../op/profile/impl/ValidateRequestObject.java     |  66 +++++-
 .../idp/flows/oidc/authorize/authorize-beans.xml   |  51 ++++-
 .../oidc/op/profile/flow/AuthorizeFlowTest.java    | 124 +++++++++-
 .../op/profile/impl/ValidateRequestObjectTest.java | 254 ++++++++++++++++++++-
 4 files changed, 488 insertions(+), 7 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRequestObject.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRequestObject.java
index 221b80ae..853e218c 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRequestObject.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRequestObject.java
@@ -31,14 +31,19 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.PlainJWT;
 import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.ResponseType;
 import com.nimbusds.oauth2.sdk.id.ClientID;
 
 import net.shibboleth.idp.plugin.oidc.op.profile.OidcEventIds;
+import net.shibboleth.oidc.jwt.claims.JWTClaimsValidation;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
 import net.shibboleth.oidc.security.impl.JWTSignatureValidationUtil;
 import net.shibboleth.oidc.security.impl.OIDCSignatureValidationParameters;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
@@ -61,6 +66,12 @@ public class ValidateRequestObject extends AbstractOIDCAuthenticationResponseAct
     /** Request Object. */
     @Nullable private JWT requestObject;
 
+    /** The claims validator to be applied for validating the signed request object. */
+    @NonnullAfterInit private JWTClaimsValidation signedClaimsValidation;
+
+    /** The claims validator to be applied for validating the plain/unsigned request object. */
+    @NonnullAfterInit private JWTClaimsValidation plainClaimsValidation;
+
     /** Constructor. */
     public ValidateRequestObject() {
         securityParametersLookupStrategy = new ChildContextLookup<>(SecurityParametersContext.class);
@@ -79,6 +90,40 @@ public class ValidateRequestObject extends AbstractOIDCAuthenticationResponseAct
                 Constraint.isNotNull(strategy, "SecurityParameterContext lookup strategy cannot be null");
     }
 
+    /**
+     * Set the claims validator used for validating the signed request object.
+     * 
+     * @param validators What to set.
+     */
+    public void setSignedClaimsValidation(@Nonnull final JWTClaimsValidation validation) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        signedClaimsValidation = Constraint.isNotNull(validation, "Signed claims validator cannot be null");
+    }
+
+    /**
+     * Set the claims validator used for validating the plain/unsigned request object.
+     * 
+     * @param validators What to set.
+     */
+    public void setPlainClaimsValidation(@Nonnull final JWTClaimsValidation validation) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        plainClaimsValidation = Constraint.isNotNull(validation, "Plain claims validator cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+
+        if (signedClaimsValidation == null) {
+            throw new ComponentInitializationException("ClaimsValidation for signed requests cannot be null");
+        }
+
+        if (plainClaimsValidation == null) {
+            throw new ComponentInitializationException("ClaimsValidation for plain requests cannot be null");
+        }
+}
+
     /** {@inheritDoc} */
     @Override
     protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -121,16 +166,18 @@ public class ValidateRequestObject extends AbstractOIDCAuthenticationResponseAct
             }
 
         }
+        final JWTClaimsSet claimsSet;
         // Validate still client_id and response_type values
         try {
-            if (requestObject.getJWTClaimsSet().getClaims().containsKey("client_id")
+            claimsSet = requestObject.getJWTClaimsSet();
+            if (claimsSet.getClaims().containsKey("client_id")
                     && !getAuthenticationRequest().getClientID()
-                            .equals(new ClientID((String) requestObject.getJWTClaimsSet().getClaim("client_id")))) {
+                            .equals(new ClientID((String) claimsSet.getClaim("client_id")))) {
                 log.error("{} client_id in request object not matching client_id request parameter", getLogPrefix());
                 ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_REQUEST_OBJECT);
                 return;
             }
-            if (requestObject.getJWTClaimsSet().getClaims().containsKey("response_type")
+            if (claimsSet.getClaims().containsKey("response_type")
                     && !getAuthenticationRequest().getResponseType().equals(new ResponseType(
                             ((String) requestObject.getJWTClaimsSet().getClaim("response_type")).split(" ")))) {
                 log.error("{} response_type in request object not matching response_type request parameter",
@@ -143,6 +190,19 @@ public class ValidateRequestObject extends AbstractOIDCAuthenticationResponseAct
             ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_REQUEST_OBJECT);
             return;
         }
+
+        try {
+            if (requestObject instanceof SignedJWT) {
+                signedClaimsValidation.validate(claimsSet, profileRequestContext);
+            } else {
+                plainClaimsValidation.validate(claimsSet, profileRequestContext);
+            }
+        } catch (final JWTValidationException e) {
+            log.warn("{} JWT validation failed: {}", getLogPrefix(), e.getMessage());
+            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_REQUEST_OBJECT);
+            return;
+        }
+
     }    
     
     // Checkstyle: CyclomaticComplexity ON
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
index ef383414..8738133e 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
@@ -102,7 +102,9 @@
         scope="prototype" />
 
     <bean id="ValidateRequestObject" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateRequestObject"
-        scope="prototype">
+        scope="prototype"
+        p:plainClaimsValidation-ref="shibboleth.oidc.PlainRequestObjectClaimsValidation"
+        p:signedClaimsValidation-ref="shibboleth.oidc.SignedRequestObjectClaimsValidation">
         <property name="securityParametersLookupStrategy">
             <bean parent="shibboleth.Functions.Compose"
                 c:g-ref="shibboleth.ChildLookup.SecurityParameters"
@@ -110,6 +112,53 @@
         </property>
     </bean>
 
+    <bean id="shibboleth.oidc.PlainRequestObjectClaimsValidation"
+        class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidation"
+        p:claimValidators-ref="PlainClaimsValidators" />
+
+    <bean id="shibboleth.oidc.SignedRequestObjectClaimsValidation"
+        class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidation"
+        p:claimValidators-ref="SignedClaimsValidators" />
+
+    <bean id="ExpiryClaimsValidator"
+        class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
+        p:clockSkew="%{idp.policy.clockSkew:PT1M}" />
+
+    <bean id="NotBeforeClaimsValidator"
+        class="net.shibboleth.oidc.security.jwt.claims.impl.NotBeforeClaimsValidator"
+        p:clockSkew="%{idp.policy.clockSkew:PT1M}" />
+
+    <bean id="IssuerClaimsValidator"
+        class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
+        p:claimName="iss">
+        <property name="valueToMatchLookupStrategy">
+            <bean parent="shibboleth.BiFunctions.Expression"
+                c:expression="#custom.apply(#input1.getInboundMessageContext()) == null ? null : #custom.apply(#input1.getInboundMessageContext()).toString()"
+                p:customObject-ref="shibboleth.ClientIDLookupStrategy" />
+        </property>
+    </bean>
+
+    <bean id="AudienceClaimsValidator"
+        class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator">
+         <property name="audienceLookupStrategy">
+            <bean parent="shibboleth.BiFunctions.Expression"
+                c:expression="#custom.apply(#input1)"
+                p:customObject-ref="shibboleth.ResponderIdLookup.Simple" />
+        </property>
+    </bean>
+
+    <util:list id="PlainClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+        <ref bean="ExpiryClaimsValidator" />
+        <ref bean="NotBeforeClaimsValidator" />
+    </util:list>
+
+    <util:list id="SignedClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+        <ref bean="ExpiryClaimsValidator" />
+        <ref bean="NotBeforeClaimsValidator" />
+        <ref bean="IssuerClaimsValidator" />
+        <ref bean="AudienceClaimsValidator" />
+    </util:list>
+
     <bean id="ValidateRedirectURI" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateRedirectURI"
         scope="prototype" />
 
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
index 9aa2142c..441c60dc 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
@@ -18,6 +18,9 @@
 package net.shibboleth.idp.plugin.oidc.op.profile.flow;
 
 import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
 
 import org.opensaml.storage.StorageService;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -28,6 +31,11 @@ import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.ParseException;
 import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
 import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
@@ -43,7 +51,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
     
     String redirectUri = "https://example.org/cb";
     String clientId = "mockClientId";
-    String clientSecret = "mockClientSecret";
+    String clientSecret = "mockClientSecretmockClientSecretmockClientSecretmockClientSecretmockClientSecret";
     
     @Autowired
     @Qualifier("shibboleth.StorageService")
@@ -144,6 +152,120 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertEquals(result.getOutcome().getId(), "ErrorView");
     }
 
+    @Test
+    public void testWithPlainReqObjectExpired() throws IOException, ParseException, SessionException {
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .expirationTime(Date.from(Instant.now().minus(Duration.ofMinutes(5))))
+                .build();
+        assertRequestObjectError(new PlainJWT(ro));
+    }
+
+    @Test
+    public void testWithPlainReqObjectNbfInFuture() throws IOException, ParseException, SessionException {
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .notBeforeTime(Date.from(Instant.now().plus(Duration.ofMinutes(5))))
+                .build();
+        assertRequestObjectError(new PlainJWT(ro));
+    }
+
+    @Test
+    public void testWithPlainReqObjectOverwriteRedirectUri() throws IOException, ParseException,
+            SessionException {
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("redirect_uri", redirectUri)
+                .build();
+        PlainJWT requestObject = new PlainJWT(ro);
+        request.setMethod("GET");
+        request.setQueryString("client_id=mockClientId&response_type=code&scope=openid%20profile&redirect_uri="
+                + "https://invalid.org/cb&request=" + requestObject.serialize());
+        storeMetadata(storageService, clientId, clientSecret, redirectUri);
+
+        initializeThreadLocals();
+
+        FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+        AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+        Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+        Assert.assertNull(successResponse.getIDToken());
+        Assert.assertNull(successResponse.getAccessToken());
+        Assert.assertNotNull(successResponse.getAuthorizationCode());
+    }
+
+    @Test
+    public void testWithSignedReqObjectNoIssuer() throws IOException, ParseException, SessionException,
+            JOSEException {
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .audience("https://op.example.org")
+                .build();
+        assertRequestObjectError(createSecretJWT(ro, clientSecret));
+    }
+
+    @Test
+    public void testWithSignedReqObjectNoAudience() throws IOException, ParseException, SessionException,
+            JOSEException {
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .issuer(clientId)
+                .build();
+        assertRequestObjectError(createSecretJWT(ro, clientSecret));
+    }
+
+    @Test
+    public void testWithSignedReqObjectWrongIssuer() throws IOException, ParseException, SessionException,
+            JOSEException {
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .audience("https://op.example.org")
+                .issuer("invalid")
+                .build();
+        assertRequestObjectError(createSecretJWT(ro, clientSecret));
+    }
+
+    @Test
+    public void testWithSignedReqObjectWrongAudience() throws IOException, ParseException, SessionException,
+            JOSEException {
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .audience("https://invalid.org")
+                .issuer(clientId)
+                .build();
+        assertRequestObjectError(createSecretJWT(ro, clientSecret));
+    }
+
+    @Test
+    public void testWithSignedReqObjectOverwriteRedirectUri() throws IOException, ParseException,
+            SessionException, JOSEException {
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .audience("https://op.example.org")
+                .issuer(clientId)
+                .claim("redirect_uri", redirectUri)
+                .build();
+        SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        request.setMethod("GET");
+        request.setQueryString("client_id=mockClientId&response_type=code&scope=openid%20profile&redirect_uri="
+                + "https://invalid.org/cb&request=" + requestObject.serialize());
+        storeMetadata(storageService, clientId, clientSecret, redirectUri);
+
+        initializeThreadLocals();
+
+        FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+        AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+        Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+        Assert.assertNull(successResponse.getIDToken());
+        Assert.assertNull(successResponse.getAccessToken());
+        Assert.assertNotNull(successResponse.getAuthorizationCode());
+    }
+
+    protected void assertRequestObjectError(final JWT requestObject) throws IOException {
+        request.setMethod("GET");
+        request.setQueryString("client_id=mockClientId&response_type=code&scope=openid%20profile&redirect_uri="
+                + redirectUri + "&request=" + requestObject.serialize());
+        storeMetadata(storageService, clientId, clientSecret, redirectUri);
+        initializeThreadLocals();
+        
+        FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertFlowExecutionResult(result, FLOW_ID);
+        Assert.assertEquals(result.getOutcome().getId(), "ErrorView");        
+    }
+
     @AfterMethod
     public void removeMetadata() throws IOException {
         removeMetadata(storageService, clientId);
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRequestObjectTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRequestObjectTest.java
index cfe21b93..f5ad9190 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRequestObjectTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRequestObjectTest.java
@@ -20,12 +20,16 @@ package net.shibboleth.idp.plugin.oidc.op.profile.impl;
 import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
 import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCMetadataContext;
 import net.shibboleth.idp.plugin.oidc.op.profile.OidcEventIds;
-import net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateRequestObject;
 import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
 import net.shibboleth.idp.profile.testing.ActionTestingSupport;
 import net.shibboleth.idp.profile.testing.RequestContextBuilder;
 import net.shibboleth.oidc.security.credential.BasicJWKCredential;
 import net.shibboleth.oidc.security.impl.OIDCSignatureValidationParameters;
+import net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator;
+import net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidation;
+import net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator;
+import net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator;
+import net.shibboleth.oidc.security.jwt.claims.impl.NotBeforeClaimsValidator;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import java.net.URI;
 import java.net.URISyntaxException;
@@ -34,6 +38,10 @@ import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.NoSuchAlgorithmException;
 import java.security.interfaces.ECPrivateKey;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
 
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
@@ -77,6 +85,8 @@ public class ValidateRequestObjectTest {
     private OIDCAuthenticationResponseContext oidcRespCtx;
 
     private KeyPair kp;
+    
+    private String issuer;
 
     @BeforeMethod
     public void setup() throws ComponentInitializationException, NoSuchAlgorithmException {
@@ -85,6 +95,7 @@ public class ValidateRequestObjectTest {
         oidcCtx = prc.getInboundMessageContext().getSubcontext(OIDCMetadataContext.class, true);
         oidcRespCtx = new OIDCAuthenticationResponseContext();
         prc.getOutboundMessageContext().addSubcontext(oidcRespCtx);
+        issuer = "https://op.example.org/";
         OIDCClientMetadata metaData = new OIDCClientMetadata();
         OIDCClientInformation information = new OIDCClientInformation(new ClientID("test"), null, metaData,
                 new Secret("ultimatetopsecretultimatetopsecret"), null, null);
@@ -107,6 +118,59 @@ public class ValidateRequestObjectTest {
         params.setSignatureAlgorithm("RS256");
         secCtx.setSignatureSigningParameters(params);
         action = new ValidateRequestObject();
+        action.setPlainClaimsValidation(buildPlainClaimsValidation());
+        action.setSignedClaimsValidation(buildSignedClaimsValidation("000123"));
+        action.initialize();
+    }
+    
+    protected ChainingJWTClaimsValidation buildSignedClaimsValidation(final String clientId) {
+        ChainingJWTClaimsValidation claimsValidation = new ChainingJWTClaimsValidation();
+        ExactMatchClaimsValidator issValidator = new ExactMatchClaimsValidator();
+        issValidator.setClaimName("iss");
+        issValidator.setValueToMatchLookupStrategy((prc, claimsSet) -> clientId);
+        AudienceClaimsValidator audValidator = new AudienceClaimsValidator();
+        audValidator.setAudienceLookupStrategy((prc, claimsSet) -> issuer);
+        claimsValidation.setClaimValidators(List.of(
+                new ExpiryClaimsValidator(),
+                new NotBeforeClaimsValidator(),
+                issValidator,
+                audValidator));
+        return claimsValidation;
+    }
+
+    protected ChainingJWTClaimsValidation buildPlainClaimsValidation() {
+        ChainingJWTClaimsValidation claimsValidation = new ChainingJWTClaimsValidation();
+        claimsValidation.setClaimValidators(List.of(
+                new ExpiryClaimsValidator(),
+                new NotBeforeClaimsValidator()));
+        return claimsValidation;
+    }
+
+    @Test(expectedExceptions = ComponentInitializationException.class)
+    public void testInitFailsNoValidators() throws ComponentInitializationException {
+        action = new ValidateRequestObject();
+        action.initialize();
+    }
+
+    @Test(expectedExceptions = ComponentInitializationException.class)
+    public void testInitFailsNoPlainValidator() throws ComponentInitializationException {
+        action = new ValidateRequestObject();
+        action.setSignedClaimsValidation(new ChainingJWTClaimsValidation());
+        action.initialize();
+    }
+
+    @Test(expectedExceptions = ComponentInitializationException.class)
+    public void testInitFailsNoSignedValidators() throws ComponentInitializationException {
+        action = new ValidateRequestObject();
+        action.setPlainClaimsValidation(new ChainingJWTClaimsValidation());
+        action.initialize();
+    }
+
+    @Test
+    public void testInitSuccess() throws ComponentInitializationException {
+        action = new ValidateRequestObject();
+        action.setPlainClaimsValidation(new ChainingJWTClaimsValidation());
+        action.setSignedClaimsValidation(new ChainingJWTClaimsValidation());
         action.initialize();
     }
 
@@ -225,6 +289,26 @@ public class ValidateRequestObjectTest {
         ActionTestingSupport.assertProceedEvent(event);
     }
 
+    @Test
+    public void testRequestObjectClientRespTypeMatchWithExpNbf()
+            throws NoSuchAlgorithmException, ComponentInitializationException, URISyntaxException {
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("client_id", "000123")
+                .claim("response_type", "code token")
+                .notBeforeTime(Date.from(Instant.now()))
+                .expirationTime(Date.from(Instant.now().plusSeconds(60))).build();
+        ResponseType rt = new ResponseType();
+        rt.add(ResponseType.Value.CODE);
+        rt.add(ResponseType.Value.TOKEN);
+        AuthenticationRequest req = new AuthenticationRequest.Builder(rt, new Scope("openid"), new ClientID("000123"),
+                URI.create("https://example.com/callback")).requestObject(new PlainJWT(ro)).nonce(new Nonce())
+                        .state(new State()).build();
+        prc.getInboundMessageContext().setMessage(req);
+        oidcRespCtx.setRequestObject(req.getRequestObject());
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
     /**
      * Test success case of RS256 signed request object.
      */
@@ -232,7 +316,29 @@ public class ValidateRequestObjectTest {
     public void testRequestObjectSignedWithRS256() throws NoSuchAlgorithmException, ComponentInitializationException,
             URISyntaxException, JOSEException, InvalidAlgorithmParameterException {
         JWSSigner signer = new RSASSASigner(kp.getPrivate());
-        JWTClaimsSet ro = new JWTClaimsSet.Builder().subject("alice").build();
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .issuer("000123")
+                .audience(issuer).build();
+        SignedJWT signed = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), ro);
+        signed.sign(signer);
+        AuthenticationRequest req =
+                new AuthenticationRequest.Builder(new ResponseType("code"), new Scope("openid"), new ClientID("000123"),
+                        URI.create("https://example.com/callback")).requestObject(signed).state(new State()).build();
+        prc.getInboundMessageContext().setMessage(req);
+        oidcRespCtx.setRequestObject(req.getRequestObject());
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
+    @Test
+    public void testRequestObjectWithExpNbfSignedWithRS256() throws NoSuchAlgorithmException,
+            ComponentInitializationException, URISyntaxException, JOSEException, InvalidAlgorithmParameterException {
+        JWSSigner signer = new RSASSASigner(kp.getPrivate());
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .issuer("000123")
+                .audience(issuer)
+                .notBeforeTime(Date.from(Instant.now()))
+                .expirationTime(Date.from(Instant.now().plusSeconds(60))).build();
         SignedJWT signed = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), ro);
         signed.sign(signer);
         AuthenticationRequest req =
@@ -349,4 +455,148 @@ public class ValidateRequestObjectTest {
         ActionTestingSupport.assertEvent(event, EventIds.INVALID_SEC_CFG);
     }
 
+    @Test
+    public void testSignedExpiredRequestObject() throws NoSuchAlgorithmException, ComponentInitializationException,
+            URISyntaxException, JOSEException, InvalidAlgorithmParameterException {
+        JWSSigner signer = new RSASSASigner(kp.getPrivate());
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .issuer("000123")
+                .audience(issuer)
+                .expirationTime(Date.from(Instant.now().minus(Duration.ofMinutes(5))))
+                .build();
+        SignedJWT signed = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), ro);
+        signed.sign(signer);
+        AuthenticationRequest req =
+                new AuthenticationRequest.Builder(new ResponseType("code"), new Scope("openid"), new ClientID("000123"),
+                        URI.create("https://example.com/callback")).requestObject(signed).state(new State()).build();
+        prc.getInboundMessageContext().setMessage(req);
+        oidcRespCtx.setRequestObject(req.getRequestObject());
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_REQUEST_OBJECT);
+    }
+
+    @Test
+    public void testSignedNotBeforeInFutureRequestObject() throws NoSuchAlgorithmException, ComponentInitializationException,
+            URISyntaxException, JOSEException, InvalidAlgorithmParameterException {
+        JWSSigner signer = new RSASSASigner(kp.getPrivate());
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .issuer("000123")
+                .audience(issuer)
+                .notBeforeTime(Date.from(Instant.now().plus(Duration.ofMinutes(5))))
+                .build();
+        SignedJWT signed = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), ro);
+        signed.sign(signer);
+        AuthenticationRequest req =
+                new AuthenticationRequest.Builder(new ResponseType("code"), new Scope("openid"), new ClientID("000123"),
+                        URI.create("https://example.com/callback")).requestObject(signed).state(new State()).build();
+        prc.getInboundMessageContext().setMessage(req);
+        oidcRespCtx.setRequestObject(req.getRequestObject());
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_REQUEST_OBJECT);
+    }
+    
+    @Test
+    public void testSignedNoIssuerRequstObject() throws NoSuchAlgorithmException, ComponentInitializationException,
+            URISyntaxException, JOSEException, InvalidAlgorithmParameterException {
+        JWSSigner signer = new RSASSASigner(kp.getPrivate());
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .audience(issuer)
+                .build();
+        SignedJWT signed = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), ro);
+        signed.sign(signer);
+        AuthenticationRequest req =
+                new AuthenticationRequest.Builder(new ResponseType("code"), new Scope("openid"), new ClientID("000123"),
+                        URI.create("https://example.com/callback")).requestObject(signed).state(new State()).build();
+        prc.getInboundMessageContext().setMessage(req);
+        oidcRespCtx.setRequestObject(req.getRequestObject());
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_REQUEST_OBJECT);
+    }
+
+    @Test
+    public void testSignedWrongIssuerRequstObject() throws NoSuchAlgorithmException, ComponentInitializationException,
+            URISyntaxException, JOSEException, InvalidAlgorithmParameterException {
+        JWSSigner signer = new RSASSASigner(kp.getPrivate());
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .issuer("invalid")
+                .audience(issuer)
+                .build();
+        SignedJWT signed = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), ro);
+        signed.sign(signer);
+        AuthenticationRequest req =
+                new AuthenticationRequest.Builder(new ResponseType("code"), new Scope("openid"), new ClientID("000123"),
+                        URI.create("https://example.com/callback")).requestObject(signed).state(new State()).build();
+        prc.getInboundMessageContext().setMessage(req);
+        oidcRespCtx.setRequestObject(req.getRequestObject());
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_REQUEST_OBJECT);
+    }
+
+    @Test
+    public void testSignedNoAudienceRequstObject() throws NoSuchAlgorithmException, ComponentInitializationException,
+            URISyntaxException, JOSEException, InvalidAlgorithmParameterException {
+        JWSSigner signer = new RSASSASigner(kp.getPrivate());
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .issuer("000123")
+                .build();
+        SignedJWT signed = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), ro);
+        signed.sign(signer);
+        AuthenticationRequest req =
+                new AuthenticationRequest.Builder(new ResponseType("code"), new Scope("openid"), new ClientID("000123"),
+                        URI.create("https://example.com/callback")).requestObject(signed).state(new State()).build();
+        prc.getInboundMessageContext().setMessage(req);
+        oidcRespCtx.setRequestObject(req.getRequestObject());
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_REQUEST_OBJECT);
+    }
+
+    @Test
+    public void testSignedWrongAudienceRequstObject() throws NoSuchAlgorithmException, ComponentInitializationException,
+            URISyntaxException, JOSEException, InvalidAlgorithmParameterException {
+        JWSSigner signer = new RSASSASigner(kp.getPrivate());
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .issuer("000123")
+                .audience("invalid")
+                .build();
+        SignedJWT signed = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), ro);
+        signed.sign(signer);
+        AuthenticationRequest req =
+                new AuthenticationRequest.Builder(new ResponseType("code"), new Scope("openid"), new ClientID("000123"),
+                        URI.create("https://example.com/callback")).requestObject(signed).state(new State()).build();
+        prc.getInboundMessageContext().setMessage(req);
+        oidcRespCtx.setRequestObject(req.getRequestObject());
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_REQUEST_OBJECT);
+    }
+
+    @Test
+    public void testPlainExpiredRequestObject() throws NoSuchAlgorithmException, ComponentInitializationException,
+            URISyntaxException, JOSEException, InvalidAlgorithmParameterException {
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .expirationTime(Date.from(Instant.now().minus(Duration.ofMinutes(5))))
+                .build();
+        AuthenticationRequest req =
+                new AuthenticationRequest.Builder(new ResponseType("code"), new Scope("openid"), new ClientID("000123"),
+                        URI.create("https://example.com/callback")).requestObject(new PlainJWT(ro)).state(new State()).build();
+        prc.getInboundMessageContext().setMessage(req);
+        oidcRespCtx.setRequestObject(req.getRequestObject());
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_REQUEST_OBJECT);
+    }
+
+    @Test
+    public void testPlainNotBeforeInFutureRequestObject() throws NoSuchAlgorithmException, ComponentInitializationException,
+            URISyntaxException, JOSEException, InvalidAlgorithmParameterException {
+        JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .notBeforeTime(Date.from(Instant.now().plus(Duration.ofMinutes(5))))
+                .build();
+        AuthenticationRequest req =
+                new AuthenticationRequest.Builder(new ResponseType("code"), new Scope("openid"), new ClientID("000123"),
+                        URI.create("https://example.com/callback")).requestObject(new PlainJWT(ro)).state(new State()).build();
+        prc.getInboundMessageContext().setMessage(req);
+        oidcRespCtx.setRequestObject(req.getRequestObject());
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_REQUEST_OBJECT);
+    }
+
 }
\ No newline at end of file

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


More information about the commits mailing list