[java-idp-oidc] 04/31: JOIDC-222 - Support for OpenID Federation

Henri Mikkonen henri.mikkonen at iki.fi
Tue Jun 24 08:52:40 UTC 2025


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

hjmikkon pushed a commit to branch dev/JOIDC-222
in repository java-idp-oidc.

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

commit bee9bb40e6639cf2f4599b6c97ec4e9c6dada79c
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Wed Jan 15 17:44:35 2025 +0200

    JOIDC-222 - Support for OpenID Federation
    
    https://shibboleth.atlassian.net/browse/JOIDC-222
    
    Current state of work (WIP), containing initial implementations for the following new features:
    - OIDFED.AutomaticRegistration profile
      - Provides means to do trust anchor specific configurations (trust marks, local policy, etc)
      - Relying party context is stored as a child under  RelyingPartyTrustChainContext
      - Trust marks are resolved after the trust chain has been selected
        - Trust mark validation exploits trust chain cache + trust engine
        - TODO: trust mark subject validation and trust mark delegation
    - RelyingPartyByTrustAnchor bean for relying party overrides
      - provides trust anchor specific overrides via 'c:trustAnchorIds="TRUST_ANCHOR_ID"' attribute
    - Metadata (client information) resolution now exploits immediate superior as mandated by spec draft
---
 ...derationAutomaticRegistrationConfiguration.java | 172 ++++++++++
 ...derationAutomaticRegistrationConfiguration.java |  82 +++++
 .../config/RelyingPartyConfigurationSupport.java   |  59 ++++
 .../oidfed/config/TrustAnchorIdLookupFunction.java |  74 +++++
 .../op/oidfed/config/TrustAnchorIdPredicate.java   |  96 ++++++
 ...ClientMetadataFromTrustChainLookupStrategy.java |  73 +++++
 ...DefaultTrustChainTrustMarksParsingStrategy.java | 104 ++++++
 ...ChainTrustedTrustMarkIssuersLookupStrategy.java | 114 +++++++
 .../impl/RelyingPartyTrustChainContext.java        |  29 +-
 .../op/oidfed/profile/impl/ResolveTrustChains.java |  23 +-
 .../op/oidfed/profile/impl/ResolveTrustMarks.java  | 360 +++++++++++++++++++++
 .../op/oidfed/profile/impl/SelectTrustChain.java   |  40 ++-
 ...eAutomaticRegistrationProfileConfiguration.java | 229 +++++++++++++
 .../DefaultLocalMetadataPolicyMergingStrategy.java | 112 +++++++
 .../LocalMetadataPolicyLookupFunction.java         |  49 +++
 .../MandatoryTrustMarksLookupFunction.java         |  48 +++
 .../MaximumTrustMarkLifetimeLookupFunction.java    |  52 +++
 .../idp/flows/oidc/authorize/authorize-beans.xml   |  46 +++
 .../idp/flows/oidc/authorize/authorize-flow.xml    |   4 +
 .../idp/service/relying-party/postconfig.xml       |   8 +
 20 files changed, 1756 insertions(+), 18 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/DefaultOIDFederationAutomaticRegistrationConfiguration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/DefaultOIDFederationAutomaticRegistrationConfiguration.java
new file mode 100644
index 00000000..42c52a7b
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/DefaultOIDFederationAutomaticRegistrationConfiguration.java
@@ -0,0 +1,172 @@
+/*
+ * 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.config;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.profile.config.AbstractConditionalProfileConfiguration;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Positive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+
+/**
+ * Implementation of a profile configuration for the OpenID Federation Automatic Registration.
+ */
+public class DefaultOIDFederationAutomaticRegistrationConfiguration extends AbstractConditionalProfileConfiguration
+        implements OIDFederationAutomaticRegistrationConfiguration {
+
+    /** OIDC provider information profile counter name. */
+    @Nonnull @NotEmpty
+    public static final String PROFILE_COUNTER = "net.shibboleth.idp.profiles.oidfed.automaticregistration";
+
+    /** Lookup function to local metadata policy to be merged into the federation policy. */
+    @Nonnull private Function<ProfileRequestContext,Map<String, MetadataPolicy>> localMetadataPolicyLookupStrategy;
+
+    /** Lookup function to mandatory trust marks. */
+    @Nonnull private Function<ProfileRequestContext,List<String>> mandatoryTrustMarksLookupStrategy;
+
+    /** Lookup function to supply maximum trust mark lifetime. */
+    @Nonnull private Function<ProfileRequestContext,Duration> maximumTrustMarkLifetimeLookupStrategy;
+
+    /**
+     * Constructor.
+     */
+    public DefaultOIDFederationAutomaticRegistrationConfiguration() {
+        this(PROFILE_ID);
+    }
+
+    /**
+     * Creates a new configuration instance.
+     *
+     * @param profileId Unique profile identifier.
+     */
+    public DefaultOIDFederationAutomaticRegistrationConfiguration(@Nonnull @NotEmpty final String profileId) {
+        super(profileId);
+        localMetadataPolicyLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyMap());
+        mandatoryTrustMarksLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+        maximumTrustMarkLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofDays(365));
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nonnull @NonnullElements @NotLive @Unmodifiable
+    public Map<String, MetadataPolicy> getLocalMetadataPolicy(
+            @Nullable final ProfileRequestContext profileRequestContext) {
+        final Map<String, MetadataPolicy> policy = localMetadataPolicyLookupStrategy.apply(profileRequestContext);
+        if (policy != null) {
+            return CollectionSupport.copyToMap(policy);
+        }
+        return CollectionSupport.emptyMap();
+    }
+
+    /**
+     * Set local metadata policy to be merged into the federation policy.
+     * 
+     * @param policy metadata policy
+     */
+    public void setLocalMetadataPolicy(
+            @Nonnull @NonnullElements @NotLive @Unmodifiable final Map<String, MetadataPolicy> policy) {
+        localMetadataPolicyLookupStrategy = FunctionSupport.constant(policy);
+    }
+
+    /**
+     * Sets lookup strategy for local metadata policy to be merged into the federation policy.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setLocalMetadataPolicyLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,Map<String, MetadataPolicy>> strategy) {
+        localMetadataPolicyLookupStrategy =
+                Constraint.isNotNull(strategy, "Local metadata policy lookup strategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nonnull @NonnullElements @NotLive @Unmodifiable
+    public List<String> getMandatoryTrustMarks(@Nullable final ProfileRequestContext profileRequestContext) {
+        final List<String> trustMarks = mandatoryTrustMarksLookupStrategy.apply(profileRequestContext);
+        if (trustMarks != null) {
+            return CollectionSupport.copyToList(trustMarks);
+        }
+        return CollectionSupport.emptyList();
+    }
+
+    /**
+     * Set mandatory trust marks.
+     * 
+     * @param marks trust marks
+     */
+    public void setMandatoryTrustMarks(@Nonnull @NonnullElements @NotLive @Unmodifiable final List<String> marks) {
+        mandatoryTrustMarksLookupStrategy = FunctionSupport.constant(marks);
+    }
+
+    /**
+     * Sets lookup strategy for mandatory trust marks value.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setMandatoryTrustMarksLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,List<String>> strategy) {
+        mandatoryTrustMarksLookupStrategy =
+                Constraint.isNotNull(strategy, "Mandatory trust marks lookup strategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Positive @Nonnull
+    public Duration getMaximumTrustMarkLifetime(@Nullable final ProfileRequestContext profileRequestContext) {
+        final Duration lifetime = maximumTrustMarkLifetimeLookupStrategy.apply(profileRequestContext);
+
+        Constraint.isTrue(lifetime != null && !lifetime.isZero() && !lifetime.isNegative(),
+                "Maximum trust mark lifetime must be greater than 0");
+        assert lifetime != null;
+        return lifetime;
+    }
+
+    /**
+     * Set the maximum lifetime of a trust mark.
+     * 
+     * @param lifetime lifetime of a trust mark
+     */
+    public void setMaximumTrustMarkLifetime(@Positive @Nonnull final Duration lifetime) {
+        final Duration trustMarkLifetime = Constraint.isNotNull(lifetime, "Maximum trust mark lifetime cannot be null");
+        Constraint.isTrue(!trustMarkLifetime.isZero() && !trustMarkLifetime.isNegative(),
+                "Maximum trust mark lifetime must be greater than 0");
+
+        maximumTrustMarkLifetimeLookupStrategy = FunctionSupport.constant(trustMarkLifetime);
+    }
+
+    /**
+     * Set a lookup strategy for the maximum trust mark lifetime.
+     *
+     * @param strategy lookup strategy
+     */
+    public void setMaximumTrustMarkLifetimeLookupStrategy(
+            @Nullable final Function<ProfileRequestContext,Duration> strategy) {
+        maximumTrustMarkLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+    }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationAutomaticRegistrationConfiguration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationAutomaticRegistrationConfiguration.java
new file mode 100644
index 00000000..d396684b
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationAutomaticRegistrationConfiguration.java
@@ -0,0 +1,82 @@
+/*
+ * 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.config;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidc.profile.config.OIDCProfileConfiguration;
+import net.shibboleth.shared.annotation.ConfigurationSetting;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Positive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+
+/** 
+ * Profile configuration for an OpenID Federation Automatic Registration.
+ */
+public interface OIDFederationAutomaticRegistrationConfiguration extends OIDCProfileConfiguration {
+    
+    /** OIDC base protocol URI. Sections 4 and 11 are the most relevant. */
+    public static final String PROTOCOL_URI = "https://openid.net/specs/openid-federation-1_0.html";
+
+    /** ID for this profile configuration. */
+    public static final String PROFILE_ID = "http://shibboleth.net/ns/profiles/oidfed/automaticregistration";
+
+    /**
+     * Get local metadata policy to be merged into the federation policy.
+     * 
+     * <p>Defaults to empty map.</p>
+     * 
+     * @param profileRequestContext profile request context
+     * 
+     * @return local metadata policy
+     */
+    @ConfigurationSetting(name="localMetadataPolicy")
+    @Nonnull @NonnullElements @NotLive @Unmodifiable
+    Map<String, MetadataPolicy> getLocalMetadataPolicy(@Nullable final ProfileRequestContext profileRequestContext);
+
+    /**
+     * Get the mandatory trust mark identifiers required by this profile configuration.
+     * 
+     * @param profileRequestContext profile request context
+     * 
+     * @return mandatory trust mark identifiers
+     */
+    @ConfigurationSetting(name="mandatoryTrustMarks")
+    @Nonnull @NonnullElements @NotLive @Unmodifiable List<String> getMandatoryTrustMarks(
+            @Nullable final ProfileRequestContext profileRequestContext);
+
+    /**
+     * Get maximum lifetime for trust marks.
+     * 
+     * <p>Defaults to one year.</p>
+     * 
+     * @param profileRequestContext profile request context
+     * 
+     * @return maximum lifetime
+     */
+    @ConfigurationSetting(name="maximumTrustMarkLifetime")
+    @Positive @Nonnull
+    Duration getMaximumTrustMarkLifetime(@Nullable final ProfileRequestContext profileRequestContext);
+
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/RelyingPartyConfigurationSupport.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/RelyingPartyConfigurationSupport.java
new file mode 100644
index 00000000..2655d226
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/RelyingPartyConfigurationSupport.java
@@ -0,0 +1,59 @@
+/*
+ * 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.config;
+
+import java.util.Collection;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.profile.relyingparty.BasicRelyingPartyConfiguration;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Support functions for building {@link RelyingPartyConfiguration} objects with activation conditions.
+ */
+public class RelyingPartyConfigurationSupport {
+
+    /**
+     * A shorthand method for constructing a {@link BasicRelyingPartyConfiguration} with an activation condition
+     * based on one or more trust anchor IDs.
+     * 
+     * <p>If a single ID is supplied, then the ID is also set as the identifier for the configuration.</p>
+     * 
+     * @param trustAnchorIds the trust anchors for which the configuration should be active
+     * 
+     * @return  a default-constructed configuration with the appropriate condition set
+     */
+    @Nonnull
+    public static BasicRelyingPartyConfiguration byTrustAnchor(@Nonnull final Collection<String> trustAnchorIds) {
+
+        Constraint.isNotNull(trustAnchorIds, "Trust Anchor ID list cannot be null");
+
+        final BasicRelyingPartyConfiguration config = new BasicRelyingPartyConfiguration(); 
+        config.setActivationCondition(new TrustAnchorIdPredicate(trustAnchorIds));
+        
+        final StringBuffer name = new StringBuffer("TrustAnchorIDs[");
+        for (final String taId: trustAnchorIds) {
+            name.append(taId).append(',');
+            
+        }
+        name.append(']');
+        final String id = name.toString();
+        assert id != null;
+        config.setId(id);
+        return config;
+    }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/TrustAnchorIdLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/TrustAnchorIdLookupFunction.java
new file mode 100644
index 00000000..d6ebfdb2
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/TrustAnchorIdLookupFunction.java
@@ -0,0 +1,74 @@
+/*
+ * 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.config;
+
+import java.util.Optional;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A function that returns selected trust anchor ID from a {@link RelyingPartyTrustChainContext} obtained via a lookup
+ * function.
+ * 
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class TrustAnchorIdLookupFunction implements Function<ProfileRequestContext, String> {
+
+    /** Strategy used to lookup the trust chain context. */
+    @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+
+    /**
+     * Constructor.
+     */
+    public TrustAnchorIdLookupFunction() {
+        final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
+                new ChildContextLookup<>(RelyingPartyTrustChainContext.class).compose(
+                        new InboundMessageContextLookup());
+        assert tcls != null;
+        trustChainContextLookupStrategy = tcls;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param strategy strategy used to lookup the trust chain context
+     */
+    public TrustAnchorIdLookupFunction(
+            @Nonnull final Function<ProfileRequestContext, RelyingPartyTrustChainContext> strategy) {
+        trustChainContextLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Nullable public String apply(@Nullable final ProfileRequestContext profileRequestContext) {
+        return Optional.ofNullable(profileRequestContext)
+                .map(profileCtx -> trustChainContextLookupStrategy.apply(profileCtx))
+                .map(trustChainCtx -> trustChainCtx.getSelectedTrustChain())
+                .filter(pair -> pair != null && pair.getFirst() != null)
+                .map(pair -> pair.getFirst())
+                .filter(list -> !list.isEmpty())
+                .map(chain -> chain.get(chain.size() - 1))
+                .map(statement -> statement.getEntityID().getValue())
+                .orElse(null);
+    }
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/TrustAnchorIdPredicate.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/TrustAnchorIdPredicate.java
new file mode 100644
index 00000000..0b381ed3
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/TrustAnchorIdPredicate.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.config;
+
+import java.util.Collection;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.StrategyIndirectedPredicate;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * Predicate that evaluates a {@link ProfileRequestContext} by looking for a trust anchor ID that matches one of a
+ * designated set, or a generic predicate.
+ */
+public class TrustAnchorIdPredicate extends StrategyIndirectedPredicate<ProfileRequestContext,String> {
+
+    /**
+     * Constructor.
+     * 
+     * @param candidates hardwired set of values to check against
+     */
+    public TrustAnchorIdPredicate(@Nonnull @ParameterName(name="candidates") final Collection<String> candidates) {
+        super(new TrustAnchorIdLookupFunction(), StringSupport.normalizeStringCollection(candidates));
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param candidate a single value to check against
+     */
+    public TrustAnchorIdPredicate(@Nonnull @NotEmpty @ParameterName(name="candidate") final String candidate) {
+        this(CollectionSupport.singleton(candidate));
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param pred generalized predicate
+     */
+    public TrustAnchorIdPredicate(@Nonnull @ParameterName(name="pred") final Predicate<String> pred) {
+        super(new TrustAnchorIdLookupFunction(), pred);
+    }
+    
+    /**
+     * Workaround for Spring type conversion ambiguities.
+     * 
+     * @param candidates hardwired set of values to check against
+     * 
+     * @return the predicate
+     */
+    @Nonnull public static TrustAnchorIdPredicate fromCandidates(@Nonnull final Collection<String> candidates) {
+        return new TrustAnchorIdPredicate(candidates);
+    }
+    
+    /**
+     * Workaround for Spring type conversion ambiguities.
+     * 
+     * @param candidate a single value to check against
+     * 
+     * @return the predicate
+     */
+    @Nonnull public static TrustAnchorIdPredicate fromCandidate(@Nonnull @NotEmpty final String candidate) {
+        return new TrustAnchorIdPredicate(candidate);
+    }
+
+    /**
+     * Workaround for Spring type conversion ambiguities.
+     * 
+     * @param pred generalized predicate
+     * 
+     * @return the predicate
+     */
+    @Nonnull public static TrustAnchorIdPredicate fromPredicate(@Nonnull final Predicate<String> pred) {
+        return new TrustAnchorIdPredicate(pred);
+    }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultClientMetadataFromTrustChainLookupStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultClientMetadataFromTrustChainLookupStrategy.java
new file mode 100644
index 00000000..55c4cef6
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultClientMetadataFromTrustChainLookupStrategy.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.minidev.json.JSONObject;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Default strategy to combine {@link OIDCClientMetadata} from the trust chain by exploiting both entity configuration
+ * and subordinate statement issued by the immediate superior.
+ */
+public class DefaultClientMetadataFromTrustChainLookupStrategy
+    implements Function<List<EntityStatement>,OIDCClientMetadata> {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(DefaultClientMetadataFromTrustChainLookupStrategy.class);
+
+    /** {@inheritDoc} */
+    @Override @Nullable
+    public OIDCClientMetadata apply(@Nullable final List<EntityStatement> chain) {
+        if (chain == null || chain.size() < 3) {
+            log.warn("Unexpected trust chain input: {}", chain == null ? null : "size = " + chain.size());
+            return null;
+        }
+        final OIDCClientMetadata configurationMetadata = chain.get(0).getClaimsSet().getRPMetadata();
+        final OIDCClientMetadata subordinateMetadata = chain.get(1).getClaimsSet().getRPMetadata();
+        if (subordinateMetadata == null || subordinateMetadata.toJSONObject().isEmpty()) {
+            return configurationMetadata;
+        }
+        if (configurationMetadata == null || configurationMetadata.toJSONObject().isEmpty()) {
+            return subordinateMetadata;
+        }
+        final Map<String, Object> configurationClaims = configurationMetadata.toJSONObject();
+        final Map<String, Object> subordinateClaims = subordinateMetadata.toJSONObject();
+        for (final String configurationClaim : configurationClaims.keySet()) {
+            if (!subordinateClaims.containsKey(configurationClaim)) {
+                subordinateClaims.put(configurationClaim, configurationClaims.get(configurationClaim));
+            }
+        }
+        try {
+            return OIDCClientMetadata.parse(new JSONObject(subordinateClaims));
+        } catch (final ParseException e) {
+            log.error("Could not parse from combined metadata map into a metadata object", e);
+        }
+        return null;
+    }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustChainTrustMarksParsingStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustChainTrustMarksParsingStrategy.java
new file mode 100644
index 00000000..f0add6d7
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustChainTrustMarksParsingStrategy.java
@@ -0,0 +1,104 @@
+/*
+ * 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;
+
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.id.Identifier;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * Default strategy for parsing map of trust marks for the given trust chain. The keys in the map refer to the entity
+ * ID for which the trust mark has been issued to.
+ * 
+ * TODO: iat / subject validation (switch into using claims validators)
+ */
+ at ThreadSafe
+public class DefaultTrustChainTrustMarksParsingStrategy
+        implements Function<List<EntityStatement>,Map<String,List<SignedJWT>>> {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(DefaultTrustChainTrustMarksParsingStrategy.class);
+
+    /** {@inheritDoc} */
+    @Override @Nullable
+    public Map<String, List<SignedJWT>> apply(@Nullable final List<EntityStatement> trustChain) {
+        if (trustChain == null || trustChain.size() < 3) {
+            log.error("Unexpected length in the trust chain: {}", trustChain == null ? "null" : trustChain.size());
+            return null;
+        }
+        
+        final Map<String, List<SignedJWT>> result = new HashMap<>();
+        for (final EntityStatement statement : trustChain) {
+            assert statement != null;
+            final SignedJWT statementJwt = statement.getSignedStatement();
+            log.trace("Inspecting entity statement {} with trust marks {}",
+                    statementJwt.serialize(), statement.getClaimsSet().getTrustMarks());
+
+            if (statement.getClaimsSet().getTrustMarks() != null) {
+                final List<SignedJWT> trustMarks = statement.getClaimsSet().getTrustMarks()
+                    .stream()
+                    .filter(entry -> verifyTrustMark(entry.getTrustMark(), entry.getID()))
+                    .map(entry -> entry.getTrustMark())
+                    .toList();
+                result.put(statement.getEntityID().getValue(), trustMarks);
+            }
+        }
+        
+        return result;
+    }
+
+    /**
+     * Verifies the trust mark id and issuer claims.
+     * 
+     * @param trustMark trust mark to be verified
+     * @param id the id to be verified from the JWT claims set
+     * @return true if valid, false otherwise
+     */
+    private boolean verifyTrustMark(@Nullable final SignedJWT trustMark, @Nullable final Identifier id) {
+        if (trustMark == null || id == null) {
+            return false;
+        }
+        try {
+            final JWTClaimsSet trustMarkClaims = trustMark.getJWTClaimsSet();
+            if (StringSupport.trimOrNull(trustMarkClaims.getIssuer()) == null) {
+                log.error("Trust Mark {} is missing mandatory issuer", trustMarkClaims.getStringClaim("id"));
+                return false;
+            }
+            if (id.getValue().equals(trustMarkClaims.getStringClaim("id"))) {
+                return true;
+            }
+            log.error("The id {} is not matching with the id-claim {}", id, trustMarkClaims.getStringClaim("id"));
+        } catch (final ParseException e) {
+            log.error("Could not parse id-claim from the trust mark", e);
+        }
+        return false;
+    }
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy.java
new file mode 100644
index 00000000..64d15be7
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy.java
@@ -0,0 +1,114 @@
+/*
+ * 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;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.type.ArrayType;
+import com.fasterxml.jackson.databind.type.MapType;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.collection.CollectionSupport;
+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 function for fetching trusted trust mark issuers from a trust chain: they are read from the trust anchor's
+ * entity configuration.
+ */
+public class DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy extends AbstractIdentifiableInitializableComponent
+    implements Function<List<EntityStatement>, Map<String, List<String>>> {
+
+    /** Class logger. */
+    @Nonnull private final Logger log =
+            LoggerFactory.getLogger(DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy.class);
+
+    /** JSON object mapper used for decoding JSON into Map. */
+    @NonnullAfterInit private ObjectMapper objectMapper;
+
+    /**
+     * Set the JSON {@link ObjectMapper} used for decoding JSON into Map.
+     * 
+     * @param mapper object mapper
+     */
+    public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
+        checkSetterPreconditions();
+
+        objectMapper = Constraint.isNotNull(mapper, "Object mapper cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        if (objectMapper == null) {
+            throw new ComponentInitializationException("Object mapper cannot be null");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Nullable @Override
+    public Map<String, List<String>> apply(@Nullable final List<EntityStatement> trustChain) {
+        checkComponentActive();
+        if (trustChain == null || trustChain.size() < 3) {
+            return null;
+        }
+        return parseTrustedIssuers(trustChain)
+                .entrySet().stream()
+                .filter(entry -> entry.getKey() != null && entry.getValue() != null)
+                .map(entry -> Map.entry(entry.getKey(), Arrays.stream(entry.getValue()).toList()))
+                .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
+    }
+
+    /**
+     * Parse the map of trusted issuers from the given trust chain, keyed with trust mark identifiers.
+     * 
+     * @param trustChain trust chain to be parsed
+     * @return map of trusted issuers
+     */
+    @Nonnull protected Map<String, String[]> parseTrustedIssuers(@Nonnull final List<EntityStatement> trustChain) {
+        final Object issuersClaim =
+                trustChain.get(trustChain.size() - 1).getClaimsSet().getClaim("trust_mark_issuers");
+        if (issuersClaim != null) {
+            final ArrayType arrayType = objectMapper.getTypeFactory().constructArrayType(String.class);
+            final JavaType stringType = objectMapper.constructType(String.class);
+            final MapType mapType =
+                    objectMapper.getTypeFactory().constructMapType(Map.class, stringType, arrayType);
+            try {
+                final Map<String, String[]> result = objectMapper.readValue(issuersClaim.toString(), mapType);
+                if (result != null) {
+                    return result;
+                }
+            } catch (final JsonProcessingException e) {
+                log.warn("Could not parse trust mark issuers from the trust chain", e);
+            }
+        }
+        return CollectionSupport.emptyMap();
+    }
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyTrustChainContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyTrustChainContext.java
index 8948c452..94442062 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyTrustChainContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyTrustChainContext.java
@@ -16,6 +16,7 @@ package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
 
 import java.time.Instant;
 import java.util.List;
+import java.util.Map;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -46,6 +47,9 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
     /** Expiration instant for the selected metadata. */
     @Nullable private Instant selectedMetadataExpiration;
 
+    /** Verified trust mark IDs for the selected trust chain. */
+    @Nullable private Map<String, List<String>> verifiedTrustMarkIds;
+
     /**
      * Get the resolved trust chains for the relying party.
      * 
@@ -113,7 +117,7 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
     }
 
     /**
-     * Get the expiration instant for the selected metadata
+     * Get the expiration instant for the selected metadata.
      * 
      * @return the expiration instant
      */
@@ -132,4 +136,27 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
         selectedMetadataExpiration = expiration;
         return this;
     }
+
+    /**
+     * Get the verified trust mark IDs for the selected trust chain.
+     * 
+     * @return verified trust mark IDs
+     */
+    @Nullable public Map<String, List<String>> getVerifiedTrustMarkIds() {
+        return verifiedTrustMarkIds;
+    }
+
+    /**
+     * Set the verified trust mark IDs for the selected trust chain.
+     * 
+     * @param ids verified trust mark IDs
+     * 
+     * @return this context
+     */
+    @Nonnull public RelyingPartyTrustChainContext setVerifiedTrustMarkIds(
+            @Nullable final Map<String, List<String>> ids) {
+        verifiedTrustMarkIds = ids;
+        return this;
+    }
+    
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustChains.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustChains.java
index d01db3d9..e55c5f81 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustChains.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustChains.java
@@ -38,6 +38,7 @@ import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
 import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
 
 import net.minidev.json.JSONObject;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultClientMetadataFromTrustChainLookupStrategy;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SubjectEntityIDCriterion;
 import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.oidc.metadata.cache.MetadataCache;
@@ -78,6 +79,9 @@ public class ResolveTrustChains extends AbstractProfileAction {
     /** Strategy used to create the trust chain context. */
     @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextCreationStrategy;
 
+    /** Strategy used to get combined OIDC client metadata from trust chain. */
+    @Nonnull private Function<List<EntityStatement>,OIDCClientMetadata> metadataLookupStrategy;
+
     /** Strategy used to merge metadata policies in trust chain for specific entity type. */
     @NonnullAfterInit private BiFunction<List<EntityStatement>,String,Map<String, MetadataPolicy>>
         metadataPolicyMergingStrategy;
@@ -97,6 +101,7 @@ public class ResolveTrustChains extends AbstractProfileAction {
                         new InboundMessageContextLookup());
         assert tccs != null;
         trustChainContextCreationStrategy = tccs;
+        metadataLookupStrategy = new DefaultClientMetadataFromTrustChainLookupStrategy();
     }
 
     /**
@@ -117,7 +122,18 @@ public class ResolveTrustChains extends AbstractProfileAction {
     public void setClientIDLookupStrategy(@Nonnull final Function<MessageContext, ClientID> strategy) {
         checkSetterPreconditions();
         clientIDLookupStrategy =
-                Constraint.isNotNull(strategy, "ClientIDLookupStrategy lookup strategy cannot be null");
+                Constraint.isNotNull(strategy, "ClientIDLookupStrategy cannot be null");
+    }
+
+    /**
+     * Set the strategy used to get combined OIDC client metadata from trust chain.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setMetadataLookupStrategy(@Nonnull final Function<List<EntityStatement>,OIDCClientMetadata> strategy) {
+        checkSetterPreconditions();
+        metadataLookupStrategy =
+                Constraint.isNotNull(strategy, "MetadataLookupStrategy cannot be null");
     }
 
     /**
@@ -129,7 +145,7 @@ public class ResolveTrustChains extends AbstractProfileAction {
             BiFunction<List<EntityStatement>,String,Map<String, MetadataPolicy>> strategy) {
         checkSetterPreconditions();
         metadataPolicyMergingStrategy =
-                Constraint.isNotNull(strategy, "MetadataPolicyMergingStrategy lookup strategy cannot be null");
+                Constraint.isNotNull(strategy, "MetadataPolicyMergingStrategy cannot be null");
     }
 
     /**
@@ -208,7 +224,8 @@ public class ResolveTrustChains extends AbstractProfileAction {
             final Map<String, MetadataPolicy> mergedPolicies =
                     metadataPolicyMergingStrategy.apply(chain, EntityType.OPENID_RELYING_PARTY.getValue());
             log.debug("{} Merged policy for chain {}", getLogPrefix(), mergedPolicies);
-            final OIDCClientMetadata metadata = chain.get(0).getClaimsSet().getRPMetadata();
+            assert chain != null;
+            final OIDCClientMetadata metadata = metadataLookupStrategy.apply(chain);
             if (metadata != null) {
                 final OIDCClientInformation clientInformation = new OIDCClientInformation(
                         new ClientID(chain.get(0).getEntityID().getValue()), metadata);
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
new file mode 100644
index 00000000..ac8b9712
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
@@ -0,0 +1,360 @@
+/*
+ * 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.impl;
+
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.opensaml.security.SecurityException;
+import org.opensaml.security.trust.TrustEngine;
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustChainTrustMarksParsingStrategy;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SubjectEntityIDCriterion;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SubjectEntityStatementCriterion;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+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.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.PredicateSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.NonnullSupplier;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Resolves the trust marks for the selected trust chain and stores the data into {@link RelyingPartyTrustChainContext}.
+ * 
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ * 
+ * @since 4.3.0
+ */
+public class ResolveTrustMarks extends AbstractProfileAction {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(ResolveTrustMarks.class);
+
+    /** Strategy used to lookup the trust chain context. */
+    @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+
+    /** Strategy used to parse trust marks from the selected trust chain. */
+    @Nonnull private Function<List<EntityStatement>,Map<String,List<SignedJWT>>> trustChainTrustMarksParsingStrategy;
+
+    /** Strategy used to lookup trusted trust mark issuers for the trust chain. */
+    @NonnullAfterInit
+    private Function<List<EntityStatement>, Map<String, List<String>>> trustedTrustMarkIssuersLookupStrategy;
+
+    /** Condition to solely take trusted trust mark issuers into account. */
+    @Nonnull private Predicate<ProfileRequestContext> trustedTrustMarkIssuersOnlyCondition;
+
+    /** Metadata cache for trust chains (for trust mark issuers). */
+    @NonnullAfterInit private MetadataCache<List<List<EntityStatement>>> trustChainCache;
+
+    /** Trust engine used to validate a trust mark signature. */
+    @NonnullAfterInit private TrustEngine<SignedJWT> trustEngine;
+
+    /** Trust chain context to operate on. */
+    @NonnullBeforeExec private RelyingPartyTrustChainContext trustChainContext;
+
+    /** The selected trust chain to resolve trust marks from. */
+    @NonnullBeforeExec private List<EntityStatement> selectedTrustChain;
+
+    /** Flag indicating that only trusted trust mark issuers are taken into account. */
+    private boolean onlyTrustedIssuers;
+
+    /** Map of trusted trust mark issuers. */
+    @NonnullBeforeExec private Map<String, List<String>> trustedIssuers;
+
+    /**
+     * Constructor.
+     */
+    public ResolveTrustMarks() {
+        final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
+                new ChildContextLookup<>(RelyingPartyTrustChainContext.class).compose(
+                        new InboundMessageContextLookup());
+        assert tcls != null;
+        trustChainContextLookupStrategy = tcls;
+        trustChainTrustMarksParsingStrategy = new DefaultTrustChainTrustMarksParsingStrategy();
+        trustedTrustMarkIssuersOnlyCondition = PredicateSupport.alwaysTrue();
+    }
+
+    /**
+     * Set the strategy used to lookup the trust chain context.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setTrustChainContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, RelyingPartyTrustChainContext> strategy) {
+        checkSetterPreconditions();
+        trustChainContextLookupStrategy =
+                Constraint.isNotNull(strategy, "TrustChainContextLookupStrategy cannot be null");
+    }
+
+    /**
+     * Set the strategy used to lookup trusted trust mark issuers for the trust chain.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setTrustedTrustMarkIssuersLookupStrategy(
+            @Nonnull final Function<List<EntityStatement>, Map<String, List<String>>> strategy) {
+        checkSetterPreconditions();
+        trustedTrustMarkIssuersLookupStrategy =
+                Constraint.isNotNull(strategy, "trustedTrustMarkIssuersLookupStrategy cannot be null");
+    }
+
+    /**
+     * Set the condition to solely take trusted trust mark issuers into account.
+     * 
+     * @param condition
+     */
+    public void setTrustedTrustMarkIssuersOnlyCondition(@Nonnull final Predicate<ProfileRequestContext> condition) {
+        checkSetterPreconditions();
+        trustedTrustMarkIssuersOnlyCondition =
+                Constraint.isNotNull(condition, "TrustedTrustMarkIssuersOnlyCondition cannot be null");
+    }
+
+    /**
+     * Set the metadata cache for trust chains.
+     * 
+     * @param cache metadata cache
+     */
+    public void setTrustChainCache(@Nonnull final MetadataCache<List<List<EntityStatement>>> cache) {
+        checkSetterPreconditions();
+        trustChainCache = Constraint.isNotNull(cache, "TrustChainCache cannot be null");
+    }
+
+    /**
+     * Set trust engine used to validate a signature.
+     * 
+     * @param engine trust engine
+     */
+    public void setTrustEngine(@Nonnull final TrustEngine<SignedJWT> engine) {
+        checkSetterPreconditions();
+        trustEngine = Constraint.isNotNull(engine, "Trust Engine cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (trustChainCache == null) {
+            throw new ComponentInitializationException("TrustChainCache cannot be null");
+        }
+        if (trustEngine == null) {
+            throw new ComponentInitializationException("Trust Engine cannot be null");
+        }
+        if (trustedTrustMarkIssuersLookupStrategy == null) {
+            throw new ComponentInitializationException("Trusted trust mark issuers lookup strategy cannot be null");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+
+        trustChainContext = trustChainContextLookupStrategy.apply(profileRequestContext);
+        if (trustChainContext == null || trustChainContext.getPolicyCompliantTrustChains() == null) {
+            log.error("{} Unable to locate policy-compliant trust chains", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+
+        final Pair<List<EntityStatement>, OIDCClientInformation> selectedChain =
+                trustChainContext.getSelectedTrustChain();
+
+        if (selectedChain == null || selectedChain.getFirst() == null) {
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            log.error("{} No selected trust chain could be resolved", getLogPrefix());
+            return false;
+        }
+
+        selectedTrustChain = selectedChain.getFirst();
+        assert selectedTrustChain != null;
+        if (selectedTrustChain.size() < 3) {
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            log.error("{} Unexpected length in the selected trust chain: {}", getLogPrefix(),
+                    selectedTrustChain.size());
+            return false;
+        }
+
+        onlyTrustedIssuers = trustedTrustMarkIssuersOnlyCondition.test(profileRequestContext);
+        trustedIssuers = Optional.ofNullable(trustedTrustMarkIssuersLookupStrategy.apply(selectedTrustChain))
+                .orElseGet(NonnullSupplier.of(CollectionSupport.emptyMap()));
+
+        if (trustedIssuers.isEmpty()) {
+            if (onlyTrustedIssuers) {
+                log.debug("{} No trusted issuers set in the selected trust anchor, trust marks won't be resolved",
+                        getLogPrefix());
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        log.debug("{} Trusted trust mark issuers {}", getLogPrefix(), trustedIssuers);
+        final Map<String, List<SignedJWT>> chainTrustMarks =
+                trustChainTrustMarksParsingStrategy.apply(selectedTrustChain);
+        if (chainTrustMarks == null || chainTrustMarks.isEmpty()) {
+            log.debug("{} No trust marks found from the selected trust chain", getLogPrefix());
+            return;
+        }
+
+        final Map<String, List<String>> verifiedTrustMarks = new HashMap<>();
+        for (final EntityStatement statement : selectedTrustChain) {
+            final List<SignedJWT> trustMarks = chainTrustMarks.get(statement.getEntityID().getValue());
+            if (trustMarks == null || trustMarks.isEmpty()) {
+                break;
+            }
+            verifiedTrustMarks.put(
+                    statement.getEntityID().getValue(),
+                    trustMarks.stream()
+                        .filter(entry -> checkTrustedIssuer(entry))
+                        .filter(entry -> verifyTrustMark(entry))
+                        .map(entry -> getTrustMarkId(entry))
+                        .filter(Objects::nonNull)
+                        .toList());
+        }
+        log.debug("{} The following trust marks are validated: {}", getLogPrefix(), verifiedTrustMarks);
+        trustChainContext.setVerifiedTrustMarkIds(verifiedTrustMarks);
+    }
+
+    /**
+     * Verifies the given trust mark meets configuration for trusted trust mark issuers.
+     * 
+     * @param jwt the trust mark to be verified
+     * @return true if the trust mark meets configuration, false otherwise
+     */
+    protected boolean checkTrustedIssuer(@Nullable final SignedJWT jwt) {
+        if (jwt == null) {
+            return false;
+        }
+        if (onlyTrustedIssuers) {
+            try {
+                final JWTClaimsSet claimsSet = jwt.getJWTClaimsSet();
+                final String id = StringSupport.trimOrNull(getTrustMarkId(jwt));
+                if (id == null) {
+                    return false;
+                }
+                if (trustedIssuers.containsKey(id)) {
+                    final String issuer = claimsSet.getIssuer();
+                    assert issuer != null;
+                    final List<String> validIssuers = trustedIssuers.get(id);
+                    if (onlyTrustedIssuers && (validIssuers == null || !validIssuers.contains(issuer)) ) {
+                        log.debug("{} Issuer {} is not valid trust mark issuer", getLogPrefix(), issuer);
+                        return false;
+                    }
+                }
+            } catch (final ParseException e) {
+                log.error("Could not parse TrustMark JWT contents", e);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Verifies the given trust mark by exploiting (1) the trust chain cache for fetching the trust chain for the issuer
+     * entity configuration and (2) the trust engine for validating the trust mark signature.
+     * 
+     * @param jwt the trust mark to be verified
+     * @return true if trust mark verification was successful, false otherwise
+     */
+    protected boolean verifyTrustMark(@Nullable final SignedJWT jwt) {
+        if (jwt == null) {
+            return false;
+        }
+        final JWTClaimsSet trustMarkClaims;
+        try {
+            trustMarkClaims = jwt.getJWTClaimsSet();
+        } catch (final ParseException e) {
+            log.error("{} Could not parse the TrustMark JWT contents", getLogPrefix(), e);
+            return false;
+        }
+        final String issuer = trustMarkClaims.getIssuer();
+        assert issuer != null;
+        log.debug("Resolving trust chain for {}", issuer);
+        final List<List<List<EntityStatement>>> cacheResult;
+        try {
+            cacheResult = trustChainCache.get(new CriteriaSet(new SubjectEntityIDCriterion(issuer)));
+        } catch (final MetadataCacheException e) {
+            log.warn("{} Exception while fetching trust chains for {}", getLogPrefix(), issuer, e);
+            return false;
+        }
+        if (cacheResult.isEmpty() || cacheResult.get(0).isEmpty()) {
+            log.warn("{} No trust chains resolved for {}", getLogPrefix(), issuer);
+            return false;
+        }
+        final List<EntityStatement> trustMarkChain = cacheResult.get(0).get(0);
+        final EntityStatement trustMarkIssuer = trustMarkChain.get(0);
+        assert trustMarkIssuer != null;
+        final CriteriaSet criteria = new CriteriaSet(new SubjectEntityStatementCriterion(trustMarkIssuer));
+        try {
+            if (trustEngine.validate(jwt, criteria)) {
+                log.debug("Successfully validated trust mark issued by {}", issuer);
+                return true;
+            }
+        } catch (final SecurityException e) {
+            log.debug("Security exception while validating trust mark signature for {}", issuer, e);
+        }
+        return false;
+    }
+
+    /**
+     * Parses the trust mark ID for the given trust mark.
+     * 
+     * @param trustMark the trust mark
+     * @return the ID, or null if it could not be parsed
+     */
+    @Nullable private String getTrustMarkId(@Nullable final SignedJWT trustMark) {
+        try {
+            return trustMark == null ? null : trustMark.getJWTClaimsSet().getStringClaim("id");
+        } catch (final ParseException e) {
+            log.error("{} Could not parse the TrustMark JWT contents", getLogPrefix(), e);
+        }
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/SelectTrustChain.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/SelectTrustChain.java
index 048638ad..897dfc69 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/SelectTrustChain.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/SelectTrustChain.java
@@ -14,7 +14,6 @@
 
 package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
 
-import java.time.Instant;
 import java.util.List;
 import java.util.function.Function;
 
@@ -32,7 +31,7 @@ import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
 
 import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultTrustChainSelectionStrategy;
 import net.shibboleth.idp.profile.AbstractProfileAction;
-import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
+import net.shibboleth.profile.context.RelyingPartyContext;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.collection.Pair;
@@ -57,6 +56,9 @@ public class SelectTrustChain extends AbstractProfileAction {
     /** Strategy used to lookup the trust chain context. */
     @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
 
+    /** Strategy used to create the relying party context where to signal the selected trust anchor. */
+    @Nonnull private Function<ProfileRequestContext, RelyingPartyContext> relyingPartyContextCreationStrategy;
+
     /** Strategy used to fetch the selected trust chain and metadata. */
     @NonnullAfterInit private Function<ProfileRequestContext,Pair<List<EntityStatement>, OIDCClientInformation>>
         selectedTrustChainLookupStrategy;
@@ -73,9 +75,26 @@ public class SelectTrustChain extends AbstractProfileAction {
                         new InboundMessageContextLookup());
         assert tcls != null;
         trustChainContextLookupStrategy = tcls;
+        final Function<ProfileRequestContext, RelyingPartyContext> rpccs =
+                new ChildContextLookup<>(RelyingPartyContext.class, true).compose(tcls);
+        assert rpccs != null;
+        relyingPartyContextCreationStrategy = rpccs;
         selectedTrustChainLookupStrategy = new DefaultTrustChainSelectionStrategy();
     }
 
+    /**
+     * Set the strategy used to return or create the {@link RelyingPartyContext}
+     * 
+     * @param strategy
+     *            creation strategy
+     */
+    public void setRelyingPartyContextCreationStrategy(
+            @Nonnull final Function<ProfileRequestContext, RelyingPartyContext> strategy) {
+        checkSetterPreconditions();
+        relyingPartyContextCreationStrategy = Constraint.isNotNull(strategy,
+                "RelyingPartyContext creation strategy cannot be null");
+    }
+
     /**
      * Set the strategy used to lookup the trust chain context.
      * 
@@ -140,17 +159,10 @@ public class SelectTrustChain extends AbstractProfileAction {
         trustChainContext.setSelectedTrustChains(selectedChain);
         final List<EntityStatement> selectedTrustChain = selectedChain.getFirst();
         assert selectedTrustChain != null;
-        Instant metadataExpiration = null;
-        for (final EntityStatement statement : selectedTrustChain) {
-            final Instant statementExpiration = statement.getClaimsSet().getExpirationTime().toInstant();
-            metadataExpiration = metadataExpiration == null ? statementExpiration : 
-                statementExpiration.isBefore(metadataExpiration) ? statementExpiration : metadataExpiration;
-        }
-        trustChainContext.setSelectedMetadataExpiration(metadataExpiration);
-
-        final OIDCMetadataContext oidcCtx = new OIDCMetadataContext();
-        oidcCtx.setClientInformation(selectedChain.getSecond());
-        profileRequestContext.ensureInboundMessageContext().addSubcontext(oidcCtx);
-        log.debug("{} Client information attached to the OIDCMetadataContext", getLogPrefix());
+        final RelyingPartyContext relyingPartyContext =
+                relyingPartyContextCreationStrategy.apply(profileRequestContext);
+        relyingPartyContext.setRelyingPartyId(
+                selectedTrustChain.get(selectedTrustChain.size() - 1).getEntityID().getValue());
+        relyingPartyContext.setVerified(true);
     }
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
new file mode 100644
index 00000000..24b0f7de
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
@@ -0,0 +1,229 @@
+/*
+ * 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.impl;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.MandatoryTrustMarksLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.LocalMetadataPolicyLookupFunction;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+
+/**
+ * Resolves metadata policy-compliant trust chains from the configurable trust chain cache, metadata policy merging
+ * strategy and enforcer. The data is populated to the {@link RelyingPartyTrustChainContext}.
+ * 
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ * 
+ * @since 4.3.0
+ */
+public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractProfileAction {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(ValidateAutomaticRegistrationProfileConfiguration.class);
+
+    /** Strategy used to lookup the trust chain context. */
+    @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+
+    /** Strategy used to lookup mandatory trust marks. */
+    @Nonnull private Function<ProfileRequestContext, List<String>> mandatoryTrustMarksLookupStrategy;
+
+    /** Strategy used to lookup local metadata policy to be merged to the client metadata. */
+    @Nonnull private Function<ProfileRequestContext, Map<String, MetadataPolicy>> localMetadataPolicyLookupStrategy;
+
+    /** Strategy used to merge local metadata policy into the client metadata. */
+    @NonnullAfterInit private BiFunction<OIDCClientInformation,Map<String, MetadataPolicy>,OIDCClientInformation>
+        localMetadataPolicyMergingStrategy;
+
+    /** Trust chain context to operate on. */
+    @NonnullBeforeExec private RelyingPartyTrustChainContext trustChainContext;
+
+    /** Selected trust chain to operate on. */
+    @NonnullBeforeExec private Pair<List<EntityStatement>, OIDCClientInformation> selectedTrustChain;
+
+    /** Client ID of the client. */
+    @NonnullBeforeExec private String clientId;
+
+    /**
+     * Constructor.
+     */
+    public ValidateAutomaticRegistrationProfileConfiguration() {
+        final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
+                new ChildContextLookup<>(RelyingPartyTrustChainContext.class).compose(
+                        new InboundMessageContextLookup());
+        assert tcls != null;
+        trustChainContextLookupStrategy = tcls;
+        mandatoryTrustMarksLookupStrategy = new MandatoryTrustMarksLookupFunction();
+        localMetadataPolicyLookupStrategy = new LocalMetadataPolicyLookupFunction();
+    }
+
+    /**
+     * Set the strategy used to lookup the trust chain context.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setTrustChainContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, RelyingPartyTrustChainContext> strategy) {
+        checkSetterPreconditions();
+        trustChainContextLookupStrategy =
+                Constraint.isNotNull(strategy, "TrustChainContextLookupStrategy cannot be null");
+    }
+
+    /**
+     * Set the strategy used to lookup mandatory trust marks.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setMandatoryTrustMarksLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, List<String>> strategy) {
+        checkSetterPreconditions();
+        mandatoryTrustMarksLookupStrategy = Constraint.isNotNull(strategy,
+                "Mandatory trust marks lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the strategy used to lookup local metadata policy to be merged to the client metadata.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setLocalMetadataPolicyLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, Map<String, MetadataPolicy>> strategy) {
+        checkSetterPreconditions();
+        localMetadataPolicyLookupStrategy = Constraint.isNotNull(strategy,
+                "Local metadata policy lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the strategy used to merge local metadata policy into the client metadata.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setLocalMetadataPolicyMergingStrategy(@Nonnull
+            final BiFunction<OIDCClientInformation,Map<String, MetadataPolicy>,OIDCClientInformation> strategy) {
+        checkSetterPreconditions();
+        localMetadataPolicyMergingStrategy =
+                Constraint.isNotNull(strategy, "Local metadata policy merging strategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+
+        trustChainContext = trustChainContextLookupStrategy.apply(profileRequestContext);
+        if (trustChainContext == null || trustChainContext.getSelectedTrustChain() == null) {
+            log.error("{} Unable to locate selected trust chain", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+
+        selectedTrustChain = trustChainContext.getSelectedTrustChain();
+        assert selectedTrustChain != null;
+        if (selectedTrustChain.getFirst() == null || selectedTrustChain.getSecond() == null) {
+            log.error("{} Selected trust chain contents is not populated", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+
+        final OIDCClientInformation clientInformation = selectedTrustChain.getSecond();
+        assert clientInformation != null;
+        clientId = clientInformation.getID().getValue();
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        final Map<String, MetadataPolicy> localMetadataPolicy =
+                localMetadataPolicyLookupStrategy.apply(profileRequestContext);
+        if (localMetadataPolicy != null && !localMetadataPolicy.isEmpty()) {
+            log.debug("{} Applying local metadata policy into the client metadata", getLogPrefix());
+            final OIDCClientInformation enforcedMetadata =
+                    localMetadataPolicyMergingStrategy.apply(selectedTrustChain.getSecond(), localMetadataPolicy);
+            if (enforcedMetadata == null) {
+                log.error("{} Could not apply the local metadata policy into the client metadata", getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+                return;
+            }
+            selectedTrustChain.setSecond(enforcedMetadata);
+        }
+ 
+        final List<String> mandatoryTrustMarks = mandatoryTrustMarksLookupStrategy.apply(profileRequestContext);
+        if (mandatoryTrustMarks != null && !mandatoryTrustMarks.isEmpty()) {
+            log.debug("{} Verifying the mandatory trust marks {}", getLogPrefix(), mandatoryTrustMarks);
+            final Map<String,List<String>> verifiedTrustMarks = trustChainContext.getVerifiedTrustMarkIds();
+            if (verifiedTrustMarks == null || verifiedTrustMarks.get(clientId) == null 
+                    || !verifiedTrustMarks.get(clientId).containsAll(mandatoryTrustMarks)) {
+                log.info("{} Rejecting registration as some of the following mandatory trust marks are missing: {}",
+                        getLogPrefix(), mandatoryTrustMarks);
+                ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+                return;
+            }
+        }
+
+        final List<EntityStatement> trustChain = selectedTrustChain.getFirst();
+        assert trustChain != null;
+        trustChainContext.setSelectedMetadataExpiration(resolveTrustChainExpiration(trustChain));
+
+        final OIDCMetadataContext oidcCtx = new OIDCMetadataContext();
+        oidcCtx.setClientInformation(selectedTrustChain.getSecond());
+        profileRequestContext.ensureInboundMessageContext().addSubcontext(oidcCtx);
+        log.debug("{} Client information attached to the OIDCMetadataContext", getLogPrefix());
+    }
+
+    /**
+     * Resolve expiration time for the given trust chain.
+     * 
+     * @param trustChain trust chain
+     * @return expiration time
+     */
+    @Nullable private Instant resolveTrustChainExpiration(@Nonnull final List<EntityStatement> trustChain) {
+        Instant metadataExpiration = null;
+        for (final EntityStatement statement : trustChain) {
+            final Instant statementExpiration = statement.getClaimsSet().getExpirationTime().toInstant();
+            metadataExpiration = metadataExpiration == null ? statementExpiration : 
+                statementExpiration.isBefore(metadataExpiration) ? statementExpiration : metadataExpiration;
+        }
+        return metadataExpiration;
+    }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultLocalMetadataPolicyMergingStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultLocalMetadataPolicyMergingStrategy.java
new file mode 100644
index 00000000..684aa0de
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultLocalMetadataPolicyMergingStrategy.java
@@ -0,0 +1,112 @@
+/*
+ * 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.navigate;
+
+import java.util.Map;
+import java.util.function.BiFunction;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+
+import net.minidev.json.JSONObject;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.collection.Pair;
+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 to merge the metadata policies for the given entity type from the given trust chain. Finally,
+ * a configurable local metadata policy is merged to the resulting map of metadata policies.
+ */
+public class DefaultLocalMetadataPolicyMergingStrategy extends AbstractIdentifiableInitializableComponent
+        implements BiFunction<OIDCClientInformation,Map<String, MetadataPolicy>,OIDCClientInformation> {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(DefaultLocalMetadataPolicyMergingStrategy.class);
+
+    /** Enforcer function for applying metadata policy for an item. */
+    @NonnullAfterInit private BiFunction<Object, MetadataPolicy, Pair<Object, Boolean>> metadataPolicyEnforcer;
+
+    /**
+     * Set the enforcer function for applying metadata policy for an item.
+     * 
+     * @param enforcer policy enforcer
+     */
+    public void setMetadataPolicyEnforcer(
+            @Nonnull final BiFunction<Object, MetadataPolicy, Pair<Object, Boolean>> enforcer) {
+        checkSetterPreconditions();
+        metadataPolicyEnforcer = Constraint.isNotNull(enforcer, "Metadata policy enforcer cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (metadataPolicyEnforcer == null) {
+            throw new ComponentInitializationException("MetadataPolicyEnforcer cannot be null");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nullable
+    public OIDCClientInformation apply(@Nullable final OIDCClientInformation inputMetadata,
+            @Nullable final Map<String, MetadataPolicy> localPolicy) {
+        checkComponentActive();
+        if (inputMetadata == null) {
+            return null;
+        }
+        if (localPolicy == null || localPolicy.isEmpty()) {
+            return inputMetadata;
+        }
+        final JSONObject result = inputMetadata.getOIDCMetadata().toJSONObject();
+        boolean compliant = true;
+        for (final String claim : localPolicy.keySet()) {
+            final MetadataPolicy policy = localPolicy.get(claim);
+            final Object value = inputMetadata.getOIDCMetadata().toJSONObject().get(claim);
+            log.debug("Claim {} set in policy included in the input: {}", claim, value == null);
+            final Pair<Object,Boolean> mergeResult = metadataPolicyEnforcer.apply(value, policy);
+            final Boolean enforcerResult = mergeResult != null ? mergeResult.getSecond() : null;
+            if (enforcerResult == null || !enforcerResult.booleanValue()) {
+                log.warn("Metadata claim {} is not compliant with the policy", claim);
+                compliant = false;
+            } else {
+                log.trace("Validation result is OK for claim {}", claim);
+                final Object enforcedValue = mergeResult != null ? mergeResult.getFirst() : null;
+                result.put(claim, enforcedValue);
+            }
+        }
+
+        if (!compliant) {
+            log.warn("The requested metadata is not compliant with the policy");
+        } else {
+            log.debug("The requested metadata is compliant with the policy");
+            try {
+                return OIDCClientInformation.parse(result);
+            } catch (final ParseException e) {
+                log.error("Could not parse the metadata object", e);
+            }
+        }
+        return null;
+    }
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/LocalMetadataPolicyLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/LocalMetadataPolicyLookupFunction.java
new file mode 100644
index 00000000..54acc5dd
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/LocalMetadataPolicyLookupFunction.java
@@ -0,0 +1,49 @@
+/*
+ * 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.navigate;
+
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.config.OIDFederationAutomaticRegistrationConfiguration;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+
+/**
+ * A function that obtains
+ * {@link OIDFederationAutomaticRegistrationConfiguration#getLocalMetadataPolicy(ProfileRequestContext)}.
+ * 
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class LocalMetadataPolicyLookupFunction extends AbstractRelyingPartyLookupFunction<Map<String, MetadataPolicy>> {
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable public Map<String, MetadataPolicy> apply(@Nullable final ProfileRequestContext input) {
+        final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+        if (rpc != null) {
+            final ProfileConfiguration pc = rpc.getProfileConfig();
+            if (pc instanceof OIDFederationAutomaticRegistrationConfiguration ofarc) {
+                return ofarc.getLocalMetadataPolicy(input);
+            }
+        }
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/MandatoryTrustMarksLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/MandatoryTrustMarksLookupFunction.java
new file mode 100644
index 00000000..3a4b8f39
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/MandatoryTrustMarksLookupFunction.java
@@ -0,0 +1,48 @@
+/*
+ * 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.navigate;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.config.OIDFederationAutomaticRegistrationConfiguration;
+
+/**
+ * A function that obtains
+ * {@link OIDFederationAutomaticRegistrationConfiguration#getMandatoryTrustMarks(ProfileRequestContext)}.
+ * 
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class MandatoryTrustMarksLookupFunction extends AbstractRelyingPartyLookupFunction<List<String>> {
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable public List<String> apply(@Nullable final ProfileRequestContext input) {
+        final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+        if (rpc != null) {
+            final ProfileConfiguration pc = rpc.getProfileConfig();
+            if (pc instanceof OIDFederationAutomaticRegistrationConfiguration ofarc) {
+                return ofarc.getMandatoryTrustMarks(input);
+            }
+        }
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/MaximumTrustMarkLifetimeLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/MaximumTrustMarkLifetimeLookupFunction.java
new file mode 100644
index 00000000..c39ed5d7
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/MaximumTrustMarkLifetimeLookupFunction.java
@@ -0,0 +1,52 @@
+/*
+ * 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.navigate;
+
+import java.time.Duration;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.config.OIDFederationAutomaticRegistrationConfiguration;
+
+/**
+ * A function that returns
+ * {@link OIDFederationAutomaticRegistrationConfiguration#getMaximumTrustMarkLifetime(ProfileRequestContext)} if such a
+ * profile is available from a {@link RelyingPartyContext} obtained via a lookup function, by default a child of the
+ * {@link ProfileRequestContext}.
+ * 
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class MaximumTrustMarkLifetimeLookupFunction extends AbstractRelyingPartyLookupFunction<Duration> {
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable public Duration apply(@Nullable final ProfileRequestContext input) {
+        final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+        if (rpc != null) {
+            final ProfileConfiguration pc = rpc.getProfileConfig();
+            if (pc instanceof OIDFederationAutomaticRegistrationConfiguration ofarc) {
+                return ofarc.getMaximumTrustMarkLifetime(input);
+            }
+        }
+        
+        return null;
+    }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
index 4ebc79fa..4b966da8 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
@@ -1176,6 +1176,23 @@
         </constructor-arg>
     </bean>
 
+    <bean id="AutomaticRegistrationRelyingPartyCreationStrategy" parent="shibboleth.Functions.Compose"
+        c:g-ref="shibboleth.ChildLookupOrCreate.RelyingPartyContext"
+        c:f-ref="RelyingPartyTrustChainContextLookupStrategy" />
+
+    <bean id="RelyingPartyTrustChainContextLookupStrategy" parent="shibboleth.Functions.Expression"
+        c:expression="#input.ensureInboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext))" />
+
+    <bean id="SelectAutomaticRegistrationRelyingPartyConfiguration"
+        class="net.shibboleth.idp.profile.impl.SelectRelyingPartyConfiguration" scope="prototype"
+        p:relyingPartyContextLookupStrategy-ref="AutomaticRegistrationRelyingPartyCreationStrategy"
+        p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyResolverService" />
+
+    <bean id="SelectAutomaticRegistrationProfileConfiguration"
+        class="net.shibboleth.idp.profile.impl.SelectProfileConfiguration" scope="prototype"
+        p:relyingPartyContextLookupStrategy-ref="AutomaticRegistrationRelyingPartyCreationStrategy"
+        p:profileId="#{T(net.shibboleth.idp.plugin.oidc.op.oidfed.config.OIDFederationAutomaticRegistrationConfiguration).PROFILE_ID}" />
+
     <bean id="SelectTrustChain" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.SelectTrustChain"
         scope="prototype">
         <property name="activationCondition">
@@ -1184,6 +1201,35 @@
         </property>
     </bean>
 
+    <bean id="ResolveTrustMarks" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ResolveTrustMarks"
+        scope="prototype"
+        p:trustChainCache-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}">
+        <property name="trustEngine">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.TokenPayloadAsymmetricKeyTrustEngine">
+                <constructor-arg>
+                    <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultEntityStatementCredentialResolver" />
+                 </constructor-arg>
+            </bean>
+        </property>
+        <property name="trustedTrustMarkIssuersLookupStrategy">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy"
+                p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper" />
+        </property>
+    </bean>
+
+    <bean id="ValidateAutomaticRegistrationProfileConfiguration"
+        class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ValidateAutomaticRegistrationProfileConfiguration"
+        scope="prototype">
+        <property name="localMetadataPolicyMergingStrategy">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultLocalMetadataPolicyMergingStrategy"
+                p:metadataPolicyEnforcer-ref="#{'%{idp.oidfed.authorize.MetadataPolicyEnforcer:DefaultMetadataPolicyEnforcer}'.trim()}"/>
+        </property>
+        <property name="mandatoryTrustMarksLookupStrategy">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.MandatoryTrustMarksLookupFunction"
+                p:relyingPartyContextLookupStrategy-ref="AutomaticRegistrationRelyingPartyCreationStrategy"/>
+        </property>
+    </bean>
+
     <bean id="InitializeRelyingPartyContext"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeRelyingPartyContext" scope="prototype"
         p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy">
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
index 413675da..9846f0f2 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
@@ -47,6 +47,10 @@
     <action-state id="DoAutomaticRegistration">
         <evaluate expression="ResolveTrustChains" />
         <evaluate expression="SelectTrustChain" />
+        <evaluate expression="ResolveTrustMarks" />
+        <evaluate expression="SelectAutomaticRegistrationRelyingPartyConfiguration" />
+        <evaluate expression="SelectAutomaticRegistrationProfileConfiguration" />
+        <evaluate expression="ValidateAutomaticRegistrationProfileConfiguration" />
         <evaluate expression="InitializeRelyingPartyContext" />
         <evaluate expression="'proceed'" />
         <transition on="proceed" to="DoSelectConfiguration">
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index b3a3d105..6cdccafd 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -116,6 +116,11 @@
     <bean id="DefaultLogoutHintMatchingPredicate"
           class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultLogoutHintMatchingPredicate"/>
 
+    <bean id="OIDFED.AutomaticRegistration" parent="AbstractOIDCProfile" lazy-init="true"
+        class="net.shibboleth.idp.plugin.oidc.op.oidfed.config.DefaultOIDFederationAutomaticRegistrationConfiguration"
+        p:mandatoryTrustMarks="%{idp.oidfed.automaticRegistration.mandatoryTrustMarks:}"
+        p:securityConfiguration-ref="shibboleth.oidc.federation.DefaultSecurityConfiguration" />
+
     <!-- Metadata-driven variants. -->
 
     <bean id="AbstractMDDrivenOAuthTokenValidatingProfile" parent="AbstractMDDrivenOAuthClientAuthenticatableProfile" abstract="true">
@@ -936,4 +941,7 @@
         </property>
     </bean>
 
+    <bean id="RelyingPartyByTrustAnchor" abstract="true" parent="RelyingParty"
+        class="net.shibboleth.idp.plugin.oidc.op.oidfed.config.RelyingPartyConfigurationSupport" factory-method="byTrustAnchor" />
+
 </beans>

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


More information about the commits mailing list