[java-idp-plugin-oidc-op-oidfed] branch main updated: Improvements to provided trust chain validation and explicit registration
Codeberg
noreply at shibboleth.net
Thu Oct 1 07:07:11 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository java-idp-plugin-oidc-op-oidfed.
View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-oidc-op-oidfed/commit/fc092cc69856316c92b9252435311f25acc08a9f
The following commit(s) were added to refs/heads/main by this push:
new fc092cc Improvements to provided trust chain validation and explicit registration
fc092cc is described below
commit fc092cc69856316c92b9252435311f25acc08a9f
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu Oct 1 10:06:38 2026 +0300
Improvements to provided trust chain validation and explicit registration
- Run the entity statement validation filters against each item in the trust chain
- Configurable properties for leaf, anchor and subordinate statements
- idp.oidfed.op.automaticRegistration.providedTrustChain.leaf.claimsValidator
- idp.oidfed.op.automaticRegistration.providedTrustChain.anchor.claimsValidator
- idp.oidfed.op.automaticRegistration.providedTrustChain.subordinate.claimsValidator
- idp.oidfed.op.explicitRegistration.providedTrustChain.leaf.claimsValidator
- idp.oidfed.op.explicitRegistration.providedTrustChain.anchor.claimsValidator
- idp.oidfed.op.explicitRegistration.providedTrustChain.subordinate.claimsValidator
- Verify link between entity ID in the request and trust chain leaf
- Include and test the mandatory audience in the explicit registration response
---
.../impl/BuildExplicitRegistrationResponse.java | 1 +
...efaultProvidedTrustChainValidationStrategy.java | 165 ++++++++++++++++++++-
.../oidfed/metadata-lookup-ext-oidfed-beans.xml | 60 +++++++-
.../idp/flows/oidfed/register/register-beans.xml | 57 ++++++-
.../op/oidfed/conf/oidfed/oidfed-op.properties | 6 +
.../AuthorizeFlowAutomaticRegistrationTest.java | 29 ++++
.../profile/flow/oidfed/RegistrationFlowTest.java | 53 +++++--
7 files changed, 355 insertions(+), 16 deletions(-)
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildExplicitRegistrationResponse.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildExplicitRegistrationResponse.java
index 6a300a5..36aa568 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildExplicitRegistrationResponse.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildExplicitRegistrationResponse.java
@@ -185,6 +185,7 @@ public class BuildExplicitRegistrationResponse extends AbstractBuildEntityStatem
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
return false;
}
+ builder.claim("aud", clientInformation.getID().getValue());
builder.claim("metadata",
CollectionSupport.singletonMap("openid_relying_party", clientInformation.toJSONObject()));
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/logic/DefaultProvidedTrustChainValidationStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/logic/DefaultProvidedTrustChainValidationStrategy.java
index e95e53e..725e799 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/logic/DefaultProvidedTrustChainValidationStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/logic/DefaultProvidedTrustChainValidationStrategy.java
@@ -14,18 +14,31 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.logic;
+import java.time.Instant;
import java.util.List;
+import java.util.Optional;
import java.util.function.BiPredicate;
+import java.util.function.Function;
+import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import org.opensaml.messaging.context.MessageContext;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+
import net.shibboleth.oidc.metadata.cache.MetadataCache;
import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+import net.shibboleth.oidfed.metadata.EntityConfiguration;
import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.SubordinateStatement;
+import net.shibboleth.oidfed.metadata.cache.BaseExpirableStatementContainer;
+import net.shibboleth.oidfed.metadata.cache.configuration.EntityConfigurationContainer;
+import net.shibboleth.oidfed.metadata.cache.subordinate.SubordinateStatementCacheIdentifier;
+import net.shibboleth.oidfed.metadata.cache.subordinate.SubordinateStatementContainer;
import net.shibboleth.oidfed.metadata.cache.trustchain.ProvidedTrustChainContainer;
import net.shibboleth.oidfed.metadata.cache.trustchain.ProvidedTrustChainContainerCriterion;
import net.shibboleth.oidfed.metadata.cache.trustchain.TrustChainsContainer;
@@ -38,7 +51,8 @@ import net.shibboleth.shared.resolver.CriteriaSet;
/**
* Default validation strategy for provided trust chain. A {@link MetadataCache} supporting
- * {@link ProvidedTrustChainContainerCriterion} is exploited for validation.
+ * {@link ProvidedTrustChainContainerCriterion} is exploited for validation. Optionally the incoming request client ID
+ * is verified with a configurable lookup strategy. If it's set to null, the validation is skipped.
*/
public class DefaultProvidedTrustChainValidationStrategy extends AbstractIdentifiableInitializableComponent
implements BiPredicate<ProfileRequestContext, List<EntityStatement<?>>> {
@@ -49,6 +63,21 @@ public class DefaultProvidedTrustChainValidationStrategy extends AbstractIdentif
/** Metadata cache for trust chain validation. */
@NonnullAfterInit private MetadataCache<TrustChainsContainer> metadataCache;
+ /** Validation strategy for leaf entity configurations in the provided trust chain. */
+ @NonnullAfterInit private Predicate<BaseExpirableStatementContainer<?>> leafEntityConfigurationValidationStrategy;
+
+ /** Validation strategy for trust anchor entity configuration in the provided trust chain. */
+ @NonnullAfterInit private Predicate<BaseExpirableStatementContainer<?>> anchorEntityConfigurationValidationStrategy;
+
+ /** Validation strategy for subordinate statements in the provided trust chain. */
+ @NonnullAfterInit private Predicate<BaseExpirableStatementContainer<?>> subordinateStatementValidationStrategy;
+
+ /**
+ * Strategy used to obtain the client id value for validating trust chain subject. May be null: in that case the
+ * client id is not verified.
+ */
+ @Nullable private Function<MessageContext, ClientID> clientIDLookupStrategy;
+
/**
* Set the metadata cache for trust chain validation.
*
@@ -59,6 +88,53 @@ public class DefaultProvidedTrustChainValidationStrategy extends AbstractIdentif
metadataCache = Constraint.isNotNull(cache, "Metadata cache cannot be null");
}
+ /**
+ * Set the strategy used to locate the client id for validating trust chain subject. May be null: in that case the
+ * client id is not verified.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setClientIDLookupStrategy(@Nullable final Function<MessageContext, ClientID> strategy) {
+ checkSetterPreconditions();
+ clientIDLookupStrategy = strategy;
+ }
+
+ /**
+ * Set the validation strategy for leaf entity configurations in the provided trust chain.
+ *
+ * @param strategy validation strategy
+ */
+ public void setLeafEntityConfigurationValidationStrategy(
+ @Nonnull final Predicate<BaseExpirableStatementContainer<?>> strategy) {
+ checkSetterPreconditions();
+ leafEntityConfigurationValidationStrategy =
+ Constraint.isNotNull(strategy, "LeafEntityConfigurationValidationStrategy cannot be null");
+ }
+
+ /**
+ * Set the validation strategy for trust anchor entity configurations in the provided trust chain.
+ *
+ * @param strategy validation strategy
+ */
+ public void setAnchorEntityConfigurationValidationStrategy(
+ @Nonnull final Predicate<BaseExpirableStatementContainer<?>> strategy) {
+ checkSetterPreconditions();
+ anchorEntityConfigurationValidationStrategy =
+ Constraint.isNotNull(strategy, "AnchorEntityConfigurationValidationStrategy cannot be null");
+ }
+
+ /**
+ * Set the validation strategy for subordinate statements in the provided trust chain.
+ *
+ * @param strategy validation strategy
+ */
+ public void setSubordinateStatementValidationStrategy(
+ @Nonnull final Predicate<BaseExpirableStatementContainer<?>> strategy) {
+ checkSetterPreconditions();
+ subordinateStatementValidationStrategy =
+ Constraint.isNotNull(strategy, "SubordinateStatementValidationStrategy cannot be null");
+ }
+
/** {@inheritDoc} */
@Override
protected void doInitialize() throws ComponentInitializationException {
@@ -67,6 +143,15 @@ public class DefaultProvidedTrustChainValidationStrategy extends AbstractIdentif
if (metadataCache == null) {
throw new ComponentInitializationException("Metadata cache cannot be null");
}
+ if (leafEntityConfigurationValidationStrategy == null) {
+ throw new ComponentInitializationException("LeafEntityConfigurationValidationStrategy cannot be null");
+ }
+ if (anchorEntityConfigurationValidationStrategy == null) {
+ throw new ComponentInitializationException("AnchorEntityConfigurationValidationStrategy cannot be null");
+ }
+ if (subordinateStatementValidationStrategy == null) {
+ throw new ComponentInitializationException("SubordinateStatementValidationStrategy cannot be null");
+ }
}
/** {@inheritDoc} */
@@ -74,6 +159,37 @@ public class DefaultProvidedTrustChainValidationStrategy extends AbstractIdentif
public boolean test(@Nullable final ProfileRequestContext profileRequestContext,
@Nullable final List<EntityStatement<?>> trustChain) {
if (trustChain != null && !trustChain.isEmpty()) {
+ if (clientIDLookupStrategy != null) {
+ final Function<MessageContext, ClientID> nonNullStrategy = clientIDLookupStrategy;
+ assert nonNullStrategy != null;
+ final String clientId = Optional.ofNullable(profileRequestContext)
+ .map(prc -> nonNullStrategy.apply(prc.getInboundMessageContext()))
+ .map(id -> id.getValue())
+ .orElse(null);
+ final String subject = trustChain.get(0).getSubject();
+ if (!subject.equals(clientId)) {
+ log.warn("The requested clientId {} does not match the trust chain subject {}", clientId, subject);
+ return false;
+ }
+ }
+ if (!validateEntityConfiguration(trustChain.get(0), true)) {
+ log.warn("Leaf entity configuration {} did not pass the validation", trustChain.get(0).getSubject());
+ return false;
+ }
+ for (int i = 1; i < trustChain.size() - 1; i++) {
+ final EntityStatement<?> subordinate = trustChain.get(i);
+ if (!validateSubordinateStatement(subordinate)) {
+ log.warn("Subordinate statement for {} issued by {} did not pass the validation",
+ subordinate.getSubject(), subordinate.getIssuer());
+ return false;
+ }
+ }
+ if (!validateEntityConfiguration(trustChain.get(trustChain.size() - 1), false)) {
+ log.warn("Trust anchor entity configuration {} did not pass the validation",
+ trustChain.get(trustChain.size() - 1).getSubject());
+ return false;
+ }
+
final ProvidedTrustChainContainer container = new ProvidedTrustChainContainer(trustChain,
trustChain.get(0).getSubject());
final ProvidedTrustChainContainerCriterion criterion =
@@ -93,4 +209,51 @@ public class DefaultProvidedTrustChainValidationStrategy extends AbstractIdentif
return false;
}
+ /**
+ * Validates the given entity statement against leaf or trust anchor entity configuration validation strategy.
+ *
+ * @param statement entity statement to be validated
+ * @param leaf flag indicating which validation strategy to use
+ * @return true if entity statement was successfully validated, false otherwise
+ */
+ protected boolean validateEntityConfiguration(final EntityStatement<?> statement, final boolean leaf) {
+ if (statement instanceof EntityConfiguration configuration) {
+ final Instant expiration = configuration.getParsedPayload().getExpiration();
+ if (expiration == null) {
+ log.warn("Entity configuration {} is missing mandatory expiration time", statement.getSubject());
+ return false;
+ }
+ final EntityConfigurationContainer container = new EntityConfigurationContainer(
+ configuration.getSubject(), configuration, expiration, expiration);
+ return leaf ? leafEntityConfigurationValidationStrategy.test(container) :
+ anchorEntityConfigurationValidationStrategy.test(container);
+ }
+ log.warn("Entity statement {}, issued by {} is not an entity configuration", statement.getSubject(),
+ statement.getIssuer());
+ return false;
+ }
+
+ /**
+ * Validates the given entity statement against subordinate statement validation strategy.
+ *
+ * @param statement entity statement to be validated
+ * @return true if entity statement was successfully validated, false otherwise
+ */
+ protected boolean validateSubordinateStatement(final EntityStatement<?> statement) {
+ if (statement instanceof SubordinateStatement subordinate) {
+ final Instant expiration = subordinate.getParsedPayload().getExpiration();
+ if (expiration == null) {
+ log.warn("Subordinate statement {}, issued by {} is missing mandatory expiration time",
+ statement.getSubject(), statement.getIssuer());
+ return false;
+ }
+ final SubordinateStatementContainer container = new SubordinateStatementContainer(
+ new SubordinateStatementCacheIdentifier(subordinate.getIssuer(), subordinate.getSubject()),
+ subordinate, expiration, expiration);
+ return subordinateStatementValidationStrategy.test(container);
+ }
+ log.warn("Entity statement {}, issued by {} is not a subordinate statement", statement.getSubject(),
+ statement.getIssuer());
+ return false;
+ }
}
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 6264bf4..34e7b45 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
@@ -38,7 +38,65 @@
</property>
<property name="providedTrustChainValidationStrategy">
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.logic.DefaultProvidedTrustChainValidationStrategy"
- p:metadataCache-ref="#{'%{idp.oidfed.op.automaticRegistration.ProvidedTrustChainMetadataCache:DefaultAutoRegisterFetchThroughProvidedTrustChainMetadataCache}'.trim()}" />
+ p:metadataCache-ref="#{'%{idp.oidfed.op.automaticRegistration.ProvidedTrustChainMetadataCache:DefaultAutoRegisterFetchThroughProvidedTrustChainMetadataCache}'.trim()}">
+ <property name="leafEntityConfigurationValidationStrategy">
+ <bean parent="shibboleth.Conditions.Expression" c:expression="#custom.test(#input, null)"
+ p:inputType="net.shibboleth.oidfed.metadata.cache.configuration.EntityConfigurationContainer">
+ <property name="customObject">
+ <bean class="net.shibboleth.oidfed.metadata.cache.DefaultEntityStatementContentValidationCondition"
+ p:claimsValidator-ref="%{idp.oidfed.op.automaticRegistration.providedTrustChain.leaf.claimsValidator:shibboleth.oidfed.DefaultEntityConfigurationClaimsValidator}"
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
+ <property name="headerValidator">
+ <bean class="net.shibboleth.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>
+ </property>
+ </bean>
+ </property>
+ <property name="anchorEntityConfigurationValidationStrategy">
+ <bean parent="shibboleth.Conditions.Expression" c:expression="#custom.test(#input, null)"
+ p:inputType="net.shibboleth.oidfed.metadata.cache.configuration.EntityConfigurationContainer">
+ <property name="customObject">
+ <bean class="net.shibboleth.oidfed.metadata.cache.DefaultEntityStatementContentValidationCondition"
+ p:claimsValidator-ref="%{idp.oidfed.op.automaticRegistration.providedTrustChain.anchor.claimsValidator:shibboleth.oidfed.DefaultEntityConfigurationClaimsValidator}"
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
+ <property name="headerValidator">
+ <bean class="net.shibboleth.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>
+ </property>
+ </bean>
+ </property>
+ <property name="subordinateStatementValidationStrategy">
+ <bean parent="shibboleth.Conditions.Expression" c:expression="#custom.test(#input, null)"
+ p:inputType="net.shibboleth.oidfed.metadata.cache.subordinate.SubordinateStatementContainer">
+ <property name="customObject">
+ <bean class="net.shibboleth.oidfed.metadata.cache.DefaultEntityStatementContentValidationCondition"
+ p:claimsValidator-ref="%{idp.oidfed.op.automaticRegistration.providedTrustChain.subordinate.claimsValidator:shibboleth.oidfed.DefaultSubordinateStatementClaimsValidator}"
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
+ <property name="headerValidator">
+ <bean class="net.shibboleth.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>
+ </property>
+ </bean>
+ </property>
+ <property name="clientIDLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultMetadataLookupExtensionContextClientIDLookupFunction" />
+ </property>
+ </bean>
</property>
</bean>
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 935557b..00bdd9c 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
@@ -55,7 +55,62 @@
p:ignoreProvidedTrustChainCondition-ref="shibboleth.Conditions.FALSE">
<property name="providedTrustChainValidationStrategy">
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.logic.DefaultProvidedTrustChainValidationStrategy"
- p:metadataCache-ref="#{'%{idp.oidfed.op.explicitRegistration.ProvidedTrustChainMetadataCache:DefaultFetchThroughProvidedTrustChainMetadataCache}'.trim()}" />
+ p:metadataCache-ref="#{'%{idp.oidfed.op.explicitRegistration.ProvidedTrustChainMetadataCache:DefaultFetchThroughProvidedTrustChainMetadataCache}'.trim()}">
+ <property name="leafEntityConfigurationValidationStrategy">
+ <bean parent="shibboleth.Conditions.Expression" c:expression="#custom.test(#input, null)"
+ p:inputType="net.shibboleth.oidfed.metadata.cache.configuration.EntityConfigurationContainer">
+ <property name="customObject">
+ <bean class="net.shibboleth.oidfed.metadata.cache.DefaultEntityStatementContentValidationCondition"
+ p:claimsValidator-ref="%{idp.oidfed.op.explicitRegistration.providedTrustChain.leaf.claimsValidator:shibboleth.oidfed.DefaultExplicitRegistrationRequestClaimsValidator}"
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
+ <property name="headerValidator">
+ <bean class="net.shibboleth.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>
+ </property>
+ </bean>
+ </property>
+ <property name="anchorEntityConfigurationValidationStrategy">
+ <bean parent="shibboleth.Conditions.Expression" c:expression="#custom.test(#input, null)"
+ p:inputType="net.shibboleth.oidfed.metadata.cache.configuration.EntityConfigurationContainer">
+ <property name="customObject">
+ <bean class="net.shibboleth.oidfed.metadata.cache.DefaultEntityStatementContentValidationCondition"
+ p:claimsValidator-ref="%{idp.oidfed.op.explicitRegistration.providedTrustChain.anchor.claimsValidator:shibboleth.oidfed.DefaultEntityConfigurationClaimsValidator}"
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
+ <property name="headerValidator">
+ <bean class="net.shibboleth.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>
+ </property>
+ </bean>
+ </property>
+ <property name="subordinateStatementValidationStrategy">
+ <bean parent="shibboleth.Conditions.Expression" c:expression="#custom.test(#input, null)"
+ p:inputType="net.shibboleth.oidfed.metadata.cache.subordinate.SubordinateStatementContainer">
+ <property name="customObject">
+ <bean class="net.shibboleth.oidfed.metadata.cache.DefaultEntityStatementContentValidationCondition"
+ p:claimsValidator-ref="%{idp.oidfed.op.explicitRegistration.providedTrustChain.subordinate.claimsValidator:shibboleth.oidfed.DefaultSubordinateStatementClaimsValidator}"
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
+ <property name="headerValidator">
+ <bean class="net.shibboleth.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>
+ </property>
+ </bean>
+ </property>
+ </bean>
</property>
<property name="providedTrustChainResolver">
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.ExplicitClientRegistrationRequestTrustChainResolver" />
diff --git a/idp-oidfed-op-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/oidfed/conf/oidfed/oidfed-op.properties b/idp-oidfed-op-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/oidfed/conf/oidfed/oidfed-op.properties
index 650088f..e07b461 100644
--- a/idp-oidfed-op-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/oidfed/conf/oidfed/oidfed-op.properties
+++ b/idp-oidfed-op-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/oidfed/conf/oidfed/oidfed-op.properties
@@ -13,6 +13,9 @@ idp.oidfed.op.automaticRegistration.providedClaimsSetDecorationType = store
idp.oidfed.op.automaticRegistration.clientInformationManager = shibboleth.oidc.ClientInformationManager
idp.oidfed.op.automaticRegistration.FederationPolicyConstraints = shibboleth.oidfed.DefaultFederationPolicyConstraints
idp.oidfed.op.automaticRegistration.AutoRegisteredTokenClaimsSetDecorators =
+idp.oidfed.op.automaticRegistration.providedTrustChain.leaf.claimsValidator = shibboleth.oidfed.DefaultEntityConfigurationClaimsValidator
+idp.oidfed.op.automaticRegistration.providedTrustChain.anchor.claimsValidator = shibboleth.oidfed.DefaultEntityConfigurationClaimsValidator
+idp.oidfed.op.automaticRegistration.providedTrustChain.subordinate.claimsValidator = shibboleth.oidfed.DefaultSubordinateStatementClaimsValidator
# metadata-lookup-ext-oidfed-beans
idp.oidfed.op.automaticRegistration.ProvidedTrustChainMetadataCache = DefaultAutoRegisterFetchThroughProvidedTrustChainMetadataCache
@@ -64,6 +67,9 @@ idp.oidfed.op.explicitRegistration.validateRemoteJwks = shibboleth.Conditions.TR
idp.oidfed.op.explicitRegistration.clientInformationManager = shibboleth.oidc.ClientInformationManager
idp.oidfed.op.explicitRegistration.StorageService = shibboleth.StorageService
idp.oidfed.op.explicitRegistration.FederationPolicyConstraints = shibboleth.oidfed.DefaultFederationPolicyConstraints
+idp.oidfed.op.explicitRegistration.providedTrustChain.leaf.claimsValidator = shibboleth.oidfed.DefaultExplicitRegistrationRequestClaimsValidator
+idp.oidfed.op.explicitRegistration.providedTrustChain.anchor.claimsValidator = shibboleth.oidfed.DefaultEntityConfigurationClaimsValidator
+idp.oidfed.op.explicitRegistration.providedTrustChain.subordinate.claimsValidator = shibboleth.oidfed.DefaultSubordinateStatementClaimsValidator
# signed-keyset-beans
idp.service.logging.oidfedconfig = OIDFED.SignedKeyset
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
index cbaaf12..8f41d3b 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
@@ -604,6 +604,35 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
Assert.assertEquals(unwrapTrustChainFromAuthorizeCode(successResponse), List.of(clientId, anchorId));
}
+ @Test
+ public void testWithPushedValidTrustChain_signedRequestObject_differentEntityIds()
+ throws IOException, UnsupportedOperationException, URISyntaxException {
+ final String clientId = uniqueClientId();
+ final String entityId = uniqueClientId();
+ final List<String> trustChain = List.of(
+ rpEntityConfiguration(entityId),
+ subordinateStatement(entityId, Map.of("openid_relying_party",
+ new OIDCClientMetadata().toJSONObject())),
+ trustedAnchorConfiguration());
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
+ .type(new JOSEObjectType("entity-statement+jwt"))
+ .keyID(rpKey.getKeyID())
+ .customParam("trust_chain", trustChain)
+ .build();
+
+ final FlowExecutionResult result =
+ launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
+ "iss", clientId,
+ "client_id", clientId,
+ "aud", issuer,
+ "exp", Instant.now().plus(Duration.ofMinutes(5)).toEpochMilli(),
+ "jti", UUID.randomUUID(),
+ "response_type", "code",
+ "scope", "openid profile",
+ "redirect_uri", redirectUri), header, rpKey));
+ Assert.assertEquals(result.getOutcome().getId(), "ErrorView");
+ }
+
@Test
public void testWithPushedInvalidTrustChain_signedRequestObject()
throws IOException, UnsupportedOperationException, URISyntaxException {
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
index fbfa99e..b9ad3d4 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
@@ -285,13 +285,24 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
}
@Test
- public void testValidTrustChain() throws Exception {
+ public void testInvalidTrustChain_noAudienceInLeaf() throws Exception {
final String clientId = uniqueClientId();
final String trustChain = "[\"" + rpEntityConfiguration(clientId) + "\", \"" +
subordinateStatement(clientId, Map.of("openid_relying_party", new OIDCClientMetadata().toJSONObject()))
+ "\", \"" + trustedAnchorConfiguration() + "\"]";
setRequest("POST", trustChain, "application/trust-chain+json");
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, "invalid_request");
+ }
+
+ @Test
+ public void testValidTrustChain() throws Exception {
+ final String clientId = uniqueClientId();
+ final String trustChain = "[\"" + explicitRegistrationRequest(clientId) + "\", \"" +
+ subordinateStatement(clientId, Map.of("openid_relying_party", new OIDCClientMetadata().toJSONObject()))
+ + "\", \"" + trustedAnchorConfiguration() + "\"]";
+ setRequest("POST", trustChain, "application/trust-chain+json");
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
verify(federationHttpClient, times(0)).executeOpen(any(),
argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
verify(federationHttpClient, times(0)).executeOpen(any(),
@@ -304,7 +315,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
public void testValidTrustChain_resolveApi() throws Exception {
final String clientId = uniqueClientId();
request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
- final String trustChain = "[\"" + rpEntityConfiguration(clientId) + "\", \"" +
+ final String trustChain = "[\"" + explicitRegistrationRequest(clientId) + "\", \"" +
subordinateStatement(clientId, Map.of("openid_relying_party", new OIDCClientMetadata().toJSONObject()))
+ "\", \"" + trustedAnchorConfiguration() + "\"]";
setRequest("POST", trustChain, "application/trust-chain+json");
@@ -322,7 +333,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
@Test
public void testValidTrustChain_validMaxLengthInAnchor() throws Exception {
final String clientId = uniqueClientId();
- final String trustChain = "[\"" + rpEntityConfiguration(clientId) + "\", \"" +
+ final String trustChain = "[\"" + explicitRegistrationRequest(clientId) + "\", \"" +
subordinateStatement(anchorId, trustedAnchorKey, leafKey, clientId, Collections.emptyMap(),
Map.of("max_path_length", Integer.valueOf(0))) + "\", \"" + trustedAnchorConfiguration() + "\"]";
setRequest("POST", trustChain, "application/trust-chain+json");
@@ -339,7 +350,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
public void testValidTrustChain_invalidMaxLengthInAnchor() throws Exception {
final String clientId = uniqueClientId();
final String intermediateId = uniqueIntermediateId();
- final String trustChain = "[\"" + rpEntityConfiguration(clientId) + "\", \"" +
+ final String trustChain = "[\"" + explicitRegistrationRequest(clientId) + "\", \"" +
subordinateStatement(intermediateId, intermediateKey, leafKey, clientId, Collections.emptyMap(),
Collections.emptyMap()) + "\", \"" +
subordinateStatement(anchorId, trustedAnchorKey, intermediateKey, intermediateId, Collections.emptyMap(),
@@ -354,7 +365,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
final String clientId = uniqueClientId();
final String intermediateId1 = uniqueIntermediateId();
final String intermediateId2 = uniqueIntermediateId();
- final String trustChain = "[\"" + rpEntityConfiguration(clientId) + "\", \"" +
+ final String trustChain = "[\"" + explicitRegistrationRequest(clientId) + "\", \"" +
subordinateStatement(intermediateId1, intermediateKey, leafKey, clientId, Collections.emptyMap(),
Collections.emptyMap()) + "\", \"" +
subordinateStatement(intermediateId2, intermediateKey, intermediateKey, intermediateId1, Collections.emptyMap(),
@@ -369,7 +380,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
@Test
public void testValidTrustChain_validNamingConstraintInAnchor() throws Exception {
final String clientId = uniqueClientId();
- final String trustChain = "[\"" + rpEntityConfiguration(clientId) + "\", \"" +
+ final String trustChain = "[\"" + explicitRegistrationRequest(clientId) + "\", \"" +
subordinateStatement(anchorId, trustedAnchorKey, leafKey, clientId, Collections.emptyMap(),
Map.of("naming_constraints", Map.of("permitted", List.of(".federation.local")))) +
"\", \"" + trustedAnchorConfiguration() + "\"]";
@@ -386,7 +397,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
@Test
public void testValidTrustChain_invalidNamingConstraintInAnchor() throws Exception {
final String clientId = uniqueClientId();
- final String trustChain = "[\"" + rpEntityConfiguration(clientId) + "\", \"" +
+ final String trustChain = "[\"" + explicitRegistrationRequest(clientId) + "\", \"" +
subordinateStatement(anchorId, trustedAnchorKey, leafKey, clientId, Collections.emptyMap(),
Map.of("naming_constraints", Map.of("permitted", List.of(".wrongfederation.local")))) +
"\", \"" + trustedAnchorConfiguration() + "\"]";
@@ -398,7 +409,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
@Test
public void testValidTrustChain_validEntityTypeInAnchor() throws Exception {
final String clientId = uniqueClientId();
- final String trustChain = "[\"" + rpEntityConfiguration(clientId) + "\", \"" +
+ final String trustChain = "[\"" + explicitRegistrationRequest(clientId) + "\", \"" +
subordinateStatement(anchorId, trustedAnchorKey, leafKey, clientId, Collections.emptyMap(),
Map.of("allowed_entity_types", List.of("openid_relying_party"))) + "\", \"" +
trustedAnchorConfiguration() + "\"]";
@@ -415,7 +426,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
@Test
public void testValidTrustChain_invalidEmptyEntityTypeInAnchor() throws Exception {
final String clientId = uniqueClientId();
- final String trustChain = "[\"" + rpEntityConfiguration(clientId) + "\", \"" +
+ final String trustChain = "[\"" + explicitRegistrationRequest(clientId) + "\", \"" +
subordinateStatement(anchorId, trustedAnchorKey, leafKey, clientId, Collections.emptyMap(),
Map.of("allowed_entity_types", Collections.emptyList())) + "\", \"" +
trustedAnchorConfiguration() + "\"]";
@@ -427,7 +438,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
@Test
public void testValidTrustChain_invalidEntityTypeInAnchor() throws Exception {
final String clientId = uniqueClientId();
- final String trustChain = "[\"" + rpEntityConfiguration(clientId) + "\", \"" +
+ final String trustChain = "[\"" + explicitRegistrationRequest(clientId) + "\", \"" +
subordinateStatement(anchorId, trustedAnchorKey, leafKey, clientId, Collections.emptyMap(),
Map.of("allowed_entity_types", List.of("openid_provider"))) + "\", \"" +
trustedAnchorConfiguration() + "\"]";
@@ -439,7 +450,8 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
@Test
public void testInvalidTrustChain_subordinateKeyNotMatchingEntityConfiguration() throws Exception {
final String clientId = uniqueClientId();
- final String trustChain = "[\"" + rpEntityConfiguration(clientId, initializeNewJwk("RSA", 2048, "mockNewKey"))
+ final String trustChain = "[\"" + explicitRegistrationRequest(clientId,
+ initializeNewJwk("RSA", 2048, "mockNewKey"))
+ "\", \"" + subordinateStatement(clientId, Map.of("openid_relying_party",
new OIDCClientMetadata().toJSONObject())) + "\", \"" + trustedAnchorConfiguration() + "\"]";
setRequest("POST", trustChain, "application/trust-chain+json");
@@ -474,7 +486,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
final Scope scope = Scope.parse("openid profile email custom");
metadata.setScope(scope);
- final String trustChain = "[\"" + rpEntityConfiguration(clientId, metadata) + "\", \"" +
+ final String trustChain = "[\"" + explicitRegistrationRequest(clientId, metadata) + "\", \"" +
subordinateStatement(clientId, Map.of("openid_relying_party", new OIDCClientMetadata().toJSONObject()))
+ "\", \"" + trustedAnchorConfiguration() + "\"]";
setRequest("POST", trustChain, "application/trust-chain+json");
@@ -492,7 +504,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
@Test
public void testValidTrustChain_repeat() throws Exception {
final String clientId = uniqueClientId();
- final String trustChain = "[\"" + rpEntityConfiguration(clientId) + "\", \"" +
+ final String trustChain = "[\"" + explicitRegistrationRequest(clientId) + "\", \"" +
subordinateStatement(clientId, Map.of("openid_relying_party", new OIDCClientMetadata().toJSONObject()))
+ "\", \"" + trustedAnchorConfiguration() + "\"]";
for (int i = 0; i < 2; i++) {
@@ -522,6 +534,13 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
assert providedClientId != null;
Assert.assertEquals(providedClientId, expectedClientId);
Assert.assertEquals(entityStatement.getSubject(), expectedClientId);
+ try {
+ final JWTClaimsSet claimsSet = entityStatement.getJwt().getJWTClaimsSet();
+ assert claimsSet != null;
+ Assert.assertEquals(claimsSet.getAudience(), List.of(providedClientId));
+ } catch (final java.text.ParseException e) {
+ Assert.fail("Could not parse the JWT claims set", e);
+ }
assert storageService != null;
final StorageRecord<String> storageRecord =
storageService.read(BaseStorageServiceClientInformationComponent.CONTEXT_NAME, providedClientId);
@@ -587,6 +606,14 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
return explicitRegistrationRequest(clientId, leafKey, null, List.of(issuer), metadata, authorityHints);
}
+ protected String explicitRegistrationRequest(final String clientId, final JWK jwk, final String... authorityHints)
+ throws URISyntaxException {
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setRedirectionURI(new URI(redirectUri));
+ metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+ return explicitRegistrationRequest(clientId, jwk, null, List.of(issuer), metadata, authorityHints);
+ }
+
protected String explicitRegistrationRequest(final String clientId, final OIDCClientMetadata metadata,
final String... authorityHints) throws URISyntaxException {
return explicitRegistrationRequest(clientId, leafKey, null, List.of(issuer), metadata, authorityHints);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list