[java-idp-plugin-oidc-op-oidfed] branch dev/CACHE-REFACTOR updated: Harmonised explicit registration request (statement and trust chain) validation

Codeberg noreply at shibboleth.net
Thu Jan 29 16:28:15 UTC 2026


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

codeberg pushed a commit to branch dev/CACHE-REFACTOR
in repository java-idp-plugin-oidc-op-oidfed.

View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-oidc-op-oidfed/commit/0021ff1020e74f53431f9b5077e7dcaa89512866

The following commit(s) were added to refs/heads/dev/CACHE-REFACTOR by this push:
     new 0021ff1  Harmonised explicit registration request (statement and trust chain) validation
0021ff1 is described below

commit 0021ff1020e74f53431f9b5077e7dcaa89512866
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu Jan 29 18:27:52 2026 +0200

    Harmonised explicit registration request (statement and trust chain) validation
    
    - By default fetch-through metadata caches are exploited for both provided entity configurations and trust chains
      - Content validation can be shared with the other existing metadata caches
        - Explicit registration specific claims validation: shibboleth.oidfed.DefaultExplicitRegistrationRequestClaimsValidator (includes audience)
      - This way also the custom cache container filter strategies may be wired via 'idp.oidfed.cache.<cacheType>.customFilterStrategies'
        - new cacheTypes: explicitRegistration.trustChain and explicitRegistration.entityConfiguration
---
 ...rustChainSignatureValidationFilterStrategy.java | 125 ------------------
 ...efaultProvidedTrustChainValidationStrategy.java | 119 -----------------
 .../ProvidedEntityConfigurationContainer.java      |  55 ++++++++
 ...videdEntityConfigurationContainerCriterion.java |  88 +++++++++++++
 .../cache/local/ProvidedTrustChainContainer.java   |  50 ++++++++
 .../ProvidedTrustChainContainerCriterion.java      |  81 ++++++++++++
 ...videdEntityConfigurationValidationStrategy.java |  97 ++++++++++++++
 ...efaultProvidedTrustChainValidationStrategy.java |  96 ++++++++++++++
 .../META-INF/net.shibboleth.idp/postconfig.xml     |  38 ++++++
 .../idp/flows/oidfed/register/register-beans.xml   | 142 +++++++++++++--------
 ...shedAuthorizeFlowAutomaticRegistrationTest.java |   2 +
 .../profile/flow/oidfed/RegistrationFlowTest.java  |  86 +++++++++++--
 .../oidfed/TokenFlowAutomaticRegistrationTest.java |  19 ++-
 13 files changed, 686 insertions(+), 312 deletions(-)

diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainSignatureValidationFilterStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainSignatureValidationFilterStrategy.java
deleted file mode 100644
index 04b2901..0000000
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainSignatureValidationFilterStrategy.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.BiFunction;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.security.trust.TrustEngine;
-import org.slf4j.Logger;
-
-import com.nimbusds.jwt.SignedJWT;
-
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
-import net.shibboleth.oidc.metadata.filter.MetadataFilterContext;
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.shared.resolver.CriteriaSet;
-
-/**
- * Default signature validating filter for trust chains. The signature for each entity statement within trust chain is
- * verified by using the contents of the jwks-claim of the payload of the next entity statement in the chain until the
- * final subordinate statement of the chain. The configurable {@link TrustEngine} is used for validating the signature.
- * The trust engine is fed with the JWT to be verified and the {@link SignatureValidationKeyContainerJwtCriterion}
- * containing the JWT with the keyset used for validation.
- * 
- * The trust anchor signature validation is performed by passing the trust anchor entity statement of each trust chain
- * to the configurable validation filter strategy.
- * 
- * All the chains that don't pass the full validation are filtered out from the result.
- */
- at ThreadSafeAfterInit
-public class DefaultProvidedTrustChainSignatureValidationFilterStrategy
-    extends AbstractTrustEngineSignatureValidationComponent
-    implements BiFunction<List<List<EntityStatement<?>>>, MetadataFilterContext, List<List<EntityStatement<?>>>> {
-
-    /** Class logger. */
-    @Nonnull private Logger log =
-            LoggerFactory.getLogger(DefaultProvidedTrustChainSignatureValidationFilterStrategy.class);
-
-    /** Signature validation filter strategy for trust anchor entity statements. */
-    @NonnullAfterInit private BiFunction<EntityStatement<?>, MetadataFilterContext, EntityStatement<?>>
-        entityStatementSignatureValidationFilterStrategy;
-
-    /**
-     * Set the signature validation filter strategy for trust anchor entity statements.
-     * 
-     * @param strategy signature validation filter strategy
-     */
-    public void setEntityStatementSignatureValidationFilterStrategy(@Nonnull final
-            BiFunction<EntityStatement<?>, MetadataFilterContext, EntityStatement<?>> strategy) {
-        checkSetterPreconditions();
-        entityStatementSignatureValidationFilterStrategy = Constraint.isNotNull(strategy,
-                "EntityStatementSignatureValidationFilterStrategy cannot be null");
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected void doInitialize() throws ComponentInitializationException {
-        super.doInitialize();
-        if (entityStatementSignatureValidationFilterStrategy == null) {
-            throw new ComponentInitializationException(
-                    "EntityStatementSignatureValidationFilterStrategy cannot be null");
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override @Nullable
-    public List<List<EntityStatement<?>>> apply(@Nullable final List<List<EntityStatement<?>>> trustChains,
-            @Nullable final MetadataFilterContext filterContext) {
-        checkComponentActive();
-        if (trustChains == null) {
-            return null;
-        }
-        final List<List<EntityStatement<?>>> result = new ArrayList<>();
-        
-        trustChainLoop: for (final List<EntityStatement<?>> trustChain : trustChains) {
-            for (int i = 0; i < trustChain.size() - 1; i++) {
-                final EntityStatement<?> trustChainEntry = trustChain.get(i);
-                final SignedJWT keyContainer = trustChain.get(i + 1).getJwt();
-                final CriteriaSet criteria =
-                        new CriteriaSet(new SignatureValidationKeyContainerJwtCriterion(keyContainer));
-                if (!validateJwt(trustChainEntry.getJwt(), criteria,
-                        trustChain.get(i).getSubject())) {
-                    log.warn("The signature check for {} failed, ignoring trust chain anchored by {}",
-                            i == 0 ? "leaf entity configuration" : "subordinate statement",
-                                    trustChain.get(trustChain.size() - 1).getSubject());
-                    log.trace("Key container {} could not validate the trust chain entry {}", keyContainer.serialize(),
-                            trustChainEntry.getJwt().serialize());
-                    continue trustChainLoop;
-                }
-            }
-            final EntityStatement<?> trustAnchorStatement = trustChain.get(trustChain.size() - 1);
-            final String entityId = trustAnchorStatement.getSubject();
-            final EntityStatement<?> filteredStatement =
-                    entityStatementSignatureValidationFilterStrategy.apply(trustAnchorStatement, filterContext);
-            if (filteredStatement != null) {
-                log.debug("Successfully validated entity statement for trust anchor {}", entityId);
-                result.add(trustChain);
-            } else {
-                log.debug("Signature validation failed for trust anchor {}", entityId);
-            }
-        }
-        return result;
-    }
-
-}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainValidationStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainValidationStrategy.java
deleted file mode 100644
index 2580a24..0000000
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainValidationStrategy.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache;
-
-import java.util.List;
-import java.util.Map;
-import java.util.function.BiFunction;
-import java.util.function.BiPredicate;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.constraints.FederationPolicyConstraint;
-import net.shibboleth.oidc.metadata.filter.MetadataFilterContext;
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.LoggerFactory;
-
-/**
- * Default strategy for validating provided trust chain signatures via configurable signature validation filter and
- * applying the constraints.
- */
-public class DefaultProvidedTrustChainValidationStrategy extends AbstractIdentifiableInitializableComponent 
-    implements BiPredicate<ProfileRequestContext, List<EntityStatement<?>>> {
-
-    /** Class logger. */
-    @Nonnull private Logger log = LoggerFactory.getLogger(DefaultProvidedTrustChainValidationStrategy.class);
-
-    /** Strategy for validating trust anchor's entity configuration signature. */
-    @NonnullAfterInit BiFunction<List<List<EntityStatement<?>>>, MetadataFilterContext, List<List<EntityStatement<?>>>>
-        trustChainSignatureValidationFilterStrategy;
-
-    /** Map of supported federation policy constraints. */
-    @NonnullAfterInit private Map<String, FederationPolicyConstraint> federationPolicyConstraints;
-
-    /**
-     * Set the strategy for validating trust chain signatures.
-     * 
-     * @param strategy validation strategy
-     */
-    public void setTrustChainSignatureValidationFilterStrategy(
-            @Nonnull final BiFunction<List<List<EntityStatement<?>>>,
-                MetadataFilterContext, List<List<EntityStatement<?>>>> strategy) {
-        checkSetterPreconditions();
-        trustChainSignatureValidationFilterStrategy = Constraint.isNotNull(strategy,
-                "TrustChainSignatureValidationFilterStrategy cannot be null");
-    }
-
-    /**
-     * Set the map of supported federation policy constraints.
-     * 
-     * @param constraints map of supported federation policy constraints.
-     */
-    public void setFederationPolicyConstraints(@Nonnull final Map<String, FederationPolicyConstraint> constraints) {
-        checkSetterPreconditions();
-        federationPolicyConstraints = Constraint.isNotNull(constraints, "Map of policy constraints cannot be null");
-    }
-
-    /**
-     * Get the map of supported federation policy constraints.
-     * 
-     * @return map of supported federation policy constraints.
-     */
-    @Nonnull public Map<String, FederationPolicyConstraint> getFederationPolicyConstraints() {
-        checkComponentActive();
-        assert federationPolicyConstraints != null;
-        return federationPolicyConstraints;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected void doInitialize() throws ComponentInitializationException {
-        super.doInitialize();
-        if (trustChainSignatureValidationFilterStrategy == null) {
-            throw new ComponentInitializationException("TrustChainSignatureValidationFilterStrategy cannot be null");
-        }
-        if (federationPolicyConstraints == null) {
-            throw new ComponentInitializationException("Map of policy constraints cannot be null");
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean test(@Nullable final ProfileRequestContext profileRequestContext,
-            @Nullable final List<EntityStatement<?>> trustChain) {
-        checkComponentActive();
-        if (trustChain == null || trustChain.size() < 3 || trustChain.contains(null)) {
-            log.error("No satisfactory trust chain provided");
-            return false;
-        }
-
-        final List<List<EntityStatement<?>>> validatedChains =
-                trustChainSignatureValidationFilterStrategy.apply(List.of(trustChain), null);
-        if (validatedChains == null || validatedChains.size() != 1) {
-            log.debug("The trust chain did not pass the signature validation");
-            return false;
-        }
-
-        return true;
-    }
-}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedEntityConfigurationContainer.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedEntityConfigurationContainer.java
new file mode 100644
index 0000000..a7186d8
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedEntityConfigurationContainer.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local;
+
+import java.time.Instant;
+import java.util.Optional;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityConfiguration;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.RemoteEntityConfigurationContainer;
+
+/**
+ * Metadata cache container for provided entity configuration.
+ */
+public class ProvidedEntityConfigurationContainer extends RemoteEntityConfigurationContainer {
+
+    /** Serial version UID. */
+    private static final long serialVersionUID = -6322735237721678470L;
+
+    /**
+     * Constructor.
+     *
+     * @param configuration provided entity configuration
+     */
+    public ProvidedEntityConfigurationContainer(@Nonnull final EntityConfiguration configuration) {
+        super(configuration.getSubject(), configuration, parseExpirationInstant(configuration),
+                parseExpirationInstant(configuration));
+    }
+
+    /**
+     * Parse expiration instant from entity configuration.
+     * 
+     * @param configuration entity configuration
+     * @return expiration set in configuration, or 5 minutes from now if it was not set
+     */
+    @Nonnull private static Instant parseExpirationInstant(@Nonnull final EntityConfiguration configuration) {
+        final Instant expiration = Optional.ofNullable(configuration.getParsedPayload().getExpiration())
+                .orElse(Instant.now().plusSeconds(300));
+        assert expiration != null;
+        return expiration;
+    }
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedEntityConfigurationContainerCriterion.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedEntityConfigurationContainerCriterion.java
new file mode 100644
index 0000000..81f5b37
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedEntityConfigurationContainerCriterion.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing a provided entity configuration container.
+ */
+public class ProvidedEntityConfigurationContainerCriterion implements Criterion {
+
+    /** The entity configuration container value. */
+    @Nonnull final ProvidedEntityConfigurationContainer value;
+
+    /**
+     * Constructor.
+     *
+     * @param container the entity configuration container value, must not be null
+     */
+    public ProvidedEntityConfigurationContainerCriterion(
+            @Nonnull final ProvidedEntityConfigurationContainer container) {
+        value = Constraint.isNotNull(container, "Entity configuration container cannot be null");
+    }
+
+    /**
+     * Get the entity configuration container value.
+     * 
+     * @return the container value
+     */
+    @Nonnull public ProvidedEntityConfigurationContainer getValue() {
+        return value;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        final var statement = value.getStatement();
+        return "ProvidedEntityConfigurationContainerCriterion [value=" 
+                + (statement == null ? "null" : statement.getJwt().serialize()) 
+                + "]";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return Objects.hash(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final ProvidedEntityConfigurationContainerCriterion other = (ProvidedEntityConfigurationContainerCriterion) obj;
+        final var statement = value.getStatement();
+        final var otherStatement = other.value.getStatement();
+        if (statement == null) {
+            return otherStatement == null;
+        } else {
+            return statement.getJwt().serialize().equals(otherStatement == null ?
+                    null : otherStatement.getJwt().serialize());
+        }
+    }
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedTrustChainContainer.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedTrustChainContainer.java
new file mode 100644
index 0000000..1ecce44
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedTrustChainContainer.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local;
+
+import java.time.Instant;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.RemoteTrustChainsContainer;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Metadata cache container for provided trust chain. The expiration is hardcoded to five minutes as it's assumed
+ * that the container is solely used with fetch-through caches and the expiration validation is done during the
+ * filtering phase.
+ */
+public class ProvidedTrustChainContainer extends RemoteTrustChainsContainer {
+
+    /** Serial version UID. */
+    private static final long serialVersionUID = -6322735111721678470L;
+
+    /**
+     * Constructor.
+     *
+     * @param trustChain trust chain
+     * @param entity entity ID for the trust chain subject
+     */
+    public ProvidedTrustChainContainer(@Nonnull final List<EntityStatement<?>> trustChain,
+            @Nonnull @NotEmpty final String entity) {
+        super(Constraint.isNotNull(Instant.now().plusSeconds(300), "Expiration instant cannot be null"),
+                entity, CollectionSupport.listOf(trustChain));
+    }
+
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedTrustChainContainerCriterion.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedTrustChainContainerCriterion.java
new file mode 100644
index 0000000..86fee10
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedTrustChainContainerCriterion.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing a provided trust chain container.
+ */
+public class ProvidedTrustChainContainerCriterion implements Criterion {
+
+    /** The trust chain container value. */
+    @Nonnull final ProvidedTrustChainContainer value;
+
+    /**
+     * Constructor.
+     *
+     * @param container the trust chain container value, must not be null
+     */
+    public ProvidedTrustChainContainerCriterion(
+            @Nonnull final ProvidedTrustChainContainer container) {
+        value = Constraint.isNotNull(container, "Trust chain container cannot be null");
+    }
+
+    /**
+     * Get the trust chain container value.
+     * 
+     * @return the container value
+     */
+    @Nonnull public ProvidedTrustChainContainer getValue() {
+        return value;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "ProvidedTrustChainContainerCriterion [value="  + value + "]";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return Objects.hash(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final ProvidedTrustChainContainerCriterion other = (ProvidedTrustChainContainerCriterion) obj;
+        final var chains = value.getTrustChains();
+        final var otherChains = other.value.getTrustChains();
+        return value.getEntityId().equals(other.value.getEntityId()) && chains.containsAll(otherChains)
+                && otherChains.containsAll(chains);
+    }
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/logic/DefaultProvidedEntityConfigurationValidationStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/logic/DefaultProvidedEntityConfigurationValidationStrategy.java
new file mode 100644
index 0000000..fd42bd6
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/logic/DefaultProvidedEntityConfigurationValidationStrategy.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.logic;
+
+import java.util.List;
+import java.util.function.BiPredicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityConfiguration;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.ProvidedEntityConfigurationContainer;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.ProvidedEntityConfigurationContainerCriterion;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.RemoteEntityConfigurationContainer;
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Default validation strategy for provided entity configuration. A {@link MetadataCache} supporting
+ * {@link ProvidedEntityConfigurationContainerCriterion} is exploited for validation.
+ */
+public class DefaultProvidedEntityConfigurationValidationStrategy extends AbstractIdentifiableInitializableComponent
+    implements BiPredicate<ProfileRequestContext, EntityStatement<?>> {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(DefaultProvidedEntityConfigurationValidationStrategy.class);
+
+    /** Metadata cache for entity configuration validation. */
+    @NonnullAfterInit private MetadataCache<RemoteEntityConfigurationContainer> metadataCache;
+
+    /**
+     * Set the metadata cache for entity configuration validation.
+     * 
+     * @param cache What to set.
+     */
+    public void setMetadataCache(@Nonnull final MetadataCache<RemoteEntityConfigurationContainer> cache) {
+        checkSetterPreconditions();
+        metadataCache = Constraint.isNotNull(cache, "Metadata cache cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+
+        if (metadataCache == null) {
+            throw new ComponentInitializationException("Metadata cache cannot be null");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean test(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nullable final EntityStatement<?> entityStatement) {
+        if (entityStatement instanceof EntityConfiguration entityConfiguration) {
+            final ProvidedEntityConfigurationContainer container =
+                    new ProvidedEntityConfigurationContainer(entityConfiguration);
+            final ProvidedEntityConfigurationContainerCriterion criterion =
+                    new ProvidedEntityConfigurationContainerCriterion(container);
+            try {
+                final List<RemoteEntityConfigurationContainer> result =
+                        metadataCache.get(new CriteriaSet(criterion));
+                if (result.size() == 1 && result.get(0).getStatement() != null) {
+                    return true;
+                }
+            } catch (final MetadataCacheException e) {
+                log.debug("{} Exception catched when validating entty configuration", e);
+            }
+           
+        } else {
+            log.error("No entity configuration provided, returning false");
+        }
+        return false;
+    }
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/logic/DefaultProvidedTrustChainValidationStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/logic/DefaultProvidedTrustChainValidationStrategy.java
new file mode 100644
index 0000000..2ff1715
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/logic/DefaultProvidedTrustChainValidationStrategy.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.logic;
+
+import java.util.List;
+import java.util.function.BiPredicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.ProvidedTrustChainContainer;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.ProvidedTrustChainContainerCriterion;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.RemoteTrustChainsContainer;
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Default validation strategy for provided trust chain. A {@link MetadataCache} supporting
+ * {@link ProvidedTrustChainContainerCriterion} is exploited for validation.
+ */
+public class DefaultProvidedTrustChainValidationStrategy extends AbstractIdentifiableInitializableComponent
+    implements BiPredicate<ProfileRequestContext, List<EntityStatement<?>>> {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(DefaultProvidedTrustChainValidationStrategy.class);
+
+    /** Metadata cache for trust chain validation. */
+    @NonnullAfterInit private MetadataCache<RemoteTrustChainsContainer> metadataCache;
+
+    /**
+     * Set the metadata cache for trust chain validation.
+     * 
+     * @param cache What to set.
+     */
+    public void setMetadataCache(@Nonnull final MetadataCache<RemoteTrustChainsContainer> cache) {
+        checkSetterPreconditions();
+        metadataCache = Constraint.isNotNull(cache, "Metadata cache cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+
+        if (metadataCache == null) {
+            throw new ComponentInitializationException("Metadata cache cannot be null");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean test(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nullable final List<EntityStatement<?>> trustChain) {
+        if (trustChain != null && !trustChain.isEmpty()) {
+            final ProvidedTrustChainContainer container = new ProvidedTrustChainContainer(trustChain,
+                    trustChain.get(0).getSubject());
+            final ProvidedTrustChainContainerCriterion criterion =
+                    new ProvidedTrustChainContainerCriterion(container);
+            try {
+                final List<RemoteTrustChainsContainer> result = metadataCache.get(new CriteriaSet(criterion));
+                if (result.size() == 1 && !result.get(0).getTrustChains().isEmpty()) {
+                    return true;
+                }
+            } catch (final MetadataCacheException e) {
+                log.debug("Exception catched when validating trust chain", e);
+            }
+
+        } else {
+            log.error("No trust chain provided, returning false");
+        }
+        return false;
+    }
+
+}
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 12762ff..3b5b4ce 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -1066,6 +1066,44 @@
         p:entityConfigurationCache-ref="shibboleth.oidfed.EntityConfigurationMetadataCache"
         p:trustMarkCache-ref="shibboleth.oidfed.TrustMarkMetadataCache"/>
 
+    <bean id="shibboleth.oidfed.DefaultExplicitRegistrationRequestClaimsValidator"
+        class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator">
+        <property name="claimValidators">
+            <util:list value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+                <bean class="net.shibboleth.oidc.security.jwt.claims.impl.IssuedAtClaimsValidator"
+                    p:clockSkew="%{idp.policy.clockSkew:PT1M}"
+                    p:messageLifetime="%{idp.policy.messageLifetime:PT1M}"
+                    p:requiredRule="true" />
+                <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+                    p:requiredClaims="iss" />
+                <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+                    p:requiredClaims="sub" />
+                <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+                    p:requiredClaims="jwks" />
+                <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
+                    p:clockSkew="%{idp.policy.clockSkew:PT1M}" />
+                <bean class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator">
+                    <property name="audienceLookupStrategy">
+                        <bean parent="shibboleth.BiFunctions.Constant"
+                          c:target-ref="shibboleth.oidc.issuer" />
+                    </property>
+               </bean>
+                <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+                    p:prohibitedClaims="metadata_policy" />
+                <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+                    p:prohibitedClaims="metadata_policy_crit" />
+                <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+                    p:prohibitedClaims="constraints" />
+                <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+                    p:prohibitedClaims="source_endpoint" />
+                <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.jwt.claims.impl.CritClaimsValidator"
+                    p:recognizedClaims="%{idp.oidfed.cache.explicitRegistration.critClaims:%{idp.oidfed.cache.default.critClaims:}}" />
+                <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+                    p:requiredClaims="authority_hints" />
+            </util:list>
+        </property>
+    </bean>
+
     <import resource="${idp.home}/conf/oidfed/oidfed-trustchain-resolver.xml"/>
 
 </beans>
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
index 6cce525..6ad1232 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
@@ -46,42 +46,61 @@
         p:metadataValidationCondition-ref="#{'%{idp.oidfed.MetadataValidationCondition:DefaultMetadataValidationCondition}'.trim()}"
         p:federationPolicyConstraints-ref="%{idp.oidfed.FederationPolicyConstraints:shibboleth.oidfed.DefaultFederationPolicyConstraints}">
         <property name="providedTrustChainValidationStrategy">
-            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultProvidedTrustChainValidationStrategy"
-                p:federationPolicyConstraints-ref="%{idp.oidfed.FederationPolicyConstraints:shibboleth.oidfed.DefaultFederationPolicyConstraints}">
-                <property name="trustChainSignatureValidationFilterStrategy">
-                    <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultProvidedTrustChainSignatureValidationFilterStrategy">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.logic.DefaultProvidedTrustChainValidationStrategy"
+                p:metadataCache-ref="#{'%{idp.oidfed.register.ProvidedTrustChainMetadataCache:FetchThroughProvidedTrustChainMetadataCache}'.trim()}" />
+        </property>
+        <property name="providedTrustChainLookupStrategy">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.ExplicitClientRegistrationRequestTrustChainLookupFunction" />
+        </property>
+    </bean>
+
+    <bean id="FetchThroughProvidedTrustChainMetadataCache" parent="shibboleth.oidc.CacheBuilder">
+        <constructor-arg>
+            <bean p:cacheId="FetchThroughProvidedTrustChainMetadataCache" parent="FetchThroughProvidedTrustChainMetadataCacheBuilderSpec"/>
+        </constructor-arg>
+    </bean>
+
+    <bean id="FetchThroughProvidedTrustChainMetadataCacheBuilderSpec"
+        class="net.shibboleth.oidc.metadata.cache.impl.FetchThroughMetadataCacheBuilderSpec">
+        <property name="criteriaToIdentifierStrategy">
+            <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.ProvidedTrustChainContainerCriterion))?.getValue()?.getEntityId()" />
+        </property>
+        <property name="identifierExtractionStrategy">
+            <bean parent="shibboleth.Functions.Expression" c:expression="#input?.getEntityId()"/>
+        </property>
+        <property name="fetchStrategy">
+            <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.ProvidedTrustChainContainerCriterion))?.getValue()" />
+        </property>
+        <property name="metadataFilterStrategy">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.trustchain.DefaultRemoteTrustChainSignatureValidationFilterStrategy"
+                p:customFilterStrategies="#{getObject('%{idp.oidfed.cache.explicitRegistration.trustChain.customFilterStrategies:}'.trim())}">
+                <property name="trustEngine">
+                    <bean class="net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine">
+                        <constructor-arg index="0">
+                            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadSignatureValidationCredentialResolver" />
+                        </constructor-arg>
+                        <constructor-arg index="1">
+                            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadJOSEObjectCredentialResolver" />
+                        </constructor-arg>
+                    </bean>
+                </property>
+                <property name="entityStatementSignatureValidationFilterStrategy">
+                    <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.DefaultEntityStatementSignatureValidationFilterStrategy">
                         <property name="trustEngine">
                             <bean class="net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine">
                                 <constructor-arg index="0">
-                                    <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadSignatureValidationCredentialResolver" />
-                                </constructor-arg>
-                                <constructor-arg index="1">
-                                    <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadJOSEObjectCredentialResolver" />
-                                </constructor-arg>
-                            </bean>
-                        </property>
-                        <property name="entityStatementSignatureValidationFilterStrategy">
-                            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.DefaultEntityStatementSignatureValidationFilterStrategy">
-                                <property name="trustEngine">
-                                    <bean class="net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine">
-                                        <constructor-arg index="0">
-                                            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultLocalTrustAnchorCredentialResolver"
-                                                c:cache-ref="shibboleth.oidfed.LocalTrustAnchorsMetadataCache" />
-                                         </constructor-arg>
-                                         <constructor-arg index="1">
-                                             <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadJOSEObjectCredentialResolver" />
-                                         </constructor-arg>
-                                    </bean>
-                                </property>
+                                    <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultLocalTrustAnchorCredentialResolver"
+                                        c:cache-ref="shibboleth.oidfed.LocalTrustAnchorsMetadataCache" />
+                                 </constructor-arg>
+                                 <constructor-arg index="1">
+                                     <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadJOSEObjectCredentialResolver" />
+                                 </constructor-arg>
                             </bean>
                         </property>
                     </bean>
                 </property>
             </bean>
         </property>
-        <property name="providedTrustChainLookupStrategy">
-            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.ExplicitClientRegistrationRequestTrustChainLookupFunction" />
-        </property>
     </bean>
 
     <bean id="ExplicitClientRegistrationRequestEntityConfigurationLookupFunction"
@@ -91,11 +110,43 @@
         scope="prototype"
         p:providedEntityConfigurationLookupStrategy-ref="ExplicitClientRegistrationRequestEntityConfigurationLookupFunction">
         <property name="providedEntityConfigurationValidationStrategy">
-            <bean parent="shibboleth.BiConditions.Expression"
-                c:expression="#custom.apply(#input2, null) != null">
-                <property name="customObject">
-                    <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.DefaultEntityStatementSignatureValidationFilterStrategy"
-                        p:trustEngine-ref="shibboleth.oidfed.DefaultEntityConfigurationTrustEngine"/>
+            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.logic.DefaultProvidedEntityConfigurationValidationStrategy"
+                p:metadataCache-ref="#{'%{idp.oidfed.register.ProvidedEntityConfigurationMetadataCache:FetchThroughProvidedEntityConfigurationMetadataCache}'.trim()}" />
+        </property>
+    </bean>
+
+    <bean id="FetchThroughProvidedEntityConfigurationMetadataCache" parent="shibboleth.oidc.CacheBuilder">
+        <constructor-arg>
+            <bean p:cacheId="FetchThroughProvidedEntityConfigurationMetadataCache" parent="FetchThroughProvidedEntityConfigurationMetadataCacheBuilderSpec"/>
+        </constructor-arg>
+    </bean>
+
+    <bean id="FetchThroughProvidedEntityConfigurationMetadataCacheBuilderSpec"
+        class="net.shibboleth.oidc.metadata.cache.impl.FetchThroughMetadataCacheBuilderSpec">
+        <property name="criteriaToIdentifierStrategy">
+            <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.ProvidedEntityConfigurationContainerCriterion))?.getValue()?.getEntityId()" />
+        </property>
+        <property name="identifierExtractionStrategy">
+            <bean parent="shibboleth.Functions.Expression" c:expression="#input?.getEntityId()"/>
+        </property>
+        <property name="fetchStrategy">
+            <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.ProvidedEntityConfigurationContainerCriterion))?.getValue()" />
+        </property>
+        <property name="metadataFilterStrategy">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.DefaultEntityStatementMetadataFilterStrategy"
+                p:customFilterStrategies="#{getObject('%{idp.oidfed.cache.explicitRegistration.entityConfiguration.customFilterStrategies:}'.trim())}">
+                <property name="validationConditions">
+                    <util:list value-type="java.util.function.BiPredicate">
+                        <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.DefaultEntityStatementSignatureValidationCondition"
+                            p:trustEngine-ref="shibboleth.oidfed.DefaultEntityConfigurationTrustEngine">
+                            <property name="criteriaSetLookupStrategy">
+                                <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.configuration.DefaultEntityConfigurationCriteriaSetLookupFunction"/>
+                            </property>
+                        </bean>
+                        <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.DefaultEntityStatementContentValidationCondition"
+                            p:claimsValidator-ref="%{idp.oidfed.cache.explicitRegistrationRequest.claimsValidator:shibboleth.oidfed.DefaultExplicitRegistrationRequestClaimsValidator}"
+                            p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"/>
+                    </util:list>
                 </property>
             </bean>
         </property>
@@ -150,27 +201,18 @@
                 p:criteriaToSubjectEntityIdStrategy-ref="shibboleth.oidfed.DefaultSubjectEntityIDCriteriaToIdentifierStrategy"
                 p:entityConfigurationCache-ref="shibboleth.oidfed.EntityConfigurationMetadataCache"
                 p:subordinateStatementCache-ref="shibboleth.oidfed.SubordinateEntityStatementMetadataCache"
-                p:localTrustAnchorsCache-ref="shibboleth.oidfed.LocalTrustAnchorsMetadataCache">
-                <property name="criteriaToValidContainerLifetimeStrategy">
-                    <bean parent="shibboleth.Functions.Constant">
-                        <constructor-arg>
-                            <!-- Doesn't matter for the fetch-through cache -->
-                            <bean class="java.time.Duration" factory-method="parse" c:_0="PT5M" />
-                        </constructor-arg>
-                    </bean>
-                </property>
-                <property name="criteriaToInvalidContainerLifetimeStrategy">
-                    <bean parent="shibboleth.Functions.Constant">
-                        <constructor-arg>
-                            <!-- Doesn't matter for the fetch-through cache -->
-                            <bean class="java.time.Duration" factory-method="parse" c:_0="PT5M" />
-                        </constructor-arg>
-                    </bean>
-                </property>
-            </bean>
+                p:localTrustAnchorsCache-ref="shibboleth.oidfed.LocalTrustAnchorsMetadataCache"
+                p:criteriaToValidContainerLifetimeStrategy-ref="ConstantFiveMinutes"
+                p:criteriaToInvalidContainerLifetimeStrategy-ref="ConstantFiveMinutes" />
         </property>
     </bean>
 
+    <bean id="ConstantFiveMinutes" parent="shibboleth.Functions.Constant">
+        <constructor-arg>
+            <bean class="java.time.Duration" factory-method="parse" c:_0="PT5M" />
+        </constructor-arg>
+    </bean>
+
     <bean id="DefaultMetadataPolicyEnforcer"
         class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyEnforcer"
         p:metadataPolicyOperators-ref="#{'%{idp.oidfed.register.MetadataPolicyOperatorsy:shibboleth.oidfed.StandardMetadataPolicyOperators}'.trim()}"/>
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
index 108e208..d9b8b8f 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
@@ -427,6 +427,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
         final String requestUri = response.getRequestURI().toString();
         Assert.assertTrue(requestUri.startsWith("urn:ietf:params:oauth:request_uri:"));
         final Map<String,Object> claims = statelessDeserializer.apply(null, response.getRequestURI());
+        assert claims != null;
         Assert.assertNull(claims.get(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN));
         verifyAuthorizeEndpoint(clientId, requestUri);
 
@@ -523,6 +524,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
         final String requestUri = resp.getRequestURI().toString();
         Assert.assertTrue(requestUri.startsWith("urn:ietf:params:oauth:request_uri:"));
         final Map<String,Object> claims = statelessDeserializer.apply(null, resp.getRequestURI());
+        assert claims != null;
         Assert.assertNotNull(claims.get(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN));
     }
 
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
index 18c0314..d25233d 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
@@ -22,8 +22,11 @@ import static org.mockito.Mockito.verify;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
+import java.net.URISyntaxException;
 import java.net.URLEncoder;
+import java.time.Instant;
 import java.util.Collections;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -38,7 +41,9 @@ import org.testng.annotations.Test;
 
 import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.jwk.JWK;
 import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.ParseException;
 import com.nimbusds.oauth2.sdk.Scope;
@@ -102,7 +107,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
     public void testValidEntityConfiguration_invalidType() throws Exception {
         final String clientId = uniqueClientId();
         rpConfigureMockHttpClient(clientId);
-        setRequest("POST", rpEntityConfiguration(clientId), "application/json");
+        setRequest("POST", explicitRegistrationRequest(clientId), "application/json");
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertErrorCode(result, "invalid_request");
     }
@@ -111,7 +116,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
     public void testValidEntityConfiguration() throws Exception {
         final String clientId = uniqueClientId();
         rpConfigureMockHttpClient(clientId);
-        setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+        setRequest("POST", explicitRegistrationRequest(clientId), "application/entity-statement+jwt");
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
@@ -123,18 +128,33 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
     public void testValidEntityConfiguration_emptyMetadataPolicyCrit() throws Exception {
         final String clientId = uniqueClientId();
         rpConfigureMockHttpClientWithMetadataCrit(clientId, CollectionSupport.emptyList());
-        setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+        setRequest("POST", explicitRegistrationRequest(clientId), "application/entity-statement+jwt");
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
         assertErrorCode(result, "invalid_client");
     }
 
+    @Test
+    public void testValidEntityConfiguration_emptyCritical() throws Exception {
+        final String clientId = uniqueClientId();
+        final OIDCClientMetadata metadata = new OIDCClientMetadata();
+        metadata.setRedirectionURI(new URI(redirectUri));
+        metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+        rpConfigureMockHttpClient(clientId, metadata);
+        setRequest("POST", explicitRegistrationRequest(clientId, metadata, CollectionSupport.emptyList(),
+                rpKey), "application/entity-statement+jwt");
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        verify(federationHttpClient, times(0)).executeOpen(any(),
+                argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
+        assertErrorCode(result, "invalid_request");
+    }
+
     @Test
     public void testValidEntityConfiguration_verifyPar() throws Exception {
         final String clientId = uniqueClientId();
         rpConfigureMockHttpClient(clientId);
-        setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+        setRequest("POST", explicitRegistrationRequest(clientId), "application/entity-statement+jwt");
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
@@ -148,7 +168,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         final String clientId = uniqueClientId();
         request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
         rpResolveEntityConfigureMockHttpClient(clientId);
-        setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+        setRequest("POST", explicitRegistrationRequest(clientId), "application/entity-statement+jwt");
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
@@ -161,7 +181,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         final String clientId = uniqueClientId();
         request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
         rpConfigureMockHttpClient(clientId);
-        setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+        setRequest("POST", explicitRegistrationRequest(clientId), "application/entity-statement+jwt");
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
@@ -173,7 +193,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         final String clientId = uniqueClientId();
         request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
         request.addHeader(USE_CUSTOM_FAILBACK_TO_LOCAL_CONDITION, "true");
-        setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+        setRequest("POST", explicitRegistrationRequest(clientId), "application/entity-statement+jwt");
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
@@ -186,7 +206,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
         request.addHeader(USE_CUSTOM_FAILBACK_TO_LOCAL_CONDITION, "true");
         rpConfigureMockHttpClient(clientId);
-        setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+        setRequest("POST", explicitRegistrationRequest(clientId), "application/entity-statement+jwt");
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
@@ -203,7 +223,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
         final Scope scope = Scope.parse("openid profile email custom");
         metadata.setScope(scope);
-        setRequest("POST", rpEntityConfiguration(clientId, metadata), "application/entity-statement+jwt");
+        setRequest("POST", explicitRegistrationRequest(clientId, metadata), "application/entity-statement+jwt");
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
@@ -223,7 +243,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         final Scope scope = Scope.parse("openid profile email custom");
         metadata.setScope(scope);
         rpResolveEntityConfigureMockHttpClient(clientId, metadata);
-        setRequest("POST", rpEntityConfiguration(clientId, metadata), "application/entity-statement+jwt");
+        setRequest("POST", explicitRegistrationRequest(clientId, metadata), "application/entity-statement+jwt");
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
@@ -238,7 +258,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         final String clientId = uniqueClientId();
         for (int i = 0; i < 2; i++) {
             rpConfigureMockHttpClient(clientId);
-            setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+            setRequest("POST", explicitRegistrationRequest(clientId), "application/entity-statement+jwt");
             final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
             verify(federationHttpClient, times(0)).executeOpen(any(),
                     argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
@@ -253,7 +273,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         for (int i = 0; i < 2; i++) {
             request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
             rpResolveEntityConfigureMockHttpClient(clientId);
-            setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+            setRequest("POST", explicitRegistrationRequest(clientId), "application/entity-statement+jwt");
             final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
             verify(federationHttpClient, times(0)).executeOpen(any(),
                     argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
@@ -554,4 +574,46 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
                 externalContext);
         Assert.assertEquals(result.getOutcome().getId(), END_STATE_ID);
     }
+
+    protected String explicitRegistrationRequest(final String clientId, final String... authorityHints)
+            throws URISyntaxException {
+        final OIDCClientMetadata metadata = new OIDCClientMetadata();
+        metadata.setRedirectionURI(new URI(redirectUri));
+        metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+        return explicitRegistrationRequest(clientId, leafKey, null, List.of(issuer), metadata, authorityHints);
+    }
+
+    protected String explicitRegistrationRequest(final String clientId, final OIDCClientMetadata metadata,
+            final String... authorityHints) throws URISyntaxException {
+        return explicitRegistrationRequest(clientId, leafKey, null, List.of(issuer), metadata, authorityHints);
+    }
+
+    protected String explicitRegistrationRequest(final String clientId, final OIDCClientMetadata metadata,
+            final List<String> critical, final JWK leafKey, final String... authorityHints)
+                    throws URISyntaxException {
+        return explicitRegistrationRequest(clientId, leafKey, critical, List.of(issuer), metadata, authorityHints);
+    }
+
+    protected String explicitRegistrationRequest(final String clientId, final JWK leafKey,
+            final List<String> critical, final List<String> audience, final OIDCClientMetadata metadata,
+            final String... authorityHints) throws URISyntaxException {
+        final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(clientId).subject(clientId)
+                .issueTime(Date.from(Instant.now()))
+                .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+                .audience(audience)
+                .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+                .claim("metadata", Map.of("openid_relying_party", metadata.toJSONObject()))
+                .claim("authority_hints", authorityHints == null || authorityHints.length == 0 ?
+                        new String[] { anchorId } : authorityHints);
+        if (audience != null) {
+            builder.audience(audience);
+        }
+        if (critical != null) {
+            builder.claim("crit", critical);
+        }
+        final EntityStatement<?> rpConfiguration =
+                TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, leafKey, builder.build());
+        return rpConfiguration.getJwt().serialize();
+    }
+
 }
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/TokenFlowAutomaticRegistrationTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/TokenFlowAutomaticRegistrationTest.java
index 4cbd501..ead63e7 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/TokenFlowAutomaticRegistrationTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/TokenFlowAutomaticRegistrationTest.java
@@ -30,6 +30,7 @@ import org.testng.annotations.Test;
 
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.OAuth2Error;
 import com.nimbusds.oauth2.sdk.Scope;
@@ -71,8 +72,10 @@ public class TokenFlowAutomaticRegistrationTest extends AbstractFederationFlowTe
         Assert.assertNotNull(response.getTokens().getAccessToken());
         final AccessTokenClaimsSet tokenClaims =
                 AccessTokenClaimsSet.parse(response.getTokens().getAccessToken().getValue(), getDataSealer());
-        Assert.assertEquals(tokenClaims.getClaimsSet().getStringListClaim(ClaimsSetExtensionSupport
-                .KEY_AUTO_REGISTERED_TRUST_CHAIN), List.of(clientId, anchorId));
+        final JWTClaimsSet claimsSet = tokenClaims.getClaimsSet();
+        assert claimsSet != null;
+        Assert.assertEquals(claimsSet.getStringListClaim(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN),
+                List.of(clientId, anchorId));
         Assert.assertNotNull(response.getOIDCTokens().getIDToken());
         Assert.assertNotNull(response.getOIDCTokens().getIDToken().getJWTClaimsSet().getClaim("at_hash"));
     }
@@ -95,8 +98,10 @@ public class TokenFlowAutomaticRegistrationTest extends AbstractFederationFlowTe
         Assert.assertNotNull(response.getTokens().getAccessToken());
         final AccessTokenClaimsSet tokenClaims =
                 AccessTokenClaimsSet.parse(response.getTokens().getAccessToken().getValue(), getDataSealer());
-        Assert.assertEquals(tokenClaims.getClaimsSet().getStringListClaim(ClaimsSetExtensionSupport
-                .KEY_AUTO_REGISTERED_TRUST_CHAIN), List.of(clientId, anchorId));
+        final JWTClaimsSet claimsSet = tokenClaims.getClaimsSet();
+        assert claimsSet != null;
+        Assert.assertEquals(claimsSet.getStringListClaim(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN),
+                List.of(clientId, anchorId));
         Assert.assertNotNull(response.getOIDCTokens().getIDToken());
         Assert.assertNotNull(response.getOIDCTokens().getIDToken().getJWTClaimsSet().getClaim("at_hash"));
     }
@@ -155,8 +160,10 @@ public class TokenFlowAutomaticRegistrationTest extends AbstractFederationFlowTe
         Assert.assertNotNull(response.getTokens().getAccessToken());
         final AccessTokenClaimsSet tokenClaims =
                 AccessTokenClaimsSet.parse(response.getTokens().getAccessToken().getValue(), getDataSealer());
-        Assert.assertEquals(tokenClaims.getClaimsSet().getStringListClaim(ClaimsSetExtensionSupport
-                .KEY_AUTO_REGISTERED_TRUST_CHAIN), List.of(clientId, anchorId));
+        final JWTClaimsSet claimsSet = tokenClaims.getClaimsSet();
+        assert claimsSet != null;
+        Assert.assertEquals(claimsSet.getStringListClaim(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN),
+                List.of(clientId, anchorId));
         Assert.assertNotNull(response.getOIDCTokens().getIDToken());
         Assert.assertNotNull(response.getOIDCTokens().getIDToken().getJWTClaimsSet().getClaim("at_hash"));
     }

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


More information about the commits mailing list