[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