[java-idp-plugin-oidc-op-oidfed] 01/03: Profile configuration option to control remote trust mark validation
Codeberg
noreply at shibboleth.net
Wed May 6 12:57:04 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch dev/CACHE-REFACTOR
in repository java-idp-plugin-oidc-op-oidfed.
View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-oidc-op-oidfed/commit/567d19b477eed55752b457e859cdc37ee969de12
commit 567d19b477eed55752b457e859cdc37ee969de12
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Wed May 6 14:38:01 2026 +0300
Profile configuration option to control remote trust mark validation
- OIDFederationTrustMarkValidatingProfileConfiguration contains 'remoteTrustMarkValidation' flag
- Initially implemented in AbstractOIDFederationRegistrationProfileConfiguration, defaults to true
- RelyingPartyTrustChainContext contains a map of verifiedTrustMarkIssuers (entity configurations of trust mark issuers)
- Populated during the trust mark resolution
- Exploited by the metadata cache for Trust Mark Status
---
...ionTrustMarkValidatingProfileConfiguration.java | 10 +
.../RemoteTrustMarkValidationPredicate.java | 43 ++++
.../context/RelyingPartyTrustChainContext.java | 21 ++
.../cache/BaseExpirableStatementContainer.java | 5 +-
...tEntityStatementContentValidationCondition.java | 29 ++-
.../cache/trustmark/TrustMarkStatusContainer.java | 16 +-
...FederationRegistrationProfileConfiguration.java | 32 +++
.../op/oidfed/profile/impl/ResolveTrustMarks.java | 13 +-
.../op/oidfed/profile/impl/ValidateTrustMarks.java | 247 +++++++++++++++++++++
.../DefaultTrustMarkStatusCredentialResolver.java | 102 +++++++++
.../META-INF/net.shibboleth.idp/postconfig.xml | 42 +++-
.../oidfed/metadata-lookup-ext-oidfed-beans.xml | 5 +
.../oidfed/metadata-lookup-ext-oidfed-flow.xml | 3 +
.../idp/flows/oidfed/register/register-beans.xml | 3 +
.../flow/oidfed/AbstractFederationFlowTest.java | 23 +-
.../UserInfoFlowAutomaticRegistrationTest.java | 14 ++
16 files changed, 589 insertions(+), 19 deletions(-)
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationTrustMarkValidatingProfileConfiguration.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationTrustMarkValidatingProfileConfiguration.java
index b2ab1fe..a047dfc 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationTrustMarkValidatingProfileConfiguration.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationTrustMarkValidatingProfileConfiguration.java
@@ -61,4 +61,14 @@ public interface OIDFederationTrustMarkValidatingProfileConfiguration extends OA
@Positive @Nonnull
Duration getMaximumTrustMarkLifetime(@Nullable final ProfileRequestContext profileRequestContext);
+ /**
+ * Get whether trust marks should be remotely validated.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return whether trust marks should be remotely validated
+ */
+ @ConfigurationSetting(name="remoteTrustMarkValidation")
+ boolean isRemoteTrustMarkValidation(@Nullable final ProfileRequestContext profileRequestContext);
+
}
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/navigate/RemoteTrustMarkValidationPredicate.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/navigate/RemoteTrustMarkValidationPredicate.java
new file mode 100644
index 0000000..5ef0467
--- /dev/null
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/navigate/RemoteTrustMarkValidationPredicate.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.navigate;
+
+import java.util.Optional;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.OIDFederationTrustMarkValidatingProfileConfiguration;
+import net.shibboleth.profile.context.logic.AbstractRelyingPartyPredicate;
+
+/**
+ * A predicate implementation that forwards to
+ * {@link OIDFederationTrustMarkValidatingProfileConfiguration#isRemoteTrustMarkValidation(ProfileRequestContext)},
+ * defaults to true.
+ */
+public class RemoteTrustMarkValidationPredicate extends AbstractRelyingPartyPredicate {
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean test(@Nullable final ProfileRequestContext input) {
+ return Optional.ofNullable(getRelyingPartyContextLookupStrategy().apply(input))
+ .map(rpc -> rpc.getProfileConfig())
+ .filter(OIDFederationTrustMarkValidatingProfileConfiguration.class::isInstance)
+ .map(OIDFederationTrustMarkValidatingProfileConfiguration.class::cast)
+ .map(pc -> pc.isRemoteTrustMarkValidation(input))
+ .orElse(true);
+ }
+}
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/RelyingPartyTrustChainContext.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/RelyingPartyTrustChainContext.java
index c850ea7..5677288 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/RelyingPartyTrustChainContext.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/RelyingPartyTrustChainContext.java
@@ -15,6 +15,7 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context;
import java.time.Instant;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -27,6 +28,7 @@ import com.nimbusds.jwt.SignedJWT;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.TrustedRemoteResolverEntity;
+import net.shibboleth.shared.annotation.constraint.Live;
/**
* Subcontext carrying information for trust chains related to a relying party.
@@ -54,12 +56,22 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
/** Verified trust marks for the selected trust chain. */
@Nullable private Map<String, List<SignedJWT>> verifiedTrustMarks;
+ /** Verified trust mark issuers. */
+ @Nonnull @Live private Map<String, EntityStatement<?>> verifiedTrustMarkIssuers;
+
/** All previously selected but rejected trust chains. */
@Nullable private List<List<EntityStatement<?>>> rejectedTrustChains;
/** All already attempted trusted remote resolver entities. */
@Nullable private List<TrustedRemoteResolverEntity> attemptedTrustedRemoteResolverEntities;
+ /**
+ * Constructor.
+ */
+ public RelyingPartyTrustChainContext() {
+ verifiedTrustMarkIssuers = new HashMap<>();
+ }
+
/**
* Get the trust chain provided within the request.
*
@@ -213,6 +225,15 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
return this;
}
+ /**
+ * Get the verified trust mark issuers.
+ *
+ * @return verified trust mark issuers
+ */
+ @Nonnull @Live public Map<String, EntityStatement<?>> getVerifiedTrustMarkIssuers() {
+ return verifiedTrustMarkIssuers;
+ }
+
/**
* Get the previously selected but rejected trust chains for the relying party.
*
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/BaseExpirableStatementContainer.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/BaseExpirableStatementContainer.java
index d5bb2dd..d975cfa 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/BaseExpirableStatementContainer.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/BaseExpirableStatementContainer.java
@@ -21,7 +21,7 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.BaseJWTWrapper;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.BaseExpirableSubjectPayload;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.BasePayload;
import net.shibboleth.shared.logic.Constraint;
/**
@@ -29,8 +29,7 @@ import net.shibboleth.shared.logic.Constraint;
*
* @param <T> wrapped statement
*/
-public abstract class BaseExpirableStatementContainer
- <T extends BaseJWTWrapper<? extends BaseExpirableSubjectPayload>>
+public abstract class BaseExpirableStatementContainer<T extends BaseJWTWrapper<? extends BasePayload>>
extends BaseExpirableMetadataContainer
implements Serializable {
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultEntityStatementContentValidationCondition.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultEntityStatementContentValidationCondition.java
index c071569..8d9a03a 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultEntityStatementContentValidationCondition.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultEntityStatementContentValidationCondition.java
@@ -28,6 +28,8 @@ import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.BaseJWTWrapper;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.BasePayload;
import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
import net.shibboleth.oidc.jwt.claims.JWTValidationException;
import net.shibboleth.oidc.metadata.filter.MetadataFilterContext;
@@ -64,6 +66,10 @@ public class DefaultEntityStatementContentValidationCondition
/** Supplier for the Current HTTP request, if available. */
@NonnullAfterInit private NonnullSupplier<HttpServletRequest> httpServletRequestSupplier;
+ @NonnullAfterInit
+ private BiPredicate<BaseExpirableStatementContainer<?>, BaseJWTWrapper<? extends BasePayload>>
+ containerIdValidationStrategy;
+
/**
* Set the claims validator to use for validating the entity statement claims.
*
@@ -91,7 +97,20 @@ public class DefaultEntityStatementContentValidationCondition
*/
public void setHttpServletRequestSupplier(@Nonnull final NonnullSupplier<HttpServletRequest> requestSupplier) {
checkSetterPreconditions();
- httpServletRequestSupplier = Constraint.isNotNull(requestSupplier, "Http servlet request supplier cannot be null");
+ httpServletRequestSupplier = Constraint.isNotNull(requestSupplier,
+ "Http servlet request supplier cannot be null");
+ }
+
+ /**
+ * Set the validation strategy for the container identifier.
+ *
+ * @param strategy validation strategy
+ */
+ public void setContainerIdValidationStrategy(@Nonnull final
+ BiPredicate<BaseExpirableStatementContainer<?>, BaseJWTWrapper<? extends BasePayload>> strategy) {
+ checkSetterPreconditions();
+ containerIdValidationStrategy = Constraint.isNotNull(strategy,
+ "Container ID validation strategy cannot be null");
}
/** {@inheritDoc} */
@@ -107,6 +126,9 @@ public class DefaultEntityStatementContentValidationCondition
if (httpServletRequestSupplier == null) {
throw new ComponentInitializationException("Http servlet request supplier cannot be null");
}
+ if (containerIdValidationStrategy == null) {
+ throw new ComponentInitializationException("Container ID validation strategy cannot be null");
+ }
}
/** {@inheritDoc} */
@@ -121,9 +143,8 @@ public class DefaultEntityStatementContentValidationCondition
final var wrapper = responseContainer.getStatement();
assert wrapper != null;
- if (!responseContainer.getEntityId().equals(wrapper.getParsedPayload().getSubject())) {
- log.warn("Entity statement subject {} does not match with the requested entity ID {}",
- wrapper.getParsedPayload().getSubject(), responseContainer.getEntityId());
+ if (!containerIdValidationStrategy.test(responseContainer, wrapper)) {
+ log.warn("Container ID validation failed with the requested ID {}", responseContainer.getEntityId());
return false;
}
final ProfileRequestContext profileRequestContext =
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/trustmark/TrustMarkStatusContainer.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/trustmark/TrustMarkStatusContainer.java
index f461aa7..0193772 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/trustmark/TrustMarkStatusContainer.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/trustmark/TrustMarkStatusContainer.java
@@ -15,20 +15,24 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.trustmark;
import java.io.Serializable;
+import java.text.ParseException;
import java.time.Instant;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import com.nimbusds.jwt.JWTClaimsSet;
+
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.TrustMarkStatus;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.BaseExpirableStatementContainer;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.ConstraintViolationException;
/**
* A container class for metadata caches carrying ID and statement details related to Trust Mark status.
*/
-public class TrustMarkStatusContainer extends BaseExpirableStatementContainer implements Serializable {
+public class TrustMarkStatusContainer extends BaseExpirableStatementContainer<TrustMarkStatus> implements Serializable {
/** Serial version UID. */
private static final long serialVersionUID = 756269369356884270L;
@@ -62,7 +66,15 @@ public class TrustMarkStatusContainer extends BaseExpirableStatementContainer im
/** {@inheritDoc} */
@Nonnull @NotEmpty public String getEntityId() {
- return identifier.getEndpoint(); //TODO: revisit
+ try {
+ final JWTClaimsSet claimsSet = identifier.getTrustMark().getJWTClaimsSet();
+ if (claimsSet != null) {
+ return Constraint.isNotEmpty(claimsSet.getIssuer(), "Issuer of the trust mark cannot be empty");
+ }
+ } catch (final ParseException e) {
+ // no op
+ }
+ throw new ConstraintViolationException("Could not resolve issuer of the trust mark");
}
}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/AbstractOIDFederationRegistrationProfileConfiguration.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/AbstractOIDFederationRegistrationProfileConfiguration.java
index 2547df6..2ac713e 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/AbstractOIDFederationRegistrationProfileConfiguration.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/AbstractOIDFederationRegistrationProfileConfiguration.java
@@ -18,6 +18,7 @@ import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
+import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -36,6 +37,7 @@ import net.shibboleth.shared.annotation.constraint.Unmodifiable;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.logic.FunctionSupport;
+import net.shibboleth.shared.logic.PredicateSupport;
/**
* Abstract implementation class for profile configurations related OpenID Federation client registration.
@@ -52,6 +54,9 @@ public class AbstractOIDFederationRegistrationProfileConfiguration extends Abstr
/** Lookup function to supply maximum trust mark lifetime. */
@Nonnull private Function<ProfileRequestContext,Duration> maximumTrustMarkLifetimeLookupStrategy;
+ /** Whether trust marks should be remotely validated. */
+ @Nonnull private Predicate<ProfileRequestContext> remoteTrustMarkValidationCondition;
+
/**
* Constructor.
*
@@ -63,6 +68,7 @@ public class AbstractOIDFederationRegistrationProfileConfiguration extends Abstr
localMetadataPolicyLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyMap());
mandatoryTrustMarksLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
maximumTrustMarkLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofDays(365));
+ remoteTrustMarkValidationCondition = PredicateSupport.alwaysTrue();
}
/** {@inheritDoc} */
@@ -161,4 +167,30 @@ public class AbstractOIDFederationRegistrationProfileConfiguration extends Abstr
@Nullable final Function<ProfileRequestContext,Duration> strategy) {
maximumTrustMarkLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
}
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isRemoteTrustMarkValidation(@Nullable final ProfileRequestContext profileRequestContext) {
+ return remoteTrustMarkValidationCondition.test(profileRequestContext);
+ }
+
+ /**
+ * Set whether trust marks should be remotely validated.
+ *
+ * @param flag flag to set
+ */
+ public void setRemoteTrustMarkValidation(final boolean flag) {
+ remoteTrustMarkValidationCondition = flag ? PredicateSupport.alwaysTrue() : PredicateSupport.alwaysFalse();
+ }
+
+ /**
+ * Set condition for whether trust marks should be remotely validated.
+ *
+ * @param condition condition to set
+ */
+ public void setRemoteTrustMarkValidationPredicate(@Nonnull final Predicate<ProfileRequestContext> condition) {
+ remoteTrustMarkValidationCondition = Constraint.isNotNull(condition, "Condition cannot be null");
+ }
+
+
}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
index 9b2feff..9ea5e9b 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustMarks.java
@@ -52,6 +52,7 @@ 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.shared.annotation.constraint.Live;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
import net.shibboleth.shared.collection.CollectionSupport;
@@ -361,15 +362,16 @@ public class ResolveTrustMarks extends AbstractProfileAction {
trustMarks.stream()
.filter(entry -> onlyTrustedIssuers ?
checkTrustedIssuer(entry, trustedIssuers, trustedOwners) : true)
- .filter(entry -> verifyTrustMark(entry, trustedOwners, profileRequestContext))
+ .filter(entry -> verifyTrustMark(entry, trustedOwners, profileRequestContext,
+ trustChainContext.getVerifiedTrustMarkIssuers()))
.filter(Objects::nonNull)
- .toList());
+ .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))
- .toList()));
+ .collect(Collectors.toList())));
log.debug("{} The following trust marks are validated: {}", getLogPrefix(), verifiedTrustMarkIds);
trustChainContext.setVerifiedTrustMarkIds(verifiedTrustMarkIds);
}
@@ -450,11 +452,13 @@ public class ResolveTrustMarks extends AbstractProfileAction {
* @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 final ProfileRequestContext profileRequestContext,
+ @Nonnull @Live final Map<String, EntityStatement<?>> trustedTrustMarkIssuers) {
if (jwt == null) {
return false;
}
@@ -483,6 +487,7 @@ public class ResolveTrustMarks extends AbstractProfileAction {
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)) {
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateTrustMarks.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateTrustMarks.java
new file mode 100644
index 0000000..0cb57ae
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/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.idp.plugin.oidc.op.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.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.trustmark.TrustMarkStatusCacheIdentifier;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.trustmark.TrustMarkStatusContainer;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.trustmark.TrustMarkStatusIdentifierCriterion;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.navigate.RemoteTrustMarkValidationPredicate;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.RelyingPartyTrustChainContext;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.VerifiedTrustChain;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.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 setRemoteTrustMarkValidationPredicate(@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/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultTrustMarkStatusCredentialResolver.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultTrustMarkStatusCredentialResolver.java
new file mode 100644
index 0000000..ce4fdd1
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/security/credential/DefaultTrustMarkStatusCredentialResolver.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.credential.Credential;
+import org.slf4j.Logger;
+
+import com.nimbusds.jose.jwk.JWKSet;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.BasePayload;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.IssuerEntityStatementCriterion;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.SubjectStatementCriterion;
+import net.shibboleth.oidc.security.credential.impl.BasicJOSEObjectCredentialResolver;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+
+/**
+ * Resolves credentials from the jwks-claim located in the signed JWT (issuer of a trust mark status) payload.
+ * First, a JWT is fetched via {@link SubjectStatementCriterion}. Its issuer must match with the entity
+ * statement fetched via {@link IssuerEntityStatementCriterion}.
+ */
+public class DefaultTrustMarkStatusCredentialResolver extends BasicJOSEObjectCredentialResolver {
+
+ /** Logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultTrustMarkStatusCredentialResolver.class);
+
+ /** {@inheritDoc} */
+ @Override
+ protected Iterable<Credential> resolveFromSource(@Nullable final CriteriaSet criteriaSet) throws ResolverException {
+ if (criteriaSet == null) {
+ throw new ResolverException("No criteria set supplied");
+ }
+
+ final List<Credential> result = parseJwkSet(criteriaSet).getKeys().stream()
+ .filter(Objects::nonNull)
+ .map(jwk -> jwk != null ? buildJWKCredential(jwk, null) : null)
+ .filter(Objects::nonNull)
+ .map(Credential.class::cast)
+ .toList();
+ assert result != null;
+ return result;
+ }
+
+ /**
+ * Parses the JWKSet from the given criteria set.
+ *
+ * @param criteriaSet criteria set containing source JWT for the JWKSet
+ * @return the JWKSet parsed from the JWT payload
+ * @throws ResolverException if the JWKSet could not be parsed or found
+ */
+ @Nonnull protected JWKSet parseJwkSet(@Nonnull final CriteriaSet criteriaSet) throws ResolverException {
+ final BasePayload subjectPayload = Optional.ofNullable(criteriaSet.get(SubjectStatementCriterion.class))
+ .map(criterion -> criterion.getValue().getParsedPayload()).orElse(null);
+ if (subjectPayload == null) {
+ log.debug("No mandatory criteria supplied for resolving subject, resolver could not process");
+ throw new ResolverException(
+ "Credential criteria set did not contain criterion to resolve subject");
+ }
+ final IssuerEntityStatementCriterion issuerCriterion =
+ criteriaSet.get(IssuerEntityStatementCriterion.class);
+ if (issuerCriterion == null) {
+ log.debug("No mandatory IssuerEntityStatementCriterion supplied, resolver could not process");
+ throw new ResolverException(
+ "Credential criteria set did not contain an instance of IssuerEntityStatementCriterion");
+ }
+ final EntityStatement<?> issuerStatement = issuerCriterion.getValue();
+ if (!issuerStatement.getSubject().equals(subjectPayload.getIssuer())) {
+ throw new ResolverException("Credential criteria do not match for subject and issuer");
+ }
+ if (!issuerStatement.getSubject().equals(issuerStatement.getIssuer())) {
+ throw new ResolverException("Issuer entity statement is not self signed");
+ }
+ final JWKSet jwks = issuerStatement.getParsedPayload().getJwks();
+
+ if (jwks == null || jwks.isEmpty()) {
+ throw new ResolverException("Could not parse mandatory jwks");
+ }
+ return jwks;
+ }
+
+}
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 23aaaba..b43cf42 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -148,6 +148,9 @@
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultTrustChainHeaderValidationCondition"
p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper" />
</property>
+ <property name="containerIdValidationStrategy">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="#input1?.getEntityId()?.equals(#input2?.getParsedPayload()?.getSubject())"/>
+ </property>
</bean>
</util:list>
</property>
@@ -250,6 +253,9 @@
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultTrustChainHeaderValidationCondition"
p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper" />
</property>
+ <property name="containerIdValidationStrategy">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="#input1?.getEntityId()?.equals(#input2?.getParsedPayload()?.getSubject())"/>
+ </property>
</bean>
</util:list>
</property>
@@ -460,6 +466,9 @@
<property name="headerValidator">
<bean parent="shibboleth.BiConditions.Expression" c:expression="true"/>
</property>
+ <property name="containerIdValidationStrategy">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="#input1?.getEntityId()?.equals(#input2?.getParsedPayload()?.getSubject())"/>
+ </property>
</bean>
</util:list>
</property>
@@ -572,13 +581,20 @@
</property>
</bean>
+ <bean id="shibboleth.oidfed.TrustMarkStatusMetadataCache" parent="shibboleth.oidc.CacheBuilder">
+ <constructor-arg>
+ <bean p:cacheId="DefaultTrustMarkStatusMetadataCache" parent="shibboleth.oidfed.TrustMarkStatusMetadataCacheBuilderSpec"
+ p:cleanupTaskInterval="PT30S"/>
+ </constructor-arg>
+ </bean>
+
<bean id="shibboleth.oidfed.TrustMarkStatusMetadataCacheBuilderSpec"
class="net.shibboleth.oidc.metadata.cache.impl.DynamicMetadataCacheBuilderSpec"
p:minCacheDuration="%{idp.oidfed.cache.trustMarkStatus.maxRefreshDelay:PT60S}"
p:maxCacheDuration="%{idp.oidfed.cache.trustMarkStatus.maxRefreshDelay:PT30M}"
p:metadataExpirationTimeStrategy-ref="DefaultResponseContainerExpirationTimeStrategy">
<property name="identifierExtractionStrategy">
- <bean parent="shibboleth.Functions.Expression" c:expression="#input?.getRequest().getTrustMark().serialize()"/>
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.getIdentifier()"/>
</property>
<property name="criteriaToIdentifierStrategy">
<bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.trustmark.TrustMarkStatusIdentifierCriterion))?.getIdentifier()"/>
@@ -588,8 +604,17 @@
p:customFilterStrategies="#{getObject('%{idp.oidfed.cache.trustMarkStatus.customFilterStrategies:}'.trim())}">
<property name="validationConditions">
<util:list value-type="java.util.function.BiPredicate">
- <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementSignatureValidationCondition"
- p:trustEngine-ref="shibboleth.oidfed.DefaultSubordinateStatementTrustEngine">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementSignatureValidationCondition">
+ <property name="trustEngine">
+ <bean class="net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine">
+ <constructor-arg index="0">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultTrustMarkStatusCredentialResolver" />
+ </constructor-arg>
+ <constructor-arg index="1">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadJOSEObjectCredentialResolver" />
+ </constructor-arg>
+ </bean>
+ </property>
<property name="criteriaSetLookupStrategy">
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.trustmark.DefaultTrustMarkValidationCriteriaSetLookupFunction"
p:trustChainCache-ref="shibboleth.oidfed.TrustChainMetadataCache"/>
@@ -601,6 +626,9 @@
<property name="headerValidator">
<bean parent="shibboleth.BiConditions.Expression" c:expression="true"/>
</property>
+ <property name="containerIdValidationStrategy">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="#input1?.getEntityId()?.equals(#input2?.getParsedPayload()?.getIssuer())"/>
+ </property>
</bean>
</util:list>
</property>
@@ -636,7 +664,7 @@
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultClientAuthenticationLookupFunction"
p:clientAuthenticationDecoratorsLookupStrategy-ref="shibboleth.oidfed.cache.DefaultEndpointAuthenticationFunctions">
<property name="supportedAuthenticationMethodsLookupStrategy">
- <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.FederationEndpointEntityStatementCriterion))?.getValue().getParsedPayload().getMetadata()?.getFederationEntityMetadata()?.get('federation_trust_mark_status_endpoint_auth_methods')" />
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.FederationEndpointEntityStatementCriterion))?.getValue()?.getParsedPayload()?.getMetadata()?.getFederationEntityMetadata()?.get('federation_trust_mark_status_endpoint_auth_methods')" />
</property>
</bean>
</property>
@@ -700,6 +728,9 @@
<property name="headerValidator">
<bean parent="shibboleth.BiConditions.Expression" c:expression="true"/>
</property>
+ <property name="containerIdValidationStrategy">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="#input1?.getEntityId()?.equals(#input2?.getParsedPayload()?.getSubject())"/>
+ </property>
</bean>
</util:list>
</property>
@@ -830,6 +861,9 @@
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultTrustChainHeaderValidationCondition"
p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper" />
</property>
+ <property name="containerIdValidationStrategy">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="#input1?.getEntityId()?.equals(#input2?.getParsedPayload()?.getSubject())"/>
+ </property>
</bean>
</util:list>
</property>
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-beans.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-beans.xml
index 1419a94..0181ddc 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-beans.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-beans.xml
@@ -189,6 +189,11 @@
</property>
</bean>
+ <bean id="ValidateTrustMarks" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ValidateTrustMarks"
+ scope="prototype"
+ p:trustMarkStatusCache-ref="#{'%{idp.oidfed.authorize.TrustMarkStatusMetadataCache:shibboleth.oidfed.TrustMarkStatusMetadataCache}'.trim()}">
+ </bean>
+
<bean id="ValidateAutomaticRegistrationProfileConfiguration"
class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ValidateAutomaticRegistrationProfileConfiguration"
scope="prototype">
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-flow.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-flow.xml
index 60ecb5d..ac07f2d 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-flow.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-flow.xml
@@ -56,6 +56,7 @@
<evaluate expression="ResolveTrustMarks" />
<evaluate expression="SelectAutomaticRegistrationRelyingPartyConfiguration" />
<evaluate expression="SelectAutomaticRegistrationProfileConfiguration" />
+ <evaluate expression="ValidateTrustMarks" />
<evaluate expression="ValidateAutomaticRegistrationProfileConfiguration" />
<evaluate expression="InitializeRelyingPartyContext" />
<evaluate expression="'proceed'" />
@@ -66,6 +67,7 @@
</action-state>
<end-state id="proceed"/>
+ <end-state id="InvalidMessageContext"/>
<end-state id="InvalidMetadataPolicy"/>
<end-state id="InvalidMetadataAgainstPolicy"/>
<end-state id="NoTrustChainsResolved" />
@@ -74,6 +76,7 @@
<end-state id="HandleError"/>
<global-transitions>
+ <transition on="InvalidMessageContext" to="InvalidMessageContext" />
<transition on="InvalidMetadataPolicy" to="InvalidMetadataPolicy" />
<transition on="InvalidMetadataAgainstPolicy" to="InvalidMetadataAgainstPolicy" />
<transition on="NoTrustChainsResolved" to="NoTrustChainsResolved" />
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
index 79cdd84..aa2c5e6 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
@@ -152,6 +152,9 @@
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultTrustChainHeaderValidationCondition"
p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper" />
</property>
+ <property name="containerIdValidationStrategy">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="#input1?.getEntityId()?.equals(#input2?.getParsedPayload()?.getSubject())"/>
+ </property>
</bean>
</util:list>
</property>
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
index b31d518..47432de 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
@@ -101,6 +101,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
protected final String anchorResolveEndpoint = anchorId + "/resolve";
protected final String trustMarkIssuerId = "https://trust-mark-issuer.federation.local";
protected final String trustMarkEndpoint = "https://trust-mark-issuer.federation.local/issue";
+ protected final String trustMarkStatusEndpoint = "https://trust-mark-issuer.federation.local/status";
protected final String issuer = "https://op.example.org";
protected JWK rpKey;
@@ -312,7 +313,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
.claim("authority_hints", authorityHints == null || authorityHints.length == 0 ?
new String[] { anchorId } : authorityHints)
.claim("metadata", Map.of("federation_entity", Map.of("trust_mark_endpoint",
- trustMarkEndpoint)))
+ trustMarkEndpoint, "federation_trust_mark_status_endpoint", trustMarkStatusEndpoint)))
.build();
final EntityStatement<?> rpConfiguration =
TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, trustMarkIssuerKey, claimsSet);
@@ -472,9 +473,16 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
protected String rpResolveEntityResponse(final String clientId, final Map<String, Object> metadata,
final List<Map<String, String>> trustMarks) {
+ final OIDCClientMetadata rpMetadata = new OIDCClientMetadata();
+ try {
+ rpMetadata.setRedirectionURI(new URI(redirectUri));
+ } catch (URISyntaxException e) {
+ Assert.fail("Could not initialize an URI", e);
+ }
+ rpMetadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
final List<String> trustChain;
try {
- trustChain = List.of(rpEntityConfiguration(clientId),
+ trustChain = List.of(rpEntityConfiguration(clientId, rpMetadata, trustMarks, leafKey),
subordinateStatement(clientId, Map.of("openid_relying_party",
new OIDCClientMetadata().toJSONObject())),
trustedAnchorConfiguration());
@@ -795,6 +803,17 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
.build();
}
+ protected String trustMarkStatusResponse(final String issuer, final String trustMark, final String status,
+ final JWK signerKey) {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(issuer)
+ .issueTime(Date.from(Instant.now()))
+ .claim("trust_mark", trustMark)
+ .claim("status", status)
+ .build();
+ return TrustChainTestUtil.signedJwt(JWSAlgorithm.RS256, signerKey,
+ "trust-mark-status-response+jwt", claimsSet).serialize();
+ }
+
protected class RequestUriMatcher implements ArgumentMatcher<ClassicHttpRequest> {
@Nonnull private final String uri;
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java
index a041a57..48a6314 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java
@@ -104,6 +104,8 @@ public class UserInfoFlowAutomaticRegistrationTest extends AbstractFederationFlo
mapResponse(subordinateStatementUrl(anchorFetchEndpoint, trustMarkIssuerId),
mockResponse(subordinateStatement(trustMarkIssuerId,
Map.of("federation_entity", CollectionSupport.emptyMap()), trustMarkIssuerKey)));
+ mapResponse(trustMarkStatusEndpoint, mockResponse(200, "application/trust-mark-status-response+jwt",
+ trustMarkStatusResponse(trustMarkIssuerId, trustMark, "active", trustMarkIssuerKey)));
} catch (UnsupportedOperationException | IOException e) {
Assert.fail("Could not initialize mock HTTP client", e);
}
@@ -148,6 +150,18 @@ public class UserInfoFlowAutomaticRegistrationTest extends AbstractFederationFlo
rpResolveEntityConfigureMockHttpClient(clientId, metadata, List.of(Map.of(
"trust_mark_type", "https://example.org/email-allowing-trust-mark",
"trust_mark", trustMark)));
+ try {
+ mapResponse(entityConfigurationUrl(trustMarkIssuerId),
+ mockResponse(trustMarkIssuerConfiguration(trustMarkIssuerId)));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, trustMarkIssuerId),
+ mockResponse(subordinateStatement(trustMarkIssuerId,
+ Map.of("federation_entity", CollectionSupport.emptyMap()), trustMarkIssuerKey)));
+ mapResponse(trustMarkStatusEndpoint, mockResponse(200, "application/trust-mark-status-response+jwt",
+ trustMarkStatusResponse(trustMarkIssuerId, trustMark, "active", trustMarkIssuerKey)));
+ } catch (UnsupportedOperationException | IOException e) {
+ Assert.fail("Could not initialize mock HTTP client", e);
+ }
+
request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
final BearerAccessToken token = buildToken(clientId, List.of(clientId, anchorId));
request.addHeader("Authorization", token.toAuthorizationHeader());
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list