[java-idp-oidc] 02/02: JOIDC-222 - Support for OpenID Federation
Henri Mikkonen
henri.mikkonen at iki.fi
Wed Mar 26 15:05:09 UTC 2025
This is an automated email from the git hooks/post-receive script.
hjmikkon pushed a commit to branch dev/JOIDC-222
in repository java-idp-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=029aee73e64287715da97fb710b4c68de47e170a
commit 029aee73e64287715da97fb710b4c68de47e170a
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Wed Mar 26 17:03:50 2025 +0200
JOIDC-222 - Support for OpenID Federation
https://shibboleth.atlassian.net/browse/JOIDC-222
- Initial support for "pushed" trust chain validation in the explicit registration flow
- Trust chain resolution process (via metadata caches) is avoided
- Trust anchor needs to be locally trusted
---
...ctTrustEngineSignatureValidationComponent.java} | 41 +---
...StatementSignatureValidationFilterStrategy.java | 65 +-----
...efaultProvidedTrustChainValidationStrategy.java | 111 +++++++++
.../profile/impl/ValidateProvidedTrustChain.java | 253 +++++++++++++++++++++
...egistrationRequestTrustChainLookupFunction.java | 50 ++++
.../idp/flows/oidfed/register/register-beans.xml | 35 +++
.../idp/flows/oidfed/register/register-flow.xml | 13 +-
.../profile/flow/oidfed/RegistrationFlowTest.java | 35 +++
8 files changed, 511 insertions(+), 92 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultEntityStatementSignatureValidationFilterStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/AbstractTrustEngineSignatureValidationComponent.java
similarity index 70%
copy from idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultEntityStatementSignatureValidationFilterStrategy.java
copy to idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/AbstractTrustEngineSignatureValidationComponent.java
index a40a1279..45c847cf 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultEntityStatementSignatureValidationFilterStrategy.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/AbstractTrustEngineSignatureValidationComponent.java
@@ -14,8 +14,6 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata;
-import java.util.function.BiFunction;
-
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -26,9 +24,7 @@ import org.slf4j.Logger;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
-import net.shibboleth.oidc.metadata.filter.MetadataFilterContext;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.logic.Constraint;
@@ -36,16 +32,12 @@ import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.shared.resolver.CriteriaSet;
/**
- * Default signature validating filter for entity statement. The signature validation is performed via configurable
- * {@link TrustEngine}.
+ * Abstract component performing signature validation via {@link TrustEngine}.
*/
- at ThreadSafeAfterInit
-public class DefaultEntityStatementSignatureValidationFilterStrategy extends AbstractIdentifiableInitializableComponent
- implements BiFunction<EntityStatement, MetadataFilterContext, EntityStatement> {
+public class AbstractTrustEngineSignatureValidationComponent extends AbstractIdentifiableInitializableComponent {
/** Class logger. */
- @Nonnull private Logger log =
- LoggerFactory.getLogger(DefaultEntityStatementSignatureValidationFilterStrategy.class);
+ @Nonnull private Logger log = LoggerFactory.getLogger(AbstractTrustEngineSignatureValidationComponent.class);
/** Trust engine used to validate a signature. */
@NonnullAfterInit private TrustEngine<SignedJWT> trustEngine;
@@ -82,24 +74,14 @@ public class DefaultEntityStatementSignatureValidationFilterStrategy extends Abs
}
}
- /** {@inheritDoc} */
- @Override @Nullable
- public EntityStatement apply(@Nullable final EntityStatement entityStatement,
- @Nullable final MetadataFilterContext filterContext) {
- checkComponentActive();
- if (entityStatement == null) {
- return null;
- }
-
- final String entityId = entityStatement.getEntityID().getValue();
- log.trace("Starting signature validation of entity statement for {}", entityId);
- final CriteriaSet criteria = new CriteriaSet(new SubjectEntityStatementCriterion(entityStatement));
- if (validateStatement(entityStatement, criteria, entityId)) {
- return entityStatement;
- }
- return null;
- }
-
+ /**
+ * Validates the given entity statement via trust engine and the given criteria.
+ *
+ * @param entityStatement the entity statement to be validated
+ * @param criteria the criteria (expanded with the optional default criteria)
+ * @param entityId the entity ID used for logging
+ * @return true if validation was successful, false otherwise
+ */
protected boolean validateStatement(@Nonnull final EntityStatement entityStatement,
@Nonnull final CriteriaSet criteria, @Nullable final String entityId) {
if (defaultCriteria != null && !defaultCriteria.isEmpty()) {
@@ -118,4 +100,5 @@ public class DefaultEntityStatementSignatureValidationFilterStrategy extends Abs
log.warn("Trust Engine validation failed for {}", entityId);
return false;
}
+
}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultEntityStatementSignatureValidationFilterStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultEntityStatementSignatureValidationFilterStrategy.java
index a40a1279..a3a06e46 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultEntityStatementSignatureValidationFilterStrategy.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultEntityStatementSignatureValidationFilterStrategy.java
@@ -19,19 +19,13 @@ import java.util.function.BiFunction;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import org.opensaml.security.SecurityException;
import org.opensaml.security.trust.TrustEngine;
import org.slf4j.Logger;
-import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
import net.shibboleth.oidc.metadata.filter.MetadataFilterContext;
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
-import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.shared.resolver.CriteriaSet;
@@ -40,48 +34,14 @@ import net.shibboleth.shared.resolver.CriteriaSet;
* {@link TrustEngine}.
*/
@ThreadSafeAfterInit
-public class DefaultEntityStatementSignatureValidationFilterStrategy extends AbstractIdentifiableInitializableComponent
- implements BiFunction<EntityStatement, MetadataFilterContext, EntityStatement> {
+public class DefaultEntityStatementSignatureValidationFilterStrategy
+ extends AbstractTrustEngineSignatureValidationComponent
+ implements BiFunction<EntityStatement, MetadataFilterContext, EntityStatement> {
/** Class logger. */
@Nonnull private Logger log =
LoggerFactory.getLogger(DefaultEntityStatementSignatureValidationFilterStrategy.class);
- /** Trust engine used to validate a signature. */
- @NonnullAfterInit private TrustEngine<SignedJWT> trustEngine;
-
- /** Set of externally specified default criteria for input to the trust engine. */
- @Nullable private CriteriaSet defaultCriteria;
-
- /**
- * 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 the optional set of default criteria used as input to the trust engine.
- *
- * @param criteria criteria set to use
- */
- public void setDefaultCriteria(@Nullable final CriteriaSet criteria) {
- checkSetterPreconditions();
- defaultCriteria = criteria;
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
- if (trustEngine == null) {
- throw new ComponentInitializationException("Trust Engine cannot be null");
- }
- }
-
/** {@inheritDoc} */
@Override @Nullable
public EntityStatement apply(@Nullable final EntityStatement entityStatement,
@@ -99,23 +59,4 @@ public class DefaultEntityStatementSignatureValidationFilterStrategy extends Abs
}
return null;
}
-
- protected boolean validateStatement(@Nonnull final EntityStatement entityStatement,
- @Nonnull final CriteriaSet criteria, @Nullable final String entityId) {
- if (defaultCriteria != null && !defaultCriteria.isEmpty()) {
- criteria.addAll(defaultCriteria);
- }
- try {
- final SignedJWT jwt = entityStatement.getSignedStatement();
- assert jwt != null;
- if (trustEngine.validate(jwt, criteria)) {
- log.debug("Successfully validated entity statement for {}", entityId);
- return true;
- }
- } catch (final SecurityException e) {
- log.debug("Could not validate entity statement for {}", entityId, e);
- }
- log.warn("Trust Engine validation failed for {}", entityId);
- return false;
- }
}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultProvidedTrustChainValidationStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultProvidedTrustChainValidationStrategy.java
new file mode 100644
index 00000000..65c8eb0d
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultProvidedTrustChainValidationStrategy.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata;
+
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+
+import net.shibboleth.oidc.metadata.filter.MetadataFilterContext;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Default strategy for validating provided trust chain signatures via {@link TrustEngine} and configurable
+ * trust anchor signature validation filter.
+ */
+public class DefaultProvidedTrustChainValidationStrategy
+ extends AbstractTrustEngineSignatureValidationComponent
+ implements BiPredicate<ProfileRequestContext, List<EntityStatement>> {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultProvidedTrustChainValidationStrategy.class);
+
+ /** Strategy for validating trust anchor's entity configuration signature. */
+ @NonnullAfterInit BiFunction<EntityStatement, MetadataFilterContext, EntityStatement>
+ trustAnchorSignatureValidationFilterStrategy;
+
+ /**
+ * Set the strategy for validating trust anchor's entity configuration signature.
+ *
+ * @param strategy validation strategy
+ */
+ public void setTrustAnchorSignatureValidationFilterStrategy(
+ @Nonnull final BiFunction<EntityStatement, MetadataFilterContext, EntityStatement> strategy) {
+ checkSetterPreconditions();
+ trustAnchorSignatureValidationFilterStrategy = Constraint.isNotNull(strategy,
+ "TrustAnchorSignatureValidationFilterStrategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ if (trustAnchorSignatureValidationFilterStrategy == null) {
+ throw new ComponentInitializationException("TrustAnchorSignatureValidationFilterStrategy cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean test(@Nullable final ProfileRequestContext profileRequestContext,
+ @Nullable final List<EntityStatement> trustChain) {
+ if (trustChain == null || trustChain.size() < 3 || trustChain.contains(null)) {
+ log.error("No satisfactory trust chain provided");
+ return false;
+ }
+
+ final EntityStatement entityConfiguration = trustChain.get(0);
+ assert entityConfiguration != null;
+ if (!validateStatement(entityConfiguration,
+ new CriteriaSet(new SubjectEntityStatementCriterion(entityConfiguration)),
+ entityConfiguration.getEntityID().getValue())) {
+ log.debug("Entity configuration signature validation failed");
+ return false;
+ }
+ for (int i = 1; i < trustChain.size() - 2; i++) {
+ final EntityStatement subordinateStatement = trustChain.get(i);
+ final EntityStatement issuerStatement = trustChain.get(i + 1);
+ assert subordinateStatement != null;
+ assert issuerStatement != null;
+ final CriteriaSet criteria = new CriteriaSet();
+ criteria.add(new IssuerEntityStatementCriterion(issuerStatement));
+ criteria.add(new SubjectEntityStatementCriterion(subordinateStatement));
+ if (!validateStatement(subordinateStatement, criteria, subordinateStatement.getEntityID().getValue())) {
+ log.debug("Subordinate statement {} signature validation failed", subordinateStatement.getEntityID());
+ return false;
+ }
+ }
+
+ final EntityStatement trustAnchor = trustChain.get(trustChain.size() - 1);
+ if (!trustAnchor.equals(trustAnchorSignatureValidationFilterStrategy.apply(trustAnchor, null))) {
+ log.debug("Trust anchor {} validation failed", trustAnchor.getEntityID());
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateProvidedTrustChain.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateProvidedTrustChain.java
new file mode 100644
index 00000000..22bb359c
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateProvidedTrustChain.java
@@ -0,0 +1,253 @@
+/*
+ * 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.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+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.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityType;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.minidev.json.JSONObject;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultClientMetadataFromTrustChainLookupStrategy;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+
+/**
+ * Validates the provided trust chain and enforces metadata policy merging
+ * strategy and enforcer. The data is populated to the {@link RelyingPartyTrustChainContext}.
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ *
+ * @since 4.3.0
+ */
+public class ValidateProvidedTrustChain extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(ValidateProvidedTrustChain.class);
+
+ /** Strategy used to validate provided trust chain. */
+ @NonnullAfterInit
+ private BiPredicate<ProfileRequestContext, List<EntityStatement>> providedTrustChainValidationStrategy;
+
+ /** Strategy used to create the trust chain context. */
+ @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextCreationStrategy;
+
+ /** Strategy used to locate the provided trust chain. */
+ @NonnullAfterInit private Function<ProfileRequestContext, List<EntityStatement>> providedTrustChainLookupStrategy;
+
+ /** Strategy used to get combined OIDC client metadata from trust chain. */
+ @Nonnull private Function<List<EntityStatement>,OIDCClientMetadata> metadataLookupStrategy;
+
+ /** Strategy used to merge metadata policies in trust chain for specific entity type. */
+ @NonnullAfterInit private BiFunction<List<EntityStatement>,String,Map<String, MetadataPolicy>>
+ metadataPolicyMergingStrategy;
+
+ /** Enforcer function for applying metadata policy for an item. */
+ @NonnullAfterInit private BiFunction<Object, MetadataPolicy, Pair<Object, Boolean>> metadataPolicyEnforcer;
+
+ /** Trust chain to operate on. */
+ @NonnullBeforeExec private List<EntityStatement> trustChain;
+
+ /**
+ * Constructor.
+ */
+ public ValidateProvidedTrustChain() {
+ final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tccs =
+ new ChildContextLookup<>(RelyingPartyTrustChainContext.class, true).compose(
+ new InboundMessageContextLookup());
+ assert tccs != null;
+ trustChainContextCreationStrategy = tccs;
+ metadataLookupStrategy = new DefaultClientMetadataFromTrustChainLookupStrategy();
+ }
+
+ /**
+ * Set the strategy used to get combined OIDC client metadata from trust chain.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setMetadataLookupStrategy(@Nonnull final Function<List<EntityStatement>,OIDCClientMetadata> strategy) {
+ checkSetterPreconditions();
+ metadataLookupStrategy =
+ Constraint.isNotNull(strategy, "MetadataLookupStrategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to locate the provided trust chain.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setProvidedTrustChainLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, List<EntityStatement>> strategy) {
+ providedTrustChainLookupStrategy =
+ Constraint.isNotNull(strategy, "ProvidedTrustChainLookupStrategy cannot be null");
+ }
+ /**
+ * Set the strategy used to merge metadata policies in trust chain for specific entity type.
+ *
+ * @param strategy merging strategy
+ */
+ public void setMetadataPolicyMergingStrategy(@Nonnull final
+ BiFunction<List<EntityStatement>,String,Map<String, MetadataPolicy>> strategy) {
+ checkSetterPreconditions();
+ metadataPolicyMergingStrategy =
+ Constraint.isNotNull(strategy, "MetadataPolicyMergingStrategy cannot be null");
+ }
+
+ /**
+ * Set the enforcer function for applying metadata policy for an item.
+ *
+ * @param enforcer policy enforcer
+ */
+ public void setMetadataPolicyEnforcer(
+ @Nonnull final BiFunction<Object, MetadataPolicy, Pair<Object, Boolean>> enforcer) {
+ checkSetterPreconditions();
+ metadataPolicyEnforcer = Constraint.isNotNull(enforcer, "Metadata policy enforcer cannot be null");
+ }
+
+ /**
+ * Set the strategy used to validate provided trust chain.
+ *
+ * @param strategy validation strategy
+ */
+ public void setProvidedTrustChainValidationStrategy(
+ @Nonnull final BiPredicate<ProfileRequestContext, List<EntityStatement>> strategy) {
+ checkSetterPreconditions();
+ providedTrustChainValidationStrategy =
+ Constraint.isNotNull(strategy, "ProvidedTrustChainValidationStrategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (providedTrustChainLookupStrategy == null) {
+ throw new ComponentInitializationException("ProvidedTrustChainLookupStrategy cannot be null");
+ }
+ if (providedTrustChainValidationStrategy == null) {
+ throw new ComponentInitializationException("ProvidedTrustChainValidationStrategy cannot be null");
+ }
+ if (metadataPolicyEnforcer == null) {
+ throw new ComponentInitializationException("MetadataPolicyEnforcer cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+ trustChain = providedTrustChainLookupStrategy.apply(profileRequestContext);
+ if (trustChain == null) {
+ log.error("{} Unable to fetch trust chain", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ if (!providedTrustChainValidationStrategy.test(profileRequestContext, trustChain)) {
+ log.error("{} The trust chain validation failed", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return;
+ }
+
+ final RelyingPartyTrustChainContext trustChainContext =
+ trustChainContextCreationStrategy.apply(profileRequestContext);
+ assert trustChain != null;
+ trustChainContext.setResolvedTrustChains(CollectionSupport.listOf(trustChain));
+ final List<Pair<List<EntityStatement>, OIDCClientInformation>> policyCompliantChains = new ArrayList<>();
+
+ final Map<String, MetadataPolicy> mergedPolicies =
+ metadataPolicyMergingStrategy.apply(trustChain, EntityType.OPENID_RELYING_PARTY.getValue());
+ log.debug("{} Merged policy for chain {}", getLogPrefix(), mergedPolicies);
+ final OIDCClientMetadata metadata = metadataLookupStrategy.apply(trustChain);
+ if (metadata == null) {
+ log.error("{} Could not extract metadata", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return;
+ }
+ final OIDCClientInformation clientInformation = new OIDCClientInformation(
+ new ClientID(trustChain.get(0).getEntityID().getValue()), metadata);
+ final JSONObject requestMetadata = clientInformation.toJSONObject();
+ boolean compliant = true;
+ for (final String claim : mergedPolicies.keySet()) {
+ final MetadataPolicy policy = mergedPolicies.get(claim);
+ final Object value = requestMetadata.get(claim);
+ log.debug("{} Claim {} set in policy included in the request: {}", getLogPrefix(), claim,
+ value == null);
+ final Pair<Object,Boolean> mergeResult = metadataPolicyEnforcer.apply(value, policy);
+ final Boolean enforcerResult = mergeResult != null ? mergeResult.getSecond() : null;
+ if (enforcerResult == null || !enforcerResult.booleanValue()) {
+ log.warn("{} Metadata claim {} is not compliant with the policy", getLogPrefix(), claim);
+ compliant = false;
+ } else {
+ log.trace("{} Validation result is OK for claim {}", getLogPrefix(), claim);
+ final Object enforcedValue = mergeResult != null ? mergeResult.getFirst() : null;
+ requestMetadata.put(claim, enforcedValue);
+ log.debug("{} The requested metadata is compliant with the policy", getLogPrefix());
+ }
+ }
+ if (!compliant) {
+ log.error("{} The request metadata is not compliant with the merged policy", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return;
+ }
+
+ try {
+ policyCompliantChains.add(new Pair<>(trustChain, OIDCClientInformation.parse(requestMetadata)));
+ } catch (final ParseException e) {
+ log.error("{} Could not parse the metadata object", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return;
+ }
+
+ log.debug("{} Setting the policy compliant trust chains into the context: {}", getLogPrefix(),
+ policyCompliantChains);
+ trustChainContext.setPolicyCompliantTrustChains(policyCompliantChains);
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestTrustChainLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestTrustChainLookupFunction.java
new file mode 100644
index 00000000..283ab0bb
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestTrustChainLookupFunction.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate;
+
+import java.util.List;
+import java.util.Optional;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ExplicitClientRegistrationRequest;
+
+/**
+ * A function that returns pushed trust chain set in the explicit registration request.
+ *
+ * @since 4.3.0
+ */
+ at ThreadSafe
+public class ExplicitClientRegistrationRequestTrustChainLookupFunction
+ implements ContextDataLookupFunction<ProfileRequestContext, List<EntityStatement>> {
+
+ /** {@inheritDoc} */
+ @Nullable
+ public List<EntityStatement> apply(@Nullable final ProfileRequestContext input) {
+ return Optional.ofNullable(input)
+ .map(profileRequestContext -> profileRequestContext.getInboundMessageContext())
+ .map(messageContext -> messageContext.getMessage())
+ .filter(ExplicitClientRegistrationRequest.class::isInstance)
+ .map(ExplicitClientRegistrationRequest.class::cast)
+ .map(request -> request.getTrustChain())
+ .orElse(null);
+ }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
index 0845f640..5a60fb0e 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
@@ -37,6 +37,41 @@
<bean id="ExplicitRegistrationRelyingPartyCreationStrategy" parent="shibboleth.Functions.Expression"
c:expression="#input.ensureSubcontext(T(net.shibboleth.profile.context.RelyingPartyContext))" />
+ <bean id="ValidateProvidedTrustChain" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ValidateProvidedTrustChain"
+ scope="prototype"
+ p:metadataPolicyMergingStrategy-ref="#{'%{idp.oidfed.register.TrustChainMetadataPolicyMergingStrategy:DefaultTrustChainMetadataPolicyMergingStrategy}'.trim()}"
+ p:metadataPolicyEnforcer-ref="#{'%{idp.oidfed.register.MetadataPolicyEnforcer:DefaultMetadataPolicyEnforcer}'.trim()}">
+ <property name="providedTrustChainValidationStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultProvidedTrustChainValidationStrategy">
+ <property name="trustEngine">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.TokenPayloadAsymmetricKeyTrustEngine">
+ <constructor-arg>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultEntityStatementCredentialResolver" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="trustAnchorSignatureValidationFilterStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultEntityStatementSignatureValidationFilterStrategy">
+ <property name="trustEngine">
+ <bean class="net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine">
+ <constructor-arg index="0">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultLocalTrustAnchorCredentialResolver"
+ c:cache-ref="shibboleth.oidfed.LocalTrustAnchorsMetadataCache" />
+ </constructor-arg>
+ <constructor-arg index="1">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultEntityStatementCredentialResolver" />
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ <property name="providedTrustChainLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.ExplicitClientRegistrationRequestTrustChainLookupFunction" />
+ </property>
+ </bean>
+
<bean id="ResolveTrustChains" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ResolveTrustChains"
scope="prototype"
p:trustChainCache-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-flow.xml
index 493fae0c..6c4a51d7 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-flow.xml
@@ -6,10 +6,21 @@
<action-state id="InitializeMandatoryContexts">
<evaluate expression="'proceed'" />
<transition on="proceed" to="DecodeMessage">
- <set name="flowScope.transitionAfterDecode" value="'ResolveTrustChains'" />
+ <set name="flowScope.transitionAfterDecode" value="'SelectTrustChainResolution'" />
</transition>
</action-state>
+ <decision-state id="SelectTrustChainResolution">
+ <if test="opensamlProfileRequestContext.ensureInboundMessageContext().getMessage().getTrustChain() != null"
+ then="ValidateProvidedTrustChain" else="ResolveTrustChains" />
+ </decision-state>
+
+ <action-state id="ValidateProvidedTrustChain">
+ <evaluate expression="ValidateProvidedTrustChain"/>
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="SelectTrustChain" />
+ </action-state>
+
<action-state id="ResolveTrustChains">
<evaluate expression="ResolveTrustChains" />
<evaluate expression="'proceed'" />
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
index c41d1361..22cd0b55 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
@@ -103,6 +103,41 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
Assert.assertEquals(storedInfo.getOIDCMetadata().getRedirectionURIStrings(),
metadata.getRedirectionURIStrings());
Assert.assertTrue(metadata.getRedirectionURIStrings().contains(redirectUri));
+ }
+
+ @Test
+ public void testValidTrustChain() throws Exception {
+ final String clientId = uniqueClientId();
+ final String trustChain = "[\"" + rpEntityConfiguration(clientId) + "\", \"" +
+ subordinateStatement(clientId) + "\", \"" + trustedAnchorConfiguration() + "\"]";
+ setRequest("POST", trustChain, "application/trust-chain+json");
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final ExplicitClientRegistrationResponse parsedResponse =
+ parseSuccessResponse(result, ExplicitClientRegistrationResponse.class);
+ final EntityStatement entityStatement = parsedResponse.getEntityStatement();
+ final EntityStatementClaimsSet statementClaims = entityStatement.getClaimsSet();
+ Assert.assertEquals(statementClaims.getAuthorityHints().stream().map(id -> id.getValue()).toList(),
+ List.of(anchorId));
+ Assert.assertEquals(statementClaims.getClaim("trust_anchor"), anchorId);
+ final OIDCClientInformation clientInfo = entityStatement.getClaimsSet().getRPInformation();
+ final OIDCClientMetadata metadata = clientInfo.getOIDCMetadata();
+ final String providedClientId = clientInfo.getID().getValue();
+ assert providedClientId != null;
+ assert storageService != null;
+ final StorageRecord<String> storageRecord =
+ storageService.read(BaseStorageServiceClientInformationComponent.CONTEXT_NAME, providedClientId);
+ Assert.assertNotNull(storageRecord, "Record with clientId " + providedClientId + " was null");
+ assert storageRecord != null;
+ final String record = storageRecord.getValue();
+ Assert.assertNotNull(record);
+ final JSONParser parser = new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE);
+ final OIDCClientInformation storedInfo = OIDCClientInformation.parse((JSONObject) parser.parse(record));
+ Assert.assertEquals(storedInfo.getID(), clientInfo.getID());
+ Assert.assertEquals(storedInfo.getSecret(), clientInfo.getSecret());
+ Assert.assertEquals(storedInfo.getOIDCMetadata().getRedirectionURIStrings(),
+ metadata.getRedirectionURIStrings());
+ Assert.assertTrue(metadata.getRedirectionURIStrings().contains(redirectUri));
}
+
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list