[java-oidc-common] 17/28: JOIDCRP-17 - Add JWT Encryption Parameter Resolver Support

Phil Smart philip.smart at jisc.ac.uk
Wed Oct 5 10:34:48 UTC 2022


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

philsmart pushed a commit to branch dev/JCOMOIDC-41
in repository java-oidc-common.

View the commit online:
http://git.shibboleth.net/view/?p=java-oidc-common.git;a=commit;h=0fca90316ad954da06bbd5b9db10049bdbd29345

commit 0fca90316ad954da06bbd5b9db10049bdbd29345
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Aug 19 09:39:35 2022 +0100

    JOIDCRP-17 - Add JWT Encryption Parameter Resolver Support
    
     - Move provider metadata parameter resolver to commons from RP
    
    https://shibboleth.atlassian.net/browse/JOIDCRP-17
---
 ...oviderMetadataEncryptionParametersResolver.java | 336 +++++++++++++++++
 .../BasicJWTEncryptionParametersResolverTest.java  |  97 +----
 ...erMetadataEncryptionParametersResolverTest.java | 411 +++++++++++++++++++++
 .../oidc/security/impl/TestCredentialHelper.java   | 182 +++++++++
 ...ider-resolver-remote-jwkset-response-no-alg.jwk |  66 ++++
 ...st-provider-resolver-remote-jwkset-response.jwk |  66 ++++
 .../test-resolver-provider-encryption.json         |  89 +++++
 7 files changed, 1166 insertions(+), 81 deletions(-)

diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ProviderMetadataEncryptionParametersResolver.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ProviderMetadataEncryptionParametersResolver.java
new file mode 100644
index 0000000..6b2184e
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/ProviderMetadataEncryptionParametersResolver.java
@@ -0,0 +1,336 @@
+/*
+ * 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.oidc.security.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.jwk.ECKey;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.KeyType;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.oidc.jwk.RemoteJwkSetCache;
+import net.shibboleth.oidc.security.JWTEncryptionConfiguration;
+import net.shibboleth.oidc.security.JWTEncryptionParameters;
+import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+import net.shibboleth.oidc.security.criterion.JWKSetCriterion;
+import net.shibboleth.oidc.security.criterion.JWTEncryptionConfigurationCriterion;
+import net.shibboleth.oidc.security.criterion.ProviderMetadataCriterion;
+import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.logic.FunctionSupport;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+
+/**
+ * An extension of {@link BasicJWTEncryptionParametersResolver} to support parameter resolution from an
+ * OpenID Provider's metadata (remote keyset), in addition to those resolved from local configuration by the 
+ * base class. 
+ * 
+ * <p>The set of supported and configured key transport ('alg') and encryption methods ('enc') are derived
+ * from the intersection of those supported by local configuration, and those supported by the downstream
+ * OpenID Provider. The order of those algorithms in the local configuration are preserved, and affect which 
+ * credential is chosen. As algorithm 'alg' and 'enc' support is optional in provider metadata, failure to 
+ * locate them will result in the default behaviour of the parent class being applied (resolve from local 
+ * configuration). If they are present, but are not compatible with the set configured in the encryption 
+ * configuration, no parameters are returned (a failure). </p>
+ * 
+ * <p>Generally, the following logic applies:</p>
+ * <ul>
+ * <li>Private KeyWrapping and direct encryption credentials are found in the local 
+ * {@link JWTEncryptionConfiguration}.</li>
+ * <li>Public KeyEncryption or KeyAgreement credentials are found in the OpenID Provider's KeySet. 
+ * The Provider's metadata must be contained inside a ProviderMetadataCriterion, otherwise only local 
+ * credentials can be resolved.</li>
+ * </ul>
+ * 
+ * 
+ */
+public class ProviderMetadataEncryptionParametersResolver extends BasicJWTEncryptionParametersResolver {
+    
+    /** Logger. */
+    private final Logger log = LoggerFactory.getLogger(ProviderMetadataEncryptionParametersResolver.class);
+    
+    /** A strategy to locate the encryption methods ('enc') appropriate for the JWT to be encrypted.*/
+    @Nonnull private Function<OIDCProviderMetadata, List<EncryptionMethod>> providerEncryptionMethodsLookupStrategy;
+    
+    /** A strategy to locate the algorithms ('alg') appropriate for the JWT to be encrypted.*/
+    @Nonnull private Function<OIDCProviderMetadata, List<JWEAlgorithm>> providerKeyTransportAlgorithmsLookupStrategy;
+    
+    /** The cache for remote JWK key sets. */
+    @Nullable private RemoteJwkSetCache remoteJwkSetCache;
+    
+    /** The remote key refresh interval. Default value: 30 minutes. */
+    @Positive
+    private Duration keyFetchInterval = Duration.ofMinutes(30);
+    
+    /** Constructor.*/
+    public ProviderMetadataEncryptionParametersResolver() {
+        super();
+        providerEncryptionMethodsLookupStrategy = FunctionSupport.constant(Collections.emptyList());
+        providerKeyTransportAlgorithmsLookupStrategy = FunctionSupport.constant(Collections.emptyList());
+    }
+    
+    /**
+     * Set the strategy used to locate the algorothms ('alg') from the OpenID Provider metadata
+     * appropriate for the JWT to be encrypted.
+     *     
+     * @param strategy the strategy
+     */
+    public void setProviderKeyTransportAlgorithmsLookupStrategy(
+            @Nonnull final Function<OIDCProviderMetadata, List<JWEAlgorithm>> strategy) {
+
+        providerKeyTransportAlgorithmsLookupStrategy = Constraint.isNotNull(strategy,
+                "ProviderAlgorithmsLookupStrategy can not be null");
+    }
+    
+    /**
+     * Set the strategy used to locate the encryption methods ('enc') from the OpenID Provider metadata
+     * appropriate for the JWT to be encrypted.
+     *     
+     * @param strategy the strategy
+     */
+    public void setProviderEncryptionMethodsLookupStrategy(
+            @Nonnull final Function<OIDCProviderMetadata, List<EncryptionMethod>> strategy) {
+
+        providerEncryptionMethodsLookupStrategy = Constraint.isNotNull(strategy,
+                "ProviderEncryptionMethodsLookupStrategy can not be null");
+    }
+    
+    /**
+     * Set the cache for remote JWK key sets.
+     * 
+     * @param jwkSetCache What to set.
+     */
+    public void setRemoteJwkSetCache(final RemoteJwkSetCache jwkSetCache) {
+        remoteJwkSetCache = Constraint.isNotNull(jwkSetCache, "The remote JWK set cache cannot be null");
+    }
+    
+    /**
+     * Set the remote key refresh interval.
+     * 
+     * @param interval What to set.
+     */
+    public void setKeyFetchInterval(@Positive final Duration interval) {
+        Constraint.isFalse(interval == null || interval.isNegative(), "Remote key refresh must be greater than 0");
+        keyFetchInterval = interval;
+    }
+    
+    @Override
+    protected void resolveAndPopulateCredentialsAndAlgorithms(@Nonnull final JWTEncryptionParameters params,
+            @Nonnull final CriteriaSet criteria, @Nonnull final Predicate<String> includeExcludePredicate) {
+        
+        if (remoteJwkSetCache == null) {
+            log.debug("OIDC Provider metadata encryption parameters resolver does not have a remote JWKSet cache set,"
+                    + "falling back to default local configuration");
+            super.resolveAndPopulateCredentialsAndAlgorithms(params, criteria, includeExcludePredicate);
+            return;
+        }
+        
+        if (!criteria.contains(JWTEncryptionConfigurationCriterion.class)) {
+            log.debug("No encryption configuration criterion, encryption parameters can not be resolved");
+            return;
+        }
+        
+        if (!criteria.contains(ProviderMetadataCriterion.class)) {
+            log.debug("No provider metadata criterion, falling back to local configuration");
+            super.resolveAndPopulateCredentialsAndAlgorithms(params, criteria, includeExcludePredicate);
+            return;
+        }    
+        final OIDCProviderMetadata metadata = criteria.get(ProviderMetadataCriterion.class).getMetadata();
+        
+        final List<JWTEncryptionConfiguration> encryptionConfigurations =
+                criteria.get(JWTEncryptionConfigurationCriterion.class).getConfigurations();
+        if (encryptionConfigurations.isEmpty()) {
+            log.debug("No encryption configuration, encryption parameters can not be resolved");
+            return;
+        }
+        
+        // We populate the parameters for the algorithms the provider has registered in metadata
+        final List<JWEAlgorithm> keyTransportAlgorithms =  
+                providerKeyTransportAlgorithmsLookupStrategy.apply(metadata);      
+        log.trace("Resolved effective key transport algorithms from provider metadata: {}", keyTransportAlgorithms);
+        if (keyTransportAlgorithms.isEmpty()) {
+            log.debug("No algorithm ('alg') information in provider metadata, "
+                    + "falling back to default local configuration");
+            super.resolveAndPopulateCredentialsAndAlgorithms(params, criteria, includeExcludePredicate);
+            return;
+        }        
+        
+        final List<EncryptionMethod> dataEncryptionMethods = 
+                providerEncryptionMethodsLookupStrategy.apply(metadata);        
+        log.trace("Resolved effective data encryption algorithms from provider metadata: {}", dataEncryptionMethods);
+        
+        final List<String> keyTransportAlgorithmSupported =
+                getEffectiveKeyTransportAlgorithms(criteria, includeExcludePredicate);
+        log.trace("Resolved supported key transport algorithms from config: {}", 
+                keyTransportAlgorithmSupported);        
+        
+        final List<String> dataEncryptionAlgorithmsSupported =
+                getEffectiveDataEncryptionAlgorithms(criteria, includeExcludePredicate);
+        log.trace("Resolved supported data encryption algorithms from config: {}", dataEncryptionAlgorithmsSupported);
+        
+        
+        final List<String> supportedAndConfiguredKeyTransportAlgorithms = 
+                findAlgorithmIntersection(keyTransportAlgorithms.stream().map(JWEAlgorithm::getName)
+                        .collect(Collectors.toList()),keyTransportAlgorithmSupported);
+        
+        final List<String> supportedAndConfiguredDataEncryptionAlgorithms = 
+                findAlgorithmIntersection(dataEncryptionMethods.stream().map(EncryptionMethod::getName)
+                        .collect(Collectors.toList()),dataEncryptionAlgorithmsSupported);
+        
+        log.debug("Supported and configured key transport algorithms: {}", 
+                supportedAndConfiguredKeyTransportAlgorithms);
+        log.debug("Supported and configured data encryption algorithms: {}", 
+                supportedAndConfiguredDataEncryptionAlgorithms);
+        
+        if (supportedAndConfiguredKeyTransportAlgorithms.isEmpty()) {
+            log.warn("No supported key transport algorithm. Provider metadata and configuration are not compatible");
+            return;
+        }
+        if (supportedAndConfiguredDataEncryptionAlgorithms.isEmpty()) {
+            log.warn("No supported data encryption method. Provider metadata and configuration are not compatible");
+            return;
+        }
+        
+        // Add JWKSet criterion so the callback methods can pull out the OP's JWK set.
+        criteria.add(new JWKSetCriterion(getProviderKeys(metadata)));
+        
+        // Now we have resolved the set of supported 'alg' and 'enc' algorithms, delegate back to the base
+        // class to check locally configured credentials, and call back to this class to resolve from 
+        // the providers key set.
+        super.resolveCredentialForSupportedAlgorithm(criteria, 
+                convertStringAlgorithmURIsToJwkAlgorithms(supportedAndConfiguredKeyTransportAlgorithms),
+                convertStringEncryptionMethodURIsToEncryptionMethods(supportedAndConfiguredDataEncryptionAlgorithms),
+                getEffectiveKeyTransportCredentials(criteria),
+                getEffectiveDataEncryptionCredentials(criteria),
+                params);
+        
+        if (params.getKeyTransportEncryptionCredential() == null && params.getDataEncryptionCredential() == null) {
+            log.debug("Unable to resolve either key transport or data encryption credential");
+        }
+    }
+    
+    /**
+     * {@inheritDoc}
+     * <p>Resolves key transport credentials compatible with the given algorithm from those defined in the 
+     * OP's JWKSet (stored in the criteria).</p>
+     */
+    @Override
+    protected void resolveKeyTransportCredentialForSupportedAlgorithmFromAdditionalSource(
+            @Nonnull final JWEAlgorithm algorithm, @Nonnull final EncryptionMethod encryptionMethod,
+            @Nonnull final CriteriaSet criteria, @Nonnull final JWTEncryptionParameters params) {
+        
+        final JWKSetCriterion jwkSetCriterion = criteria.get(JWKSetCriterion.class);
+        if (jwkSetCriterion == null) {
+            log.debug("Unable to find JWKSet criterion, can not resolver provider keys");
+            return;
+        }
+        final JWKSet providerKeySet = jwkSetCriterion.getJWKSet();
+        if (providerKeySet == null) {
+            log.debug("Unable to find keys in JWKSet criterion, can not resolver provider keys");
+            return;
+        }
+        
+        // All keys in provider metadata should be key transport, not direct data/content encryption, as that
+        // is a public document.
+        final JWK key = 
+                providerKeySet.getKeys().stream()
+                .filter(Objects::nonNull)
+                .filter(k -> KeyUse.ENCRYPTION == k.getKeyUse())
+                .filter(k -> k.getAlgorithm().equals(algorithm))
+                .findFirst().orElse(null);
+        
+        if (key != null) {
+            final BasicJWKCredential jwkCredential = new BasicJWKCredential();
+            jwkCredential.setAlgorithm(algorithm);
+            jwkCredential.setKid(key.getKeyID());
+            try {
+                if (key.getKeyType().equals(KeyType.RSA)) {
+                    jwkCredential.setPublicKey(((RSAKey) key).toPublicKey());
+                } else if (key.getKeyType().equals(KeyType.EC)){
+                    jwkCredential.setPublicKey(((ECKey) key).toPublicKey());
+                }
+            } catch (final JOSEException e) {
+                log.warn("Unable to parse keyset", e);
+                return;
+            }
+            if (checkKeyAlgorithmAndLength(jwkCredential, algorithm.getName())) {
+                log.debug("Selected key '{}' for alg {} and enc {}", key.getKeyID(), 
+                        algorithm.getName(), encryptionMethod.getName());
+                params.setKeyTransportEncryptionCredential(jwkCredential);
+                params.setKeyTransportEncryptionAlgorithm(algorithm.getName());
+                params.setDataEncryptionAlgorithm(encryptionMethod.getName()); 
+            }
+        }
+        
+    }
+    
+    /**
+     * Fetch the OpenID Provider's remote JWKSet.
+     * 
+     * @param metadata the OpenID Provider's metadata
+     * 
+     * @return the JSON Web Keys set. Or an empty key set if the fetch failed.
+     */
+    @Nonnull private JWKSet getProviderKeys(@Nonnull final OIDCProviderMetadata metadata) {
+            final JWKSet keys = remoteJwkSetCache.fetch(metadata.getJWKSetURI(),
+                    Instant.now().plus(keyFetchInterval));
+            if (keys == null) {
+                return new JWKSet();
+            } else {
+                return keys;
+            }
+    }
+    
+    /**
+     * Return a new list of algorithms that represents the set intersection of the two input algorithm lists.
+     * The original order of algorithms from the {@code configAlgorithms} list is preserved.
+     * 
+     * @param providerAlgorithms the set of algorithms specified by the OpenID Provider
+     * @param configAlgorithms the set of algorithms specified by the IdP's configuration
+     * 
+     * @return the intersection of both lists
+     */
+    @Nonnull private List<String> findAlgorithmIntersection(@Nonnull final List<String> providerAlgorithms,
+            @Nonnull final List<String> configAlgorithms){        
+        return configAlgorithms.stream().filter(providerAlgorithms::contains).collect(Collectors.toList());
+        
+    }
+    
+    
+
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/BasicJWTEncryptionParametersResolverTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/BasicJWTEncryptionParametersResolverTest.java
index a0a5b15..ceb9014 100644
--- a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/BasicJWTEncryptionParametersResolverTest.java
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/BasicJWTEncryptionParametersResolverTest.java
@@ -97,7 +97,7 @@ public class BasicJWTEncryptionParametersResolverTest {
                 .keyUse(KeyUse.ENCRYPTION)
                 .keyID("mock-key")
                 .generate();
-        config.setKeyTransportEncryptionCredentials(List.of(createKeyEncryptionCredential(key)));
+        config.setKeyTransportEncryptionCredentials(List.of(TestCredentialHelper.createKeyEncryptionCredential(key)));
         final JWTEncryptionParameters param = resolver.resolveSingle(criteria);
         
         assertNotNull(param);
@@ -120,7 +120,7 @@ public class BasicJWTEncryptionParametersResolverTest {
                 .algorithm(JWEAlgorithm.ECDH_ES)
                 .keyID("mock-key")
                 .generate();
-        config.setKeyTransportEncryptionCredentials(List.of(createKeyAgreementCredential(key)));
+        config.setKeyTransportEncryptionCredentials(List.of(TestCredentialHelper.createKeyAgreementCredential(key)));
         final JWTEncryptionParameters param = resolver.resolveSingle(criteria);
         
         assertNotNull(param);
@@ -138,7 +138,7 @@ public class BasicJWTEncryptionParametersResolverTest {
         final CriteriaSet criteria =  buildBasicCriteriaSet();
         config.setKeyTransportEncryptionAlgorithms(
                 List.of(KeyManagementConstants.ALGO_ID_ALG_AES_256_KW));        
-        config.setKeyTransportEncryptionCredentials(List.of(createSharedSecretCredential("mock-key",
+        config.setKeyTransportEncryptionCredentials(List.of(TestCredentialHelper.createSharedSecretCredential("mock-key",
                 SYMMETRIC_KEY, JWEAlgorithm.A256KW)));
         final JWTEncryptionParameters param = resolver.resolveSingle(criteria);
         
@@ -158,7 +158,7 @@ public class BasicJWTEncryptionParametersResolverTest {
         final CriteriaSet criteria =  buildBasicCriteriaSet();
         config.setKeyTransportEncryptionAlgorithms(
                 List.of(KeyManagementConstants.ALGO_ID_ALG_DIR));        
-        config.setDataEncryptionCredentials(List.of(createSharedSecretCredential("mock-key",
+        config.setDataEncryptionCredentials(List.of(TestCredentialHelper.createSharedSecretCredential("mock-key",
                 SYMMETRIC_KEY, JWEAlgorithm.DIR)));
         
         final JWTEncryptionParameters param = resolver.resolveSingle(criteria);            
@@ -178,7 +178,7 @@ public class BasicJWTEncryptionParametersResolverTest {
         final CriteriaSet criteria =  buildBasicCriteriaSet();
         config.setKeyTransportEncryptionAlgorithms(
                 List.of(KeyManagementConstants.ALGO_ID_ALG_RSA_OAEP_256, KeyManagementConstants.ALGO_ID_ALG_DIR));        
-        config.setDataEncryptionCredentials(List.of(createSharedSecretCredential("mock-key",
+        config.setDataEncryptionCredentials(List.of(TestCredentialHelper.createSharedSecretCredential("mock-key",
                 SYMMETRIC_KEY, JWEAlgorithm.DIR)));
         
         final JWTEncryptionParameters param = resolver.resolveSingle(criteria);            
@@ -197,7 +197,7 @@ public class BasicJWTEncryptionParametersResolverTest {
         final CriteriaSet criteria =  buildBasicCriteriaSet();
         config.setKeyTransportEncryptionAlgorithms(
                 List.of(KeyManagementConstants.ALGO_ID_ALG_DIR));        
-        config.setDataEncryptionCredentials(List.of(createSharedSecretCredential("mock-key",
+        config.setDataEncryptionCredentials(List.of(TestCredentialHelper.createSharedSecretCredential("mock-key",
                 SYMMETRIC_KEY, JWEAlgorithm.DIR)));
         config.setDataEncryptionAlgorithms(List.of(EncryptionConstants.ALGO_ID_ENC_ALG_A128CBC_HS256));
         
@@ -216,8 +216,8 @@ public class BasicJWTEncryptionParametersResolverTest {
                 .keyUse(KeyUse.ENCRYPTION)
                 .keyID("mock-key")
                 .generate();
-        config.setKeyTransportEncryptionCredentials(List.of(createKeyEncryptionCredential(key)));
-        config.setDataEncryptionCredentials(List.of(createSharedSecretCredential("mock-key",
+        config.setKeyTransportEncryptionCredentials(List.of(TestCredentialHelper.createKeyEncryptionCredential(key)));
+        config.setDataEncryptionCredentials(List.of(TestCredentialHelper.createSharedSecretCredential("mock-key",
                 SYMMETRIC_KEY, JWEAlgorithm.DIR)));
         config.setDataEncryptionAlgorithms(List.of(EncryptionConstants.ALGO_ID_ENC_ALG_A256CBC_HS512));
         
@@ -241,8 +241,8 @@ public class BasicJWTEncryptionParametersResolverTest {
                 .keyUse(KeyUse.ENCRYPTION)
                 .keyID("mock-key")
                 .generate();
-        config.setKeyTransportEncryptionCredentials(List.of(createKeyEncryptionCredential(key)));
-        config.setDataEncryptionCredentials(List.of(createSharedSecretCredential("mock-key",
+        config.setKeyTransportEncryptionCredentials(List.of(TestCredentialHelper.createKeyEncryptionCredential(key)));
+        config.setDataEncryptionCredentials(List.of(TestCredentialHelper.createSharedSecretCredential("mock-key",
                 SYMMETRIC_KEY, JWEAlgorithm.DIR)));
         config.setDataEncryptionAlgorithms(List.of(EncryptionConstants.ALGO_ID_ENC_ALG_A256CBC_HS512));
         
@@ -265,7 +265,7 @@ public class BasicJWTEncryptionParametersResolverTest {
                 .keyUse(KeyUse.ENCRYPTION)
                 .keyID("mock-key")
                 .generate();
-        config.setKeyTransportEncryptionCredentials(List.of(createKeyEncryptionCredential(key)));
+        config.setKeyTransportEncryptionCredentials(List.of(TestCredentialHelper.createKeyEncryptionCredential(key)));
         final JWTEncryptionParameters param = resolver.resolveSingle(criteria);
         
         assertNull(param);           
@@ -285,8 +285,8 @@ public class BasicJWTEncryptionParametersResolverTest {
                 .keyUse(KeyUse.ENCRYPTION)
                 .keyID("mock-key-correct-type")
                 .generate();
-        config.setKeyTransportEncryptionCredentials(List.of(createKeyEncryptionCredential(key),
-                createKeyEncryptionCredential(keyCorrect)));
+        config.setKeyTransportEncryptionCredentials(List.of(TestCredentialHelper.createKeyEncryptionCredential(key),
+                TestCredentialHelper.createKeyEncryptionCredential(keyCorrect)));
         final JWTEncryptionParameters param = resolver.resolveSingle(criteria);
         
         assertNotNull(param);
@@ -309,8 +309,8 @@ public class BasicJWTEncryptionParametersResolverTest {
                 .keyUse(KeyUse.ENCRYPTION)
                 .keyID("mock-key-wrong-type")
                 .generate();        
-        config.setKeyTransportEncryptionCredentials(List.of(createKeyEncryptionCredential(key)));
-        config.setDataEncryptionCredentials(List.of(createSharedSecretCredential("mock-key",
+        config.setKeyTransportEncryptionCredentials(List.of(TestCredentialHelper.createKeyEncryptionCredential(key)));
+        config.setDataEncryptionCredentials(List.of(TestCredentialHelper.createSharedSecretCredential("mock-key",
                 SYMMETRIC_KEY, JWEAlgorithm.A128KW)));
         final JWTEncryptionParameters param = resolver.resolveSingle(criteria);
         
@@ -326,75 +326,10 @@ public class BasicJWTEncryptionParametersResolverTest {
                 .keyUse(KeyUse.ENCRYPTION)
                 .keyID("mock-key")
                 .generate();
-        config.setKeyTransportEncryptionCredentials(List.of(createKeyEncryptionCredential(key)));
+        config.setKeyTransportEncryptionCredentials(List.of(TestCredentialHelper.createKeyEncryptionCredential(key)));
         final JWTEncryptionParameters param = resolver.resolveSingle(criteria);
         
         assertNull(param);           
     }
-    
-    /**
-     * Create a key encryption {@link JWKCredential} from the given RSA key.
-     * 
-     * @param secret the RSAKey to convert to a {@link JWKCredential}.
-     * 
-     * @return the credential
-     * @throws JOSEException  on error
-     */
-    public 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(secret.getKeyID());
-        jwkCredential.setAlgorithm(secret.getAlgorithm());
-        return jwkCredential;
-    }
-    
-    /**
-     * Create a key agreement encryption {@link JWKCredential} from the given EC key.
-     * 
-     * @param secret the ECKey to convert to a {@link JWKCredential}.
-     * 
-     * @return the credential
-     * @throws JOSEException  on error
-     */
-    public JWKCredential createKeyAgreementCredential(final ECKey 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(secret.getKeyID());
-        jwkCredential.setAlgorithm(secret.getAlgorithm());
-        return jwkCredential;
-    }
-    
-    /**
-     * Create a simple symmetric key client credential from from the given shared secret.
-     * 
-     * @param kid the key ID
-     * @param secret the secret to convert to a {@link JWKCredential}.
-     * @param algorithm the JWA algorithm to set on the credential.
-     * 
-     * @return the credential
-     * @throws KeyException on error creating the key
-     */
-    public JWKCredential createSharedSecretCredential(final String kid, final String secret, 
-            final Algorithm algorithm) 
-            throws KeyException {
-        final BasicExpiringJWKCredential jwkCredential = new BasicExpiringJWKCredential();
-        jwkCredential.setSecretKey(KeySupport.decodeSecretKey(JWSAssemblyUtils.getSecretBytes(secret), "AES"));
-        jwkCredential.setCredentialExpiresAt(Duration.ZERO);
-        jwkCredential.setUsageType(UsageType.UNSPECIFIED);
-        jwkCredential.setKid(kid);
-        jwkCredential.setAlgorithm(algorithm);
-        jwkCredential.getKeyNames().add("mockKey");
-        return jwkCredential;
-    }
-    
 }
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/ProviderMetadataEncryptionParametersResolverTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/ProviderMetadataEncryptionParametersResolverTest.java
new file mode 100644
index 0000000..4d74ec7
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/ProviderMetadataEncryptionParametersResolverTest.java
@@ -0,0 +1,411 @@
+/*
+ * 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.oidc.security.impl;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.protocol.HttpContext;
+import org.mockito.Mockito;
+import org.opensaml.core.config.InitializationException;
+import org.opensaml.storage.StorageService;
+import org.opensaml.storage.impl.MemoryStorageService;
+import org.opensaml.xmlsec.config.GlobalAlgorithmRegistryInitializer;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.util.FileCopyUtils;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.oidc.jwa.support.EncryptionConstants;
+import net.shibboleth.oidc.jwa.support.KeyManagementConstants;
+import net.shibboleth.oidc.jwk.RemoteJwkSetCache;
+import net.shibboleth.oidc.security.JWTEncryptionParameters;
+import net.shibboleth.oidc.security.credential.JWKCredential;
+import net.shibboleth.oidc.security.criterion.JWTEncryptionConfigurationCriterion;
+import net.shibboleth.oidc.security.criterion.ProviderMetadataCriterion;
+import net.shibboleth.oidc.security.criterion.StaticCredentialCriterion;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+
+/** Tests for the {@link ProviderMetadataEncryptionParametersResolver}.*/
+public class ProviderMetadataEncryptionParametersResolverTest {
+    
+    /**
+     * Example of good provider metadata that supports request_object_encryption.
+     */
+    private static final ClassPathResource GOOD_PROVIDER_CONFIGURATION_INFO = 
+            new ClassPathResource("/metadata/test-resolver-provider-encryption.json");
+    
+    /** A remote JWKSet.*/
+    private static final ClassPathResource REMOTE_JWKSET = 
+            new ClassPathResource("/credentials/test-provider-resolver-remote-jwkset-response.jwk"); 
+    
+    /** A remote JWKSet with no 'alg' parameters.*/
+    private static final ClassPathResource REMOTE_JWKSET_NO_ALG_PARAM = 
+            new ClassPathResource("/credentials/test-provider-resolver-remote-jwkset-response-no-alg.jwk"); 
+    
+    /** The client_secret.*/
+    private static final String CLIENT_SECRET = "Xp2s5v8y/B?E(H+MbQeThWmYq3t6w9z$";
+    
+    /** The mock symmetric key e.g. for keywrap.*/
+    private static final String SYMMETRIC_KEY = "/A?D(G+KbPdSgVkYp3s6v9y$B&E)H at Mc";
+    
+    /** A second mock symmetric key e.g. for keywrap.*/
+    private static final String SYMMETRIC_KEY_TWO = "/ArB(G+KbPdSgVkYp3s6v9y$B&E)H at Mc";
+    
+    /** The resolver to test.*/
+    private ProviderMetadataEncryptionParametersResolver resolver;
+    
+    /** The basic config.*/
+    private BasicJWTEncryptionConfiguration config;
+    
+    /**
+     * Read a file into a string.
+     * 
+     * @param location the location of the file to read
+     * 
+     * @return the file as a string
+     */
+    private String readJsonFromFile(@Nonnull final Resource location) {
+        try (Reader reader = new InputStreamReader(location.getInputStream(), StandardCharsets.UTF_8)) {
+            return FileCopyUtils.copyToString(reader);
+        } catch (final Exception ex) {
+            fail();
+            return null;
+        }
+    }
+    
+
+    @BeforeMethod
+    public void setup() throws ComponentInitializationException, ClientProtocolException, IOException {
+        //Create an algorithm registry here, as opensaml init will not take place for these tests      
+        try {
+            final GlobalAlgorithmRegistryInitializer gar = new GlobalAlgorithmRegistryInitializer();
+            gar.init();
+        } catch (final InitializationException e) {
+            fail();
+        }
+        
+        resolver = new ProviderMetadataEncryptionParametersResolver();
+        resolver.setProviderEncryptionMethodsLookupStrategy(OIDCProviderMetadata::getRequestObjectJWEEncs);
+        resolver.setProviderKeyTransportAlgorithmsLookupStrategy(OIDCProviderMetadata::getRequestObjectJWEAlgs);
+        final RemoteJwkSetCache cache = new RemoteJwkSetCache();
+        cache.setStorage(buildStorageService());
+        cache.setHttpClient(createMockHttpClient(readJsonFromFile(REMOTE_JWKSET)));
+        resolver.setRemoteJwkSetCache(cache);
+    }
+    
+    protected HttpClient createMockHttpClient(final String output) throws ClientProtocolException, IOException {
+        final HttpClient httpClient = Mockito.mock(HttpClient.class);
+        final HttpResponse httpResponse = Mockito.mock(HttpResponse.class);
+        Mockito.when(httpResponse.getEntity()).thenReturn(new StringEntity(output));
+        Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(), 
+                (HttpContext) Mockito.any())).thenReturn(httpResponse);
+        return httpClient;
+    }
+    
+    private StorageService buildStorageService() throws ComponentInitializationException {
+        final MemoryStorageService storageService = new MemoryStorageService();
+        storageService.setId("mockId");
+        storageService.initialize();
+        return storageService;
+    }
+    
+    private CriteriaSet buildBasicCriteriaSet() throws Exception {        
+        
+        config = new BasicJWTEncryptionConfiguration();
+        config.setKeyTransportEncryptionAlgorithms(
+                List.of(KeyManagementConstants.ALGO_ID_ALG_AES_128_KW, KeyManagementConstants.ALGO_ID_ALG_RSA_OAEP_256,
+                KeyManagementConstants.ALGO_ID_ALG_ECDH_ES_AES_192_KW, KeyManagementConstants.ALGO_ID_ALG_RSA_OAEP));
+        config.setDataEncryptionAlgorithms(
+                List.of(EncryptionConstants.ALGO_ID_ENC_ALG_A128CBC_HS256,EncryptionConstants.ALGO_ID_ENC_ALG_A128GCM,
+                        EncryptionConstants.ALGO_ID_ENC_ALG_A256CBC_HS512));
+        final CriteriaSet criteria = new CriteriaSet(new JWTEncryptionConfigurationCriterion(List.of(config)));
+        criteria.add(new ProviderMetadataCriterion(
+                OIDCProviderMetadata.parse(readJsonFromFile(GOOD_PROVIDER_CONFIGURATION_INFO))));
+        criteria.add(
+                new StaticCredentialCriterion(TestCredentialHelper.createClientSecretCredential(CLIENT_SECRET)));
+        return criteria;
+    }
+    
+    @Test
+    public void testSuccessfulResolution() throws Exception {
+        final Iterable<JWTEncryptionParameters> params = resolver.resolve(buildBasicCriteriaSet());
+        assertNotNull(params);
+        assertTrue(params.iterator().hasNext());
+        final JWTEncryptionParameters param = params.iterator().next();
+        assertNotNull(param.getDataEncryptionAlgorithm());
+        assertNotNull(param.getKeyTransportEncryptionAlgorithm()); 
+        assertNotNull(param.getKeyTransportEncryptionCredential());
+        assertNotNull(param.getKeyTransportEncryptionCredential().getPublicKey());
+    }
+    
+    /* Algorithms are know because they are limited by config.*/
+    @Test
+    public void testSuccessfulResolution_ForKeyEncryption() throws Exception {
+        final CriteriaSet criteria =  buildBasicCriteriaSet();
+        config.setKeyTransportEncryptionAlgorithms(List.of(KeyManagementConstants.ALGO_ID_ALG_RSA_OAEP));
+        config.setDataEncryptionAlgorithms(List.of(EncryptionConstants.ALGO_ID_ENC_ALG_A256CBC_HS512));
+        
+        final Iterable<JWTEncryptionParameters> params = resolver.resolve(criteria);
+        assertNotNull(params);
+        assertTrue(params.iterator().hasNext());
+        final JWTEncryptionParameters param = params.iterator().next();
+        assertNotNull(param.getDataEncryptionAlgorithm());
+        assertNotNull(param.getKeyTransportEncryptionAlgorithm());  
+        assertEquals(param.getDataEncryptionAlgorithm(),EncryptionConstants.ALGO_ID_ENC_ALG_A256CBC_HS512);
+        assertEquals(param.getKeyTransportEncryptionAlgorithm(),KeyManagementConstants.ALGO_ID_ALG_RSA_OAEP);
+        assertNotNull(param.getKeyTransportEncryptionCredential());
+        assertNotNull(param.getKeyTransportEncryptionCredential().getPublicKey());
+    }
+    
+    /* Should chose key encryption creds as they are the only ones configured, and are first
+     * in the algorithm list*/
+    @Test
+    public void testSuccessfulResolution_ForKeyEncryption_WhenKeyWrapPossible() throws Exception {
+        final CriteriaSet criteria =  buildBasicCriteriaSet();
+        config.setKeyTransportEncryptionAlgorithms(List.of(KeyManagementConstants.ALGO_ID_ALG_RSA_OAEP, 
+                KeyManagementConstants.ALGO_ID_ALG_AES_128_KW));
+        config.setDataEncryptionAlgorithms(List.of(EncryptionConstants.ALGO_ID_ENC_ALG_A256CBC_HS512));
+        
+        final Iterable<JWTEncryptionParameters> params = resolver.resolve(criteria);
+        assertNotNull(params);
+        assertTrue(params.iterator().hasNext());
+        final JWTEncryptionParameters param = params.iterator().next();
+        assertNotNull(param.getDataEncryptionAlgorithm());
+        assertNotNull(param.getKeyTransportEncryptionAlgorithm());  
+        assertEquals(param.getDataEncryptionAlgorithm(),EncryptionConstants.ALGO_ID_ENC_ALG_A256CBC_HS512);
+        assertEquals(param.getKeyTransportEncryptionAlgorithm(),KeyManagementConstants.ALGO_ID_ALG_RSA_OAEP);
+        assertNotNull(param.getKeyTransportEncryptionCredential());
+        assertNotNull(param.getKeyTransportEncryptionCredential().getPublicKey());
+    }
+    
+    /* Should chose key wrap creds first, as that algorithm is first in the list.*/
+    @Test
+    public void testSuccessfulResolution_ForKeyWrap_WhenKeyEncryptionPossible() throws Exception {
+        final CriteriaSet criteria =  buildBasicCriteriaSet();
+        config.setKeyTransportEncryptionAlgorithms(List.of(KeyManagementConstants.ALGO_ID_ALG_AES_256_KW,
+                KeyManagementConstants.ALGO_ID_ALG_RSA_OAEP));
+        config.setDataEncryptionAlgorithms(List.of(EncryptionConstants.ALGO_ID_ENC_ALG_A256CBC_HS512));
+        config.setKeyTransportEncryptionCredentials(
+                List.of(TestCredentialHelper.createClientSecretCredential("mockKey", 
+                        SYMMETRIC_KEY, JWEAlgorithm.A256KW)));
+        
+        final Iterable<JWTEncryptionParameters> params = resolver.resolve(criteria);
+        assertNotNull(params);
+        assertTrue(params.iterator().hasNext());
+        final JWTEncryptionParameters param = params.iterator().next();
+        assertNotNull(param.getDataEncryptionAlgorithm());
+        assertNotNull(param.getKeyTransportEncryptionAlgorithm());  
+        assertEquals(param.getDataEncryptionAlgorithm(),EncryptionConstants.ALGO_ID_ENC_ALG_A256CBC_HS512);
+        assertEquals(param.getKeyTransportEncryptionAlgorithm(),KeyManagementConstants.ALGO_ID_ALG_AES_256_KW);
+        assertNotNull(param.getKeyTransportEncryptionCredential());
+        assertNotNull(param.getKeyTransportEncryptionCredential().getSecretKey());
+    }
+    
+    @Test
+    public void testSuccessfulResolution_ForKeyWrap() throws Exception {
+        final CriteriaSet criteria =  buildBasicCriteriaSet();
+        config.setKeyTransportEncryptionAlgorithms(List.of(KeyManagementConstants.ALGO_ID_ALG_AES_256_KW));
+        config.setDataEncryptionAlgorithms(List.of(EncryptionConstants.ALGO_ID_ENC_ALG_A128GCM));
+        config.setKeyTransportEncryptionCredentials(
+                List.of(TestCredentialHelper.createClientSecretCredential("mockKey", SYMMETRIC_KEY, 
+                        JWEAlgorithm.A256KW)));
+        
+        final Iterable<JWTEncryptionParameters> params = resolver.resolve(criteria);
+        assertNotNull(params);
+        assertTrue(params.iterator().hasNext());
+        final JWTEncryptionParameters param = params.iterator().next();
+        assertNotNull(param.getDataEncryptionAlgorithm());
+        assertNotNull(param.getKeyTransportEncryptionAlgorithm());  
+        assertEquals(param.getDataEncryptionAlgorithm(),EncryptionConstants.ALGO_ID_ENC_ALG_A128GCM);
+        assertEquals(param.getKeyTransportEncryptionAlgorithm(),KeyManagementConstants.ALGO_ID_ALG_AES_256_KW);
+        assertNotNull(param.getKeyTransportEncryptionCredential());
+        assertNotNull(param.getKeyTransportEncryptionCredential().getSecretKey());
+    }
+    
+    /* Runtime does not yet support 'dir' key transport, so disabled for now.*/
+    @Test(enabled = false)
+    public void testSuccessfulResolution_ForDirectEncryption() throws Exception {
+        final CriteriaSet criteria =  buildBasicCriteriaSet();
+        config.setKeyTransportEncryptionAlgorithms(List.of(KeyManagementConstants.ALGO_ID_ALG_DIR));
+        config.setDataEncryptionAlgorithms(List.of(EncryptionConstants.ALGO_ID_ENC_ALG_A256GCM));
+        config.setDataEncryptionCredentials(
+                List.of(TestCredentialHelper.createClientSecretCredential("mockKey", SYMMETRIC_KEY, 
+                        JWEAlgorithm.DIR)));
+        
+        final Iterable<JWTEncryptionParameters> params = resolver.resolve(criteria);
+        assertNotNull(params);
+        assertTrue(params.iterator().hasNext());
+        final JWTEncryptionParameters param = params.iterator().next();
+        assertNotNull(param.getDataEncryptionAlgorithm());
+        assertNotNull(param.getKeyTransportEncryptionAlgorithm());  
+        assertEquals(param.getDataEncryptionAlgorithm(),EncryptionConstants.ALGO_ID_ENC_ALG_A256GCM);
+        assertEquals(param.getKeyTransportEncryptionAlgorithm(),KeyManagementConstants.ALGO_ID_ALG_DIR);
+        assertNull(param.getKeyTransportEncryptionCredential());
+        assertNotNull(param.getDataEncryptionCredential());
+        assertNotNull(param.getDataEncryptionCredential().getSecretKey());
+        assertTrue(param.getDataEncryptionCredential().getKeyNames().contains("mockKey"));
+    }
+    
+    /* 
+     * The local RSA key should be derived from the local config even if provider metadata is excluded from 
+     * the criteria set.
+     */
+    @Test
+    public void testSuccessfulResolution_NoProviderMetadata_FallBackToLocalBehaviour() throws Exception {
+        // Do not add ProviderMetadataCriterion
+        buildBasicCriteriaSet();
+        final CriteriaSet criteria = new CriteriaSet(new JWTEncryptionConfigurationCriterion(List.of(config)));
+        
+        config.setKeyTransportEncryptionAlgorithms(List.of(KeyManagementConstants.ALGO_ID_ALG_RSA_OAEP_256));
+        config.setDataEncryptionAlgorithms(List.of(EncryptionConstants.ALGO_ID_ENC_ALG_A128GCM));
+        
+        final RSAKey key = new RSAKeyGenerator(2048)
+                .algorithm(JWEAlgorithm.RSA_OAEP_256)
+                .keyUse(KeyUse.ENCRYPTION)
+                .keyID("mock-key")
+                .generate();
+        
+        config.setKeyTransportEncryptionCredentials(
+                List.of(TestCredentialHelper.createKeyEncryptionCredential(key)));
+        
+        final Iterable<JWTEncryptionParameters> params = resolver.resolve(criteria);
+        assertNotNull(params);
+        assertTrue(params.iterator().hasNext());
+        final JWTEncryptionParameters param = params.iterator().next();
+        assertNotNull(param.getDataEncryptionAlgorithm());
+        assertNotNull(param.getKeyTransportEncryptionAlgorithm());  
+        assertEquals(param.getDataEncryptionAlgorithm(),EncryptionConstants.ALGO_ID_ENC_ALG_A128GCM);
+        assertEquals(param.getKeyTransportEncryptionAlgorithm(),KeyManagementConstants.ALGO_ID_ALG_RSA_OAEP_256);
+        assertNotNull(param.getKeyTransportEncryptionCredential());
+        assertTrue(param.getKeyTransportEncryptionCredential().getKeyNames().contains("mock-key"));
+        assertNotNull(param.getKeyTransportEncryptionCredential().getPublicKey());
+    }
+    
+    /* The first mockKey in the list should be resolved.*/
+    @Test
+    public void testSuccessfulResolution_ForKeyWrap_MoreThanOneKeyWrapCred() throws Exception {
+        final CriteriaSet criteria =  buildBasicCriteriaSet();
+        config.setKeyTransportEncryptionAlgorithms(List.of(KeyManagementConstants.ALGO_ID_ALG_AES_256_KW));
+        config.setDataEncryptionAlgorithms(List.of(EncryptionConstants.ALGO_ID_ENC_ALG_A128GCM));
+        config.setKeyTransportEncryptionCredentials(
+                List.of(TestCredentialHelper.createClientSecretCredential("mockKey", SYMMETRIC_KEY, 
+                        JWEAlgorithm.A256KW),
+                        TestCredentialHelper.createClientSecretCredential("mockKeyTwo", SYMMETRIC_KEY_TWO, 
+                                JWEAlgorithm.A256KW)));
+        
+        final Iterable<JWTEncryptionParameters> params = resolver.resolve(criteria);
+        assertNotNull(params);
+        assertTrue(params.iterator().hasNext());
+        final JWTEncryptionParameters param = params.iterator().next();
+        assertNotNull(param.getDataEncryptionAlgorithm());
+        assertNotNull(param.getKeyTransportEncryptionAlgorithm());  
+        assertEquals(param.getDataEncryptionAlgorithm(),EncryptionConstants.ALGO_ID_ENC_ALG_A128GCM);
+        assertEquals(param.getKeyTransportEncryptionAlgorithm(),KeyManagementConstants.ALGO_ID_ALG_AES_256_KW);
+        // TODO this is not guaranteed, so should we be checking it?
+        assertEquals(((JWKCredential)param.getKeyTransportEncryptionCredential()).getKid(),"mockKey");
+        assertNotNull(param.getKeyTransportEncryptionCredential());
+        assertNotNull(param.getKeyTransportEncryptionCredential().getSecretKey());
+    }
+    
+    /* Do not provide a symmetric key in the params.*/
+    @Test
+    public void testUnSuccessfulResolution_ForKeyWrap() throws Exception {
+        final CriteriaSet criteria =  buildBasicCriteriaSet();
+        config.setKeyTransportEncryptionAlgorithms(List.of(KeyManagementConstants.ALGO_ID_ALG_AES_256_KW));
+        config.setDataEncryptionAlgorithms(List.of(EncryptionConstants.ALGO_ID_ENC_ALG_A128GCM));
+        
+        final Iterable<JWTEncryptionParameters> params = resolver.resolve(criteria);
+        assertNotNull(params);
+        assertFalse(params.iterator().hasNext());    
+    }
+    
+    @Test
+    public void testSuccessfulResolution_ForKeyAgreement() throws Exception {
+        final CriteriaSet criteria =  buildBasicCriteriaSet();
+        config.setKeyTransportEncryptionAlgorithms(List.of(KeyManagementConstants.ALGO_ID_ALG_ECDH_ES));
+        config.setDataEncryptionAlgorithms(List.of(EncryptionConstants.ALGO_ID_ENC_ALG_A128GCM));
+        
+        final Iterable<JWTEncryptionParameters> params = resolver.resolve(criteria);
+        assertNotNull(params);
+        assertTrue(params.iterator().hasNext());
+        final JWTEncryptionParameters param = params.iterator().next();
+        assertNotNull(param.getDataEncryptionAlgorithm());
+        assertNotNull(param.getKeyTransportEncryptionAlgorithm());  
+        assertEquals(param.getDataEncryptionAlgorithm(),EncryptionConstants.ALGO_ID_ENC_ALG_A128GCM);
+        assertEquals(param.getKeyTransportEncryptionAlgorithm(),KeyManagementConstants.ALGO_ID_ALG_ECDH_ES);
+        assertNotNull(param.getKeyTransportEncryptionCredential());
+        assertNotNull(param.getKeyTransportEncryptionCredential().getPublicKey());
+    }
+    
+    @Test
+    public void testUnSuccessfulResolution_NoSupportedKeyTransportAlgorithm() throws Exception {
+        final CriteriaSet criteria =  buildBasicCriteriaSet();
+        config.setKeyTransportEncryptionAlgorithms(List.of("NOT-SUPPORTED"));
+        
+        final Iterable<JWTEncryptionParameters> params = resolver.resolve(criteria);
+        assertNotNull(params);
+        assertFalse(params.iterator().hasNext());
+    }
+    
+    @Test
+    public void testUnSuccessfulResolution_OnlyConfigCriterion() throws Exception {
+        buildBasicCriteriaSet();
+        final CriteriaSet criteria =  new CriteriaSet(new JWTEncryptionConfigurationCriterion(List.of(config)));
+        
+        final Iterable<JWTEncryptionParameters> params = resolver.resolve(criteria);
+        assertNotNull(params);
+        assertFalse(params.iterator().hasNext());
+    }
+    
+    @Test
+    public void testUnSuccessfulResolution_NoSupportedDataEncryptionMethod() throws Exception {
+        final CriteriaSet criteria =  buildBasicCriteriaSet();
+        config.setDataEncryptionAlgorithms(List.of("NOT-SUPPORTED"));
+        
+        final Iterable<JWTEncryptionParameters> params = resolver.resolve(criteria);
+        assertNotNull(params);
+        assertFalse(params.iterator().hasNext());
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/TestCredentialHelper.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/TestCredentialHelper.java
new file mode 100644
index 0000000..3a50207
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/impl/TestCredentialHelper.java
@@ -0,0 +1,182 @@
+/*
+ * 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.oidc.security.impl;
+
+import java.security.KeyException;
+import java.time.Duration;
+
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.crypto.KeySupport;
+
+import com.nimbusds.jose.Algorithm;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.jwk.AsymmetricJWK;
+import com.nimbusds.jose.jwk.ECKey;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.RSAKey;
+
+import net.shibboleth.oidc.security.credential.BasicExpiringJWKCredential;
+import net.shibboleth.oidc.security.credential.JWKCredential;
+
+/** Helper that creates different credentials.*/
+public final class TestCredentialHelper {
+    
+    private TestCredentialHelper() {
+        
+    }
+    
+    /**
+     * Create a simple symmetric key client credential from from the given shared secret.
+     * 
+     * @param secret the secret to convert to a {@link JWKCredential}.
+     * 
+     * @return the credential
+     * @throws KeyException on error creating the key
+     */
+    public static JWKCredential createClientSecretCredential(final String secret) throws KeyException {        
+        return createClientSecretCredential("mockKey", secret, null);
+    }
+    
+    /**
+     * Create a simple symmetric key client credential from from the given shared secret.
+     * 
+     * @param kid the key ID
+     * @param secret the secret to convert to a {@link JWKCredential}.
+     * @param algorithm the JWA algorithm to set on the credential.
+     * 
+     * @return the credential
+     * @throws KeyException on error creating the key
+     */
+    public static JWKCredential createClientSecretCredential(final String kid, final String secret, 
+            final Algorithm algorithm) 
+            throws KeyException {
+        return createSharedSecretCredential(kid, secret, algorithm);
+    }
+    
+    /**
+     * Create a simple symmetric key client credential from from the given shared secret.
+     * 
+     * @param kid the key ID
+     * @param secret the secret to convert to a {@link JWKCredential}.
+     * @param algorithm the JWA algorithm to set on the credential.
+     * 
+     * @return the credential
+     * @throws KeyException on error creating the key
+     */
+    public static JWKCredential createSharedSecretCredential(final String kid, final String secret, 
+            final Algorithm algorithm) 
+            throws KeyException {
+        final BasicExpiringJWKCredential jwkCredential = new BasicExpiringJWKCredential();
+        jwkCredential.setSecretKey(KeySupport.decodeSecretKey(JWSAssemblyUtils.getSecretBytes(secret), "AES"));
+        jwkCredential.setCredentialExpiresAt(Duration.ZERO);
+        jwkCredential.setUsageType(UsageType.UNSPECIFIED);
+        jwkCredential.setKid(kid);
+        jwkCredential.setAlgorithm(algorithm);
+        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 KeyException on error creating the key
+     */
+    public static JWKCredential createDirectEncryptionCredentialFromSharedSecret(final String secret) 
+            throws KeyException {
+        final BasicExpiringJWKCredential jwkCredential = new BasicExpiringJWKCredential();
+        jwkCredential.setSecretKey(KeySupport.decodeSecretKey(
+                    JWSAssemblyUtils.getSecretBytes(secret), "AES"));
+        jwkCredential.setCredentialExpiresAt(Duration.ZERO);
+        jwkCredential.setUsageType(UsageType.UNSPECIFIED);
+        
+        jwkCredential.setKid("mockKey");
+        jwkCredential.getKeyNames().add("mockKey");
+        jwkCredential.setAlgorithm(JWEAlgorithm.DIR);
+        return jwkCredential;
+    }
+    
+    /**
+     * Create an asymmetric signing credential.
+     * 
+     * @param key the key to convert to a {@link JWKCredential}.
+     * 
+     * @return the credential
+     * @throws JOSEException on error
+     */
+    public static JWKCredential createAsymmetricSigningCredential(final AsymmetricJWK key) throws JOSEException {
+        final BasicExpiringJWKCredential jwkCredential = new BasicExpiringJWKCredential();
+        jwkCredential.setPrivateKey(key.toPrivateKey());
+        jwkCredential.setPublicKey(key.toPublicKey());
+        jwkCredential.setCredentialExpiresAt(Duration.ZERO);
+        jwkCredential.setUsageType(UsageType.SIGNING);
+        
+        jwkCredential.setKid(((JWK)key).getKeyID());
+        jwkCredential.getKeyNames().add(((JWK)key).getKeyID());
+        jwkCredential.setAlgorithm(((JWK)key).getAlgorithm());
+        return jwkCredential;
+    }
+    
+    
+    /**
+     * Create a key encryption {@link JWKCredential} from the given RSA key.
+     * 
+     * @param secret the RSAKey to convert to a {@link JWKCredential}.
+     * 
+     * @return the credential
+     * @throws JOSEException  on error
+     */
+    public static 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(secret.getKeyID());
+        jwkCredential.setAlgorithm(secret.getAlgorithm());
+        return jwkCredential;
+    }
+    
+    /**
+     * Create a key agreement encryption {@link JWKCredential} from the given EC key.
+     * 
+     * @param secret the ECKey to convert to a {@link JWKCredential}.
+     * 
+     * @return the credential
+     * @throws JOSEException  on error
+     */
+    public static JWKCredential createKeyAgreementCredential(final ECKey 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(secret.getKeyID());
+        jwkCredential.setAlgorithm(secret.getAlgorithm());
+        return jwkCredential;
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/test/resources/credentials/test-provider-resolver-remote-jwkset-response-no-alg.jwk b/oidc-common-crypto-impl/src/test/resources/credentials/test-provider-resolver-remote-jwkset-response-no-alg.jwk
new file mode 100644
index 0000000..5473bee
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/resources/credentials/test-provider-resolver-remote-jwkset-response-no-alg.jwk
@@ -0,0 +1,66 @@
+{
+"keys": [
+{
+"kty": "RSA",
+"e": "AQAB",
+"use": "sig",
+"kid": "7da9fea4-7a38-4398-aaef-8226b26776a2",
+"n": "kk-3jeBmUPbpMk0fEdIn-APAdNOoOckA0e-SiALLxy5dWfG-GyF51g31zuM_iNiSiMSsmG2ZAVi48iItFpd-JW9IIT40TC147I6aKrel0Rf39Mwp-1tCzME6VYEgOmgI9qDg2e4edt1cvjQfiw3IZlXakwgYQn2BuoknoCBVjETVLHrnsvEqXhPffzML9O5Ze_nBOX6-pCAzVsimr-ljoln2GQz-ID5fGzlflXJV78v7QzlyyAAQovYQMxiEBgecHu44S0Iu_esLEOOobQkZyHc-OgcwEazfJUEUhKEnevVTJFlQF3Odxp1I6W9zd-zLUceqIMKF5Xs10AfmkPhboQ"
+},
+{
+"kty": "RSA",
+"e": "AQAB",
+"use": "sig",
+"kid": "9ae4e77c-a0c7-4c52-982f-b8e5e6b62ab8",
+"n": "lBh4Ujl1k_H9CAfJe-SD-ngZnllWh5lShhv2FF_OlSlDEwr5wbf4WimeQhqLtfeT-dJXALpLSncaG_5y8pHHh0Pflnx_pZfCoOOc4Fba7wZgpHzfSQePwIDH8ygmzMLNzLaECa5m1LxnDD0oVHsABOab-_6_Uvuvam5xo2pKfJHoxkVsEDxQ2R0T_GfqC2bmCNJCdadeqw43yF_ILBRX-9sosA_7GPwyBWKAyiHX-DTUKwWrpR2bwCGE2Bxfgj3cDa97prSX8Vwpj_DEPOH8hbMAjO-N4EBcvcJZ0O0CD3X3IrquC__wqc9aOMEh2xbRxnTHdrNNG1KqzS8-L2yS0w"
+},
+{
+"kty": "EC",
+"use": "sig",
+"crv": "P-256",
+"kid": "7b1a7c28-df25-4d54-b111-11903db56d52",
+"x": "XRlwH72XaSlYjybpA6q4DTHsOphTuSWNPULNKwQ38wo",
+"y": "xWIoYZAyQxZM7RJCL-k14PdIHkCPo4m2tKRiCWyXU9w"
+},
+{
+"kty": "EC",
+"use": "sig",
+"crv": "P-256",
+"kid": "00cfe876-ed35-4052-8045-be1088c3212f",
+"x": "SwSHs_Df-Qxl83Mibu_lWzxn0mBn9hGts9gougQlrqs",
+"y": "kKQfhy4jDV_cxpC3iptQTFODkgENp-HC4XK7NIDqt5s"
+},
+{
+"kty": "EC",
+"use": "sig",
+"crv": "secp256k1",
+"kid": "4d1d9b37-3acc-46b9-9426-ee209db8541f",
+"x": "cZb41D8qgFxbpxnqOcp-kc78M8EdtSYqotje0IhWk_o",
+"y": "viJ95PgOZdYFPHRqdO4NOhRQgkejVDv8RmDprbLE31g"
+},
+{
+"kty": "OKP",
+"use": "sig",
+"crv": "Ed25519",
+"kid": "0c54869d-7d20-4faf-b607-3b040d1e1f27",
+"x": "gi3CalT0xmz8V52rgfdvYyM-rUwKnf8gUUUqB87Gycw"
+},
+{
+"kty": "RSA",
+"e": "AQAB",
+"use": "enc",
+"kid": "87ff206d-15f9-4b8c-ba88-a8c17014da13",
+"alg": "RSA-OAEP",
+"n": "uAVnVD3cMEbrAsDg1c3n6GfzR3sSg9C9pbjTw39_jgWk5YQCHPPOt4zYyZZL2JCnm9TFjnndCCW5ZPWHPJjumiNB2r-vC0CmI-T66JSRX3YYw0h2Odiusr_74FNe_mYyEuClFa4hwo-RMgrp8L1sbrAWcgGOc84rD6-fZXVrWFMkOb0jg6tqF1EwBSxZFG1cfvUmatNuBXs6njPHvvqhd7Bz6adK4YkpzCUbD-jSjpvAvU-Q4TZT_bXq4WRFOPqXv2NX4ch7ErjEm5tJEk7BIqOh7Byg0pWB4WAwsMcZKnHlp7JjtB2T1s_45_iqD2xipxpF-NxoHUlz67qHt7-W4Q"
+},
+{
+"kty": "EC",
+"use": "enc",
+"crv": "P-256",
+"kid": "c689ce91-8d82-45f2-b671-38ee38e7599f",
+"x": "redOUw802EuKJRoS8kQx6_RjuCypx0dcMBhv4IAALvQ",
+"y": "oaypduaS_wPLGCDQfJ0SKtQu0urJzhr4ZRn5wYMhiyk",
+"alg": "ECDH-ES"
+}
+]
+}
\ No newline at end of file
diff --git a/oidc-common-crypto-impl/src/test/resources/credentials/test-provider-resolver-remote-jwkset-response.jwk b/oidc-common-crypto-impl/src/test/resources/credentials/test-provider-resolver-remote-jwkset-response.jwk
new file mode 100644
index 0000000..5473bee
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/resources/credentials/test-provider-resolver-remote-jwkset-response.jwk
@@ -0,0 +1,66 @@
+{
+"keys": [
+{
+"kty": "RSA",
+"e": "AQAB",
+"use": "sig",
+"kid": "7da9fea4-7a38-4398-aaef-8226b26776a2",
+"n": "kk-3jeBmUPbpMk0fEdIn-APAdNOoOckA0e-SiALLxy5dWfG-GyF51g31zuM_iNiSiMSsmG2ZAVi48iItFpd-JW9IIT40TC147I6aKrel0Rf39Mwp-1tCzME6VYEgOmgI9qDg2e4edt1cvjQfiw3IZlXakwgYQn2BuoknoCBVjETVLHrnsvEqXhPffzML9O5Ze_nBOX6-pCAzVsimr-ljoln2GQz-ID5fGzlflXJV78v7QzlyyAAQovYQMxiEBgecHu44S0Iu_esLEOOobQkZyHc-OgcwEazfJUEUhKEnevVTJFlQF3Odxp1I6W9zd-zLUceqIMKF5Xs10AfmkPhboQ"
+},
+{
+"kty": "RSA",
+"e": "AQAB",
+"use": "sig",
+"kid": "9ae4e77c-a0c7-4c52-982f-b8e5e6b62ab8",
+"n": "lBh4Ujl1k_H9CAfJe-SD-ngZnllWh5lShhv2FF_OlSlDEwr5wbf4WimeQhqLtfeT-dJXALpLSncaG_5y8pHHh0Pflnx_pZfCoOOc4Fba7wZgpHzfSQePwIDH8ygmzMLNzLaECa5m1LxnDD0oVHsABOab-_6_Uvuvam5xo2pKfJHoxkVsEDxQ2R0T_GfqC2bmCNJCdadeqw43yF_ILBRX-9sosA_7GPwyBWKAyiHX-DTUKwWrpR2bwCGE2Bxfgj3cDa97prSX8Vwpj_DEPOH8hbMAjO-N4EBcvcJZ0O0CD3X3IrquC__wqc9aOMEh2xbRxnTHdrNNG1KqzS8-L2yS0w"
+},
+{
+"kty": "EC",
+"use": "sig",
+"crv": "P-256",
+"kid": "7b1a7c28-df25-4d54-b111-11903db56d52",
+"x": "XRlwH72XaSlYjybpA6q4DTHsOphTuSWNPULNKwQ38wo",
+"y": "xWIoYZAyQxZM7RJCL-k14PdIHkCPo4m2tKRiCWyXU9w"
+},
+{
+"kty": "EC",
+"use": "sig",
+"crv": "P-256",
+"kid": "00cfe876-ed35-4052-8045-be1088c3212f",
+"x": "SwSHs_Df-Qxl83Mibu_lWzxn0mBn9hGts9gougQlrqs",
+"y": "kKQfhy4jDV_cxpC3iptQTFODkgENp-HC4XK7NIDqt5s"
+},
+{
+"kty": "EC",
+"use": "sig",
+"crv": "secp256k1",
+"kid": "4d1d9b37-3acc-46b9-9426-ee209db8541f",
+"x": "cZb41D8qgFxbpxnqOcp-kc78M8EdtSYqotje0IhWk_o",
+"y": "viJ95PgOZdYFPHRqdO4NOhRQgkejVDv8RmDprbLE31g"
+},
+{
+"kty": "OKP",
+"use": "sig",
+"crv": "Ed25519",
+"kid": "0c54869d-7d20-4faf-b607-3b040d1e1f27",
+"x": "gi3CalT0xmz8V52rgfdvYyM-rUwKnf8gUUUqB87Gycw"
+},
+{
+"kty": "RSA",
+"e": "AQAB",
+"use": "enc",
+"kid": "87ff206d-15f9-4b8c-ba88-a8c17014da13",
+"alg": "RSA-OAEP",
+"n": "uAVnVD3cMEbrAsDg1c3n6GfzR3sSg9C9pbjTw39_jgWk5YQCHPPOt4zYyZZL2JCnm9TFjnndCCW5ZPWHPJjumiNB2r-vC0CmI-T66JSRX3YYw0h2Odiusr_74FNe_mYyEuClFa4hwo-RMgrp8L1sbrAWcgGOc84rD6-fZXVrWFMkOb0jg6tqF1EwBSxZFG1cfvUmatNuBXs6njPHvvqhd7Bz6adK4YkpzCUbD-jSjpvAvU-Q4TZT_bXq4WRFOPqXv2NX4ch7ErjEm5tJEk7BIqOh7Byg0pWB4WAwsMcZKnHlp7JjtB2T1s_45_iqD2xipxpF-NxoHUlz67qHt7-W4Q"
+},
+{
+"kty": "EC",
+"use": "enc",
+"crv": "P-256",
+"kid": "c689ce91-8d82-45f2-b671-38ee38e7599f",
+"x": "redOUw802EuKJRoS8kQx6_RjuCypx0dcMBhv4IAALvQ",
+"y": "oaypduaS_wPLGCDQfJ0SKtQu0urJzhr4ZRn5wYMhiyk",
+"alg": "ECDH-ES"
+}
+]
+}
\ No newline at end of file
diff --git a/oidc-common-crypto-impl/src/test/resources/metadata/test-resolver-provider-encryption.json b/oidc-common-crypto-impl/src/test/resources/metadata/test-resolver-provider-encryption.json
new file mode 100644
index 0000000..02633d9
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/resources/metadata/test-resolver-provider-encryption.json
@@ -0,0 +1,89 @@
+{
+   "issuer":"https://localhost:9921",
+   "authorization_endpoint":"https://localhost:9921/o/oauth2/v2/auth",
+   "device_authorization_endpoint":"https://localhost:9921/device/code",
+   "token_endpoint":"https://localhost:9921/token",
+   "userinfo_endpoint":"https://localhost:9921/v1/userinfo",
+   "revocation_endpoint":"https://localhost:9921/revoke",
+   "jwks_uri":"https://localhost:9921/oauth2/v3/certs",
+   "claims_parameter_supported":true,
+   "request_parameter_supported":true,
+   "response_types_supported":[
+      "code",
+      "token",
+      "id_token",
+      "code token",
+      "code id_token",
+      "token id_token",
+      "code token id_token",
+      "none"
+   ],
+   "subject_types_supported":[
+      "public"
+   ],
+   "id_token_signing_alg_values_supported":[
+      "RS256"
+   ],
+   "request_object_signing_alg_values_supported":[
+      "HS256"
+   ],
+   "request_object_encryption_alg_values_supported":[
+      "RSA1_5",
+      "RSA-OAEP",
+      "RSA-OAEP-256",
+      "RSA-OAEP-384",
+      "RSA-OAEP-512",
+      "ECDH-ES",
+      "ECDH-ES+A128KW",
+      "ECDH-ES+A192KW",
+      "ECDH-ES+A256KW",
+      "A128KW",
+      "A192KW",
+      "A256KW",
+      "A128GCMKW",
+      "A192GCMKW",
+      "A256GCMKW",
+      "dir"
+   ],
+   "request_object_encryption_enc_values_supported":[
+      "A128CBC-HS256",
+      "A192CBC-HS384",
+      "A256CBC-HS512",
+      "A128GCM",
+      "A192GCM",
+      "A256GCM"
+   ],
+   "scopes_supported":[
+      "openid",
+      "email",
+      "profile"
+   ],
+   "token_endpoint_auth_methods_supported":[
+      "client_secret_post",
+      "client_secret_basic"
+   ],
+   "claims_supported":[
+      "aud",
+      "email",
+      "email_verified",
+      "exp",
+      "family_name",
+      "given_name",
+      "iat",
+      "iss",
+      "locale",
+      "name",
+      "picture",
+      "sub"
+   ],
+   "code_challenge_methods_supported":[
+      "plain",
+      "S256"
+   ],
+   "grant_types_supported":[
+      "authorization_code",
+      "refresh_token",
+      "urn:ietf:params:oauth:grant-type:device_code",
+      "urn:ietf:params:oauth:grant-type:jwt-bearer"
+   ]
+}
\ No newline at end of file

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


More information about the commits mailing list