[java-oidfed-common] 02/02: Use trust anchor's entity configuration keys for trust mark validation

Codeberg noreply at shibboleth.net
Thu Oct 8 05:32:19 UTC 2026


This is an automated email from the git hooks/post-receive script.

codeberg pushed a commit to branch main
in repository java-oidfed-common.

View the commit online:
https://codeberg.org/Shibboleth/java-oidfed-common/commit/8100271d27a54a4eb1155b8b08d6127bc719543b

commit 8100271d27a54a4eb1155b8b08d6127bc719543b
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu Oct 8 08:30:45 2026 +0300

    Use trust anchor's entity configuration keys for trust mark validation
    
    - Previously the locally trusted anchor keys were used: this was too strict as they're needed solely for the trust anchor entity configuration validation
      - Anchor may use another keys for signing trust marks
    - Cover in flow tests
---
 .../flow/TestTrustChainResolutionFlowTest.java     | 105 +++++++++++++++++++++
 ...ustMarkIssuerConfigurationFetchingStrategy.java |   2 +-
 .../oidfed/testing/TestFederationCredentials.java  |   9 ++
 .../oidfed/testing/fed-local-anchor2.jwk           |  13 +++
 4 files changed, 128 insertions(+), 1 deletion(-)

diff --git a/oidfed-common-conf-impl/src/test/java/net/shibboleth/oidfed/flow/TestTrustChainResolutionFlowTest.java b/oidfed-common-conf-impl/src/test/java/net/shibboleth/oidfed/flow/TestTrustChainResolutionFlowTest.java
index e18acbb..3aec485 100644
--- a/oidfed-common-conf-impl/src/test/java/net/shibboleth/oidfed/flow/TestTrustChainResolutionFlowTest.java
+++ b/oidfed-common-conf-impl/src/test/java/net/shibboleth/oidfed/flow/TestTrustChainResolutionFlowTest.java
@@ -26,6 +26,8 @@ import java.util.Map;
 
 import javax.annotation.Nonnull;
 
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.webflow.execution.FlowExecutionOutcome;
 import org.springframework.webflow.executor.FlowExecutionResult;
 import org.testng.Assert;
@@ -39,6 +41,8 @@ import com.nimbusds.jose.jwk.JWKSet;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
 
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidfed.metadata.cache.configuration.EntityConfigurationContainer;
 import net.shibboleth.oidfed.testing.FederationJwtSupport;
 import net.shibboleth.oidfed.testing.TestFederationCredentials;
 import net.shibboleth.shared.collection.CollectionSupport;
@@ -51,6 +55,10 @@ public class TestTrustChainResolutionFlowTest extends AbstractFederationFlowTest
     /** The flow id. */
     @Nonnull public static final String FLOW_ID = "admin/oidfed/test-trust-chain-resolution";
 
+    @Autowired
+    @Qualifier("shibboleth.oidfed.EntityConfigurationMetadataCache")
+    MetadataCache<EntityConfigurationContainer> entityConfigurationCache;
+
     /** Constructor. */
     public TestTrustChainResolutionFlowTest() {
         super(FLOW_ID, "ResponseView");
@@ -185,6 +193,103 @@ public class TestTrustChainResolutionFlowTest extends AbstractFederationFlowTest
         assertTrustMarkDetails(0);
     }
 
+    @Test
+    public void testTrustMarkWithAnchorAsIssuer_tmSignedWithLocallyTrustedKey() throws Exception {
+        final String entityId = super.uniqueClientId();
+        request.setMethod("GET");
+        request.addParameter("entityID", entityId);
+        request.addParameter("includeTrustMark", "true");
+
+        final OIDCClientMetadata metadata = new OIDCClientMetadata();
+        metadata.setRedirectionURI(new URI(redirectUri));
+        metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+
+        final String trustMark = FederationJwtSupport.trustMark(JWSAlgorithm.RS256, trustedAnchorKey, anchorId,
+                entityId, "https://example.org/email-allowing-trust-mark", Instant.now().plusSeconds(300)).serialize();
+        final String rpEntityConfiguration = rpEntityConfiguration(entityId, metadata, List.of(Map.of(
+                "trust_mark_type", "https://example.org/email-allowing-trust-mark",
+                "trust_mark", trustMark)), leafKey);
+        final Map<String,List<String>> trustMarkIssuers =
+                Map.of("https://example.org/email-allowing-trust-mark", List.of(anchorId));
+        final String anchorEntityConfiguration =
+                trustedAnchorConfiguration(null, trustMarkIssuers, trustedAnchorKey, new JWKSet(trustedAnchorKey), defaultTrustMarkOwners());
+        entityConfigurationCache.clearAll();
+        rpConfigureMockHttpClient(entityId, rpEntityConfiguration, anchorEntityConfiguration);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        entityConfigurationCache.clearAll();
+
+        final FlowExecutionOutcome outcome = result.getOutcome();
+        assertEquals(outcome.getId(), "ResponseView");
+        assertResponseArraySize(1);
+        assertTrustMarkDetails(1);
+    }
+
+    @Test
+    public void testTrustMarkWithAnchorAsIssuer_tmSignedWithAdditionalAnchorKey() throws Exception {
+        final String entityId = super.uniqueClientId();
+        request.setMethod("GET");
+        request.addParameter("entityID", entityId);
+        request.addParameter("includeTrustMark", "true");
+
+        final OIDCClientMetadata metadata = new OIDCClientMetadata();
+        metadata.setRedirectionURI(new URI(redirectUri));
+        metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+
+        final JWK additionalKey = TestFederationCredentials.trustAnchorKeyUntrusted();
+        final JWKSet jwks = new JWKSet(List.of(trustedAnchorKey, additionalKey));
+        final Map<String,List<String>> trustMarkIssuers =
+                Map.of("https://example.org/email-allowing-trust-mark", List.of(anchorId));
+        final String anchorEntityConfiguration =
+                trustedAnchorConfiguration(null, trustMarkIssuers, trustedAnchorKey, jwks, defaultTrustMarkOwners());
+        final String trustMark = FederationJwtSupport.trustMark(JWSAlgorithm.RS256, additionalKey, anchorId,
+                entityId, "https://example.org/email-allowing-trust-mark", Instant.now().plusSeconds(300)).serialize();
+        final String rpEntityConfiguration = rpEntityConfiguration(entityId, metadata, List.of(Map.of(
+                "trust_mark_type", "https://example.org/email-allowing-trust-mark",
+                "trust_mark", trustMark)), leafKey);
+        entityConfigurationCache.clearAll();
+        rpConfigureMockHttpClient(entityId, rpEntityConfiguration, anchorEntityConfiguration);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        entityConfigurationCache.clearAll();
+
+        final FlowExecutionOutcome outcome = result.getOutcome();
+        assertEquals(outcome.getId(), "ResponseView");
+        assertResponseArraySize(1);
+        assertTrustMarkDetails(1);
+    }
+
+    @Test
+    public void testTrustMarkWithAnchorAsIssuer_tmSignedWithAdditionalUnmappedAnchorKey() throws Exception {
+        final String entityId = super.uniqueClientId();
+        request.setMethod("GET");
+        request.addParameter("entityID", entityId);
+        request.addParameter("includeTrustMark", "true");
+
+        final OIDCClientMetadata metadata = new OIDCClientMetadata();
+        metadata.setRedirectionURI(new URI(redirectUri));
+        metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+
+        final JWK additionalKey = TestFederationCredentials.trustAnchorKeyUntrusted();
+        final Map<String,List<String>> trustMarkIssuers =
+                Map.of("https://example.org/email-allowing-trust-mark", List.of(anchorId));
+        final String anchorEntityConfiguration =
+                trustedAnchorConfiguration(null, trustMarkIssuers, trustedAnchorKey, new JWKSet(trustedAnchorKey),
+                        defaultTrustMarkOwners());
+        final String trustMark = FederationJwtSupport.trustMark(JWSAlgorithm.RS256, additionalKey, anchorId,
+                entityId, "https://example.org/email-allowing-trust-mark", Instant.now().plusSeconds(300)).serialize();
+        final String rpEntityConfiguration = rpEntityConfiguration(entityId, metadata, List.of(Map.of(
+                "trust_mark_type", "https://example.org/email-allowing-trust-mark",
+                "trust_mark", trustMark)), leafKey);
+        entityConfigurationCache.clearAll();
+        rpConfigureMockHttpClient(entityId, rpEntityConfiguration, anchorEntityConfiguration);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        entityConfigurationCache.clearAll();
+
+        final FlowExecutionOutcome outcome = result.getOutcome();
+        assertEquals(outcome.getId(), "ResponseView");
+        assertResponseArraySize(1);
+        assertTrustMarkDetails(0);
+    }
+
     @Test
     public void testWithTrustMarkDetailsParameter_unmatchingTrustMarkType() throws Exception {
         request.setMethod("GET");
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkIssuerConfigurationFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkIssuerConfigurationFetchingStrategy.java
index 6ba1fbd..2b68bb8 100644
--- a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkIssuerConfigurationFetchingStrategy.java
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkIssuerConfigurationFetchingStrategy.java
@@ -127,7 +127,7 @@ public class DefaultTrustMarkIssuerConfigurationFetchingStrategy
                     if (issuerStatement != null) {
                         final var anchorCriteria = new CriteriaSet(
                                 new SubjectEntityStatementCriterion(issuerStatement));
-                        if (validateJwt(wrapper.getJwt(), anchorCriteria, issuer)) {
+                        if (validateJwt(issuerStatement.getJwt(), anchorCriteria, issuer)) {
                             return issuerStatement;
                         }
                         log.warn("Could not validate the issuer entity configuration against trust engine");
diff --git a/oidfed-common-testing/src/main/java/net/shibboleth/oidfed/testing/TestFederationCredentials.java b/oidfed-common-testing/src/main/java/net/shibboleth/oidfed/testing/TestFederationCredentials.java
index a089e7b..ca7e089 100644
--- a/oidfed-common-testing/src/main/java/net/shibboleth/oidfed/testing/TestFederationCredentials.java
+++ b/oidfed-common-testing/src/main/java/net/shibboleth/oidfed/testing/TestFederationCredentials.java
@@ -39,6 +39,15 @@ public class TestFederationCredentials {
                 .build();
     }
 
+    public static JWK trustAnchorKeyUntrusted() {
+        final BasicJWKCredential localAnchor =
+                loadJWKCredential("/net/shibboleth/oidfed/testing/fed-local-anchor2.jwk");
+        return new RSAKey.Builder((RSAPublicKey) localAnchor.getPublicKey())
+                .privateKey(localAnchor.getPrivateKey())
+                .keyID("anotherAnchorKey")
+                .build();
+    }
+
     public static JWK intermediateKey() {
         final BasicJWKCredential localIntermediate =
                 loadJWKCredential("/net/shibboleth/oidfed/testing/fed-local-intermediate.jwk");
diff --git a/oidfed-common-testing/src/main/resources/net/shibboleth/oidfed/testing/fed-local-anchor2.jwk b/oidfed-common-testing/src/main/resources/net/shibboleth/oidfed/testing/fed-local-anchor2.jwk
new file mode 100644
index 0000000..cb5bfc8
--- /dev/null
+++ b/oidfed-common-testing/src/main/resources/net/shibboleth/oidfed/testing/fed-local-anchor2.jwk
@@ -0,0 +1,13 @@
+{
+  "p": "6kv5lprCgVS3iuSVZO3T1-W08GLrpS8Xnh6HkDJmBHTF1r_Dp2LrSt2KuW8fX8KyNDc0KKzpkjxqSg4THYUGO74RB6HBuPE0pZ7ZmyZGQzFAnbZYXxLElgXobTUnc4GRCI_R-oyLXkc-UJB7xNvGWReveqcFgSETDdBMlAALAI8",
+  "kty": "RSA",
+  "q": "94hxbDstRi-cbVCBs1LWx5MCs3fOxZNQs96TNr2Lmx3ZEpE78MYDuuQOyOnFKE4k4k0VeeU8722t99Wz4HfSWE9Xpuf6E6ptQBuQ3IPUv3b6kvt_F8MQ3NRVMYhzXZbtl6_kvhLu9dTEEvyQFXW55qeqLfvRR5nG1uJ4RCC9eG8",
+  "d": "NlLM1-ibXVXfFRZTKuxlwo1uwwlW3klN3L_4DIz4mRQ9nQ4YUXMXuOrMVXO4VDMDg_89d3hlP3jdGPzpIXv0248H0e1u8h1rBxvqywbajdplHIdacWgdLvecRyfxM9zGkE-7spgIIyMUJDy5XY-3dPpmSUzoMT531AZDxCdOEDsP1LqgJPUXxTn7Lal-DhznCCV7JMEaAvLs9lzyPrtqBYvYhH5JvRal2ZhylBCJONS3m9TCOjXWyg-9Pu-OK6yv11qjOhee97vAwCpBUYgcnpjQHrLOogjdREhCKljjP-V2f5z1W4E47zxbxDFXZCbaaE-k8djNcOo_phaULtI8jQ",
+  "e": "AQAB",
+  "use": "sig",
+  "kid": "anotherAnchorKey",
+  "qi": "vIX_D9z7mNzNlNWRw6yqNhFarNmjhMqLE9wx6P0972XD5_qSIhVoTekdb0vcQWOxfOkN-RvZSH-NMZ1pbHfkVM5fUWs0kuLwZ1qQ3WKSyThBP6lpJKmmA4uNrqssitmAoZWu6neo33S20oRCx3D3SirHgd2tH9iSBeZlFewgsZ4",
+  "dp": "ppXwVwGgTB51Bd8yzXk_x2C-vrBGxcLu8vDYCFGdMYvpYqAkN65gh3FvghsdXpPq6q9ZeZY_2Eb2d8L8GmALp4yjRR7gw2NUwHFqXSLgkEPDop8UxDxu5BA9SvChJUI5YbIIZTFqolTbXKLuLXDp3Uy26uk4Q7DFgGvbFtvU9sc",
+  "dq": "r_tIMBtOT8TPoIQJAfWWDyM8W12FGsBmMsq9vMA7PZeV2XZ5t-7_feJfhIV2jvYnkOAesZ_Bl7NRaI59KMvnNrOJ7HamO08CBv7SGYR8P3cWJGM68yjSlz06KFILOvFvj1UU-fE6BHo0wA7hXPBqr0pqWfDa093HGMS_nifsrG8",
+  "n": "4owt-361mhOFvdotqlbWdZG8qgF_RNyBkqhbCbWSmJZemhFSkUQg11T5PmA9HpDhTzs9FVqqnv5t28VTIpoLn491F9ICwxB86-37Qja6Z1Pkz3qqHUQmwXFA1MDQ95Pfb_eYK_qi2wZbknqgPBiEWkccY41oHOjWKJi7s78zhK0wyOiGVuS67gfmrGC0lTpi0vT6LgpEgssaN0JOlIzp2vJqdD9Gw5lNeDN0P5A34BEBtPPNl70wyDlQrQoC-elVQSNT5SbLRyWk06puNnPLVLwfThyvwCS0iW8JE5GfX-PHLwHqhvykCVRLvmQXAdzA8o0THdu1WzTd6SpedptGAQ"
+}

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list