[java-idp-oidc] 01/02: JCOMOIDC-41 - Move OIDC Signature Validation resolvers and parameter classes to commons
Henri Mikkonen
henri.mikkonen at iki.fi
Fri Mar 17 10:23:21 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=c5282ae05d51d14d43561413c64600cfe320db40
commit c5282ae05d51d14d43561413c64600cfe320db40
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Mar 17 12:20:02 2023 +0200
JCOMOIDC-41 - Move OIDC Signature Validation resolvers and parameter classes to commons
https://shibboleth.atlassian.net/browse/JCOMOIDC-41
- Added JWT access token encryption to the authorize-flow
- Improved JWT signature tests
- Added JWT encryption tests, with and without encryptionOptional and signatures
---
.../logic/PayloadFromUserInfoLookupFunction.java | 7 +-
idp-oidc-extension-impl/pom.xml | 7 +
.../idp/flows/oidc/authorize/authorize-beans.xml | 64 +++
.../idp/flows/oidc/authorize/authorize-flow.xml | 2 +
.../idp/flows/oidc/token/token-beans.xml | 2 +
.../flow/AbstractIssuedJWTSecurityTest.java | 556 +++++++++++++++++++++
.../AbstractOidcClientAuthenticationFlowTest.java | 11 +-
.../oidc/op/profile/flow/AbstractOidcFlowTest.java | 41 +-
.../oidc/op/profile/flow/AuthorizeFlowTest.java | 15 +-
.../op/profile/flow/IssuedEncryptedJWTTest.java | 475 ++++++++++++++++++
.../op/profile/flow/IssuedJWTSignatureTest.java | 404 ---------------
.../oidc/op/profile/flow/IssuedSignedJWTTest.java | 154 ++++++
.../plugin/oidc/op/profile/flow/TokenFlowTest.java | 14 +-
.../plugin/oidc/op/profile/flow/UserInfoTest.java | 11 +-
.../src/test/resources/conf/relying-party.xml | 17 +
15 files changed, 1350 insertions(+), 430 deletions(-)
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/logic/PayloadFromUserInfoLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/logic/PayloadFromUserInfoLookupFunction.java
index 3202f9a1..1619814f 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/logic/PayloadFromUserInfoLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/logic/PayloadFromUserInfoLookupFunction.java
@@ -42,10 +42,13 @@ public class PayloadFromUserInfoLookupFunction implements Function<MessageContex
}
final OIDCAuthenticationResponseContext oidcResponseCtx =
messageContext.getSubcontext(OIDCAuthenticationResponseContext.class);
- if (oidcResponseCtx == null || oidcResponseCtx.getUserInfo() == null) {
+ if (oidcResponseCtx == null ||
+ (oidcResponseCtx.getUserInfo() == null && oidcResponseCtx.getProcessedToken() == null)) {
return null;
}
- return new Payload(oidcResponseCtx.getUserInfo().toJSONObject());
+ return oidcResponseCtx.getProcessedToken() == null
+ ? new Payload(oidcResponseCtx.getUserInfo().toJSONObject())
+ : new Payload(oidcResponseCtx.getProcessedToken().serialize());
}
}
diff --git a/idp-oidc-extension-impl/pom.xml b/idp-oidc-extension-impl/pom.xml
index 338ef4be..076223a9 100644
--- a/idp-oidc-extension-impl/pom.xml
+++ b/idp-oidc-extension-impl/pom.xml
@@ -293,6 +293,13 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>net.shibboleth.oidc</groupId>
+ <artifactId>oidc-common-crypto-impl</artifactId>
+ <version>${oidc.common.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
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 d94d50eb..549378c6 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
@@ -527,6 +527,49 @@
</bean>
</property>
</bean>
+
+ <bean id="PopulateThirdPartyAccessTokenEncryptionParameters"
+ class="net.shibboleth.oidc.profile.impl.PopulateJWTEncryptionParameters" scope="prototype"
+ p:encryptionOptionalPredicate-ref="AudienceEncryptionOptionalPredicate"
+ p:securityParametersContextLookupStrategy-ref="AudienceSecurityParametersCreationStrategy"
+ p:forFriendlyName="JWT Access Token">
+ <property name="clientMetadataContextLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultOIDCMetadataContextLookupFunction"
+ p:inbound="false" />
+ </property>
+ <property name="configurationLookupStrategy">
+ <bean lazy-init="true"
+ class="net.shibboleth.oidc.profile.config.navigate.JWTEncryptionConfigurationLookupFunction"
+ p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
+ </property>
+ <property name="encryptionParametersResolver">
+ <bean class="net.shibboleth.oidc.security.jose.impl.DefaultEncryptionParametersResolver">
+ <property name="keyTransportEncryptionAlgorithmsLookupStrategy">
+ <bean
+ class="net.shibboleth.oidc.security.jose.impl.ClientInformationKeyTransportEncryptionAlgorithmsLookupStrategy">
+ <constructor-arg>
+ <bean
+ class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
+ c:keyName="id_token_encrypted_response_alg"/>
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="dataEncryptionAlgorithmsLookupStrategy">
+ <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationDataEncryptionAlgorithmsLookupStrategy">
+ <constructor-arg>
+ <bean
+ class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
+ c:keyName="id_token_encrypted_response_enc"/>
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ </bean>
+
+ <bean id="AudienceEncryptionOptionalPredicate"
+ class="net.shibboleth.oidc.profile.config.logic.EncryptionOptionalPredicate"
+ p:relyingPartyContextLookupStrategy-ref="AudienceRelyingPartyCreationStrategy" />
<bean id="AudienceSecurityParametersCreationStrategy" parent="shibboleth.Functions.Compose"
c:g-ref="shibboleth.oidc.ChildLookupOrCreate.JWTSecurityParameters"
@@ -588,6 +631,27 @@
</bean>
</constructor-arg>
</bean>
+
+ <bean id="EncryptAccessToken" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
+ scope="prototype" c:executionDirection="OUTBOUND">
+ <constructor-arg name="messageHandler">
+ <bean id="EncryptAccessTokenHandler"
+ class="net.shibboleth.oidc.security.impl.EncryptJWTHandler" scope="prototype"
+ p:securityParametersLookupStrategy-ref="AudienceSecurityParametersCreationViaMessageContextStrategy"
+ p:logName="Access Token">
+ <property name="payloadToEncryptLookupStrategy">
+ <bean
+ class="net.shibboleth.idp.plugin.oidc.op.messaging.context.logic.PayloadFromJWTAccessTokenLookupFunction" />
+ </property>
+ <property name="jwtUpdateConsumer">
+ <bean
+ class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.JWTAccessTokenUpdateStrategy" />
+ </property>
+ </bean>
+ </constructor-arg>
+ </bean>
+
+
<!-- ID token actions. -->
<bean id="PopulateIDTokenSignatureSigningParameters"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
index 375728e8..5b8b9c9e 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
@@ -252,9 +252,11 @@
-->
<action-state id="BuildTokensForThirdPartyAccess">
<evaluate expression="PopulateThirdPartyAccessTokenSignatureSigningParameters" />
+ <evaluate expression="PopulateThirdPartyAccessTokenEncryptionParameters" />
<evaluate expression="AddAttributeClaimsToAccessToken" />
<evaluate expression="BuildAccessToken" />
<evaluate expression="SignAccessToken" />
+ <evaluate expression="EncryptAccessToken"/>
<evaluate expression="SetOAuthAccessTokenToResponseContext" />
<evaluate expression="'proceed'" />
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 579f0957..982a8764 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
@@ -572,6 +572,7 @@
<bean id="PopulateThirdPartyAccessTokenEncryptionParameters"
class="net.shibboleth.oidc.profile.impl.PopulateJWTEncryptionParameters" scope="prototype"
p:encryptionOptionalPredicate-ref="AudienceEncryptionOptionalPredicate"
+ p:securityParametersContextLookupStrategy-ref="AudienceSecurityParametersCreationStrategy"
p:forFriendlyName="JWT Access Token">
<property name="clientMetadataContextLookupStrategy">
<bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultOIDCMetadataContextLookupFunction"
@@ -667,6 +668,7 @@
<constructor-arg name="messageHandler">
<bean id="EncryptAccessTokenHandler"
class="net.shibboleth.oidc.security.impl.EncryptJWTHandler" scope="prototype"
+ p:securityParametersLookupStrategy-ref="AudienceSecurityParametersCreationViaMessageContextStrategy"
p:logName="Access Token">
<property name="payloadToEncryptLookupStrategy">
<bean
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractIssuedJWTSecurityTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractIssuedJWTSecurityTest.java
new file mode 100644
index 00000000..be9f62cd
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractIssuedJWTSecurityTest.java
@@ -0,0 +1,556 @@
+/*
+ * 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.KeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.text.ParseException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.Assert;
+
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.JWEDecrypter;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSVerifier;
+import com.nimbusds.jose.crypto.AESDecrypter;
+import com.nimbusds.jose.crypto.DirectDecrypter;
+import com.nimbusds.jose.crypto.ECDHDecrypter;
+import com.nimbusds.jose.crypto.ECDSAVerifier;
+import com.nimbusds.jose.crypto.MACVerifier;
+import com.nimbusds.jose.crypto.RSADecrypter;
+import com.nimbusds.jose.crypto.RSASSAVerifier;
+import com.nimbusds.jwt.EncryptedJWT;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.AccessTokenResponse;
+import com.nimbusds.oauth2.sdk.AuthorizationResponse;
+import com.nimbusds.oauth2.sdk.AuthorizationSuccessResponse;
+import com.nimbusds.oauth2.sdk.ResponseType;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+import com.nimbusds.oauth2.sdk.auth.JWTAuthentication;
+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.oidc.security.credential.JWKCredential;
+import net.shibboleth.oidc.security.impl.support.TestCredentialHelper;
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.security.DataSealerException;
+
+public abstract class AbstractIssuedJWTSecurityTest extends AbstractOidcFlowTest {
+
+ String defaultClientId = "mockClientId";
+ String defaultClientIdEncryptionEnforced = "mockClientIdEncryptionEnforced";
+ String defaultClientSecret = "/A?D(G+KbPdSgVkYp3s6v9y$B&E)H at Mc";
+ String defaultClientSecret64B = "/A?D(G+KbPdSgVkYp3s6v9y$B&E)H at Mc/A?D(G+KbPdSgVkYp3s6v9y$B&E)H at Mc";
+
+
+ public enum JWT_FETCHING_TYPE {
+
+ USERINFO,
+
+ TOKEN_ID_TOKEN,
+
+ TOKEN_ACCESS_TOKEN,
+
+ AUTHORIZE_ID_TOKEN,
+
+ AUTHORIZE_ACCESS_TOKEN,
+ }
+
+ protected final JWT_FETCHING_TYPE fetchingType;
+
+ protected final boolean encryptionOptional;
+
+ protected AbstractIssuedJWTSecurityTest(final JWT_FETCHING_TYPE type, final String flowId) {
+ this(type, flowId, true);
+ }
+
+ protected AbstractIssuedJWTSecurityTest(final JWT_FETCHING_TYPE type, final String flowId,
+ final boolean optionalEncryption) {
+ super(flowId);
+ fetchingType = type;
+ encryptionOptional = optionalEncryption;
+ }
+
+ protected JWT obtainJwt(final JWSAlgorithm jwsAlgorithm) {
+ return obtainJwt(defaultClientSecret, jwsAlgorithm);
+ }
+
+ protected JWT obtainJwt(final String clientSecret, final JWSAlgorithm jwsAlgorithm) {
+ return obtainJwt(clientSecret, null, jwsAlgorithm, null, null);
+ }
+
+ protected JWT obtainJwt(final String clientSecret, final PublicKey publicKey, final JWSAlgorithm jwsAlgorithm,
+ final JWEAlgorithm jweAlgorithm, final EncryptionMethod encryptionMethod) {
+ return obtainJwt(encryptionOptional ? defaultClientId : defaultClientIdEncryptionEnforced, clientSecret,
+ publicKey, jwsAlgorithm, jweAlgorithm, encryptionMethod);
+ }
+
+ protected JWT obtainJwt(final String clientId, final String clientSecret, final PublicKey publicKey,
+ final JWSAlgorithm jwsAlgorithm, final JWEAlgorithm jweAlgorithm, final EncryptionMethod encryptionMethod) {
+ switch (fetchingType) {
+ case USERINFO:
+ return obtainUserInfoAsJwt(clientId, clientSecret, publicKey, jwsAlgorithm, jweAlgorithm,
+ encryptionMethod);
+ case TOKEN_ID_TOKEN:
+ return obtainIdTokenFromTokenEndpoint(clientId, clientSecret, publicKey, jwsAlgorithm,
+ jweAlgorithm, encryptionMethod);
+ case TOKEN_ACCESS_TOKEN:
+ return obtainJwtAccessTokenFromTokenEndpoint(clientId, clientSecret, publicKey,
+ jwsAlgorithm, jweAlgorithm, encryptionMethod);
+ //return obtainJwtAccessTokenFromTokenEndpoint("mockClientIdJwtAccessToken", clientSecret, publicKey,
+ // jwsAlgorithm, jweAlgorithm, encryptionMethod);
+ case AUTHORIZE_ID_TOKEN:
+ return obtainIdTokenFromAuthorizeEndpoint(clientId, clientSecret, publicKey, jwsAlgorithm,
+ jweAlgorithm, encryptionMethod);
+ case AUTHORIZE_ACCESS_TOKEN:
+ return obtainAccessTokenFromAuthorizeEndpoint(clientId, clientSecret, publicKey, jwsAlgorithm,
+ jweAlgorithm, encryptionMethod);
+ default:
+ Assert.fail();
+ }
+ return null;
+ }
+
+ protected static void assertSignedJwt(final JWT jwt, final JWSAlgorithm algorithm, final String clientSecret) {
+ assertSignedJwt(jwt, algorithm, null, clientSecret);
+ }
+
+ protected static void assertSignedJwt(final JWT jwt, final JWSAlgorithm algorithm, final PublicKey publicKey) {
+ assertSignedJwt(jwt, algorithm, publicKey, null);
+ }
+
+ protected static void assertSignedJwt(final JWT jwt, final JWSAlgorithm algorithm, final PublicKey publicKey,
+ final String clientSecret) {
+ Assert.assertTrue(SignedJWT.class.isInstance(jwt), "The JWT was not SignedJWT: " + jwt);
+ final SignedJWT signedJwt = (SignedJWT) jwt;
+ Assert.assertEquals(signedJwt.getHeader().getAlgorithm(), algorithm);
+ final JWSVerifier verifier;
+ try {
+ if (JWSAlgorithm.Family.HMAC_SHA.contains(algorithm)) {
+ verifier = new MACVerifier(clientSecret);
+ } else 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("The algorithm " + algorithm + " was not detected for validation");
+ return;
+ }
+ Assert.assertTrue(signedJwt.verify(verifier), "Could not verify signature with " + algorithm);
+ } catch (JOSEException e) {
+ Assert.fail();
+ }
+ }
+
+ protected static void assertEncryptedJwt(final JWT jwt, final JWEAlgorithm algorithm,
+ final EncryptionMethod method, final String clientSecret, final PrivateKey privateKey) {
+ final EncryptedJWT encryptedJwt = assertAndDecryptJwt(jwt, algorithm, method, clientSecret, privateKey);
+ Assert.assertNotNull(encryptedJwt.getPayload());
+ Assert.assertNotNull(encryptedJwt.getPayload().toJSONObject());
+ Assert.assertEquals(encryptedJwt.getPayload().toJSONObject().get("sub"), "mockSubject");
+ }
+
+ protected static EncryptedJWT assertAndDecryptJwt(final JWT jwt, final JWEAlgorithm algorithm,
+ final EncryptionMethod method, final String clientSecret, final PrivateKey privateKey) {
+
+ Assert.assertTrue(EncryptedJWT.class.isInstance(jwt));
+ final EncryptedJWT encryptedJwt = (EncryptedJWT) jwt;
+ Assert.assertEquals(encryptedJwt.getHeader().getAlgorithm(), algorithm);
+ Assert.assertEquals(encryptedJwt.getHeader().getEncryptionMethod(), method);
+ final JWEDecrypter decrypter;
+ try {
+ if (JWEAlgorithm.DIR.equals(algorithm)) {
+ decrypter = new DirectDecrypter(clientSecret.getBytes());
+ } else if (JWEAlgorithm.Family.RSA.contains(algorithm)) {
+ decrypter = new RSADecrypter(privateKey);
+ } else if (JWEAlgorithm.Family.AES_GCM_KW.contains(algorithm)
+ || JWEAlgorithm.Family.AES_KW.contains(algorithm)) {
+ final JWKCredential cred = TestCredentialHelper.createClientSecretCredential(clientSecret)
+ .toEncryptionCredential(algorithm, method);
+ decrypter = new AESDecrypter(cred.getSecretKey());
+ } else if (JWEAlgorithm.Family.ECDH_ES.contains(algorithm)) {
+ decrypter = new ECDHDecrypter((ECPrivateKey) privateKey);
+ } else {
+ Assert.fail("JWE algorithm '"+ algorithm.getName() +"' not expected");
+ return null;
+ }
+ encryptedJwt.decrypt(decrypter);
+ } catch (final JOSEException | KeyException e) {
+ Assert.fail(e.getMessage(), e);
+ }
+ return encryptedJwt;
+ }
+
+ protected static void assertEncryptedSignedJwt(final JWT jwt, final JWSAlgorithm jwsAlg, final JWEAlgorithm jweAlg,
+ final EncryptionMethod method, final String clientSecret, final PublicKey jwsValidationKey) {
+ assertEncryptedSignedJwt(jwt, jwsAlg, jweAlg, method, clientSecret, null, null, jwsValidationKey);
+ }
+
+ protected static void assertEncryptedSignedJwt(final JWT jwt, final JWSAlgorithm jwsAlg, final JWEAlgorithm jweAlg,
+ final EncryptionMethod method, final String clientSecret, final PrivateKey privateKey,
+ final PublicKey publicKey, final PublicKey jwsValidationKey) {
+ final EncryptedJWT encryptedJwt = assertAndDecryptJwt(jwt, jweAlg, method, clientSecret, privateKey);
+ Assert.assertNotNull(encryptedJwt.getPayload());
+ try {
+ final SignedJWT signedJwt = SignedJWT.parse(encryptedJwt.getPayload().toString());
+ assertSignedJwt(signedJwt, jwsAlg, jwsValidationKey, clientSecret);
+ } catch (ParseException e) {
+ Assert.fail("Could not parse SignedJWT from the unencrypted JWT", e);
+ }
+ //Assert.assertNotNull(signedJwt,
+ // "Could not parse SignedJWT from: " + encryptedJwt.getPayload() + ", with JWSAlg " + jwsAlg);
+
+ }
+
+ protected void assertNoJwtResponse(final String clientId, final String clientSecret,
+ final PublicKey publicKey, final JWSAlgorithm jwsAlgorithm, final JWT_FETCHING_TYPE fetchingType) {
+ assertNoJwtResponse(clientId, clientSecret, publicKey, jwsAlgorithm, null, null, fetchingType);
+ }
+
+ protected void assertNoJwtResponse(final String clientSecret,
+ final PublicKey publicKey, final JWSAlgorithm jwsAlgorithm, final JWEAlgorithm jweAlgorithm,
+ final EncryptionMethod method, final JWT_FETCHING_TYPE fetchingType) {
+ assertNoJwtResponse(encryptionOptional ? defaultClientId : defaultClientIdEncryptionEnforced, clientSecret,
+ publicKey, jwsAlgorithm, jweAlgorithm, method, fetchingType);
+ }
+
+ protected void assertNoJwtResponse(final String clientId, final String clientSecret,
+ final PublicKey publicKey, final JWSAlgorithm jwsAlgorithm, final JWEAlgorithm jweAlgorithm,
+ final EncryptionMethod method, final JWT_FETCHING_TYPE fetchingType) {
+ if (fetchingType.equals(JWT_FETCHING_TYPE.USERINFO)) {
+ // UserInfo responds raw JSON when alg is disabled
+ final UserInfoSuccessResponse response = obtainUserInfoResponse(clientId, clientSecret, publicKey,
+ jwsAlgorithm, jweAlgorithm, method);
+ if (encryptionOptional) {
+ if (jwsAlgorithm == null) {
+ Assert.assertNotNull(response.getUserInfo());
+ Assert.assertNull(response.getUserInfoJWT());
+ } else if (jweAlgorithm == null) {
+ // covers the IssuedSignedJWT's noKey-checks
+ Assert.assertNotNull(response.getUserInfo());
+ Assert.assertNull(response.getUserInfoJWT());
+ } else {
+ Assert.assertNull(response.getUserInfo());
+ Assert.assertNotNull(response.getUserInfoJWT());
+ }
+ } else if (jweAlgorithm == null) {
+ Assert.assertNotNull(response.getUserInfo());
+ Assert.assertNull(response.getUserInfoJWT());
+ } else {
+ Assert.assertNull(response);
+ }
+ } else {
+ final JWT jwt = obtainJwt(clientId, clientSecret, publicKey, jwsAlgorithm, jweAlgorithm, method);
+ // covers the signature tests from IssuedEncryptedJWTTest when no encryption key is found
+ if (jwsAlgorithm != null && jweAlgorithm != null && encryptionOptional) {
+ Assert.assertTrue(SignedJWT.class.isInstance(jwt), "Expected SignedJWT, obtained " + jwt);
+ } else {
+ Assert.assertNull(jwt);
+
+ }
+ }
+ }
+
+ public UserInfoSuccessResponse obtainUserInfoResponse(final String clientId, final JWSAlgorithm jwsAlgorithm) {
+ return obtainUserInfoResponse(clientId, null, null, jwsAlgorithm, null, null);
+ }
+
+ public UserInfoSuccessResponse obtainUserInfoResponse(final String clientId, final String clientSecret,
+ final PublicKey publicKey, 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);
+ if (publicKey != null) {
+ metadata.setJWKSet(super.buildJWKSet(publicKey));
+ }
+ try {
+ storeMetadataObject(storageService, clientId, clientSecret, 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 String clientSecret, final PublicKey publicKey,
+ final JWSAlgorithm jwsAlgorithm, final JWEAlgorithm jweAlgorithm, final EncryptionMethod encryptionMethod) {
+ final UserInfoSuccessResponse response = obtainUserInfoResponse(clientId, clientSecret, publicKey, jwsAlgorithm,
+ jweAlgorithm, encryptionMethod);
+ if (response != null) {
+ Assert.assertNull(response.getUserInfo());
+ Assert.assertNotNull(response.getUserInfoJWT());
+ return response.getUserInfoJWT();
+ }
+ return null;
+ }
+
+ protected JWT obtainIdTokenFromTokenEndpoint(final String clientId, final String clientSecret,
+ final PublicKey publicKey, final JWSAlgorithm storedJwsAlgorithm, final JWEAlgorithm storedJweAlgorithm,
+ final EncryptionMethod storedJweMethod) {
+ try {
+ final OIDCClientMetadata metadata = buildMetadataSkeleton();
+ final JWTAuthentication clientAuth;
+ final Set<PublicKey> keys = new HashSet<>();
+ if (clientSecret != null) {
+ clientAuth = TokenFlowTest.buildSecretJwtAuth(clientId, clientSecret, "http://localhost");
+ metadata.setTokenEndpointAuthJWSAlg(JWSAlgorithm.HS256);
+ metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ } else {
+ clientAuth = TokenFlowTest.buildPrivateKeyJwtAuth(clientId, rsaPrivateKey, "http://localhost");
+ metadata.setTokenEndpointAuthJWSAlg(JWSAlgorithm.RS256);
+ metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT);
+ keys.add(rsaPublicKey);
+ }
+ metadata.setScope(Scope.parse("openid profile email offline_access"));
+ metadata.setIDTokenJWEAlg(storedJweAlgorithm);
+ metadata.setIDTokenJWSAlg(storedJwsAlgorithm);
+ metadata.setIDTokenJWEEnc(storedJweMethod);
+ if (publicKey != null) {
+ keys.add(publicKey);
+ }
+ if (!keys.isEmpty()) {
+ metadata.setJWKSet(super.buildJWKSet(keys.toArray(new PublicKey[0])));
+ }
+ final FlowExecutionResult result = TokenFlowTest.launchWithJwtAuthentication(flowExecutor, clientAuth,
+ externalContext, request, "http://localhost", "openid", metadata, clientSecret,
+ 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(e.getMessage(), e);
+ return null;
+ }
+ }
+
+ protected JWT obtainJwtAccessTokenFromTokenEndpoint(final String clientId, final String clientSecret,
+ final PublicKey publicKey, final JWSAlgorithm storedJwsAlgorithm, final JWEAlgorithm storedJweAlgorithm,
+ final EncryptionMethod storedJweMethod) {
+ try {
+ final String resource = encryptionOptional ?
+ "https://mock.example.org" : "https://encryption.enforced.example.org";
+ final JWTAuthentication clientAuth;
+ final OIDCClientMetadata metadata = buildMetadataSkeleton();
+ if (clientSecret != null) {
+ clientAuth = TokenFlowTest.buildSecretJwtAuth(clientId, clientSecret, "http://localhost");
+ metadata.setTokenEndpointAuthJWSAlg(JWSAlgorithm.HS256);
+ metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ } else {
+ clientAuth = TokenFlowTest.buildPrivateKeyJwtAuth(clientId, rsaPrivateKey, "http://localhost");
+ metadata.setTokenEndpointAuthJWSAlg(JWSAlgorithm.RS256);
+ metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT);
+ metadata.setJWKSet(super.buildJWKSet(rsaPublicKey));
+ }
+ 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);
+ if (publicKey != null) {
+ resourceMetadata.setJWKSet(super.buildJWKSet(publicKey));
+ }
+
+ super.storeMetadataObject(storageService, resource, clientSecret, resourceMetadata);
+
+ final FlowExecutionResult result = TokenFlowTest.launchWithJwtAuthentication(flowExecutor, clientAuth,
+ externalContext, request, "http://localhost", "profile", metadata, clientSecret,
+ storageService);
+ removeMetadata(storageService, clientId);
+ removeMetadata(storageService, resource);
+ if (parseResponse(result).indicatesSuccess()) {
+ final AccessTokenResponse response = parseSuccessResponse(result, AccessTokenResponse.class);
+ Assert.assertNotNull(response.getTokens().getAccessToken());
+ return parseEncryptedOrSignedJWT(response.getTokens().getAccessToken().getValue());
+ } else {
+ return null;
+ }
+ } catch (final Exception e) {
+ Assert.fail(e.getMessage(), e);
+ return null;
+ }
+ }
+
+ protected JWT obtainIdTokenFromAuthorizeEndpoint(final String clientId, final String clientSecret,
+ final PublicKey publicKey,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);
+ if (publicKey != null) {
+ metadata.setJWKSet(super.buildJWKSet(publicKey));
+ }
+ try {
+ metadata.setRedirectionURI(new URI(redirectUri));
+ super.storeMetadataObject(storageService, clientId, clientSecret, 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 String clientSecret,
+ final PublicKey publicKey,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 = encryptionOptional ?
+ "https://mock.example.org" : "https://encryption.enforced.example.org";
+
+ AuthorizeFlowTest.setRequestParameters(request, List.of(new Pair<>("client_id", clientId),
+ new Pair<>("response_type", "token"),
+ new Pair<>("scope", "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));
+ metadata.setResponseTypes(Set.of(ResponseType.TOKEN));
+
+ final OIDCClientMetadata resourceMetadata = buildMetadataSkeleton();
+ resourceMetadata.setScope(Scope.parse("openid profile email offline_access"));
+ resourceMetadata.setIDTokenJWEAlg(storedJweAlgorithm);
+ resourceMetadata.setIDTokenJWSAlg(storedJwsAlgorithm);
+ resourceMetadata.setIDTokenJWEEnc(storedJweMethod);
+ if (publicKey != null) {
+ resourceMetadata.setJWKSet(super.buildJWKSet(publicKey));
+ metadata.setJWKSet(super.buildJWKSet(publicKey));
+ }
+
+ try {
+ metadata.setRedirectionURI(new URI(redirectUri));
+ super.storeMetadataObject(storageService, clientId, clientSecret, metadata);
+ super.storeMetadataObject(storageService, resource, clientSecret, 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 AuthorizationResponse responseMessage = parseSuccessResponse(result, AuthorizationResponse.class);
+ final AuthorizationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+ Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+ Assert.assertNotNull(successResponse.getAccessToken());
+ return parseEncryptedOrSignedJWT(successResponse.getAccessToken().getValue());
+ }
+ return null;
+ }
+
+ protected JWT parseEncryptedOrSignedJWT(final String input) {
+ try {
+ return SignedJWT.parse(input);
+ } catch (final ParseException e) {
+ try {
+ return EncryptedJWT.parse(input);
+ } catch (ParseException e1) {
+ Assert.fail("Could not parse either SignedJWT or EncryptedJWT from the input" + input, e1);
+ }
+ }
+ return null;
+ }
+
+}
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 f71f2aeb..2680a955 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
@@ -608,7 +608,8 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
return buildSecretJwtAuth(id, secret, jwtAud);
}
- protected static ClientSecretJWT buildSecretJwtAuth(final String id, String secret, final String jwtAud) throws JOSEException, URISyntaxException {
+ 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));
}
@@ -617,7 +618,13 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
return new PrivateKeyJWT(new ClientID(clientId), new URI(jwtAud),
JWSAlgorithm.RS256, (PrivateKey) rsaPrivateKey, null, null);
}
-
+
+ protected static PrivateKeyJWT buildPrivateKeyJwtAuth(final String id, final PrivateKey privateKey,
+ final String jwtAud) throws JOSEException, URISyntaxException {
+ return new PrivateKeyJWT(new ClientID(id), new URI(jwtAud),
+ JWSAlgorithm.RS256, privateKey, null, null);
+ }
+
protected static void populateClientAssertionParams(final Map<String, String> requestParameters,
final JWTAuthentication clientAuth) {
requestParameters.put("client_assertion", clientAuth.getClientAssertion().serialize());
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 3de58353..7aef9b12 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
@@ -29,6 +29,7 @@ import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Instant;
+import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
@@ -67,6 +68,7 @@ import com.nimbusds.jose.crypto.RSAEncrypter;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
+import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
@@ -203,9 +205,11 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
protected <AResponseType extends Response> AResponseType parseSuccessResponse(final FlowExecutionResult result,
final Class<AResponseType> clazz) {
final Response response = parseResponse(result);
- Assert.assertTrue(response.indicatesSuccess());
- Assert.assertTrue(clazz.isInstance(response));
- return clazz.cast(response);
+ if (response.indicatesSuccess()) {
+ Assert.assertTrue(clazz.isInstance(response));
+ return clazz.cast(response);
+ }
+ return null;
}
protected void assertErrorCode(final FlowExecutionResult result, final String errorCode) {
@@ -397,19 +401,22 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
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();
+ protected static JWKSet buildJWKSet(final PublicKey... publicKeys) {
+ final List<JWK> keys = new ArrayList<>();
+ for (final PublicKey publicKey : publicKeys) {
+ if (publicKey instanceof RSAPublicKey) {
+ final RSAKey rsaKey = new RSAKey.Builder((RSAPublicKey) publicKey).build();
+ keys.add(rsaKey);
+ } else if (publicKey instanceof ECPublicKey) {
+ final ECPublicKey ecPublicKey = (ECPublicKey) publicKey;
+ final ECKey ecKey = new ECKey.Builder(Curve.forECParameterSpec(ecPublicKey.getParams()),
+ ecPublicKey).build();
+ keys.add(ecKey);
+ } else {
+ Assert.fail();
+ }
}
- return null;
+ return new JWKSet(keys);
}
protected void removeMetadata(final StorageService storageService, final String clientId) throws IOException {
storageService.delete(BaseStorageServiceClientInformationComponent.CONTEXT_NAME, clientId);
@@ -524,11 +531,11 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
}
protected static BasicJWKCredential loadES384SigningCredential() {
- return loadCredential("/credentials/idp-signing-es-384.jwk");
+ return loadCredential("/credentials/idp-signing-es384.jwk");
}
protected static BasicJWKCredential loadES512SigningCredential() {
- return loadCredential("/credentials/idp-signing-es-521.jwk");
+ return loadCredential("/credentials/idp-signing-es521.jwk");
}
protected static BasicJWKCredential loadCredential(final String classPathLocation) {
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 eeb7ffd2..2734c11d 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
@@ -1544,13 +1544,24 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
@Factory
public Object[] createIdTokenSecurityTests() {
return new Object[] {
- new IssuedJWTSignatureTest(IssuedJWTSignatureTest.JWT_FETCHING_TYPE.AUTHORIZE_ID_TOKEN, FLOW_ID) };
+ new IssuedSignedJWTTest(AbstractIssuedJWTSecurityTest.JWT_FETCHING_TYPE.AUTHORIZE_ID_TOKEN, FLOW_ID),
+ new IssuedEncryptedJWTTest(AbstractIssuedJWTSecurityTest.JWT_FETCHING_TYPE.AUTHORIZE_ID_TOKEN, FLOW_ID,
+ true, false),
+ new IssuedEncryptedJWTTest(AbstractIssuedJWTSecurityTest.JWT_FETCHING_TYPE.AUTHORIZE_ID_TOKEN, FLOW_ID,
+ true, true)
+ };
}
@Factory
public Object[] createAccessTokenSecurityTests() {
return new Object[] {
- new IssuedJWTSignatureTest(IssuedJWTSignatureTest.JWT_FETCHING_TYPE.AUTHORIZE_ACCESS_TOKEN, FLOW_ID) };
+ new IssuedSignedJWTTest(AbstractIssuedJWTSecurityTest.JWT_FETCHING_TYPE.AUTHORIZE_ACCESS_TOKEN,
+ FLOW_ID),
+ new IssuedEncryptedJWTTest(AbstractIssuedJWTSecurityTest.JWT_FETCHING_TYPE.AUTHORIZE_ACCESS_TOKEN,
+ FLOW_ID, true, false),
+ new IssuedEncryptedJWTTest(AbstractIssuedJWTSecurityTest.JWT_FETCHING_TYPE.AUTHORIZE_ACCESS_TOKEN,
+ FLOW_ID, true, true)
+ };
}
protected String getRequestObjectWithClaimsRequestPayload() {
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssuedEncryptedJWTTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssuedEncryptedJWTTest.java
new file mode 100644
index 00000000..9882a360
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssuedEncryptedJWTTest.java
@@ -0,0 +1,475 @@
+/*
+ * 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.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.testng.Assert;
+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.openid.connect.sdk.UserInfoSuccessResponse;
+
+/**
+ * Tests for verifying that the JWT encryption 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 IssuedEncryptedJWTTest extends AbstractIssuedJWTSecurityTest {
+
+ private final static List<JWSAlgorithm> JWS_RSA_ALGORITHMS = List.of(JWSAlgorithm.RS256, JWSAlgorithm.RS384,
+ JWSAlgorithm.RS512, JWSAlgorithm.PS256, JWSAlgorithm.PS384, JWSAlgorithm.PS512);
+
+ private final static List<JWSAlgorithm> JWS_HMAC_ALGORITHMS = List.of(JWSAlgorithm.HS256, JWSAlgorithm.HS384,
+ JWSAlgorithm.HS512);
+
+ private final boolean testSignedJwt;
+
+ public IssuedEncryptedJWTTest(final JWT_FETCHING_TYPE type, final String flowId) {
+ this(type, flowId, false, false);
+ }
+
+ public IssuedEncryptedJWTTest(final JWT_FETCHING_TYPE type, final String flowId, final boolean testSigned,
+ final boolean encryptionOptional) {
+ super(type, flowId, encryptionOptional);
+ testSignedJwt = testSigned;
+ }
+
+ @Test
+ public void testJwtEncryption_noSigAlgNorEncSpecified() throws Exception {
+ if (fetchingType.equals(JWT_FETCHING_TYPE.USERINFO)) {
+ // No encryption nor signing done on UserInfo by default
+ final String clientId = encryptionOptional ? defaultClientId : defaultClientIdEncryptionEnforced;
+ final UserInfoSuccessResponse response = obtainUserInfoResponse(clientId, defaultClientSecret,
+ rsaPublicKey, null, null, null);
+ if (encryptionOptional) {
+ Assert.assertNotNull(response.getUserInfo());
+ Assert.assertNull(response.getUserInfoJWT());
+ } else {
+ Assert.assertNull(response);
+ }
+ } else {
+ final JWT jwt = obtainJwt(defaultClientSecret, rsaPublicKey, null, null, null);
+ if (encryptionOptional) {
+ assertSignedJwt(jwt, JWSAlgorithm.RS256, loadRSSigningCredential().getPublicKey());
+ } else {
+ Assert.assertNull(jwt);
+ }
+ }
+ }
+
+ protected void assertSecretBasedEncryption(final JWEAlgorithm jweAlgorithm, final EncryptionMethod method) {
+ if (testSignedJwt) {
+ final List<JWSAlgorithm> jwsAlgs = Stream.concat(JWS_RSA_ALGORITHMS.stream(), JWS_HMAC_ALGORITHMS.stream())
+ .collect(Collectors.toList());
+ for (final JWSAlgorithm jwsAlgorithm : jwsAlgs) {
+ final JWT jwt = obtainJwt(defaultClientSecret64B, null, jwsAlgorithm, jweAlgorithm, method);
+ assertEncryptedSignedJwt(jwt, jwsAlgorithm, jweAlgorithm, method, defaultClientSecret64B,
+ loadRSSigningCredential().getPublicKey());
+ }
+ final JWT ec2Jwt = obtainJwt(defaultClientSecret64B, null, JWSAlgorithm.ES256, jweAlgorithm, method);
+ assertEncryptedSignedJwt(ec2Jwt, JWSAlgorithm.ES256, jweAlgorithm, method, defaultClientSecret64B,
+ loadESSigningCredential().getPublicKey());
+ final JWT ec5Jwt = obtainJwt(defaultClientSecret64B, null, JWSAlgorithm.ES512, jweAlgorithm, method);
+ assertEncryptedSignedJwt(ec5Jwt, JWSAlgorithm.ES512, jweAlgorithm, method, defaultClientSecret64B,
+ loadES512SigningCredential().getPublicKey());
+
+ } else {
+ final JWT jwt = obtainJwt(defaultClientSecret, null, null, jweAlgorithm, method);
+ assertEncryptedJwt(jwt, jweAlgorithm, method, defaultClientSecret, null);
+ }
+ }
+
+ @Test
+ public void testJwtEncryption_a128kwAnd128cbcSpecified() throws Exception {
+ assertSecretBasedEncryption(JWEAlgorithm.A128KW, EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_a192kwAnd128cbcSpecified() throws Exception {
+ assertSecretBasedEncryption(JWEAlgorithm.A192KW, EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_a256kwAnd128cbcSpecified() throws Exception {
+ assertSecretBasedEncryption(JWEAlgorithm.A256KW, EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_a128gcmkwAnd128cbcSpecified() throws Exception {
+ assertSecretBasedEncryption(JWEAlgorithm.A128GCMKW, EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_a192gcmkwAnd128cbcSpecified() throws Exception {
+ assertSecretBasedEncryption(JWEAlgorithm.A192GCMKW, EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_a256gcmkwAnd128cbcSpecified() throws Exception {
+ assertSecretBasedEncryption(JWEAlgorithm.A256GCMKW, EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_a128kwAnd192cbcSpecified() throws Exception {
+ assertSecretBasedEncryption(JWEAlgorithm.A128KW, EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_a192kwAnd192cbcSpecified() throws Exception {
+ assertSecretBasedEncryption(JWEAlgorithm.A192KW, EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_a256kwAnd192cbcSpecified() throws Exception {
+ assertSecretBasedEncryption(JWEAlgorithm.A256KW, EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_a128gcmkwAnd192cbcSpecified() throws Exception {
+ assertSecretBasedEncryption(JWEAlgorithm.A128GCMKW, EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_a192gcmkwAnd192cbcSpecified() throws Exception {
+ assertSecretBasedEncryption(JWEAlgorithm.A192GCMKW, EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_a256gcmkwAnd192cbcSpecified() throws Exception {
+ assertSecretBasedEncryption(JWEAlgorithm.A256GCMKW, EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_a128kwAnd256cbcSpecified() throws Exception {
+ assertSecretBasedEncryption(JWEAlgorithm.A128KW, EncryptionMethod.A256CBC_HS512);
+ }
+
+ @Test
+ public void testJwtEncryption_a192kwAnd256cbcSpecified() throws Exception {
+ assertSecretBasedEncryption(JWEAlgorithm.A192KW, EncryptionMethod.A256CBC_HS512);
+ }
+
+ @Test
+ public void testJwtEncryption_a256kwAnd256cbcSpecified() throws Exception {
+ assertSecretBasedEncryption(JWEAlgorithm.A256KW, EncryptionMethod.A256CBC_HS512);
+ }
+
+ @Test
+ public void testJwtEncryption_a128gcmkwAnd256cbcSpecified() throws Exception {
+ assertSecretBasedEncryption(JWEAlgorithm.A128GCMKW, EncryptionMethod.A256CBC_HS512);
+ }
+
+ @Test
+ public void testJwtEncryption_a192gcmkwAnd256cbcSpecified() throws Exception {
+ assertSecretBasedEncryption(JWEAlgorithm.A192GCMKW, EncryptionMethod.A256CBC_HS512);
+ }
+
+ @Test
+ public void testJwtEncryption_a256gcmkwAnd256cbcSpecified() throws Exception {
+ assertSecretBasedEncryption(JWEAlgorithm.A256GCMKW, EncryptionMethod.A256CBC_HS512);
+ }
+
+ protected void assertNoSymmetricKeyResponse(final JWEAlgorithm jweAlgorithm, final EncryptionMethod method) {
+ if (testSignedJwt) {
+ for (final JWSAlgorithm jwsAlgorithm : JWS_RSA_ALGORITHMS) {
+ assertNoJwtResponse(null, rsaPublicKey, jwsAlgorithm, jweAlgorithm, method, fetchingType);
+ }
+ assertNoJwtResponse(null, loadESSigningCredential().getPublicKey(), JWSAlgorithm.ES256, jweAlgorithm,
+ method, fetchingType);
+ assertNoJwtResponse(null, loadES512SigningCredential().getPublicKey(), JWSAlgorithm.ES512, jweAlgorithm,
+ method, fetchingType);
+ } else {
+ assertNoJwtResponse(null, null, null, jweAlgorithm, method, fetchingType);
+ }
+ }
+
+ @Test
+ public void testJwtEncryption_a128kwAnd128cbcSpecified_noKey() throws Exception {
+ assertNoSymmetricKeyResponse(JWEAlgorithm.A128KW, EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_a192kwAnd128cbcSpecified_noKey() throws Exception {
+ assertNoSymmetricKeyResponse(JWEAlgorithm.A192KW, EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_a256kwAnd128cbcSpecified_noKey() throws Exception {
+ assertNoSymmetricKeyResponse(JWEAlgorithm.A256KW, EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_a128gcmkwAnd128cbcSpecified_noKey() throws Exception {
+ assertNoSymmetricKeyResponse(JWEAlgorithm.A128GCMKW, EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_a192gcmkwAnd128cbcSpecified_noKey() throws Exception {
+ assertNoSymmetricKeyResponse(JWEAlgorithm.A192GCMKW, EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_a256gcmkwAnd128cbcSpecified_noKey() throws Exception {
+ assertNoSymmetricKeyResponse(JWEAlgorithm.A256GCMKW, EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_a128kwAnd192cbcSpecified_noKey() throws Exception {
+ assertNoSymmetricKeyResponse(JWEAlgorithm.A128KW, EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_a192kwAnd192cbcSpecified_noKey() throws Exception {
+ assertNoSymmetricKeyResponse(JWEAlgorithm.A192KW, EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_a256kwAnd192cbcSpecified_noKey() throws Exception {
+ assertNoSymmetricKeyResponse(JWEAlgorithm.A256KW, EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_a128gcmkwAnd192cbcSpecified_noKey() throws Exception {
+ assertNoSymmetricKeyResponse(JWEAlgorithm.A128GCMKW, EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_a192gcmkwAnd192cbcSpecified_noKey() throws Exception {
+ assertNoSymmetricKeyResponse(JWEAlgorithm.A192GCMKW, EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_a256gcmkwAnd192cbcSpecified_noKey() throws Exception {
+ assertNoSymmetricKeyResponse(JWEAlgorithm.A256GCMKW, EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_a128kwAnd256cbcSpecified_noKey() throws Exception {
+ assertNoSymmetricKeyResponse(JWEAlgorithm.A128KW, EncryptionMethod.A256CBC_HS512);
+ }
+
+ @Test
+ public void testJwtEncryption_a192kwAnd256cbcSpecified_noKey() throws Exception {
+ assertNoSymmetricKeyResponse(JWEAlgorithm.A192KW, EncryptionMethod.A256CBC_HS512);
+ }
+
+ @Test
+ public void testJwtEncryption_a256kwAnd256cbcSpecified_noKey() throws Exception {
+ assertNoSymmetricKeyResponse(JWEAlgorithm.A256KW, EncryptionMethod.A256CBC_HS512);
+ }
+
+ @Test
+ public void testJwtEncryption_a128gcmkwAnd256cbcSpecified_noKey() throws Exception {
+ assertNoSymmetricKeyResponse(JWEAlgorithm.A128GCMKW, EncryptionMethod.A256CBC_HS512);
+ }
+
+ @Test
+ public void testJwtEncryption_a192gcmkwAnd256cbcSpecified_noKey() throws Exception {
+ assertNoSymmetricKeyResponse(JWEAlgorithm.A192GCMKW, EncryptionMethod.A256CBC_HS512);
+ }
+
+ @Test
+ public void testJwtEncryption_a256gcmkwAnd256cbcSpecified_noKey() throws Exception {
+ assertNoSymmetricKeyResponse(JWEAlgorithm.A256GCMKW, EncryptionMethod.A256CBC_HS512);
+ }
+
+ protected void assertPublicKeyBasedEncryption(final PublicKey publicKey, final PrivateKey privateKey,
+ final JWEAlgorithm jweAlgorithm, final EncryptionMethod method) {
+ if (testSignedJwt) {
+ final List<JWSAlgorithm> jwsAlgs = Stream.concat(JWS_RSA_ALGORITHMS.stream(), JWS_HMAC_ALGORITHMS.stream())
+ .collect(Collectors.toList());
+ for (final JWSAlgorithm jwsAlgorithm : jwsAlgs) {
+ final JWT jwt = obtainJwt(defaultClientSecret64B, publicKey, jwsAlgorithm, jweAlgorithm, method);
+ assertEncryptedSignedJwt(jwt, jwsAlgorithm, jweAlgorithm, method, defaultClientSecret64B, privateKey,
+ publicKey, loadRSSigningCredential().getPublicKey());
+ }
+ } else {
+ final JWT jwt = obtainJwt(defaultClientSecret, publicKey, null, jweAlgorithm, method);
+ assertEncryptedJwt(jwt, jweAlgorithm, method, defaultClientSecret, privateKey);
+ }
+ }
+
+ @Test
+ public void testJwtEncryption_ecdhAnd128cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(ecKey.toPublicKey(), ecKey.toPrivateKey(), JWEAlgorithm.ECDH_ES,
+ EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_ecdh128kwAnd128cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(ecKey.toPublicKey(), ecKey.toPrivateKey(), JWEAlgorithm.ECDH_ES_A128KW,
+ EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_ecdh192kwAnd128cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(ecKey.toPublicKey(), ecKey.toPrivateKey(), JWEAlgorithm.ECDH_ES_A192KW,
+ EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_ecdh256kwAnd128cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(ecKey.toPublicKey(), ecKey.toPrivateKey(), JWEAlgorithm.ECDH_ES_A256KW,
+ EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_ecdhAnd192cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(ecKey.toPublicKey(), ecKey.toPrivateKey(), JWEAlgorithm.ECDH_ES,
+ EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_ecdh128kwAnd192cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(ecKey.toPublicKey(), ecKey.toPrivateKey(), JWEAlgorithm.ECDH_ES_A128KW,
+ EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_ecdh192kwAnd192cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(ecKey.toPublicKey(), ecKey.toPrivateKey(), JWEAlgorithm.ECDH_ES_A192KW,
+ EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_ecdh256kwAnd192cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(ecKey.toPublicKey(), ecKey.toPrivateKey(), JWEAlgorithm.ECDH_ES_A256KW,
+ EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_ecdhAnd256cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(ecKey.toPublicKey(), ecKey.toPrivateKey(), JWEAlgorithm.ECDH_ES,
+ EncryptionMethod.A256CBC_HS512);
+ }
+
+ @Test
+ public void testJwtEncryption_ecdh128kwAnd256cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(ecKey.toPublicKey(), ecKey.toPrivateKey(), JWEAlgorithm.ECDH_ES_A128KW,
+ EncryptionMethod.A256CBC_HS512);
+ }
+
+ @Test
+ public void testJwtEncryption_ecdh192kwAnd256cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(ecKey.toPublicKey(), ecKey.toPrivateKey(), JWEAlgorithm.ECDH_ES_A192KW,
+ EncryptionMethod.A256CBC_HS512);
+ }
+
+ @Test
+ public void testJwtEncryption_ecdh256kwAnd256cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(ecKey.toPublicKey(), ecKey.toPrivateKey(), JWEAlgorithm.ECDH_ES_A256KW,
+ EncryptionMethod.A256CBC_HS512);
+ }
+
+ @Test
+ public void testJwtEncryption_rsa15And128cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(rsaPublicKey, rsaPrivateKey, JWEAlgorithm.RSA1_5,
+ EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_rsaOaepAnd128cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(rsaPublicKey, rsaPrivateKey, JWEAlgorithm.RSA_OAEP,
+ EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_rsaOaep256And128cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(rsaPublicKey, rsaPrivateKey, JWEAlgorithm.RSA_OAEP_256,
+ EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_rsaOaep384And128cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(rsaPublicKey, rsaPrivateKey, JWEAlgorithm.RSA_OAEP_384,
+ EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_rsaOaep512And128cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(rsaPublicKey, rsaPrivateKey, JWEAlgorithm.RSA_OAEP_512,
+ EncryptionMethod.A128CBC_HS256);
+ }
+
+ @Test
+ public void testJwtEncryption_rsa15And192cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(rsaPublicKey, rsaPrivateKey, JWEAlgorithm.RSA1_5,
+ EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_rsaOaepAnd192cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(rsaPublicKey, rsaPrivateKey, JWEAlgorithm.RSA_OAEP,
+ EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_rsaOaep256And192cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(rsaPublicKey, rsaPrivateKey, JWEAlgorithm.RSA_OAEP_256,
+ EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_rsaOaep384And192cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(rsaPublicKey, rsaPrivateKey, JWEAlgorithm.RSA_OAEP_384,
+ EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_rsaOaep512And192cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(rsaPublicKey, rsaPrivateKey, JWEAlgorithm.RSA_OAEP_512,
+ EncryptionMethod.A192CBC_HS384);
+ }
+
+ @Test
+ public void testJwtEncryption_rsa15And256cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(rsaPublicKey, rsaPrivateKey, JWEAlgorithm.RSA1_5,
+ EncryptionMethod.A256CBC_HS512);
+ }
+
+ @Test
+ public void testJwtEncryption_rsaOaepAnd256cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(rsaPublicKey, rsaPrivateKey, JWEAlgorithm.RSA_OAEP,
+ EncryptionMethod.A256CBC_HS512);
+ }
+
+ @Test
+ public void testJwtEncryption_rsaOaep256And256cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(rsaPublicKey, rsaPrivateKey, JWEAlgorithm.RSA_OAEP_256,
+ EncryptionMethod.A256CBC_HS512);
+ }
+
+ @Test
+ public void testJwtEncryption_rsaOaep384And256cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(rsaPublicKey, rsaPrivateKey, JWEAlgorithm.RSA_OAEP_384,
+ EncryptionMethod.A256CBC_HS512);
+ }
+
+ @Test
+ public void testJwtEncryption_rsaOaep512And256cbcSpecified() throws Exception {
+ assertPublicKeyBasedEncryption(rsaPublicKey, rsaPrivateKey, JWEAlgorithm.RSA_OAEP_512,
+ EncryptionMethod.A256CBC_HS512);
+ }
+}
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
deleted file mode 100644
index 31c79440..00000000
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssuedJWTSignatureTest.java
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * 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/IssuedSignedJWTTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssuedSignedJWTTest.java
new file mode 100644
index 00000000..19be55ce
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssuedSignedJWTTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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 org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
+
+/**
+ * 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 IssuedSignedJWTTest extends AbstractIssuedJWTSecurityTest {
+
+ public IssuedSignedJWTTest(final JWT_FETCHING_TYPE type, final String flowId) {
+ super(type, flowId);
+ }
+
+ @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);
+ Assert.assertNotNull(response.getUserInfo());
+ Assert.assertNull(response.getUserInfoJWT());
+ } else {
+ final JWT jwt = obtainJwt(null);
+ assertSignedJwt(jwt, JWSAlgorithm.RS256, loadRSSigningCredential().getPublicKey());
+ }
+ }
+
+ @Test
+ public void testJwtSecurity_jwtRS256SigAlgAndEncNotSpecified() throws Exception {
+ final JWT jwt = obtainJwt(JWSAlgorithm.RS256);
+ assertSignedJwt(jwt, JWSAlgorithm.RS256, loadRSSigningCredential().getPublicKey());
+ }
+
+ @Test
+ public void testJwtSecurity_jwtRS384SigAlgAndEncNotSpecified() throws Exception {
+ final JWT jwt = obtainJwt(JWSAlgorithm.RS384);
+ assertSignedJwt(jwt, JWSAlgorithm.RS384, loadRSSigningCredential().getPublicKey());
+ }
+
+ @Test
+ public void testJwtSecurity_jwtRS512SigAlgAndEncNotSpecified() throws Exception {
+ final JWT jwt = obtainJwt(JWSAlgorithm.RS512);
+ assertSignedJwt(jwt, JWSAlgorithm.RS512, loadRSSigningCredential().getPublicKey());
+ }
+
+ @Test
+ public void testJwtSecurity_jwtPS256SigAlgAndEncNotSpecified() throws Exception {
+ final JWT jwt = obtainJwt(JWSAlgorithm.PS256);
+ assertSignedJwt(jwt, JWSAlgorithm.PS256, loadRSSigningCredential().getPublicKey());
+ }
+
+ @Test
+ public void testJwtSecurity_jwtPS384SigAlgAndEncNotSpecified() throws Exception {
+ final JWT jwt = obtainJwt(JWSAlgorithm.PS384);
+ assertSignedJwt(jwt, JWSAlgorithm.PS384, loadRSSigningCredential().getPublicKey());
+ }
+
+ @Test
+ public void testJwtSecurity_jwtPS512SigAlgAndEncNotSpecified() throws Exception {
+ final JWT jwt = obtainJwt(JWSAlgorithm.PS512);
+ assertSignedJwt(jwt, JWSAlgorithm.PS512, loadRSSigningCredential().getPublicKey());
+ }
+
+ @Test
+ public void testJwtSecurity_jwtES256SigAlgAndEncNotSpecified() throws Exception {
+ final JWT jwt = obtainJwt(JWSAlgorithm.ES256);
+ assertSignedJwt(jwt, JWSAlgorithm.ES256, loadESSigningCredential().getPublicKey());
+ }
+
+ @Test
+ public void testJwtSecurity_jwtES384SigAlgAndEncNotSpecified() throws Exception {
+ // ES384 is globally excluded
+ assertNoJwtResponse(defaultClientId, defaultClientSecret, rsaPublicKey, JWSAlgorithm.ES384, null, null,
+ fetchingType);
+ }
+
+ @Test
+ public void testJwtSecurity_jwtES512SigAlgAndEncNotSpecified() throws Exception {
+ final JWT jwt = obtainJwt(JWSAlgorithm.ES512);
+ assertSignedJwt(jwt, JWSAlgorithm.ES512, loadCredential("/credentials/idp-signing-es521.jwk").getPublicKey());
+ }
+
+ @Test
+ public void testJwtSecurity_jwtHS256SigAlgAndEncNotSpecified_32BKey() throws Exception {
+ final JWT jwt = obtainJwt(defaultClientSecret, JWSAlgorithm.HS256);
+ assertSignedJwt(jwt, JWSAlgorithm.HS256, defaultClientSecret);
+ }
+
+ @Test
+ public void testJwtSecurity_jwtHS384SigAlgAndEncNotSpecified_32BKey() throws Exception {
+ assertNoJwtResponse(defaultClientId, defaultClientSecret, null, JWSAlgorithm.HS384, fetchingType);
+ }
+
+ @Test
+ public void testJwtSecurity_jwtHS512SigAlgAndEncNotSpecified_32BKey() throws Exception {
+ assertNoJwtResponse(defaultClientId, defaultClientSecret, null, JWSAlgorithm.HS512, fetchingType);
+ }
+
+ @Test
+ public void testJwtSecurity_jwtHS256SigAlgAndEncNotSpecified_64BKey() throws Exception {
+ final JWT jwt = obtainJwt(defaultClientSecret64B, JWSAlgorithm.HS256);
+ assertSignedJwt(jwt, JWSAlgorithm.HS256, defaultClientSecret64B);
+ }
+
+ @Test
+ public void testJwtSecurity_jwtHS384SigAlgAndEncNotSpecified_64BKey() throws Exception {
+ final JWT jwt = obtainJwt(defaultClientSecret64B, JWSAlgorithm.HS384);
+ assertSignedJwt(jwt, JWSAlgorithm.HS384, defaultClientSecret64B);
+ }
+
+ @Test
+ public void testJwtSecurity_jwtHS512SigAlgAndEncNotSpecified_64BKey() throws Exception {
+ final JWT jwt = obtainJwt(defaultClientSecret64B, JWSAlgorithm.HS512);
+ assertSignedJwt(jwt, JWSAlgorithm.HS512, defaultClientSecret64B);
+ }
+
+ @Test
+ public void testJwtSecurity_jwtHS256SigAlgAndEncNotSpecified_NoKey() throws Exception {
+ assertNoJwtResponse(defaultClientId, null, null, JWSAlgorithm.HS512, fetchingType);
+ }
+
+ @Test
+ public void testJwtSecurity_jwtHS384SigAlgAndEncNotSpecified_NoKey() throws Exception {
+ assertNoJwtResponse(defaultClientId, null, null, JWSAlgorithm.HS512, fetchingType);
+ }
+
+ @Test
+ public void testJwtSecurity_jwtHS512SigAlgAndEncNotSpecified_NoKey() throws Exception {
+ assertNoJwtResponse(defaultClientId, null, null, JWSAlgorithm.HS512, fetchingType);
+ }
+
+}
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 77d12f5d..bb74fa16 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
@@ -865,13 +865,23 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
@Factory
public Object[] createIdTokenSecurityTests() {
return new Object[] {
- new IssuedJWTSignatureTest(IssuedJWTSignatureTest.JWT_FETCHING_TYPE.TOKEN_ID_TOKEN, FLOW_ID) };
+ new IssuedSignedJWTTest(AbstractIssuedJWTSecurityTest.JWT_FETCHING_TYPE.TOKEN_ID_TOKEN, FLOW_ID),
+ new IssuedEncryptedJWTTest(AbstractIssuedJWTSecurityTest.JWT_FETCHING_TYPE.TOKEN_ID_TOKEN, FLOW_ID,
+ true, false),
+ new IssuedEncryptedJWTTest(AbstractIssuedJWTSecurityTest.JWT_FETCHING_TYPE.TOKEN_ID_TOKEN, FLOW_ID,
+ true, true)
+ };
}
@Factory
public Object[] createAccessTokenSecurityTests() {
return new Object[] {
- new IssuedJWTSignatureTest(IssuedJWTSignatureTest.JWT_FETCHING_TYPE.TOKEN_ACCESS_TOKEN, FLOW_ID) };
+ new IssuedSignedJWTTest(AbstractIssuedJWTSecurityTest.JWT_FETCHING_TYPE.TOKEN_ACCESS_TOKEN, FLOW_ID),
+ new IssuedEncryptedJWTTest(AbstractIssuedJWTSecurityTest.JWT_FETCHING_TYPE.TOKEN_ACCESS_TOKEN, FLOW_ID,
+ true, false),
+ new IssuedEncryptedJWTTest(AbstractIssuedJWTSecurityTest.JWT_FETCHING_TYPE.TOKEN_ACCESS_TOKEN, FLOW_ID,
+ true, true)
+ };
}
private AccessTokenClaimsSet unwrapAccessToken(final OIDCTokenResponse tokenResponse) {
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 a5de9adc..70e94d84 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
@@ -332,7 +332,16 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
@Factory
public Object[] createUserInfoAsJwtSecurityTests() {
return new Object[] {
- new IssuedJWTSignatureTest(IssuedJWTSignatureTest.JWT_FETCHING_TYPE.USERINFO, FLOW_ID) };
+ new IssuedSignedJWTTest(AbstractIssuedJWTSecurityTest.JWT_FETCHING_TYPE.USERINFO, FLOW_ID),
+ new IssuedEncryptedJWTTest(AbstractIssuedJWTSecurityTest.JWT_FETCHING_TYPE.USERINFO, FLOW_ID, false,
+ false),
+ new IssuedEncryptedJWTTest(AbstractIssuedJWTSecurityTest.JWT_FETCHING_TYPE.USERINFO, FLOW_ID, false,
+ true),
+ new IssuedEncryptedJWTTest(AbstractIssuedJWTSecurityTest.JWT_FETCHING_TYPE.USERINFO, FLOW_ID, true,
+ false),
+ new IssuedEncryptedJWTTest(AbstractIssuedJWTSecurityTest.JWT_FETCHING_TYPE.USERINFO, FLOW_ID, true,
+ true)
+ };
}
}
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 0eac39ea..497c9c1a 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
@@ -64,6 +64,15 @@
</bean>
<util:list id="shibboleth.RelyingPartyOverrides">
+ <bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdEncryptionEnforced">
+ <property name="profileConfigurations">
+ <list>
+ <bean parent="OIDC.SSO.MDDriven" p:encryptionOptional="false" />
+ <bean parent="OAUTH2.Token.MDDriven" p:encryptionOptional="false" />
+ <bean parent="OIDC.UserInfo.MDDriven" p:encryptionOptional="false" />
+ </list>
+ </property>
+ </bean>
<bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdRefreshTokenRotation">
<property name="profileConfigurations">
<list>
@@ -166,6 +175,14 @@
</list>
</property>
</bean>
+ <bean parent="RelyingPartyByName"
+ c:relyingPartyIds="#{{'https://encryption.enforced.example.org'}}">
+ <property name="profileConfigurations">
+ <list>
+ <bean parent="OAUTH2.TokenAudience.MDDriven" p:accessTokenType="JWT" p:encryptionOptional="false" />
+ </list>
+ </property>
+ </bean>
<bean parent="RelyingPartyByName" c:relyingPartyIds="mockDynRegClientNoProfilePolicy">
<property name="profileConfigurations">
<list>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list