[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