[java-oidfed-common] 01/02: Move trust chain selection and trust mark resolution/validation from the OP plugin

Codeberg noreply at shibboleth.net
Thu Jun 4 10:58:28 UTC 2026


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

codeberg pushed a commit to branch main
in repository java-oidfed-common.

View the commit online:
https://codeberg.org/Shibboleth/java-oidfed-common/commit/34c8a2bf6be5045986c2b2d3143976f59d2076ec

commit 34c8a2bf6be5045986c2b2d3143976f59d2076ec
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu Jun 4 13:43:38 2026 +0300

    Move trust chain selection and trust mark resolution/validation from the OP plugin
    
    - SWF actions: ResolveTrustMarks, SelectTrustChain, ValidateTrustMarks
    - Default navigation strategies related to them
---
 .../oidfed/profile/impl/ResolveTrustMarks.java     | 569 +++++++++++++++++++++
 .../oidfed/profile/impl/SelectTrustChain.java      | 158 ++++++
 .../oidfed/profile/impl/ValidateTrustMarks.java    | 247 +++++++++
 .../AbstractTrustChainContextLookupFunction.java   |  83 +++
 ...DefaultSelectedTrustChainIDsLookupStrategy.java |  63 +++
 ...dTrustChainImmediateSuperiorLookupStrategy.java |  41 ++
 ...ltSelectedTrustChainMetadataLookupStrategy.java |  51 ++
 ...electedTrustChainTrustAnchorLookupStrategy.java |  41 ++
 .../DefaultTrustChainSelectionStrategy.java        |  91 ++++
 ...DefaultTrustChainTrustMarksParsingStrategy.java | 128 +++++
 ...ChainTrustedTrustMarkIssuersLookupStrategy.java |  66 +++
 ...tChainTrustedTrustMarkOwnersLookupStrategy.java |  61 +++
 12 files changed, 1599 insertions(+)

diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/impl/ResolveTrustMarks.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/impl/ResolveTrustMarks.java
new file mode 100644
index 0000000..cadb3b9
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/impl/ResolveTrustMarks.java
@@ -0,0 +1,569 @@
+/*
+ * 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.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 java.util.stream.Collectors;
+
+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 net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.cache.SubjectEntityIDCriterion;
+import net.shibboleth.oidfed.metadata.cache.SubjectEntityStatementCriterion;
+import net.shibboleth.oidfed.metadata.cache.TrustMarkOwnersCriterion;
+import net.shibboleth.oidfed.metadata.cache.trustchain.TrustChainsContainer;
+import net.shibboleth.oidfed.metadata.payload.claim.TrustMarkOwner;
+import net.shibboleth.oidfed.profile.context.RelyingPartyTrustChainContext;
+import net.shibboleth.oidfed.profile.context.VerifiedTrustChain;
+import net.shibboleth.shared.annotation.constraint.Live;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.collection.CollectionSupport;
+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}
+ */
+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. */
+    @NonnullAfterInit
+    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;
+
+    /** Strategy used to lookup trusted trust mark owners for the trust chain. */
+    @NonnullAfterInit
+    private Function<List<EntityStatement<?>>, Map<String, TrustMarkOwner>> trustedTrustMarkOwnersLookupStrategy;
+
+    /** 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<TrustChainsContainer> trustChainCache;
+
+    /** Trust engine used to validate a trust mark signature. */
+    @NonnullAfterInit private TrustEngine<SignedJWT> trustEngine;
+
+    /** Trust engine used to validate a delegated trust mark signature. */
+    @NonnullAfterInit private TrustEngine<SignedJWT> delegationTrustEngine;
+
+    /** Strategy used to lookup trust mark claims validator. */
+    @NonnullAfterInit private Function<ProfileRequestContext,ClaimsValidator> trustMarkClaimsValidationLookupStrategy;
+
+    /** Strategy used to lookup delegated trust mark claims validator. */
+    @NonnullAfterInit
+    private Function<ProfileRequestContext,ClaimsValidator> delegatedTrustMarkClaimsValidationLookupStrategy;
+
+    /** Trust chain context to operate on. */
+    @NonnullBeforeExec private RelyingPartyTrustChainContext trustChainContext;
+
+    /** The selected trust chain to resolve trust marks from. */
+    @NonnullBeforeExec private List<EntityStatement<?>> selectedTrustChain;
+
+    /** Trust mark claims validator. */
+    @NonnullBeforeExec private ClaimsValidator trustMarkClaimsValidator;
+
+    /** Delegated trust mark claims validator. */
+    @NonnullBeforeExec private ClaimsValidator delegatedTrustMarkClaimsValidator;
+    
+    /**
+     * Constructor.
+     */
+    public ResolveTrustMarks() {
+        final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
+                new ChildContextLookup<>(RelyingPartyTrustChainContext.class).compose(
+                        new InboundMessageContextLookup());
+        assert tcls != null;
+        trustChainContextLookupStrategy = tcls;
+        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 parse trust marks from the selected trust chain.
+     * 
+     * @param strategy parsing strategy
+     */
+    public void setTrustChainTrustMarksParsingStrategy(
+            @Nonnull final Function<List<EntityStatement<?>>,Map<String,List<SignedJWT>>> strategy) {
+        checkSetterPreconditions();
+        trustChainTrustMarksParsingStrategy =
+                Constraint.isNotNull(strategy, "TrustChainTrustMarksParsingStrategy 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 strategy used to lookup trusted trust mark issuers for the trust chain.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setTrustedTrustMarkOwnersLookupStrategy(
+            @Nonnull final Function<List<EntityStatement<?>>, Map<String, TrustMarkOwner>> strategy) {
+        checkSetterPreconditions();
+        trustedTrustMarkOwnersLookupStrategy =
+                Constraint.isNotNull(strategy, "trustedTrustMarkOwnersLookupStrategy cannot be null");
+    }
+
+    /**
+     * Set the condition to solely take trusted trust mark issuers into account.
+     * 
+     * @param condition condition to set
+     */
+    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<TrustChainsContainer> 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");
+    }
+
+    /**
+     * Set trust engine used to validate a delegated trust mark signature.
+     * 
+     * @param engine trust engine
+     */
+    public void setDelegationTrustEngine(@Nonnull final TrustEngine<SignedJWT> engine) {
+        checkSetterPreconditions();
+        delegationTrustEngine = Constraint.isNotNull(engine, "Delegation Trust Engine cannot be null");
+    }
+
+    /**
+     * Set the strategy used to lookup trust mark claims validator.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setTrustMarkClaimsValidationLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, ClaimsValidator> strategy) {
+        checkSetterPreconditions();
+        trustMarkClaimsValidationLookupStrategy =
+                Constraint.isNotNull(strategy, "TrustMarkClaimsValidationLookupStrategy cannot be null");
+    }
+
+    /**
+     * Set the strategy used to lookup delegated trust mark claims validator.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setDelegatedTrustMarkClaimsValidationLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, ClaimsValidator> strategy) {
+        checkSetterPreconditions();
+        delegatedTrustMarkClaimsValidationLookupStrategy =
+                Constraint.isNotNull(strategy, "DelegatedTrustMarkClaimsValidationLookupStrategy 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 (delegationTrustEngine == null) {
+            throw new ComponentInitializationException("Delegation Trust Engine cannot be null");
+        }
+        if (trustChainTrustMarksParsingStrategy == null) {
+            throw new ComponentInitializationException("Trust marks parsing strategy cannot be null");
+        }
+        if (trustedTrustMarkIssuersLookupStrategy == null) {
+            throw new ComponentInitializationException("Trusted trust mark issuers lookup strategy cannot be null");
+        }
+        if (trustedTrustMarkOwnersLookupStrategy == null) {
+            throw new ComponentInitializationException("Trusted trust mark owners lookup strategy cannot be null");
+        }
+        if (trustMarkClaimsValidationLookupStrategy == null) {
+            throw new ComponentInitializationException("TrustMarkClaimsValidationLookupStrategy cannot be null");
+        }
+        if (delegatedTrustMarkClaimsValidationLookupStrategy == null) {
+            throw new ComponentInitializationException(
+                    "DelegatedTrustMarkClaimsValidationLookupStrategy 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 VerifiedTrustChain selectedChain = trustChainContext.getSelectedTrustChain();
+
+        if (selectedChain == null) {
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            log.error("{} No selected trust chain could be resolved", getLogPrefix());
+            return false;
+        }
+
+        selectedTrustChain = selectedChain.getTrustChain();
+
+        trustMarkClaimsValidator = trustMarkClaimsValidationLookupStrategy.apply(profileRequestContext);
+        if (trustMarkClaimsValidator == null) {
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            log.error("{} Unable to locate trust mark claims validator", getLogPrefix());
+            return false;
+        }
+
+        delegatedTrustMarkClaimsValidator =
+                delegatedTrustMarkClaimsValidationLookupStrategy.apply(profileRequestContext);
+        if (delegatedTrustMarkClaimsValidator == null) {
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            log.error("{} Unable to locate delegated trust mark claims validator", getLogPrefix());
+            return false;
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        final boolean onlyTrustedIssuers = trustedTrustMarkIssuersOnlyCondition.test(profileRequestContext);
+
+        trustChainContext.setVerifiedTrustMarks(CollectionSupport.emptyMap());
+        trustChainContext.setVerifiedTrustMarkIds(CollectionSupport.emptyMap());
+
+        final Map<String, List<SignedJWT>> chainTrustMarks =
+                Optional.ofNullable(trustChainTrustMarksParsingStrategy.apply(selectedTrustChain))
+                    .orElseGet(NonnullSupplier.of(CollectionSupport.emptyMap()))
+                    .entrySet().stream()
+                    .collect(Collectors.toMap(entry -> entry.getKey(),
+                            entry -> entry.getValue().stream()
+                                .filter(trustMark ->
+                                        validateClaims(trustMarkClaimsValidator, trustMark, profileRequestContext))
+                            .toList()));
+        if (chainTrustMarks == null || chainTrustMarks.isEmpty()) {
+            log.debug("{} No valid trust marks found from the selected trust chain", getLogPrefix());
+            return;
+        }
+
+        final Map<String, List<String>> trustedIssuers =
+                Optional.ofNullable(trustedTrustMarkIssuersLookupStrategy.apply(selectedTrustChain))
+                    .orElseGet(NonnullSupplier.of(CollectionSupport.emptyMap()));
+        log.debug("{} Trusted trust mark issuers {}", getLogPrefix(), trustedIssuers);
+        assert trustedIssuers != null;
+
+        final Map<String, TrustMarkOwner> trustedOwners =
+                Optional.ofNullable(trustedTrustMarkOwnersLookupStrategy.apply(selectedTrustChain))
+                    .orElseGet(NonnullSupplier.of(CollectionSupport.emptyMap()));
+        log.debug("{} Trusted trust mark owners {}", getLogPrefix(), trustedOwners);
+        assert trustedOwners != null;
+        
+        final Map<String, List<SignedJWT>> verifiedTrustMarks = new HashMap<>();
+        for (final EntityStatement<?> statement : selectedTrustChain) {
+            final List<SignedJWT> trustMarks = chainTrustMarks.get(statement.getSubject());
+            if (trustMarks == null || trustMarks.isEmpty()) {
+                break;
+            }
+            verifiedTrustMarks.put(
+                    statement.getSubject(),
+                    trustMarks.stream()
+                        .filter(entry -> onlyTrustedIssuers ?
+                                checkTrustedIssuer(entry, trustedIssuers, trustedOwners) : true)
+                        .filter(entry -> verifyTrustMark(entry, trustedOwners, profileRequestContext,
+                                trustChainContext.getVerifiedTrustMarkIssuers()))
+                        .filter(Objects::nonNull)
+                        .collect(Collectors.toList()));
+        }
+        trustChainContext.setVerifiedTrustMarks(verifiedTrustMarks);
+        final Map<String, List<String>> verifiedTrustMarkIds = verifiedTrustMarks.entrySet().stream()
+                .collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue().stream()
+                        .map(list -> getTrustMarkId(list))
+                        .collect(Collectors.toList())));
+        log.debug("{} The following trust marks are validated: {}", getLogPrefix(), verifiedTrustMarkIds);
+        trustChainContext.setVerifiedTrustMarkIds(verifiedTrustMarkIds);
+    }
+
+    /**
+     * Validates the given trust mark JWT against the given claims validator.
+     * 
+     * @param claimsValidator the claims validator (chain)
+     * @param jwt the trust mark
+     * @param profileRequestContext the profile request context
+     * @return true if validation succeeded, false otherwise
+     */
+    protected boolean validateClaims(@Nullable final ClaimsValidator claimsValidator, @Nullable final SignedJWT jwt,
+            @Nonnull final ProfileRequestContext profileRequestContext) {
+        if (claimsValidator == null || jwt == null) {
+            return false;
+        }
+        try {
+            final JWTClaimsSet claimsSet = jwt.getJWTClaimsSet();
+            assert claimsSet != null;
+            claimsValidator.validate(claimsSet, profileRequestContext);
+            return true;
+        } catch (final JWTValidationException | ParseException e) {
+            log.debug("{} Claims validation failed", getLogPrefix(), e);
+        }
+        return false;
+    }
+
+    /**
+     * Verifies the given trust mark meets configuration for trusted trust mark issuers.
+     * 
+     * @param jwt the trust mark to be verified
+     * @param trustedIssuers the trusted trust mark issuers
+     * @param trustedOwners the trusted trust mark owners
+     * @return true if the trust mark meets configuration, false otherwise
+     */
+    protected boolean checkTrustedIssuer(@Nullable final SignedJWT jwt,
+            @Nonnull final Map<String, List<String>> trustedIssuers,
+            @Nonnull final Map<String, TrustMarkOwner> trustedOwners) {
+        if (jwt == null) {
+            return false;
+        }
+        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 (validIssuers == null || !validIssuers.contains(issuer)) {
+                    log.debug("{} Issuer {} is not valid trust mark issuer", getLogPrefix(), issuer);
+                    return false;
+                }
+            } else if (trustedOwners.containsKey(id)) {
+                log.debug("{} Trust mark ID {} is included in trusted owners", getLogPrefix(), id);
+                if (claimsSet.getStringClaim("delegation") == null) {
+                    log.debug("(} Trust mark ID {} does not contain a delegation claim", getLogPrefix(), id);
+                    return false;
+                }
+            } else {
+                log.debug("{} Trust mark ID {} is not included in the trusted issuers", getLogPrefix(), id);
+                return false;
+            }
+        } catch (final ParseException e) {
+            log.error("{} Could not parse TrustMark JWT contents", getLogPrefix(), 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
+     * @param trustedOwners the trusted trust mark owners
+     * @param profileRequestContext the profile request context
+     * @param trustedTrustMarkIssuers the map of trusted issuers that will be populated if issuer was verified
+     * @return true if trust mark verification was successful, false otherwise
+     */
+    protected boolean verifyTrustMark(@Nullable final SignedJWT jwt,
+            @Nonnull final Map<String, TrustMarkOwner> trustedOwners,
+            @Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull @Live final Map<String, EntityStatement<?>> trustedTrustMarkIssuers) {
+        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 {}", getLogPrefix(), issuer);
+        final List<TrustChainsContainer> 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).getTrustChains().isEmpty()) {
+            log.warn("{} No trust chains resolved for {}", getLogPrefix(), issuer);
+            return false;
+        }
+        final List<EntityStatement<?>> trustMarkChain = cacheResult.get(0).getTrustChains().get(0);
+        final EntityStatement<?> trustMarkIssuer = trustMarkChain.get(0);
+        assert trustMarkIssuer != null;
+        final CriteriaSet criteria = new CriteriaSet(new SubjectEntityStatementCriterion(trustMarkIssuer));
+        trustedTrustMarkIssuers.put(trustMarkIssuer.getSubject(), trustMarkIssuer);
+        log.trace("{} Validating entity statement {}", getLogPrefix(), trustMarkIssuer.getJwt().serialize());
+        try {
+            if (trustEngine.validate(jwt, criteria)) {
+                final String id = getTrustMarkId(jwt);
+                assert id != null;
+                log.debug("{} Successfully validated trust mark {} issued by {}", getLogPrefix(), id, issuer);
+                if (trustedOwners.containsKey(id)) {
+                    return validateDelegatedTrustMark(trustMarkClaims, id, trustedOwners, profileRequestContext);
+                } else {
+                    return true;
+                }
+            }
+        } catch (final SecurityException e) {
+            log.debug("{} Security exception while validating trust mark signature for {}", getLogPrefix(), issuer, e);
+        }
+        return false;
+    }
+
+    /**
+     * Validates a delegated trust mark.
+     * 
+     * @param trustMarkClaims the claims set containing delegation claim
+     * @param id the trust mark identifier
+     * @param trustedOwners the trusted trust mark owners
+     * @param profileRequestContext the profile request context
+     * @return true if delegation JWT was valid, false otherwise
+     */
+    protected boolean validateDelegatedTrustMark(@Nonnull final JWTClaimsSet trustMarkClaims,
+            @Nonnull final String id,
+            @Nonnull final Map<String, TrustMarkOwner> trustedOwners,
+            @Nonnull final ProfileRequestContext profileRequestContext) {
+        log.debug("{} Validating delegated trust mark {}", getLogPrefix(), id);
+        try {
+            final SignedJWT delegationJwt = SignedJWT.parse(trustMarkClaims.getStringClaim("delegation"));
+            final CriteriaSet delegationCriteria = new CriteriaSet(
+                    new TrustMarkOwnersCriterion(trustedOwners),
+                    new SubjectEntityIDCriterion(id));
+            if (validateClaims(delegatedTrustMarkClaimsValidator, delegationJwt, profileRequestContext)) {
+                assert delegationJwt != null;
+                if (delegationTrustEngine.validate(delegationJwt, delegationCriteria)) {
+                    final String issuer = delegationJwt.getJWTClaimsSet().getIssuer();
+                    log.debug("{} Successfully validated delegated {} signature issued by {}", getLogPrefix(), id,
+                            issuer);
+                    if (issuer != null && issuer.equals(trustMarkClaims.getSubject())) {
+                        return true;
+                    } else {
+                        log.debug("{} The issuer of the delegation {} does not match with the subject {}",
+                                getLogPrefix(), issuer, trustMarkClaims.getSubject());
+                    }
+                }
+            }
+        } catch (final SecurityException  e) {
+            log.debug("{} Security exception while validating trust mark signature for {}", getLogPrefix(),
+                    trustMarkClaims.getIssuer(), e);
+        } catch (final ParseException e) {
+            log.debug("{} Parsing exception while processing delegated trust mark from {}", getLogPrefix(),
+                    trustMarkClaims.getIssuer(), 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("trust_mark_type");
+        } 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/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/impl/SelectTrustChain.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/impl/SelectTrustChain.java
new file mode 100644
index 0000000..5bac12f
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/impl/SelectTrustChain.java
@@ -0,0 +1,158 @@
+/*
+ * 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.oidfed.profile.impl;
+
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+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.slf4j.Logger;
+
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.profile.OidFederationEventIds;
+import net.shibboleth.oidfed.profile.context.RelyingPartyTrustChainContext;
+import net.shibboleth.oidfed.profile.context.VerifiedTrustChain;
+import net.shibboleth.oidfed.profile.navigate.DefaultTrustChainSelectionStrategy;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Selects the trust chain via configurable lookup strategy and stores it to the {@link RelyingPartyTrustChainContext}.
+ * 
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ */
+public class SelectTrustChain extends AbstractProfileAction {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(SelectTrustChain.class);
+
+    /** 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. */
+    @Nonnull private Function<ProfileRequestContext,VerifiedTrustChain> selectedTrustChainLookupStrategy;
+
+    /** Trust chain context to operate on. */
+    @NonnullBeforeExec private RelyingPartyTrustChainContext trustChainContext;
+
+    /**
+     * Constructor.
+     */
+    public SelectTrustChain() {
+        final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
+                new ChildContextLookup<>(RelyingPartyTrustChainContext.class).compose(
+                        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.
+     * 
+     * @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 fetch the selected trust chain and metadata.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setSelectedTrustChainLookupStrategy(@Nonnull final
+            Function<ProfileRequestContext,VerifiedTrustChain> strategy) {
+        checkSetterPreconditions();
+        selectedTrustChainLookupStrategy =
+                Constraint.isNotNull(strategy, "SelectedTrustChainLookupStrategy 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.debug("{} Unable to locate policy-compliant trust chains, nothing to do", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, OidFederationEventIds.NO_TRUST_CHAINS_RESOLVED);
+            return false;
+        }
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        final VerifiedTrustChain selectedChain = selectedTrustChainLookupStrategy.apply(profileRequestContext);
+
+        if (selectedChain == null) {
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            log.error("{} No selected trust chain could be resolved", getLogPrefix());
+            return;
+        }
+
+        final List<List<EntityStatement<?>>> rejectedTrustChains = trustChainContext.getRejectedTrustChains();
+        if (rejectedTrustChains != null && rejectedTrustChains.contains(selectedChain.getTrustChain())) {
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            log.error("{} The selected trust chain has been previously rejected", getLogPrefix());
+            return;
+        }
+        trustChainContext.setSelectedTrustChains(selectedChain);
+        final List<EntityStatement<?>> selectedTrustChain = selectedChain.getTrustChain();
+        assert selectedTrustChain != null;
+        final RelyingPartyContext relyingPartyContext =
+                relyingPartyContextCreationStrategy.apply(profileRequestContext);
+        relyingPartyContext.setRelyingPartyId(
+                selectedTrustChain.get(selectedTrustChain.size() - 1).getSubject());
+        relyingPartyContext.setVerified(true);
+    }
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/impl/ValidateTrustMarks.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/impl/ValidateTrustMarks.java
new file mode 100644
index 0000000..d3e7e88
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/impl/ValidateTrustMarks.java
@@ -0,0 +1,247 @@
+/*
+ * 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.oidfed.profile.impl;
+
+import java.text.ParseException;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+
+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.slf4j.Logger;
+
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.cache.trustmark.TrustMarkStatusCacheIdentifier;
+import net.shibboleth.oidfed.metadata.cache.trustmark.TrustMarkStatusContainer;
+import net.shibboleth.oidfed.metadata.cache.trustmark.TrustMarkStatusIdentifierCriterion;
+import net.shibboleth.oidfed.profile.config.navigate.RemoteTrustMarkValidationPredicate;
+import net.shibboleth.oidfed.profile.context.RelyingPartyTrustChainContext;
+import net.shibboleth.oidfed.profile.context.VerifiedTrustChain;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Validates the trust marks for the selected trust chain and updates the info to {@link RelyingPartyTrustChainContext}.
+ * 
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ */
+public class ValidateTrustMarks extends AbstractProfileAction {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(ValidateTrustMarks.class);
+
+    /** Strategy used to lookup the trust chain context. */
+    @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+
+    /** Metadata cache for trust mark status. */
+    @NonnullAfterInit private MetadataCache<TrustMarkStatusContainer> trustMarkStatusCache;
+
+    /** Condition for whether trust marks should be remotely validated. */
+    @Nonnull private Predicate<ProfileRequestContext> remoteTrustMarkValidationCondition;
+
+    /** Trust chain context to operate on. */
+    @NonnullBeforeExec private RelyingPartyTrustChainContext trustChainContext;
+
+    /** The selected trust chain to resolve trust marks from. */
+    @NonnullBeforeExec private List<EntityStatement<?>> selectedTrustChain;
+
+    /**
+     * Constructor.
+     */
+    public ValidateTrustMarks() {
+        final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
+                new ChildContextLookup<>(RelyingPartyTrustChainContext.class).compose(
+                        new InboundMessageContextLookup());
+        assert tcls != null;
+        trustChainContextLookupStrategy = tcls;
+        remoteTrustMarkValidationCondition = new RemoteTrustMarkValidationPredicate();
+    }
+
+    /**
+     * 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 metadata cache for trust mark status.
+     * 
+     * @param cache metadata cache
+     */
+    public void setTrustMarkStatusCache(@Nonnull final MetadataCache<TrustMarkStatusContainer> cache) {
+        checkSetterPreconditions();
+        trustMarkStatusCache = Constraint.isNotNull(cache, "TrustMarkStatusCache cannot be null");
+    }
+
+    /**
+     * Set condition for whether trust marks should be remotely validated.
+     * 
+     * @param condition condition to set
+     */
+     public void setRemoteTrustMarkValidationCondition(@Nonnull final Predicate<ProfileRequestContext> condition) {
+         remoteTrustMarkValidationCondition = Constraint.isNotNull(condition, "Condition cannot be null");
+     }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (trustMarkStatusCache == null) {
+            throw new ComponentInitializationException("TrustMarkStatusCache cannot be null");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+
+        if (!remoteTrustMarkValidationCondition.test(profileRequestContext)) {
+            log.debug("{} Remote trust mark validation condition returned false, nothing to do", getLogPrefix());
+            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 VerifiedTrustChain selectedChain = trustChainContext.getSelectedTrustChain();
+
+        if (selectedChain == null) {
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            log.error("{} No selected trust chain could be resolved", getLogPrefix());
+            return false;
+        }
+
+        selectedTrustChain = selectedChain.getTrustChain();
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        final Map<String, List<SignedJWT>> verifiedTrustMarks = trustChainContext.getVerifiedTrustMarks();
+        final Map<String, List<String>> verifiedTrustMarkIds = trustChainContext.getVerifiedTrustMarkIds();
+        final String subject = selectedTrustChain.get(0).getSubject();
+        final List<SignedJWT> subjectTrustMarks = verifiedTrustMarks != null ? verifiedTrustMarks.get(subject) : null;
+        final List<String> subjectTrustMarkIds =
+                verifiedTrustMarkIds != null ? verifiedTrustMarkIds.get(subject) : null;
+        if (subjectTrustMarks == null || subjectTrustMarks.isEmpty()) {
+            log.debug("{} No trust marks to validate for {}", getLogPrefix(), subject);
+            return;
+        }
+        if (subjectTrustMarkIds == null || subjectTrustMarks.size() != subjectTrustMarkIds.size()) {
+            log.error("{} Unexpected contents for the verified trust mark IDs", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return;
+        }
+        log.trace("{} Subject trust marks before validation {}", getLogPrefix(), subjectTrustMarks);
+        for (final SignedJWT trustMark : CollectionSupport.copyToList(subjectTrustMarks)) {
+            assert trustMark != null;
+            try {
+                final String issuer = trustMark.getJWTClaimsSet().getIssuer();
+                final String trustMarkSubject = trustMark.getJWTClaimsSet().getSubject();
+                final String trustMarkType = trustMark.getJWTClaimsSet().getStringClaim("trust_mark_type");
+                final EntityStatement<?> issuerStatement = trustChainContext.getVerifiedTrustMarkIssuers().get(issuer);
+                if (issuerStatement == null) {
+                    log.warn("{} Could not resolve trust mark issuer statement for {}", getLogPrefix(), issuer);
+                    ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+                    return;
+                }
+                final String uri = Optional.ofNullable(issuerStatement.getParsedPayload().getMetadata())
+                        .map(metadata -> metadata.getFederationEntityMetadata())
+                        .map(entityMetadata -> entityMetadata.get("federation_trust_mark_status_endpoint"))
+                        .filter(String.class::isInstance)
+                        .map(String.class::cast)
+                        .map(value -> StringSupport.trimOrNull(value))
+                        .orElse(null);
+                if (uri == null) {
+                    log.debug("{} No trust mark status endpoint defined for {}, nothing to do", getLogPrefix(), issuer);
+                    continue;
+                }
+                final TrustMarkStatusCacheIdentifier cacheIdentifier =
+                        new TrustMarkStatusCacheIdentifier(uri, trustMark);
+                final CriteriaSet criteria = new CriteriaSet(new TrustMarkStatusIdentifierCriterion(cacheIdentifier));
+                final List<TrustMarkStatusContainer> cacheResult = trustMarkStatusCache.get(criteria);
+                if (cacheResult.isEmpty()) {
+                    log.warn("{} Could not fetch status via metadata cache for {}, issued by {}", getLogPrefix(),
+                            trustMarkType, issuer);
+                    subjectTrustMarks.remove(trustMark);
+                    subjectTrustMarkIds.remove(trustMarkType);
+                    continue;
+                }
+                final SignedJWT statusJwt = Optional.ofNullable(cacheResult.get(0).getStatement())
+                        .map(statement -> statement.getJwt())
+                        .orElse(null);
+                if (statusJwt == null) {
+                    log.warn("{} Could not fetch status JWT via metadata cache for {}", getLogPrefix(), issuer);
+                    subjectTrustMarks.remove(trustMark);
+                    subjectTrustMarkIds.remove(trustMarkType);
+                    continue;
+                }
+                final String status = statusJwt.getJWTClaimsSet().getStringClaim("status");
+                if (!"active".equals(status)) {
+                    log.warn("{} Status for trust mark {} for {} was not active: {}", getLogPrefix(), trustMarkType,
+                            trustMarkSubject, status);
+                    subjectTrustMarks.remove(trustMark);
+                    subjectTrustMarkIds.remove(trustMarkType);
+                    continue;
+                }
+                log.debug("{} Status for trust mark {} for {} is verified", getLogPrefix(), trustMarkType,
+                        trustMarkSubject);
+                continue;
+            } catch (final ParseException e) {
+                log.error("{} Could not parse claims set from the JWT", getLogPrefix(), e);
+            } catch (MetadataCacheException e) {
+                log.warn("{} Could not fetch status via metadata cache", getLogPrefix(), e);
+            }
+            subjectTrustMarks.remove(trustMark);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/AbstractTrustChainContextLookupFunction.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/AbstractTrustChainContextLookupFunction.java
new file mode 100644
index 0000000..4ef9f90
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/AbstractTrustChainContextLookupFunction.java
@@ -0,0 +1,83 @@
+/*
+ * 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.oidfed.profile.navigate;
+
+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 org.slf4j.Logger;
+
+import net.shibboleth.oidfed.profile.context.RelyingPartyTrustChainContext;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * An abstract function for lookup functions dealing with {@link RelyingPartyTrustChainContext}.
+ *
+ * @param <T> The return type of the lookup function
+ */
+public abstract class AbstractTrustChainContextLookupFunction<T> implements Function<ProfileRequestContext, T> {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(AbstractTrustChainContextLookupFunction.class);
+
+    /** Strategy used to locate the trust chain context. */
+    @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+
+    /**
+     * Constructor.
+     */
+    public AbstractTrustChainContextLookupFunction() {
+        final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
+                new ChildContextLookup<>(RelyingPartyTrustChainContext.class).compose(
+                        new InboundMessageContextLookup());
+        assert tcls != null;
+        trustChainContextLookupStrategy = tcls;
+    }
+
+    /**
+     * Set the strategy used to locate the trust chain context.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setTrustChainContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, RelyingPartyTrustChainContext> strategy) {
+        trustChainContextLookupStrategy =
+                Constraint.isNotNull(strategy, "TrustChainContextLookupStrategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nullable public T apply(@Nullable final ProfileRequestContext input) {
+        final RelyingPartyTrustChainContext trustChainContext = trustChainContextLookupStrategy.apply(input);
+        if (trustChainContext == null) {
+            log.debug("Could not resolve trust chain context, returning null");
+            return null;
+        }
+        return doApply(trustChainContext);
+    }
+
+    /**
+     * Perform the lookup operation on the {@link RelyingPartyTrustChainContext}.
+     * 
+     * @param trustChainContext the context, guaranteed to be non-null
+     * @return result
+     */
+    @Nullable protected abstract T doApply(@Nonnull final RelyingPartyTrustChainContext trustChainContext);
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultSelectedTrustChainIDsLookupStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultSelectedTrustChainIDsLookupStrategy.java
new file mode 100644
index 0000000..d01402b
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultSelectedTrustChainIDsLookupStrategy.java
@@ -0,0 +1,63 @@
+/*
+ * 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.oidfed.profile.navigate;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.profile.context.RelyingPartyTrustChainContext;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Default strategy for looking up the entity IDs of the selected trust chain. The selected trust chain is fetched via
+ * {@link RelyingPartyTrustChainContext#getSelectedTrustChain()}.
+ */
+public class DefaultSelectedTrustChainIDsLookupStrategy extends AbstractTrustChainContextLookupFunction<List<String>> {
+
+    /** Strategy used to get entity IDs from a trust chain. */
+    @Nonnull private Function<List<EntityStatement<?>>, List<String>> trustChainIDsLookupStrategy;
+
+    /**
+     * Constructor.
+     */
+    public DefaultSelectedTrustChainIDsLookupStrategy() {
+        super();
+        trustChainIDsLookupStrategy = new DefaultTrustChainIDsLookupStrategy();
+    }
+
+    /**
+     * Set the strategy used to get entity IDs from a trust chain.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setTrustChainIDsLookupStrategy(
+            @Nonnull final Function<List<EntityStatement<?>>, List<String>> strategy) {
+        trustChainIDsLookupStrategy = Constraint.isNotNull(strategy, "TrustChainIDsLookupStrategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nullable
+    public List<String> doApply(@Nonnull final RelyingPartyTrustChainContext trustChainContext) {
+        return Optional.ofNullable(trustChainContext.getSelectedTrustChain())
+                .map(verifiedChain -> verifiedChain.getTrustChain())
+                .map(chain -> trustChainIDsLookupStrategy.apply(chain))
+                .orElse(null);
+    }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultSelectedTrustChainImmediateSuperiorLookupStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultSelectedTrustChainImmediateSuperiorLookupStrategy.java
new file mode 100644
index 0000000..bd2f694
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultSelectedTrustChainImmediateSuperiorLookupStrategy.java
@@ -0,0 +1,41 @@
+/*
+ * 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.oidfed.profile.navigate;
+
+import java.util.Optional;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidfed.profile.context.RelyingPartyTrustChainContext;
+
+/**
+ * Default strategy for looking up the entity ID of the immediate superior in the selected trust chain. The selected
+ * trust chain is fetched via {@link RelyingPartyTrustChainContext#getSelectedTrustChain()}.
+ */
+public class DefaultSelectedTrustChainImmediateSuperiorLookupStrategy
+    extends AbstractTrustChainContextLookupFunction<String> {
+
+    /** {@inheritDoc} */
+    @Override @Nullable
+    public String doApply(@Nonnull final RelyingPartyTrustChainContext trustChainContext) {
+        return Optional.ofNullable(trustChainContext.getSelectedTrustChain())
+                .map(verifiedChain -> verifiedChain.getTrustChain())
+                .filter(list -> list.size() >= 3)
+                .map(list -> list.get(1))
+                .map(entityStatement -> entityStatement.getIssuer())
+                .orElse(null);
+    }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultSelectedTrustChainMetadataLookupStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultSelectedTrustChainMetadataLookupStrategy.java
new file mode 100644
index 0000000..2afb03f
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultSelectedTrustChainMetadataLookupStrategy.java
@@ -0,0 +1,51 @@
+/*
+ * 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.oidfed.profile.navigate;
+
+import java.util.Optional;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.minidev.json.JSONObject;
+import net.shibboleth.oidfed.profile.context.RelyingPartyTrustChainContext;
+
+/**
+ * Default strategy for looking up the metadata the selected trust chain. The selected trust chain is fetched via
+ * {@link RelyingPartyTrustChainContext#getSelectedTrustChain()}.
+ */
+public class DefaultSelectedTrustChainMetadataLookupStrategy
+    extends AbstractTrustChainContextLookupFunction<OIDCClientMetadata> {
+
+    /** {@inheritDoc} */
+    @Override @Nullable
+    public OIDCClientMetadata doApply(@Nonnull final RelyingPartyTrustChainContext trustChainContext) {
+        return Optional.ofNullable(trustChainContext.getSelectedTrustChain())
+                .map(verifiedChain -> verifiedChain.getMetadata())
+                .map(map -> map.getOpenidRelyingPartyMetadata())
+                .map(JSONObject::new)
+                .map(json-> {
+                    try {
+                        return OIDCClientMetadata.parse(json);
+                    } catch (ParseException e) {
+                        return null;
+                    }
+                })
+                .orElse(null);
+    }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultSelectedTrustChainTrustAnchorLookupStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultSelectedTrustChainTrustAnchorLookupStrategy.java
new file mode 100644
index 0000000..f2d5615
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultSelectedTrustChainTrustAnchorLookupStrategy.java
@@ -0,0 +1,41 @@
+/*
+ * 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.oidfed.profile.navigate;
+
+import java.util.Optional;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.oidfed.profile.context.RelyingPartyTrustChainContext;
+
+/**
+ * Default strategy for looking up the entity ID of the trust anchor in the selected trust chain. The selected trust
+ * chain is fetched via {@link RelyingPartyTrustChainContext#getSelectedTrustChain()}.
+ */
+public class DefaultSelectedTrustChainTrustAnchorLookupStrategy
+    extends AbstractTrustChainContextLookupFunction<String> {
+
+    /** {@inheritDoc} */
+    @Override @Nullable
+    public String doApply(@Nonnull final RelyingPartyTrustChainContext trustChainContext) {
+        return Optional.ofNullable(trustChainContext.getSelectedTrustChain())
+                .map(verifiedChain -> verifiedChain.getTrustChain())
+                .filter(list -> list.size() >= 3)
+                .map(list -> list.get(list.size() - 1))
+                .map(entityStatement -> entityStatement.getSubject())
+                .orElse(null);
+    }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultTrustChainSelectionStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultTrustChainSelectionStrategy.java
new file mode 100644
index 0000000..9b27ff0
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultTrustChainSelectionStrategy.java
@@ -0,0 +1,91 @@
+/*
+ * 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.oidfed.profile.navigate;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.profile.context.RelyingPartyTrustChainContext;
+import net.shibboleth.oidfed.profile.context.VerifiedTrustChain;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Default strategy for choosing a specific trust chain: it simply selects the first one in the list whose size is the
+ * shortest. The selection must not be included in the list of previously rejected trust chains, obtained via
+ * {@link RelyingPartyTrustChainContext#getRejectedTrustChains()}.
+ */
+public class DefaultTrustChainSelectionStrategy
+    extends AbstractTrustChainContextLookupFunction<VerifiedTrustChain> {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(DefaultTrustChainSelectionStrategy.class);
+
+    /** {@inheritDoc} */
+    @Override @Nullable
+    public VerifiedTrustChain doApply(
+            @Nonnull final RelyingPartyTrustChainContext trustChainContext) {
+        final List<VerifiedTrustChain> policyCompliantChains =
+                trustChainContext.getPolicyCompliantTrustChains();
+
+        if (policyCompliantChains == null || policyCompliantChains.isEmpty()) {
+            log.debug("No policy compliant chains located");
+            return null;
+        }
+
+        log.trace("Policy-compatible trust chains: {}", policyCompliantChains.size());
+        if (policyCompliantChains.size() > 1) {
+            int shortestIndex = -1;
+            for (int i = 0; i < policyCompliantChains.size(); i++) {
+                final List<EntityStatement<?>> candidate = policyCompliantChains.get(i).getTrustChain();
+                if (isTrustChainRejected(trustChainContext, candidate)) {
+                    continue;
+                }
+                if (shortestIndex == -1) {
+                    shortestIndex = i;
+                } else {
+                    final List<EntityStatement<?>> shortest = policyCompliantChains.get(shortestIndex).getTrustChain();
+                    if (candidate.size() < shortest.size()) {
+                        shortestIndex = i;
+                    }
+                }
+            }
+            log.trace("Shortest non-rejected index {}", shortestIndex);
+            return shortestIndex == -1 ? null : policyCompliantChains.get(shortestIndex);
+        }
+        final List<EntityStatement<?>> candidate = policyCompliantChains.get(0).getTrustChain();
+        return isTrustChainRejected(trustChainContext, candidate) ? null : policyCompliantChains.get(0);
+    }
+
+    /**
+     * Checks whether the trust chain has been previously rejected in the given context.
+     * 
+     * @param trustChainContext context containing the previously rejected trust chain
+     * @param trustChain trust chain to be verified
+     * @return true if trust chain is null or previously rejected, false otherwise
+     */
+    private boolean isTrustChainRejected(@Nonnull final RelyingPartyTrustChainContext trustChainContext,
+            @Nullable final List<EntityStatement<?>> trustChain) {
+        if (trustChain == null) {
+            return true;
+        }
+        final List<List<EntityStatement<?>>> rejectedTrustChains = trustChainContext.getRejectedTrustChains();
+        return rejectedTrustChains != null && rejectedTrustChains.contains(trustChain);
+    }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultTrustChainTrustMarksParsingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultTrustChainTrustMarksParsingStrategy.java
new file mode 100644
index 0000000..f0529ba
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultTrustChainTrustMarksParsingStrategy.java
@@ -0,0 +1,128 @@
+/*
+ * 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.oidfed.profile.navigate;
+
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.payload.EntityConfigurationPayload;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+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.
+ */
+ at ThreadSafeAfterInit
+public class DefaultTrustChainTrustMarksParsingStrategy extends AbstractIdentifiableInitializableComponent
+        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) {
+        checkComponentActive();
+        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;
+            if (statement.getParsedPayload() instanceof EntityConfigurationPayload ecp) {
+                final SignedJWT statementJwt = statement.getJwt();
+                final List<Map<String, String>> rawTrustMarks = ecp.getTrustMarks();
+                log.trace("Inspecting entity statement {} with trust marks {}", statementJwt.serialize(),
+                        rawTrustMarks);
+
+                if (rawTrustMarks != null && !rawTrustMarks.isEmpty()) {
+                    log.trace("Transforming the trust mark into a JWT");
+                    final List<SignedJWT> trustMarks = rawTrustMarks
+                            .stream()
+                            .map(entry -> parseTrustMark(entry))
+                            .filter(Objects::nonNull)
+                            .toList();
+                    log.debug("Returning {} trust marks for entity {}", trustMarks.size(),
+                            statement.getSubject());
+                    result.put(statement.getSubject(), trustMarks);
+                }
+            }
+        }
+        
+        return result;
+    }
+
+    /**
+     * Parses and validates JWT from the trust mark entry.
+     * 
+     * @param trustMarkEntry trust mark entry as Strign to be parsed into a JWT
+     * @return trust mark JWT if valid, null otherwise
+     */
+    @Nullable private SignedJWT parseTrustMark(@Nullable final Map<String, String> trustMarkEntry) {
+        if (trustMarkEntry == null) {
+            return null;
+        }
+        return verifyTrustMark(trustMarkEntry.get("trust_mark"), trustMarkEntry.get("trust_mark_type"));
+    }
+
+    /**
+     * 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 trust mark JWT if valid, null otherwise
+     */
+    @Nullable private SignedJWT verifyTrustMark(@Nullable final String trustMark, @Nullable final String id) {
+        if (trustMark == null || id == null) {
+            log.trace("Could not parse trust mark {} with trust_mark_type {}", trustMark, id);
+            return null;
+        }
+        try {
+            final SignedJWT jwt = SignedJWT.parse((String) trustMark);
+            final JWTClaimsSet trustMarkClaims = jwt.getJWTClaimsSet();
+            if (StringSupport.trimOrNull(trustMarkClaims.getIssuer()) == null) {
+                log.error("Trust Mark {} is missing mandatory issuer",
+                        trustMarkClaims.getStringClaim("trust_mark_type"));
+                return null;
+            }
+            if (id.equals(trustMarkClaims.getStringClaim("trust_mark_type"))) {
+                return jwt;
+            }
+            log.error("The id {} is not matching with the trust_mark_type-claim {}", id,
+                    trustMarkClaims.getStringClaim("trust_mark_type"));
+        } catch (final ParseException e) {
+            log.error("Could not parse id-claim from the trust mark", e);
+        }
+        return null;
+    }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy.java
new file mode 100644
index 0000000..dbe785a
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy.java
@@ -0,0 +1,66 @@
+/*
+ * 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.oidfed.profile.navigate;
+
+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 net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.payload.EntityConfigurationPayload;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+
+/**
+ * Default function for fetching trusted trust mark issuers from a trust chain: they are read from the trust anchor's
+ * entity configuration.
+ */
+ at ThreadSafeAfterInit
+public class DefaultTrustChainTrustedTrustMarkIssuersLookupStrategy extends AbstractIdentifiableInitializableComponent
+    implements Function<List<EntityStatement<?>>, Map<String, List<String>>> {
+
+    /** {@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(), entry.getValue()))
+                .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, List<String>> parseTrustedIssuers(
+            @Nonnull final List<EntityStatement<?>> trustChain) {
+        final Map<String, List<String>> map =
+                trustChain.get(trustChain.size() - 1).getParsedPayload() instanceof EntityConfigurationPayload ecp ?
+                        ecp.getTrustMarkIssuers() : null;
+        return map == null ? CollectionSupport.emptyMap() : map;
+    }
+}
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy.java
new file mode 100644
index 0000000..522f033
--- /dev/null
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/profile/navigate/DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy.java
@@ -0,0 +1,61 @@
+/*
+ * 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.oidfed.profile.navigate;
+
+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 net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.payload.EntityConfigurationPayload;
+import net.shibboleth.oidfed.metadata.payload.claim.TrustMarkOwner;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Default function for fetching trusted trust mark owners from a trust chain: they are read from the trust anchor's
+ * entity configuration.
+ */
+ at ThreadSafe
+public class DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy
+    implements Function<List<EntityStatement<?>>, Map<String, TrustMarkOwner>> {
+
+    /** Class logger. */
+    @Nonnull private final Logger log =
+            LoggerFactory.getLogger(DefaultTrustChainTrustedTrustMarkOwnersLookupStrategy.class);
+
+    /** {@inheritDoc} */
+    @Nullable @Override
+    public Map<String, TrustMarkOwner> apply(@Nullable final List<EntityStatement<?>> trustChain) {
+        if (trustChain == null || trustChain.size() < 3) {
+            return null;
+        }
+        final Map<String, TrustMarkOwner> ownersClaim =
+                trustChain.get(trustChain.size() - 1).getParsedPayload() instanceof EntityConfigurationPayload ecp ?
+                        ecp.getTrustMarkOwners() : null;
+        log.debug("Parsed trust_mark_owners claim {}", ownersClaim);
+        if (ownersClaim != null) {
+            return ownersClaim;
+        }
+        log.debug("Returning empty map");
+        return CollectionSupport.emptyMap();
+    }
+}

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


More information about the commits mailing list