[java-idp-oidc] branch main updated: JCOMOIDC-41 - Move OIDC Signature Validation resolvers and parameter classes to commons
Henri Mikkonen
henri.mikkonen at iki.fi
Wed Mar 1 14:56:02 UTC 2023
This is an automated email from the git hooks/post-receive script.
hjmikkon 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=e7dfec5ec309825132240fb9ddf8e32b7ad2db6a
The following commit(s) were added to refs/heads/main by this push:
new e7dfec5e JCOMOIDC-41 - Move OIDC Signature Validation resolvers and parameter classes to commons
e7dfec5e is described below
commit e7dfec5ec309825132240fb9ddf8e32b7ad2db6a
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Wed Mar 1 16:54:31 2023 +0200
JCOMOIDC-41 - Move OIDC Signature Validation resolvers and parameter classes to commons
https://shibboleth.atlassian.net/browse/JCOMOIDC-41
Improved the signature signing configuration wiring in authorize, token and userinfo flows.
Improved flow tests for verifying that the issued JWT signature matches the expected configuration.
---
.../idp/flows/oidc/authorize/authorize-beans.xml | 24 +-
.../idp/flows/oidc/token/token-beans.xml | 57 ++-
.../idp/flows/oidc/userinfo/userinfo-beans.xml | 11 +-
.../op/profile/flow/AbstractOidcApiFlowTest.java | 32 --
.../AbstractOidcClientAuthenticationFlowTest.java | 14 +-
.../oidc/op/profile/flow/AbstractOidcFlowTest.java | 165 ++++++---
.../oidc/op/profile/flow/AuthorizeFlowTest.java | 19 +
.../op/profile/flow/IssuedJWTSignatureTest.java | 404 +++++++++++++++++++++
.../plugin/oidc/op/profile/flow/TokenFlowTest.java | 62 +++-
.../plugin/oidc/op/profile/flow/UserInfoTest.java | 15 +-
.../src/test/resources/conf/relying-party.xml | 10 +-
11 files changed, 703 insertions(+), 110 deletions(-)
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 59816c26..d94d50eb 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
@@ -508,7 +508,7 @@
<bean id="PopulateThirdPartyAccessTokenSignatureSigningParameters"
class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureSigningParameters"
- scope="prototype"
+ scope="prototype" p:noResultIsError="true"
p:securityParametersContextLookupStrategy-ref="AudienceSecurityParametersCreationStrategy"
c:strategy-ref="shibboleth.MessageContextLookup.Outbound">
<property name="configurationLookupStrategy">
@@ -517,7 +517,14 @@
p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
</property>
<property name="signatureSigningParametersResolver">
- <bean class="net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningParametersResolver" />
+ <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
+ <constructor-arg name="signatureAlgorithmLookupStrategy">
+ <bean
+ class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
+ c:keyName="id_token_signed_response_alg" />
+ </constructor-arg>
+ <constructor-arg name="defaultAlgorithmValue" value="RS256" />
+ </bean>
</property>
</bean>
@@ -564,7 +571,7 @@
p:typeHeader="at+jwt" />
-->
<bean id="SignAccessToken" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
- scope="prototype" c:executionDirection="OUTBOUND ">
+ scope="prototype" c:executionDirection="OUTBOUND">
<constructor-arg name="messageHandler">
<bean id="SignAccessTokenHandler"
class="net.shibboleth.oidc.security.impl.SignJWTHandler" scope="prototype" p:logName="Access Token"
@@ -585,14 +592,21 @@
<bean id="PopulateIDTokenSignatureSigningParameters"
class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureSigningParameters" scope="prototype"
- c:strategy-ref="shibboleth.MessageContextLookup.Outbound">
+ c:strategy-ref="shibboleth.MessageContextLookup.Inbound" p:noResultIsError="true">
<property name="configurationLookupStrategy">
<bean lazy-init="true"
class="net.shibboleth.oidc.profile.config.navigate.JWTSignatureSigningConfigurationLookupFunction"
p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
</property>
<property name="signatureSigningParametersResolver">
- <bean class="net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningParametersResolver" />
+ <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
+ <constructor-arg name="signatureAlgorithmLookupStrategy">
+ <bean
+ class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
+ c:keyName="id_token_signed_response_alg" />
+ </constructor-arg>
+ <constructor-arg name="defaultAlgorithmValue" value="RS256" />
+ </bean>
</property>
</bean>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
index 11b5e3c3..579f0957 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
@@ -309,7 +309,14 @@
p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
</property>
<property name="signatureSigningParametersResolver">
- <bean class="net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningParametersResolver" />
+ <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
+ <constructor-arg name="signatureAlgorithmLookupStrategy">
+ <bean
+ class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
+ c:keyName="id_token_signed_response_alg" />
+ </constructor-arg>
+ <constructor-arg name="defaultAlgorithmValue" value="RS256" />
+ </bean>
</property>
<property name="securityParametersContextLookupStrategy">
<bean parent="shibboleth.Functions.Compose"
@@ -323,11 +330,23 @@
p:dataSealer="#{getObject('%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim())}" />
<bean id="SignOIDCAccessToken" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
- scope="prototype" c:executionDirection="OUTBOUND ">
+ scope="prototype" c:executionDirection="OUTBOUND">
<constructor-arg name="messageHandler">
<bean id="SignOIDCAccessTokenHandler"
class="net.shibboleth.oidc.security.impl.SignJWTHandler" scope="prototype" p:logName="Access Token"
p:typeHeader="at+jwt">
+ <property name="securityParametersLookupStrategy">
+ <bean parent="shibboleth.Functions.Compose">
+ <constructor-arg name="g">
+ <bean parent="shibboleth.Functions.Compose"
+ c:g-ref="shibboleth.oidc.ChildLookupOrCreate.JWTSecurityParameters"
+ c:f-ref="shibboleth.ChildLookup.RelyingParty" />
+ </constructor-arg>
+ <constructor-arg name="f">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input.getParent()" />
+ </constructor-arg>
+ </bean>
+ </property>
<property name="claimsToSignLookupStrategy">
<bean
class="net.shibboleth.idp.plugin.oidc.op.messaging.context.logic.JWTClaimsSetFromJWTAccessTokenLookupFunction" />
@@ -370,15 +389,22 @@
<bean id="PopulateIDTokenSignatureSigningParameters"
class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureSigningParameters"
- scope="prototype"
- c:strategy-ref="shibboleth.MessageContextLookup.Outbound">
+ scope="prototype" p:noResultIsError="true"
+ c:strategy-ref="shibboleth.MessageContextLookup.Inbound">
<property name="configurationLookupStrategy">
<bean lazy-init="true"
class="net.shibboleth.oidc.profile.config.navigate.JWTSignatureSigningConfigurationLookupFunction"
p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
</property>
<property name="signatureSigningParametersResolver">
- <bean class="net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningParametersResolver" />
+ <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
+ <constructor-arg name="signatureAlgorithmLookupStrategy">
+ <bean
+ class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
+ c:keyName="id_token_signed_response_alg" />
+ </constructor-arg>
+ <constructor-arg name="defaultAlgorithmValue" value="RS256" />
+ </bean>
</property>
<property name="securityParametersContextLookupStrategy">
<bean parent="shibboleth.Functions.Compose"
@@ -462,7 +488,7 @@
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ManipulateClaimsForIDToken" scope="prototype" />
<bean id="SignIDToken" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
- scope="prototype" c:executionDirection="OUTBOUND ">
+ scope="prototype" c:executionDirection="OUTBOUND">
<constructor-arg name="messageHandler">
<bean id="SignIDTokenHandler"
class="net.shibboleth.oidc.security.impl.SignJWTHandler" scope="prototype" p:logName="ID Token">
@@ -512,16 +538,23 @@
<bean id="PopulateThirdPartyAccessTokenSignatureSigningParameters"
class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureSigningParameters"
- scope="prototype"
- c:strategy-ref="shibboleth.MessageContextLookup.Outbound"
- p:securityParametersContextLookupStrategy-ref="AudienceSecurityParametersCreationStrategy">
+ scope="prototype" p:noResultIsError="true"
+ p:securityParametersContextLookupStrategy-ref="AudienceSecurityParametersCreationStrategy"
+ c:strategy-ref="shibboleth.MessageContextLookup.Outbound">
<property name="configurationLookupStrategy">
<bean lazy-init="true"
class="net.shibboleth.oidc.profile.config.navigate.JWTSignatureSigningConfigurationLookupFunction"
- p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
+ p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver"/>
</property>
<property name="signatureSigningParametersResolver">
- <bean class="net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningParametersResolver" />
+ <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
+ <constructor-arg name="signatureAlgorithmLookupStrategy">
+ <bean
+ class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
+ c:keyName="id_token_signed_response_alg" />
+ </constructor-arg>
+ <constructor-arg name="defaultAlgorithmValue" value="RS256" />
+ </bean>
</property>
</bean>
@@ -611,7 +644,7 @@
p:relyingPartyContextLookupStrategy-ref="AudienceRelyingPartyCreationStrategy" />
<bean id="SignAccessToken" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
- scope="prototype" c:executionDirection="OUTBOUND ">
+ scope="prototype" c:executionDirection="OUTBOUND">
<constructor-arg name="messageHandler">
<bean id="SignAccessTokenHandler"
class="net.shibboleth.oidc.security.impl.SignJWTHandler" scope="prototype" p:logName="Access Token"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
index 129653a4..5b4ac97c 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
@@ -79,7 +79,7 @@
<bean id="PopulateUserInfoResponseSignatureSigningParameters"
class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureSigningParameters" scope="prototype"
- c:strategy-ref="shibboleth.MessageContextLookup.Outbound"
+ c:strategy-ref="shibboleth.MessageContextLookup.Inbound"
p:configurationLookupStrategy-ref="JWTSignatureSigningConfigurationLookupFunction"
p:signatureSigningParametersResolver-ref="shibboleth.oidc.UserInfoSignatureSigningParametersResolver">
<property name="securityParametersContextLookupStrategy">
@@ -103,7 +103,14 @@
<bean id="shibboleth.oidc.UserInfoSignatureSigningParametersResolver"
- class="net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningParametersResolver" />
+ class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
+ <constructor-arg name="signatureAlgorithmLookupStrategy">
+ <bean
+ class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
+ c:keyName="userinfo_signed_response_alg" />
+ </constructor-arg>
+ <constructor-arg name="defaultAlgorithmValue" value="" />
+ </bean>
<bean id="PopulateUserInfoResponseEncryptionParameters"
class="net.shibboleth.oidc.profile.impl.PopulateJWTEncryptionParameters" scope="prototype"
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 1b09f786..268f4f6a 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
@@ -55,11 +55,6 @@ public class AbstractOidcApiFlowTest extends AbstractOidcFlowTest {
protected AbstractOidcApiFlowTest(final String flowId) {
super(flowId);
}
-
- protected BearerAccessToken buildToken(final String clientId, final String subject, final Scope scope)
- throws URISyntaxException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
- return buildToken(clientId, subject, scope, null);
- }
protected BearerAccessToken buildLegacyToken(final String clientId, final String subject, final Scope scope,
String... consentedClaims)
@@ -67,33 +62,6 @@ public class AbstractOidcApiFlowTest extends AbstractOidcFlowTest {
return buildLegacyToken(clientId, subject, scope, null, consentedClaims);
}
- protected BearerAccessToken buildToken(final String clientId, final String subject, final Scope scope,
- final ClaimsSet userInfoDeliverySet)
- throws URISyntaxException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
- return buildToken(clientId, subject, scope, userInfoDeliverySet, null, null);
- }
-
- protected BearerAccessToken buildToken(final String clientId, final String subject, final Scope scope,
- final ClaimsSet userInfoDeliverySet, final String id, final String rootId)
- throws URISyntaxException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
- final String jti = id == null ? idGenerator.generateIdentifier() : id;
- final TokenClaimsSet claims = new AccessTokenClaimsSet.Builder()
- .setJWTID(jti)
- .setClientID(new ClientID(clientId))
- .setIssuer("https://op.example.org")
- .setPrincipal("jdoe")
- .setSubject(subject)
- .setIssuedAt(Instant.now())
- .setExpiresAt(Instant.now().plusSeconds(30))
- .setAuthenticationTime(Instant.now())
- .setRedirectURI(new URI("https://example.org/cb"))
- .setScope(scope)
- .setDlClaimsUI(userInfoDeliverySet)
- .setRootTokenIdentifier(rootId)
- .build();
- return new BearerAccessToken(claims.serialize(BaseOIDCResponseActionTest.initializeDataSealer()));
- }
-
protected RefreshToken buildRefreshToken(final String clientId, final String subject, final Scope scope,
final ClaimsSet userInfoDeliverySet, final String id, final String rootId)
throws URISyntaxException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
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 305c6fd5..f71f2aeb 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
@@ -601,16 +601,24 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
}
protected ClientSecretJWT buildSecretJwtAuth(String secret) throws JOSEException, URISyntaxException {
- return new ClientSecretJWT(new ClientID(clientId), new URI(jwtAud),
+ return buildSecretJwtAuth(clientId, secret);
+ }
+
+ protected ClientSecretJWT buildSecretJwtAuth(final String id, String secret) throws JOSEException, URISyntaxException {
+ return buildSecretJwtAuth(id, secret, jwtAud);
+ }
+
+ protected static ClientSecretJWT buildSecretJwtAuth(final String id, String secret, final String jwtAud) throws JOSEException, URISyntaxException {
+ return new ClientSecretJWT(new ClientID(id), new URI(jwtAud),
JWSAlgorithm.HS256, new Secret(secret));
}
-
+
protected PrivateKeyJWT buildPrivateKeyJwtAuth() throws JOSEException, URISyntaxException {
return new PrivateKeyJWT(new ClientID(clientId), new URI(jwtAud),
JWSAlgorithm.RS256, (PrivateKey) rsaPrivateKey, null, null);
}
- protected void populateClientAssertionParams(final Map<String, String> requestParameters,
+ protected static void populateClientAssertionParams(final Map<String, String> requestParameters,
final JWTAuthentication clientAuth) {
requestParameters.put("client_assertion", clientAuth.getClientAssertion().serialize());
requestParameters.put("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
index 9ce472df..3de58353 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
@@ -80,15 +80,22 @@ import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
import com.nimbusds.oauth2.sdk.auth.Secret;
import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+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.idp.test.flows.AbstractFlowTest;
import net.shibboleth.oidc.metadata.impl.BaseStorageServiceClientInformationComponent;
import net.shibboleth.oidc.security.credential.BasicJWKCredential;
import net.shibboleth.oidc.security.credential.impl.BasicJWKCredentialFactoryBean;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.net.HttpServletRequestResponseContext;
import net.shibboleth.utilities.java.support.security.DataSealer;
+import net.shibboleth.utilities.java.support.security.DataSealerException;
/**
* Abstract unit test for the OIDC flows.
@@ -98,14 +105,18 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
public static final String END_STATE_ID = "CommitResponse";
- private String flowId;
+ protected String flowId;
- private String endStateId;
+ protected String endStateId;
@Autowired
@Qualifier("shibboleth.oidc.TokenSealer")
private DataSealer dataSealer;
+ @Autowired
+ @Qualifier("shibboleth.StorageService")
+ StorageService storageService;
+
RSAPrivateKey rsaPrivateKey;
RSAPublicKey rsaPublicKey;
@@ -213,10 +224,14 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
}
protected void setHttpFormRequest(final String method, final Map<String, String> parameters) {
- setRequest(method, "", "application/x-www-form-urlencoded");
+ setHttpFormRequest(request, method, parameters);
+ }
+
+ protected static void setHttpFormRequest(final MockHttpServletRequest request, final String method, final Map<String, String> parameters) {
+ setRequest(request, method, "", "application/x-www-form-urlencoded");
request.setParameters(parameters);
}
-
+
protected void setBasicAuth(final String username, final String password) {
request.removeHeader("Authorization");
request.addHeader("Authorization",
@@ -224,11 +239,15 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
}
protected void setRequest(final String method, final String body, final String contentType) {
+ setRequest(request, method, body, contentType);
+ }
+
+ protected static void setRequest(final MockHttpServletRequest request, final String method, final String body, final String contentType) {
request.setMethod(method);
request.setContentType(contentType);
request.setContent(body.getBytes());
}
-
+
protected void storeMetadata(final StorageService storageService, final String clientId, final String secret,
final Scope scope, final String... redirectUri) throws IOException {
storeMetadata(storageService, clientId, secret, scope, null, ClientAuthenticationMethod.CLIENT_SECRET_BASIC,
@@ -317,9 +336,7 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
final EncryptionMethod requestObjectEncMethod, final String... redirectUri)
throws IOException {
- final OIDCClientMetadata metadata = new OIDCClientMetadata();
- metadata.setGrantTypes(new HashSet<GrantType>(List.of(GrantType.AUTHORIZATION_CODE,
- GrantType.REFRESH_TOKEN, GrantType.CLIENT_CREDENTIALS)));
+ final OIDCClientMetadata metadata = buildMetadataSkeleton();
final HashSet<URI> uris = new HashSet<>();
if (redirectUri != null) {
for (final String uri : redirectUri) {
@@ -330,16 +347,6 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
}
}
}
- final HashSet<ResponseType> responseTypes = new HashSet<>();
- responseTypes.add(new ResponseType("code"));
- // implicit flows
- responseTypes.add(new ResponseType("id_token"));
- responseTypes.add(new ResponseType("id_token", "token"));
- // hybrid flows
- responseTypes.add(new ResponseType("code", "id_token"));
- responseTypes.add(new ResponseType("code", "token"));
- responseTypes.add(new ResponseType("code", "id_token", "token"));
- metadata.setResponseTypes(responseTypes);
metadata.setRedirectionURIs(uris);
metadata.setScope(scope);
metadata.setTokenEndpointAuthJWSAlg(tokenEndpointSigAlg);
@@ -354,34 +361,56 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
metadata.setCustomField("audience", List.of("https://rp.example.org", "https://rp2.example.org",
"https://resource.example.org"));
}
- final OIDCClientInformation information;
- if (publicKey == null) {
- information = new OIDCClientInformation(new ClientID(clientId), new Date(),
- metadata, secret != null ? new Secret(secret) : null);
- } else {
- final JWKSet jwkSet;
- if (publicKey instanceof RSAPublicKey) {
- final RSAKey rsaKey = new RSAKey.Builder((RSAPublicKey) publicKey).build();
- jwkSet = new JWKSet(rsaKey);
- } else if (publicKey instanceof ECPublicKey) {
- final ECPublicKey ecPublicKey = (ECPublicKey) publicKey;
- final ECKey ecKey = new ECKey.Builder(Curve.forECParameterSpec(ecPublicKey.getParams()),
- ecPublicKey).build();
- jwkSet = new JWKSet(ecKey);
- } else {
- Assert.fail();
- return;
- }
- metadata.setJWKSet(jwkSet);
- information = new OIDCClientInformation(new ClientID(clientId), new Date(),
- metadata, secret != null ? new Secret(secret) : null);
+ if (publicKey != null) {
+ metadata.setJWKSet(buildJWKSet(publicKey));
}
+ storeMetadataObject(storageService, clientId, secret, metadata);
+ }
+
+// Checkstyle: ParameterNumber ON
+
+ protected static void storeMetadataObject(final StorageService storageService, final String clientId, final String secret,
+ final OIDCClientMetadata metadata) throws IOException {
+
+ metadata.setGrantTypes(new HashSet<GrantType>(List.of(GrantType.AUTHORIZATION_CODE,
+ GrantType.REFRESH_TOKEN, GrantType.CLIENT_CREDENTIALS)));
+ final OIDCClientInformation information = new OIDCClientInformation(new ClientID(clientId), new Date(),
+ metadata, secret != null ? new Secret(secret) : null);
storageService.create(BaseStorageServiceClientInformationComponent.CONTEXT_NAME, clientId,
information.toJSONObject().toJSONString(), System.currentTimeMillis() + (60 * 60 * 1000));
-
}
-// Checkstyle: ParameterNumber ON
-
+
+ protected static OIDCClientMetadata buildMetadataSkeleton() {
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setGrantTypes(new HashSet<GrantType>(List.of(GrantType.AUTHORIZATION_CODE,
+ GrantType.REFRESH_TOKEN, GrantType.CLIENT_CREDENTIALS)));
+ final HashSet<ResponseType> responseTypes = new HashSet<>();
+ responseTypes.add(new ResponseType("code"));
+ // implicit flows
+ responseTypes.add(new ResponseType("id_token"));
+ responseTypes.add(new ResponseType("id_token", "token"));
+ // hybrid flows
+ responseTypes.add(new ResponseType("code", "id_token"));
+ responseTypes.add(new ResponseType("code", "token"));
+ responseTypes.add(new ResponseType("code", "id_token", "token"));
+ metadata.setResponseTypes(responseTypes);
+ return metadata;
+ }
+
+ protected static JWKSet buildJWKSet(final PublicKey publicKey) {
+ if (publicKey instanceof RSAPublicKey) {
+ final RSAKey rsaKey = new RSAKey.Builder((RSAPublicKey) publicKey).build();
+ return new JWKSet(rsaKey);
+ } else if (publicKey instanceof ECPublicKey) {
+ final ECPublicKey ecPublicKey = (ECPublicKey) publicKey;
+ final ECKey ecKey = new ECKey.Builder(Curve.forECParameterSpec(ecPublicKey.getParams()),
+ ecPublicKey).build();
+ return new JWKSet(ecKey);
+ } else {
+ Assert.fail();
+ }
+ return null;
+ }
protected void removeMetadata(final StorageService storageService, final String clientId) throws IOException {
storageService.delete(BaseStorageServiceClientInformationComponent.CONTEXT_NAME, clientId);
}
@@ -483,10 +512,26 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
}
protected static BasicJWKCredential loadEncryptionCredential() {
- return loadEncryptionCredential("/credentials/idp-encryption-rsa.jwk");
+ return loadCredential("/credentials/idp-encryption-rsa.jwk");
+ }
+
+ protected static BasicJWKCredential loadRSSigningCredential() {
+ return loadCredential("/credentials/idp-signing-rs.jwk");
}
- protected static BasicJWKCredential loadEncryptionCredential(final String classPathLocation) {
+ protected static BasicJWKCredential loadESSigningCredential() {
+ return loadCredential("/credentials/idp-signing-es.jwk");
+ }
+
+ protected static BasicJWKCredential loadES384SigningCredential() {
+ return loadCredential("/credentials/idp-signing-es-384.jwk");
+ }
+
+ protected static BasicJWKCredential loadES512SigningCredential() {
+ return loadCredential("/credentials/idp-signing-es-521.jwk");
+ }
+
+ protected static BasicJWKCredential loadCredential(final String classPathLocation) {
final BasicJWKCredentialFactoryBean factory = new BasicJWKCredentialFactoryBean();
factory.setResource(new ClassPathResource(classPathLocation));
try {
@@ -498,4 +543,36 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
}
}
+ protected BearerAccessToken buildToken(final String clientId, final String subject, final Scope scope)
+ throws URISyntaxException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+ return buildToken(clientId, subject, scope, null);
+ }
+
+ protected BearerAccessToken buildToken(final String clientId, final String subject, final Scope scope,
+ final ClaimsSet userInfoDeliverySet)
+ throws URISyntaxException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+ return buildToken(clientId, subject, scope, userInfoDeliverySet, null, null);
+ }
+
+ protected BearerAccessToken buildToken(final String clientId, final String subject, final Scope scope,
+ final ClaimsSet userInfoDeliverySet, final String id, final String rootId)
+ throws URISyntaxException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+ final String jti = id == null ? idGenerator.generateIdentifier() : id;
+ final TokenClaimsSet claims = new AccessTokenClaimsSet.Builder()
+ .setJWTID(jti)
+ .setClientID(new ClientID(clientId))
+ .setIssuer("https://op.example.org")
+ .setPrincipal("jdoe")
+ .setSubject(subject)
+ .setIssuedAt(Instant.now())
+ .setExpiresAt(Instant.now().plusSeconds(30))
+ .setAuthenticationTime(Instant.now())
+ .setRedirectURI(new URI("https://example.org/cb"))
+ .setScope(scope)
+ .setDlClaimsUI(userInfoDeliverySet)
+ .setRootTokenIdentifier(rootId)
+ .build();
+ return new BearerAccessToken(claims.serialize(BaseOIDCResponseActionTest.initializeDataSealer()));
+ }
+
}
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 17fb4b7e..eeb7ffd2 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
@@ -33,10 +33,12 @@ import java.util.List;
import org.opensaml.storage.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.webflow.executor.FlowExecutionResult;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Factory;
import org.testng.annotations.Test;
import com.nimbusds.jose.EncryptionMethod;
@@ -1539,6 +1541,18 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
JWSAlgorithm.ES512, ecKey.toECPublicKey(), JWEAlgorithm.RSA_OAEP, EncryptionMethod.A128CBC_HS256);
}
+ @Factory
+ public Object[] createIdTokenSecurityTests() {
+ return new Object[] {
+ new IssuedJWTSignatureTest(IssuedJWTSignatureTest.JWT_FETCHING_TYPE.AUTHORIZE_ID_TOKEN, FLOW_ID) };
+ }
+
+ @Factory
+ public Object[] createAccessTokenSecurityTests() {
+ return new Object[] {
+ new IssuedJWTSignatureTest(IssuedJWTSignatureTest.JWT_FETCHING_TYPE.AUTHORIZE_ACCESS_TOKEN, FLOW_ID) };
+ }
+
protected String getRequestObjectWithClaimsRequestPayload() {
return "{\n"
+ " \"iss\": \"" + clientId + "\",\n"
@@ -1732,6 +1746,11 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
}
protected void setRequestParameters(final List<Pair<String, String>> pairs) {
+ setRequestParameters(request, pairs);
+ }
+
+ protected static void setRequestParameters(final MockHttpServletRequest request,
+ final List<Pair<String, String>> pairs) {
final StringBuffer query = new StringBuffer();
for (final Pair<String, String> pair : pairs) {
request.addParameter(pair.getFirst(), pair.getSecond());
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssuedJWTSignatureTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssuedJWTSignatureTest.java
new file mode 100644
index 00000000..31c79440
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssuedJWTSignatureTest.java
@@ -0,0 +1,404 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.profile.flow;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.text.ParseException;
+import java.util.List;
+
+import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSVerifier;
+import com.nimbusds.jose.crypto.ECDSAVerifier;
+import com.nimbusds.jose.crypto.RSASSAVerifier;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.AccessTokenResponse;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretJWT;
+import com.nimbusds.oauth2.sdk.token.AccessToken;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.security.DataSealerException;
+
+/**
+ * Tests for verifying that the signature on JWT issued by OP matches the expected configuration. OP may issue JWTs
+ * from authorize, token and userinfo endpoints, depending on the RP or resource metadata.
+ */
+public class IssuedJWTSignatureTest extends AbstractOidcFlowTest {
+
+ String defaultClientId = "mockClientId";
+ String defaultClientSecret =
+ "mockClientSecretmockClientSecretmockClientSecretmockClientSecretmockClientSecret";
+
+
+ public enum JWT_FETCHING_TYPE {
+
+ USERINFO,
+
+ TOKEN_ID_TOKEN,
+
+ TOKEN_ACCESS_TOKEN,
+
+ AUTHORIZE_ID_TOKEN,
+
+ AUTHORIZE_ACCESS_TOKEN,
+ }
+
+ private final JWT_FETCHING_TYPE fetchingType;
+
+ public IssuedJWTSignatureTest(final JWT_FETCHING_TYPE type, final String flowId) {
+ super(flowId);
+ fetchingType = type;
+ }
+
+ protected JWT obtainJwt(final JWSAlgorithm jwsAlgorithm, final JWEAlgorithm jweAlgorithm, final EncryptionMethod encryptionMethod) {
+ switch (fetchingType) {
+ case USERINFO:
+ return obtainUserInfoAsJwt(defaultClientId, jwsAlgorithm, jweAlgorithm, encryptionMethod);
+ case TOKEN_ID_TOKEN:
+ return obtainIdTokenFromTokenEndpoint(defaultClientId, jwsAlgorithm, jweAlgorithm, encryptionMethod);
+ case TOKEN_ACCESS_TOKEN:
+ return obtainJwtAccessTokenFromTokenEndpoint("mockClientIdJwtAccessToken", jwsAlgorithm, jweAlgorithm,
+ encryptionMethod);
+ case AUTHORIZE_ID_TOKEN:
+ return obtainIdTokenFromAuthorizeEndpoint(defaultClientId, jwsAlgorithm, jweAlgorithm,
+ encryptionMethod);
+ case AUTHORIZE_ACCESS_TOKEN:
+ return obtainAccessTokenFromAuthorizeEndpoint(defaultClientId, jwsAlgorithm, jweAlgorithm,
+ encryptionMethod);
+ default:
+ Assert.fail();
+ }
+ return null;
+ }
+
+ @Test
+ public void testJwtSecurity_jwtSigAlgAndEncNotSpecified() throws Exception {
+ if (fetchingType.equals(JWT_FETCHING_TYPE.USERINFO)) {
+ // No signing done on UserInfo by default
+ final UserInfoSuccessResponse response = obtainUserInfoResponse(defaultClientId, null, null, null);
+ Assert.assertNotNull(response.getUserInfo());
+ Assert.assertNull(response.getUserInfoJWT());
+ } else {
+ final JWT jwt = obtainJwt(null, null, null);
+ assertSignedJwt(jwt, JWSAlgorithm.RS256, loadRSSigningCredential().getPublicKey());
+ }
+ }
+
+ @Test
+ public void testJwtSecurity_jwtRS256SigAlgAndEncNotSpecified() throws Exception {
+ final JWT jwt = obtainJwt(JWSAlgorithm.RS256, null, null);
+ assertSignedJwt(jwt, JWSAlgorithm.RS256, loadRSSigningCredential().getPublicKey());
+ }
+
+ @Test
+ public void testJwtSecurity_jwtRS384SigAlgAndEncNotSpecified() throws Exception {
+ final JWT jwt = obtainJwt(JWSAlgorithm.RS384, null, null);
+ assertSignedJwt(jwt, JWSAlgorithm.RS384, loadRSSigningCredential().getPublicKey());
+ }
+
+ @Test
+ public void testJwtSecurity_jwtRS512SigAlgAndEncNotSpecified() throws Exception {
+ final JWT jwt = obtainJwt(JWSAlgorithm.RS512, null, null);
+ assertSignedJwt(jwt, JWSAlgorithm.RS512, loadRSSigningCredential().getPublicKey());
+ }
+
+ @Test
+ public void testJwtSecurity_jwtPS256SigAlgAndEncNotSpecified() throws Exception {
+ final JWT jwt = obtainJwt(JWSAlgorithm.PS256, null, null);
+ assertSignedJwt(jwt, JWSAlgorithm.PS256, loadRSSigningCredential().getPublicKey());
+ }
+
+ @Test
+ public void testJwtSecurity_jwtPS384SigAlgAndEncNotSpecified() throws Exception {
+ final JWT jwt = obtainJwt(JWSAlgorithm.PS384, null, null);
+ assertSignedJwt(jwt, JWSAlgorithm.PS384, loadRSSigningCredential().getPublicKey());
+ }
+
+ @Test
+ public void testJwtSecurity_jwtPS512SigAlgAndEncNotSpecified() throws Exception {
+ final JWT jwt = obtainJwt(JWSAlgorithm.PS512, null, null);
+ assertSignedJwt(jwt, JWSAlgorithm.PS512, loadRSSigningCredential().getPublicKey());
+ }
+
+ @Test
+ public void testJwtSecurity_jwtES256SigAlgAndEncNotSpecified() throws Exception {
+ final JWT jwt = obtainJwt(JWSAlgorithm.ES256, null, null);
+ assertSignedJwt(jwt, JWSAlgorithm.ES256, loadESSigningCredential().getPublicKey());
+ }
+
+ @Test
+ public void testJwtSecurity_jwtES384SigAlgAndEncNotSpecified() throws Exception {
+ // ES384 is globally excluded
+ if (fetchingType.equals(JWT_FETCHING_TYPE.USERINFO)) {
+ // UserInfo responds raw JSON when alg is disabled
+ final UserInfoSuccessResponse response = obtainUserInfoResponse(defaultClientId, JWSAlgorithm.ES384, null,
+ null);
+ Assert.assertNotNull(response.getUserInfo());
+ Assert.assertNull(response.getUserInfoJWT());
+ } else {
+ final JWT jwt = obtainJwt(JWSAlgorithm.ES384, null, null);
+ Assert.assertNull(jwt);
+ }
+ }
+
+ @Test
+ public void testJwtSecurity_jwtES512SigAlgAndEncNotSpecified() throws Exception {
+ final JWT jwt = obtainJwt(JWSAlgorithm.ES512, null, null);
+ assertSignedJwt(jwt, JWSAlgorithm.ES512, loadCredential("/credentials/idp-signing-es521.jwk").getPublicKey());
+ }
+
+ protected static void assertSignedJwt(final JWT jwt, final JWSAlgorithm algorithm, final PublicKey publicKey) {
+ Assert.assertTrue(SignedJWT.class.isInstance(jwt));
+ final SignedJWT signedJwt = (SignedJWT) jwt;
+ Assert.assertEquals(signedJwt.getHeader().getAlgorithm(), algorithm);
+ final JWSVerifier verifier;
+ try {
+ if (JWSAlgorithm.Family.RSA.contains(algorithm) && publicKey instanceof RSAPublicKey) {
+ verifier = new RSASSAVerifier((RSAPublicKey) publicKey);
+ } else if (JWSAlgorithm.Family.EC.contains(algorithm) && publicKey instanceof ECPublicKey) {
+ verifier = new ECDSAVerifier((ECPublicKey) publicKey);
+ } else {
+ Assert.fail();
+ return;
+ }
+ Assert.assertTrue(signedJwt.verify(verifier));
+ } catch (JOSEException e) {
+ Assert.fail();
+ }
+ }
+
+ public UserInfoSuccessResponse obtainUserInfoResponse(final String clientId, final JWSAlgorithm jwsAlgorithm, final JWEAlgorithm jweAlgorithm,
+ final EncryptionMethod encryptionMethod) {
+ request.setMethod("GET");
+ try {
+ removeMetadata(storageService, clientId);
+ } catch (final IOException e) {
+ Assert.fail();
+ }
+ BearerAccessToken token;
+ try {
+ token = buildToken(clientId, "mockSubject", new Scope("openid"));
+ } catch (final NoSuchAlgorithmException | URISyntaxException | DataSealerException
+ | ComponentInitializationException e) {
+ Assert.fail();
+ return null;
+ }
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setScope(new Scope("openid"));
+ metadata.setUserInfoJWSAlg(jwsAlgorithm);
+ metadata.setUserInfoJWEAlg(jweAlgorithm);
+ metadata.setUserInfoJWEEnc(encryptionMethod);
+ try {
+ storeMetadataObject(storageService, clientId, "mockSecret", metadata);
+ } catch (final IOException e) {
+ Assert.fail();
+ return null;
+ }
+ request.addHeader("Authorization", token.toAuthorizationHeader());
+ final FlowExecutionResult result = flowExecutor.launchExecution(flowId, null, externalContext);
+ try {
+ removeMetadata(storageService, clientId);
+ } catch (final IOException e) {
+ Assert.fail();
+ }
+ return parseSuccessResponse(result, UserInfoSuccessResponse.class);
+ }
+
+ public JWT obtainUserInfoAsJwt(final String clientId, final JWSAlgorithm jwsAlgorithm, final JWEAlgorithm jweAlgorithm,
+ final EncryptionMethod encryptionMethod) {
+ final UserInfoSuccessResponse response = obtainUserInfoResponse(clientId, jwsAlgorithm, jweAlgorithm, encryptionMethod);
+ Assert.assertNull(response.getUserInfo());
+ Assert.assertNotNull(response.getUserInfoJWT());
+ return response.getUserInfoJWT();
+ }
+
+ protected JWT obtainIdTokenFromTokenEndpoint(final String clientId, final JWSAlgorithm storedJwsAlgorithm, final JWEAlgorithm storedJweAlgorithm,
+ final EncryptionMethod storedJweMethod) {
+ try {
+ final ClientSecretJWT clientAuth = TokenFlowTest.buildSecretJwtAuth(clientId, defaultClientSecret, "http://localhost");
+ final OIDCClientMetadata metadata = buildMetadataSkeleton();
+ metadata.setScope(Scope.parse("openid profile email offline_access"));
+ metadata.setTokenEndpointAuthJWSAlg(JWSAlgorithm.HS256);
+ metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ metadata.setIDTokenJWEAlg(storedJweAlgorithm);
+ metadata.setIDTokenJWSAlg(storedJwsAlgorithm);
+ metadata.setIDTokenJWEEnc(storedJweMethod);
+ final FlowExecutionResult result = TokenFlowTest.launchWithJwtAuthentication(flowExecutor, clientAuth,
+ externalContext, request, "http://localhost", "openid", metadata, defaultClientSecret,
+ storageService);
+ removeMetadata(storageService, clientId);
+ if (parseResponse(result).indicatesSuccess()) {
+ final OIDCTokenResponse response = parseSuccessResponse(result, OIDCTokenResponse.class);
+ Assert.assertNotNull(response.getOIDCTokens().getIDToken());
+ return response.getOIDCTokens().getIDToken();
+ } else {
+ return null;
+ }
+ } catch (final Exception e) {
+ Assert.fail();
+ return null;
+ }
+ }
+
+ protected JWT obtainJwtAccessTokenFromTokenEndpoint(final String clientId, final JWSAlgorithm storedJwsAlgorithm,
+ final JWEAlgorithm storedJweAlgorithm, final EncryptionMethod storedJweMethod) {
+ try {
+ final ClientSecretJWT clientAuth = TokenFlowTest.buildSecretJwtAuth(clientId, defaultClientSecret, "http://localhost");
+ final OIDCClientMetadata metadata = buildMetadataSkeleton();
+ metadata.setScope(Scope.parse("openid profile email offline_access"));
+ metadata.setTokenEndpointAuthJWSAlg(JWSAlgorithm.HS256);
+ metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ metadata.setIDTokenJWEAlg(storedJweAlgorithm);
+ metadata.setIDTokenJWSAlg(storedJwsAlgorithm);
+ metadata.setIDTokenJWEEnc(storedJweMethod);
+ final FlowExecutionResult result = TokenFlowTest.launchWithJwtAuthentication(flowExecutor, clientAuth,
+ externalContext, request, "http://localhost", "openid", metadata, defaultClientSecret,
+ storageService);
+ removeMetadata(storageService, clientId);
+ if (parseResponse(result).indicatesSuccess()) {
+ final AccessTokenResponse response = parseSuccessResponse(result, AccessTokenResponse.class);
+ Assert.assertNotNull(response.getTokens().getAccessToken());
+ final AccessToken accessToken = response.getTokens().getAccessToken();
+ return SignedJWT.parse(accessToken.getValue());
+ } else {
+ return null;
+ }
+ } catch (final Exception e) {
+ Assert.fail(e.getMessage(), e);
+ return null;
+ }
+ }
+
+ protected JWT obtainIdTokenFromAuthorizeEndpoint(final String clientId, final JWSAlgorithm storedJwsAlgorithm,
+ final JWEAlgorithm storedJweAlgorithm, final EncryptionMethod storedJweMethod) {
+ setBasicAuth("jdoe", "changeit");
+ request.setMethod("GET");
+ final String redirectUri = "https://example.org/cb";
+ AuthorizeFlowTest.setRequestParameters(request, List.of(new Pair<>("client_id", clientId),
+ new Pair<>("response_type", "id_token"),
+ new Pair<>("scope", "openid profile"),
+ new Pair<>("redirect_uri", redirectUri),
+ new Pair<>("nonce", "idhas3h23hi13h1o2i32")));
+ final OIDCClientMetadata metadata = buildMetadataSkeleton();
+ metadata.setScope(Scope.parse("openid profile email offline_access"));
+ metadata.setIDTokenJWEAlg(storedJweAlgorithm);
+ metadata.setIDTokenJWSAlg(storedJwsAlgorithm);
+ metadata.setIDTokenJWEEnc(storedJweMethod);
+ try {
+ metadata.setRedirectionURI(new URI(redirectUri));
+ super.storeMetadataObject(storageService, clientId, defaultClientSecret, metadata);
+ } catch (final IOException | URISyntaxException e) {
+ Assert.fail(e.getMessage(), e);
+ }
+
+ initializeThreadLocals();
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(flowId, null, externalContext);
+ try {
+ super.removeMetadata(storageService, clientId);
+ } catch (final IOException e) {
+ Assert.fail(e.getMessage(), e);
+ }
+ if (parseResponse(result).indicatesSuccess()) {
+ final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+ final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+ Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+ Assert.assertNotNull(successResponse.getIDToken());
+ return successResponse.getIDToken();
+ }
+ return null;
+ }
+
+ protected JWT obtainAccessTokenFromAuthorizeEndpoint(final String clientId, final JWSAlgorithm storedJwsAlgorithm,
+ final JWEAlgorithm storedJweAlgorithm, final EncryptionMethod storedJweMethod) {
+ setBasicAuth("jdoe", "changeit");
+ request.setMethod("GET");
+ final String redirectUri = "https://example.org/cb";
+ final String resource = "https://mock.example.org";
+
+ AuthorizeFlowTest.setRequestParameters(request, List.of(new Pair<>("client_id", clientId),
+ new Pair<>("response_type", "id_token token"),
+ new Pair<>("scope", "openid profile"),
+ new Pair<>("redirect_uri", redirectUri),
+ new Pair<>("resource", resource),
+ new Pair<>("nonce", "idhas3h23hi13h1o2i32")));
+
+ final OIDCClientMetadata metadata = buildMetadataSkeleton();
+ metadata.setScope(Scope.parse("openid profile email offline_access"));
+ metadata.setCustomField("audience", List.of(resource));
+
+ final OIDCClientMetadata resourceMetadata = buildMetadataSkeleton();
+ resourceMetadata.setScope(Scope.parse("openid profile email offline_access"));
+ resourceMetadata.setIDTokenJWEAlg(storedJweAlgorithm);
+ resourceMetadata.setIDTokenJWSAlg(storedJwsAlgorithm);
+ resourceMetadata.setIDTokenJWEEnc(storedJweMethod);
+
+ try {
+ metadata.setRedirectionURI(new URI(redirectUri));
+ super.storeMetadataObject(storageService, clientId, defaultClientSecret, metadata);
+ super.storeMetadataObject(storageService, resource, defaultClientSecret, resourceMetadata);
+ } catch (final IOException | URISyntaxException e) {
+ Assert.fail(e.getMessage(), e);
+ }
+
+ initializeThreadLocals();
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(flowId, null, externalContext);
+ try {
+ super.removeMetadata(storageService, clientId);
+ super.removeMetadata(storageService, resource);
+ } catch (final IOException e) {
+ Assert.fail(e.getMessage(), e);
+ }
+ if (parseResponse(result).indicatesSuccess()) {
+ final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+ final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+ Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+ Assert.assertNotNull(successResponse.getAccessToken());
+ try {
+ return SignedJWT.parse(successResponse.getAccessToken().getValue());
+ } catch (final ParseException e) {
+ Assert.fail(e.getMessage(), e);
+ }
+ }
+ return null;
+ }
+
+}
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 ce1def17..77d12f5d 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
@@ -32,11 +32,17 @@ import org.opensaml.storage.RevocationCache;
import org.opensaml.storage.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.webflow.context.ExternalContext;
import org.springframework.webflow.executor.FlowExecutionResult;
+import org.springframework.webflow.executor.FlowExecutor;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Factory;
import org.testng.annotations.Test;
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jwt.JWT;
import com.nimbusds.oauth2.sdk.AccessTokenResponse;
@@ -55,6 +61,7 @@ import com.nimbusds.oauth2.sdk.token.RefreshToken;
import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
import net.minidev.json.JSONObject;
import net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateGrantTest;
@@ -84,6 +91,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
String clientIdPkceS256Public = "mockPublicClientIdPKCES256";
String clientIdCustomTokens = "mockClientIdCustomTokens";
String clientIdRefreshTokenRotation = "mockClientIdRefreshTokenRotation";
+ String clientIdJwtAccessToken = "mockClientIdJwtAccessToken";
String codeVerifier = "9234567812345678123456781234567812345678123456781234567812345678";
Scope scope = Scope.parse("openid profile email offline_access");
@@ -107,8 +115,9 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
removeMetadata(storageService, clientIdPkceS256);
removeMetadata(storageService, clientIdCustomTokens);
removeMetadata(storageService, clientIdRefreshTokenRotation);
+ removeMetadata(storageService, clientIdJwtAccessToken);
}
-
+
@Test
public void testNoClientId() throws IOException, ParseException {
setHttpFormRequest("POST", createRequestParameters(redirectUri, "authorization_code", "mockCode", null));
@@ -853,6 +862,18 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
Assert.assertTrue(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE, id));
}
+ @Factory
+ public Object[] createIdTokenSecurityTests() {
+ return new Object[] {
+ new IssuedJWTSignatureTest(IssuedJWTSignatureTest.JWT_FETCHING_TYPE.TOKEN_ID_TOKEN, FLOW_ID) };
+ }
+
+ @Factory
+ public Object[] createAccessTokenSecurityTests() {
+ return new Object[] {
+ new IssuedJWTSignatureTest(IssuedJWTSignatureTest.JWT_FETCHING_TYPE.TOKEN_ACCESS_TOKEN, FLOW_ID) };
+ }
+
private AccessTokenClaimsSet unwrapAccessToken(final OIDCTokenResponse tokenResponse) {
final AccessToken accessToken = tokenResponse.getTokens().getAccessToken();
Assert.assertNotNull(accessToken);
@@ -895,6 +916,39 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
return flowExecutor.launchExecution(FLOW_ID, null, externalContext);
}
+ protected FlowExecutionResult launchWithJwtAuthentication(final JWTAuthentication authnMethod,
+ final JWSAlgorithm algorithm, final JWSAlgorithm idTokenSigAlg, final JWEAlgorithm idTokenEncAlg,
+ final EncryptionMethod idTokenEncMethod)
+ throws Exception {
+ final OIDCClientMetadata metadata = buildMetadataSkeleton();
+ metadata.setScope(scope);
+ metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ metadata.setTokenEndpointAuthJWSAlg(JWSAlgorithm.HS256);
+ metadata.setIDTokenJWSAlg(idTokenSigAlg);
+ metadata.setIDTokenJWEAlg(idTokenEncAlg);
+ metadata.setIDTokenJWEEnc(idTokenEncMethod);
+ return launchWithJwtAuthentication(flowExecutor, authnMethod, externalContext, request, redirectUri,
+ scope.toString(), metadata, clientSecret, storageService);
+ }
+
+ protected static FlowExecutionResult launchWithJwtAuthentication(final FlowExecutor flowExecutor,
+ final JWTAuthentication authnMethod, final ExternalContext externalContext,
+ final MockHttpServletRequest request, final String redirectUri, final String scope,
+ final OIDCClientMetadata metadata, final String clientSecret, final StorageService storageService)
+ throws Exception {
+ final String clientId = authnMethod.getClientID().getValue();
+ final String code = ValidateGrantTest.buildAuthorizationCode(clientId, "https://op.example.org", "jdoe"
+ , "mock",
+ redirectUri, scope.toString()).toString();
+ storeMetadataObject(storageService, clientId, clientSecret, metadata);
+ final Map<String, String> requestParameters =
+ createRequestParameters(redirectUri, "authorization_code", code, clientId);
+ populateClientAssertionParams(requestParameters, authnMethod);
+ setHttpFormRequest(request, "POST", requestParameters);
+ return flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+
+ }
+
protected FlowExecutionResult launchWithJwtAuthentication(final JWT jwt, final JWSAlgorithm algorithm,
final ClientAuthenticationMethod method, final PublicKey publicKey) throws Exception {
final String code = ValidateGrantTest.buildAuthorizationCode(clientId, "https://op.example.org", "jdoe", "mock",
@@ -911,7 +965,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
return flowExecutor.launchExecution(FLOW_ID, null, externalContext);
}
- protected Map<String, String> createRequestParameters(final String redirectUri, final String grantType,
+ protected static Map<String, String> createRequestParameters(final String redirectUri, final String grantType,
final String code, final String clientId) {
final Map<String, String> parameters = new HashMap<>();
addNonNullValue(parameters, "redirect_uri", redirectUri);
@@ -925,7 +979,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
return parameters;
}
- protected Map<String, String> createRequestParameters(final String redirectUri, final String grantType,
+ protected static Map<String, String> createRequestParameters(final String redirectUri, final String grantType,
final String code, final String clientId, final String codeChallenge, final String codeChallengeMethod,
final String codeVerifier) {
final Map<String, String> parameters = createRequestParameters(redirectUri, grantType, code, clientId);
@@ -935,7 +989,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
return parameters;
}
- private void addNonNullValue(final Map<String, String> map, final String key, final String value) {
+ private static void addNonNullValue(final Map<String, String> map, final String key, final String value) {
if (value != null) {
map.put(key, value);
}
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 3662722c..a5de9adc 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
@@ -22,15 +22,14 @@ import java.net.URISyntaxException;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
-import org.opensaml.profile.action.EventIds;
import org.opensaml.storage.RevocationCache;
-import org.opensaml.storage.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.webflow.executor.FlowExecutionResult;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Factory;
import org.testng.annotations.Test;
import com.nimbusds.jose.JWSAlgorithm;
@@ -63,10 +62,6 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
Scope scope = Scope.parse("openid profile email");
- @Autowired
- @Qualifier("shibboleth.StorageService")
- StorageService storageService;
-
@Autowired
@Qualifier("shibboleth.oidc.RevocationCache")
private RevocationCache revocationCache;
@@ -74,7 +69,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
public UserInfoTest() {
super(FLOW_ID);
}
-
+
@BeforeMethod
public void init() throws IOException {
request.setMethod("GET");
@@ -334,4 +329,10 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
assertErrorCode(result, BearerTokenError.INVALID_TOKEN.getCode());
}
+ @Factory
+ public Object[] createUserInfoAsJwtSecurityTests() {
+ return new Object[] {
+ new IssuedJWTSignatureTest(IssuedJWTSignatureTest.JWT_FETCHING_TYPE.USERINFO, FLOW_ID) };
+ }
+
}
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml b/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml
index d2bc1e84..0eac39ea 100644
--- a/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml
+++ b/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml
@@ -88,6 +88,14 @@
</list>
</property>
</bean>
+ <bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdJwtAccessToken">
+ <property name="profileConfigurations">
+ <list>
+ <bean parent="OIDC.SSO.MDDriven" />
+ <bean parent="OAUTH2.Token.MDDriven" p:accessTokenType="JWT" p:encryptionOptional="true" />
+ </list>
+ </property>
+ </bean>
<bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdNoRefreshTokensInSSOProfile">
<property name="profileConfigurations">
<list>
@@ -151,7 +159,7 @@
</property>
</bean>
<bean parent="RelyingPartyByName"
- c:relyingPartyIds="#{{'https://rp.example.org', 'https://resource.example.org'}}">
+ c:relyingPartyIds="#{{'https://rp.example.org', 'https://resource.example.org', 'https://mock.example.org'}}">
<property name="profileConfigurations">
<list>
<bean parent="OAUTH2.TokenAudience.MDDriven" p:accessTokenType="JWT" p:encryptionOptional="true" />
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list