[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