[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