[java-idp-plugin-oidc-op-oidfed] branch main updated: Initial implementation for the trust mark status metadata cache. WIP.

Henri Mikkonen henri.mikkonen at iki.fi
Wed Nov 12 14:05:16 UTC 2025


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

hjmikkon pushed a commit to branch main
in repository java-idp-plugin-oidc-op-oidfed.

View the commit online:
https://git.shibboleth.net/view/?p=java-idp-plugin-oidc-op-oidfed.git;a=commit;h=d9cbf84575cb6b20bc9e1d382fe5c19feaf9887c

The following commit(s) were added to refs/heads/main by this push:
     new d9cbf84  Initial implementation for the trust mark status metadata cache. WIP.
d9cbf84 is described below

commit d9cbf84575cb6b20bc9e1d382fe5c19feaf9887c
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Wed Nov 12 16:05:01 2025 +0200

    Initial implementation for the trust mark status metadata cache. WIP.
    
    - Extracted resolve entity API fetching strategy's common bits into an abstract class
      - Both resolve entity and trust mark status exploit that
    - Use container with null response for entity-configuration and resolve-entity
      - Avoids warnings by metadata cache -layer when fetching strategy returns null
---
 .../messaging/impl/TrustMarkStatusRequest.java     |  72 ++++++++
 .../messaging/impl/TrustMarkStatusResponse.java    |  87 +++++++++
 ...FederationEndpointResponseFetchingStrategy.java | 194 +++++++++++++++++++++
 ...ntityConfigurationResponseFetchingStrategy.java |   5 +-
 ...faultResolveEntityResponseFetchingStrategy.java |   8 +-
 ...ultResolveEntityTrustChainFetchingStrategy.java | 169 ++++--------------
 .../DefaultTrustMarkStatusFetchingStrategy.java    | 113 ++++++++++++
 ...sResponseSignatureValidationFilterStrategy.java | 128 ++++++++++++++
 .../oidfed/metadata/NimbusResponseContainer.java   |  11 +-
 .../metadata/ResolveEntityResponseContainer.java   |   3 +-
 .../op/oidfed/metadata/TrustMarkCriterion.java     |  79 +++++++++
 .../metadata/TrustMarkStatusRequestCriterion.java  |  80 +++++++++
 ....java => TrustMarkStatusResponseContainer.java} |  33 ++--
 .../META-INF/net.shibboleth.idp/postconfig.xml     |  25 ++-
 14 files changed, 850 insertions(+), 157 deletions(-)

diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/messaging/impl/TrustMarkStatusRequest.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/messaging/impl/TrustMarkStatusRequest.java
new file mode 100644
index 0000000..65a50bb
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/messaging/impl/TrustMarkStatusRequest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.messaging.impl;
+
+import java.net.URI;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.Request;
+import com.nimbusds.oauth2.sdk.http.HTTPRequest;
+
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Request message to the OpenID federation Trust Mark Status endpoint.
+ */
+public class TrustMarkStatusRequest implements Request {
+
+    /** The endpoint URI of the request. */
+    @Nonnull private final URI endpointUri;
+
+    /** The trust mark JWT for the request. */
+    @Nonnull private final SignedJWT trustMark;
+
+    /**
+     * Constructor.
+     *
+     * @param uri endpoint URI
+     * @param jwt trust mark
+     */
+    public TrustMarkStatusRequest(@Nonnull final URI uri, @Nonnull final SignedJWT jwt) {
+        endpointUri = Constraint.isNotNull(uri, "Endpoint URI cannot be null");
+        trustMark = Constraint.isNotNull(jwt, "Trust Mark JWT cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nonnull
+    public URI getEndpointURI() {
+        return endpointUri;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public HTTPRequest toHTTPRequest() {
+        //TODO
+        return null;
+    }
+
+    /**
+     * Get the trust mark for the request.
+     * 
+     * @return trust mark
+     */
+    @Nullable public SignedJWT getTrustMark() {
+        return trustMark;
+    }
+
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/messaging/impl/TrustMarkStatusResponse.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/messaging/impl/TrustMarkStatusResponse.java
new file mode 100644
index 0000000..fa94f8d
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/messaging/impl/TrustMarkStatusResponse.java
@@ -0,0 +1,87 @@
+/*
+ * 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.messaging.impl;
+
+import javax.annotation.Nonnull;
+
+import com.nimbusds.common.contenttype.ContentType;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.http.HTTPResponse;
+
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * Response message to the OpenID federation Trust Mark status endpoint.
+ */
+public class TrustMarkStatusResponse extends AbstractSignedJWTResponse {
+
+    /** The JWT type header. */
+    @Nonnull
+    public static final JOSEObjectType JWT_TYPE_HEADER = new JOSEObjectType("trust-mark-status-response+jwt");
+
+    /** The content type. */
+    @Nonnull public static final ContentType HTTP_RESPONSE_CONTENT_TYPE =
+            new ContentType("application", JWT_TYPE_HEADER.toString());
+
+    /**
+     * Constructor.
+     *
+     * @param statement JWT
+     */
+    public TrustMarkStatusResponse(@Nonnull final SignedJWT statement) {
+        super(statement);
+    }
+
+    /** {@inheritDoc} */
+    protected ContentType getHttpResponseContentType() {
+        return HTTP_RESPONSE_CONTENT_TYPE;
+    }
+
+    /** {@inheritDoc} */
+    protected JOSEObjectType getJWTTypeHeader() {
+        return JWT_TYPE_HEADER;
+    }
+
+    /**
+     * Parses a federation resolve entity success response from the given HTTP response.
+     *
+     * @param httpResponse the HTTP response
+     * @return resolve entity success response
+     * @throws ParseException if HTTP response could not be parsed into resolve entity response
+     */
+    @Nonnull
+    public static TrustMarkStatusResponse parse(@Nonnull final HTTPResponse httpResponse)
+        throws ParseException {
+
+        httpResponse.ensureStatusCode(HTTPResponse.SC_OK);
+        httpResponse.ensureEntityContentType(HTTP_RESPONSE_CONTENT_TYPE);
+        final String content = httpResponse.getContent();
+
+        if (StringSupport.trimOrNull(content) == null) {
+            throw new ParseException("Message body is empty");
+        }
+
+        try {
+            final SignedJWT jwt = SignedJWT.parse(httpResponse.getContent());
+            assert jwt != null;
+            return new TrustMarkStatusResponse(jwt);
+        } catch (final java.text.ParseException | ConstraintViolationException e) {
+            throw new ParseException(e.getMessage(), e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/AbstractFederationEndpointResponseFetchingStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/AbstractFederationEndpointResponseFetchingStrategy.java
new file mode 100644
index 0000000..72f05cf
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/AbstractFederationEndpointResponseFetchingStrategy.java
@@ -0,0 +1,194 @@
+/*
+ * 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.io.IOException;
+import java.net.URISyntaxException;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ProtocolException;
+import org.opensaml.security.httpclient.HttpClientSecurityParameters;
+import org.opensaml.security.httpclient.HttpClientSecuritySupport;
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.oauth2.sdk.Request;
+
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+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;
+
+/**
+ * An abstract base class for fetching strategies related to OpenID Federation endpoints.
+ *
+ * @param <R> request message class sent to the federation endpoint
+ * @param <C> response message container class stored to the metadata cache
+ */
+public abstract class
+    AbstractFederationEndpointResponseFetchingStrategy<R extends Request, C extends NimbusResponseContainer>
+    extends AbstractIdentifiableInitializableComponent implements Function<CriteriaSet, C> {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(AbstractFederationEndpointResponseFetchingStrategy.class);
+
+    /** HTTP client to use. */
+    @NonnullAfterInit protected HttpClient httpClient;
+
+    /** HTTP client security parameters. */
+    @Nullable private HttpClientSecurityParameters httpClientSecurityParameters;
+
+    /** JSON object mapper used for decoding JSON into Map. */
+    @NonnullAfterInit private ObjectMapper objectMapper;
+
+    /** Strategy to fetch request from the criteria set. */
+    @NonnullAfterInit private Function<CriteriaSet, R> criteriaToRequestStrategy;
+
+    /**
+     * Set the {@link HttpClient} to use.
+     * 
+     * @param client HTTP client to use
+     */
+    public void setHttpClient(@Nonnull final HttpClient client) {
+        checkSetterPreconditions();
+        httpClient = Constraint.isNotNull(client, "HttpClient cannot be null");
+    }
+
+    /**
+     * Set the optional client security parameters.
+     * 
+     * @param params the new client security parameters
+     */
+    public void setHttpClientSecurityParameters(@Nullable final HttpClientSecurityParameters params) {
+        checkSetterPreconditions();
+        httpClientSecurityParameters = params;
+    }
+
+    /**
+     * Set the strategy to fetch request from the criteria set.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setCriteriaToRequestStrategy(@Nonnull final Function<CriteriaSet, R> strategy) {
+        checkSetterPreconditions();
+        criteriaToRequestStrategy = Constraint.isNotNull(strategy, "Criteria to request strategy cannot be null");
+    }
+
+    /**
+
+     * Set the JSON {@link ObjectMapper} used for decoding JSON into Map.
+     * 
+     * @param mapper object mapper
+     */
+    public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
+        checkSetterPreconditions();
+
+        objectMapper = Constraint.isNotNull(mapper, "Object mapper cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        if (httpClient == null) {
+            throw new ComponentInitializationException("Httpclient cannot be null");
+        }
+        if (objectMapper == null) {
+            throw new ComponentInitializationException("Object mapper cannot be null");
+        }
+        if (criteriaToRequestStrategy == null) {
+            throw new ComponentInitializationException("Criteria to request strategy cannot be null");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nullable
+    public C apply(@Nullable final CriteriaSet criteria) {
+        checkComponentActive();
+        if (criteria == null) {
+            log.error("No criteria set provided");
+            return null;
+        }
+        final R request = criteriaToRequestStrategy.apply(criteria);
+        if (request == null) {
+            log.debug("Could not resolve request from criteria {}", criteria);
+            return null;
+        }
+        final ClassicHttpRequest httpRequest = initializeHttpRequest(criteria, request);
+        if (httpRequest == null) {
+            log.debug("Could not initialize HTTP request from {}", request);
+            return null;
+        }
+        final HttpClientContext httpContext = buildHttpContext(httpRequest);
+        try (final ClassicHttpResponse response = httpClient.executeOpen(null, httpRequest, httpContext)) {
+            final String scheme = httpRequest.getUri().getScheme();
+            assert scheme != null;
+            HttpClientSecuritySupport.checkTLSCredentialEvaluated(httpContext, scheme);
+            return parseHttpResponse(criteria, request, response);
+        } catch (final ProtocolException | URISyntaxException | IOException e) {
+            log.debug("Unable to fetch resolve entity response from URI: {}", request.getEndpointURI(), e);
+        }
+
+        return null;
+    }
+
+    /**
+     * Build the {@link HttpClientContext} instance to be used with {@link #httpClient}.
+     * 
+     * @param request the HTTP client request
+     * @return the client context instance
+     */
+    @Nonnull
+    protected HttpClientContext buildHttpContext(@Nonnull final ClassicHttpRequest request) {
+        final HttpClientContext clientContext = HttpClientContext.create();
+        assert clientContext != null;
+        HttpClientSecuritySupport.marshalSecurityParameters(clientContext, httpClientSecurityParameters, false);
+        HttpClientSecuritySupport.addDefaultTLSTrustEngineCriteria(clientContext, request);
+        return clientContext;
+    }
+
+    /**
+     * Initializes the {@link ClassicHttpRequest} to be used with {@link #httpClient}.
+     * 
+     * @param criteria criteria set
+     * @param request Nimbus request message
+     * @return the request to be used with {@link #httpClient}
+     */
+    @Nullable protected abstract ClassicHttpRequest initializeHttpRequest(@Nonnull final CriteriaSet criteria,
+            @Nonnull R request);
+
+    /**
+     * Parses the response message container to be stored in the metadata cache.
+     * 
+     * @param criteria criteria set
+     * @param request Nimbus request message
+     * @param response the HTTP response obtained from {@link #httpClient}
+     * @return the response message container
+     * @throws ProtocolException if HTTP protocol violation occurs
+     * @throws IOException if generic I/O exception occurs
+     */
+    @Nullable protected abstract C parseHttpResponse(@Nonnull final CriteriaSet criteria, @Nonnull final R request,
+            @Nullable final ClassicHttpResponse response) throws ProtocolException, IOException;
+
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultEntityConfigurationResponseFetchingStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultEntityConfigurationResponseFetchingStrategy.java
index 1862386..53e0d30 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultEntityConfigurationResponseFetchingStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultEntityConfigurationResponseFetchingStrategy.java
@@ -14,6 +14,7 @@
 
 package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata;
 
+import java.time.Instant;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
@@ -45,8 +46,8 @@ public class DefaultEntityConfigurationResponseFetchingStrategy extends Abstract
         }
         final NimbusResponseCriterion responseCriterion = criteria.get(NimbusResponseCriterion.class);
         if (responseCriterion == null) {
-            log.debug("No response criterion given, returning null");
-            return null;
+            log.debug("No response criterion given, returning container with a null response expiring now");
+            return new NimbusResponseContainer(null, Instant.now());
         }
         final ResponseContainerExpirationCriterion expirationCriterion =
                 criteria.get(ResponseContainerExpirationCriterion.class);
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultResolveEntityResponseFetchingStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultResolveEntityResponseFetchingStrategy.java
index 70ba1cc..0cbbd0b 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultResolveEntityResponseFetchingStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultResolveEntityResponseFetchingStrategy.java
@@ -14,6 +14,7 @@
 
 package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata;
 
+import java.time.Instant;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
@@ -27,7 +28,8 @@ import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.resolver.CriteriaSet;
 
 /**
- * Default strategy for fetching resolve entity response container via criteria.
+ * Default strategy for fetching resolve entity response container via criteria. If no {@link NimbusResponseCriterion}
+ * is included in the given criteria, a container with a null response expiring now is returned.
  */
 @ThreadSafeAfterInit
 public class DefaultResolveEntityResponseFetchingStrategy extends AbstractIdentifiableInitializableComponent
@@ -50,8 +52,8 @@ public class DefaultResolveEntityResponseFetchingStrategy extends AbstractIdenti
         }
         final NimbusResponseCriterion responseCriterion = criteria.get(NimbusResponseCriterion.class);
         if (responseCriterion == null) {
-            log.debug("No response criterion given, returning null");
-            return null;
+            log.debug("No response criterion given, returning container with a null response expiring now");
+            return new ResolveEntityResponseContainer(null, requestCriterion.getRequest(), Instant.now());
         }
         final ResponseContainerExpirationCriterion expirationCriterion =
                 criteria.get(ResponseContainerExpirationCriterion.class);
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultResolveEntityTrustChainFetchingStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultResolveEntityTrustChainFetchingStrategy.java
index 9cb323c..b3c2d3b 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultResolveEntityTrustChainFetchingStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultResolveEntityTrustChainFetchingStrategy.java
@@ -20,15 +20,11 @@ import java.net.URISyntaxException;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import org.apache.hc.client5.http.classic.HttpClient;
 import org.apache.hc.client5.http.classic.methods.HttpGet;
-import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.apache.hc.core5.http.ClassicHttpResponse;
 import org.apache.hc.core5.http.ContentType;
@@ -38,126 +34,44 @@ import org.apache.hc.core5.http.ProtocolException;
 import org.apache.hc.core5.http.io.entity.EntityUtils;
 import org.apache.hc.core5.http.message.BasicNameValuePair;
 import org.apache.hc.core5.net.URIBuilder;
-import org.opensaml.security.httpclient.HttpClientSecurityParameters;
-import org.opensaml.security.httpclient.HttpClientSecuritySupport;
 import org.slf4j.Logger;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.nimbusds.oauth2.sdk.ParseException;
 import com.nimbusds.oauth2.sdk.http.HTTPResponse;
 
 import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ResolveEntityRequest;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ResolveEntityResponse;
-import net.shibboleth.oidc.metadata.cache.MetadataCache;
-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;
 
 /**
- * Default strategy for fetching trust chains via resolve entity API for an entity specified in the criteria set.
- * Caches for entity configurations and subordinate statements are exploited for actual fetching of the entity
- * statements. The entity configuration may also be delivered via {@link SubjectEntityStatementCriterion} in the
- * criteria set.
+ * Default strategy for fetching trust chain data via resolve entity API for a request specified in the criteria set.
  */
 @ThreadSafeAfterInit
-public class DefaultResolveEntityTrustChainFetchingStrategy extends AbstractIdentifiableInitializableComponent
-        implements Function<CriteriaSet, ResolveEntityResponseContainer> {
+public class DefaultResolveEntityTrustChainFetchingStrategy
+    extends AbstractFederationEndpointResponseFetchingStrategy<ResolveEntityRequest, ResolveEntityResponseContainer> {
 
     /** Class logger. */
     @Nonnull private Logger log = LoggerFactory.getLogger(DefaultResolveEntityTrustChainFetchingStrategy.class);
 
-    /** Cache containing local copies of trusted trust anchor keys. */
-    @NonnullAfterInit private MetadataCache<Map<String, LocalKeyContainer>> localTrustAnchorsCache;
-
-    /** HTTP client to use. */
-    @NonnullAfterInit protected HttpClient httpClient;
-
-    /** HTTP client security parameters. */
-    @Nullable private HttpClientSecurityParameters httpClientSecurityParameters;
-
-    /** JSON object mapper used for decoding JSON into Map. */
-    @NonnullAfterInit private ObjectMapper objectMapper;
-
-    /**
-     * Set the cache containing local copies of trusted trust anchor keys.
-     * 
-     * @param cache cache containing local copies of trusted trust anchor keys.
-     */
-    public void setLocalTrustAnchorsCache(@Nonnull final MetadataCache<Map<String, LocalKeyContainer>> cache) {
-        checkSetterPreconditions();
-        localTrustAnchorsCache = Constraint.isNotNull(cache, "Local Trust Anchor cache cannot be null");
-    }
-
-    /**
-     * Set the {@link HttpClient} to use.
-     * 
-     * @param client HTTP client to use
-     */
-    public void setHttpClient(@Nonnull final HttpClient client) {
-        checkSetterPreconditions();
-        httpClient = Constraint.isNotNull(client, "HttpClient cannot be null");
-    }
-
-    /**
-     * Set the optional client security parameters.
-     * 
-     * @param params the new client security parameters
-     */
-    public void setHttpClientSecurityParameters(@Nullable final HttpClientSecurityParameters params) {
-        checkSetterPreconditions();
-        httpClientSecurityParameters = params;
-    }
     /**
-
-     * Set the JSON {@link ObjectMapper} used for decoding JSON into Map.
-     * 
-     * @param mapper object mapper
+     * Constructor.
      */
-    public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
-        checkSetterPreconditions();
-
-        objectMapper = Constraint.isNotNull(mapper, "Object mapper cannot be null");
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected void doInitialize() throws ComponentInitializationException {
-        super.doInitialize();
-        if (localTrustAnchorsCache == null) {
-            throw new ComponentInitializationException("Local Trust Anchor cache cannot be null");
-        }
-        if (httpClient == null) {
-            throw new ComponentInitializationException("Httpclient cannot be null");
-        }
-        if (objectMapper == null) {
-            throw new ComponentInitializationException("Object mapper cannot be null");
-        }
+    public DefaultResolveEntityTrustChainFetchingStrategy() {
+        setCriteriaToRequestStrategy(criteria -> {
+            final ResolveEntityRequestCriterion requestCriterion = criteria.get(ResolveEntityRequestCriterion.class);
+            if (requestCriterion == null) {
+                log.debug("No request criterion given, returning null");
+                return null;
+            }
+            return requestCriterion.getRequest();
+        });
     }
 
     /** {@inheritDoc} */
-    @Override @Nullable
-    public ResolveEntityResponseContainer apply(@Nullable final CriteriaSet criteria) {
-        checkComponentActive();
-        if (criteria == null) {
-            return null;
-        }
-
-        final ResolveEntityRequestCriterion requestCriterion = criteria.get(ResolveEntityRequestCriterion.class);
-        if (requestCriterion == null) {
-            log.debug("No request criterion given, returning null");
-            return null;
-        }
-        final ResponseContainerExpirationCriterion expirationCriterion =
-                criteria.get(ResponseContainerExpirationCriterion.class);
-        if (expirationCriterion == null) {
-            log.debug("No expiration criterion given, returning null");
-            return null;
-        }
-        final ResolveEntityRequest resolveRequest = requestCriterion.getRequest();
+    @Nullable protected ClassicHttpRequest initializeHttpRequest(@Nonnull final CriteriaSet criteria,
+            @Nonnull ResolveEntityRequest resolveRequest) {
         final HttpGet httpRequest = new HttpGet(resolveRequest.getEndpointURI());
         final List<NameValuePair> nvps = new ArrayList<>();
         nvps.add(new BasicNameValuePair("sub", resolveRequest.getSubject()));
@@ -175,45 +89,34 @@ public class DefaultResolveEntityTrustChainFetchingStrategy extends AbstractIden
         log.debug("Using URI {} for fetching resolve entity API response", uri);
         httpRequest.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED);
         httpRequest.setUri(uri);
+        return httpRequest;
+    }
 
-        final HttpClientContext httpContext = buildHttpContext(httpRequest);
-        try (final ClassicHttpResponse response = httpClient.executeOpen(null, httpRequest, httpContext)) {
-            final String scheme = httpRequest.getUri().getScheme();
-            assert scheme != null;
-            HttpClientSecuritySupport.checkTLSCredentialEvaluated(httpContext, scheme);
-            if (response != null) {
-                final HTTPResponse nimbusResponse = new HTTPResponse(response.getCode());
-                nimbusResponse.setContent(EntityUtils.toString(response.getEntity()));
+    /** {@inheritDoc} */
+    @Nullable protected ResolveEntityResponseContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
+            @Nonnull final ResolveEntityRequest request, @Nullable final ClassicHttpResponse response)
+                    throws ProtocolException, IOException {
+        final ResponseContainerExpirationCriterion expirationCriterion =
+                criteria.get(ResponseContainerExpirationCriterion.class);
+        if (expirationCriterion == null) {
+            log.debug("No expiration criterion given, returning null");
+            return null;
+        }
+        if (response != null) {
+            final HTTPResponse nimbusResponse = new HTTPResponse(response.getCode());
+            nimbusResponse.setContent(EntityUtils.toString(response.getEntity()));
+            try {
                 nimbusResponse.setContentType(response.getEntity().getContentType());
                 final ResolveEntityResponse resolveResponse = ResolveEntityResponse.parse(nimbusResponse);
                 final Instant expirationTime = expirationCriterion.getExpirationInstant();
-                return new ResolveEntityResponseContainer(resolveResponse, resolveRequest, expirationTime);
-            } else {
-                log.debug("Unable to fetch resolve entity response from URI: {} (null response)", uri);
-                //TODO: cache non-success results?
-                return null;
+                return new ResolveEntityResponseContainer(resolveResponse, request, expirationTime);
+            } catch (final ParseException e) {
+                log.warn("Could not parse resolve entity response from URI: {}", request.getEndpointURI(), e);
             }
-        } catch (final ProtocolException | URISyntaxException | IOException | ParseException e) {
-            log.debug("Unable to fetch resolve entity response from URI: {}", uri, e);
+        } else {
+            log.debug("Unable to fetch resolve entity response from URI: {} (null response)", request.getEndpointURI());
         }
 
         return null;
-
     } 
-
-    /**
-     * Build the {@link HttpClientContext} instance to be used by the HttpClient.
-     * 
-     * @param request the HTTP client request
-     * @return the client context instance
-     */
-    @Nonnull
-    protected HttpClientContext buildHttpContext(@Nonnull final ClassicHttpRequest request) {
-        final HttpClientContext clientContext = HttpClientContext.create();
-        assert clientContext != null;
-        HttpClientSecuritySupport.marshalSecurityParameters(clientContext, httpClientSecurityParameters, false);
-        HttpClientSecuritySupport.addDefaultTLSTrustEngineCriteria(clientContext, request);
-        return clientContext;
-    }
-
 }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustMarkStatusFetchingStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustMarkStatusFetchingStrategy.java
new file mode 100644
index 0000000..55184cc
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustMarkStatusFetchingStrategy.java
@@ -0,0 +1,113 @@
+/*
+ * 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.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
+import org.slf4j.Logger;
+
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.http.HTTPResponse;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.TrustMarkStatusRequest;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.TrustMarkStatusResponse;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Default strategy for fetching trust mark status via Trust Mark STatus API for the request specified in the criteria
+ * set. The status response is stored inside a {@link TrustMarkStatusResponseContainer}.
+ */
+ at ThreadSafeAfterInit
+public class DefaultTrustMarkStatusFetchingStrategy
+    extends AbstractFederationEndpointResponseFetchingStrategy<TrustMarkStatusRequest, TrustMarkStatusResponseContainer> {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(DefaultTrustMarkStatusFetchingStrategy.class);
+
+    /**
+     * Constructor.
+     */
+    public DefaultTrustMarkStatusFetchingStrategy() {
+        setCriteriaToRequestStrategy(criteria -> {
+            final TrustMarkStatusRequestCriterion requestCriterion =
+                    criteria.get(TrustMarkStatusRequestCriterion.class);
+            if (requestCriterion == null) {
+                log.debug("No request criterion given, returning null");
+                return null;
+            }
+            return requestCriterion.getRequest();
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Nullable protected ClassicHttpRequest initializeHttpRequest(@Nonnull final CriteriaSet criteria,
+            @Nonnull TrustMarkStatusRequest request) {
+        final HttpPost httpRequest = new HttpPost(request.getEndpointURI());
+        final List<NameValuePair> nvps = new ArrayList<>();
+        nvps.add(new BasicNameValuePair("trust_mark", request.getTrustMark().serialize()));
+        final UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(nvps, StandardCharsets.UTF_8);
+        httpRequest.setEntity(urlEncodedFormEntity);
+        httpRequest.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED);
+
+        return httpRequest;
+    }
+
+    /** {@inheritDoc} */
+    @Nullable protected TrustMarkStatusResponseContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
+            @Nonnull final TrustMarkStatusRequest request, @Nullable final ClassicHttpResponse response)
+                    throws ProtocolException, IOException {
+        final ResponseContainerExpirationCriterion expirationCriterion =
+                criteria.get(ResponseContainerExpirationCriterion.class);
+        if (expirationCriterion == null) {
+            log.debug("No expiration criterion given, returning null");
+            return null;
+        }
+        if (response != null) {
+            final HTTPResponse nimbusResponse = new HTTPResponse(response.getCode());
+            nimbusResponse.setContent(EntityUtils.toString(response.getEntity()));
+            try {
+                nimbusResponse.setContentType(response.getEntity().getContentType());
+                final TrustMarkStatusResponse statusResponse = TrustMarkStatusResponse.parse(nimbusResponse);
+                final Instant expirationTime = expirationCriterion.getExpirationInstant();
+                return new TrustMarkStatusResponseContainer(statusResponse, request.getTrustMark(), expirationTime);
+            } catch (final ParseException e) {
+                log.warn("Could not parse resolve entity response from URI: {}", request.getEndpointURI(), e);
+            }
+        } else {
+            log.debug("Unable to fetch resolve entity response from URI: {} (null response)", request.getEndpointURI());
+        }
+
+        return null;
+    }
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustMarkStatusResponseSignatureValidationFilterStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustMarkStatusResponseSignatureValidationFilterStrategy.java
new file mode 100644
index 0000000..bb63f0d
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultTrustMarkStatusResponseSignatureValidationFilterStrategy.java
@@ -0,0 +1,128 @@
+/*
+ * 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.text.ParseException;
+import java.util.List;
+import java.util.function.BiFunction;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+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.idp.plugin.oidc.op.oidfed.messaging.impl.TrustMarkStatusResponse;
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+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.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Default signature validating filter for Trust Mark Status response. The signature validation is performed via
+ * configurable {@link TrustEngine}.
+ */
+ at ThreadSafeAfterInit
+public class DefaultTrustMarkStatusResponseSignatureValidationFilterStrategy
+    extends AbstractTrustEngineSignatureValidationComponent
+    implements BiFunction<TrustMarkStatusResponseContainer, MetadataFilterContext, TrustMarkStatusResponseContainer> {
+
+    /** Class logger. */
+    @Nonnull private Logger log =
+            LoggerFactory.getLogger(DefaultTrustMarkStatusResponseSignatureValidationFilterStrategy.class);
+
+    /** Cache used to fetch the issuer entity configuration from. */
+    @NonnullAfterInit private MetadataCache<List<EntityStatement>> trustChainCache;
+
+    /**
+     * Set the cache used to fetch the trust chain for the trust mark issuer from.
+     * 
+     * @param cache cache used to fetch the trust chain for the trust mark issuer from
+     */
+    public void setTrustChainCache(@Nonnull final MetadataCache<List<EntityStatement>> cache) {
+        checkSetterPreconditions();
+        trustChainCache = Constraint.isNotNull(cache, "Trust Chain cache cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        if (trustChainCache == null) {
+            throw new ComponentInitializationException("Trust Chain cache cannot be null");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nullable
+    public TrustMarkStatusResponseContainer apply(@Nullable final TrustMarkStatusResponseContainer response,
+            @Nullable final MetadataFilterContext filterContext) {
+        checkComponentActive();
+        if (response == null) {
+            return null;
+        }
+        if (response.getResponse() instanceof TrustMarkStatusResponse successResponse) {
+            final SignedJWT jwt = successResponse.getJWT();
+            final String entityId;
+            try {
+                entityId = jwt.getJWTClaimsSet().getIssuer();
+            } catch (final ParseException e) {
+                log.error("Could not parse issuer entity ID value from the response", e);
+                return null;
+            }
+            final EntityStatement issuerStatement = fetchIssuerStatement(entityId);
+            if (issuerStatement == null) {
+                return null;
+            }
+            log.trace("Starting signature validation of success response from {}", entityId);
+            final CriteriaSet criteria = new CriteriaSet(new SubjectEntityStatementCriterion(issuerStatement));
+            if (validateJwt(jwt, criteria, entityId)) {
+                return response;
+            }
+        } else {
+            log.trace("Ignoring signature validation for the error response");
+            return response;
+        }
+        return null;
+    }
+
+    /**
+     * Fetch the issuer entity configuration from the trust chain metadata cache.
+     * 
+     * @param issuer the issuer entity ID
+     * @return the issuer entity configuration, or null if could not be fetched
+     */
+    @Nullable protected EntityStatement fetchIssuerStatement(@Nonnull final String issuer) {
+        final CriteriaSet criteria = new CriteriaSet(new SubjectEntityIDCriterion(issuer));
+        try {
+            final List<List<EntityStatement>> result = trustChainCache.get(criteria);
+            if (!result.isEmpty() && !result.get(0).isEmpty()) {
+                return result.get(0).get(0);
+            }
+        } catch (final MetadataCacheException e) {
+            log.debug("Error while fetching issuer trust chain for {}", issuer, e);
+        }
+        log.warn("Could not fetch entity configuration for {}", issuer);
+        return null;
+    }
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/NimbusResponseContainer.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/NimbusResponseContainer.java
index 9928613..188c501 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/NimbusResponseContainer.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/NimbusResponseContainer.java
@@ -18,6 +18,7 @@ import java.io.Serializable;
 import java.time.Instant;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import com.nimbusds.oauth2.sdk.Response;
 
@@ -32,7 +33,7 @@ public class NimbusResponseContainer implements Serializable {
     private static final long serialVersionUID = 656269369356865370L;
 
     /** Response message. */
-    @Nonnull private final Response response;
+    @Nullable private final Response response;
 
     /** Expiration instant for this container. */
     @Nonnull private final Instant expiration;
@@ -43,9 +44,9 @@ public class NimbusResponseContainer implements Serializable {
      * @param responseMessage response message
      * @param expirationInstant expiration instant
      */
-    public NimbusResponseContainer(@Nonnull final Response responseMessage,
+    public NimbusResponseContainer(@Nullable final Response responseMessage,
             @Nonnull final Instant expirationInstant) {
-        response = Constraint.isNotNull(responseMessage, "Response message cannot be null");
+        response = responseMessage;
         expiration = Constraint.isNotNull(expirationInstant, "Expiration instant cannot be null");
     }
 
@@ -54,7 +55,7 @@ public class NimbusResponseContainer implements Serializable {
      * 
      * @return response message
      */
-    public Response getResponse() {
+    @Nullable public Response getResponse() {
         return response;
     }
 
@@ -63,7 +64,7 @@ public class NimbusResponseContainer implements Serializable {
      * 
      * @return expiration instant
      */
-    public Instant getExpirationInstant() {
+    @Nonnull public Instant getExpirationInstant() {
         return expiration;
     }
 
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponseContainer.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponseContainer.java
index 103a436..edeabe8 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponseContainer.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponseContainer.java
@@ -18,6 +18,7 @@ import java.io.Serializable;
 import java.time.Instant;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import com.nimbusds.oauth2.sdk.Response;
 
@@ -42,7 +43,7 @@ public class ResolveEntityResponseContainer extends NimbusResponseContainer impl
      * @param requestMessage request message
      * @param expirationInstant expiration instant
      */
-    public ResolveEntityResponseContainer(@Nonnull final Response responseMessage,
+    public ResolveEntityResponseContainer(@Nullable final Response responseMessage,
             @Nonnull final ResolveEntityRequest requestMessage, @Nonnull final Instant expirationInstant) {
         super(responseMessage, expirationInstant);
         request = Constraint.isNotNull(requestMessage, "Request message cannot be null");
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkCriterion.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkCriterion.java
new file mode 100644
index 0000000..3bc1f5b
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkCriterion.java
@@ -0,0 +1,79 @@
+/*
+ * 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.Objects;
+
+import javax.annotation.Nonnull;
+
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing a trust mark.
+ */
+public class TrustMarkCriterion implements Criterion {
+
+    /** The trust mark value. */
+    @Nonnull final SignedJWT value;
+
+    /**
+     * Constructor.
+     *
+     * @param jwt the trust mark value, must not be null
+     */
+    public TrustMarkCriterion(@Nonnull final SignedJWT jwt) {
+        value = Constraint.isNotNull(jwt, "Trust Mark cannot be null");
+    }
+
+    /**
+     * Get the trust mark value.
+     * 
+     * @return the trust mar value
+     */
+    @Nonnull public SignedJWT getValue() {
+        return value;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "TrustMarkCriterion [value=" + value.serialize() + "]";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return Objects.hash(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final TrustMarkCriterion other = (TrustMarkCriterion) obj;
+        return value.serialize().equals(other.value.serialize());
+    }
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkStatusRequestCriterion.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkStatusRequestCriterion.java
new file mode 100644
index 0000000..f33a060
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkStatusRequestCriterion.java
@@ -0,0 +1,80 @@
+/*
+ * 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.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.TrustMarkStatusRequest;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing request message to a Trust Mark Status API.
+ */
+public class TrustMarkStatusRequestCriterion implements Criterion {
+
+    /** The request message. */
+    @Nonnull private final TrustMarkStatusRequest request;
+
+    /**
+     * Constructor.
+     *
+     * @param requestMessage request message, must not be null
+     */
+    public TrustMarkStatusRequestCriterion(@Nonnull final TrustMarkStatusRequest requestMessage) {
+        request = Constraint.isNotNull(requestMessage, "Request cannot be null");
+    }
+
+    /**
+     * Get the request message.
+     * 
+     * @return the request message
+     */
+    @Nonnull public TrustMarkStatusRequest getRequest() {
+        return request;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "TrustMarkStatusRequestCriterion [request=" + request + "]";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return Objects.hash(request);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final TrustMarkStatusRequestCriterion other = (TrustMarkStatusRequestCriterion) obj;
+        //TODO: proper equals-check
+        return request.equals(other.request);
+    }
+
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponseContainer.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkStatusResponseContainer.java
similarity index 58%
copy from idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponseContainer.java
copy to idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkStatusResponseContainer.java
index 103a436..3a2652a 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponseContainer.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/TrustMarkStatusResponseContainer.java
@@ -19,33 +19,33 @@ import java.time.Instant;
 
 import javax.annotation.Nonnull;
 
+import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.Response;
 
-import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ResolveEntityRequest;
 import net.shibboleth.shared.logic.Constraint;
 
 /**
- * A container class for metadata caches carrying request and response message details related to Resolve Entity API.
+ * A container class for metadata caches carrying request and response message details related to Trust Mark Status API.
  */
-public class ResolveEntityResponseContainer extends NimbusResponseContainer implements Serializable {
+public class TrustMarkStatusResponseContainer extends NimbusResponseContainer implements Serializable {
 
     /** Serial version UID. */
-    private static final long serialVersionUID = 756269369356865370L;
+    private static final long serialVersionUID = 756269383556865370L;
 
-    /** Request message. */
-    @Nonnull private final ResolveEntityRequest request;
+    /** Trust Mark. */
+    @Nonnull private final SignedJWT trustMark;
 
     /**
      * Constructor.
      *
      * @param responseMessage response message
-     * @param requestMessage request message
+     * @param jwt trust mark
      * @param expirationInstant expiration instant
      */
-    public ResolveEntityResponseContainer(@Nonnull final Response responseMessage,
-            @Nonnull final ResolveEntityRequest requestMessage, @Nonnull final Instant expirationInstant) {
+    public TrustMarkStatusResponseContainer(@Nonnull final Response responseMessage,
+            @Nonnull final SignedJWT jwt, @Nonnull final Instant expirationInstant) {
         super(responseMessage, expirationInstant);
-        request = Constraint.isNotNull(requestMessage, "Request message cannot be null");
+        trustMark = Constraint.isNotNull(jwt, "Trust Mark JWT cannot be null");
     }
 
     /**
@@ -53,8 +53,17 @@ public class ResolveEntityResponseContainer extends NimbusResponseContainer impl
      * 
      * @return request message
      */
-    public ResolveEntityRequest getRequest() {
-        return request;
+    public SignedJWT getTrustMark() {
+        return trustMark;
+    }
+
+    /**
+     * Get response message.
+     * 
+     * @return response message
+     */
+    @Nonnull public Response getResponse() {
+        return super.getResponse();
     }
 
 }
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 020249d..ea4b2d8 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -255,7 +255,6 @@
             <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultResolveEntityTrustChainFetchingStrategy"
                 p:httpClient="#{getObject('shibboleth.oidfed.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
                 p:httpClientSecurityParameters="#{getObject('shibboleth.oidfed.NonBrowser.HttpClientSecurityParameters')}"
-                p:localTrustAnchorsCache-ref="shibboleth.oidfed.LocalTrustAnchorsMetadataCache"
                 p:objectMapper-ref="shibboleth.oidfed.policy.JSONObjectMapper"/>
         </property>
     </bean>
@@ -305,6 +304,30 @@
         </property>
     </bean>
 
+    <bean id="shibboleth.oidfed.TrustMarkStatusMetadataCacheBuilderSpec"
+        class="net.shibboleth.oidc.metadata.cache.impl.DynamicMetadataCacheBuilderSpec"
+        p:metadataExpirationTimeStrategy-ref="shibboleth.oidfed.DefaultEntityStatementExpirationTimeStrategy"
+        p:minCacheDuration="%{idp.oidfed.cache.trustMarkStatus.maxRefreshDelay:PT60S}"
+        p:maxCacheDuration="%{idp.oidfed.cache.trustMarkStatus.maxRefreshDelay:PT30M}">
+        <property name="identifierExtractionStrategy">
+            <bean parent="shibboleth.Functions.Expression" c:expression="#input?.getRequest().getTrustMark().serialize()"/>
+        </property>
+        <property name="criteriaToIdentifierStrategy">
+            <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.TrustMarkRequestCriterion))?.getRequest().getTrustMark().serialize()"/>
+        </property>
+        <property name="metadataFilterStrategy">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustMarkStatusResponseSignatureValidationFilterStrategy"
+                p:trustChainCache-ref="shibboleth.oidfed.TrustChainMetadataCache"
+                p:trustEngine-ref="shibboleth.oidfed.DefaultEntityConfigurationTrustEngine"/>
+        </property>
+        <property name="fetchStrategy">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultTrustMarkStatusFetchingStrategy"
+                p:httpClient="#{getObject('shibboleth.oidfed.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
+                p:httpClientSecurityParameters="#{getObject('shibboleth.oidfed.NonBrowser.HttpClientSecurityParameters')}"
+                p:objectMapper-ref="shibboleth.oidfed.policy.JSONObjectMapper"/>
+        </property>
+    </bean>
+
     <bean id="shibboleth.oidfed.DefaultDelegatedTrustMarkTrustEngine"
         class="net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine">
         <constructor-arg index="0">

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


More information about the commits mailing list