[java-idp-plugin-oidc-rp] branch main updated: Improvements to the flow tests

Phil Smart philip.smart at jisc.ac.uk
Fri Jun 10 16:18:46 UTC 2022


This is an automated email from the git hooks/post-receive script.

philsmart pushed a commit to branch main
in repository java-idp-plugin-oidc-rp.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-oidc-rp.git;a=commit;h=a47fe2a54eedd381ccc1347f8a633a44eecaaffc

The following commit(s) were added to refs/heads/main by this push:
     new a47fe2a  Improvements to the flow tests
a47fe2a is described below

commit a47fe2a54eedd381ccc1347f8a633a44eecaaffc
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Jun 10 17:18:37 2022 +0100

    Improvements to the flow tests
---
 .../authn/oidc/rp/impl/ValidateTokenClaims.java    |   2 +-
 ....java => ValidateUserInfoJSONObjectClaims.java} |  11 +-
 .../oidc-relying-party-authn-beans.xml             |   4 +-
 .../oidc-relying-party-authn-flow.xml              |   2 +-
 .../plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java  | 634 ++++++++-------------
 .../plugin/authn/oidc/rp/impl/TestTokenHelper.java | 327 +++++++++++
 6 files changed, 559 insertions(+), 421 deletions(-)

diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateTokenClaims.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateTokenClaims.java
index 94b7ad9..e2e357b 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateTokenClaims.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateTokenClaims.java
@@ -83,7 +83,7 @@ public class ValidateTokenClaims extends AbstractOIDCAuthenticationAction {
     
     /** A cleanup hook to execute after either a successful or unsuccessful claims validation. */
     @Nullable private Consumer<ProfileRequestContext> cleanupHook;
-    
+     
     /** The parsed claimset. */
     @Nullable private JWTClaimsSet claimsSet;
     
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateUserInfoClaims.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateUserInfoJSONObjectClaims.java
similarity index 94%
rename from idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateUserInfoClaims.java
rename to idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateUserInfoJSONObjectClaims.java
index af84011..889cb59 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateUserInfoClaims.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateUserInfoJSONObjectClaims.java
@@ -33,6 +33,7 @@ import org.slf4j.LoggerFactory;
 
 import com.nimbusds.jwt.JWTClaimsSet;
 
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext;
@@ -42,12 +43,12 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
 /**
- * Validate a successful UserInfo Response according to section 5.3.2 of OpenID Connect Core 1.0. 
+ * Validate a successful UserInfo JSON Object Response according to section 5.3.2 of OpenID Connect Core 1.0. 
  */
-public class ValidateUserInfoClaims extends AbstractOIDCAuthenticationAction {
+public class ValidateUserInfoJSONObjectClaims extends AbstractAuthenticationAction {
     
     /** Class logger.*/
-    @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateUserInfoClaims.class);
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateUserInfoJSONObjectClaims.class);
     
     /** Strategy used to look up the {@link UserInfoResponseContext}. */
     @Nonnull private Function<ProfileRequestContext, UserInfoResponseContext> 
@@ -64,7 +65,7 @@ public class ValidateUserInfoClaims extends AbstractOIDCAuthenticationAction {
     @Nullable private JWTClaimsSet idTokenClaims; 
 
     /** Constructor.*/
-    public ValidateUserInfoClaims() {
+    public ValidateUserInfoJSONObjectClaims() {
         userInfoResponseContextLookupStrategy =
                 new ChildContextLookup<>(UserInfoResponseContext.class).compose(
                         new InboundMessageContextLookup());
@@ -142,7 +143,7 @@ public class ValidateUserInfoClaims extends AbstractOIDCAuthenticationAction {
     @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AuthenticationContext authenticationContext) { 
         
-          log.trace("{} Validating UserInfo claims", getLogPrefix());          
+          log.trace("{} Validating UserInfo JSON Object claims", getLogPrefix());          
          
           final UserInfoResponse response = userInfoCtx.getUserInfo();
           if (!response.isClaimsSetAvailable()) {
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
index 26d175a..9b7da49 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
@@ -630,9 +630,9 @@
 
     <!-- UserInfo Decryption and Signature Validation Done -->
 
-    <!-- TODO turn this into a ClaimsValidator actions -->
+        <!-- This is a very simplified and hard coded version of the claims verification used for a JWT. Maybe look to replace -->
     <bean id="ValidateUserInfoPlainResponseClaims" parent="NestedWebFlowProfileActionAdaptor" scope="prototype"
-        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateUserInfoClaims"
+        class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateUserInfoJSONObjectClaims"
         p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup" />
 
 
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
index 807aa84..8c6c8e9 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
@@ -134,7 +134,7 @@
     <!-- A plain JWT will skip token validation and go straight to claims validation -->
     <decision-state id="CheckUserInfoResponseType">        <if
             test="CheckUserInfoPlainResponseTypeCondition.test(opensamlProfileRequestContext.getSubcontext('net.shibboleth.idp.authn.context.AuthenticationContext').getSubcontext('org.opensaml.profile.context.ProfileRequestContext'))"
-            then="ValidateUserInfoClaimsSet" else="ValidateUserInfoJWT"/>
+            then="ValidateUserInfoPlaimClaimsSet" else="ValidateUserInfoJWT"/>
     </decision-state>
     
     <!-- Actions to perform if the UserInfo response is a JWT type -->
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
index 525aae7..fc89950 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
@@ -217,6 +217,23 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
             + "  \"nickname\": \"Dee\",\n"
             + "  \"family_name\": \"User\"\n"
             + "}";
+    
+    /** Mock JSON Object response from the UserInfo endpoint.*/
+    @Nonnull @NotEmpty
+    protected final String USERINFO_RESPONSE_NO_SUB ="{\n"
+            + "  \"website\": \"https://openid.net/\",\n"
+            + "  \"zoneinfo\": \"America/Los_Angeles\",\n"
+            + "  \"birthdate\": \"2000-02-03\",\n"
+            + "  \"gender\": \"female\",\n"
+            + "  \"preferred_username\": \"d.tu\",\n"
+            + "  \"given_name\": \"Demo\",\n"
+            + "  \"middle_name\": \"Theresa\",\n"
+            + "  \"locale\": \"en-US\",\n"
+            + "  \"updated_at\": 1580000000,\n"
+            + "  \"name\": \"Demo T. User\",\n"
+            + "  \"nickname\": \"Dee\",\n"
+            + "  \"family_name\": \"User\"\n"
+            + "}";
 
     
     /** Path to the flow to be tested.*/
@@ -313,219 +330,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
                 new ClassPathResource("conf/authn/rp-credentials.xml"), null);
     }
     
-    /**
-     * Create an OAuth access token with a runtime constructed id_token. This allows the
-     * expiry to be current. The token is signed with the client_secret using HS256.
-     * 
-     * TODO the JWT is signed with a local MAC and is not consistent with the OP metadata. Might need to change this
-     * 
-     * @return a serialized access token response.
-     * 
-     * @throws Exception on error.
-     */
-    private String createAccessTokenResponseJSON() throws Exception {
-        final var header = new JWSHeader.Builder(JWSAlgorithm.HS256)
-                .type(JOSEObjectType.JWT)
-                .build();
-        final var payload = new JWTClaimsSet.Builder()
-                .issuer(OP_ISSUER_ID)
-                .audience(List.of(CLIENT_ID,"demo_rp2"))
-                .subject("jdoe")
-                .claim("nonce", "abadnonce")
-                .claim("azp", CLIENT_ID)
-                .claim("name","jdoe")
-                .expirationTime(Date.from(Instant.now().plusSeconds(120)))
-                .build();
-        payload.getClaims().forEach((k,v) -> log.debug("{}:{}",k,v));
-        final var signedJWT = new SignedJWT(header,payload);
-        signedJWT.sign(new MACSigner(CLIENT_SECRET));
-        final String accessTokenSerialized = "{\n"
-        + "  \"access_token\": \"W0y5aDNAzEPNpSzu1cuMG904BZuQFZJUUwG5F3ct0zydZWy1ji\",\n"
-        + "  \"token_type\": \"Bearer\",\n"
-        + "  \"id_token\": \""+signedJWT.serialize()+"\",\n"
-        + "  \"scope\": \"openid\"\n"
-        + "}";
-        log.debug("Access token: \n {}",accessTokenSerialized);
-        return accessTokenSerialized;
-    }
     
-    /**
-     * Create an OAuth access token with a runtime constructed id_token. This allows the
-     * expiry to be current. The token is signed using HS256 and encrypted using the 'Direct Encryption' (dir)
-     * management mode i.e. no key wrapping. Both use the shared client_secret.
-     * 
-     * @return a serialized access token response.
-     * 
-     * @throws Exception on error.
-     */
-    private String createAccessTokenResponseJSONSignedAndEncrypted() throws Exception {
-        final var header = new JWSHeader.Builder(JWSAlgorithm.HS256)
-                .type(JOSEObjectType.JWT)
-                .build();
-        final var payload = new JWTClaimsSet.Builder()
-                .issuer(OP_ISSUER_ID)
-                .audience(List.of(CLIENT_ID,"demo_rp2"))
-                .subject("jdoe")
-                .claim("nonce", "abadnonce")
-                .claim("azp", CLIENT_ID)
-                .claim("name","jdoe")
-                .expirationTime(Date.from(Instant.now().plusSeconds(120)))
-                .build();
-        payload.getClaims().forEach((k,v) -> log.debug("{}:{}",k,v));
-        final var signedJWT = new SignedJWT(header,payload);
-        signedJWT.sign(new MACSigner(CLIENT_SECRET));
-        
-        final JWEObject jweObject = 
-                new JWEObject(new JWEHeader.Builder(JWEAlgorithm.DIR, EncryptionMethod.A256GCM)
-                .contentType("JWT")
-                .build(),
-                new Payload(signedJWT));
-        jweObject.encrypt(new DirectEncrypter(CLIENT_SECRET.getBytes(StandardCharsets.UTF_8)));
-        final EncryptedJWT jwe = EncryptedJWT.parse(jweObject.serialize());
-        
-        final String accessTokenSerialized = "{\n"
-        + "  \"access_token\": \"W0y5aDNAzEPNpSzu1cuMG904BZuQFZJUUwG5F3ct0zydZWy1ji\",\n"
-        + "  \"token_type\": \"Bearer\",\n"
-        + "  \"id_token\": \""+jwe.serialize()+"\",\n"
-        + "  \"scope\": \"openid\"\n"
-        + "}";
-        log.debug("Access token: \n {}",accessTokenSerialized);
-        return accessTokenSerialized;
-    }
-    
-    /**
-     * Create an OAuth access token with a runtime constructed id_token. This allows the
-     * expiry to be current. The token is signed using HS256 and encrypted using a key encryption
-     * management mode.
-     * 
-     * @return a serialized access token response.
-     * 
-     * @throws Exception on error.
-     */
-    private Pair<String, RSAKey> createAccessTokenResponseJSONSignedAndAsymmetricEncrypted() throws Exception {
-        final var header = new JWSHeader.Builder(JWSAlgorithm.HS256)
-                .type(JOSEObjectType.JWT)
-                .build();
-        final var payload = new JWTClaimsSet.Builder()
-                .issuer(OP_ISSUER_ID)
-                .audience(List.of(CLIENT_ID,"demo_rp2"))
-                .subject("jdoe")
-                .claim("nonce", "abadnonce")
-                .claim("azp", CLIENT_ID)
-                .claim("name","jdoe")
-                .expirationTime(Date.from(Instant.now().plusSeconds(120)))
-                .build();
-        payload.getClaims().forEach((k,v) -> log.debug("{}:{}",k,v));
-        final var signedJWT = new SignedJWT(header,payload);
-        signedJWT.sign(new MACSigner(CLIENT_SECRET));
-        
-        
-        final RSAKey keyRecipient = new RSAKeyGenerator(2048)
-                .keyID("1")
-                .keyUse(KeyUse.ENCRYPTION)
-                .generate();
-        
-        final JWEObject jweObject = 
-                new JWEObject(new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
-                .contentType("JWT")
-                .build(),
-                new Payload(signedJWT));
-        jweObject.encrypt(new RSAEncrypter(keyRecipient.toPublicJWK()));
-        final EncryptedJWT jwe = EncryptedJWT.parse(jweObject.serialize());
-        
-        final String accessTokenSerialized = "{\n"
-        + "  \"access_token\": \"W0y5aDNAzEPNpSzu1cuMG904BZuQFZJUUwG5F3ct0zydZWy1ji\",\n"
-        + "  \"token_type\": \"Bearer\",\n"
-        + "  \"id_token\": \""+jwe.serialize()+"\",\n"
-        + "  \"scope\": \"openid\"\n"
-        + "}";
-        log.debug("Access token: \n {}",accessTokenSerialized);
-        return new Pair<String, RSAKey>(accessTokenSerialized, keyRecipient);
-    }
-    
-    /**
-     * Create a signed UserInfo response JWT.
-     * 
-     * @param issuer the issuer
-     * @param audience the audience
-     * @return the signed JWT
-     * @throws JOSEException on error
-     */
-    private Pair<ECKey, SignedJWT> createAsymetricSignedUserInfoJWTResponseJSON(
-            final String issuer, final String audience) 
-            throws JOSEException {
-        
-        final var key = new ECKeyGenerator(Curve.P_256).keyID("123").generate();
-        
-        final var header = new JWSHeader.Builder(JWSAlgorithm.ES256)
-                .type(JOSEObjectType.JWT)
-                .keyID(key.getKeyID())
-                .build();
-        final var payload = new JWTClaimsSet.Builder()
-                .issuer(issuer)
-                .audience(audience)
-                .subject("jdoe")
-                .claim("preferred_username", "jdoe")
-                .claim("name", "J Doe")
-                .build();
-        
-        final var signedJWT = new SignedJWT(header, payload);
-        signedJWT.sign(new ECDSASigner(key.toECPrivateKey()));
-        return new Pair(key,signedJWT);
-    }
-    
-    /**
-     * Create a Plain UserInfo response JWT.
-     * 
-     * @param issuer the issuer
-     * @param audience the audience
-     * @return the signed JWT
-     * @throws JOSEException on error
-     */
-    private PlainJWT createPlainUserInfoJWTResponseJSON(final String issuer, final String audience) 
-            throws JOSEException {
-        
-        
-        final var payload = new JWTClaimsSet.Builder()
-                .issuer(issuer)
-                .audience(audience)
-                .subject("jdoe")
-                .claim("preferred_username", "jdoe")
-                .claim("name", "J Doe")
-                .build();
-        
-        return new PlainJWT(payload);
-    }
-    
-    /**
-     * Create an asymetrically signed and encrypted UserInfo response JWT. Return both signature and encryption
-     * keys alongside the EncryptedJWT in the response.
-     * 
-     * @param issuer the issuer
-     * @param audience the audience
-     * @return the signed JWT alongside the pair of signature and encryption keys
-     * 
-     * @throws JOSEException on error
-     */
-    private Pair<Pair<ECKey, RSAKey>, EncryptedJWT> createAsymetricSignedAndAssymetricEncryptedUserInfoJWTResponse(
-            final String issuer, final String audience) throws Exception {
-        
-        final RSAKey keyRecipient = new RSAKeyGenerator(2048)
-                .keyID("2")
-                .keyUse(KeyUse.ENCRYPTION)
-                .generate();
-        
-        final var keySignedJWTPair = createAsymetricSignedUserInfoJWTResponseJSON(issuer, audience);
-        
-        final JWEObject jweObject = 
-                new JWEObject(new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
-                .contentType("JWT")
-                .build(),
-                new Payload(keySignedJWTPair.getSecond()));
-        jweObject.encrypt(new RSAEncrypter(keyRecipient.toPublicJWK()));
-        return new Pair<Pair<ECKey, RSAKey>, EncryptedJWT>(
-                new Pair(keySignedJWTPair.getFirst(),keyRecipient), EncryptedJWT.parse(jweObject.serialize()));
-    }
     
     /**
      * Create a running server that mimics responses from an OpenID Connect provider.
@@ -613,6 +418,66 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
     }
     
     
+    /**
+     * Create a direct encryption {@link JWKCredential} from the given shared secret.
+     * 
+     * @param secret the secret to convert to a {@link JWKCredential}.
+     * 
+     * @return the credential
+     */
+    private JWKCredential createDirectEncryptionCredentialFromSharedSecret(final String secret) {
+        final BasicExpiringJWKCredential jwkCredential = new BasicExpiringJWKCredential();
+        jwkCredential.setSecretKey(new SecretKeySpec(JWSAssemblyUtils.getSecretBytes(secret), "NONE"));
+        jwkCredential.setCredentialExpiresAt(Duration.ZERO);
+        jwkCredential.setUsageType(UsageType.UNSPECIFIED);
+        jwkCredential.getCredentialContextSet().add(
+                new JWKEncryptionCredentialContext(EncryptionMethod.A256GCM));
+        jwkCredential.setKid("mockKey");
+        jwkCredential.getKeyNames().add("mockKey");
+        jwkCredential.setAlgorithm(JWEAlgorithm.DIR);
+        return jwkCredential;
+    }
+    
+    /**
+     * Create a simple client credential from from the given shared secret.
+     * 
+     * @param secret the secret to convert to a {@link JWKCredential}.
+     * 
+     * @return the credential
+     */
+    //TODO used for both signing and encryption, so alg needs to reflect this
+    private JWKCredential createClientSecretCredential(final String secret) {
+        final BasicExpiringJWKCredential jwkCredential = new BasicExpiringJWKCredential();
+        jwkCredential.setSecretKey(new SecretKeySpec(JWSAssemblyUtils.getSecretBytes(secret), "NONE"));
+        jwkCredential.setCredentialExpiresAt(Duration.ZERO);
+        jwkCredential.setUsageType(UsageType.UNSPECIFIED);
+        jwkCredential.setKid("mockKey");
+        jwkCredential.getKeyNames().add("mockKey");
+        return jwkCredential;
+    }
+    
+    /**
+     * Create a direct encryption {@link JWKCredential} from the given shared secret.
+     * 
+     * @param secret the secret to convert to a {@link JWKCredential}.
+     * 
+     * @return the credential
+     * @throws JOSEException 
+     */
+    private JWKCredential createKeyEncryptionCredential(final RSAKey secret) throws JOSEException {
+        final BasicExpiringJWKCredential jwkCredential = new BasicExpiringJWKCredential();
+        jwkCredential.setPrivateKey(secret.toPrivateKey());
+        jwkCredential.setPublicKey(secret.toPublicKey());
+        jwkCredential.setCredentialExpiresAt(Duration.ZERO);
+        jwkCredential.setUsageType(UsageType.ENCRYPTION);
+        
+        jwkCredential.setKid(secret.getKeyID());
+        jwkCredential.getKeyNames().add("mockKey");
+        jwkCredential.setAlgorithm(secret.getAlgorithm());
+        return jwkCredential;
+    }
+    
+    
     @Override
     @Nonnull protected ProfileRequestContext buildProfileRequestContext(@Nonnull final String flowId,
             @Nonnull final boolean forceAuthn,
@@ -654,14 +519,50 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         assertCurrentStateEquals("AuthRequest");
     }
     
+    /**
+     * Create a basic security configuration, which can be overriden per test if required.
+     * 
+
+     * @return the basic security configuration.
+     */
+    private OIDCSecurityConfiguration createBasicSecurityConfigAndValidationParams() {
+
+        final var securityConfig = new OIDCSecurityConfiguration();
+        
+        final var idTokenSigValConfig = new BasicSignatureValidationConfiguration<SignedJWT>();
+        idTokenSigValConfig.setSignatureTrustEngine(
+                new ExplicitKeySignedJWTTrustEngine(new CriterionCredentialResolver(), 
+                        new BasicJOSEObjectCredentialResolver()));
+        securityConfig.setIdTokenJwtSignatureValidationConfig(idTokenSigValConfig);
+        
+        final var userInfoTokenSigValConfig = new BasicSignatureValidationConfiguration<SignedJWT>();
+        userInfoTokenSigValConfig.setSignatureTrustEngine(
+                new ExplicitKeySignedJWTTrustEngine(new CriterionCredentialResolver(), 
+                        new BasicJOSEObjectCredentialResolver()));
+        securityConfig.setUserInfoTokenJwtSignatureValidationConfig(userInfoTokenSigValConfig);
+        
+        //The CEK resolver just resolves keys from the criteria set.
+        final var idTokenDecryptConfig = new BasicJWTDecryptionConfiguration();        
+        idTokenDecryptConfig.setContentEncryptionKeyCredentialResolver(new CriterionCredentialResolver());
+        securityConfig.setIdTokenJwtDecryptionConfig(idTokenDecryptConfig);
+        
+        final var userInfoDecryptConfig = new BasicJWTDecryptionConfiguration();        
+        userInfoDecryptConfig.setContentEncryptionKeyCredentialResolver(new CriterionCredentialResolver());
+        securityConfig.setUserInfoJwtDecryptionConfig(userInfoDecryptConfig);
+        
+        
+        return securityConfig;
+    }
+    
     
     /** 
      * Test the flow from the external authorization request to the end of the flow.
+     * Using a MAC signed id_token and plain UserInfo JSON Response.
      * 
      * @throws Exception on error.
      */
     @Test 
-    public void testAuthnFlowFromAuthorizationCallback() throws Exception {
+    public void testAuthnFlowFromAuthorizationCallback_Using_MACSignedIDToken_PlainUserInfo() throws Exception {
         
         setFlowPath(FLOW);
         setFlowModelResources(flowResources);
@@ -678,80 +579,74 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         // First is token exchange
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/json")
-                .setBody(createAccessTokenResponseJSON()));
-        // Second is userInfo
+                .setBody(TestTokenHelper.createAccessTokenResponseJSONWithHMACIDToken(
+                        OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID, "demo_rp2"), CLIENT_SECRET)));
+        // Second is plain userInfo
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/json")
                 .setBody(USERINFO_RESPONSE));
         mockOPServer.start(9918);
         
 
-        final FlowExecutionImpl flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
+        final var flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
                 .createFlowExecution(getFlowDefinition());
-        final ProfileRequestContext prc =  buildProfileRequestContext("authn/OIDCRelyingParty", false,false);
-        prc.getSubcontext(AuthenticationContext.class).setAuthenticatingAuthority(OP_ISSUER_ID);
-        
-        // create a nested PRC under the authentication context
-        final ProfileRequestContext nestPrc = (ProfileRequestContext) 
-                prc.getSubcontext(AuthenticationContext.class).addSubcontext(new ProfileRequestContext(), true);  
+        final ProfileRequestContext prc = populateBasicContextTreeFromAuthnResponse(flowExecution); 
+                        
         
-        // Add under nest PRC
-        final RelyingPartyContext partyContext = new RelyingPartyContext();
-        final OIDCAuthorizationConfiguration partyConfig = new OIDCAuthorizationConfiguration();        
-        partyContext.setProfileConfig(partyConfig);
-        partyConfig.setClientAuthenticationMethod("client_secret_basic");
-        partyConfig.setClientId(CLIENT_ID);
-        partyConfig.setClientCredential(createDirectEncryptionCredentialFromSharedSecret(CLIENT_SECRET));
-        // Set a default security config for the profile config
-        final OIDCSecurityConfiguration secConfig = new OIDCSecurityConfiguration();
-        final BasicSignatureValidationConfiguration<SignedJWT> sigValidation = 
-                new BasicSignatureValidationConfiguration<>();
-        sigValidation.setSignatureTrustEngine(new ExplicitKeySignedJWTTrustEngine(
-                new CredentialResolver() {
-            
-            @Override
-            public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
-                final BasicJWKCredential jwkCredential = new BasicJWKCredential();
-                jwkCredential.setAlgorithm(JWSAlgorithm.HS256);
-                jwkCredential.setKid("secret_key");                
-                jwkCredential.setSecretKey(new SecretKeySpec(
-                        JWSAssemblyUtils.getSecretBytes(CLIENT_SECRET), "NONE"));              
-                return jwkCredential;
-            }
-            
-            @Override
-            public Iterable<Credential> resolve(final CriteriaSet criteria) throws ResolverException {
-                return List.of(resolveSingle(criteria));
-            }
-        }));
+        updateFlowExecution(flowExecution);
         
-        secConfig.setIdTokenJwtSignatureValidationConfig(sigValidation);
-        partyConfig.setSecurityConfiguration(secConfig);
-                
-        final RelyingPartyConfiguration rPartyConfig = new RelyingPartyConfiguration();        
-        rPartyConfig.setResponderId("http://idp.example.com/");
-        partyContext.setConfiguration(rPartyConfig);
-        nestPrc.addSubcontext(partyContext);
+        //set start view and ending event to transition on.
+        externalContext.setEventId("proceed");
+        setCurrentState("AuthRequest");       
+        resumeFlow(externalContext);
         
+        mockOPServer.shutdown();
         
+        //assert success conditions
+        assertFlowExecutionEnded();
+        assertNotNull(prc.getSubcontext(AuthenticationContext.class));
+        assertNotNull(prc.getSubcontext(SubjectCanonicalizationContext.class));
+        assertEquals(prc.getSubcontext(SubjectCanonicalizationContext.class).getPrincipalName(),"jdoe");
+      
        
-        // Setup outbound context
-        final MessageContext outMsgCtx = new MessageContext();        
-        outMsgCtx.setMessage(createAuthenticationRequest());        
-        outMsgCtx.addSubcontext(createPeerContext());
-        outMsgCtx.addSubcontext(createResponseTypeAndModeContext());
-        nestPrc.setOutboundMessageContext(outMsgCtx);    
-        outMsgCtx.getSubcontext(OIDCPeerEntityContext.class).addSubcontext(createOAuth2ClientContext(CLIENT_ID,null));
+    }
+    
+    /** 
+     * Test the flow from the external authorization request to the end of the flow.
+     * Using a MAC signed id_token and an invalid UserInfo JSON Response - it has not subject.
+     * 
+     * @throws Exception on error.
+     */
+    @Test 
+    public void testAuthnFlowFromAuthorizationCallback_Using_MACSignedIDToken_InvalidPlainUserInfo() throws Exception {
         
-        // Setup inbound context.
-        final MessageContext inMsgCtx = new MessageContext();
-        inMsgCtx.setMessage(createAuthenticationResponse());
-        nestPrc.setInboundMessageContext(inMsgCtx);
+        setFlowPath(FLOW);
+        setFlowModelResources(flowResources);
+        setSubflows(subflows);        
         
-        // Add prc to flow.
-        prc.getSubcontext(AuthenticationContext.class)
-        .addSubcontext(new ExternalAuthenticationContext(new ExternalAuthenticationImpl(false)));
-                        flowExecution.getConversationScope().put("opensamlProfileRequestContext",prc);
+        final Map<String,String> mockProperties = Map.of(                
+                "idp.entityID", "http://idp.example.com/",
+                "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID,
+                "idp.oidc.rp.redirecturl.allowedOrigins", RP_ALLOWED_ORIGINS);
+        
+        setMockProperties(mockProperties);
+        
+        final MockWebServer mockOPServer = createSimpleServer();
+        // First is token exchange
+        mockOPServer.enqueue(new MockResponse().setResponseCode(200)
+                .setHeader("content-type", "application/json")
+                .setBody(TestTokenHelper.createAccessTokenResponseJSONWithHMACIDToken(
+                        OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID, "demo_rp2"), CLIENT_SECRET)));
+        // Second is plain userInfo
+        mockOPServer.enqueue(new MockResponse().setResponseCode(200)
+                .setHeader("content-type", "application/json")
+                .setBody(USERINFO_RESPONSE_NO_SUB));
+        mockOPServer.start(9918);
+        
+
+        final var flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
+                .createFlowExecution(getFlowDefinition());
+        final ProfileRequestContext prc = populateBasicContextTreeFromAuthnResponse(flowExecution); 
                         
         
         updateFlowExecution(flowExecution);
@@ -766,39 +661,14 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         //assert success conditions
         assertFlowExecutionEnded();
         assertNotNull(prc.getSubcontext(AuthenticationContext.class));
-        assertNotNull(prc.getSubcontext(SubjectCanonicalizationContext.class));
-        assertEquals(prc.getSubcontext(SubjectCanonicalizationContext.class).getPrincipalName(),"jdoe");
-      
+        assertNull(prc.getSubcontext(SubjectCanonicalizationContext.class));      
        
     }
     
-    private OIDCSecurityConfiguration createSecurityConfigAndValidationParamsForHMAC(
-            @Nonnull final byte[] secret, @Nonnull final String algo) {
-
-        final var securityConfig = new OIDCSecurityConfiguration();
-        final var sigValConfig = new BasicSignatureValidationConfiguration<SignedJWT>();
-        final var credResolver = new CredentialResolver() {
-            
-            @Override
-            public Credential resolveSingle(final CriteriaSet criteria) throws ResolverException {
-                final var cred = new BasicJWKCredential();
-                cred.setAlgorithm(Algorithm.parse(algo));
-                cred.setSecretKey(new SecretKeySpec(secret, algo));
-                return cred;
-            }
-            
-            @Override
-            public Iterable<Credential> resolve(final CriteriaSet criteria) throws ResolverException {
-                return List.of(resolveSingle(criteria));
-            }
-        };
-        sigValConfig.setSignatureTrustEngine(new ExplicitKeySignedJWTTrustEngine(credResolver));
-        securityConfig.setIdTokenJwtSignatureValidationConfig(sigValConfig);
-        return securityConfig;
-    }
+    
     
     @Test 
-    public void testAuthnFlowFromAuthorizationCallback_UsingSignedJWTUserInfoResponse() throws Exception {
+    public void testAuthnFlowFromAuthorizationCallback_Using_HMAC_UserInfo_And_IDToken_Response() throws Exception {
         
         setFlowPath(FLOW);
         setFlowModelResources(flowResources);
@@ -815,55 +685,20 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         // First is token exchange
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/json")
-                .setBody(createAccessTokenResponseJSON()));
+                .setBody(TestTokenHelper.createAccessTokenResponseJSONWithHMACIDToken(
+                        OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID, "demo_rp2"), CLIENT_SECRET)));
         // Second is userInfo
+        final var userInfoToken = TestTokenHelper.createHMACSignedUserInfoJWTResponseJSON(
+                OP_ISSUER_ID,CLIENT_ID, List.of(CLIENT_ID), CLIENT_SECRET);
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/jwt")
-                .setBody(createAsymetricSignedUserInfoJWTResponseJSON(OP_ISSUER_ID,CLIENT_ID).serialize()));
+                .setBody(userInfoToken.serialize()));
         mockOPServer.start(9918);
         
 
-        final FlowExecutionImpl flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
+        final var flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
                 .createFlowExecution(getFlowDefinition());
-        final ProfileRequestContext prc =  buildProfileRequestContext("authn/OIDCRelyingParty", false,false);
-        prc.getSubcontext(AuthenticationContext.class).setAuthenticatingAuthority(OP_ISSUER_ID);
-        
-        // create a nested PRC under the authentication context
-        final ProfileRequestContext nestPrc = (ProfileRequestContext) 
-                prc.getSubcontext(AuthenticationContext.class).addSubcontext(new ProfileRequestContext(), true);  
-        
-        // Add under nest PRC
-        final RelyingPartyContext partyContext = new RelyingPartyContext();
-        final OIDCAuthorizationConfiguration partyConfig = new OIDCAuthorizationConfiguration();        
-        partyContext.setProfileConfig(partyConfig);
-        
-        partyConfig.setSecurityConfiguration(createSecurityConfigAndValidationParamsForHMAC(
-                CLIENT_SECRET.getBytes(), "HS256"));
-        partyConfig.setClientAuthenticationLookupStrategy(p ->
-            new ClientSecretBasic(new ClientID(CLIENT_ID), new Secret("secret")));
-                
-        final RelyingPartyConfiguration rPartyConfig = new RelyingPartyConfiguration();
-        rPartyConfig.setResponderId("http://idp.example.com/");
-        partyContext.setConfiguration(rPartyConfig);
-        nestPrc.addSubcontext(partyContext);
-
-        // Setup outbound context
-        final MessageContext outMsgCtx = new MessageContext();        
-        outMsgCtx.setMessage(createAuthenticationRequest());        
-        outMsgCtx.addSubcontext(createPeerContext());
-        outMsgCtx.addSubcontext(createResponseTypeAndModeContext());
-        nestPrc.setOutboundMessageContext(outMsgCtx);  
-        outMsgCtx.getSubcontext(OIDCPeerEntityContext.class).addSubcontext(createOAuth2ClientContext(CLIENT_ID,null));
-        
-        // Setup inbound context.
-        final MessageContext inMsgCtx = new MessageContext();
-        inMsgCtx.setMessage(createAuthenticationResponse());
-        nestPrc.setInboundMessageContext(inMsgCtx);
-        
-        // Add prc to flow.
-        prc.getSubcontext(AuthenticationContext.class)
-        .addSubcontext(new ExternalAuthenticationContext(new ExternalAuthenticationImpl(false)));
-                        flowExecution.getConversationScope().put("opensamlProfileRequestContext",prc);
+        final ProfileRequestContext prc = populateBasicContextTreeFromAuthnResponse(flowExecution);  
                         
         
         updateFlowExecution(flowExecution);
@@ -903,10 +738,12 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         // First is token exchange
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/json")
-                .setBody(createAccessTokenResponseJSONSignedAndEncrypted()));
+                .setBody(TestTokenHelper.createAccessTokenResponseJSONIDTokenSignedAndDirEncrypted(
+                        OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID, "demo_rp2"), CLIENT_SECRET)));
         // Second is userInfo
-        final var userInfoTokenAndKey = 
-                createAsymetricSignedAndAssymetricEncryptedUserInfoJWTResponse(OP_ISSUER_ID,CLIENT_ID);
+        final var userInfoTokenAndKey =
+                TestTokenHelper.createAsymetricSignedAndAsymetricEncryptedUserInfoJWTResponse(
+                        OP_ISSUER_ID,List.of(CLIENT_ID), CLIENT_ID);
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/jwt")
                 .setBody(userInfoTokenAndKey.getSecond().serialize()));
@@ -1042,7 +879,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
     }
     
     /**
-     * Uses symmetric MAC and asymmetric encryption.
+     * Uses symmetric MAC and asymmetric encryption. Plain UserInfo response.
      * 
      * @throws Exception on error.
      */
@@ -1062,7 +899,9 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         
         setMockProperties(mockProperties);
         
-        final Pair<String, RSAKey> accessTokenAndKey = createAccessTokenResponseJSONSignedAndAsymmetricEncrypted();
+        final Pair<String, RSAKey> accessTokenAndKey = 
+                TestTokenHelper.createAccessTokenResponseJSONWithSignedAndAsymmetricEncryptedIDToken(
+                OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID), CLIENT_SECRET);
         final MockWebServer mockOPServer = createSimpleServer();
         // First is token exchange
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
@@ -1169,8 +1008,9 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
        
     }
     
+    //FIXME why are we testing a plain UserInfo JWT? is this permissible
     @Test 
-    public void testAuthnFlowFromAuthorizationCallback_UsingPlainJWTUserInfoResponse() 
+    public void testAuthnFlowFromAuthorizationCallback_UsingPlainJSONObjectUserInfoResponse() 
             throws Exception {
         
         setFlowPath(FLOW);
@@ -1189,17 +1029,41 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         // First is token exchange
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/json")
-                .setBody(createAccessTokenResponseJSON()));
+                .setBody(TestTokenHelper.createAccessTokenResponseJSONWithHMACIDToken(
+                        OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID, "demo_rp2"), CLIENT_SECRET)));
         // Second is userInfo
         mockOPServer.enqueue(new MockResponse().setResponseCode(200)
                 .setHeader("content-type", "application/jwt")
-                .setBody(createPlainUserInfoJWTResponseJSON(OP_ISSUER_ID,CLIENT_ID)
+                .setBody(TestTokenHelper.createPlainUserInfoJWTResponseJSON(OP_ISSUER_ID,CLIENT_ID)
                         .serialize()));
         mockOPServer.start(9918);
         
 
-        final FlowExecutionImpl flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
+        final var flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
                 .createFlowExecution(getFlowDefinition());
+        final ProfileRequestContext prc = populateBasicContextTreeFromAuthnResponse(flowExecution);           
+        
+        updateFlowExecution(flowExecution);
+        
+        //set start view and ending event to transition on.
+        externalContext.setEventId("proceed");
+        setCurrentState("AuthRequest");       
+        resumeFlow(externalContext);
+        
+        mockOPServer.shutdown();
+        
+        //assert success conditions
+        assertFlowExecutionEnded();
+        assertNotNull(prc.getSubcontext(AuthenticationContext.class));
+        assertNotNull(prc.getSubcontext(SubjectCanonicalizationContext.class));
+        assertEquals(prc.getSubcontext(SubjectCanonicalizationContext.class).getPrincipalName(),"jdoe");
+      
+       
+    }
+    
+    private ProfileRequestContext populateBasicContextTreeFromAuthnResponse(
+            final FlowExecutionImpl flowExecution) throws Exception {
+        
         final ProfileRequestContext prc =  buildProfileRequestContext("authn/OIDCRelyingParty", false,false);
         prc.getSubcontext(AuthenticationContext.class).setAuthenticatingAuthority(OP_ISSUER_ID);
         
@@ -1211,12 +1075,14 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         final RelyingPartyContext partyContext = new RelyingPartyContext();
         final OIDCAuthorizationConfiguration partyConfig = new OIDCAuthorizationConfiguration();  
         partyContext.setProfileConfig(partyConfig);
-        partyConfig.setClientAuthenticationLookupStrategy(p ->
-                new ClientSecretBasic(new ClientID(CLIENT_ID), new Secret("secret")));
+        partyConfig.setClientCredential(createClientSecretCredential(CLIENT_SECRET));
+        partyConfig.setClientAuthenticationMethod("client_secret_basic");
+        partyConfig.setClientId(CLIENT_ID);
         final RelyingPartyConfiguration rPartyConfig = new RelyingPartyConfiguration();
         rPartyConfig.setResponderId("http://idp.example.com/");
         partyContext.setConfiguration(rPartyConfig);
         nestPrc.addSubcontext(partyContext);
+        partyConfig.setSecurityConfiguration(createBasicSecurityConfigAndValidationParams());
        
         // Setup outbound context
         final MessageContext outMsgCtx = new MessageContext();        
@@ -1236,66 +1102,10 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         .addSubcontext(new ExternalAuthenticationContext(new ExternalAuthenticationImpl(false)));
                         flowExecution.getConversationScope().put("opensamlProfileRequestContext",prc);
                         
+         return prc;
         
-        updateFlowExecution(flowExecution);
-        
-        //set start view and ending event to transition on.
-        externalContext.setEventId("proceed");
-        setCurrentState("AuthRequest");       
-        resumeFlow(externalContext);
-        
-        mockOPServer.shutdown();
-        
-        //assert success conditions
-        assertFlowExecutionEnded();
-        assertNotNull(prc.getSubcontext(AuthenticationContext.class));
-        assertNotNull(prc.getSubcontext(SubjectCanonicalizationContext.class));
-        assertEquals(prc.getSubcontext(SubjectCanonicalizationContext.class).getPrincipalName(),"jdoe");
-      
-       
-    }
-    
-    /**
-     * Create a direct encryption {@link JWKCredential} from the given shared secret.
-     * 
-     * @param secret the secret to convert to a {@link JWKCredential}.
-     * 
-     * @return the credential
-     */
-    private JWKCredential createDirectEncryptionCredentialFromSharedSecret(final String secret) {
-        final BasicExpiringJWKCredential jwkCredential = new BasicExpiringJWKCredential();
-        jwkCredential.setSecretKey(new SecretKeySpec(JWSAssemblyUtils.getSecretBytes(secret), "NONE"));
-        jwkCredential.setCredentialExpiresAt(Duration.ZERO);
-        jwkCredential.setUsageType(UsageType.UNSPECIFIED);
-        jwkCredential.getCredentialContextSet().add(
-                new JWKEncryptionCredentialContext(EncryptionMethod.A256GCM));
-        jwkCredential.setKid("mockKey");
-        jwkCredential.getKeyNames().add("mockKey");
-        jwkCredential.setAlgorithm(JWEAlgorithm.DIR);
-        return jwkCredential;
-    }
-    
-    /**
-     * Create a direct encryption {@link JWKCredential} from the given shared secret.
-     * 
-     * @param secret the secret to convert to a {@link JWKCredential}.
-     * 
-     * @return the credential
-     * @throws JOSEException 
-     */
-    private JWKCredential createKeyEncryptionCredential(final RSAKey secret) throws JOSEException {
-        final BasicExpiringJWKCredential jwkCredential = new BasicExpiringJWKCredential();
-        jwkCredential.setPrivateKey(secret.toPrivateKey());
-        jwkCredential.setPublicKey(secret.toPublicKey());
-        jwkCredential.setCredentialExpiresAt(Duration.ZERO);
-        jwkCredential.setUsageType(UsageType.ENCRYPTION);
-        
-        jwkCredential.setKid(secret.getKeyID());
-        jwkCredential.getKeyNames().add("mockKey");
-        jwkCredential.setAlgorithm(secret.getAlgorithm());
-        return jwkCredential;
     }
-    
+
     
     /** 
      * Test the flow from the external authorization request to the end of the flow when an error
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java
new file mode 100644
index 0000000..e79dd48
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java
@@ -0,0 +1,327 @@
+/*
+ * 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.authn.oidc.rp.impl;
+
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.JWEHeader;
+import com.nimbusds.jose.JWEObject;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.Payload;
+import com.nimbusds.jose.crypto.DirectEncrypter;
+import com.nimbusds.jose.crypto.ECDSASigner;
+import com.nimbusds.jose.crypto.MACSigner;
+import com.nimbusds.jose.crypto.RSAEncrypter;
+import com.nimbusds.jose.jwk.Curve;
+import com.nimbusds.jose.jwk.ECKey;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+import com.nimbusds.jwt.EncryptedJWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.utilities.java.support.collection.Pair;
+
+/** A helper class for generating tokens for testing.*/
+public final class TestTokenHelper {
+    
+    /** Private constructor.*/
+    private TestTokenHelper() {
+        
+    }
+    
+    /**
+     * Build a basic {@link JWTClaimsSet} from the supplied parameters.
+     * 
+     * @param subject the subject
+     * @param nonce the nonce
+     * @param name the name
+     * @param opIssuerId the OP's issuer ID
+     * @param clientId the client_id
+     * @param audience the list of audiences
+     * 
+     * @return the JWT claims set.
+     */
+    public static JWTClaimsSet createBasicClaims(
+            final String subject, final String nonce, final String name,
+            final String opIssuerId, final String clientId, 
+            final List<String> audience) {
+        
+        return new JWTClaimsSet.Builder()
+                .issuer(opIssuerId)
+                .audience(audience)
+                .subject(subject)
+                .claim("nonce", nonce)
+                .claim("azp", clientId)
+                .claim("name",name)
+                .expirationTime(Date.from(Instant.now().plusSeconds(120)))
+                .build();
+    }
+    
+    /**
+     * Create an OAuth access token with a runtime constructed id_token. This allows the
+     * expiry to be current. The token is signed with the client_secret using HS256.
+     * 
+     * @param opIssuerId the OP's issuer ID
+     * @param clientId the client_id
+     * @param audience the list of audiences for the id_token token
+     * @param clientSecret the client_secret used to sign the id_token
+     * 
+     * 
+     * @return a serialized access token response.
+     * 
+     * @throws Exception on error.
+     */
+    public static String createAccessTokenResponseJSONWithHMACIDToken(
+            final String opIssuerId, final String clientId, 
+            final List<String> audience, final String clientSecret) throws Exception {
+        
+        final var header = new JWSHeader.Builder(JWSAlgorithm.HS256)
+                .type(JOSEObjectType.JWT)
+                .build();
+        
+        final var payload = createBasicClaims("jdoe", "abadnonce", "jdoe", opIssuerId, clientId, audience);
+       
+        final var signedJWT = new SignedJWT(header,payload);
+        signedJWT.sign(new MACSigner(clientSecret));
+        
+        return buildTemplateAccessTokenJSONResponse(signedJWT.serialize());
+    }
+    
+    /**
+     * Create an OAuth access token with a runtime constructed id_token. This allows the
+     * expiry to be current. The token is signed using HS256 and encrypted using the 'Direct Encryption' (dir)
+     * management mode i.e. no key wrapping. Both use the shared client_secret.
+     * 
+     * @param opIssuerId the OP's issuer ID
+     * @param clientId the client_id
+     * @param audience the list of audiences for the id_token token
+     * @param clientSecret the client_secret used to sign the id_token
+     * 
+     * @return a serialized access token response.
+     * 
+     * @throws Exception on error.
+     */
+    public static String createAccessTokenResponseJSONIDTokenSignedAndDirEncrypted(final String opIssuerId, 
+            final String clientId, final List<String> audience, final String clientSecret) throws Exception {
+        
+        final var header = new JWSHeader.Builder(JWSAlgorithm.HS256)
+                .type(JOSEObjectType.JWT)
+                .build();
+        
+        final var payload = createBasicClaims("jdoe", "abadnonce", "jdoe", opIssuerId, clientId, audience);
+        final var signedJWT = new SignedJWT(header,payload);
+        signedJWT.sign(new MACSigner(clientSecret));
+        
+        final JWEObject jweObject = 
+                new JWEObject(new JWEHeader.Builder(JWEAlgorithm.DIR, EncryptionMethod.A256GCM)
+                .contentType("JWT")
+                .build(),
+                new Payload(signedJWT));
+        jweObject.encrypt(new DirectEncrypter(clientSecret.getBytes(StandardCharsets.UTF_8)));
+        final EncryptedJWT jwe = EncryptedJWT.parse(jweObject.serialize());
+        
+        
+        return buildTemplateAccessTokenJSONResponse(jwe.serialize());
+    }
+    
+    /**
+     * Create an OAuth access token with a runtime constructed id_token. This allows the
+     * expiry to be current. The token is signed using HS256 (MAC) and encrypted using a key encryption
+     * management mode.
+     * 
+     * @param opIssuerId the OP's issuer ID
+     * @param clientId the client_id
+     * @param audience the list of audiences for the id_token token
+     * @param clientSecret the client_secret used to sign the id_token
+     * 
+     * @return a serialized access token response and the RSAKey used to encrypt the CEK.
+     * 
+     * @throws Exception on error.
+     */
+    public static Pair<String, RSAKey> createAccessTokenResponseJSONWithSignedAndAsymmetricEncryptedIDToken(
+            final String opIssuerId, 
+            final String clientId, final List<String> audience, final String clientSecret) throws Exception {
+        
+        final var header = new JWSHeader.Builder(JWSAlgorithm.HS256)
+                .type(JOSEObjectType.JWT)
+                .build();
+        
+        final var payload = createBasicClaims("jdoe", "abadnonce", "jdoe", opIssuerId, clientId, audience);
+        final var signedJWT = new SignedJWT(header,payload);
+        signedJWT.sign(new MACSigner(clientSecret));
+        
+        
+        final RSAKey keyRecipient = new RSAKeyGenerator(2048)
+                .keyID("1")
+                .keyUse(KeyUse.ENCRYPTION)
+                .generate();
+        
+        final JWEObject jweObject = 
+                new JWEObject(new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
+                .contentType("JWT")
+                .build(),
+                new Payload(signedJWT));
+        jweObject.encrypt(new RSAEncrypter(keyRecipient.toPublicJWK()));
+        final EncryptedJWT jwe = EncryptedJWT.parse(jweObject.serialize());
+
+        return new Pair<String, RSAKey>(buildTemplateAccessTokenJSONResponse(jwe.serialize()), keyRecipient);
+    }
+    
+    /**
+     * Create an RSA asymetrically signed and encrypted UserInfo response JWT. Return both signature and encryption
+     * keys alongside the EncryptedJWT in the response.
+     * 
+     * @param issuer the issuer
+     * @param audience the audience
+     * @param clientId the client_id
+     * @return the signed JWT alongside the pair of signature and encryption keys
+     * @throws Exception 
+     * 
+     * @throws JOSEException on error
+     */
+    public static Pair<Pair<ECKey, RSAKey>, EncryptedJWT> createAsymetricSignedAndAsymetricEncryptedUserInfoJWTResponse(
+            final String issuer, final List<String> audience, final String clientId) throws Exception {
+        
+        final RSAKey keyRecipient = new RSAKeyGenerator(2048)
+                .keyID("2")
+                .keyUse(KeyUse.ENCRYPTION)
+                .generate();
+        
+        final var keySignedJWTPair = createECDSASignedUserInfoJWTResponseJSON(issuer, clientId, audience);
+        
+        final JWEObject jweObject = 
+                new JWEObject(new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
+                .contentType("JWT")
+                .build(),
+                new Payload(keySignedJWTPair.getSecond()));
+        jweObject.encrypt(new RSAEncrypter(keyRecipient.toPublicJWK()));
+        
+        return new Pair<Pair<ECKey, RSAKey>, EncryptedJWT>(
+                new Pair<ECKey, RSAKey>(keySignedJWTPair.getFirst(),keyRecipient), 
+                EncryptedJWT.parse(jweObject.serialize()));
+    }
+    
+    /**
+     * Create an ECDSA signed UserInfo response JWT.
+     * 
+     * @param opIssuerId the issuer
+     * @param clientId the client_id
+     * @param audience the audience
+     * 
+     * @return the signed JWT
+     * 
+     * @throws JOSEException on error
+     */
+    public static Pair<ECKey, SignedJWT> createECDSASignedUserInfoJWTResponseJSON(
+            final String opIssuerId, final String clientId, final List<String> audience) 
+            throws JOSEException {
+        
+        final var key = new ECKeyGenerator(Curve.P_256).keyID("123").generate();
+        
+        final var header = new JWSHeader.Builder(JWSAlgorithm.ES256)
+                .type(JOSEObjectType.JWT)
+                .keyID(key.getKeyID())
+                .build();
+        
+        final var payload = createBasicClaims("jdoe", "adbadnonce", "Jdoe", opIssuerId, clientId, audience);
+        
+        final var signedJWT = new SignedJWT(header, payload);
+        signedJWT.sign(new ECDSASigner(key.toECPrivateKey()));
+        return new Pair<ECKey, SignedJWT>(key,signedJWT);
+    }
+    
+    /**
+     * Create a HMAC signed UserInfo response JWT.
+     * 
+     * @param opIssuerId the issuer
+     * @param clientId the client_id
+     * @param audience the audience
+     * @param clientSecret the client_secret
+     * 
+     * @return the signed JWT
+     * 
+     * @throws JOSEException on error
+     */
+    public static SignedJWT createHMACSignedUserInfoJWTResponseJSON(
+            final String opIssuerId, final String clientId, final List<String> audience, final String clientSecret) 
+            throws JOSEException {
+        
+        final var header = new JWSHeader.Builder(JWSAlgorithm.HS256)
+                .type(JOSEObjectType.JWT)
+                .build();
+
+        final var payload = createBasicClaims("jdoe", "adbadnonce", "Jdoe", opIssuerId, clientId, audience);
+        
+        final var signedJWT = new SignedJWT(header, payload);
+        signedJWT.sign(new MACSigner(clientSecret));
+        return signedJWT;
+    }
+    
+    /**
+     * Create a Plain UserInfo response JWT.
+     * 
+     * @param issuer the issuer
+     * @param audience the audience
+     * @return the signed JWT
+     * @throws JOSEException on error
+     */
+    public static PlainJWT createPlainUserInfoJWTResponseJSON(final String issuer, final String audience) 
+            throws JOSEException {
+        
+        
+        final var payload = new JWTClaimsSet.Builder()
+                .issuer(issuer)
+                .audience(audience)
+                .subject("jdoe")
+                .claim("preferred_username", "jdoe")
+                .claim("name", "J Doe")
+                .build();
+        
+        return new PlainJWT(payload);
+    }
+    
+    /**
+     * Build a simple OAuth2.0/OIDC Access Token JSON response using the serialized JWT.
+     * 
+     * @param serializedJWT the id_token serialized
+     * 
+     * @return An access token response
+     */
+    private static String buildTemplateAccessTokenJSONResponse(final String serializedJWT) {
+        return "{\n"
+                + "  \"access_token\": \"W0y5aDNAzEPNpSzu1cuMG904BZuQFZJUUwG5F3ct0zydZWy1ji\",\n"
+                + "  \"token_type\": \"Bearer\",\n"
+                + "  \"id_token\": \""+serializedJWT+"\",\n"
+                + "  \"scope\": \"openid\"\n"
+                + "}";
+    }
+
+}

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


More information about the commits mailing list