[java-idp-oidc] branch main updated: Add initial success/failure JWT introspection tests.

Scott Cantor cantor.2 at osu.edu
Mon Jan 31 23:37:36 UTC 2022


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=a9ef0e630244683cb875fdaeb2fe658e911f29cb

The following commit(s) were added to refs/heads/main by this push:
     new a9ef0e63 Add initial success/failure JWT introspection tests.
a9ef0e63 is described below

commit a9ef0e630244683cb875fdaeb2fe658e911f29cb
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Mon Jan 31 18:37:33 2022 -0500

    Add initial success/failure JWT introspection tests.
---
 .../op/profile/flow/AbstractOidcApiFlowTest.java   | 66 ++++++++++++++++++++++
 .../op/profile/flow/IntrospectionFlowTest.java     | 59 ++++++++++++++++---
 .../src/test/resources/conf/global.xml             |  4 ++
 3 files changed, 121 insertions(+), 8 deletions(-)

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 91bcb45a..3d1d10ad 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
@@ -20,8 +20,25 @@ package net.shibboleth.idp.plugin.oidc.op.profile.flow;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.interfaces.ECPrivateKey;
 import java.time.Instant;
+import java.util.Collection;
 
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+
+import com.nimbusds.jose.Algorithm;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSSigner;
+import com.nimbusds.jose.crypto.ECDSASigner;
+import com.nimbusds.jose.crypto.MACSigner;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.Scope;
 import com.nimbusds.oauth2.sdk.id.ClientID;
 import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
@@ -30,6 +47,8 @@ import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
 import net.shibboleth.idp.plugin.oidc.op.profile.impl.BaseOIDCResponseActionTest;
 import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
 import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+import net.shibboleth.oidc.security.credential.JWKCredential;
+import net.shibboleth.oidc.security.impl.CredentialConversionUtil;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.security.DataSealerException;
 
@@ -80,4 +99,51 @@ public class AbstractOidcApiFlowTest extends AbstractOidcFlowTest {
         return new BearerAccessToken(BaseOIDCResponseActionTest.initializeDataSealer().wrap(json,
                 Instant.now().plusSeconds(30)));
     }
+
+    protected BearerAccessToken buildJWTToken(final String clientId, final String subject, final Scope scope,
+            final Collection<String> audience, final PrivateKey key, final String alg) throws JOSEException {
+        final TokenClaimsSet claims = new AccessTokenClaimsSet.Builder()
+                .setJWTID(idGenerator)
+                .setClientID(new ClientID(clientId))
+                .setIssuer("https://op.example.org")
+                .setSubject(subject)
+                .setIssuedAt(Instant.now())
+                .setNotBefore(Instant.now().minusSeconds(300))
+                .setExpiresAt(Instant.now().plusSeconds(1800))
+                .setAuthenticationTime(Instant.now())
+                .setScope(scope)
+                .setAudience(audience)
+                .build();
+        if (key != null) {
+            final JWSAlgorithm jwsAlgorithm = new JWSAlgorithm(alg);
+            final JWSSigner signer = getSigner(key, jwsAlgorithm);
+            final JWSHeader.Builder headerBuilder = new JWSHeader.Builder(jwsAlgorithm);
+            final SignedJWT jwt = new SignedJWT(headerBuilder.build(), claims.getClaimsSet());
+            jwt.sign(signer);
+            return new BearerAccessToken(jwt.serialize());
+        } else {
+            return new BearerAccessToken(new PlainJWT(claims.getClaimsSet()).serialize());
+        }
+    }
+
+    /**
+     * Returns correct implementation of signer based on algorithm type.
+     * 
+     * @param key signing key
+     * @param jwsAlgorithm JWS algorithm
+     * 
+     * @return signer for algorithm and private key
+     * 
+     * @throws JOSEException if algorithm cannot be supported
+     */
+    private JWSSigner getSigner(final PrivateKey key, final Algorithm jwsAlgorithm) throws JOSEException {
+        if (JWSAlgorithm.Family.EC.contains(jwsAlgorithm)) {
+            return new ECDSASigner((ECPrivateKey) key);
+        }
+        if (JWSAlgorithm.Family.RSA.contains(jwsAlgorithm)) {
+            return new RSASSASigner(key);
+        }
+        throw new JOSEException("Unsupported algorithm " + jwsAlgorithm.getName());
+    }
+    
 }
\ No newline at end of file
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 a93d71b4..d8d53200 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
@@ -32,6 +32,7 @@ import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.OAuth2Error;
@@ -41,6 +42,9 @@ import com.nimbusds.oauth2.sdk.TokenIntrospectionSuccessResponse;
 import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
 import com.nimbusds.oauth2.sdk.id.Audience;
 
+import net.shibboleth.idp.plugin.oidc.op.profile.spring.factory.BasicJWKCredentialFactoryBean;
+import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+import net.shibboleth.oidc.security.credential.JWKCredential;
 import net.shibboleth.utilities.java.support.collection.Pair;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.security.DataSealerException;
@@ -61,6 +65,10 @@ public class IntrospectionFlowTest extends AbstractOidcClientAuthenticationFlowT
 
     private Scope scope = Scope.parse("openid profile email");
     
+    @Autowired
+    @Qualifier("testbed.DefaultRSSigningCredential")
+    private JWKCredential signingKey = null;
+    
     @Autowired
     @Qualifier("shibboleth.StorageService")
     private StorageService storageService;
@@ -79,7 +87,7 @@ public class IntrospectionFlowTest extends AbstractOidcClientAuthenticationFlowT
             ComponentInitializationException {
         setBasicAuth(clientId, clientSecret);
         setHttpFormRequest("POST", Collections.singletonMap("token",
-                super.buildToken(clientId, "sub", Scope.parse("openid")).toJSONObject().getAsString("access_token")));
+                buildToken(clientId, "sub", Scope.parse("openid")).toJSONObject().getAsString("access_token")));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertErrorCode(result, OAuth2Error.UNAUTHORIZED_CLIENT_CODE);
     }
@@ -90,7 +98,7 @@ public class IntrospectionFlowTest extends AbstractOidcClientAuthenticationFlowT
         storeMetadata(storageService, clientId, clientSecret, scope);
         setBasicAuth(clientId, clientSecret);
         setHttpFormRequest("POST", Collections.singletonMap("token_not",
-                super.buildToken(clientId, "sub", Scope.parse("openid")).toJSONObject().getAsString("access_token")));
+                buildToken(clientId, "sub", Scope.parse("openid")).toJSONObject().getAsString("access_token")));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
     }
@@ -102,7 +110,7 @@ public class IntrospectionFlowTest extends AbstractOidcClientAuthenticationFlowT
         setBasicAuth(clientId, clientSecret);
         setHttpFormRequest("POST", Map.of(
                 "token",
-                super.buildToken(clientId, "sub", Scope.parse("openid")).toJSONObject().getAsString("access_token"),
+                buildToken(clientId, "sub", Scope.parse("openid")).toJSONObject().getAsString("access_token"),
                 "token_type",
                 "access_token"));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
@@ -119,7 +127,7 @@ public class IntrospectionFlowTest extends AbstractOidcClientAuthenticationFlowT
             ComponentInitializationException {
         setBasicAuth(clientIdSaml, clientSecretSaml);
         setHttpFormRequest("POST", Collections.singletonMap("token",
-                super.buildToken(clientIdSaml, "sub", Scope.parse("openid")).toJSONObject().getAsString("access_token")));
+                buildToken(clientIdSaml, "sub", Scope.parse("openid")).toJSONObject().getAsString("access_token")));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         final TokenIntrospectionSuccessResponse resp =
                 parseSuccessResponse(result, TokenIntrospectionSuccessResponse.class);
@@ -133,7 +141,7 @@ public class IntrospectionFlowTest extends AbstractOidcClientAuthenticationFlowT
         storeMetadata(storageService, clientId, clientSecret, scope);
         setBasicAuth(clientId, clientSecret);
         setHttpFormRequest("POST", Collections.singletonMap("token",
-                super.buildLegacyToken(clientId, "sub",
+                buildLegacyToken(clientId, "sub",
                         Scope.parse("openid")).toJSONObject().getAsString("access_token")));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         final TokenIntrospectionSuccessResponse resp =
@@ -148,7 +156,7 @@ public class IntrospectionFlowTest extends AbstractOidcClientAuthenticationFlowT
         storeMetadata(storageService, clientId, clientSecret, scope);
         setBasicAuth(clientId, clientSecret);
         setHttpFormRequest("POST", Collections.singletonMap("token",
-                super.buildLegacyToken(clientId, "sub", Scope.parse("openid"),
+                buildLegacyToken(clientId, "sub", Scope.parse("openid"),
                         "mail").toJSONObject().getAsString("access_token")));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         final TokenIntrospectionSuccessResponse resp =
@@ -157,6 +165,41 @@ public class IntrospectionFlowTest extends AbstractOidcClientAuthenticationFlowT
         Assert.assertTrue(resp.isActive());
     }
 
+    @Test
+    public void testSuccessJWT() throws JOSEException, IOException {
+        storeMetadata(storageService, clientId, clientSecret, scope);
+        setBasicAuth(clientId, clientSecret);
+        setHttpFormRequest("POST", Map.of(
+                "token",
+                buildJWTToken(clientId, "sub", scope, null, signingKey.getPrivateKey(), "RS256").toJSONObject()
+                    .getAsString("access_token"),
+                "token_type",
+                "access_token"));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final TokenIntrospectionSuccessResponse resp =
+                parseSuccessResponse(result, TokenIntrospectionSuccessResponse.class);
+        Assert.assertTrue(resp.isActive());
+        Assert.assertEquals(resp.getClientID().getValue(), clientId);
+        Assert.assertEquals(resp.getScope(), scope);
+        Assert.assertNull(resp.getAudience());
+    }
+
+    @Test
+    public void testFailureJWTWrongKey() throws JOSEException, IOException {
+        storeMetadata(storageService, clientId, clientSecret, scope);
+        setBasicAuth(clientId, clientSecret);
+        setHttpFormRequest("POST", Map.of(
+                "token",
+                buildJWTToken(clientId, "sub", scope, null, rsaPrivateKey, "RS256").toJSONObject()
+                    .getAsString("access_token"),
+                "token_type",
+                "access_token"));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final TokenIntrospectionSuccessResponse resp =
+                parseSuccessResponse(result, TokenIntrospectionSuccessResponse.class);
+        Assert.assertFalse(resp.isActive());
+    }
+
     @Test
     public void testUnidentifiedToken() throws IOException, NoSuchAlgorithmException, URISyntaxException,
             DataSealerException, ComponentInitializationException {
@@ -176,7 +219,7 @@ public class IntrospectionFlowTest extends AbstractOidcClientAuthenticationFlowT
         storeMetadata(storageService, clientId, clientSecret, scope);
         setBasicAuth(clientId, clientSecret + "X");
         setHttpFormRequest("POST", Collections.singletonMap("token",
-                super.buildToken(clientId, "sub", Scope.parse("openid")).toJSONObject().getAsString("access_token")));
+                buildToken(clientId, "sub", Scope.parse("openid")).toJSONObject().getAsString("access_token")));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         final TokenIntrospectionErrorResponse resp = (TokenIntrospectionErrorResponse) parseErrorResponse(result);
         Assert.assertEquals(resp.getErrorObject().getCode(), OAuth2Error.INVALID_CLIENT_CODE);
@@ -190,7 +233,7 @@ public class IntrospectionFlowTest extends AbstractOidcClientAuthenticationFlowT
             storeMetadata(storageService, clientId, null, scope, algorithm, method, null, rsaPublicKey);
         }
         final String accessToken = 
-                super.buildToken(clientId, "sub", Scope.parse("openid")).toJSONObject().getAsString("access_token");
+                buildToken(clientId, "sub", Scope.parse("openid")).toJSONObject().getAsString("access_token");
         final Map<String, String> requestParameters = createRequestParameters(accessToken, clientId);
         populateClientAssertionParams(requestParameters, jwt);
         setHttpFormRequest("POST", requestParameters);
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/global.xml b/idp-oidc-extension-impl/src/test/resources/conf/global.xml
index 94970221..a6efaae1 100644
--- a/idp-oidc-extension-impl/src/test/resources/conf/global.xml
+++ b/idp-oidc-extension-impl/src/test/resources/conf/global.xml
@@ -31,4 +31,8 @@
         <value>%{idp.home}/conf/metadata-filters.xml</value>
     </util:list>
     
+    <!-- Copy of IdP signing key for tests. -->
+    <bean id="testbed.DefaultRSSigningCredential" parent="shibboleth.JWKCredential"
+        p:resource="%{idp.signing.oidc.rs.key}" />
+    
 </beans>

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


More information about the commits mailing list