[java-idp-plugin-oidc-op-oidfed] 03/03: Integrated the remote trust mark validation option into resolve entity API.
Codeberg
noreply at shibboleth.net
Wed May 6 12:57:06 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/f6d34274e47e80f80adb6f7b0f62ed361d2a3ef4
commit f6d34274e47e80f80adb6f7b0f62ed361d2a3ef4
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Wed May 6 15:56:44 2026 +0300
Integrated the remote trust mark validation option into resolve entity API.
---
...ederationResolveEntityProfileConfiguration.java | 3 +-
...ederationResolveEntityProfileConfiguration.java | 108 +++++++++++++++++++++
.../oidfed/resolve-entity/resolve-entity-beans.xml | 5 +
.../oidfed/resolve-entity/resolve-entity-flow.xml | 1 +
.../profile/flow/oidfed/ResolveEntityFlowTest.java | 44 ++++++++-
5 files changed, 159 insertions(+), 2 deletions(-)
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationResolveEntityProfileConfiguration.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationResolveEntityProfileConfiguration.java
index cacd25e..bdc7ca3 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationResolveEntityProfileConfiguration.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationResolveEntityProfileConfiguration.java
@@ -32,7 +32,8 @@ import net.shibboleth.shared.annotation.constraint.Positive;
*/
public interface OIDFederationResolveEntityProfileConfiguration extends OverriddenIssuerProfileConfiguration,
OIDFederationProfileConfiguration, OIDFederationResponseCachingProfileConfiguration,
- OAuth2ClientAuthenticableProfileConfiguration, OAuth2ClientAuthenticableClientProfileConfiguration {
+ OAuth2ClientAuthenticableProfileConfiguration, OAuth2ClientAuthenticableClientProfileConfiguration,
+ OIDFederationTrustMarkValidatingProfileConfiguration {
/** OIDC base protocol URI. */
public static final String PROTOCOL_URI = "https://openid.net/specs/openid-federation-1_0.html";
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/DefaultOIDFederationResolveEntityProfileConfiguration.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/DefaultOIDFederationResolveEntityProfileConfiguration.java
index 69a3114..2a3cf90 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/DefaultOIDFederationResolveEntityProfileConfiguration.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/DefaultOIDFederationResolveEntityProfileConfiguration.java
@@ -15,7 +15,9 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.impl;
import java.time.Duration;
+import java.util.List;
import java.util.function.Function;
+import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -24,10 +26,15 @@ import org.opensaml.profile.context.ProfileRequestContext;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.OIDFederationResolveEntityProfileConfiguration;
import net.shibboleth.oidc.profile.oauth2.config.impl.AbstractOAuth2ClientAuthenticableProfileConfiguration;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.annotation.constraint.NotLive;
import net.shibboleth.shared.annotation.constraint.Positive;
+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;
/**
* Implementation of a profile configuration for the OpenID Federation Resolve Entity.
@@ -48,6 +55,15 @@ public class DefaultOIDFederationResolveEntityProfileConfiguration
/** Lookup function to supply cached error response lifetime. */
@Nonnull private Function<ProfileRequestContext,Duration> cachedErrorResponseLifetimeLookupStrategy;
+ /** Lookup function to mandatory trust marks. */
+ @Nonnull private Function<ProfileRequestContext,List<String>> mandatoryTrustMarksLookupStrategy;
+
+ /** 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.
*/
@@ -65,6 +81,9 @@ public class DefaultOIDFederationResolveEntityProfileConfiguration
issuerLookupStrategy = FunctionSupport.constant(null);
cachedSuccessResponseLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofMinutes(5));
cachedErrorResponseLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofMinutes(5));
+ mandatoryTrustMarksLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+ maximumTrustMarkLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofDays(365));
+ remoteTrustMarkValidationCondition = PredicateSupport.alwaysTrue();
}
/** {@inheritDoc} */
@@ -163,4 +182,93 @@ public class DefaultOIDFederationResolveEntityProfileConfiguration
cachedErrorResponseLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
}
+ /** {@inheritDoc} */
+ @Override @Nonnull @NonnullElements @NotLive @Unmodifiable
+ public List<String> getMandatoryTrustMarks(@Nullable final ProfileRequestContext profileRequestContext) {
+ final List<String> trustMarks = mandatoryTrustMarksLookupStrategy.apply(profileRequestContext);
+ if (trustMarks != null) {
+ return CollectionSupport.copyToList(trustMarks);
+ }
+ return CollectionSupport.emptyList();
+ }
+
+ /**
+ * Set mandatory trust marks.
+ *
+ * @param marks trust marks
+ */
+ public void setMandatoryTrustMarks(@Nonnull @NonnullElements @NotLive @Unmodifiable final List<String> marks) {
+ mandatoryTrustMarksLookupStrategy = FunctionSupport.constant(marks);
+ }
+
+ /**
+ * Sets lookup strategy for mandatory trust marks value.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setMandatoryTrustMarksLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,List<String>> strategy) {
+ mandatoryTrustMarksLookupStrategy =
+ Constraint.isNotNull(strategy, "Mandatory trust marks lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Positive @Nonnull
+ public Duration getMaximumTrustMarkLifetime(@Nullable final ProfileRequestContext profileRequestContext) {
+ final Duration lifetime = maximumTrustMarkLifetimeLookupStrategy.apply(profileRequestContext);
+
+ Constraint.isTrue(lifetime != null && !lifetime.isZero() && !lifetime.isNegative(),
+ "Maximum trust mark lifetime must be greater than 0");
+ assert lifetime != null;
+ return lifetime;
+ }
+
+ /**
+ * Set the maximum lifetime of a trust mark.
+ *
+ * @param lifetime lifetime of a trust mark
+ */
+ public void setMaximumTrustMarkLifetime(@Positive @Nonnull final Duration lifetime) {
+ final Duration trustMarkLifetime = Constraint.isNotNull(lifetime, "Maximum trust mark lifetime cannot be null");
+ Constraint.isTrue(!trustMarkLifetime.isZero() && !trustMarkLifetime.isNegative(),
+ "Maximum trust mark lifetime must be greater than 0");
+
+ maximumTrustMarkLifetimeLookupStrategy = FunctionSupport.constant(trustMarkLifetime);
+ }
+
+ /**
+ * Set a lookup strategy for the maximum trust mark lifetime.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setMaximumTrustMarkLifetimeLookupStrategy(
+ @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");
+ }
+
}
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml
index a857621..378f293 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml
@@ -149,6 +149,11 @@
</property>
</bean>
+ <bean id="ValidateTrustMarks" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ValidateTrustMarks"
+ scope="prototype"
+ p:trustMarkStatusCache-ref="#{'%{idp.oidfed.resolve-entity.TrustMarkStatusMetadataCache:shibboleth.oidfed.TrustMarkStatusMetadataCache}'.trim()}">
+ </bean>
+
<bean id="PopulateResolveResponseSignatureSigningParameters"
class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureSigningParameters" scope="prototype"
c:strategy-ref="shibboleth.MessageContextLookup.Outbound"
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-flow.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-flow.xml
index fb65540..887dbda 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-flow.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-flow.xml
@@ -80,6 +80,7 @@
<evaluate expression="SelectTrustChain" />
<evaluate expression="ValidateSelectedTrustChain" />
<evaluate expression="ResolveTrustMarks" />
+ <evaluate expression="ValidateTrustMarks" />
<evaluate expression="'proceed'" />
<transition on="ReselectTrustChain" to="SelectTrustChain" />
<transition on="proceed" to="BuildResponse" />
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/ResolveEntityFlowTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/ResolveEntityFlowTest.java
index f8c9c49..cfd7a18 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/ResolveEntityFlowTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/ResolveEntityFlowTest.java
@@ -15,6 +15,7 @@
package net.shibboleth.idp.plugin.oidc.op.profile.flow.oidfed;
import java.io.IOException;
+import java.net.URI;
import java.net.URISyntaxException;
import java.time.Instant;
import java.util.Date;
@@ -36,12 +37,13 @@ import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.oauth2.sdk.Response;
import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
import net.shibboleth.idp.plugin.oidc.op.oidfed.TrustChainTestUtil;
import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ResolveEntityResponse;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
import net.shibboleth.oidc.profile.messaging.JSONErrorResponse;
-
+import net.shibboleth.shared.collection.CollectionSupport;
import net.minidev.json.JSONObject;
/**
@@ -111,6 +113,46 @@ public class ResolveEntityFlowTest extends AbstractFederationFlowTest {
Assert.assertNull(response.getJWTClaimsSet().getClaim("authority_hints"));
}
+ @Test
+ public void testRPWithTrustedTrustAnchor_validTrustMark() throws Exception {
+ request.setMethod("GET");
+ final String clientId = uniqueClientId();
+ final String trustMark = TrustChainTestUtil.trustMark(JWSAlgorithm.RS256, trustMarkIssuerKey, trustMarkIssuerId,
+ clientId, "https://example.org/email-allowing-trust-mark", Instant.now().plusSeconds(300)).serialize();
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setRedirectionURI(new URI(redirectUri));
+ metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+ final String rpEntityConfiguration = rpEntityConfiguration(clientId, metadata, List.of(Map.of(
+ "trust_mark_type", "https://example.org/email-allowing-trust-mark",
+ "trust_mark", trustMark)), leafKey);
+ rpConfigureMockHttpClient(clientId, rpEntityConfiguration);
+ 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.setContentType("application/x-www-form-urlencoded");
+ request.setQueryString("sub=" + clientId + "&trust_anchor=" + anchorId + "&entity_type=openid_relying_party");
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final ResolveEntityResponse parsedResponse =
+ parseSuccessResponse(result, ResolveEntityResponse.class);
+ final SignedJWT response = parsedResponse.getJWT();
+ Assert.assertEquals(response.getHeader().getType(), new JOSEObjectType("resolve-response+jwt"));
+ Assert.assertEquals(response.getJWTClaimsSet().getSubject(), clientId);
+ Assert.assertNotNull(response.getJWTClaimsSet().getClaim("metadata"));
+ Assert.assertNull(response.getJWTClaimsSet().getClaim("authority_hints"));
+ final Map<String,Object> trustMarks = response.getJWTClaimsSet().getJSONObjectClaim("trust_marks");
+ Assert.assertNotNull(trustMarks, "Could not find trust marks for client " + clientId);
+ Assert.assertEquals(trustMarks.size(), 1);
+ Assert.assertEquals(trustMarks.get("https://example.org/email-allowing-trust-mark"), trustMark);
+ }
+
@Test
public void testRPWithTrustedTrustAnchor_subordinateKeyNotMatchingEntityConfiguration() throws Exception {
request.setMethod("GET");
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list